From 957e5018f4445cd83de01e3da07717f7afa23909 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 4 Dec 2019 22:52:58 -0800 Subject: [PATCH 001/677] Version bump to 0.104.0dev0 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index fab722ea5e6..dc937f81482 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,6 +1,6 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 -MINOR_VERSION = 103 +MINOR_VERSION = 104 PATCH_VERSION = "0.dev0" __short_version__ = "{}.{}".format(MAJOR_VERSION, MINOR_VERSION) __version__ = "{}.{}".format(__short_version__, PATCH_VERSION) From e99184bf68cf4d475624d4b77eccca74ae5fa267 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 5 Dec 2019 00:28:56 -0800 Subject: [PATCH 002/677] Install requirements of after_dependencies when loading integrations (#29491) * Install requirements of after_dependencies when loading integrations * Fix smartthings test --- homeassistant/components/auth/manifest.json | 9 +++----- homeassistant/components/camera/manifest.json | 7 +----- homeassistant/components/cast/manifest.json | 1 + .../components/google_assistant/manifest.json | 1 + .../components/mobile_app/http_api.py | 13 +++++------ .../components/mobile_app/manifest.json | 1 + .../components/mobile_app/webhook.py | 9 ++++---- .../components/mobile_app/websocket_api.py | 2 +- .../components/onboarding/manifest.json | 9 ++------ .../components/owntracks/__init__.py | 2 +- .../components/owntracks/config_flow.py | 5 +---- .../components/owntracks/manifest.json | 12 +++------- homeassistant/components/proxy/camera.py | 22 +++++++++++-------- homeassistant/components/proxy/manifest.json | 4 +--- .../components/smartthings/manifest.json | 14 ++++-------- .../components/smartthings/smartapp.py | 18 +++------------ homeassistant/setup.py | 4 ++-- script/hassfest/dependencies.py | 13 +++++------ .../smartthings/test_config_flow.py | 9 ++++---- 19 files changed, 57 insertions(+), 98 deletions(-) diff --git a/homeassistant/components/auth/manifest.json b/homeassistant/components/auth/manifest.json index 1b0ab33f381..2f3e724b583 100644 --- a/homeassistant/components/auth/manifest.json +++ b/homeassistant/components/auth/manifest.json @@ -3,10 +3,7 @@ "name": "Auth", "documentation": "https://www.home-assistant.io/integrations/auth", "requirements": [], - "dependencies": [ - "http" - ], - "codeowners": [ - "@home-assistant/core" - ] + "dependencies": ["http"], + "after_dependencies": ["onboarding"], + "codeowners": ["@home-assistant/core"] } diff --git a/homeassistant/components/camera/manifest.json b/homeassistant/components/camera/manifest.json index a3395965e4f..1bd4bb7caeb 100644 --- a/homeassistant/components/camera/manifest.json +++ b/homeassistant/components/camera/manifest.json @@ -3,11 +3,6 @@ "name": "Camera", "documentation": "https://www.home-assistant.io/integrations/camera", "requirements": [], - "dependencies": [ - "http" - ], - "after_dependencies": [ - "stream" - ], + "dependencies": ["http"], "codeowners": [] } diff --git a/homeassistant/components/cast/manifest.json b/homeassistant/components/cast/manifest.json index b6776a17f7c..8ad6f8fdb8d 100644 --- a/homeassistant/components/cast/manifest.json +++ b/homeassistant/components/cast/manifest.json @@ -5,6 +5,7 @@ "documentation": "https://www.home-assistant.io/integrations/cast", "requirements": ["pychromecast==4.0.1"], "dependencies": [], + "after_dependencies": ["cloud"], "zeroconf": ["_googlecast._tcp.local."], "codeowners": [] } diff --git a/homeassistant/components/google_assistant/manifest.json b/homeassistant/components/google_assistant/manifest.json index f97977a7400..94dd3b7f079 100644 --- a/homeassistant/components/google_assistant/manifest.json +++ b/homeassistant/components/google_assistant/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/google_assistant", "requirements": [], "dependencies": ["http"], + "after_dependencies": ["camera"], "codeowners": ["@home-assistant/cloud"] } diff --git a/homeassistant/components/mobile_app/http_api.py b/homeassistant/components/mobile_app/http_api.py index 11ca39e8b60..5be2d19789e 100644 --- a/homeassistant/components/mobile_app/http_api.py +++ b/homeassistant/components/mobile_app/http_api.py @@ -37,9 +37,7 @@ class RegistrationsView(HomeAssistantView): webhook_id = generate_secret() - cloud_loaded = "cloud" in hass.config.components - - if cloud_loaded and hass.components.cloud.async_active_subscription(): + if hass.components.cloud.async_active_subscription(): data[ CONF_CLOUDHOOK_URL ] = await hass.components.cloud.async_create_cloudhook(webhook_id) @@ -59,11 +57,10 @@ class RegistrationsView(HomeAssistantView): ) remote_ui_url = None - if cloud_loaded: - try: - remote_ui_url = hass.components.cloud.async_remote_ui_url() - except hass.components.cloud.CloudNotAvailable: - pass + try: + remote_ui_url = hass.components.cloud.async_remote_ui_url() + except hass.components.cloud.CloudNotAvailable: + pass return self.json( { diff --git a/homeassistant/components/mobile_app/manifest.json b/homeassistant/components/mobile_app/manifest.json index 29ee35e002c..230a60fdf25 100644 --- a/homeassistant/components/mobile_app/manifest.json +++ b/homeassistant/components/mobile_app/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/mobile_app", "requirements": ["PyNaCl==1.3.0"], "dependencies": ["http", "webhook"], + "after_dependencies": ["cloud"], "codeowners": ["@robbiet480"] } diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index 98687e6658f..46f17b401bc 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -309,10 +309,9 @@ async def handle_webhook( if CONF_CLOUDHOOK_URL in registration: resp[CONF_CLOUDHOOK_URL] = registration[CONF_CLOUDHOOK_URL] - if "cloud" in hass.config.components: - try: - resp[CONF_REMOTE_UI_URL] = hass.components.cloud.async_remote_ui_url() - except hass.components.cloud.CloudNotAvailable: - pass + try: + resp[CONF_REMOTE_UI_URL] = hass.components.cloud.async_remote_ui_url() + except hass.components.cloud.CloudNotAvailable: + pass return webhook_response(resp, registration=registration, headers=headers) diff --git a/homeassistant/components/mobile_app/websocket_api.py b/homeassistant/components/mobile_app/websocket_api.py index bc5305c36fa..a18e5247bfa 100644 --- a/homeassistant/components/mobile_app/websocket_api.py +++ b/homeassistant/components/mobile_app/websocket_api.py @@ -115,7 +115,7 @@ async def websocket_delete_registration( except HomeAssistantError: return error_message(msg["id"], "internal_error", "Error deleting registration") - if CONF_CLOUDHOOK_URL in registration and "cloud" in hass.config.components: + if CONF_CLOUDHOOK_URL in registration: await hass.components.cloud.async_delete_cloudhook(webhook_id) connection.send_message(result_message(msg["id"], "ok")) diff --git a/homeassistant/components/onboarding/manifest.json b/homeassistant/components/onboarding/manifest.json index 2febfc481e0..8e525ff0baa 100644 --- a/homeassistant/components/onboarding/manifest.json +++ b/homeassistant/components/onboarding/manifest.json @@ -3,11 +3,6 @@ "name": "Onboarding", "documentation": "https://www.home-assistant.io/integrations/onboarding", "requirements": [], - "dependencies": [ - "auth", - "http" - ], - "codeowners": [ - "@home-assistant/core" - ] + "dependencies": ["auth", "http", "person"], + "codeowners": ["@home-assistant/core"] } diff --git a/homeassistant/components/owntracks/__init__.py b/homeassistant/components/owntracks/__init__.py index d30e667f368..8556e8a7556 100644 --- a/homeassistant/components/owntracks/__init__.py +++ b/homeassistant/components/owntracks/__init__.py @@ -118,7 +118,7 @@ async def async_unload_entry(hass, entry): async def async_remove_entry(hass, entry): """Remove an OwnTracks config entry.""" - if not entry.data.get("cloudhook") or "cloud" not in hass.config.components: + if not entry.data.get("cloudhook"): return await hass.components.cloud.async_delete_cloudhook(entry.data[CONF_WEBHOOK_ID]) diff --git a/homeassistant/components/owntracks/config_flow.py b/homeassistant/components/owntracks/config_flow.py index ff4a649e0ce..1a8bb838e18 100644 --- a/homeassistant/components/owntracks/config_flow.py +++ b/homeassistant/components/owntracks/config_flow.py @@ -66,10 +66,7 @@ class OwnTracksFlow(config_entries.ConfigFlow, domain=DOMAIN): async def _get_webhook_id(self): """Generate webhook ID.""" webhook_id = self.hass.components.webhook.async_generate_id() - if ( - "cloud" in self.hass.config.components - and self.hass.components.cloud.async_active_subscription() - ): + if self.hass.components.cloud.async_active_subscription(): webhook_url = await self.hass.components.cloud.async_create_cloudhook( webhook_id ) diff --git a/homeassistant/components/owntracks/manifest.json b/homeassistant/components/owntracks/manifest.json index 529d7990a86..63fdfb94cf7 100644 --- a/homeassistant/components/owntracks/manifest.json +++ b/homeassistant/components/owntracks/manifest.json @@ -3,14 +3,8 @@ "name": "Owntracks", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/owntracks", - "requirements": [ - "PyNaCl==1.3.0" - ], - "dependencies": [ - "webhook" - ], - "after_dependencies": [ - "mqtt" - ], + "requirements": ["PyNaCl==1.3.0"], + "dependencies": ["webhook"], + "after_dependencies": ["mqtt", "cloud"], "codeowners": [] } diff --git a/homeassistant/components/proxy/camera.py b/homeassistant/components/proxy/camera.py index 90487120ffe..893fadfe178 100644 --- a/homeassistant/components/proxy/camera.py +++ b/homeassistant/components/proxy/camera.py @@ -7,7 +7,13 @@ import logging from PIL import Image import voluptuous as vol -from homeassistant.components.camera import PLATFORM_SCHEMA, Camera +from homeassistant.components.camera import ( + PLATFORM_SCHEMA, + Camera, + async_get_image, + async_get_mjpeg_stream, + async_get_still_stream, +) from homeassistant.const import CONF_ENTITY_ID, CONF_MODE, CONF_NAME from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv @@ -227,7 +233,7 @@ class ProxyCamera(Camera): return self._last_image self._last_image_time = now - image = await self.hass.components.camera.async_get_image(self._proxied_camera) + image = await async_get_image(self.hass, self._proxied_camera) if not image: _LOGGER.error("Error getting original camera image") return self._last_image @@ -247,12 +253,12 @@ class ProxyCamera(Camera): async def handle_async_mjpeg_stream(self, request): """Generate an HTTP MJPEG stream from camera images.""" if not self._stream_opts: - return await self.hass.components.camera.async_get_mjpeg_stream( - request, self._proxied_camera + return await async_get_mjpeg_stream( + self.hass, request, self._proxied_camera ) - return await self.hass.components.camera.async_get_still_stream( - request, self._async_stream_image, self.content_type, self.frame_interval + return await async_get_still_stream( + request, self._async_stream_image, self.content_type, self.frame_interval, ) @property @@ -263,9 +269,7 @@ class ProxyCamera(Camera): async def _async_stream_image(self): """Return a still image response from the camera.""" try: - image = await self.hass.components.camera.async_get_image( - self._proxied_camera - ) + image = await async_get_image(self.hass, self._proxied_camera) if not image: return None except HomeAssistantError: diff --git a/homeassistant/components/proxy/manifest.json b/homeassistant/components/proxy/manifest.json index 39f7b9064fc..5344ea6fe83 100644 --- a/homeassistant/components/proxy/manifest.json +++ b/homeassistant/components/proxy/manifest.json @@ -2,9 +2,7 @@ "domain": "proxy", "name": "Proxy", "documentation": "https://www.home-assistant.io/integrations/proxy", - "requirements": [ - "pillow==6.2.1" - ], + "requirements": ["pillow==6.2.1"], "dependencies": [], "codeowners": [] } diff --git a/homeassistant/components/smartthings/manifest.json b/homeassistant/components/smartthings/manifest.json index 8b5bf65afa1..0ab71382fad 100644 --- a/homeassistant/components/smartthings/manifest.json +++ b/homeassistant/components/smartthings/manifest.json @@ -3,14 +3,8 @@ "name": "Smartthings", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/smartthings", - "requirements": [ - "pysmartapp==0.3.2", - "pysmartthings==0.6.9" - ], - "dependencies": [ - "webhook" - ], - "codeowners": [ - "@andrewsayre" - ] + "requirements": ["pysmartapp==0.3.2", "pysmartthings==0.6.9"], + "dependencies": ["webhook"], + "after_dependencies": ["cloud"], + "codeowners": ["@andrewsayre"] } diff --git a/homeassistant/components/smartthings/smartapp.py b/homeassistant/components/smartthings/smartapp.py index 6acd29397ae..9b67df21491 100644 --- a/homeassistant/components/smartthings/smartapp.py +++ b/homeassistant/components/smartthings/smartapp.py @@ -88,10 +88,7 @@ async def validate_installed_app(api, installed_app_id: str): def validate_webhook_requirements(hass: HomeAssistantType) -> bool: """Ensure HASS is setup properly to receive webhooks.""" - if ( - "cloud" in hass.config.components - and hass.components.cloud.async_active_subscription() - ): + if hass.components.cloud.async_active_subscription(): return True if hass.data[DOMAIN][CONF_CLOUDHOOK_URL] is not None: return True @@ -105,11 +102,7 @@ def get_webhook_url(hass: HomeAssistantType) -> str: Return the cloudhook if available, otherwise local webhook. """ cloudhook_url = hass.data[DOMAIN][CONF_CLOUDHOOK_URL] - if ( - "cloud" in hass.config.components - and hass.components.cloud.async_active_subscription() - and cloudhook_url is not None - ): + if hass.components.cloud.async_active_subscription() and cloudhook_url is not None: return cloudhook_url return webhook.async_generate_url(hass, hass.data[DOMAIN][CONF_WEBHOOK_ID]) @@ -229,7 +222,6 @@ async def setup_smartapp_endpoint(hass: HomeAssistantType): cloudhook_url = config.get(CONF_CLOUDHOOK_URL) if ( cloudhook_url is None - and "cloud" in hass.config.components and hass.components.cloud.async_active_subscription() and not hass.config_entries.async_entries(DOMAIN) ): @@ -281,11 +273,7 @@ async def unload_smartapp_endpoint(hass: HomeAssistantType): return # Remove the cloudhook if it was created cloudhook_url = hass.data[DOMAIN][CONF_CLOUDHOOK_URL] - if ( - cloudhook_url - and "cloud" in hass.config.components - and hass.components.cloud.async_is_logged_in() - ): + if cloudhook_url and hass.components.cloud.async_is_logged_in(): await hass.components.cloud.async_delete_cloudhook( hass.data[DOMAIN][CONF_WEBHOOK_ID] ) diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 314938feeed..42296a4935d 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -288,8 +288,8 @@ async def async_process_deps_reqs( raise HomeAssistantError("Could not set up all dependencies.") if not hass.config.skip_pip and integration.requirements: - await requirements.async_process_requirements( - hass, integration.domain, integration.requirements + await requirements.async_get_integration_with_requirements( + hass, integration.domain ) processed.add(integration.domain) diff --git a/script/hassfest/dependencies.py b/script/hassfest/dependencies.py index e9933995715..e47deb76ad5 100644 --- a/script/hassfest/dependencies.py +++ b/script/hassfest/dependencies.py @@ -43,15 +43,12 @@ def validate_dependencies(integration: Integration): if referenced: for domain in sorted(referenced): - print( - "Warning: {} references integration {} but it's not a " - "dependency".format(integration.domain, domain) + integration.add_error( + "dependencies", + "Using component {} but it's not in 'dependencies' or 'after_dependencies'".format( + domain + ), ) - # Not enforced yet. - # integration.add_error( - # 'dependencies', - # "Using component {} but it's not a dependency".format(domain) - # ) def validate(integrations: Dict[str, Integration], config): diff --git a/tests/components/smartthings/test_config_flow.py b/tests/components/smartthings/test_config_flow.py index 521f1c6a6a8..82a24f38287 100644 --- a/tests/components/smartthings/test_config_flow.py +++ b/tests/components/smartthings/test_config_flow.py @@ -7,7 +7,6 @@ from pysmartthings import APIResponseError from homeassistant import data_entry_flow from homeassistant.setup import async_setup_component -from homeassistant.components import cloud from homeassistant.components.smartthings import smartapp from homeassistant.components.smartthings.config_flow import SmartThingsFlowHandler from homeassistant.components.smartthings.const import ( @@ -18,7 +17,7 @@ from homeassistant.components.smartthings.const import ( DOMAIN, ) -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, mock_coro async def test_step_user(hass): @@ -211,9 +210,11 @@ async def test_cloudhook_app_created_then_show_wait_form( await smartapp.unload_smartapp_endpoint(hass) with patch.object( - cloud, "async_active_subscription", return_value=True + hass.components.cloud, "async_active_subscription", return_value=True ), patch.object( - cloud, "async_create_cloudhook", return_value="http://cloud.test" + hass.components.cloud, + "async_create_cloudhook", + return_value=mock_coro("http://cloud.test"), ) as mock_create_cloudhook: await smartapp.setup_smartapp_endpoint(hass) From f6d1eb97a3390a75240c9665113fc786555f55ce Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Thu, 5 Dec 2019 09:56:17 +0100 Subject: [PATCH 003/677] Move imports to top for decora_wifi (#29439) --- homeassistant/components/decora_wifi/light.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/decora_wifi/light.py b/homeassistant/components/decora_wifi/light.py index 6171f65ef24..7d8aa104bb0 100644 --- a/homeassistant/components/decora_wifi/light.py +++ b/homeassistant/components/decora_wifi/light.py @@ -2,17 +2,22 @@ import logging +# pylint: disable=import-error +from decora_wifi import DecoraWiFiSession +from decora_wifi.models.person import Person +from decora_wifi.models.residence import Residence +from decora_wifi.models.residential_account import ResidentialAccount import voluptuous as vol from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_TRANSITION, - Light, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, SUPPORT_TRANSITION, + Light, ) -from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, EVENT_HOMEASSISTANT_STOP import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) @@ -28,11 +33,6 @@ NOTIFICATION_TITLE = "myLeviton Decora Setup" def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Decora WiFi platform.""" - # pylint: disable=import-error - from decora_wifi import DecoraWiFiSession - from decora_wifi.models.person import Person - from decora_wifi.models.residential_account import ResidentialAccount - from decora_wifi.models.residence import Residence email = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) From a050d5484754030b13e34be2fd636bcd4af91804 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 5 Dec 2019 01:15:28 -0800 Subject: [PATCH 004/677] Make hassfest stricter (#29494) * Make hassfest stricter * Update manifest.json --- .../components/ambiclimate/manifest.json | 10 +-- homeassistant/components/apns/manifest.json | 5 +- homeassistant/components/camera/manifest.json | 1 + homeassistant/components/cloud/manifest.json | 1 + .../components/doorbird/manifest.json | 10 +-- .../components/hikvisioncam/switch.py | 5 +- homeassistant/components/html5/manifest.json | 10 +-- .../components/logbook/manifest.json | 5 +- .../components/logi_circle/manifest.json | 2 +- homeassistant/components/melissa/climate.py | 19 +++--- .../components/nsw_fuel_station/sensor.py | 2 +- homeassistant/components/person/manifest.json | 1 + homeassistant/components/plant/manifest.json | 10 +-- homeassistant/components/point/manifest.json | 12 +--- homeassistant/components/rachio/manifest.json | 6 +- .../components/statistics/manifest.json | 5 +- .../components/telegram_bot/__init__.py | 5 +- homeassistant/components/tts/manifest.json | 13 ++-- homeassistant/components/wink/manifest.json | 7 +-- .../components/workday/binary_sensor.py | 3 +- .../components/worxlandroid/sensor.py | 2 +- script/hassfest/dependencies.py | 62 ++++++++++++++++++- 22 files changed, 112 insertions(+), 84 deletions(-) diff --git a/homeassistant/components/ambiclimate/manifest.json b/homeassistant/components/ambiclimate/manifest.json index 3d175165abd..151b761dff8 100644 --- a/homeassistant/components/ambiclimate/manifest.json +++ b/homeassistant/components/ambiclimate/manifest.json @@ -3,11 +3,7 @@ "name": "Ambiclimate", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/ambiclimate", - "requirements": [ - "ambiclimate==0.2.1" - ], - "dependencies": [], - "codeowners": [ - "@danielhiversen" - ] + "requirements": ["ambiclimate==0.2.1"], + "dependencies": ["http"], + "codeowners": ["@danielhiversen"] } diff --git a/homeassistant/components/apns/manifest.json b/homeassistant/components/apns/manifest.json index 4845c45a963..3c38238f7eb 100644 --- a/homeassistant/components/apns/manifest.json +++ b/homeassistant/components/apns/manifest.json @@ -2,9 +2,8 @@ "domain": "apns", "name": "Apns", "documentation": "https://www.home-assistant.io/integrations/apns", - "requirements": [ - "apns2==0.3.0" - ], + "requirements": ["apns2==0.3.0"], "dependencies": [], + "after_dependencies": ["device_tracker"], "codeowners": [] } diff --git a/homeassistant/components/camera/manifest.json b/homeassistant/components/camera/manifest.json index 1bd4bb7caeb..32cd7c3fe47 100644 --- a/homeassistant/components/camera/manifest.json +++ b/homeassistant/components/camera/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/camera", "requirements": [], "dependencies": ["http"], + "after_dependencies": ["stream", "media_player"], "codeowners": [] } diff --git a/homeassistant/components/cloud/manifest.json b/homeassistant/components/cloud/manifest.json index ec9a556af0a..accc4a0c0f9 100644 --- a/homeassistant/components/cloud/manifest.json +++ b/homeassistant/components/cloud/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/cloud", "requirements": ["hass-nabucasa==0.30"], "dependencies": ["http", "webhook"], + "after_dependencies": ["alexa", "google_assistant"], "codeowners": ["@home-assistant/cloud"] } diff --git a/homeassistant/components/doorbird/manifest.json b/homeassistant/components/doorbird/manifest.json index c9cdb32e18a..61225b86a44 100644 --- a/homeassistant/components/doorbird/manifest.json +++ b/homeassistant/components/doorbird/manifest.json @@ -2,11 +2,7 @@ "domain": "doorbird", "name": "Doorbird", "documentation": "https://www.home-assistant.io/integrations/doorbird", - "requirements": [ - "doorbirdpy==2.0.8" - ], - "dependencies": [], - "codeowners": [ - "@oblogic7" - ] + "requirements": ["doorbirdpy==2.0.8"], + "dependencies": ["http"], + "codeowners": ["@oblogic7"] } diff --git a/homeassistant/components/hikvisioncam/switch.py b/homeassistant/components/hikvisioncam/switch.py index 020b894c0f7..f86853a5468 100644 --- a/homeassistant/components/hikvisioncam/switch.py +++ b/homeassistant/components/hikvisioncam/switch.py @@ -5,7 +5,7 @@ import hikvision.api from hikvision.error import HikvisionError, MissingParamError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice from homeassistant.const import ( CONF_HOST, CONF_NAME, @@ -16,7 +16,6 @@ from homeassistant.const import ( STATE_ON, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import ToggleEntity # This is the last working version, please test before updating @@ -60,7 +59,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([HikvisionMotionSwitch(name, hikvision_cam)]) -class HikvisionMotionSwitch(ToggleEntity): +class HikvisionMotionSwitch(SwitchDevice): """Representation of a switch to toggle on/off motion detection.""" def __init__(self, name, hikvision_cam): diff --git a/homeassistant/components/html5/manifest.json b/homeassistant/components/html5/manifest.json index 667a5789182..dd794ae0386 100644 --- a/homeassistant/components/html5/manifest.json +++ b/homeassistant/components/html5/manifest.json @@ -2,11 +2,7 @@ "domain": "html5", "name": "HTML5 Notifications", "documentation": "https://www.home-assistant.io/integrations/html5", - "requirements": [ - "pywebpush==1.9.2" - ], - "dependencies": ["frontend"], - "codeowners": [ - "@robbiet480" - ] + "requirements": ["pywebpush==1.9.2"], + "dependencies": ["http"], + "codeowners": ["@robbiet480"] } diff --git a/homeassistant/components/logbook/manifest.json b/homeassistant/components/logbook/manifest.json index e8e3ad8ac2e..08083ea4024 100644 --- a/homeassistant/components/logbook/manifest.json +++ b/homeassistant/components/logbook/manifest.json @@ -3,9 +3,6 @@ "name": "Logbook", "documentation": "https://www.home-assistant.io/integrations/logbook", "requirements": [], - "dependencies": [ - "frontend", - "recorder" - ], + "dependencies": ["frontend", "http", "recorder"], "codeowners": [] } diff --git a/homeassistant/components/logi_circle/manifest.json b/homeassistant/components/logi_circle/manifest.json index 22502956e06..bd6dc8a8d27 100644 --- a/homeassistant/components/logi_circle/manifest.json +++ b/homeassistant/components/logi_circle/manifest.json @@ -4,6 +4,6 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/logi_circle", "requirements": ["logi_circle==0.2.2"], - "dependencies": ["ffmpeg"], + "dependencies": ["ffmpeg", "http"], "codeowners": ["@evanjd"] } diff --git a/homeassistant/components/melissa/climate.py b/homeassistant/components/melissa/climate.py index 38f4977c96a..c09203c3e33 100644 --- a/homeassistant/components/melissa/climate.py +++ b/homeassistant/components/melissa/climate.py @@ -11,8 +11,11 @@ from homeassistant.components.climate.const import ( HVAC_MODE_OFF, SUPPORT_FAN_MODE, SUPPORT_TARGET_TEMPERATURE, + FAN_AUTO, + FAN_HIGH, + FAN_MEDIUM, + FAN_LOW, ) -from homeassistant.components.fan import SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS from . import DATA_MELISSA @@ -29,7 +32,7 @@ OP_MODES = [ HVAC_MODE_OFF, ] -FAN_MODES = [HVAC_MODE_AUTO, SPEED_HIGH, SPEED_MEDIUM, SPEED_LOW] +FAN_MODES = [FAN_AUTO, FAN_HIGH, FAN_MEDIUM, FAN_LOW] async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): @@ -200,11 +203,11 @@ class MelissaClimate(ClimateDevice): if fan == self._api.FAN_AUTO: return HVAC_MODE_AUTO if fan == self._api.FAN_LOW: - return SPEED_LOW + return FAN_LOW if fan == self._api.FAN_MEDIUM: - return SPEED_MEDIUM + return FAN_MEDIUM if fan == self._api.FAN_HIGH: - return SPEED_HIGH + return FAN_HIGH _LOGGER.warning("Fan mode %s could not be mapped to hass", fan) return None @@ -224,10 +227,10 @@ class MelissaClimate(ClimateDevice): """Translate hass fan modes to melissa modes.""" if fan == HVAC_MODE_AUTO: return self._api.FAN_AUTO - if fan == SPEED_LOW: + if fan == FAN_LOW: return self._api.FAN_LOW - if fan == SPEED_MEDIUM: + if fan == FAN_MEDIUM: return self._api.FAN_MEDIUM - if fan == SPEED_HIGH: + if fan == FAN_HIGH: return self._api.FAN_HIGH _LOGGER.warning("Melissa have no setting for %s fan mode", fan) diff --git a/homeassistant/components/nsw_fuel_station/sensor.py b/homeassistant/components/nsw_fuel_station/sensor.py index a84aa554be9..3c900b46be0 100644 --- a/homeassistant/components/nsw_fuel_station/sensor.py +++ b/homeassistant/components/nsw_fuel_station/sensor.py @@ -6,7 +6,7 @@ from typing import Optional import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.light import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ATTR_ATTRIBUTION from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle diff --git a/homeassistant/components/person/manifest.json b/homeassistant/components/person/manifest.json index cf50b8029c2..afcd428d6af 100644 --- a/homeassistant/components/person/manifest.json +++ b/homeassistant/components/person/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/person", "requirements": [], "dependencies": [], + "after_dependencies": ["device_tracker"], "codeowners": [] } diff --git a/homeassistant/components/plant/manifest.json b/homeassistant/components/plant/manifest.json index 721a57e7822..c1e009ccec3 100644 --- a/homeassistant/components/plant/manifest.json +++ b/homeassistant/components/plant/manifest.json @@ -3,11 +3,7 @@ "name": "Plant", "documentation": "https://www.home-assistant.io/integrations/plant", "requirements": [], - "dependencies": [ - "group", - "zone" - ], - "codeowners": [ - "@ChristianKuehnel" - ] + "dependencies": ["group", "zone"], + "after_dependencies": ["recorder"], + "codeowners": ["@ChristianKuehnel"] } diff --git a/homeassistant/components/point/manifest.json b/homeassistant/components/point/manifest.json index 4c29f37e67c..1c74052ee7e 100644 --- a/homeassistant/components/point/manifest.json +++ b/homeassistant/components/point/manifest.json @@ -3,13 +3,7 @@ "name": "Point", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/point", - "requirements": [ - "pypoint==1.1.2" - ], - "dependencies": [ - "webhook" - ], - "codeowners": [ - "@fredrike" - ] + "requirements": ["pypoint==1.1.2"], + "dependencies": ["webhook", "http"], + "codeowners": ["@fredrike"] } diff --git a/homeassistant/components/rachio/manifest.json b/homeassistant/components/rachio/manifest.json index 79e3677d65e..fae640f9262 100644 --- a/homeassistant/components/rachio/manifest.json +++ b/homeassistant/components/rachio/manifest.json @@ -2,9 +2,7 @@ "domain": "rachio", "name": "Rachio", "documentation": "https://www.home-assistant.io/integrations/rachio", - "requirements": [ - "rachiopy==0.1.3" - ], - "dependencies": [], + "requirements": ["rachiopy==0.1.3"], + "dependencies": ["http"], "codeowners": [] } diff --git a/homeassistant/components/statistics/manifest.json b/homeassistant/components/statistics/manifest.json index 3dab05942b9..17ade1283ce 100644 --- a/homeassistant/components/statistics/manifest.json +++ b/homeassistant/components/statistics/manifest.json @@ -4,7 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/statistics", "requirements": [], "dependencies": [], - "codeowners": [ - "@fabaff" - ] + "after_dependencies": ["recorder"], + "codeowners": ["@fabaff"] } diff --git a/homeassistant/components/telegram_bot/__init__.py b/homeassistant/components/telegram_bot/__init__.py index d365060e204..12bde6c72d8 100644 --- a/homeassistant/components/telegram_bot/__init__.py +++ b/homeassistant/components/telegram_bot/__init__.py @@ -19,7 +19,6 @@ from telegram.parsemode import ParseMode from telegram.utils.request import Request import voluptuous as vol -from homeassistant.components.notify import ATTR_DATA, ATTR_MESSAGE, ATTR_TITLE from homeassistant.const import ( ATTR_COMMAND, ATTR_LATITUDE, @@ -35,6 +34,10 @@ from homeassistant.exceptions import TemplateError _LOGGER = logging.getLogger(__name__) +ATTR_DATA = "data" +ATTR_MESSAGE = "message" +ATTR_TITLE = "title" + ATTR_ARGS = "args" ATTR_AUTHENTICATION = "authentication" ATTR_CALLBACK_QUERY = "callback_query" diff --git a/homeassistant/components/tts/manifest.json b/homeassistant/components/tts/manifest.json index cb780523977..b57d5c36112 100644 --- a/homeassistant/components/tts/manifest.json +++ b/homeassistant/components/tts/manifest.json @@ -2,13 +2,8 @@ "domain": "tts", "name": "Tts", "documentation": "https://www.home-assistant.io/integrations/tts", - "requirements": [ - "mutagen==1.43.0" - ], - "dependencies": [ - "http" - ], - "codeowners": [ - "@robbiet480" - ] + "requirements": ["mutagen==1.43.0"], + "dependencies": ["http"], + "after_dependencies": ["media_player"], + "codeowners": ["@robbiet480"] } diff --git a/homeassistant/components/wink/manifest.json b/homeassistant/components/wink/manifest.json index acf9c38e632..a1bae648292 100644 --- a/homeassistant/components/wink/manifest.json +++ b/homeassistant/components/wink/manifest.json @@ -2,10 +2,7 @@ "domain": "wink", "name": "Wink", "documentation": "https://www.home-assistant.io/integrations/wink", - "requirements": [ - "pubnubsub-handler==1.0.8", - "python-wink==1.10.5" - ], - "dependencies": ["configurator"], + "requirements": ["pubnubsub-handler==1.0.8", "python-wink==1.10.5"], + "dependencies": ["configurator", "http"], "codeowners": [] } diff --git a/homeassistant/components/workday/binary_sensor.py b/homeassistant/components/workday/binary_sensor.py index f95447c1e72..efa8b6ad77b 100644 --- a/homeassistant/components/workday/binary_sensor.py +++ b/homeassistant/components/workday/binary_sensor.py @@ -5,9 +5,8 @@ from datetime import datetime, timedelta import holidays import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_NAME, WEEKDAYS -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/worxlandroid/sensor.py b/homeassistant/components/worxlandroid/sensor.py index 4e9bf0a6a4a..ad583d6d943 100644 --- a/homeassistant/components/worxlandroid/sensor.py +++ b/homeassistant/components/worxlandroid/sensor.py @@ -10,7 +10,7 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -from homeassistant.components.switch import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_HOST, CONF_PIN, CONF_TIMEOUT from homeassistant.helpers.aiohttp_client import async_get_clientsession diff --git a/script/hassfest/dependencies.py b/script/hassfest/dependencies.py index e47deb76ad5..42a31f20610 100644 --- a/script/hassfest/dependencies.py +++ b/script/hassfest/dependencies.py @@ -16,7 +16,19 @@ def grep_dir(path: pathlib.Path, glob_pattern: str, search_pattern: str) -> Set[ continue for match in pattern.finditer(fil.read_text()): - found.add(match.groups()[0]) + integration = match.groups()[1] + + if ( + # If it's importing something from itself + integration == path.name + # Platform file + or (path / f"{integration}.py").exists() + # Dir for platform + or (path / integration).exists() + ): + continue + + found.add(match.groups()[1]) return found @@ -30,19 +42,65 @@ ALLOWED_USED_COMPONENTS = { "hassio", "system_health", "websocket_api", + "automation", + "device_automation", + "zone", + "homeassistant", + "system_log", + "person", + # Discovery + "ssdp", + "discovery", + # Other + "mjpeg", # base class, has no reqs or component to load. } +IGNORE_VIOLATIONS = [ + # Has same requirement, gets defaults. + ("sql", "recorder"), + # Sharing a base class + ("openalpr_cloud", "openalpr_local"), + ("lutron_caseta", "lutron"), + ("ffmpeg_noise", "ffmpeg_motion"), + # Demo + ("demo", "manual"), + ("demo", "openalpr_local"), + # This should become a helper method that integrations can submit data to + ("websocket_api", "lovelace"), + # Expose HA to external systems + "homekit", + "alexa", + "google_assistant", + "emulated_hue", + "prometheus", + "conversation", + "logbook", + # These should be extracted to external package + "pvoutput", + "dwd_weather_warnings", + # Should be rewritten to use own data fetcher + "scrape", +] + def validate_dependencies(integration: Integration): """Validate all dependencies.""" # Find usage of hass.components - referenced = grep_dir(integration.path, "**/*.py", r"hass\.components\.(\w+)") + referenced = grep_dir( + integration.path, "**/*.py", r"(hass|homeassistant)\.components\.(\w+)" + ) referenced -= ALLOWED_USED_COMPONENTS referenced -= set(integration.manifest["dependencies"]) referenced -= set(integration.manifest.get("after_dependencies", [])) if referenced: for domain in sorted(referenced): + if ( + integration.domain in IGNORE_VIOLATIONS + or (integration.domain, domain) in IGNORE_VIOLATIONS + ): + continue + integration.add_error( "dependencies", "Using component {} but it's not in 'dependencies' or 'after_dependencies'".format( From 564fed787986f43fafae707be62ca8890010637a Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Thu, 5 Dec 2019 12:50:53 +0100 Subject: [PATCH 005/677] Move imports to top for ambient_station (#29497) --- homeassistant/components/ambient_station/__init__.py | 2 +- homeassistant/components/ambient_station/config_flow.py | 4 ++-- tests/components/ambient_station/test_config_flow.py | 9 +++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/ambient_station/__init__.py b/homeassistant/components/ambient_station/__init__.py index 7a805d6b867..58389dd1831 100644 --- a/homeassistant/components/ambient_station/__init__.py +++ b/homeassistant/components/ambient_station/__init__.py @@ -8,8 +8,8 @@ import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( - ATTR_NAME, ATTR_LOCATION, + ATTR_NAME, CONF_API_KEY, EVENT_HOMEASSISTANT_STOP, ) diff --git a/homeassistant/components/ambient_station/config_flow.py b/homeassistant/components/ambient_station/config_flow.py index 256e55ba402..c20b43598ca 100644 --- a/homeassistant/components/ambient_station/config_flow.py +++ b/homeassistant/components/ambient_station/config_flow.py @@ -1,4 +1,6 @@ """Config flow to configure the Ambient PWS component.""" +from aioambient import Client +from aioambient.errors import AmbientError import voluptuous as vol from homeassistant import config_entries @@ -40,8 +42,6 @@ class AmbientStationFlowHandler(config_entries.ConfigFlow): async def async_step_user(self, user_input=None): """Handle the start of the config flow.""" - from aioambient import Client - from aioambient.errors import AmbientError if not user_input: return await self._show_form() diff --git a/tests/components/ambient_station/test_config_flow.py b/tests/components/ambient_station/test_config_flow.py index 701a6dacb98..c94a51be94e 100644 --- a/tests/components/ambient_station/test_config_flow.py +++ b/tests/components/ambient_station/test_config_flow.py @@ -3,12 +3,13 @@ import json import aioambient import pytest +from unittest.mock import patch from homeassistant import data_entry_flow from homeassistant.components.ambient_station import CONF_APP_KEY, DOMAIN, config_flow from homeassistant.const import CONF_API_KEY -from tests.common import load_fixture, MockConfigEntry, MockDependency, mock_coro +from tests.common import load_fixture, MockConfigEntry, mock_coro @pytest.fixture @@ -20,9 +21,9 @@ def get_devices_response(): @pytest.fixture def mock_aioambient(get_devices_response): """Mock the aioambient library.""" - with MockDependency("aioambient") as mock_aioambient_: - mock_aioambient_.Client().api.get_devices.return_value = get_devices_response - yield mock_aioambient_ + with patch("homeassistant.components.ambient_station.config_flow.Client") as Client: + Client().api.get_devices.return_value = get_devices_response + yield Client async def test_duplicate_error(hass): From 086d1f432d89957df836c0a19233482aeba739d7 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Thu, 5 Dec 2019 12:59:59 +0100 Subject: [PATCH 006/677] Move imports to top for google_pubsub (#29498) --- homeassistant/components/google_pubsub/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/google_pubsub/__init__.py b/homeassistant/components/google_pubsub/__init__.py index c4136c3b9cb..bc7811a7a8f 100644 --- a/homeassistant/components/google_pubsub/__init__.py +++ b/homeassistant/components/google_pubsub/__init__.py @@ -5,6 +5,7 @@ import logging import os from typing import Any, Dict +from google.cloud import pubsub_v1 import voluptuous as vol from homeassistant.const import EVENT_STATE_CHANGED, STATE_UNAVAILABLE, STATE_UNKNOWN @@ -38,7 +39,6 @@ CONFIG_SCHEMA = vol.Schema( def setup(hass: HomeAssistant, yaml_config: Dict[str, Any]): """Activate Google Pub/Sub component.""" - from google.cloud import pubsub_v1 config = yaml_config[DOMAIN] project_id = config[CONF_PROJECT_ID] From 009e4df6efc9d743cec5493fb28f20b8d4096362 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Thu, 5 Dec 2019 13:19:50 +0100 Subject: [PATCH 007/677] Move imports to top for hue (#29501) --- homeassistant/components/hue/__init__.py | 4 ++-- homeassistant/components/hue/config_flow.py | 1 + homeassistant/components/hue/helpers.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/hue/__init__.py b/homeassistant/components/hue/__init__.py index 027ec205195..f2b9bd1a229 100644 --- a/homeassistant/components/hue/__init__.py +++ b/homeassistant/components/hue/__init__.py @@ -9,9 +9,9 @@ from homeassistant.const import CONF_FILENAME, CONF_HOST from homeassistant.helpers import config_validation as cv, device_registry as dr from .bridge import HueBridge -from .config_flow import ( +from .config_flow import ( # Loading the config flow file will register the flow configured_hosts, -) # Loading the config flow file will register the flow +) from .const import DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/hue/config_flow.py b/homeassistant/components/hue/config_flow.py index 375042c8835..84b435d02ed 100644 --- a/homeassistant/components/hue/config_flow.py +++ b/homeassistant/components/hue/config_flow.py @@ -134,6 +134,7 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): This flow is triggered by the SSDP component. It will check if the host is already configured and delegate to the import step if not. """ + # pylint: disable=import-outside-toplevel from homeassistant.components.ssdp import ATTR_MANUFACTURERURL, ATTR_NAME if discovery_info[ATTR_MANUFACTURERURL] != HUE_MANUFACTURERURL: diff --git a/homeassistant/components/hue/helpers.py b/homeassistant/components/hue/helpers.py index af0f996b537..8a5fa973e4f 100644 --- a/homeassistant/components/hue/helpers.py +++ b/homeassistant/components/hue/helpers.py @@ -1,7 +1,7 @@ """Helper functions for Philips Hue.""" +from homeassistant import config_entries from homeassistant.helpers.device_registry import async_get_registry as get_dev_reg from homeassistant.helpers.entity_registry import async_get_registry as get_ent_reg -from homeassistant import config_entries from .const import DOMAIN From e9917c6a81fbf8601717185884a42208d993b2f3 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Thu, 5 Dec 2019 13:22:01 +0100 Subject: [PATCH 008/677] Move imports to top for google_translate (#29499) --- homeassistant/components/google_translate/tts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/google_translate/tts.py b/homeassistant/components/google_translate/tts.py index 3add45b8cb8..e35a229ab98 100644 --- a/homeassistant/components/google_translate/tts.py +++ b/homeassistant/components/google_translate/tts.py @@ -6,6 +6,7 @@ import re import aiohttp from aiohttp.hdrs import REFERER, USER_AGENT import async_timeout +from gtts_token import gtts_token import voluptuous as vol import yarl @@ -115,7 +116,6 @@ class GoogleProvider(Provider): async def async_get_tts_audio(self, message, language, options=None): """Load TTS from google.""" - from gtts_token import gtts_token token = gtts_token.Token() websession = async_get_clientsession(self.hass) From 04722fdd63a80f55e87e4e06b9c8857acd10b02d Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Thu, 5 Dec 2019 13:42:09 +0100 Subject: [PATCH 009/677] Move imports to top for http (#29500) --- homeassistant/components/http/__init__.py | 1 - homeassistant/components/http/auth.py | 1 - homeassistant/components/http/ban.py | 1 - homeassistant/components/http/cors.py | 4 ++-- homeassistant/components/http/data_validator.py | 1 - homeassistant/components/http/real_ip.py | 1 - homeassistant/components/http/static.py | 3 +-- 7 files changed, 3 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index 4d3985a7af3..c720d134c9f 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -26,7 +26,6 @@ from .real_ip import setup_real_ip from .static import CACHE_HEADERS, CachingStaticResource from .view import HomeAssistantView # noqa: F401 - # mypy: allow-untyped-defs, no-check-untyped-defs DOMAIN = "http" diff --git a/homeassistant/components/http/auth.py b/homeassistant/components/http/auth.py index 97bd9b7d4bc..3866e770de0 100644 --- a/homeassistant/components/http/auth.py +++ b/homeassistant/components/http/auth.py @@ -11,7 +11,6 @@ from homeassistant.util import dt as dt_util from .const import KEY_AUTHENTICATED, KEY_HASS_USER, KEY_REAL_IP - # mypy: allow-untyped-defs, no-check-untyped-defs _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/http/ban.py b/homeassistant/components/http/ban.py index 7d1e24f3698..553d3657160 100644 --- a/homeassistant/components/http/ban.py +++ b/homeassistant/components/http/ban.py @@ -17,7 +17,6 @@ from homeassistant.util.yaml import dump from .const import KEY_REAL_IP - # mypy: allow-untyped-defs, no-check-untyped-defs _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/http/cors.py b/homeassistant/components/http/cors.py index de4547f4782..2d99a049e4b 100644 --- a/homeassistant/components/http/cors.py +++ b/homeassistant/components/http/cors.py @@ -1,11 +1,10 @@ """Provide CORS support for the HTTP component.""" +from aiohttp.hdrs import ACCEPT, AUTHORIZATION, CONTENT_TYPE, ORIGIN from aiohttp.web_urldispatcher import Resource, ResourceRoute, StaticResource -from aiohttp.hdrs import ACCEPT, CONTENT_TYPE, ORIGIN, AUTHORIZATION from homeassistant.const import HTTP_HEADER_X_REQUESTED_WITH from homeassistant.core import callback - # mypy: allow-untyped-defs, no-check-untyped-defs ALLOWED_CORS_HEADERS = [ @@ -23,6 +22,7 @@ def setup_cors(app, origins): """Set up CORS.""" # This import should remain here. That way the HTTP integration can always # be imported by other integrations without it's requirements being installed. + # pylint: disable=import-outside-toplevel import aiohttp_cors cors = aiohttp_cors.setup( diff --git a/homeassistant/components/http/data_validator.py b/homeassistant/components/http/data_validator.py index 5945a4ca402..017644a4d36 100644 --- a/homeassistant/components/http/data_validator.py +++ b/homeassistant/components/http/data_validator.py @@ -4,7 +4,6 @@ import logging import voluptuous as vol - # mypy: allow-untyped-defs _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/http/real_ip.py b/homeassistant/components/http/real_ip.py index f327c86a4c1..f2334ce0a2f 100644 --- a/homeassistant/components/http/real_ip.py +++ b/homeassistant/components/http/real_ip.py @@ -8,7 +8,6 @@ from homeassistant.core import callback from .const import KEY_REAL_IP - # mypy: allow-untyped-defs diff --git a/homeassistant/components/http/static.py b/homeassistant/components/http/static.py index 7d84be0f6dd..a5fe686a651 100644 --- a/homeassistant/components/http/static.py +++ b/homeassistant/components/http/static.py @@ -3,10 +3,9 @@ from pathlib import Path from aiohttp import hdrs from aiohttp.web import FileResponse -from aiohttp.web_exceptions import HTTPNotFound, HTTPForbidden +from aiohttp.web_exceptions import HTTPForbidden, HTTPNotFound from aiohttp.web_urldispatcher import StaticResource - # mypy: allow-untyped-defs CACHE_TIME = 31 * 86400 # = 1 month From ed464a75b205dde77668c5cef5d5026a24c9d049 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Thu, 5 Dec 2019 13:42:56 +0100 Subject: [PATCH 010/677] Move imports to top for system_log (#29465) --- homeassistant/components/system_log/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/system_log/__init__.py b/homeassistant/components/system_log/__init__.py index 68561d45f8f..44ff9c49a01 100644 --- a/homeassistant/components/system_log/__init__.py +++ b/homeassistant/components/system_log/__init__.py @@ -8,8 +8,8 @@ import voluptuous as vol from homeassistant import __path__ as HOMEASSISTANT_PATH from homeassistant.components.http import HomeAssistantView -import homeassistant.helpers.config_validation as cv from homeassistant.const import EVENT_HOMEASSISTANT_STOP +import homeassistant.helpers.config_validation as cv CONF_MAX_ENTRIES = "max_entries" CONF_FIRE_EVENT = "fire_event" @@ -57,6 +57,7 @@ def _figure_out_source(record, call_stack, hass): paths = [HOMEASSISTANT_PATH[0], hass.config.config_dir] try: # If netdisco is installed check its path too. + # pylint: disable=import-outside-toplevel from netdisco import __path__ as netdisco_path paths.append(netdisco_path[0]) From ed5cdb528c3c2a810843ec2fbc5a983b46676094 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Thu, 5 Dec 2019 13:44:11 +0100 Subject: [PATCH 011/677] Move imports to top for group (#29485) * Move imports to top for group * Fix failing test for group --- homeassistant/components/group/__init__.py | 21 +++++++------- homeassistant/components/group/cover.py | 24 ++++++++-------- homeassistant/components/group/light.py | 28 +++++++++---------- homeassistant/components/group/notify.py | 6 ++-- .../components/group/reproduce_state.py | 5 ++-- .../components/group/test_reproduce_state.py | 4 ++- 6 files changed, 42 insertions(+), 46 deletions(-) diff --git a/homeassistant/components/group/__init__.py b/homeassistant/components/group/__init__.py index ba12e22b53e..16094ed4832 100644 --- a/homeassistant/components/group/__init__.py +++ b/homeassistant/components/group/__init__.py @@ -7,34 +7,33 @@ import voluptuous as vol from homeassistant import core as ha from homeassistant.const import ( + ATTR_ASSUMED_STATE, ATTR_ENTITY_ID, + ATTR_ICON, + ATTR_NAME, CONF_ICON, CONF_NAME, + SERVICE_RELOAD, STATE_CLOSED, STATE_HOME, + STATE_LOCKED, STATE_NOT_HOME, STATE_OFF, + STATE_OK, STATE_ON, STATE_OPEN, - STATE_LOCKED, - STATE_UNLOCKED, - STATE_OK, STATE_PROBLEM, STATE_UNKNOWN, - ATTR_ASSUMED_STATE, - SERVICE_RELOAD, - ATTR_NAME, - ATTR_ICON, + STATE_UNLOCKED, ) from homeassistant.core import callback -from homeassistant.loader import bind_hass +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.config_validation import make_entity_service_schema from homeassistant.helpers.entity import Entity, async_generate_entity_id from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.event import async_track_state_change -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.config_validation import make_entity_service_schema from homeassistant.helpers.typing import HomeAssistantType - +from homeassistant.loader import bind_hass # mypy: allow-untyped-calls, allow-untyped-defs, no-check-untyped-defs diff --git a/homeassistant/components/group/cover.py b/homeassistant/components/group/cover.py index f7a9643e5c8..d9efdfa53c6 100644 --- a/homeassistant/components/group/cover.py +++ b/homeassistant/components/group/cover.py @@ -4,18 +4,6 @@ from typing import Dict, Optional, Set import voluptuous as vol -from homeassistant.const import ( - ATTR_ASSUMED_STATE, - ATTR_ENTITY_ID, - ATTR_SUPPORTED_FEATURES, - CONF_ENTITIES, - CONF_NAME, - STATE_CLOSED, -) -from homeassistant.core import callback, State -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.event import async_track_state_change - from homeassistant.components.cover import ( ATTR_CURRENT_POSITION, ATTR_CURRENT_TILT_POSITION, @@ -41,7 +29,17 @@ from homeassistant.components.cover import ( SUPPORT_STOP_TILT, CoverDevice, ) - +from homeassistant.const import ( + ATTR_ASSUMED_STATE, + ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, + CONF_ENTITIES, + CONF_NAME, + STATE_CLOSED, +) +from homeassistant.core import State, callback +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.event import async_track_state_change # mypy: allow-incomplete-defs, allow-untyped-calls, allow-untyped-defs # mypy: no-check-untyped-defs diff --git a/homeassistant/components/group/light.py b/homeassistant/components/group/light.py index 2cd65028131..f0c81696469 100644 --- a/homeassistant/components/group/light.py +++ b/homeassistant/components/group/light.py @@ -8,20 +8,6 @@ from typing import Any, Callable, Iterator, List, Optional, Tuple, cast import voluptuous as vol from homeassistant.components import light -from homeassistant.const import ( - ATTR_ENTITY_ID, - ATTR_SUPPORTED_FEATURES, - CONF_ENTITIES, - CONF_NAME, - STATE_ON, - STATE_UNAVAILABLE, -) -from homeassistant.core import CALLBACK_TYPE, State, callback -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.event import async_track_state_change -from homeassistant.helpers.typing import ConfigType, HomeAssistantType -from homeassistant.util import color as color_util - from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, @@ -42,7 +28,19 @@ from homeassistant.components.light import ( SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE, ) - +from homeassistant.const import ( + ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, + CONF_ENTITIES, + CONF_NAME, + STATE_ON, + STATE_UNAVAILABLE, +) +from homeassistant.core import CALLBACK_TYPE, State, callback +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.event import async_track_state_change +from homeassistant.helpers.typing import ConfigType, HomeAssistantType +from homeassistant.util import color as color_util # mypy: allow-incomplete-defs, allow-untyped-calls, allow-untyped-defs # mypy: no-check-untyped-defs diff --git a/homeassistant/components/group/notify.py b/homeassistant/components/group/notify.py index e17990690fa..2209e0e2333 100644 --- a/homeassistant/components/group/notify.py +++ b/homeassistant/components/group/notify.py @@ -6,9 +6,6 @@ import logging import voluptuous as vol -from homeassistant.const import ATTR_SERVICE -import homeassistant.helpers.config_validation as cv - from homeassistant.components.notify import ( ATTR_DATA, ATTR_MESSAGE, @@ -16,7 +13,8 @@ from homeassistant.components.notify import ( PLATFORM_SCHEMA, BaseNotificationService, ) - +from homeassistant.const import ATTR_SERVICE +import homeassistant.helpers.config_validation as cv # mypy: allow-untyped-calls, allow-untyped-defs, no-check-untyped-defs diff --git a/homeassistant/components/group/reproduce_state.py b/homeassistant/components/group/reproduce_state.py index 827e9bb1dcb..78790701934 100644 --- a/homeassistant/components/group/reproduce_state.py +++ b/homeassistant/components/group/reproduce_state.py @@ -2,15 +2,16 @@ from typing import Iterable, Optional from homeassistant.core import Context, State +from homeassistant.helpers.state import async_reproduce_state from homeassistant.helpers.typing import HomeAssistantType +from . import get_entity_ids + async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], context: Optional[Context] = None ) -> None: """Reproduce component states.""" - from . import get_entity_ids - from homeassistant.helpers.state import async_reproduce_state states_copy = [] for state in states: diff --git a/tests/components/group/test_reproduce_state.py b/tests/components/group/test_reproduce_state.py index 502ea9e51fc..f3a56a46472 100644 --- a/tests/components/group/test_reproduce_state.py +++ b/tests/components/group/test_reproduce_state.py @@ -21,7 +21,9 @@ async def test_reproduce_group(hass): context=state.context, ) - with patch("homeassistant.helpers.state.async_reproduce_state") as fun: + with patch( + "homeassistant.components.group.reproduce_state.async_reproduce_state" + ) as fun: fun.return_value = Future() fun.return_value.set_result(None) From 204ca3f3a6e24ef11ece2e2ee490a8d77553c147 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Thu, 5 Dec 2019 13:44:59 +0100 Subject: [PATCH 012/677] Move imports to top for frontend (#29487) --- homeassistant/components/frontend/__init__.py | 1 + homeassistant/components/frontend/storage.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 7ef2bd38644..efb1c34653b 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -242,6 +242,7 @@ def _frontend_root(dev_repo_path): if dev_repo_path is not None: return pathlib.Path(dev_repo_path) / "hass_frontend" # Keep import here so that we can import frontend without installing reqs + # pylint: disable=import-outside-toplevel import hass_frontend return hass_frontend.where() diff --git a/homeassistant/components/frontend/storage.py b/homeassistant/components/frontend/storage.py index 75b7b356ef9..2f68c5f8e01 100644 --- a/homeassistant/components/frontend/storage.py +++ b/homeassistant/components/frontend/storage.py @@ -1,10 +1,10 @@ """API for persistent storage for the frontend.""" from functools import wraps + import voluptuous as vol from homeassistant.components import websocket_api - # mypy: allow-untyped-calls, allow-untyped-defs DATA_STORAGE = "frontend_storage" From 2a92eb1962a54b9b0cf9e349fe00bd0b5028b6b7 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Thu, 5 Dec 2019 16:56:01 +0100 Subject: [PATCH 013/677] Move imports to top for ipma (#29507) --- homeassistant/components/ipma/__init__.py | 1 + homeassistant/components/ipma/weather.py | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/ipma/__init__.py b/homeassistant/components/ipma/__init__.py index 702e12a8a63..a00941624f5 100644 --- a/homeassistant/components/ipma/__init__.py +++ b/homeassistant/components/ipma/__init__.py @@ -1,5 +1,6 @@ """Component for the Portuguese weather service - IPMA.""" from homeassistant.core import Config, HomeAssistant + from .config_flow import IpmaFlowHandler # noqa: F401 from .const import DOMAIN # noqa: F401 diff --git a/homeassistant/components/ipma/weather.py b/homeassistant/components/ipma/weather.py index 9f1836c7389..c088d76d165 100644 --- a/homeassistant/components/ipma/weather.py +++ b/homeassistant/components/ipma/weather.py @@ -1,22 +1,23 @@ """Support for IPMA weather service.""" -import logging from datetime import timedelta +import logging import async_timeout +from pyipma import Station import voluptuous as vol from homeassistant.components.weather import ( - WeatherEntity, - PLATFORM_SCHEMA, ATTR_FORECAST_CONDITION, ATTR_FORECAST_PRECIPITATION, ATTR_FORECAST_TEMP, ATTR_FORECAST_TEMP_LOW, ATTR_FORECAST_TIME, + PLATFORM_SCHEMA, + WeatherEntity, ) -from homeassistant.const import CONF_NAME, TEMP_CELSIUS, CONF_LATITUDE, CONF_LONGITUDE -from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, TEMP_CELSIUS from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -84,7 +85,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async def async_get_station(hass, latitude, longitude): """Retrieve weather station, station name to be used as the entity name.""" - from pyipma import Station websession = async_get_clientsession(hass) with async_timeout.timeout(10): From 76f455cea9b6df03b41c29679348e7bf44b06e76 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Thu, 5 Dec 2019 16:56:53 +0100 Subject: [PATCH 014/677] Move imports to top for mediaroom (#29509) --- .../components/mediaroom/media_player.py | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/mediaroom/media_player.py b/homeassistant/components/mediaroom/media_player.py index 8e02ee56a75..539138783ee 100644 --- a/homeassistant/components/mediaroom/media_player.py +++ b/homeassistant/components/mediaroom/media_player.py @@ -1,9 +1,10 @@ """Support for the Mediaroom Set-up-box.""" import logging +from pymediaroom import PyMediaroomError, Remote, State, install_mediaroom_protocol import voluptuous as vol -from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice from homeassistant.components.media_player.const import ( MEDIA_TYPE_CHANNEL, SUPPORT_NEXT_TRACK, @@ -99,7 +100,6 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([new_stb]) if not config[CONF_OPTIMISTIC]: - from pymediaroom import install_mediaroom_protocol already_installed = hass.data.get(DISCOVERY_MEDIAROOM, None) if not already_installed: @@ -123,7 +123,6 @@ class MediaroomDevice(MediaPlayerDevice): def set_state(self, mediaroom_state): """Map pymediaroom state to HA state.""" - from pymediaroom import State state_map = { State.OFF: STATE_OFF, @@ -139,7 +138,6 @@ class MediaroomDevice(MediaPlayerDevice): def __init__(self, host, device_id, optimistic=False, timeout=DEFAULT_TIMEOUT): """Initialize the device.""" - from pymediaroom import Remote self.host = host self.stb = Remote(host) @@ -184,7 +182,6 @@ class MediaroomDevice(MediaPlayerDevice): async def async_play_media(self, media_type, media_id, **kwargs): """Play media.""" - from pymediaroom import PyMediaroomError _LOGGER.debug( "STB(%s) Play media: %s (%s)", self.stb.stb_ip, media_id, media_type @@ -237,7 +234,6 @@ class MediaroomDevice(MediaPlayerDevice): async def async_turn_on(self): """Turn on the receiver.""" - from pymediaroom import PyMediaroomError try: self.set_state(await self.stb.turn_on()) @@ -250,7 +246,6 @@ class MediaroomDevice(MediaPlayerDevice): async def async_turn_off(self): """Turn off the receiver.""" - from pymediaroom import PyMediaroomError try: self.set_state(await self.stb.turn_off()) @@ -263,7 +258,6 @@ class MediaroomDevice(MediaPlayerDevice): async def async_media_play(self): """Send play command.""" - from pymediaroom import PyMediaroomError try: _LOGGER.debug("media_play()") @@ -277,7 +271,6 @@ class MediaroomDevice(MediaPlayerDevice): async def async_media_pause(self): """Send pause command.""" - from pymediaroom import PyMediaroomError try: await self.stb.send_cmd("PlayPause") @@ -290,7 +283,6 @@ class MediaroomDevice(MediaPlayerDevice): async def async_media_stop(self): """Send stop command.""" - from pymediaroom import PyMediaroomError try: await self.stb.send_cmd("Stop") @@ -303,7 +295,6 @@ class MediaroomDevice(MediaPlayerDevice): async def async_media_previous_track(self): """Send Program Down command.""" - from pymediaroom import PyMediaroomError try: await self.stb.send_cmd("ProgDown") @@ -316,7 +307,6 @@ class MediaroomDevice(MediaPlayerDevice): async def async_media_next_track(self): """Send Program Up command.""" - from pymediaroom import PyMediaroomError try: await self.stb.send_cmd("ProgUp") @@ -329,7 +319,6 @@ class MediaroomDevice(MediaPlayerDevice): async def async_volume_up(self): """Send volume up command.""" - from pymediaroom import PyMediaroomError try: await self.stb.send_cmd("VolUp") @@ -340,7 +329,6 @@ class MediaroomDevice(MediaPlayerDevice): async def async_volume_down(self): """Send volume up command.""" - from pymediaroom import PyMediaroomError try: await self.stb.send_cmd("VolDown") @@ -350,7 +338,6 @@ class MediaroomDevice(MediaPlayerDevice): async def async_mute_volume(self, mute): """Send mute command.""" - from pymediaroom import PyMediaroomError try: await self.stb.send_cmd("Mute") From 19893b8f3cf5162593f38c67b787f322ae899927 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Thu, 5 Dec 2019 16:58:14 +0100 Subject: [PATCH 015/677] Move imports to top for islamic_prayer_times (#29506) * Move imports to top for islamic_prayer_times * Fix test_sensor.py for islamic_prayer_times * Format test_sensor.py with black * Fix tests for islamic prayer times sensor --- .../components/islamic_prayer_times/sensor.py | 8 ++--- .../islamic_prayer_times/test_sensor.py | 31 ++++++++++++------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/islamic_prayer_times/sensor.py b/homeassistant/components/islamic_prayer_times/sensor.py index 88cbd2cb431..3f7de535407 100644 --- a/homeassistant/components/islamic_prayer_times/sensor.py +++ b/homeassistant/components/islamic_prayer_times/sensor.py @@ -1,15 +1,16 @@ """Platform to retrieve Islamic prayer times information for Home Assistant.""" -import logging from datetime import datetime, timedelta +import logging +from prayer_times_calculator import PrayerTimesCalculator import voluptuous as vol -import homeassistant.helpers.config_validation as cv -import homeassistant.util.dt as dt_util from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import DEVICE_CLASS_TIMESTAMP +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_point_in_time +import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) @@ -148,7 +149,6 @@ class IslamicPrayerTimesData: def get_new_prayer_times(self): """Fetch prayer times for today.""" - from prayer_times_calculator import PrayerTimesCalculator today = datetime.today().strftime("%Y-%m-%d") diff --git a/tests/components/islamic_prayer_times/test_sensor.py b/tests/components/islamic_prayer_times/test_sensor.py index 734b82076c2..ad229404a30 100644 --- a/tests/components/islamic_prayer_times/test_sensor.py +++ b/tests/components/islamic_prayer_times/test_sensor.py @@ -3,7 +3,6 @@ from datetime import datetime, timedelta from unittest.mock import patch from homeassistant.setup import async_setup_component from homeassistant.components.islamic_prayer_times.sensor import IslamicPrayerTimesData -from tests.common import MockDependency import homeassistant.util.dt as dt_util from tests.common import async_fire_time_changed @@ -34,8 +33,10 @@ async def test_islamic_prayer_times_min_config(hass): """Test minimum Islamic prayer times configuration.""" min_config_sensors = ["fajr", "dhuhr", "asr", "maghrib", "isha"] - with MockDependency("prayer_times_calculator") as mock_pt_calc: - mock_pt_calc.PrayerTimesCalculator.return_value.fetch_prayer_times.return_value = ( + with patch( + "homeassistant.components.islamic_prayer_times.sensor.PrayerTimesCalculator" + ) as PrayerTimesCalculator: + PrayerTimesCalculator.return_value.fetch_prayer_times.return_value = ( PRAYER_TIMES ) @@ -63,8 +64,10 @@ async def test_islamic_prayer_times_multiple_sensors(hass): "midnight", ] - with MockDependency("prayer_times_calculator") as mock_pt_calc: - mock_pt_calc.PrayerTimesCalculator.return_value.fetch_prayer_times.return_value = ( + with patch( + "homeassistant.components.islamic_prayer_times.sensor.PrayerTimesCalculator" + ) as PrayerTimesCalculator: + PrayerTimesCalculator.return_value.fetch_prayer_times.return_value = ( PRAYER_TIMES ) @@ -87,8 +90,10 @@ async def test_islamic_prayer_times_with_calculation_method(hass): """Test Islamic prayer times configuration with calculation method.""" sensors = ["fajr", "maghrib"] - with MockDependency("prayer_times_calculator") as mock_pt_calc: - mock_pt_calc.PrayerTimesCalculator.return_value.fetch_prayer_times.return_value = ( + with patch( + "homeassistant.components.islamic_prayer_times.sensor.PrayerTimesCalculator" + ) as PrayerTimesCalculator: + PrayerTimesCalculator.return_value.fetch_prayer_times.return_value = ( PRAYER_TIMES ) @@ -113,8 +118,10 @@ async def test_islamic_prayer_times_with_calculation_method(hass): async def test_islamic_prayer_times_data_get_prayer_times(hass): """Test Islamic prayer times data fetcher.""" - with MockDependency("prayer_times_calculator") as mock_pt_calc: - mock_pt_calc.PrayerTimesCalculator.return_value.fetch_prayer_times.return_value = ( + with patch( + "homeassistant.components.islamic_prayer_times.sensor.PrayerTimesCalculator" + ) as PrayerTimesCalculator: + PrayerTimesCalculator.return_value.fetch_prayer_times.return_value = ( PRAYER_TIMES ) @@ -138,8 +145,10 @@ async def test_islamic_prayer_times_sensor_update(hass): "Midnight": "00:45", } - with MockDependency("prayer_times_calculator") as mock_pt_calc: - mock_pt_calc.PrayerTimesCalculator.return_value.fetch_prayer_times.side_effect = [ + with patch( + "homeassistant.components.islamic_prayer_times.sensor.PrayerTimesCalculator" + ) as PrayerTimesCalculator: + PrayerTimesCalculator.return_value.fetch_prayer_times.side_effect = [ PRAYER_TIMES, new_prayer_times, ] From 1846b45cb58b04a4db95a3bd84289e875c9295ec Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Thu, 5 Dec 2019 16:59:31 +0100 Subject: [PATCH 016/677] Move imports to top for mobile_app (#29511) --- homeassistant/components/mobile_app/helpers.py | 2 +- homeassistant/components/mobile_app/http_api.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/mobile_app/helpers.py b/homeassistant/components/mobile_app/helpers.py index cad25f371dd..400ff31be89 100644 --- a/homeassistant/components/mobile_app/helpers.py +++ b/homeassistant/components/mobile_app/helpers.py @@ -111,7 +111,7 @@ def error_response( def supports_encryption() -> bool: """Test if we support encryption.""" try: - import nacl # noqa: F401 pylint: disable=unused-import + import nacl # noqa: F401 pylint: disable=unused-import, import-outside-toplevel return True except OSError: diff --git a/homeassistant/components/mobile_app/http_api.py b/homeassistant/components/mobile_app/http_api.py index 5be2d19789e..ede18528f81 100644 --- a/homeassistant/components/mobile_app/http_api.py +++ b/homeassistant/components/mobile_app/http_api.py @@ -6,7 +6,6 @@ from aiohttp.web import Request, Response from nacl.secret import SecretBox from homeassistant.auth.util import generate_secret - from homeassistant.components.http import HomeAssistantView from homeassistant.components.http.data_validator import RequestDataValidator from homeassistant.const import CONF_WEBHOOK_ID, HTTP_CREATED From 5bdb20098e4a1c7e20d6ca6525f62cc22c2ff430 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Thu, 5 Dec 2019 16:59:56 +0100 Subject: [PATCH 017/677] Move imports to top for meteoalarm (#29510) From 98d2eadb768b36ae4bccee6249c89af726d13865 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Thu, 5 Dec 2019 18:49:28 +0100 Subject: [PATCH 018/677] Move imports to top for statistics (#29223) * Move imports to top for statistics * Added recorder to manifest.json * Deleted recorder from manifest.json, moved import back into method, added pylint disable comment * Moved recorder util imports away from the top * Move recorder imports to top, add recorder as after_dependency to manifest.json --- homeassistant/components/statistics/sensor.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/statistics/sensor.py b/homeassistant/components/statistics/sensor.py index 51868c6d0a8..6e042b1536f 100644 --- a/homeassistant/components/statistics/sensor.py +++ b/homeassistant/components/statistics/sensor.py @@ -1,25 +1,26 @@ """Support for statistics for sensor values.""" +from collections import deque import logging import statistics -from collections import deque import voluptuous as vol -import homeassistant.helpers.config_validation as cv +from homeassistant.components.recorder.models import States +from homeassistant.components.recorder.util import execute, session_scope from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_NAME, - CONF_ENTITY_ID, - EVENT_HOMEASSISTANT_START, - STATE_UNKNOWN, - STATE_UNAVAILABLE, ATTR_UNIT_OF_MEASUREMENT, + CONF_ENTITY_ID, + CONF_NAME, + EVENT_HOMEASSISTANT_START, + STATE_UNAVAILABLE, + STATE_UNKNOWN, ) from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_state_change from homeassistant.util import dt as dt_util -from homeassistant.components.recorder.util import session_scope, execute _LOGGER = logging.getLogger(__name__) @@ -275,7 +276,6 @@ class StatisticsSensor(Entity): If MaxAge is provided then query will restrict to entries younger then current datetime - MaxAge. """ - from homeassistant.components.recorder.models import States _LOGGER.debug("%s: initializing values from the database", self.entity_id) From 4e7b9eaed01b2d56a2b8b82641a2a184078c1a36 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Thu, 5 Dec 2019 19:16:16 +0100 Subject: [PATCH 019/677] Move imports to top for monoprice (#29516) * Move imports to top for monoprice * Format test with black --- homeassistant/components/monoprice/media_player.py | 8 ++++---- tests/components/monoprice/test_media_player.py | 5 ++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/monoprice/media_player.py b/homeassistant/components/monoprice/media_player.py index 1b1d9d2adf4..20b2ecebcf4 100644 --- a/homeassistant/components/monoprice/media_player.py +++ b/homeassistant/components/monoprice/media_player.py @@ -1,9 +1,11 @@ """Support for interfacing with Monoprice 6 zone home audio controller.""" import logging +from pymonoprice import get_monoprice +from serial import SerialException import voluptuous as vol -from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice from homeassistant.components.media_player.const import ( SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, @@ -20,6 +22,7 @@ from homeassistant.const import ( STATE_ON, ) import homeassistant.helpers.config_validation as cv + from .const import DOMAIN, SERVICE_RESTORE, SERVICE_SNAPSHOT _LOGGER = logging.getLogger(__name__) @@ -68,9 +71,6 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Monoprice 6-zone amplifier platform.""" port = config.get(CONF_PORT) - from serial import SerialException - from pymonoprice import get_monoprice - try: monoprice = get_monoprice(port) except SerialException: diff --git a/tests/components/monoprice/test_media_player.py b/tests/components/monoprice/test_media_player.py index a33b8553908..cb064048d7b 100644 --- a/tests/components/monoprice/test_media_player.py +++ b/tests/components/monoprice/test_media_player.py @@ -174,7 +174,10 @@ class TestMonopriceMediaPlayer(unittest.TestCase): self.hass = tests.common.get_test_home_assistant() self.hass.start() # Note, source dictionary is unsorted! - with mock.patch("pymonoprice.get_monoprice", new=lambda *a: self.monoprice): + with mock.patch( + "homeassistant.components.monoprice.media_player.get_monoprice", + new=lambda *a: self.monoprice, + ): setup_platform( self.hass, { From ec8ea022731b762481c2c88e1b14b4c9561c0b19 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 5 Dec 2019 10:40:05 -0800 Subject: [PATCH 020/677] Fix recursion --- homeassistant/requirements.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/homeassistant/requirements.py b/homeassistant/requirements.py index a0eec0f442b..eb8aeeecfae 100644 --- a/homeassistant/requirements.py +++ b/homeassistant/requirements.py @@ -3,7 +3,7 @@ import asyncio from pathlib import Path import logging import os -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List, Optional, Set from homeassistant.exceptions import HomeAssistantError import homeassistant.util.package as pkg_util @@ -28,16 +28,19 @@ class RequirementsNotFound(HomeAssistantError): async def async_get_integration_with_requirements( - hass: HomeAssistant, domain: str + hass: HomeAssistant, domain: str, done: Set[str] = None ) -> Integration: """Get an integration with installed requirements. This can raise IntegrationNotFound if manifest or integration is invalid, RequirementNotFound if there was some type of failure to install requirements. - - Does not handle circular dependencies. """ + if done is None: + done = {domain} + else: + done.add(domain) + integration = await async_get_integration(hass, domain) if hass.config.skip_pip: @@ -48,11 +51,18 @@ async def async_get_integration_with_requirements( hass, integration.domain, integration.requirements ) - deps = integration.dependencies + (integration.after_dependencies or []) + deps_to_check = [ + dep + for dep in integration.dependencies + (integration.after_dependencies or []) + if dep not in done + ] - if deps: + if deps_to_check: await asyncio.gather( - *[async_get_integration_with_requirements(hass, dep) for dep in deps] + *[ + async_get_integration_with_requirements(hass, dep, done) + for dep in deps_to_check + ] ) return integration From b4fda5faab72ed8bf9678067c3be17ec9b806cd1 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Thu, 5 Dec 2019 19:54:43 +0100 Subject: [PATCH 021/677] Move imports to top for mysensors (#29517) --- homeassistant/components/mysensors/__init__.py | 2 +- homeassistant/components/mysensors/climate.py | 2 +- homeassistant/components/mysensors/device.py | 2 +- homeassistant/components/mysensors/gateway.py | 2 +- homeassistant/components/mysensors/handler.py | 2 +- homeassistant/components/mysensors/light.py | 2 +- homeassistant/components/mysensors/sensor.py | 4 ++-- homeassistant/components/mysensors/switch.py | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/mysensors/__init__.py b/homeassistant/components/mysensors/__init__.py index cbedd947843..a528be15e14 100644 --- a/homeassistant/components/mysensors/__init__.py +++ b/homeassistant/components/mysensors/__init__.py @@ -25,7 +25,7 @@ from .const import ( MYSENSORS_GATEWAYS, ) from .device import get_mysensors_devices -from .gateway import get_mysensors_gateway, setup_gateways, finish_setup +from .gateway import finish_setup, get_mysensors_gateway, setup_gateways _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/mysensors/climate.py b/homeassistant/components/mysensors/climate.py index dc053e60de1..4939c0c83e5 100644 --- a/homeassistant/components/mysensors/climate.py +++ b/homeassistant/components/mysensors/climate.py @@ -8,10 +8,10 @@ from homeassistant.components.climate.const import ( HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_HEAT, + HVAC_MODE_OFF, SUPPORT_FAN_MODE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_RANGE, - HVAC_MODE_OFF, ) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT diff --git a/homeassistant/components/mysensors/device.py b/homeassistant/components/mysensors/device.py index 6d766530b04..e5853fce5ca 100644 --- a/homeassistant/components/mysensors/device.py +++ b/homeassistant/components/mysensors/device.py @@ -1,6 +1,6 @@ """Handle MySensors devices.""" -import logging from functools import partial +import logging from homeassistant.const import ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON from homeassistant.core import callback diff --git a/homeassistant/components/mysensors/gateway.py b/homeassistant/components/mysensors/gateway.py index 366692205a7..903ec069b51 100644 --- a/homeassistant/components/mysensors/gateway.py +++ b/homeassistant/components/mysensors/gateway.py @@ -6,6 +6,7 @@ import socket import sys import async_timeout +from mysensors import mysensors import voluptuous as vol from homeassistant.const import CONF_OPTIMISTIC, EVENT_HOMEASSISTANT_STOP @@ -84,7 +85,6 @@ async def setup_gateways(hass, config): async def _get_gateway(hass, config, gateway_conf, persistence_file): """Return gateway after setup of the gateway.""" - from mysensors import mysensors conf = config[DOMAIN] persistence = conf[CONF_PERSISTENCE] diff --git a/homeassistant/components/mysensors/handler.py b/homeassistant/components/mysensors/handler.py index 0923b6bc8de..31e836f4767 100644 --- a/homeassistant/components/mysensors/handler.py +++ b/homeassistant/components/mysensors/handler.py @@ -5,7 +5,7 @@ from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.util import decorator -from .const import MYSENSORS_GATEWAY_READY, CHILD_CALLBACK, NODE_CALLBACK +from .const import CHILD_CALLBACK, MYSENSORS_GATEWAY_READY, NODE_CALLBACK from .device import get_mysensors_devices from .helpers import discover_mysensors_platform, validate_set_msg diff --git a/homeassistant/components/mysensors/light.py b/homeassistant/components/mysensors/light.py index 8f0d0906311..19eb8e9e92c 100644 --- a/homeassistant/components/mysensors/light.py +++ b/homeassistant/components/mysensors/light.py @@ -11,8 +11,8 @@ from homeassistant.components.light import ( Light, ) from homeassistant.const import STATE_OFF, STATE_ON -from homeassistant.util.color import rgb_hex_to_rgb_list import homeassistant.util.color as color_util +from homeassistant.util.color import rgb_hex_to_rgb_list SUPPORT_MYSENSORS_RGBW = SUPPORT_COLOR | SUPPORT_WHITE_VALUE diff --git a/homeassistant/components/mysensors/sensor.py b/homeassistant/components/mysensors/sensor.py index a7d1cad98fa..ddad451d20f 100644 --- a/homeassistant/components/mysensors/sensor.py +++ b/homeassistant/components/mysensors/sensor.py @@ -2,10 +2,10 @@ from homeassistant.components import mysensors from homeassistant.components.sensor import DOMAIN from homeassistant.const import ( + ENERGY_KILO_WATT_HOUR, + POWER_WATT, TEMP_CELSIUS, TEMP_FAHRENHEIT, - POWER_WATT, - ENERGY_KILO_WATT_HOUR, ) SENSORS = { diff --git a/homeassistant/components/mysensors/switch.py b/homeassistant/components/mysensors/switch.py index fecec53370b..ec28649d70f 100644 --- a/homeassistant/components/mysensors/switch.py +++ b/homeassistant/components/mysensors/switch.py @@ -1,10 +1,10 @@ """Support for MySensors switches.""" import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components import mysensors from homeassistant.components.switch import DOMAIN, SwitchDevice from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON +import homeassistant.helpers.config_validation as cv from .const import DOMAIN as MYSENSORS_DOMAIN, SERVICE_SEND_IR_CODE From 97cb8a37454e2bb4f6ee9af23778e4e3ae38ea02 Mon Sep 17 00:00:00 2001 From: tetienne Date: Thu, 5 Dec 2019 21:33:56 +0100 Subject: [PATCH 022/677] Increase somfy SCAN_INTERVAL (#29524) - There was too many errors 504 --- homeassistant/components/somfy/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/somfy/__init__.py b/homeassistant/components/somfy/__init__.py index 184e32f1e6d..1368725777b 100644 --- a/homeassistant/components/somfy/__init__.py +++ b/homeassistant/components/somfy/__init__.py @@ -26,7 +26,7 @@ DEVICES = "devices" _LOGGER = logging.getLogger(__name__) -SCAN_INTERVAL = timedelta(seconds=10) +SCAN_INTERVAL = timedelta(seconds=30) DOMAIN = "somfy" From 42688a6e4aa99bd66e8a3aee2a936b1c4eff3e28 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Thu, 5 Dec 2019 21:55:48 +0100 Subject: [PATCH 023/677] Move imports to top for ign_sismologia (#29523) --- homeassistant/components/ign_sismologia/geo_location.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/ign_sismologia/geo_location.py b/homeassistant/components/ign_sismologia/geo_location.py index 8ad045c9f7a..deecc389e7e 100644 --- a/homeassistant/components/ign_sismologia/geo_location.py +++ b/homeassistant/components/ign_sismologia/geo_location.py @@ -3,6 +3,7 @@ from datetime import timedelta import logging from typing import Optional +from georss_ign_sismologia_client import IgnSismologiaFeedManager import voluptuous as vol from homeassistant.components.geo_location import PLATFORM_SCHEMA, GeolocationEvent @@ -87,7 +88,6 @@ class IgnSismologiaFeedEntityManager: minimum_magnitude, ): """Initialize the Feed Entity Manager.""" - from georss_ign_sismologia_client import IgnSismologiaFeedManager self._hass = hass self._feed_manager = IgnSismologiaFeedManager( From 20fdcbadff35891de6dc1965cf81b3954fe71bab Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Thu, 5 Dec 2019 21:56:42 +0100 Subject: [PATCH 024/677] Move imports to top for nextbus (#29520) * Move imports to top for nextbus * Fix test_sensor.py failed tests --- homeassistant/components/nextbus/sensor.py | 10 ++++----- tests/components/nextbus/test_sensor.py | 24 +++++++++++++--------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/nextbus/sensor.py b/homeassistant/components/nextbus/sensor.py index 7622bd133f0..983c2272adc 100644 --- a/homeassistant/components/nextbus/sensor.py +++ b/homeassistant/components/nextbus/sensor.py @@ -1,13 +1,13 @@ """NextBus sensor.""" -import logging from itertools import chain +import logging +from py_nextbus import NextBusClient import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONF_NAME -from homeassistant.const import DEVICE_CLASS_TIMESTAMP +from homeassistant.const import CONF_NAME, DEVICE_CLASS_TIMESTAMP +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util.dt import utc_from_timestamp @@ -94,8 +94,6 @@ def setup_platform(hass, config, add_entities, discovery_info=None): stop = config[CONF_STOP] name = config.get(CONF_NAME) - from py_nextbus import NextBusClient - client = NextBusClient(output_format="json") # Ensures that the tags provided are valid, also logs out valid values diff --git a/tests/components/nextbus/test_sensor.py b/tests/components/nextbus/test_sensor.py index d7c1919dff0..4bac317102a 100644 --- a/tests/components/nextbus/test_sensor.py +++ b/tests/components/nextbus/test_sensor.py @@ -2,11 +2,12 @@ from copy import deepcopy import pytest +from unittest.mock import patch import homeassistant.components.sensor as sensor import homeassistant.components.nextbus.sensor as nextbus -from tests.common import assert_setup_component, async_setup_component, MockDependency +from tests.common import assert_setup_component, async_setup_component VALID_AGENCY = "sf-muni" @@ -54,14 +55,16 @@ async def assert_setup_sensor(hass, config, count=1): @pytest.fixture def mock_nextbus(): """Create a mock py_nextbus module.""" - with MockDependency("py_nextbus") as py_nextbus: - yield py_nextbus + with patch( + "homeassistant.components.nextbus.sensor.NextBusClient" + ) as NextBusClient: + yield NextBusClient @pytest.fixture def mock_nextbus_predictions(mock_nextbus): """Create a mock of NextBusClient predictions.""" - instance = mock_nextbus.NextBusClient.return_value + instance = mock_nextbus.return_value instance.get_predictions_for_multi_stops.return_value = BASIC_RESULTS yield instance.get_predictions_for_multi_stops @@ -70,7 +73,7 @@ def mock_nextbus_predictions(mock_nextbus): @pytest.fixture def mock_nextbus_lists(mock_nextbus): """Mock all list functions in nextbus to test validate logic.""" - instance = mock_nextbus.NextBusClient.return_value + instance = mock_nextbus.return_value instance.get_agency_list.return_value = { "agency": [{"tag": "sf-muni", "title": "San Francisco Muni"}] } @@ -94,17 +97,18 @@ async def test_invalid_config(hass, mock_nextbus, mock_nextbus_lists): async def test_validate_tags(hass, mock_nextbus, mock_nextbus_lists): """Test that additional validation against the API is successful.""" - client = mock_nextbus.NextBusClient() # with self.subTest('Valid everything'): - assert nextbus.validate_tags(client, VALID_AGENCY, VALID_ROUTE, VALID_STOP) + assert nextbus.validate_tags(mock_nextbus(), VALID_AGENCY, VALID_ROUTE, VALID_STOP) # with self.subTest('Invalid agency'): - assert not nextbus.validate_tags(client, "not-valid", VALID_ROUTE, VALID_STOP) + assert not nextbus.validate_tags( + mock_nextbus(), "not-valid", VALID_ROUTE, VALID_STOP + ) # with self.subTest('Invalid route'): - assert not nextbus.validate_tags(client, VALID_AGENCY, "0", VALID_STOP) + assert not nextbus.validate_tags(mock_nextbus(), VALID_AGENCY, "0", VALID_STOP) # with self.subTest('Invalid stop'): - assert not nextbus.validate_tags(client, VALID_AGENCY, VALID_ROUTE, 0) + assert not nextbus.validate_tags(mock_nextbus(), VALID_AGENCY, VALID_ROUTE, 0) async def test_verify_valid_state( From 173966f45921365c6e2687780b29c1df55f5be75 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Fri, 6 Dec 2019 00:20:07 +0100 Subject: [PATCH 025/677] Move imports to top for switcher_kis (#29530) * Move imports to top for switcher_kis * Disabled ungrouped imports if TYPE_CHECKING is true --- .../components/switcher_kis/__init__.py | 4 +- .../components/switcher_kis/switch.py | 25 +++++------ tests/components/switcher_kis/conftest.py | 44 +++++++++++++++---- 3 files changed, 48 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/switcher_kis/__init__.py b/homeassistant/components/switcher_kis/__init__.py index 9f4347d61d2..e7e8d2d270c 100644 --- a/homeassistant/components/switcher_kis/__init__.py +++ b/homeassistant/components/switcher_kis/__init__.py @@ -5,6 +5,8 @@ from datetime import datetime, timedelta from logging import getLogger from typing import Dict, Optional +from aioswitcher.api import SwitcherV2Api +from aioswitcher.bridge import SwitcherV2Bridge import voluptuous as vol from homeassistant.auth.permissions.const import POLICY_EDIT @@ -88,7 +90,6 @@ async def _validate_edit_permission( async def async_setup(hass: HomeAssistantType, config: Dict) -> bool: """Set up the switcher component.""" - from aioswitcher.bridge import SwitcherV2Bridge phone_id = config[DOMAIN][CONF_PHONE_ID] device_id = config[DOMAIN][CONF_DEVICE_ID] @@ -122,7 +123,6 @@ async def async_setup(hass: HomeAssistantType, config: Dict) -> bool: async def async_set_auto_off_service(service: ServiceCallType) -> None: """Use for handling setting device auto-off service calls.""" - from aioswitcher.api import SwitcherV2Api await _validate_edit_permission( hass, service.context, service.data[CONF_ENTITY_ID] diff --git a/homeassistant/components/switcher_kis/switch.py b/homeassistant/components/switcher_kis/switch.py index 454baca4eef..c8eaddcb5bd 100644 --- a/homeassistant/components/switcher_kis/switch.py +++ b/homeassistant/components/switcher_kis/switch.py @@ -1,7 +1,16 @@ """Home Assistant Switcher Component Switch platform.""" from logging import getLogger -from typing import Callable, Dict, TYPE_CHECKING +from typing import TYPE_CHECKING, Callable, Dict + +from aioswitcher.api import SwitcherV2Api +from aioswitcher.consts import ( + COMMAND_OFF, + COMMAND_ON, + STATE_OFF as SWITCHER_STATE_OFF, + STATE_ON as SWITCHER_STATE_ON, + WAITING_TEXT, +) from homeassistant.components.switch import ATTR_CURRENT_POWER_W, SwitchDevice from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -16,6 +25,7 @@ from . import ( SIGNAL_SWITCHER_DEVICE_UPDATE, ) +# pylint: disable=ungrouped-imports if TYPE_CHECKING: from aioswitcher.devices import SwitcherV2Device from aioswitcher.api.messages import SwitcherV2ControlResponseMSG @@ -70,7 +80,6 @@ class SwitcherControl(SwitchDevice): @property def is_on(self) -> bool: """Return True if entity is on.""" - from aioswitcher.consts import STATE_ON as SWITCHER_STATE_ON return self._state == SWITCHER_STATE_ON @@ -82,7 +91,6 @@ class SwitcherControl(SwitchDevice): @property def device_state_attributes(self) -> Dict: """Return the optional state attributes.""" - from aioswitcher.consts import WAITING_TEXT attribs = {} @@ -96,10 +104,6 @@ class SwitcherControl(SwitchDevice): @property def available(self) -> bool: """Return True if entity is available.""" - from aioswitcher.consts import ( - STATE_OFF as SWITCHER_STATE_OFF, - STATE_ON as SWITCHER_STATE_ON, - ) return self._state in [SWITCHER_STATE_ON, SWITCHER_STATE_OFF] @@ -135,13 +139,6 @@ class SwitcherControl(SwitchDevice): async def _control_device(self, send_on: bool) -> None: """Turn the entity on or off.""" - from aioswitcher.api import SwitcherV2Api - from aioswitcher.consts import ( - COMMAND_OFF, - COMMAND_ON, - STATE_OFF as SWITCHER_STATE_OFF, - STATE_ON as SWITCHER_STATE_ON, - ) response: "SwitcherV2ControlResponseMSG" = None async with SwitcherV2Api( diff --git a/tests/components/switcher_kis/conftest.py b/tests/components/switcher_kis/conftest.py index 888ffd46c3b..2b0150cae67 100644 --- a/tests/components/switcher_kis/conftest.py +++ b/tests/components/switcher_kis/conftest.py @@ -103,10 +103,22 @@ def mock_bridge_fixture() -> Generator[None, Any, None]: mock_bridge = CoroutineMock() patchers = [ - patch("aioswitcher.bridge.SwitcherV2Bridge.start", new=mock_bridge), - patch("aioswitcher.bridge.SwitcherV2Bridge.stop", new=mock_bridge), - patch("aioswitcher.bridge.SwitcherV2Bridge.queue", get=mock_queue), - patch("aioswitcher.bridge.SwitcherV2Bridge.running", return_value=True), + patch( + "homeassistant.components.switcher_kis.SwitcherV2Bridge.start", + new=mock_bridge, + ), + patch( + "homeassistant.components.switcher_kis.SwitcherV2Bridge.stop", + new=mock_bridge, + ), + patch( + "homeassistant.components.switcher_kis.SwitcherV2Bridge.queue", + get=mock_queue, + ), + patch( + "homeassistant.components.switcher_kis.SwitcherV2Bridge.running", + return_value=True, + ), ] for patcher in patchers: @@ -127,9 +139,18 @@ def mock_failed_bridge_fixture() -> Generator[None, Any, None]: raise RuntimeError patchers = [ - patch("aioswitcher.bridge.SwitcherV2Bridge.start", return_value=None), - patch("aioswitcher.bridge.SwitcherV2Bridge.stop", return_value=None), - patch("aioswitcher.bridge.SwitcherV2Bridge.queue", get=mock_queue), + patch( + "homeassistant.components.switcher_kis.SwitcherV2Bridge.start", + return_value=None, + ), + patch( + "homeassistant.components.switcher_kis.SwitcherV2Bridge.stop", + return_value=None, + ), + patch( + "homeassistant.components.switcher_kis.SwitcherV2Bridge.queue", + get=mock_queue, + ), ] for patcher in patchers: @@ -147,8 +168,13 @@ def mock_api_fixture() -> Generator[CoroutineMock, Any, None]: mock_api = CoroutineMock() patchers = [ - patch("aioswitcher.api.SwitcherV2Api.connect", new=mock_api), - patch("aioswitcher.api.SwitcherV2Api.disconnect", new=mock_api), + patch( + "homeassistant.components.switcher_kis.SwitcherV2Api.connect", new=mock_api + ), + patch( + "homeassistant.components.switcher_kis.SwitcherV2Api.disconnect", + new=mock_api, + ), ] for patcher in patchers: From 9ba9b3339b32c205d78eeecc0b9a4cf6abfaba55 Mon Sep 17 00:00:00 2001 From: orrpan Date: Fri, 6 Dec 2019 00:23:54 +0100 Subject: [PATCH 026/677] Add full state view for emulated_hue (apps using emulated_hue, 'sleep cycle' and 'sleep as android') (#26650) * Add full state view for emulated_hue * clean and support updated sleep cycle * emulated hue add reuasable logic and cleanup code * emulated hue correct typos * Update hue_api.py * correct error message and update test_hue_api.py * cleanup test_hue_api.py --- .../components/emulated_hue/__init__.py | 4 ++ .../components/emulated_hue/hue_api.py | 69 +++++++++++++++++-- tests/components/emulated_hue/test_hue_api.py | 66 +++++++++++++++++- tests/components/emulated_hue/test_upnp.py | 25 +++++++ 4 files changed, 155 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/emulated_hue/__init__.py b/homeassistant/components/emulated_hue/__init__.py index 791085b46f3..41c23707a03 100644 --- a/homeassistant/components/emulated_hue/__init__.py +++ b/homeassistant/components/emulated_hue/__init__.py @@ -14,11 +14,13 @@ from homeassistant.components.http import real_ip from .hue_api import ( HueUsernameView, + HueUnauthorizedUser, HueAllLightsStateView, HueOneLightStateView, HueOneLightChangeView, HueGroupView, HueAllGroupsStateView, + HueFullStateView, ) from .upnp import DescriptionXmlView, UPNPResponderThread @@ -113,11 +115,13 @@ async def async_setup(hass, yaml_config): DescriptionXmlView(config).register(app, app.router) HueUsernameView().register(app, app.router) + HueUnauthorizedUser().register(app, app.router) HueAllLightsStateView(config).register(app, app.router) HueOneLightStateView(config).register(app, app.router) HueOneLightChangeView(config).register(app, app.router) HueAllGroupsStateView(config).register(app, app.router) HueGroupView(config).register(app, app.router) + HueFullStateView(config).register(app, app.router) upnp_listener = UPNPResponderThread( config.host_ip_addr, diff --git a/homeassistant/components/emulated_hue/hue_api.py b/homeassistant/components/emulated_hue/hue_api.py index e7f15e7fc53..d7db6bb2fe3 100644 --- a/homeassistant/components/emulated_hue/hue_api.py +++ b/homeassistant/components/emulated_hue/hue_api.py @@ -89,6 +89,24 @@ HUE_API_STATE_SAT_MAX = 254 HUE_API_STATE_CT_MIN = 153 # Color temp HUE_API_STATE_CT_MAX = 500 +HUE_API_USERNAME = "12345678901234567890" +UNAUTHORIZED_USER = [ + {"error": {"address": "/", "description": "unauthorized user", "type": "1"}} +] + + +class HueUnauthorizedUser(HomeAssistantView): + """Handle requests to find the emulated hue bridge.""" + + url = "/api" + name = "emulated_hue:api:unauthorized_user" + extra_urls = ["/api/"] + requires_auth = False + + async def get(self, request): + """Handle a GET request.""" + return self.json(UNAUTHORIZED_USER) + class HueUsernameView(HomeAssistantView): """Handle requests to create a username for the emulated hue bridge.""" @@ -111,7 +129,7 @@ class HueUsernameView(HomeAssistantView): if "devicetype" not in data: return self.json_message("devicetype not specified", HTTP_BAD_REQUEST) - return self.json([{"success": {"username": "12345678901234567890"}}]) + return self.json([{"success": {"username": HUE_API_USERNAME}}]) class HueAllGroupsStateView(HomeAssistantView): @@ -181,13 +199,37 @@ class HueAllLightsStateView(HomeAssistantView): if not is_local(request[KEY_REAL_IP]): return self.json_message("Only local IPs allowed", HTTP_UNAUTHORIZED) - hass = request.app["hass"] - json_response = {} + return self.json(create_list_of_entities(self.config, request)) - for entity in hass.states.async_all(): - if self.config.is_entity_exposed(entity): - number = self.config.entity_id_to_number(entity.entity_id) - json_response[number] = entity_to_json(self.config, entity) + +class HueFullStateView(HomeAssistantView): + """Return full state view of emulated hue.""" + + url = "/api/{username}" + name = "emulated_hue:username:state" + requires_auth = False + + def __init__(self, config): + """Initialize the instance of the view.""" + self.config = config + + @core.callback + def get(self, request, username): + """Process a request to get the list of available lights.""" + if not is_local(request[KEY_REAL_IP]): + return self.json_message("only local IPs allowed", HTTP_UNAUTHORIZED) + if username != HUE_API_USERNAME: + return self.json(UNAUTHORIZED_USER) + + json_response = { + "lights": create_list_of_entities(self.config, request), + "config": { + "mac": "00:00:00:00:00:00", + "swversion": "01003542", + "whitelist": {HUE_API_USERNAME: {"name": "HASS BRIDGE"}}, + "ipaddress": f"{self.config.advertise_ip}:{self.config.advertise_port}", + }, + } return self.json(json_response) @@ -673,3 +715,16 @@ def create_hue_success_response(entity_id, attr, value): """Create a success response for an attribute set on a light.""" success_key = f"/lights/{entity_id}/state/{attr}" return {"success": {success_key: value}} + + +def create_list_of_entities(config, request): + """Create a list of all entites.""" + hass = request.app["hass"] + json_response = {} + + for entity in hass.states.async_all(): + if config.is_entity_exposed(entity): + number = config.entity_id_to_number(entity.entity_id) + json_response[number] = entity_to_json(config, entity) + + return json_response diff --git a/tests/components/emulated_hue/test_hue_api.py b/tests/components/emulated_hue/test_hue_api.py index 4f0d70d0046..749493a6ca8 100644 --- a/tests/components/emulated_hue/test_hue_api.py +++ b/tests/components/emulated_hue/test_hue_api.py @@ -25,11 +25,13 @@ from homeassistant.components.emulated_hue.hue_api import ( HUE_API_STATE_BRI, HUE_API_STATE_HUE, HUE_API_STATE_SAT, + HUE_API_USERNAME, HueUsernameView, HueOneLightStateView, HueAllLightsStateView, HueOneLightChangeView, HueAllGroupsStateView, + HueFullStateView, ) from homeassistant.const import STATE_ON, STATE_OFF @@ -188,6 +190,7 @@ def hue_client(loop, hass_hue, aiohttp_client): HueOneLightStateView(config).register(web_app, web_app.router) HueOneLightChangeView(config).register(web_app, web_app.router) HueAllGroupsStateView(config).register(web_app, web_app.router) + HueFullStateView(config).register(web_app, web_app.router) return loop.run_until_complete(aiohttp_client(web_app)) @@ -252,6 +255,49 @@ def test_reachable_for_state(hass_hue, hue_client, state, is_reachable): assert state_json["state"]["reachable"] == is_reachable, state_json +@asyncio.coroutine +def test_discover_full_state(hue_client): + """Test the discovery of full state.""" + result = yield from hue_client.get("/api/" + HUE_API_USERNAME) + + assert result.status == 200 + assert "application/json" in result.headers["content-type"] + + result_json = yield from result.json() + + # Make sure array has correct content + assert "lights" in result_json + assert "lights" not in result_json["config"] + assert "config" in result_json + assert "config" not in result_json["lights"] + + lights_json = result_json["lights"] + config_json = result_json["config"] + + # Make sure array is correct size + assert len(result_json) == 2 + assert len(config_json) == 4 + assert len(lights_json) >= 1 + + # Make sure the config wrapper added to the config is there + assert "mac" in config_json + assert "00:00:00:00:00:00" in config_json["mac"] + + # Make sure the correct version in config + assert "swversion" in config_json + assert "01003542" in config_json["swversion"] + + # Make sure the correct username in config + assert "whitelist" in config_json + assert HUE_API_USERNAME in config_json["whitelist"] + assert "name" in config_json["whitelist"][HUE_API_USERNAME] + assert "HASS BRIDGE" in config_json["whitelist"][HUE_API_USERNAME]["name"] + + # Make sure the correct ip in config + assert "ipaddress" in config_json + assert "127.0.0.1:8300" in config_json["ipaddress"] + + @asyncio.coroutine def test_get_light_state(hass_hue, hue_client): """Test the getting of light state.""" @@ -763,10 +809,26 @@ def perform_put_light_state( async def test_external_ip_blocked(hue_client): """Test external IP blocked.""" + getUrls = [ + "/api/username/groups", + "/api/username", + "/api/username/lights", + "/api/username/lights/light.ceiling_lights", + ] + postUrls = ["/api"] + putUrls = ["/api/username/lights/light.ceiling_lights/state"] with patch( "homeassistant.components.http.real_ip.ip_address", return_value=ip_address("45.45.45.45"), ): - result = await hue_client.get("/api/username/lights") + for getUrl in getUrls: + result = await hue_client.get(getUrl) + assert result.status == 401 - assert result.status == 401 + for postUrl in postUrls: + result = await hue_client.post(postUrl) + assert result.status == 401 + + for putUrl in putUrls: + result = await hue_client.put(putUrl) + assert result.status == 401 diff --git a/tests/components/emulated_hue/test_upnp.py b/tests/components/emulated_hue/test_upnp.py index ead78ad56ca..4cbc79f68a5 100644 --- a/tests/components/emulated_hue/test_upnp.py +++ b/tests/components/emulated_hue/test_upnp.py @@ -82,6 +82,31 @@ class TestEmulatedHue(unittest.TestCase): assert "success" in success_json assert "username" in success_json["success"] + def test_unauthorized_view(self): + """Test unauthorized view.""" + request_json = {"devicetype": "my_device"} + + result = requests.get( + BRIDGE_URL_BASE.format("/api/unauthorized"), + data=json.dumps(request_json), + timeout=5, + ) + + assert result.status_code == 200 + assert "application/json" in result.headers["content-type"] + + resp_json = result.json() + assert len(resp_json) == 1 + success_json = resp_json[0] + assert len(success_json) == 1 + + assert "error" in success_json + error_json = success_json["error"] + assert len(error_json) == 3 + assert "/" in error_json["address"] + assert "unauthorized user" in error_json["description"] + assert "1" in error_json["type"] + def test_valid_username_request(self): """Test request with a valid username.""" request_json = {"invalid_key": "my_device"} From 3b6bc9067f5066c68936cef7e34dd54bb364d13e Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Fri, 6 Dec 2019 02:02:34 +0100 Subject: [PATCH 027/677] Move imports to top for mochad (#29514) * Move imports to top for mochad * Fix test test_light.py for mochad * Fix test test_switch.py for mochad * Make intra package imports relative in switch and light --- homeassistant/components/mochad/__init__.py | 13 +++++------ homeassistant/components/mochad/light.py | 23 ++++++++++---------- homeassistant/components/mochad/switch.py | 24 ++++++++++----------- tests/components/mochad/test_light.py | 4 ++-- tests/components/mochad/test_switch.py | 5 ++--- 5 files changed, 35 insertions(+), 34 deletions(-) diff --git a/homeassistant/components/mochad/__init__.py b/homeassistant/components/mochad/__init__.py index 77426e8ae2c..683681b50a0 100644 --- a/homeassistant/components/mochad/__init__.py +++ b/homeassistant/components/mochad/__init__.py @@ -2,11 +2,16 @@ import logging import threading +from pymochad import controller, exceptions import voluptuous as vol +from homeassistant.const import ( + CONF_HOST, + CONF_PORT, + EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP, +) import homeassistant.helpers.config_validation as cv -from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP -from homeassistant.const import CONF_HOST, CONF_PORT _LOGGER = logging.getLogger(__name__) @@ -37,8 +42,6 @@ def setup(hass, config): host = conf.get(CONF_HOST) port = conf.get(CONF_PORT) - from pymochad import exceptions - global CONTROLLER try: CONTROLLER = MochadCtrl(host, port) @@ -68,8 +71,6 @@ class MochadCtrl: self._host = host self._port = port - from pymochad import controller - self.ctrl = controller.PyMochad(server=self._host, port=self._port) @property diff --git a/homeassistant/components/mochad/light.py b/homeassistant/components/mochad/light.py index 899908c34bd..871caadd95c 100644 --- a/homeassistant/components/mochad/light.py +++ b/homeassistant/components/mochad/light.py @@ -1,30 +1,32 @@ """Support for X10 dimmer over Mochad.""" import logging +from pymochad import device import voluptuous as vol from homeassistant.components.light import ( ATTR_BRIGHTNESS, + PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, Light, - PLATFORM_SCHEMA, ) -from homeassistant.components import mochad -from homeassistant.const import CONF_NAME, CONF_PLATFORM, CONF_DEVICES, CONF_ADDRESS +from homeassistant.const import CONF_ADDRESS, CONF_DEVICES, CONF_NAME, CONF_PLATFORM from homeassistant.helpers import config_validation as cv +from . import CONF_COMM_TYPE, CONTROLLER, DOMAIN, REQ_LOCK + _LOGGER = logging.getLogger(__name__) CONF_BRIGHTNESS_LEVELS = "brightness_levels" PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { - vol.Required(CONF_PLATFORM): mochad.DOMAIN, + vol.Required(CONF_PLATFORM): DOMAIN, CONF_DEVICES: [ { vol.Optional(CONF_NAME): cv.string, vol.Required(CONF_ADDRESS): cv.x10_address, - vol.Optional(mochad.CONF_COMM_TYPE): cv.string, + vol.Optional(CONF_COMM_TYPE): cv.string, vol.Optional(CONF_BRIGHTNESS_LEVELS, default=32): vol.All( vol.Coerce(int), vol.In([32, 64, 256]) ), @@ -37,7 +39,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( def setup_platform(hass, config, add_entities, discovery_info=None): """Set up X10 dimmers over a mochad controller.""" devs = config.get(CONF_DEVICES) - add_entities([MochadLight(hass, mochad.CONTROLLER.ctrl, dev) for dev in devs]) + add_entities([MochadLight(hass, CONTROLLER.ctrl, dev) for dev in devs]) return True @@ -46,12 +48,11 @@ class MochadLight(Light): def __init__(self, hass, ctrl, dev): """Initialize a Mochad Light Device.""" - from pymochad import device self._controller = ctrl self._address = dev[CONF_ADDRESS] self._name = dev.get(CONF_NAME, f"x10_light_dev_{self._address}") - self._comm_type = dev.get(mochad.CONF_COMM_TYPE, "pl") + self._comm_type = dev.get(CONF_COMM_TYPE, "pl") self.light = device.Device(ctrl, self._address, comm_type=self._comm_type) self._brightness = 0 self._state = self._get_device_status() @@ -64,7 +65,7 @@ class MochadLight(Light): def _get_device_status(self): """Get the status of the light from mochad.""" - with mochad.REQ_LOCK: + with REQ_LOCK: status = self.light.get_status().rstrip() return status == "on" @@ -106,7 +107,7 @@ class MochadLight(Light): def turn_on(self, **kwargs): """Send the command to turn the light on.""" brightness = kwargs.get(ATTR_BRIGHTNESS, 255) - with mochad.REQ_LOCK: + with REQ_LOCK: if self._brightness_levels > 32: out_brightness = self._calculate_brightness_value(brightness) self.light.send_cmd(f"xdim {out_brightness}") @@ -124,7 +125,7 @@ class MochadLight(Light): def turn_off(self, **kwargs): """Send the command to turn the light on.""" - with mochad.REQ_LOCK: + with REQ_LOCK: self.light.send_cmd("off") self._controller.read_data() # There is no persistence for X10 modules so we need to prepare diff --git a/homeassistant/components/mochad/switch.py b/homeassistant/components/mochad/switch.py index 0713d50eb4b..14fb601f919 100644 --- a/homeassistant/components/mochad/switch.py +++ b/homeassistant/components/mochad/switch.py @@ -1,24 +1,27 @@ """Support for X10 switch over Mochad.""" import logging +from pymochad import device +from pymochad.exceptions import MochadException import voluptuous as vol -from homeassistant.components import mochad from homeassistant.components.switch import SwitchDevice -from homeassistant.const import CONF_NAME, CONF_DEVICES, CONF_PLATFORM, CONF_ADDRESS +from homeassistant.const import CONF_ADDRESS, CONF_DEVICES, CONF_NAME, CONF_PLATFORM from homeassistant.helpers import config_validation as cv +from . import CONF_COMM_TYPE, CONTROLLER, DOMAIN, REQ_LOCK + _LOGGER = logging.getLogger(__name__) PLATFORM_SCHEMA = vol.Schema( { - vol.Required(CONF_PLATFORM): mochad.DOMAIN, + vol.Required(CONF_PLATFORM): DOMAIN, CONF_DEVICES: [ { vol.Optional(CONF_NAME): cv.string, vol.Required(CONF_ADDRESS): cv.x10_address, - vol.Optional(mochad.CONF_COMM_TYPE): cv.string, + vol.Optional(CONF_COMM_TYPE): cv.string, } ], } @@ -28,7 +31,7 @@ PLATFORM_SCHEMA = vol.Schema( def setup_platform(hass, config, add_entities, discovery_info=None): """Set up X10 switches over a mochad controller.""" devs = config.get(CONF_DEVICES) - add_entities([MochadSwitch(hass, mochad.CONTROLLER.ctrl, dev) for dev in devs]) + add_entities([MochadSwitch(hass, CONTROLLER.ctrl, dev) for dev in devs]) return True @@ -37,12 +40,11 @@ class MochadSwitch(SwitchDevice): def __init__(self, hass, ctrl, dev): """Initialize a Mochad Switch Device.""" - from pymochad import device self._controller = ctrl self._address = dev[CONF_ADDRESS] self._name = dev.get(CONF_NAME, "x10_switch_dev_%s" % self._address) - self._comm_type = dev.get(mochad.CONF_COMM_TYPE, "pl") + self._comm_type = dev.get(CONF_COMM_TYPE, "pl") self.switch = device.Device(ctrl, self._address, comm_type=self._comm_type) # Init with false to avoid locking HA for long on CM19A (goes from rf # to pl via TM751, but not other way around) @@ -58,10 +60,9 @@ class MochadSwitch(SwitchDevice): def turn_on(self, **kwargs): """Turn the switch on.""" - from pymochad.exceptions import MochadException _LOGGER.debug("Reconnect %s:%s", self._controller.server, self._controller.port) - with mochad.REQ_LOCK: + with REQ_LOCK: try: # Recycle socket on new command to recover mochad connection self._controller.reconnect() @@ -75,10 +76,9 @@ class MochadSwitch(SwitchDevice): def turn_off(self, **kwargs): """Turn the switch off.""" - from pymochad.exceptions import MochadException _LOGGER.debug("Reconnect %s:%s", self._controller.server, self._controller.port) - with mochad.REQ_LOCK: + with REQ_LOCK: try: # Recycle socket on new command to recover mochad connection self._controller.reconnect() @@ -92,7 +92,7 @@ class MochadSwitch(SwitchDevice): def _get_device_status(self): """Get the status of the switch from mochad.""" - with mochad.REQ_LOCK: + with REQ_LOCK: status = self.switch.get_status().rstrip() return status == "on" diff --git a/tests/components/mochad/test_light.py b/tests/components/mochad/test_light.py index 7cb4ecb3cbc..631c5b40734 100644 --- a/tests/components/mochad/test_light.py +++ b/tests/components/mochad/test_light.py @@ -14,8 +14,8 @@ from tests.common import get_test_home_assistant @pytest.fixture(autouse=True) def pymochad_mock(): """Mock pymochad.""" - with mock.patch.dict("sys.modules", {"pymochad": mock.MagicMock()}): - yield + with mock.patch("homeassistant.components.mochad.light.device") as device: + yield device class TestMochadSwitchSetup(unittest.TestCase): diff --git a/tests/components/mochad/test_switch.py b/tests/components/mochad/test_switch.py index 5fc3d4ee415..8c0dc3554db 100644 --- a/tests/components/mochad/test_switch.py +++ b/tests/components/mochad/test_switch.py @@ -14,9 +14,8 @@ from tests.common import get_test_home_assistant @pytest.fixture(autouse=True) def pymochad_mock(): """Mock pymochad.""" - with mock.patch.dict( - "sys.modules", - {"pymochad": mock.MagicMock(), "pymochad.exceptions": mock.MagicMock()}, + with mock.patch("homeassistant.components.mochad.switch.device"), mock.patch( + "homeassistant.components.mochad.switch.MochadException" ): yield From dc911ecc5bce9778d163c285d848daf3ffbb6f3d Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Thu, 5 Dec 2019 23:31:18 -0500 Subject: [PATCH 028/677] Add input_boolean reload service (#29379) * Add input_boolean reload service. * Add reload test. * Address comments. Register reload service as an admin service Setup platform even if there're no entities --- .../components/input_boolean/__init__.py | 53 +++++++++--- .../components/input_boolean/services.yaml | 3 + tests/components/input_boolean/test_init.py | 80 +++++++++++++++++-- 3 files changed, 118 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/input_boolean/__init__.py b/homeassistant/components/input_boolean/__init__.py index 4a32ad16797..326244a7e4c 100644 --- a/homeassistant/components/input_boolean/__init__.py +++ b/homeassistant/components/input_boolean/__init__.py @@ -6,16 +6,18 @@ import voluptuous as vol from homeassistant.const import ( CONF_ICON, CONF_NAME, + SERVICE_RELOAD, + SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON, - SERVICE_TOGGLE, STATE_ON, ) -from homeassistant.loader import bind_hass import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.restore_state import RestoreEntity +import homeassistant.helpers.service +from homeassistant.loader import bind_hass DOMAIN = "input_boolean" @@ -41,6 +43,8 @@ CONFIG_SCHEMA = vol.Schema( extra=vol.ALLOW_EXTRA, ) +RELOAD_SERVICE_SCHEMA = vol.Schema({}) + @bind_hass def is_on(hass, entity_id): @@ -52,6 +56,39 @@ async def async_setup(hass, config): """Set up an input boolean.""" component = EntityComponent(_LOGGER, DOMAIN, hass) + entities = await _async_process_config(config) + + async def reload_service_handler(service_call): + """Remove all input booleans and load new ones from config.""" + conf = await component.async_prepare_reload() + if conf is None: + return + new_entities = await _async_process_config(conf) + if new_entities: + await component.async_add_entities(new_entities) + + homeassistant.helpers.service.async_register_admin_service( + hass, + DOMAIN, + SERVICE_RELOAD, + reload_service_handler, + schema=RELOAD_SERVICE_SCHEMA, + ) + + component.async_register_entity_service(SERVICE_TURN_ON, {}, "async_turn_on") + + component.async_register_entity_service(SERVICE_TURN_OFF, {}, "async_turn_off") + + component.async_register_entity_service(SERVICE_TOGGLE, {}, "async_toggle") + + if entities: + await component.async_add_entities(entities) + + return True + + +async def _async_process_config(config): + """Process config and create list of entities.""" entities = [] for object_id, cfg in config[DOMAIN].items(): @@ -64,17 +101,7 @@ async def async_setup(hass, config): entities.append(InputBoolean(object_id, name, initial, icon)) - if not entities: - return False - - component.async_register_entity_service(SERVICE_TURN_ON, {}, "async_turn_on") - - component.async_register_entity_service(SERVICE_TURN_OFF, {}, "async_turn_off") - - component.async_register_entity_service(SERVICE_TOGGLE, {}, "async_toggle") - - await component.async_add_entities(entities) - return True + return entities class InputBoolean(ToggleEntity, RestoreEntity): diff --git a/homeassistant/components/input_boolean/services.yaml b/homeassistant/components/input_boolean/services.yaml index e49d46c9b86..e391c15d3a8 100644 --- a/homeassistant/components/input_boolean/services.yaml +++ b/homeassistant/components/input_boolean/services.yaml @@ -10,3 +10,6 @@ turn_on: description: Turns on an input boolean. fields: entity_id: {description: Entity id of the input boolean to turn on., example: input_boolean.notify_alerts} +reload: + description: Reload the input_boolean configuration. + diff --git a/tests/components/input_boolean/test_init.py b/tests/components/input_boolean/test_init.py index def8db9b35c..ed5e927c2ca 100644 --- a/tests/components/input_boolean/test_init.py +++ b/tests/components/input_boolean/test_init.py @@ -2,20 +2,22 @@ # pylint: disable=protected-access import asyncio import logging +from unittest.mock import patch -from homeassistant.core import CoreState, State, Context -from homeassistant.setup import async_setup_component -from homeassistant.components.input_boolean import is_on, CONF_INITIAL, DOMAIN +from homeassistant.components.input_boolean import CONF_INITIAL, DOMAIN, is_on from homeassistant.const import ( - STATE_ON, - STATE_OFF, ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, ATTR_ICON, + SERVICE_RELOAD, SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, ) +from homeassistant.core import Context, CoreState, State +from homeassistant.setup import async_setup_component from tests.common import mock_component, mock_restore_cache @@ -165,3 +167,71 @@ async def test_input_boolean_context(hass, hass_admin_user): assert state2 is not None assert state.state != state2.state assert state2.context.user_id == hass_admin_user.id + + +async def test_reload(hass, hass_admin_user): + """Test reload service.""" + count_start = len(hass.states.async_entity_ids()) + + _LOGGER.debug("ENTITIES @ start: %s", hass.states.async_entity_ids()) + + assert await async_setup_component( + hass, + DOMAIN, + { + DOMAIN: { + "test_1": None, + "test_2": {"name": "Hello World", "icon": "mdi:work", "initial": True}, + } + }, + ) + + _LOGGER.debug("ENTITIES: %s", hass.states.async_entity_ids()) + + assert count_start + 2 == len(hass.states.async_entity_ids()) + + state_1 = hass.states.get("input_boolean.test_1") + state_2 = hass.states.get("input_boolean.test_2") + state_3 = hass.states.get("input_boolean.test_3") + + assert state_1 is not None + assert state_2 is not None + assert state_3 is None + assert STATE_ON == state_2.state + + with patch( + "homeassistant.config.load_yaml_config_file", + autospec=True, + return_value={ + DOMAIN: { + "test_2": { + "name": "Hello World reloaded", + "icon": "mdi:work_reloaded", + "initial": False, + }, + "test_3": None, + } + }, + ): + with patch("homeassistant.config.find_config_file", return_value=""): + await hass.services.async_call( + DOMAIN, + SERVICE_RELOAD, + blocking=True, + context=Context(user_id=hass_admin_user.id), + ) + await hass.async_block_till_done() + + assert count_start + 2 == len(hass.states.async_entity_ids()) + + state_1 = hass.states.get("input_boolean.test_1") + state_2 = hass.states.get("input_boolean.test_2") + state_3 = hass.states.get("input_boolean.test_3") + + assert state_1 is None + assert state_2 is not None + assert state_3 is not None + + assert STATE_OFF == state_2.state + assert "Hello World reloaded" == state_2.attributes.get(ATTR_FRIENDLY_NAME) + assert "mdi:work_reloaded" == state_2.attributes.get(ATTR_ICON) From 8def0326dd326ccdd6a117d20ccc895c46c8290d Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Fri, 6 Dec 2019 06:08:08 +0100 Subject: [PATCH 029/677] Move imports to top for izone (#29508) * Move imports to top for izone * Isorted all imports, fixed tests for config_flow --- homeassistant/components/izone/__init__.py | 2 +- homeassistant/components/izone/climate.py | 26 +++++++++---------- homeassistant/components/izone/config_flow.py | 7 +++-- homeassistant/components/izone/discovery.py | 5 ++-- tests/components/izone/test_config_flow.py | 6 ++--- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/izone/__init__.py b/homeassistant/components/izone/__init__.py index 6fecbc1f3a6..0e5dcddbc48 100644 --- a/homeassistant/components/izone/__init__.py +++ b/homeassistant/components/izone/__init__.py @@ -13,7 +13,7 @@ from homeassistant.const import CONF_EXCLUDE import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType, HomeAssistantType -from .const import IZONE, DATA_CONFIG +from .const import DATA_CONFIG, IZONE from .discovery import async_start_discovery_service, async_stop_discovery_service _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/izone/climate.py b/homeassistant/components/izone/climate.py index c932c66627b..b80dfc2542f 100644 --- a/homeassistant/components/izone/climate.py +++ b/homeassistant/components/izone/climate.py @@ -1,22 +1,21 @@ """Support for the iZone HVAC.""" import logging -from typing import Optional, List +from typing import List, Optional -from pizone import Zone, Controller +from pizone import Controller, Zone -from homeassistant.core import callback from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( - HVAC_MODE_HEAT_COOL, + FAN_AUTO, + FAN_HIGH, + FAN_LOW, + FAN_MEDIUM, HVAC_MODE_COOL, HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, - FAN_LOW, - FAN_MEDIUM, - FAN_HIGH, - FAN_AUTO, PRESET_ECO, PRESET_NONE, SUPPORT_FAN_MODE, @@ -25,23 +24,24 @@ from homeassistant.components.climate.const import ( ) from homeassistant.const import ( ATTR_TEMPERATURE, + CONF_EXCLUDE, PRECISION_HALVES, TEMP_CELSIUS, - CONF_EXCLUDE, ) +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.temperature import display_temp as show_temp from homeassistant.helpers.typing import ConfigType, HomeAssistantType -from homeassistant.helpers.dispatcher import async_dispatcher_connect from .const import ( + DATA_CONFIG, DATA_DISCOVERY_SERVICE, - IZONE, - DISPATCH_CONTROLLER_DISCOVERED, DISPATCH_CONTROLLER_DISCONNECTED, + DISPATCH_CONTROLLER_DISCOVERED, DISPATCH_CONTROLLER_RECONNECTED, DISPATCH_CONTROLLER_UPDATE, DISPATCH_ZONE_UPDATE, - DATA_CONFIG, + IZONE, ) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/izone/config_flow.py b/homeassistant/components/izone/config_flow.py index eb57a36a2bb..add1bb47a54 100644 --- a/homeassistant/components/izone/config_flow.py +++ b/homeassistant/components/izone/config_flow.py @@ -1,7 +1,7 @@ """Config flow for izone.""" -import logging import asyncio +import logging from async_timeout import timeout @@ -9,14 +9,13 @@ from homeassistant import config_entries from homeassistant.helpers import config_entry_flow from homeassistant.helpers.dispatcher import async_dispatcher_connect -from .const import IZONE, TIMEOUT_DISCOVERY, DISPATCH_CONTROLLER_DISCOVERED - +from .const import DISPATCH_CONTROLLER_DISCOVERED, IZONE, TIMEOUT_DISCOVERY +from .discovery import async_start_discovery_service, async_stop_discovery_service _LOGGER = logging.getLogger(__name__) async def _async_has_devices(hass): - from .discovery import async_start_discovery_service, async_stop_discovery_service controller_ready = asyncio.Event() async_dispatcher_connect( diff --git a/homeassistant/components/izone/discovery.py b/homeassistant/components/izone/discovery.py index 3630c28605b..c49144f1db9 100644 --- a/homeassistant/components/izone/discovery.py +++ b/homeassistant/components/izone/discovery.py @@ -1,17 +1,18 @@ """Internal discovery service for iZone AC.""" import logging + import pizone from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.helpers import aiohttp_client -from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.typing import HomeAssistantType from .const import ( DATA_DISCOVERY_SERVICE, - DISPATCH_CONTROLLER_DISCOVERED, DISPATCH_CONTROLLER_DISCONNECTED, + DISPATCH_CONTROLLER_DISCOVERED, DISPATCH_CONTROLLER_RECONNECTED, DISPATCH_CONTROLLER_UPDATE, DISPATCH_ZONE_UPDATE, diff --git a/tests/components/izone/test_config_flow.py b/tests/components/izone/test_config_flow.py index faa920271e3..b5f9aa41c80 100644 --- a/tests/components/izone/test_config_flow.py +++ b/tests/components/izone/test_config_flow.py @@ -33,9 +33,9 @@ async def test_not_found(hass, mock_disco): """Test not finding iZone controller.""" with patch( - "homeassistant.components.izone.discovery.async_start_discovery_service" + "homeassistant.components.izone.config_flow.async_start_discovery_service" ) as start_disco, patch( - "homeassistant.components.izone.discovery.async_stop_discovery_service", + "homeassistant.components.izone.config_flow.async_stop_discovery_service", return_value=mock_coro(), ) as stop_disco: start_disco.side_effect = _mock_start_discovery(hass, mock_disco) @@ -62,7 +62,7 @@ async def test_found(hass, mock_disco): "homeassistant.components.izone.climate.async_setup_entry", return_value=mock_coro(True), ) as mock_setup, patch( - "homeassistant.components.izone.discovery.async_start_discovery_service" + "homeassistant.components.izone.config_flow.async_start_discovery_service" ) as start_disco, patch( "homeassistant.components.izone.async_start_discovery_service", return_value=mock_coro(), From fcf18aca3403b553fee6aa456fd8abc1c5a74480 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Fri, 6 Dec 2019 06:10:29 +0100 Subject: [PATCH 030/677] Move imports to top for modbus (#29515) * Move imports to top for modbus * Include imports for TCP and UDP ModbusClients --- homeassistant/components/modbus/__init__.py | 19 ++++++------------- homeassistant/components/modbus/climate.py | 2 +- homeassistant/components/modbus/sensor.py | 2 +- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/modbus/__init__.py b/homeassistant/components/modbus/__init__.py index a6e901af749..823703ac4c9 100644 --- a/homeassistant/components/modbus/__init__.py +++ b/homeassistant/components/modbus/__init__.py @@ -2,6 +2,8 @@ import logging import threading +from pymodbus.client.sync import ModbusSerialClient, ModbusTcpClient, ModbusUdpClient +from pymodbus.transaction import ModbusRtuFramer import voluptuous as vol from homeassistant.const import ( @@ -91,9 +93,7 @@ def setup_client(client_config): client_type = client_config[CONF_TYPE] if client_type == "serial": - from pymodbus.client.sync import ModbusSerialClient as ModbusClient - - return ModbusClient( + return ModbusSerialClient( method=client_config[CONF_METHOD], port=client_config[CONF_PORT], baudrate=client_config[CONF_BAUDRATE], @@ -103,27 +103,20 @@ def setup_client(client_config): timeout=client_config[CONF_TIMEOUT], ) if client_type == "rtuovertcp": - from pymodbus.client.sync import ModbusTcpClient as ModbusClient - from pymodbus.transaction import ModbusRtuFramer - - return ModbusClient( + return ModbusTcpClient( host=client_config[CONF_HOST], port=client_config[CONF_PORT], framer=ModbusRtuFramer, timeout=client_config[CONF_TIMEOUT], ) if client_type == "tcp": - from pymodbus.client.sync import ModbusTcpClient as ModbusClient - - return ModbusClient( + return ModbusTcpClient( host=client_config[CONF_HOST], port=client_config[CONF_PORT], timeout=client_config[CONF_TIMEOUT], ) if client_type == "udp": - from pymodbus.client.sync import ModbusUdpClient as ModbusClient - - return ModbusClient( + return ModbusUdpClient( host=client_config[CONF_HOST], port=client_config[CONF_PORT], timeout=client_config[CONF_TIMEOUT], diff --git a/homeassistant/components/modbus/climate.py b/homeassistant/components/modbus/climate.py index c6764482d96..99ea686543d 100644 --- a/homeassistant/components/modbus/climate.py +++ b/homeassistant/components/modbus/climate.py @@ -6,8 +6,8 @@ import voluptuous as vol from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice from homeassistant.components.climate.const import ( - SUPPORT_TARGET_TEMPERATURE, HVAC_MODE_AUTO, + SUPPORT_TARGET_TEMPERATURE, ) from homeassistant.const import ( ATTR_TEMPERATURE, diff --git a/homeassistant/components/modbus/sensor.py b/homeassistant/components/modbus/sensor.py index 1a5c71812d6..86f6445b8d6 100644 --- a/homeassistant/components/modbus/sensor.py +++ b/homeassistant/components/modbus/sensor.py @@ -1,8 +1,8 @@ """Support for Modbus Register sensors.""" import logging import struct - from typing import Any, Union + import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA From 33542f0e5e61374ccc01622f8baf8581bbf55b42 Mon Sep 17 00:00:00 2001 From: Malte Franken Date: Fri, 6 Dec 2019 16:55:42 +1100 Subject: [PATCH 031/677] Bump georss_generic_client to 0.3 (#29532) * bump version of georss_generic_client library * updated requirements --- homeassistant/components/geo_rss_events/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/geo_rss_events/manifest.json b/homeassistant/components/geo_rss_events/manifest.json index c681807ad01..b8949286dea 100644 --- a/homeassistant/components/geo_rss_events/manifest.json +++ b/homeassistant/components/geo_rss_events/manifest.json @@ -3,7 +3,7 @@ "name": "Geo RSS events", "documentation": "https://www.home-assistant.io/integrations/geo_rss_events", "requirements": [ - "georss_generic_client==0.2" + "georss_generic_client==0.3" ], "dependencies": [], "codeowners": [ diff --git a/requirements_all.txt b/requirements_all.txt index c6dad0573ca..1fd51ad5d00 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -563,7 +563,7 @@ geojson_client==0.4 geopy==1.19.0 # homeassistant.components.geo_rss_events -georss_generic_client==0.2 +georss_generic_client==0.3 # homeassistant.components.ign_sismologia georss_ign_sismologia_client==0.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 484dba217a8..6b5bcf96d2d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -181,7 +181,7 @@ geojson_client==0.4 geopy==1.19.0 # homeassistant.components.geo_rss_events -georss_generic_client==0.2 +georss_generic_client==0.3 # homeassistant.components.ign_sismologia georss_ign_sismologia_client==0.2 From e2a9c652ab9222a11d6b1b5e2b6ce03493960f58 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 6 Dec 2019 09:08:06 +0100 Subject: [PATCH 032/677] Bump pytest to 5.3.1 (#29535) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 9d63b59f62a..70e78222644 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -14,6 +14,6 @@ pytest-aiohttp==0.3.0 pytest-cov==2.8.1 pytest-sugar==0.9.2 pytest-timeout==1.3.3 -pytest==5.3.0 +pytest==5.3.1 requests_mock==1.7.0 responses==0.10.6 From 0aace1da5519dd46ab97fdc266daae4349292adb Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Fri, 6 Dec 2019 09:08:54 +0100 Subject: [PATCH 033/677] Move imports to top for nx584 (#29537) --- homeassistant/components/nx584/alarm_control_panel.py | 2 +- homeassistant/components/nx584/binary_sensor.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/nx584/alarm_control_panel.py b/homeassistant/components/nx584/alarm_control_panel.py index 62bc7ae32bb..7a064ef0d00 100644 --- a/homeassistant/components/nx584/alarm_control_panel.py +++ b/homeassistant/components/nx584/alarm_control_panel.py @@ -1,6 +1,7 @@ """Support for NX584 alarm control panels.""" import logging +from nx584 import client import requests import voluptuous as vol @@ -56,7 +57,6 @@ class NX584Alarm(alarm.AlarmControlPanel): def __init__(self, hass, url, name): """Init the nx584 alarm panel.""" - from nx584 import client self._hass = hass self._name = name diff --git a/homeassistant/components/nx584/binary_sensor.py b/homeassistant/components/nx584/binary_sensor.py index 6f1c66d8d87..f6006ff2de4 100644 --- a/homeassistant/components/nx584/binary_sensor.py +++ b/homeassistant/components/nx584/binary_sensor.py @@ -3,13 +3,14 @@ import logging import threading import time +from nx584 import client as nx584_client import requests import voluptuous as vol from homeassistant.components.binary_sensor import ( DEVICE_CLASSES, - BinarySensorDevice, PLATFORM_SCHEMA, + BinarySensorDevice, ) from homeassistant.const import CONF_HOST, CONF_PORT import homeassistant.helpers.config_validation as cv @@ -39,7 +40,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the NX584 binary sensor platform.""" - from nx584 import client as nx584_client host = config.get(CONF_HOST) port = config.get(CONF_PORT) From a8571485601114b908204f9d73af2b5dd30c9ed1 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Fri, 6 Dec 2019 09:09:45 +0100 Subject: [PATCH 034/677] Move imports to top for pjlink (#29540) --- homeassistant/components/pjlink/media_player.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/pjlink/media_player.py b/homeassistant/components/pjlink/media_player.py index ea35fe7fb75..e93e6e5fb20 100644 --- a/homeassistant/components/pjlink/media_player.py +++ b/homeassistant/components/pjlink/media_player.py @@ -1,9 +1,11 @@ """Support for controlling projector via the PJLink protocol.""" import logging +from pypjlink import MUTE_AUDIO, Projector +from pypjlink.projector import ProjectorError import voluptuous as vol -from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice from homeassistant.components.media_player.const import ( SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, @@ -90,7 +92,6 @@ class PjLinkDevice(MediaPlayerDevice): def projector(self): """Create PJLink Projector instance.""" - from pypjlink import Projector projector = Projector.from_address(self._host, self._port, self._encoding) projector.authenticate(self._password) @@ -98,7 +99,6 @@ class PjLinkDevice(MediaPlayerDevice): def update(self): """Get the latest state from the device.""" - from pypjlink.projector import ProjectorError with self.projector() as projector: try: @@ -171,8 +171,6 @@ class PjLinkDevice(MediaPlayerDevice): def mute_volume(self, mute): """Mute (true) of unmute (false) media player.""" with self.projector() as projector: - from pypjlink import MUTE_AUDIO - projector.set_mute(MUTE_AUDIO, mute) def select_source(self, source): From 1bb499aec21f34688f1d17eab771458a34af3e83 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Fri, 6 Dec 2019 09:11:07 +0100 Subject: [PATCH 035/677] Move imports to top for smhi (#29545) --- homeassistant/components/smhi/config_flow.py | 2 +- homeassistant/components/smhi/weather.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/smhi/config_flow.py b/homeassistant/components/smhi/config_flow.py index 3b60cb66165..2c04896497a 100644 --- a/homeassistant/components/smhi/config_flow.py +++ b/homeassistant/components/smhi/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure SMHI component.""" +from smhi.smhi_lib import Smhi, SmhiForecastException import voluptuous as vol from homeassistant import config_entries @@ -96,7 +97,6 @@ class SmhiFlowHandler(config_entries.ConfigFlow): async def _check_location(self, longitude: str, latitude: str) -> bool: """Return true if location is ok.""" - from smhi.smhi_lib import Smhi, SmhiForecastException try: session = aiohttp_client.async_get_clientsession(self.hass) diff --git a/homeassistant/components/smhi/weather.py b/homeassistant/components/smhi/weather.py index 5f6722b72a6..574b8d85767 100644 --- a/homeassistant/components/smhi/weather.py +++ b/homeassistant/components/smhi/weather.py @@ -6,6 +6,8 @@ from typing import Dict, List import aiohttp import async_timeout +from smhi import Smhi +from smhi.smhi_lib import SmhiForecastException from homeassistant.components.weather import ( ATTR_FORECAST_CONDITION, @@ -90,7 +92,6 @@ class SmhiWeather(WeatherEntity): session: aiohttp.ClientSession = None, ) -> None: """Initialize the SMHI weather entity.""" - from smhi import Smhi self._name = name self._latitude = latitude @@ -107,7 +108,6 @@ class SmhiWeather(WeatherEntity): @Throttle(MIN_TIME_BETWEEN_UPDATES) async def async_update(self) -> None: """Refresh the forecast data from SMHI weather API.""" - from smhi.smhi_lib import SmhiForecastException def fail(): """Postpone updates.""" From d5419b77f9c245e5af006143eb55ae4dda3f174e Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Fri, 6 Dec 2019 09:13:44 +0100 Subject: [PATCH 036/677] Move imports to top for sleepiq (#29544) --- homeassistant/components/sleepiq/__init__.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/sleepiq/__init__.py b/homeassistant/components/sleepiq/__init__.py index 7035299709d..2b4d9d010a3 100644 --- a/homeassistant/components/sleepiq/__init__.py +++ b/homeassistant/components/sleepiq/__init__.py @@ -1,13 +1,14 @@ """Support for SleepIQ from SleepNumber.""" -import logging from datetime import timedelta +import logging +from sleepyq import Sleepyq import voluptuous as vol -import homeassistant.helpers.config_validation as cv +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers import discovery +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -from homeassistant.const import CONF_USERNAME, CONF_PASSWORD from homeassistant.util import Throttle DOMAIN = "sleepiq" @@ -47,8 +48,6 @@ def setup(hass, config): """ global DATA - from sleepyq import Sleepyq - username = config[DOMAIN][CONF_USERNAME] password = config[DOMAIN][CONF_PASSWORD] client = Sleepyq(username, password) From ec3ffe309ac29da3cc527a73ef0e63ab5a6a1aea Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Fri, 6 Dec 2019 10:40:38 +0100 Subject: [PATCH 037/677] Move imports to top for toon (#29553) --- homeassistant/components/toon/__init__.py | 12 ++++++------ homeassistant/components/toon/binary_sensor.py | 6 +++--- homeassistant/components/toon/climate.py | 6 +++--- homeassistant/components/toon/config_flow.py | 17 ++++++++--------- homeassistant/components/toon/sensor.py | 8 ++++---- tests/components/toon/test_config_flow.py | 16 ++++++++-------- 6 files changed, 32 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/toon/__init__.py b/homeassistant/components/toon/__init__.py index 19466ba49c4..348826a1264 100644 --- a/homeassistant/components/toon/__init__.py +++ b/homeassistant/components/toon/__init__.py @@ -1,17 +1,18 @@ """Support for Toon van Eneco devices.""" +from functools import partial import logging from typing import Any, Dict -from functools import partial +from toonapilib import Toon import voluptuous as vol +from homeassistant.const import CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME from homeassistant.core import callback -from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, CONF_SCAN_INTERVAL from homeassistant.helpers import config_validation as cv, device_registry as dr +from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send from homeassistant.helpers.entity import Entity -from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.helpers.event import async_track_time_interval -from homeassistant.helpers.dispatcher import dispatcher_send, async_dispatcher_connect +from homeassistant.helpers.typing import ConfigType, HomeAssistantType from . import config_flow # noqa: F401 from .const import ( @@ -19,10 +20,10 @@ from .const import ( CONF_CLIENT_SECRET, CONF_DISPLAY, CONF_TENANT, + DATA_TOON, DATA_TOON_CLIENT, DATA_TOON_CONFIG, DATA_TOON_UPDATED, - DATA_TOON, DEFAULT_SCAN_INTERVAL, DOMAIN, ) @@ -63,7 +64,6 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: async def async_setup_entry(hass: HomeAssistantType, entry: ConfigType) -> bool: """Set up Toon from a config entry.""" - from toonapilib import Toon conf = hass.data.get(DATA_TOON_CONFIG) diff --git a/homeassistant/components/toon/binary_sensor.py b/homeassistant/components/toon/binary_sensor.py index 9962e2c32d3..7cf52919efe 100644 --- a/homeassistant/components/toon/binary_sensor.py +++ b/homeassistant/components/toon/binary_sensor.py @@ -8,11 +8,11 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType from . import ( - ToonData, - ToonEntity, - ToonDisplayDeviceEntity, ToonBoilerDeviceEntity, ToonBoilerModuleDeviceEntity, + ToonData, + ToonDisplayDeviceEntity, + ToonEntity, ) from .const import DATA_TOON, DOMAIN diff --git a/homeassistant/components/toon/climate.py b/homeassistant/components/toon/climate.py index cfe07adfda3..9ce9991c371 100644 --- a/homeassistant/components/toon/climate.py +++ b/homeassistant/components/toon/climate.py @@ -5,6 +5,8 @@ from typing import Any, Dict, List, Optional from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, HVAC_MODE_HEAT, PRESET_AWAY, PRESET_COMFORT, @@ -12,8 +14,6 @@ from homeassistant.components.climate.const import ( PRESET_SLEEP, SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS @@ -21,8 +21,8 @@ from homeassistant.helpers.typing import HomeAssistantType from . import ToonData, ToonDisplayDeviceEntity from .const import ( - DATA_TOON_CLIENT, DATA_TOON, + DATA_TOON_CLIENT, DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, DOMAIN, diff --git a/homeassistant/components/toon/config_flow.py b/homeassistant/components/toon/config_flow.py index 62c141b003a..ce4f347eaf2 100644 --- a/homeassistant/components/toon/config_flow.py +++ b/homeassistant/components/toon/config_flow.py @@ -1,8 +1,15 @@ """Config flow to configure the Toon component.""" from collections import OrderedDict -import logging from functools import partial +import logging +from toonapilib import Toon +from toonapilib.toonapilibexceptions import ( + AgreementsRetrievalError, + InvalidConsumerKey, + InvalidConsumerSecret, + InvalidCredentials, +) import voluptuous as vol from homeassistant import config_entries @@ -67,13 +74,6 @@ class ToonFlowHandler(config_entries.ConfigFlow): async def async_step_authenticate(self, user_input=None): """Attempt to authenticate with the Toon account.""" - from toonapilib import Toon - from toonapilib.toonapilibexceptions import ( - InvalidConsumerSecret, - InvalidConsumerKey, - InvalidCredentials, - AgreementsRetrievalError, - ) if user_input is None: return await self._show_authenticaticate_form() @@ -129,7 +129,6 @@ class ToonFlowHandler(config_entries.ConfigFlow): async def async_step_display(self, user_input=None): """Select Toon display to add.""" - from toonapilib import Toon if not self.displays: return self.async_abort(reason="no_displays") diff --git a/homeassistant/components/toon/sensor.py b/homeassistant/components/toon/sensor.py index f82bcb7ac1b..79a8fa28540 100644 --- a/homeassistant/components/toon/sensor.py +++ b/homeassistant/components/toon/sensor.py @@ -2,18 +2,18 @@ import logging from homeassistant.config_entries import ConfigEntry -from homeassistant.helpers.typing import HomeAssistantType from homeassistant.const import ENERGY_KILO_WATT_HOUR, POWER_WATT +from homeassistant.helpers.typing import HomeAssistantType from . import ( + ToonBoilerDeviceEntity, ToonData, - ToonEntity, ToonElectricityMeterDeviceEntity, + ToonEntity, ToonGasMeterDeviceEntity, ToonSolarDeviceEntity, - ToonBoilerDeviceEntity, ) -from .const import CURRENCY_EUR, DATA_TOON, DOMAIN, VOLUME_CM3, VOLUME_M3, RATIO_PERCENT +from .const import CURRENCY_EUR, DATA_TOON, DOMAIN, RATIO_PERCENT, VOLUME_CM3, VOLUME_M3 _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/toon/test_config_flow.py b/tests/components/toon/test_config_flow.py index a4d7c760ca1..45d16908446 100644 --- a/tests/components/toon/test_config_flow.py +++ b/tests/components/toon/test_config_flow.py @@ -22,7 +22,7 @@ from homeassistant.components.toon.const import ( from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.setup import async_setup_component -from tests.common import MockConfigEntry, MockDependency +from tests.common import MockConfigEntry FIXTURE_APP = { DOMAIN: {CONF_CLIENT_ID: "1234567890abcdef", CONF_CLIENT_SECRET: "1234567890abcdef"} @@ -40,9 +40,9 @@ FIXTURE_DISPLAY = {CONF_DISPLAY: "display1"} @pytest.fixture def mock_toonapilib(): """Mock toonapilib.""" - with MockDependency("toonapilib") as mock_toonapilib_: - mock_toonapilib_.Toon().display_names = [FIXTURE_DISPLAY[CONF_DISPLAY]] - yield mock_toonapilib_ + with patch("homeassistant.components.toon.config_flow.Toon") as Toon: + Toon().display_names = [FIXTURE_DISPLAY[CONF_DISPLAY]] + yield Toon async def setup_component(hass): @@ -90,7 +90,7 @@ async def test_toon_abort(hass, mock_toonapilib, side_effect, reason): flow = config_flow.ToonFlowHandler() flow.hass = hass - mock_toonapilib.Toon.side_effect = side_effect + mock_toonapilib.side_effect = side_effect result = await flow.async_step_authenticate(user_input=FIXTURE_CREDENTIALS) @@ -100,7 +100,7 @@ async def test_toon_abort(hass, mock_toonapilib, side_effect, reason): async def test_invalid_credentials(hass, mock_toonapilib): """Test we show authentication form on Toon auth error.""" - mock_toonapilib.Toon.side_effect = InvalidCredentials + mock_toonapilib.side_effect = InvalidCredentials await setup_component(hass) @@ -140,7 +140,7 @@ async def test_no_displays(hass, mock_toonapilib): """Test abort when there are no displays.""" await setup_component(hass) - mock_toonapilib.Toon().display_names = [] + mock_toonapilib().display_names = [] flow = config_flow.ToonFlowHandler() flow.hass = hass @@ -177,7 +177,7 @@ async def test_abort_last_minute_fail(hass, mock_toonapilib): flow.hass = hass await flow.async_step_user(user_input=FIXTURE_CREDENTIALS) - mock_toonapilib.Toon.side_effect = Exception + mock_toonapilib.side_effect = Exception result = await flow.async_step_display(user_input=FIXTURE_DISPLAY) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT From 27530be46fd7550edba2b1725ea6b790b74328cc Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Fri, 6 Dec 2019 13:05:35 +0100 Subject: [PATCH 038/677] Move imports to top for influxdb (#29513) --- homeassistant/components/influxdb/__init__.py | 11 +++++------ homeassistant/components/influxdb/sensor.py | 2 +- tests/components/influxdb/test_init.py | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/influxdb/__init__.py b/homeassistant/components/influxdb/__init__.py index 86d489621ea..48852f27910 100644 --- a/homeassistant/components/influxdb/__init__.py +++ b/homeassistant/components/influxdb/__init__.py @@ -1,11 +1,12 @@ """Support for sending data to an Influx database.""" import logging -import re +import math import queue +import re import threading import time -import math +from influxdb import InfluxDBClient, exceptions import requests.exceptions import voluptuous as vol @@ -20,12 +21,12 @@ from homeassistant.const import ( CONF_SSL, CONF_USERNAME, CONF_VERIFY_SSL, - EVENT_STATE_CHANGED, EVENT_HOMEASSISTANT_STOP, + EVENT_STATE_CHANGED, STATE_UNAVAILABLE, STATE_UNKNOWN, ) -from homeassistant.helpers import state as state_helper, event as event_helper +from homeassistant.helpers import event as event_helper, state as state_helper import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_values import EntityValues @@ -118,7 +119,6 @@ RE_DECIMAL = re.compile(r"[^\d.]+") def setup(hass, config): """Set up the InfluxDB component.""" - from influxdb import InfluxDBClient, exceptions conf = config[DOMAIN] @@ -341,7 +341,6 @@ class InfluxThread(threading.Thread): def write_to_influxdb(self, json): """Write preprocessed events to influxdb, with retry.""" - from influxdb import exceptions for retry in range(self.max_tries + 1): try: diff --git a/homeassistant/components/influxdb/sensor.py b/homeassistant/components/influxdb/sensor.py index e94824c9abb..58fbc5605db 100644 --- a/homeassistant/components/influxdb/sensor.py +++ b/homeassistant/components/influxdb/sensor.py @@ -2,6 +2,7 @@ from datetime import timedelta import logging +from influxdb import InfluxDBClient, exceptions import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA @@ -94,7 +95,6 @@ class InfluxSensor(Entity): def __init__(self, hass, influx_conf, query): """Initialize the sensor.""" - from influxdb import InfluxDBClient, exceptions self._name = query.get(CONF_NAME) self._unit_of_measurement = query.get(CONF_UNIT_OF_MEASUREMENT) diff --git a/tests/components/influxdb/test_init.py b/tests/components/influxdb/test_init.py index 26f3c9bcd0b..3e0e1dcf706 100644 --- a/tests/components/influxdb/test_init.py +++ b/tests/components/influxdb/test_init.py @@ -10,7 +10,7 @@ from homeassistant.const import EVENT_STATE_CHANGED, STATE_OFF, STATE_ON, STATE_ from tests.common import get_test_home_assistant -@mock.patch("influxdb.InfluxDBClient") +@mock.patch("homeassistant.components.influxdb.InfluxDBClient") @mock.patch( "homeassistant.components.influxdb.InfluxThread.batch_timeout", mock.Mock(return_value=0), From c5f4872aea24b42c4fb7a5ee990fccdf655b96f5 Mon Sep 17 00:00:00 2001 From: David K <142583+neffs@users.noreply.github.com> Date: Fri, 6 Dec 2019 14:07:45 +0100 Subject: [PATCH 039/677] Limit available heat/cool modes for HomeKit thermostats (#28586) * Limit available heat/cool modes for HomeKit thermostats. The Home app only shows appropriate modes (heat/cool/auto) for the device. Depending on the climate integration, disabling the auto start might be needed. * Include improved mapping for HVAC modes in tests --- .../components/homekit/type_thermostats.py | 57 ++++++++++-- .../homekit/test_type_thermostats.py | 87 ++++++++++++++----- 2 files changed, 115 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/homekit/type_thermostats.py b/homeassistant/components/homekit/type_thermostats.py index 9adc3cc0600..b6e1e75d3c6 100644 --- a/homeassistant/components/homekit/type_thermostats.py +++ b/homeassistant/components/homekit/type_thermostats.py @@ -7,6 +7,7 @@ from homeassistant.components.climate.const import ( ATTR_CURRENT_TEMPERATURE, ATTR_HVAC_ACTION, ATTR_HVAC_MODE, + ATTR_HVAC_MODES, ATTR_MAX_TEMP, ATTR_MIN_TEMP, ATTR_TARGET_TEMP_HIGH, @@ -23,6 +24,8 @@ from homeassistant.components.climate.const import ( HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, + HVAC_MODE_AUTO, + HVAC_MODE_FAN_ONLY, SERVICE_SET_HVAC_MODE as SERVICE_SET_HVAC_MODE_THERMOSTAT, SERVICE_SET_TEMPERATURE as SERVICE_SET_TEMPERATURE_THERMOSTAT, SUPPORT_TARGET_TEMPERATURE_RANGE, @@ -60,13 +63,18 @@ from .util import temperature_to_homekit, temperature_to_states _LOGGER = logging.getLogger(__name__) +HC_HOMEKIT_VALID_MODES_WATER_HEATER = { + "Heat": 1, +} UNIT_HASS_TO_HOMEKIT = {TEMP_CELSIUS: 0, TEMP_FAHRENHEIT: 1} UNIT_HOMEKIT_TO_HASS = {c: s for s, c in UNIT_HASS_TO_HOMEKIT.items()} HC_HASS_TO_HOMEKIT = { HVAC_MODE_OFF: 0, HVAC_MODE_HEAT: 1, HVAC_MODE_COOL: 2, + HVAC_MODE_AUTO: 3, HVAC_MODE_HEAT_COOL: 3, + HVAC_MODE_FAN_ONLY: 2, } HC_HOMEKIT_TO_HASS = {c: s for s, c in HC_HASS_TO_HOMEKIT.items()} @@ -97,9 +105,9 @@ class Thermostat(HomeAccessory): # Add additional characteristics if auto mode is supported self.chars = [] - features = self.hass.states.get(self.entity_id).attributes.get( - ATTR_SUPPORTED_FEATURES, 0 - ) + state = self.hass.states.get(self.entity_id) + features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) + if features & SUPPORT_TARGET_TEMPERATURE_RANGE: self.chars.extend( (CHAR_COOLING_THRESHOLD_TEMPERATURE, CHAR_HEATING_THRESHOLD_TEMPERATURE) @@ -107,12 +115,44 @@ class Thermostat(HomeAccessory): serv_thermostat = self.add_preload_service(SERV_THERMOSTAT, self.chars) - # Current and target mode characteristics + # Current mode characteristics self.char_current_heat_cool = serv_thermostat.configure_char( CHAR_CURRENT_HEATING_COOLING, value=0 ) + + # Target mode characteristics + hc_modes = state.attributes.get(ATTR_HVAC_MODES, None) + if hc_modes is None: + _LOGGER.error( + "%s: HVAC modes not yet available. Please disable auto start for homekit.", + self.entity_id, + ) + hc_modes = ( + HVAC_MODE_HEAT, + HVAC_MODE_COOL, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_OFF, + ) + + # determine available modes for this entity, prefer AUTO over HEAT_COOL and COOL over FAN_ONLY + self.hc_homekit_to_hass = { + c: s + for s, c in HC_HASS_TO_HOMEKIT.items() + if ( + s in hc_modes + and not ( + (s == HVAC_MODE_HEAT_COOL and HVAC_MODE_AUTO in hc_modes) + or (s == HVAC_MODE_FAN_ONLY and HVAC_MODE_COOL in hc_modes) + ) + ) + } + hc_valid_values = {k: v for v, k in self.hc_homekit_to_hass.items()} + self.char_target_heat_cool = serv_thermostat.configure_char( - CHAR_TARGET_HEATING_COOLING, value=0, setter_callback=self.set_heat_cool + CHAR_TARGET_HEATING_COOLING, + value=0, + setter_callback=self.set_heat_cool, + valid_values=hc_valid_values, ) # Current and target temperature characteristics @@ -185,7 +225,7 @@ class Thermostat(HomeAccessory): """Change operation mode to value if call came from HomeKit.""" _LOGGER.debug("%s: Set heat-cool to %d", self.entity_id, value) self._flag_heat_cool = True - hass_value = HC_HOMEKIT_TO_HASS[value] + hass_value = self.hc_homekit_to_hass[value] params = {ATTR_ENTITY_ID: self.entity_id, ATTR_HVAC_MODE: hass_value} self.call_service( DOMAIN_CLIMATE, SERVICE_SET_HVAC_MODE_THERMOSTAT, params, hass_value @@ -318,7 +358,10 @@ class WaterHeater(HomeAccessory): CHAR_CURRENT_HEATING_COOLING, value=1 ) self.char_target_heat_cool = serv_thermostat.configure_char( - CHAR_TARGET_HEATING_COOLING, value=1, setter_callback=self.set_heat_cool + CHAR_TARGET_HEATING_COOLING, + value=1, + setter_callback=self.set_heat_cool, + valid_values=HC_HOMEKIT_VALID_MODES_WATER_HEATER, ) self.char_current_temp = serv_thermostat.configure_char( diff --git a/tests/components/homekit/test_type_thermostats.py b/tests/components/homekit/test_type_thermostats.py index c896ad211e8..9f9ebcdfd32 100644 --- a/tests/components/homekit/test_type_thermostats.py +++ b/tests/components/homekit/test_type_thermostats.py @@ -24,6 +24,8 @@ from homeassistant.components.climate.const import ( HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_AUTO, ) from homeassistant.components.homekit.const import ( ATTR_VALUE, @@ -64,7 +66,20 @@ async def test_thermostat(hass, hk_driver, cls, events): """Test if accessory and HA are updated accordingly.""" entity_id = "climate.test" - hass.states.async_set(entity_id, HVAC_MODE_OFF) + hass.states.async_set( + entity_id, + HVAC_MODE_OFF, + { + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], + }, + ) await hass.async_block_till_done() acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 2, None) await hass.async_add_job(acc.run) @@ -120,7 +135,7 @@ async def test_thermostat(hass, hk_driver, cls, events): hass.states.async_set( entity_id, - HVAC_MODE_COOL, + HVAC_MODE_FAN_ONLY, { ATTR_TEMPERATURE: 20.0, ATTR_CURRENT_TEMPERATURE: 25.0, @@ -164,9 +179,8 @@ async def test_thermostat(hass, hk_driver, cls, events): hass.states.async_set( entity_id, - HVAC_MODE_HEAT_COOL, + HVAC_MODE_AUTO, { - ATTR_HVAC_MODES: [HVAC_MODE_HEAT, HVAC_MODE_COOL], ATTR_TEMPERATURE: 22.0, ATTR_CURRENT_TEMPERATURE: 18.0, ATTR_HVAC_ACTION: CURRENT_HVAC_HEAT, @@ -183,7 +197,6 @@ async def test_thermostat(hass, hk_driver, cls, events): entity_id, HVAC_MODE_HEAT_COOL, { - ATTR_HVAC_MODES: [HVAC_MODE_HEAT, HVAC_MODE_COOL], ATTR_TEMPERATURE: 22.0, ATTR_CURRENT_TEMPERATURE: 25.0, ATTR_HVAC_ACTION: CURRENT_HVAC_COOL, @@ -198,9 +211,8 @@ async def test_thermostat(hass, hk_driver, cls, events): hass.states.async_set( entity_id, - HVAC_MODE_HEAT_COOL, + HVAC_MODE_AUTO, { - ATTR_HVAC_MODES: [HVAC_MODE_HEAT, HVAC_MODE_COOL], ATTR_TEMPERATURE: 22.0, ATTR_CURRENT_TEMPERATURE: 22.0, ATTR_HVAC_ACTION: CURRENT_HVAC_IDLE, @@ -226,14 +238,23 @@ async def test_thermostat(hass, hk_driver, cls, events): assert len(events) == 1 assert events[-1].data[ATTR_VALUE] == "19.0°C" - await hass.async_add_job(acc.char_target_heat_cool.client_update_value, 1) + await hass.async_add_job(acc.char_target_heat_cool.client_update_value, 2) await hass.async_block_till_done() assert call_set_hvac_mode assert call_set_hvac_mode[0].data[ATTR_ENTITY_ID] == entity_id - assert call_set_hvac_mode[0].data[ATTR_HVAC_MODE] == HVAC_MODE_HEAT - assert acc.char_target_heat_cool.value == 1 + assert call_set_hvac_mode[0].data[ATTR_HVAC_MODE] == HVAC_MODE_COOL + assert acc.char_target_heat_cool.value == 2 assert len(events) == 2 - assert events[-1].data[ATTR_VALUE] == HVAC_MODE_HEAT + assert events[-1].data[ATTR_VALUE] == HVAC_MODE_COOL + + await hass.async_add_job(acc.char_target_heat_cool.client_update_value, 3) + await hass.async_block_till_done() + assert call_set_hvac_mode + assert call_set_hvac_mode[1].data[ATTR_ENTITY_ID] == entity_id + assert call_set_hvac_mode[1].data[ATTR_HVAC_MODE] == HVAC_MODE_AUTO + assert acc.char_target_heat_cool.value == 3 + assert len(events) == 3 + assert events[-1].data[ATTR_VALUE] == HVAC_MODE_AUTO async def test_thermostat_auto(hass, hk_driver, cls, events): @@ -261,7 +282,6 @@ async def test_thermostat_auto(hass, hk_driver, cls, events): entity_id, HVAC_MODE_HEAT_COOL, { - ATTR_HVAC_MODE: HVAC_MODE_HEAT_COOL, ATTR_TARGET_TEMP_HIGH: 22.0, ATTR_TARGET_TEMP_LOW: 20.0, ATTR_CURRENT_TEMPERATURE: 18.0, @@ -278,9 +298,8 @@ async def test_thermostat_auto(hass, hk_driver, cls, events): hass.states.async_set( entity_id, - HVAC_MODE_HEAT_COOL, + HVAC_MODE_COOL, { - ATTR_HVAC_MODE: HVAC_MODE_HEAT_COOL, ATTR_TARGET_TEMP_HIGH: 23.0, ATTR_TARGET_TEMP_LOW: 19.0, ATTR_CURRENT_TEMPERATURE: 24.0, @@ -291,15 +310,14 @@ async def test_thermostat_auto(hass, hk_driver, cls, events): assert acc.char_heating_thresh_temp.value == 19.0 assert acc.char_cooling_thresh_temp.value == 23.0 assert acc.char_current_heat_cool.value == 2 - assert acc.char_target_heat_cool.value == 3 + assert acc.char_target_heat_cool.value == 2 assert acc.char_current_temp.value == 24.0 assert acc.char_display_units.value == 0 hass.states.async_set( entity_id, - HVAC_MODE_HEAT_COOL, + HVAC_MODE_AUTO, { - ATTR_HVAC_MODE: HVAC_MODE_HEAT_COOL, ATTR_TARGET_TEMP_HIGH: 23.0, ATTR_TARGET_TEMP_LOW: 19.0, ATTR_CURRENT_TEMPERATURE: 21.0, @@ -346,7 +364,6 @@ async def test_thermostat_power_state(hass, hk_driver, cls, events): HVAC_MODE_HEAT, { ATTR_SUPPORTED_FEATURES: 4096, - ATTR_HVAC_MODE: HVAC_MODE_HEAT, ATTR_TEMPERATURE: 23.0, ATTR_CURRENT_TEMPERATURE: 18.0, ATTR_HVAC_ACTION: CURRENT_HVAC_HEAT, @@ -364,7 +381,6 @@ async def test_thermostat_power_state(hass, hk_driver, cls, events): entity_id, HVAC_MODE_OFF, { - ATTR_HVAC_MODE: HVAC_MODE_HEAT, ATTR_TEMPERATURE: 23.0, ATTR_CURRENT_TEMPERATURE: 18.0, ATTR_HVAC_ACTION: CURRENT_HVAC_IDLE, @@ -378,7 +394,6 @@ async def test_thermostat_power_state(hass, hk_driver, cls, events): entity_id, HVAC_MODE_OFF, { - ATTR_HVAC_MODE: HVAC_MODE_OFF, ATTR_TEMPERATURE: 23.0, ATTR_CURRENT_TEMPERATURE: 18.0, ATTR_HVAC_ACTION: CURRENT_HVAC_IDLE, @@ -423,7 +438,6 @@ async def test_thermostat_fahrenheit(hass, hk_driver, cls, events): entity_id, HVAC_MODE_HEAT_COOL, { - ATTR_HVAC_MODE: HVAC_MODE_HEAT_COOL, ATTR_TARGET_TEMP_HIGH: 75.2, ATTR_TARGET_TEMP_LOW: 68.1, ATTR_TEMPERATURE: 71.6, @@ -503,6 +517,34 @@ async def test_thermostat_temperature_step_whole(hass, hk_driver, cls): assert acc.char_target_temp.properties[PROP_MIN_STEP] == 1.0 +async def test_thermostat_hvac_modes(hass, hk_driver, cls): + """Test if unsupported HVAC modes are deactivated in HomeKit.""" + entity_id = "climate.test" + + hass.states.async_set( + entity_id, HVAC_MODE_OFF, {ATTR_HVAC_MODES: [HVAC_MODE_HEAT, HVAC_MODE_OFF]} + ) + + await hass.async_block_till_done() + acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 2, None) + await hass.async_add_job(acc.run) + await hass.async_block_till_done() + + with pytest.raises(ValueError): + await hass.async_add_job(acc.char_target_heat_cool.set_value, 3) + await hass.async_block_till_done() + assert acc.char_target_heat_cool.value == 0 + + await hass.async_add_job(acc.char_target_heat_cool.set_value, 1) + await hass.async_block_till_done() + assert acc.char_target_heat_cool.value == 1 + + with pytest.raises(ValueError): + await hass.async_add_job(acc.char_target_heat_cool.set_value, 2) + await hass.async_block_till_done() + assert acc.char_target_heat_cool.value == 1 + + async def test_water_heater(hass, hk_driver, cls, events): """Test if accessory and HA are updated accordingly.""" entity_id = "water_heater.test" @@ -571,7 +613,8 @@ async def test_water_heater(hass, hk_driver, cls, events): await hass.async_block_till_done() assert acc.char_target_heat_cool.value == 1 - await hass.async_add_job(acc.char_target_heat_cool.client_update_value, 3) + with pytest.raises(ValueError): + await hass.async_add_job(acc.char_target_heat_cool.set_value, 3) await hass.async_block_till_done() assert acc.char_target_heat_cool.value == 1 From f3717421c0cca59395cfec4574441020f8469f2f Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Fri, 6 Dec 2019 14:59:32 +0100 Subject: [PATCH 040/677] Move imports to top for heatmiser (#29562) --- homeassistant/components/heatmiser/climate.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/heatmiser/climate.py b/homeassistant/components/heatmiser/climate.py index 1954749c21b..553ae8f4bc3 100644 --- a/homeassistant/components/heatmiser/climate.py +++ b/homeassistant/components/heatmiser/climate.py @@ -2,28 +2,27 @@ import logging from typing import List +from heatmiserV3 import connection, heatmiser import voluptuous as vol -from heatmiserV3 import heatmiser, connection from homeassistant.components.climate import ( - ClimateDevice, - PLATFORM_SCHEMA, HVAC_MODE_HEAT, HVAC_MODE_OFF, + PLATFORM_SCHEMA, + ClimateDevice, ) from homeassistant.components.climate.const import SUPPORT_TARGET_TEMPERATURE from homeassistant.const import ( - TEMP_CELSIUS, - TEMP_FAHRENHEIT, ATTR_TEMPERATURE, CONF_HOST, - CONF_PORT, CONF_ID, CONF_NAME, + CONF_PORT, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, ) import homeassistant.helpers.config_validation as cv - _LOGGER = logging.getLogger(__name__) CONF_THERMOSTATS = "tstats" From d9b52ef98c61854ca5f42d34b353a94758dec151 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Fri, 6 Dec 2019 15:00:54 +0100 Subject: [PATCH 041/677] Move imports to top for plant (#29543) --- homeassistant/components/plant/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/plant/__init__.py b/homeassistant/components/plant/__init__.py index a516e06d55b..cc405dcad1f 100644 --- a/homeassistant/components/plant/__init__.py +++ b/homeassistant/components/plant/__init__.py @@ -6,6 +6,7 @@ import logging import voluptuous as vol from homeassistant.components import group +from homeassistant.components.recorder.models import States from homeassistant.components.recorder.util import execute, session_scope from homeassistant.const import ( ATTR_TEMPERATURE, @@ -288,7 +289,6 @@ class Plant(Entity): This only needs to be done once during startup. """ - from homeassistant.components.recorder.models import States start_date = datetime.now() - timedelta(days=self._conf_check_days) entity_id = self._readingmap.get(READING_BRIGHTNESS) From 606d310ea346ee28c39101c22f535ea7743173b0 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Fri, 6 Dec 2019 15:40:04 +0100 Subject: [PATCH 042/677] Move imports to top for spc (#29547) * Move imports to top for spc * Fix pylint error by removing duplicate import --- homeassistant/components/spc/__init__.py | 10 +++++----- homeassistant/components/spc/alarm_control_panel.py | 7 ++----- homeassistant/components/spc/binary_sensor.py | 3 ++- tests/components/spc/test_init.py | 10 ++++++---- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/spc/__init__.py b/homeassistant/components/spc/__init__.py index b5db4b685ae..1601090463f 100644 --- a/homeassistant/components/spc/__init__.py +++ b/homeassistant/components/spc/__init__.py @@ -1,11 +1,14 @@ """Support for Vanderbilt (formerly Siemens) SPC alarm systems.""" import logging +from pyspcwebgw import SpcWebGateway +from pyspcwebgw.area import Area +from pyspcwebgw.zone import Zone import voluptuous as vol -from homeassistant.helpers import discovery, aiohttp_client -from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers import aiohttp_client, discovery import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import async_dispatcher_send _LOGGER = logging.getLogger(__name__) @@ -33,11 +36,8 @@ CONFIG_SCHEMA = vol.Schema( async def async_setup(hass, config): """Set up the SPC component.""" - from pyspcwebgw import SpcWebGateway async def async_upate_callback(spc_object): - from pyspcwebgw.area import Area - from pyspcwebgw.zone import Zone if isinstance(spc_object, Area): async_dispatcher_send(hass, SIGNAL_UPDATE_ALARM.format(spc_object.id)) diff --git a/homeassistant/components/spc/alarm_control_panel.py b/homeassistant/components/spc/alarm_control_panel.py index fa9a9681fff..ca5d77b2a82 100644 --- a/homeassistant/components/spc/alarm_control_panel.py +++ b/homeassistant/components/spc/alarm_control_panel.py @@ -1,6 +1,8 @@ """Support for Vanderbilt (formerly Siemens) SPC alarm systems.""" import logging +from pyspcwebgw.const import AreaMode + import homeassistant.components.alarm_control_panel as alarm from homeassistant.components.alarm_control_panel.const import ( SUPPORT_ALARM_ARM_AWAY, @@ -24,7 +26,6 @@ _LOGGER = logging.getLogger(__name__) def _get_alarm_state(area): """Get the alarm state.""" - from pyspcwebgw.const import AreaMode if area.verified_alarm: return STATE_ALARM_TRIGGERED @@ -92,24 +93,20 @@ class SpcAlarm(alarm.AlarmControlPanel): async def async_alarm_disarm(self, code=None): """Send disarm command.""" - from pyspcwebgw.const import AreaMode await self._api.change_mode(area=self._area, new_mode=AreaMode.UNSET) async def async_alarm_arm_home(self, code=None): """Send arm home command.""" - from pyspcwebgw.const import AreaMode await self._api.change_mode(area=self._area, new_mode=AreaMode.PART_SET_A) async def async_alarm_arm_night(self, code=None): """Send arm home command.""" - from pyspcwebgw.const import AreaMode await self._api.change_mode(area=self._area, new_mode=AreaMode.PART_SET_B) async def async_alarm_arm_away(self, code=None): """Send arm away command.""" - from pyspcwebgw.const import AreaMode await self._api.change_mode(area=self._area, new_mode=AreaMode.FULL_SET) diff --git a/homeassistant/components/spc/binary_sensor.py b/homeassistant/components/spc/binary_sensor.py index 1ce02af390f..2104f931c0a 100644 --- a/homeassistant/components/spc/binary_sensor.py +++ b/homeassistant/components/spc/binary_sensor.py @@ -1,6 +1,8 @@ """Support for Vanderbilt (formerly Siemens) SPC alarm systems.""" import logging +from pyspcwebgw.const import ZoneInput + from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -60,7 +62,6 @@ class SpcBinarySensor(BinarySensorDevice): @property def is_on(self): """Whether the device is switched on.""" - from pyspcwebgw.const import ZoneInput return self._zone.input == ZoneInput.OPEN diff --git a/tests/components/spc/test_init.py b/tests/components/spc/test_init.py index 25b9515e44d..f726a064dd1 100644 --- a/tests/components/spc/test_init.py +++ b/tests/components/spc/test_init.py @@ -13,7 +13,8 @@ async def test_valid_device_config(hass, monkeypatch): config = {"spc": {"api_url": "http://localhost/", "ws_url": "ws://localhost/"}} with patch( - "pyspcwebgw.SpcWebGateway.async_load_parameters", return_value=mock_coro(True) + "homeassistant.components.spc.SpcWebGateway.async_load_parameters", + return_value=mock_coro(True), ): assert await async_setup_component(hass, "spc", config) is True @@ -23,7 +24,8 @@ async def test_invalid_device_config(hass, monkeypatch): config = {"spc": {"api_url": "http://localhost/"}} with patch( - "pyspcwebgw.SpcWebGateway.async_load_parameters", return_value=mock_coro(True) + "homeassistant.components.spc.SpcWebGateway.async_load_parameters", + return_value=mock_coro(True), ): assert await async_setup_component(hass, "spc", config) is False @@ -45,11 +47,11 @@ async def test_update_alarm_device(hass): area_mock.verified_alarm = False with patch( - "pyspcwebgw.SpcWebGateway.areas", new_callable=PropertyMock + "homeassistant.components.spc.SpcWebGateway.areas", new_callable=PropertyMock ) as mock_areas: mock_areas.return_value = {"1": area_mock} with patch( - "pyspcwebgw.SpcWebGateway.async_load_parameters", + "homeassistant.components.spc.SpcWebGateway.async_load_parameters", return_value=mock_coro(True), ): assert await async_setup_component(hass, "spc", config) is True From 48aba426a9813175df6bb41aaf5f4617f46cf8c3 Mon Sep 17 00:00:00 2001 From: Martin Rowan Date: Fri, 6 Dec 2019 16:40:59 +0000 Subject: [PATCH 043/677] Bump lightwave to 0.17 (#29566) --- homeassistant/components/lightwave/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/lightwave/manifest.json b/homeassistant/components/lightwave/manifest.json index 4b2456f0df5..c39a8a6bae8 100644 --- a/homeassistant/components/lightwave/manifest.json +++ b/homeassistant/components/lightwave/manifest.json @@ -3,7 +3,7 @@ "name": "Lightwave", "documentation": "https://www.home-assistant.io/integrations/lightwave", "requirements": [ - "lightwave==0.15" + "lightwave==0.17" ], "dependencies": [], "codeowners": [] diff --git a/requirements_all.txt b/requirements_all.txt index 1fd51ad5d00..a9ac029884d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -778,7 +778,7 @@ liffylights==0.9.4 lightify==1.0.7.2 # homeassistant.components.lightwave -lightwave==0.15 +lightwave==0.17 # homeassistant.components.limitlessled limitlessled==1.1.3 From b8434fdcfda3a93ef568d7f31ad669baa65272d9 Mon Sep 17 00:00:00 2001 From: 1v0dev <56637685+1v0dev@users.noreply.github.com> Date: Fri, 6 Dec 2019 18:45:27 +0200 Subject: [PATCH 044/677] Add service to set netatmo home heating schedule (#29244) * Add service to set netatmo home heating schedule. * handle NoDevice exeption; add service argument constant --- homeassistant/components/netatmo/__init__.py | 24 +++++++++++++++++++ .../components/netatmo/services.yaml | 7 ++++++ 2 files changed, 31 insertions(+) diff --git a/homeassistant/components/netatmo/__init__.py b/homeassistant/components/netatmo/__init__.py index de371f97e28..9edeb0937a1 100644 --- a/homeassistant/components/netatmo/__init__.py +++ b/homeassistant/components/netatmo/__init__.py @@ -30,6 +30,7 @@ CONF_WEBHOOKS = "webhooks" SERVICE_ADDWEBHOOK = "addwebhook" SERVICE_DROPWEBHOOK = "dropwebhook" +SERVICE_SETSCHEDULE = "set_schedule" NETATMO_AUTH = None NETATMO_WEBHOOK_URL = None @@ -63,6 +64,7 @@ ATTR_IS_KNOWN = "is_known" ATTR_FACE_URL = "face_url" ATTR_SNAPSHOT_URL = "snapshot_url" ATTR_VIGNETTE_URL = "vignette_url" +ATTR_SCHEDULE = "schedule" MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5) MIN_TIME_BETWEEN_EVENT_UPDATES = timedelta(seconds=5) @@ -87,6 +89,8 @@ SCHEMA_SERVICE_ADDWEBHOOK = vol.Schema({vol.Optional(CONF_URL): cv.string}) SCHEMA_SERVICE_DROPWEBHOOK = vol.Schema({}) +SCHEMA_SERVICE_SETSCHEDULE = vol.Schema({vol.Required(ATTR_SCHEDULE): cv.string}) + def setup(hass, config): """Set up the Netatmo devices.""" @@ -106,6 +110,12 @@ def setup(hass, config): _LOGGER.error("Unable to connect to Netatmo API") return False + try: + home_data = pyatmo.HomeData(auth) + except pyatmo.NoDevice: + home_data = None + _LOGGER.debug("No climate device. Disable %s service", SERVICE_SETSCHEDULE) + # Store config to be used during entry setup hass.data[DATA_NETATMO_AUTH] = auth @@ -151,6 +161,20 @@ def setup(hass, config): schema=SCHEMA_SERVICE_DROPWEBHOOK, ) + def _service_setschedule(service): + """Service to change current home schedule.""" + schedule_name = service.data.get(ATTR_SCHEDULE) + home_data.switchHomeSchedule(schedule=schedule_name) + _LOGGER.info("Set home schedule to %s", schedule_name) + + if home_data is not None: + hass.services.register( + DOMAIN, + SERVICE_SETSCHEDULE, + _service_setschedule, + schema=SCHEMA_SERVICE_SETSCHEDULE, + ) + return True diff --git a/homeassistant/components/netatmo/services.yaml b/homeassistant/components/netatmo/services.yaml index a928f4765e0..d8fa223780a 100644 --- a/homeassistant/components/netatmo/services.yaml +++ b/homeassistant/components/netatmo/services.yaml @@ -28,3 +28,10 @@ set_light_off: entity_id: description: Entity id. example: 'camera.living_room' + +set_schedule: + description: Set the home heating schedule + fields: + schedule: + description: Schedule name + example: Standard \ No newline at end of file From c9415ab75d84aedf5a1e667e40c49c3383fcbe91 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Fri, 6 Dec 2019 17:46:24 +0100 Subject: [PATCH 045/677] Move imports to top for homematic (#29558) --- homeassistant/components/homematic/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/homematic/__init__.py b/homeassistant/components/homematic/__init__.py index 42fb73f6da2..828e170c67d 100644 --- a/homeassistant/components/homematic/__init__.py +++ b/homeassistant/components/homematic/__init__.py @@ -1,8 +1,9 @@ """Support for HomeMatic devices.""" -from datetime import timedelta, datetime +from datetime import datetime, timedelta from functools import partial import logging +from pyhomematic import HMConnection import voluptuous as vol from homeassistant.const import ( @@ -366,7 +367,6 @@ SCHEMA_SERVICE_PUT_PARAMSET = vol.Schema( def setup(hass, config): """Set up the Homematic component.""" - from pyhomematic import HMConnection conf = config[DOMAIN] hass.data[DATA_CONF] = remotes = {} From d257fff9cec9af2dca86451c59bf796fc4294c01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Fri, 6 Dec 2019 19:54:11 +0200 Subject: [PATCH 046/677] Use "kB" and "s" as UPnP/IGD units (#29552) For consistency with various existing components, and they're more commonly used and compact than "kbyte" and "sec". --- homeassistant/components/upnp/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/upnp/sensor.py b/homeassistant/components/upnp/sensor.py index 4c85e904b1d..7b5071aabd3 100644 --- a/homeassistant/components/upnp/sensor.py +++ b/homeassistant/components/upnp/sensor.py @@ -189,7 +189,7 @@ class PerSecondUPnPIGDSensor(UpnpSensor): @property def unit_of_measurement(self) -> str: """Return the unit of measurement of this entity, if any.""" - return f"{self.unit}/sec" + return f"{self.unit}/s" def _is_overflowed(self, new_value) -> bool: """Check if value has overflowed.""" @@ -222,7 +222,7 @@ class KBytePerSecondUPnPIGDSensor(PerSecondUPnPIGDSensor): @property def unit(self) -> str: """Get unit we are measuring in.""" - return "kbyte" + return "kB" async def _async_fetch_value(self) -> float: """Fetch value from device.""" From 8afe13e81840b23c2cd0b241ace1c233481dbf57 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sat, 7 Dec 2019 00:39:18 +0530 Subject: [PATCH 047/677] Upgrade certifi to >=2019.11.28 (#29571) --- 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 e0a823da44e..372ff66bffd 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -6,7 +6,7 @@ astral==1.10.1 async_timeout==3.0.1 attrs==19.3.0 bcrypt==3.1.7 -certifi>=2019.9.11 +certifi>=2019.11.28 contextvars==2.4;python_version<"3.7" cryptography==2.8 defusedxml==0.6.0 diff --git a/requirements_all.txt b/requirements_all.txt index a9ac029884d..f7e608cc1fd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -4,7 +4,7 @@ astral==1.10.1 async_timeout==3.0.1 attrs==19.3.0 bcrypt==3.1.7 -certifi>=2019.9.11 +certifi>=2019.11.28 contextvars==2.4;python_version<"3.7" importlib-metadata==0.23 jinja2>=2.10.3 diff --git a/setup.py b/setup.py index 99195ee2f56..c153ad1f763 100755 --- a/setup.py +++ b/setup.py @@ -36,7 +36,7 @@ REQUIRES = [ "async_timeout==3.0.1", "attrs==19.3.0", "bcrypt==3.1.7", - "certifi>=2019.9.11", + "certifi>=2019.11.28", 'contextvars==2.4;python_version<"3.7"', "importlib-metadata==0.23", "jinja2>=2.10.3", From 6af30bc232a7139af3a334015f1f7b6d0eab9c3b Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Fri, 6 Dec 2019 20:40:00 +0100 Subject: [PATCH 048/677] Move imports to top for notion (#29539) * Move imports to top for notion * Fix mocking library in test_config_flow.py --- homeassistant/components/notion/binary_sensor.py | 1 - homeassistant/components/notion/config_flow.py | 4 ++-- tests/components/notion/test_config_flow.py | 7 ++++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/notion/binary_sensor.py b/homeassistant/components/notion/binary_sensor.py index 85495c040fa..5079348e821 100644 --- a/homeassistant/components/notion/binary_sensor.py +++ b/homeassistant/components/notion/binary_sensor.py @@ -17,7 +17,6 @@ from . import ( SENSOR_WINDOW_HINGED_VERTICAL, NotionEntity, ) - from .const import DATA_CLIENT, DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/notion/config_flow.py b/homeassistant/components/notion/config_flow.py index affda29e4d6..2af231d582e 100644 --- a/homeassistant/components/notion/config_flow.py +++ b/homeassistant/components/notion/config_flow.py @@ -1,4 +1,6 @@ """Config flow to configure the Notion integration.""" +from aionotion import async_get_client +from aionotion.errors import NotionError import voluptuous as vol from homeassistant import config_entries @@ -40,8 +42,6 @@ class NotionFlowHandler(config_entries.ConfigFlow): async def async_step_user(self, user_input=None): """Handle the start of the config flow.""" - from aionotion import async_get_client - from aionotion.errors import NotionError if not user_input: return await self._show_form() diff --git a/tests/components/notion/test_config_flow.py b/tests/components/notion/test_config_flow.py index 42b28d2c0e1..aa942a8905d 100644 --- a/tests/components/notion/test_config_flow.py +++ b/tests/components/notion/test_config_flow.py @@ -1,12 +1,13 @@ """Define tests for the Notion config flow.""" import aionotion +from unittest.mock import patch import pytest from homeassistant import data_entry_flow from homeassistant.components.notion import DOMAIN, config_flow from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from tests.common import MockConfigEntry, MockDependency, mock_coro +from tests.common import MockConfigEntry, mock_coro @pytest.fixture @@ -18,8 +19,8 @@ def mock_client_coro(): @pytest.fixture def mock_aionotion(mock_client_coro): """Mock the aionotion library.""" - with MockDependency("aionotion") as mock_: - mock_.async_get_client.return_value = mock_client_coro + with patch("homeassistant.components.notion.config_flow.async_get_client") as mock_: + mock_.return_value = mock_client_coro yield mock_ From 74d86dfff95fef5ea28fd7d4af072dcd40b256da Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Fri, 6 Dec 2019 21:11:02 +0100 Subject: [PATCH 049/677] Move imports to top for soundtouch (#29546) * Move imports to top for soundtouch * Format with black * Fix pyling error by renaming variable * Rename entity instances to include entity instead of device --- .../components/soundtouch/media_player.py | 14 +- .../soundtouch/test_media_player.py | 137 ++++++++++++++---- 2 files changed, 116 insertions(+), 35 deletions(-) diff --git a/homeassistant/components/soundtouch/media_player.py b/homeassistant/components/soundtouch/media_player.py index a9f6e05011f..4a0f6b55b22 100644 --- a/homeassistant/components/soundtouch/media_player.py +++ b/homeassistant/components/soundtouch/media_player.py @@ -2,6 +2,7 @@ import logging import re +from libsoundtouch import soundtouch_device import voluptuous as vol from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice @@ -100,9 +101,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return remote_config = {"id": "ha.component.soundtouch", "host": host, "port": port} - soundtouch_device = SoundTouchDevice(None, remote_config) - hass.data[DATA_SOUNDTOUCH].append(soundtouch_device) - add_entities([soundtouch_device]) + bose_soundtouch_entity = SoundTouchDevice(None, remote_config) + hass.data[DATA_SOUNDTOUCH].append(bose_soundtouch_entity) + add_entities([bose_soundtouch_entity]) else: name = config.get(CONF_NAME) remote_config = { @@ -110,9 +111,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): "port": config.get(CONF_PORT), "host": config.get(CONF_HOST), } - soundtouch_device = SoundTouchDevice(name, remote_config) - hass.data[DATA_SOUNDTOUCH].append(soundtouch_device) - add_entities([soundtouch_device]) + bose_soundtouch_entity = SoundTouchDevice(name, remote_config) + hass.data[DATA_SOUNDTOUCH].append(bose_soundtouch_entity) + add_entities([bose_soundtouch_entity]) def service_handle(service): """Handle the applying of a service.""" @@ -184,7 +185,6 @@ class SoundTouchDevice(MediaPlayerDevice): def __init__(self, name, config): """Create Soundtouch Entity.""" - from libsoundtouch import soundtouch_device self._device = soundtouch_device(config["host"], config["port"]) if name is None: diff --git a/tests/components/soundtouch/test_media_player.py b/tests/components/soundtouch/test_media_player.py index bf6d2f72b4a..f9921b5bcb2 100644 --- a/tests/components/soundtouch/test_media_player.py +++ b/tests/components/soundtouch/test_media_player.py @@ -148,7 +148,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): logging.disable(logging.NOTSET) self.hass.stop() - @mock.patch("libsoundtouch.soundtouch_device", side_effect=None) + @mock.patch( + "homeassistant.components.soundtouch.media_player.soundtouch_device", + side_effect=None, + ) def test_ensure_setup_config(self, mocked_soundtouch_device): """Test setup OK with custom config.""" soundtouch.setup_platform(self.hass, default_component(), mock.MagicMock()) @@ -158,7 +161,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): assert all_devices[0].config["port"] == 8090 assert mocked_soundtouch_device.call_count == 1 - @mock.patch("libsoundtouch.soundtouch_device", side_effect=None) + @mock.patch( + "homeassistant.components.soundtouch.media_player.soundtouch_device", + side_effect=None, + ) def test_ensure_setup_discovery(self, mocked_soundtouch_device): """Test setup with discovery.""" new_device = { @@ -174,7 +180,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): assert all_devices[0].config["host"] == "192.168.1.1" assert mocked_soundtouch_device.call_count == 1 - @mock.patch("libsoundtouch.soundtouch_device", side_effect=None) + @mock.patch( + "homeassistant.components.soundtouch.media_player.soundtouch_device", + side_effect=None, + ) def test_ensure_setup_discovery_no_duplicate(self, mocked_soundtouch_device): """Test setup OK if device already exists.""" soundtouch.setup_platform(self.hass, default_component(), mock.MagicMock()) @@ -203,7 +212,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): @mock.patch("libsoundtouch.device.SoundTouchDevice.volume") @mock.patch("libsoundtouch.device.SoundTouchDevice.status") - @mock.patch("libsoundtouch.soundtouch_device", side_effect=_mock_soundtouch_device) + @mock.patch( + "homeassistant.components.soundtouch.media_player.soundtouch_device", + side_effect=_mock_soundtouch_device, + ) def test_update(self, mocked_soundtouch_device, mocked_status, mocked_volume): """Test update device state.""" soundtouch.setup_platform(self.hass, default_component(), mock.MagicMock()) @@ -218,7 +230,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): @mock.patch( "libsoundtouch.device.SoundTouchDevice.status", side_effect=MockStatusPlaying ) - @mock.patch("libsoundtouch.soundtouch_device", side_effect=_mock_soundtouch_device) + @mock.patch( + "homeassistant.components.soundtouch.media_player.soundtouch_device", + side_effect=_mock_soundtouch_device, + ) def test_playing_media( self, mocked_soundtouch_device, mocked_status, mocked_volume ): @@ -240,7 +255,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): @mock.patch( "libsoundtouch.device.SoundTouchDevice.status", side_effect=MockStatusUnknown ) - @mock.patch("libsoundtouch.soundtouch_device", side_effect=_mock_soundtouch_device) + @mock.patch( + "homeassistant.components.soundtouch.media_player.soundtouch_device", + side_effect=_mock_soundtouch_device, + ) def test_playing_unknown_media( self, mocked_soundtouch_device, mocked_status, mocked_volume ): @@ -257,7 +275,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): "libsoundtouch.device.SoundTouchDevice.status", side_effect=MockStatusPlayingRadio, ) - @mock.patch("libsoundtouch.soundtouch_device", side_effect=_mock_soundtouch_device) + @mock.patch( + "homeassistant.components.soundtouch.media_player.soundtouch_device", + side_effect=_mock_soundtouch_device, + ) def test_playing_radio( self, mocked_soundtouch_device, mocked_status, mocked_volume ): @@ -277,7 +298,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): @mock.patch("libsoundtouch.device.SoundTouchDevice.volume", side_effect=MockVolume) @mock.patch("libsoundtouch.device.SoundTouchDevice.status") - @mock.patch("libsoundtouch.soundtouch_device", side_effect=_mock_soundtouch_device) + @mock.patch( + "homeassistant.components.soundtouch.media_player.soundtouch_device", + side_effect=_mock_soundtouch_device, + ) def test_get_volume_level( self, mocked_soundtouch_device, mocked_status, mocked_volume ): @@ -293,7 +317,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): @mock.patch( "libsoundtouch.device.SoundTouchDevice.status", side_effect=MockStatusStandby ) - @mock.patch("libsoundtouch.soundtouch_device", side_effect=_mock_soundtouch_device) + @mock.patch( + "homeassistant.components.soundtouch.media_player.soundtouch_device", + side_effect=_mock_soundtouch_device, + ) def test_get_state_off( self, mocked_soundtouch_device, mocked_status, mocked_volume ): @@ -309,7 +336,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): @mock.patch( "libsoundtouch.device.SoundTouchDevice.status", side_effect=MockStatusPause ) - @mock.patch("libsoundtouch.soundtouch_device", side_effect=_mock_soundtouch_device) + @mock.patch( + "homeassistant.components.soundtouch.media_player.soundtouch_device", + side_effect=_mock_soundtouch_device, + ) def test_get_state_pause( self, mocked_soundtouch_device, mocked_status, mocked_volume ): @@ -325,7 +355,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): "libsoundtouch.device.SoundTouchDevice.volume", side_effect=MockVolumeMuted ) @mock.patch("libsoundtouch.device.SoundTouchDevice.status") - @mock.patch("libsoundtouch.soundtouch_device", side_effect=_mock_soundtouch_device) + @mock.patch( + "homeassistant.components.soundtouch.media_player.soundtouch_device", + side_effect=_mock_soundtouch_device, + ) def test_is_muted(self, mocked_soundtouch_device, mocked_status, mocked_volume): """Test device volume is muted.""" soundtouch.setup_platform(self.hass, default_component(), mock.MagicMock()) @@ -335,7 +368,7 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH] assert all_devices[0].is_volume_muted is True - @mock.patch("libsoundtouch.soundtouch_device") + @mock.patch("homeassistant.components.soundtouch.media_player.soundtouch_device") def test_media_commands(self, mocked_soundtouch_device): """Test supported media commands.""" soundtouch.setup_platform(self.hass, default_component(), mock.MagicMock()) @@ -346,7 +379,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): @mock.patch("libsoundtouch.device.SoundTouchDevice.power_off") @mock.patch("libsoundtouch.device.SoundTouchDevice.volume") @mock.patch("libsoundtouch.device.SoundTouchDevice.status") - @mock.patch("libsoundtouch.soundtouch_device", side_effect=_mock_soundtouch_device) + @mock.patch( + "homeassistant.components.soundtouch.media_player.soundtouch_device", + side_effect=_mock_soundtouch_device, + ) def test_should_turn_off( self, mocked_soundtouch_device, mocked_status, mocked_volume, mocked_power_off ): @@ -362,7 +398,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): @mock.patch("libsoundtouch.device.SoundTouchDevice.power_on") @mock.patch("libsoundtouch.device.SoundTouchDevice.volume") @mock.patch("libsoundtouch.device.SoundTouchDevice.status") - @mock.patch("libsoundtouch.soundtouch_device", side_effect=_mock_soundtouch_device) + @mock.patch( + "homeassistant.components.soundtouch.media_player.soundtouch_device", + side_effect=_mock_soundtouch_device, + ) def test_should_turn_on( self, mocked_soundtouch_device, mocked_status, mocked_volume, mocked_power_on ): @@ -378,7 +417,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): @mock.patch("libsoundtouch.device.SoundTouchDevice.volume_up") @mock.patch("libsoundtouch.device.SoundTouchDevice.volume") @mock.patch("libsoundtouch.device.SoundTouchDevice.status") - @mock.patch("libsoundtouch.soundtouch_device", side_effect=_mock_soundtouch_device) + @mock.patch( + "homeassistant.components.soundtouch.media_player.soundtouch_device", + side_effect=_mock_soundtouch_device, + ) def test_volume_up( self, mocked_soundtouch_device, mocked_status, mocked_volume, mocked_volume_up ): @@ -394,7 +436,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): @mock.patch("libsoundtouch.device.SoundTouchDevice.volume_down") @mock.patch("libsoundtouch.device.SoundTouchDevice.volume") @mock.patch("libsoundtouch.device.SoundTouchDevice.status") - @mock.patch("libsoundtouch.soundtouch_device", side_effect=_mock_soundtouch_device) + @mock.patch( + "homeassistant.components.soundtouch.media_player.soundtouch_device", + side_effect=_mock_soundtouch_device, + ) def test_volume_down( self, mocked_soundtouch_device, mocked_status, mocked_volume, mocked_volume_down ): @@ -410,7 +455,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): @mock.patch("libsoundtouch.device.SoundTouchDevice.set_volume") @mock.patch("libsoundtouch.device.SoundTouchDevice.volume") @mock.patch("libsoundtouch.device.SoundTouchDevice.status") - @mock.patch("libsoundtouch.soundtouch_device", side_effect=_mock_soundtouch_device) + @mock.patch( + "homeassistant.components.soundtouch.media_player.soundtouch_device", + side_effect=_mock_soundtouch_device, + ) def test_set_volume_level( self, mocked_soundtouch_device, mocked_status, mocked_volume, mocked_set_volume ): @@ -426,7 +474,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): @mock.patch("libsoundtouch.device.SoundTouchDevice.mute") @mock.patch("libsoundtouch.device.SoundTouchDevice.volume") @mock.patch("libsoundtouch.device.SoundTouchDevice.status") - @mock.patch("libsoundtouch.soundtouch_device", side_effect=_mock_soundtouch_device) + @mock.patch( + "homeassistant.components.soundtouch.media_player.soundtouch_device", + side_effect=_mock_soundtouch_device, + ) def test_mute( self, mocked_soundtouch_device, mocked_status, mocked_volume, mocked_mute ): @@ -442,7 +493,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): @mock.patch("libsoundtouch.device.SoundTouchDevice.play") @mock.patch("libsoundtouch.device.SoundTouchDevice.volume") @mock.patch("libsoundtouch.device.SoundTouchDevice.status") - @mock.patch("libsoundtouch.soundtouch_device", side_effect=_mock_soundtouch_device) + @mock.patch( + "homeassistant.components.soundtouch.media_player.soundtouch_device", + side_effect=_mock_soundtouch_device, + ) def test_play( self, mocked_soundtouch_device, mocked_status, mocked_volume, mocked_play ): @@ -458,7 +512,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): @mock.patch("libsoundtouch.device.SoundTouchDevice.pause") @mock.patch("libsoundtouch.device.SoundTouchDevice.volume") @mock.patch("libsoundtouch.device.SoundTouchDevice.status") - @mock.patch("libsoundtouch.soundtouch_device", side_effect=_mock_soundtouch_device) + @mock.patch( + "homeassistant.components.soundtouch.media_player.soundtouch_device", + side_effect=_mock_soundtouch_device, + ) def test_pause( self, mocked_soundtouch_device, mocked_status, mocked_volume, mocked_pause ): @@ -474,7 +531,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): @mock.patch("libsoundtouch.device.SoundTouchDevice.play_pause") @mock.patch("libsoundtouch.device.SoundTouchDevice.volume") @mock.patch("libsoundtouch.device.SoundTouchDevice.status") - @mock.patch("libsoundtouch.soundtouch_device", side_effect=_mock_soundtouch_device) + @mock.patch( + "homeassistant.components.soundtouch.media_player.soundtouch_device", + side_effect=_mock_soundtouch_device, + ) def test_play_pause_play( self, mocked_soundtouch_device, mocked_status, mocked_volume, mocked_play_pause ): @@ -491,7 +551,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): @mock.patch("libsoundtouch.device.SoundTouchDevice.next_track") @mock.patch("libsoundtouch.device.SoundTouchDevice.volume") @mock.patch("libsoundtouch.device.SoundTouchDevice.status") - @mock.patch("libsoundtouch.soundtouch_device", side_effect=_mock_soundtouch_device) + @mock.patch( + "homeassistant.components.soundtouch.media_player.soundtouch_device", + side_effect=_mock_soundtouch_device, + ) def test_next_previous_track( self, mocked_soundtouch_device, @@ -519,7 +582,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): ) @mock.patch("libsoundtouch.device.SoundTouchDevice.volume") @mock.patch("libsoundtouch.device.SoundTouchDevice.status") - @mock.patch("libsoundtouch.soundtouch_device", side_effect=_mock_soundtouch_device) + @mock.patch( + "homeassistant.components.soundtouch.media_player.soundtouch_device", + side_effect=_mock_soundtouch_device, + ) def test_play_media( self, mocked_soundtouch_device, @@ -544,7 +610,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): @mock.patch("libsoundtouch.device.SoundTouchDevice.play_url") @mock.patch("libsoundtouch.device.SoundTouchDevice.volume") @mock.patch("libsoundtouch.device.SoundTouchDevice.status") - @mock.patch("libsoundtouch.soundtouch_device", side_effect=_mock_soundtouch_device) + @mock.patch( + "homeassistant.components.soundtouch.media_player.soundtouch_device", + side_effect=_mock_soundtouch_device, + ) def test_play_media_url( self, mocked_soundtouch_device, mocked_status, mocked_volume, mocked_play_url ): @@ -560,7 +629,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): @mock.patch("libsoundtouch.device.SoundTouchDevice.create_zone") @mock.patch("libsoundtouch.device.SoundTouchDevice.volume") @mock.patch("libsoundtouch.device.SoundTouchDevice.status") - @mock.patch("libsoundtouch.soundtouch_device", side_effect=_mock_soundtouch_device) + @mock.patch( + "homeassistant.components.soundtouch.media_player.soundtouch_device", + side_effect=_mock_soundtouch_device, + ) def test_play_everywhere( self, mocked_soundtouch_device, mocked_status, mocked_volume, mocked_create_zone ): @@ -605,7 +677,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): @mock.patch("libsoundtouch.device.SoundTouchDevice.create_zone") @mock.patch("libsoundtouch.device.SoundTouchDevice.volume") @mock.patch("libsoundtouch.device.SoundTouchDevice.status") - @mock.patch("libsoundtouch.soundtouch_device", side_effect=_mock_soundtouch_device) + @mock.patch( + "homeassistant.components.soundtouch.media_player.soundtouch_device", + side_effect=_mock_soundtouch_device, + ) def test_create_zone( self, mocked_soundtouch_device, mocked_status, mocked_volume, mocked_create_zone ): @@ -649,7 +724,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): @mock.patch("libsoundtouch.device.SoundTouchDevice.remove_zone_slave") @mock.patch("libsoundtouch.device.SoundTouchDevice.volume") @mock.patch("libsoundtouch.device.SoundTouchDevice.status") - @mock.patch("libsoundtouch.soundtouch_device", side_effect=_mock_soundtouch_device) + @mock.patch( + "homeassistant.components.soundtouch.media_player.soundtouch_device", + side_effect=_mock_soundtouch_device, + ) def test_remove_zone_slave( self, mocked_soundtouch_device, @@ -697,7 +775,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): @mock.patch("libsoundtouch.device.SoundTouchDevice.add_zone_slave") @mock.patch("libsoundtouch.device.SoundTouchDevice.volume") @mock.patch("libsoundtouch.device.SoundTouchDevice.status") - @mock.patch("libsoundtouch.soundtouch_device", side_effect=_mock_soundtouch_device) + @mock.patch( + "homeassistant.components.soundtouch.media_player.soundtouch_device", + side_effect=_mock_soundtouch_device, + ) def test_add_zone_slave( self, mocked_soundtouch_device, From 31c71989e99531b410d2c2118fcdd132c6c26e07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Fri, 6 Dec 2019 22:53:26 +0200 Subject: [PATCH 050/677] Huawei LTE device tracker fixes (#29551) * Include MAC address in device state attributes for absent devices too * Use MAC address as default name whether device is connected or not * Fix initialization of known entities Closes https://github.com/home-assistant/home-assistant/issues/29354 --- .../components/huawei_lte/device_tracker.py | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/huawei_lte/device_tracker.py b/homeassistant/components/huawei_lte/device_tracker.py index 29d7f437b5f..f5f834fa186 100644 --- a/homeassistant/components/huawei_lte/device_tracker.py +++ b/homeassistant/components/huawei_lte/device_tracker.py @@ -2,7 +2,7 @@ import logging import re -from typing import Any, Dict, Set +from typing import Any, Dict, List, Optional, Set import attr from stringcase import snakecase @@ -40,13 +40,17 @@ async def async_setup_entry(hass, config_entry, async_add_entities): # Initialize already tracked entities tracked: Set[str] = set() registry = await entity_registry.async_get_registry(hass) + known_entities: List[HuaweiLteScannerEntity] = [] for entity in registry.entities.values(): if ( entity.domain == DEVICE_TRACKER_DOMAIN and entity.config_entry_id == config_entry.entry_id ): tracked.add(entity.unique_id) - async_add_new_entities(hass, router.url, async_add_entities, tracked, True) + known_entities.append( + HuaweiLteScannerEntity(router, entity.unique_id.partition("-")[2]) + ) + async_add_entities(known_entities, True) # Tell parent router to poll hosts list to gather new devices router.subscriptions[KEY_WLAN_HOST_LIST].add(_DEVICE_SCAN) @@ -66,13 +70,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_new_entities(hass, router.url, async_add_entities, tracked) -def async_add_new_entities( - hass, router_url, async_add_entities, tracked, included: bool = False -): - """Add new entities. - - :param included: if True, setup only items in tracked, and vice versa - """ +def async_add_new_entities(hass, router_url, async_add_entities, tracked): + """Add new entities that are not already being tracked.""" router = hass.data[DOMAIN].routers[router_url] try: hosts = router.data[KEY_WLAN_HOST_LIST]["Hosts"]["Host"] @@ -83,8 +82,7 @@ def async_add_new_entities( new_entities = [] for host in (x for x in hosts if x.get("MacAddress")): entity = HuaweiLteScannerEntity(router, host["MacAddress"]) - tracking = entity.unique_id in tracked - if tracking != included: + if entity.unique_id in tracked: continue tracked.add(entity.unique_id) new_entities.append(entity) @@ -113,12 +111,16 @@ class HuaweiLteScannerEntity(HuaweiLteBaseEntity, ScannerEntity): mac: str = attr.ib() _is_connected: bool = attr.ib(init=False, default=False) - _name: str = attr.ib(init=False, default="device") + _hostname: Optional[str] = attr.ib(init=False, default=None) _device_state_attributes: Dict[str, Any] = attr.ib(init=False, factory=dict) + def __attrs_post_init__(self): + """Initialize internal state.""" + self._device_state_attributes["mac_address"] = self.mac + @property def _entity_name(self) -> str: - return self._name + return self._hostname or self.mac @property def _device_unique_id(self) -> str: @@ -145,8 +147,7 @@ class HuaweiLteScannerEntity(HuaweiLteBaseEntity, ScannerEntity): host = next((x for x in hosts if x.get("MacAddress") == self.mac), None) self._is_connected = host is not None if self._is_connected: - # HostName may be present with explicit None value - self._name = host.get("HostName") or self.mac + self._hostname = host.get("HostName") self._device_state_attributes = { _better_snakecase(k): v for k, v in host.items() if k != "HostName" } From fb66a6cf81666ab2a7a25017d7faec36d6486087 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Fri, 6 Dec 2019 22:58:32 +0200 Subject: [PATCH 051/677] Treat BaseException as over-general (#29573) To follow pylint's defaults. --- homeassistant/components/whois/sensor.py | 4 ++-- pylintrc | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/whois/sensor.py b/homeassistant/components/whois/sensor.py index 3c78d80ba92..dc9da1100f0 100644 --- a/homeassistant/components/whois/sensor.py +++ b/homeassistant/components/whois/sensor.py @@ -44,7 +44,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): "WHOIS lookup for %s didn't contain an expiration date", domain ) return - except whois.BaseException as ex: + except whois.BaseException as ex: # pylint: disable=broad-except _LOGGER.error("Exception %s occurred during WHOIS lookup for %s", ex, domain) return @@ -96,7 +96,7 @@ class WhoisSensor(Entity): """Get the current WHOIS data for the domain.""" try: response = self.whois(self._domain) - except whois.BaseException as ex: + except whois.BaseException as ex: # pylint: disable=broad-except _LOGGER.error("Exception %s occurred during WHOIS lookup", ex) self._empty_state_and_attributes() return diff --git a/pylintrc b/pylintrc index 44659ddb376..3235d583865 100644 --- a/pylintrc +++ b/pylintrc @@ -64,4 +64,4 @@ ignored-classes=_CountingAttr expected-line-ending-format=LF [EXCEPTIONS] -overgeneral-exceptions=Exception,HomeAssistantError +overgeneral-exceptions=BaseException,Exception,HomeAssistantError From 1ee80576621933e3264e9c9fb8cbd12ba61e175c Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Fri, 6 Dec 2019 22:12:46 +0100 Subject: [PATCH 052/677] Move imports to top for zha (#29555) * Move imports to top for zha * Move back some imports, add annotation for disabling import-outside-toplevel * Move import config_flow before import api --- homeassistant/components/zha/__init__.py | 2 +- homeassistant/components/zha/core/channels/__init__.py | 2 +- homeassistant/components/zha/core/channels/hvac.py | 2 +- .../components/zha/core/channels/manufacturerspecific.py | 1 - homeassistant/components/zha/core/channels/security.py | 2 +- homeassistant/components/zha/core/device.py | 2 +- homeassistant/components/zha/core/store.py | 3 +-- homeassistant/components/zha/device_trigger.py | 2 +- 8 files changed, 7 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/zha/__init__.py b/homeassistant/components/zha/__init__.py index ecd27c48839..07eb3c53a95 100644 --- a/homeassistant/components/zha/__init__.py +++ b/homeassistant/components/zha/__init__.py @@ -100,7 +100,7 @@ async def async_setup_entry(hass, config_entry): if config.get(CONF_ENABLE_QUIRKS, True): # needs to be done here so that the ZHA module is finished loading # before zhaquirks is imported - import zhaquirks # noqa: F401 pylint: disable=unused-import + import zhaquirks # noqa: F401 pylint: disable=unused-import, import-outside-toplevel zha_gateway = ZHAGateway(hass, config, config_entry) await zha_gateway.async_initialize() diff --git a/homeassistant/components/zha/core/channels/__init__.py b/homeassistant/components/zha/core/channels/__init__.py index 29cecb7784e..2d24c4ce045 100644 --- a/homeassistant/components/zha/core/channels/__init__.py +++ b/homeassistant/components/zha/core/channels/__init__.py @@ -394,7 +394,7 @@ class EventRelayChannel(ZigbeeChannel): ) -# pylint: disable=wrong-import-position +# pylint: disable=wrong-import-position, import-outside-toplevel from . import closures # noqa: F401 from . import general # noqa: F401 from . import homeautomation # noqa: F401 diff --git a/homeassistant/components/zha/core/channels/hvac.py b/homeassistant/components/zha/core/channels/hvac.py index 14d982ab1e8..db4745d51c3 100644 --- a/homeassistant/components/zha/core/channels/hvac.py +++ b/homeassistant/components/zha/core/channels/hvac.py @@ -6,6 +6,7 @@ https://home-assistant.io/integrations/zha/ """ import logging +from zigpy.exceptions import DeliveryError import zigpy.zcl.clusters.hvac as hvac from homeassistant.core import callback @@ -35,7 +36,6 @@ class FanChannel(ZigbeeChannel): async def async_set_speed(self, value) -> None: """Set the speed of the fan.""" - from zigpy.exceptions import DeliveryError try: await self.cluster.write_attributes({"fan_mode": value}) diff --git a/homeassistant/components/zha/core/channels/manufacturerspecific.py b/homeassistant/components/zha/core/channels/manufacturerspecific.py index 31dd5cd63d1..39f45f6c4a2 100644 --- a/homeassistant/components/zha/core/channels/manufacturerspecific.py +++ b/homeassistant/components/zha/core/channels/manufacturerspecific.py @@ -18,7 +18,6 @@ from ..const import ( SIGNAL_ATTR_UPDATED, ) - _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/zha/core/channels/security.py b/homeassistant/components/zha/core/channels/security.py index e4840dae86d..69e4ea1a27a 100644 --- a/homeassistant/components/zha/core/channels/security.py +++ b/homeassistant/components/zha/core/channels/security.py @@ -6,6 +6,7 @@ https://home-assistant.io/integrations/zha/ """ import logging +from zigpy.exceptions import DeliveryError import zigpy.zcl.clusters.security as security from homeassistant.core import callback @@ -149,7 +150,6 @@ class IASZoneChannel(ZigbeeChannel): if self._zha_device.manufacturer == "LUMI": self.debug("finished IASZoneChannel configuration") return - from zigpy.exceptions import DeliveryError self.debug("started IASZoneChannel configuration") diff --git a/homeassistant/components/zha/core/device.py b/homeassistant/components/zha/core/device.py index e5d1678ad6f..9ace477d621 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -11,8 +11,8 @@ import logging import time import zigpy.exceptions -import zigpy.quirks from zigpy.profiles import zha, zll +import zigpy.quirks from homeassistant.core import callback from homeassistant.helpers.dispatcher import ( diff --git a/homeassistant/components/zha/core/store.py b/homeassistant/components/zha/core/store.py index bcc9b2a42d4..46fef76b656 100644 --- a/homeassistant/components/zha/core/store.py +++ b/homeassistant/components/zha/core/store.py @@ -2,8 +2,7 @@ # pylint: disable=unused-import from collections import OrderedDict import logging -from typing import MutableMapping -from typing import cast +from typing import MutableMapping, cast import attr diff --git a/homeassistant/components/zha/device_trigger.py b/homeassistant/components/zha/device_trigger.py index cdd62b11d1e..b7c46e5a40a 100644 --- a/homeassistant/components/zha/device_trigger.py +++ b/homeassistant/components/zha/device_trigger.py @@ -2,11 +2,11 @@ import voluptuous as vol import homeassistant.components.automation.event as event +from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA from homeassistant.components.device_automation.exceptions import ( InvalidDeviceAutomationConfig, ) from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_PLATFORM, CONF_TYPE -from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA from . import DOMAIN from .core.helpers import async_get_zha_device From 23fb36407614fd5baff41bd8793a64a6a4453910 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Fri, 6 Dec 2019 22:13:43 +0100 Subject: [PATCH 053/677] Move imports to top for openuv (#29541) * Move imports to top for openuv * Renamed mock_pyopenuv_ to MockClient in test_config_flow --- homeassistant/components/openuv/__init__.py | 8 +++----- homeassistant/components/openuv/config_flow.py | 5 ++--- tests/components/openuv/test_config_flow.py | 9 +++++---- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/openuv/__init__.py b/homeassistant/components/openuv/__init__.py index 16b7a50a4ae..167fcdcd0e6 100644 --- a/homeassistant/components/openuv/__init__.py +++ b/homeassistant/components/openuv/__init__.py @@ -1,7 +1,9 @@ """Support for UV data from openuv.io.""" -import logging import asyncio +import logging +from pyopenuv import Client +from pyopenuv.errors import OpenUvError import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT @@ -164,8 +166,6 @@ async def async_setup(hass, config): async def async_setup_entry(hass, config_entry): """Set up OpenUV as config entry.""" - from pyopenuv import Client - from pyopenuv.errors import OpenUvError _verify_domain_control = verify_domain_control(hass, DOMAIN) @@ -255,7 +255,6 @@ class OpenUV: async def async_update_protection_data(self): """Update binary sensor (protection window) data.""" - from pyopenuv.errors import OpenUvError if TYPE_PROTECTION_WINDOW in self.binary_sensor_conditions: try: @@ -268,7 +267,6 @@ class OpenUV: async def async_update_uv_index_data(self): """Update sensor (uv index, etc) data.""" - from pyopenuv.errors import OpenUvError if any(c in self.sensor_conditions for c in SENSORS): try: diff --git a/homeassistant/components/openuv/config_flow.py b/homeassistant/components/openuv/config_flow.py index 40ec2abf2fe..7dd8ed45a79 100644 --- a/homeassistant/components/openuv/config_flow.py +++ b/homeassistant/components/openuv/config_flow.py @@ -1,5 +1,6 @@ """Config flow to configure the OpenUV component.""" - +from pyopenuv import Client +from pyopenuv.errors import OpenUvError import voluptuous as vol from homeassistant import config_entries @@ -59,8 +60,6 @@ class OpenUvFlowHandler(config_entries.ConfigFlow): async def async_step_user(self, user_input=None): """Handle the start of the config flow.""" - from pyopenuv import Client - from pyopenuv.errors import OpenUvError if not user_input: return await self._show_form() diff --git a/tests/components/openuv/test_config_flow.py b/tests/components/openuv/test_config_flow.py index c57e22e44e2..43dd5924a72 100644 --- a/tests/components/openuv/test_config_flow.py +++ b/tests/components/openuv/test_config_flow.py @@ -1,6 +1,7 @@ """Define tests for the OpenUV config flow.""" import pytest from pyopenuv.errors import OpenUvError +from unittest.mock import patch from homeassistant import data_entry_flow from homeassistant.components.openuv import DOMAIN, config_flow @@ -11,7 +12,7 @@ from homeassistant.const import ( CONF_LONGITUDE, ) -from tests.common import MockConfigEntry, MockDependency, mock_coro +from tests.common import MockConfigEntry, mock_coro @pytest.fixture @@ -23,9 +24,9 @@ def uv_index_response(): @pytest.fixture def mock_pyopenuv(uv_index_response): """Mock the pyopenuv library.""" - with MockDependency("pyopenuv") as mock_pyopenuv_: - mock_pyopenuv_.Client().uv_index.return_value = uv_index_response - yield mock_pyopenuv_ + with patch("homeassistant.components.openuv.config_flow.Client") as MockClient: + MockClient().uv_index.return_value = uv_index_response + yield MockClient async def test_duplicate_error(hass): From b4c95421d3c5cdde6323702aa4b6a7a5634ef451 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Sat, 7 Dec 2019 00:32:13 +0000 Subject: [PATCH 054/677] [ci skip] Translation update --- .../components/alarm_control_panel/.translations/pt-BR.json | 5 +++++ homeassistant/components/plex/.translations/pt-BR.json | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/homeassistant/components/alarm_control_panel/.translations/pt-BR.json b/homeassistant/components/alarm_control_panel/.translations/pt-BR.json index 156ede8851b..f72ae0e820e 100644 --- a/homeassistant/components/alarm_control_panel/.translations/pt-BR.json +++ b/homeassistant/components/alarm_control_panel/.translations/pt-BR.json @@ -6,6 +6,11 @@ "arm_night": "Armar {entity_name} noite", "disarm": "Desarmar {entity_name}", "trigger": "Disparar {entidade_nome}" + }, + "trigger_type": { + "armed_night": "{entity_name} armadado para noite", + "disarmed": "{entity_name} desarmado", + "triggered": "{entity_name} acionado" } } } \ No newline at end of file diff --git a/homeassistant/components/plex/.translations/pt-BR.json b/homeassistant/components/plex/.translations/pt-BR.json index 9a759e309c2..be97c7fdcb7 100644 --- a/homeassistant/components/plex/.translations/pt-BR.json +++ b/homeassistant/components/plex/.translations/pt-BR.json @@ -1,4 +1,9 @@ { + "config": { + "abort": { + "non-interactive": "Importa\u00e7\u00e3o n\u00e3o interativa" + } + }, "options": { "step": { "plex_mp_settings": { From e2adfc3979901e4e1b4c5e2acf0a9c0b4d99a2e1 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Sat, 7 Dec 2019 07:25:15 +0100 Subject: [PATCH 055/677] Move imports to top for onboarding (#29542) --- homeassistant/components/onboarding/__init__.py | 7 +++---- homeassistant/components/onboarding/views.py | 6 +++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/onboarding/__init__.py b/homeassistant/components/onboarding/__init__.py index 5527f5f2abe..bedfa703a9b 100644 --- a/homeassistant/components/onboarding/__init__.py +++ b/homeassistant/components/onboarding/__init__.py @@ -1,9 +1,10 @@ """Support to help onboard new users.""" from homeassistant.core import callback -from homeassistant.loader import bind_hass from homeassistant.helpers.storage import Store +from homeassistant.loader import bind_hass -from .const import DOMAIN, STEP_USER, STEPS, STEP_INTEGRATION, STEP_CORE_CONFIG +from . import views +from .const import DOMAIN, STEP_CORE_CONFIG, STEP_INTEGRATION, STEP_USER, STEPS STORAGE_KEY = DOMAIN STORAGE_VERSION = 3 @@ -64,8 +65,6 @@ async def async_setup(hass, config): hass.data[DOMAIN] = data - from . import views - await views.async_setup(hass, data, store) return True diff --git a/homeassistant/components/onboarding/views.py b/homeassistant/components/onboarding/views.py index 2e79393fe42..8eac430ac49 100644 --- a/homeassistant/components/onboarding/views.py +++ b/homeassistant/components/onboarding/views.py @@ -8,12 +8,12 @@ from homeassistant.components.http.view import HomeAssistantView from homeassistant.core import callback from .const import ( + DEFAULT_AREAS, DOMAIN, + STEP_CORE_CONFIG, + STEP_INTEGRATION, STEP_USER, STEPS, - DEFAULT_AREAS, - STEP_INTEGRATION, - STEP_CORE_CONFIG, ) From 977f51a9e45e7afed3230d3a2e197b3844ca0e66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sat, 7 Dec 2019 08:34:46 +0200 Subject: [PATCH 056/677] Update Travis dist to bionic (#29575) --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c9638b02a2f..e35787bb1e8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ sudo: false -dist: xenial +dist: bionic addons: apt: sources: From 9d7799c0af4de52e24fa1c40bfffb5d25ecc01b0 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sat, 7 Dec 2019 13:36:55 +0530 Subject: [PATCH 057/677] Upgrade pyyaml to 5.2.0 (#29586) --- 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 372ff66bffd..07b9b4c80e8 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -19,7 +19,7 @@ netdisco==2.6.0 pip>=8.0.3 python-slugify==4.0.0 pytz>=2019.03 -pyyaml==5.1.2 +pyyaml==5.2.0 requests==2.22.0 ruamel.yaml==0.15.100 sqlalchemy==1.3.11 diff --git a/requirements_all.txt b/requirements_all.txt index f7e608cc1fd..094728834df 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -13,7 +13,7 @@ cryptography==2.8 pip>=8.0.3 python-slugify==4.0.0 pytz>=2019.03 -pyyaml==5.1.2 +pyyaml==5.2.0 requests==2.22.0 ruamel.yaml==0.15.100 voluptuous==0.11.7 diff --git a/setup.py b/setup.py index c153ad1f763..73cc893cbe2 100755 --- a/setup.py +++ b/setup.py @@ -46,7 +46,7 @@ REQUIRES = [ "pip>=8.0.3", "python-slugify==4.0.0", "pytz>=2019.03", - "pyyaml==5.1.2", + "pyyaml==5.2.0", "requests==2.22.0", "ruamel.yaml==0.15.100", "voluptuous==0.11.7", From ee657f3c2ff84b8649cc3fa32e923ab11ee67e37 Mon Sep 17 00:00:00 2001 From: SNoof85 Date: Sat, 7 Dec 2019 12:09:43 +0100 Subject: [PATCH 058/677] Add service to reboot the Freebox (#29525) --- homeassistant/components/freebox/__init__.py | 6 ++++++ homeassistant/components/freebox/services.yaml | 5 +++++ 2 files changed, 11 insertions(+) create mode 100644 homeassistant/components/freebox/services.yaml diff --git a/homeassistant/components/freebox/__init__.py b/homeassistant/components/freebox/__init__.py index 64c59c3ef2a..96874ddeb94 100644 --- a/homeassistant/components/freebox/__init__.py +++ b/homeassistant/components/freebox/__init__.py @@ -71,6 +71,12 @@ async def async_setup_freebox(hass, config, host, port): else: hass.data[DATA_FREEBOX] = fbx + async def async_freebox_reboot(call): + """Handle reboot service call.""" + await fbx.system.reboot() + + hass.services.async_register(DOMAIN, "reboot", async_freebox_reboot) + hass.async_create_task(async_load_platform(hass, "sensor", DOMAIN, {}, config)) hass.async_create_task( async_load_platform(hass, "device_tracker", DOMAIN, {}, config) diff --git a/homeassistant/components/freebox/services.yaml b/homeassistant/components/freebox/services.yaml new file mode 100644 index 00000000000..be7afa60562 --- /dev/null +++ b/homeassistant/components/freebox/services.yaml @@ -0,0 +1,5 @@ +# Freebox service entries description. + +reboot: + # Description of the service + description: Reboots the Freebox. From d838a56c1d0b4f04a522fa846f7752a5b58b4004 Mon Sep 17 00:00:00 2001 From: butako <1004460+butako@users.noreply.github.com> Date: Sat, 7 Dec 2019 15:14:09 +0000 Subject: [PATCH 059/677] Improve Tahoma Velux support (#27920) * Improved Velux support. Added Velux Solar Roller Blind. Fixed Velux Integra Window. * fix indentation * black formatting * added new devices in correct sorted order --- homeassistant/components/tahoma/__init__.py | 1 + homeassistant/components/tahoma/cover.py | 14 +++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) mode change 100644 => 100755 homeassistant/components/tahoma/cover.py diff --git a/homeassistant/components/tahoma/__init__.py b/homeassistant/components/tahoma/__init__.py index 02cdba5c46a..640cc6418d0 100644 --- a/homeassistant/components/tahoma/__init__.py +++ b/homeassistant/components/tahoma/__init__.py @@ -46,6 +46,7 @@ TAHOMA_TYPES = { "io:SomfyBasicContactIOSystemSensor": "sensor", "io:SomfyContactIOSystemSensor": "sensor", "io:VerticalExteriorAwningIOComponent": "cover", + "io:VerticalInteriorBlindVeluxIOComponent": "cover", "io:WindowOpenerVeluxIOComponent": "cover", "io:GarageOpenerIOComponent": "cover", "io:DiscreteGarageOpenerIOComponent": "cover", diff --git a/homeassistant/components/tahoma/cover.py b/homeassistant/components/tahoma/cover.py old mode 100644 new mode 100755 index 6c5dcbd807c..e11c2f4cdf5 --- a/homeassistant/components/tahoma/cover.py +++ b/homeassistant/components/tahoma/cover.py @@ -35,6 +35,7 @@ TAHOMA_DEVICE_CLASSES = { "io:RollerShutterVeluxIOComponent": DEVICE_CLASS_SHUTTER, "io:RollerShutterWithLowSpeedManagementIOComponent": DEVICE_CLASS_SHUTTER, "io:VerticalExteriorAwningIOComponent": DEVICE_CLASS_AWNING, + "io:VerticalInteriorBlindVeluxIOComponent": DEVICE_CLASS_BLIND, "io:WindowOpenerVeluxIOComponent": DEVICE_CLASS_WINDOW, "io:GarageOpenerIOComponent": DEVICE_CLASS_GARAGE, "io:DiscreteGarageOpenerIOComponent": DEVICE_CLASS_GARAGE, @@ -163,10 +164,15 @@ class TahomaCover(TahomaDevice, CoverDevice): def set_cover_position(self, **kwargs): """Move the cover to a specific position.""" - if self.tahoma_device.type == HORIZONTAL_AWNING: - self.apply_action("setPosition", kwargs.get(ATTR_POSITION, 0)) + if self.tahoma_device.type == "io:WindowOpenerVeluxIOComponent": + command = "setClosure" else: - self.apply_action("setPosition", 100 - kwargs.get(ATTR_POSITION, 0)) + command = "setPosition" + + if self.tahoma_device.type == HORIZONTAL_AWNING: + self.apply_action(command, kwargs.get(ATTR_POSITION, 0)) + else: + self.apply_action(command, 100 - kwargs.get(ATTR_POSITION, 0)) @property def is_closed(self): @@ -235,6 +241,8 @@ class TahomaCover(TahomaDevice, CoverDevice): HORIZONTAL_AWNING, "io:RollerShutterGenericIOComponent", "io:VerticalExteriorAwningIOComponent", + "io:VerticalInteriorBlindVeluxIOComponent", + "io:WindowOpenerVeluxIOComponent", ): self.apply_action("stop") else: From ccb0fd5e32d8ed5a430dd42e7b24bc980facc881 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Sat, 7 Dec 2019 15:17:30 -0500 Subject: [PATCH 060/677] Register automation.reload service as an admin service. (#29582) * homeassistant/components/automation/__init__.py * Register automation.reload as an admin service. --- .../components/automation/__init__.py | 9 +++++-- tests/components/automation/common.py | 6 +++-- tests/components/automation/test_init.py | 25 +++++++++++-------- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 3863ab0c88d..2b775e3a602 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -27,6 +27,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.config_validation import make_entity_service_schema from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.helpers.service import async_register_admin_service from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.typing import TemplateVarsType from homeassistant.loader import bind_hass @@ -179,8 +180,12 @@ async def async_setup(hass, config): DOMAIN, SERVICE_TRIGGER, trigger_service_handler, schema=TRIGGER_SERVICE_SCHEMA ) - hass.services.async_register( - DOMAIN, SERVICE_RELOAD, reload_service_handler, schema=RELOAD_SERVICE_SCHEMA + async_register_admin_service( + hass, + DOMAIN, + SERVICE_RELOAD, + reload_service_handler, + schema=RELOAD_SERVICE_SCHEMA, ) hass.services.async_register( diff --git a/tests/components/automation/common.py b/tests/components/automation/common.py index c7aa8f1eced..729a6bb7212 100644 --- a/tests/components/automation/common.py +++ b/tests/components/automation/common.py @@ -44,6 +44,8 @@ async def async_trigger(hass, entity_id=ENTITY_MATCH_ALL): @bind_hass -async def async_reload(hass): +async def async_reload(hass, context=None): """Reload the automation from config.""" - await hass.services.async_call(DOMAIN, SERVICE_RELOAD) + await hass.services.async_call( + DOMAIN, SERVICE_RELOAD, blocking=True, context=context + ) diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index a0573ce7c1b..d5498c04814 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -1,28 +1,28 @@ """The tests for the automation component.""" from datetime import timedelta -from unittest.mock import patch, Mock +from unittest.mock import Mock, patch import pytest -from homeassistant.core import State, CoreState, Context -from homeassistant.setup import async_setup_component import homeassistant.components.automation as automation from homeassistant.const import ( - ATTR_NAME, ATTR_ENTITY_ID, - STATE_ON, - STATE_OFF, - EVENT_HOMEASSISTANT_START, + ATTR_NAME, EVENT_AUTOMATION_TRIGGERED, + EVENT_HOMEASSISTANT_START, + STATE_OFF, + STATE_ON, ) -from homeassistant.exceptions import HomeAssistantError +from homeassistant.core import Context, CoreState, State +from homeassistant.exceptions import HomeAssistantError, Unauthorized +from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from tests.common import ( assert_setup_component, async_fire_time_changed, - mock_restore_cache, async_mock_service, + mock_restore_cache, ) from tests.components.automation import common @@ -445,7 +445,7 @@ async def test_services(hass, calls): assert automation.is_on(hass, entity_id) -async def test_reload_config_service(hass, calls): +async def test_reload_config_service(hass, calls, hass_admin_user, hass_read_only_user): """Test the reload config service.""" assert await async_setup_component( hass, @@ -488,7 +488,10 @@ async def test_reload_config_service(hass, calls): }, ): with patch("homeassistant.config.find_config_file", return_value=""): - await common.async_reload(hass) + with pytest.raises(Unauthorized): + await common.async_reload(hass, Context(user_id=hass_read_only_user.id)) + await hass.async_block_till_done() + await common.async_reload(hass, Context(user_id=hass_admin_user.id)) await hass.async_block_till_done() # De-flake ?! await hass.async_block_till_done() From 256056430ea4a06f46312403cc6eb41b69673c0c Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Sat, 7 Dec 2019 15:24:56 -0500 Subject: [PATCH 061/677] Add input_datetime reload service. (#29581) * Add input_datetime reload service. * Add reload service test. --- .../components/input_datetime/__init__.py | 61 +++++++++++----- .../components/input_datetime/services.yaml | 3 + tests/components/input_datetime/test_init.py | 71 ++++++++++++++++++- 3 files changed, 116 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/input_datetime/__init__.py b/homeassistant/components/input_datetime/__init__.py index 36180ed2bad..654f3547ad6 100644 --- a/homeassistant/components/input_datetime/__init__.py +++ b/homeassistant/components/input_datetime/__init__.py @@ -1,16 +1,22 @@ """Support to select a date and/or a time.""" -import logging import datetime +import logging import voluptuous as vol -from homeassistant.const import ATTR_DATE, ATTR_TIME, CONF_ICON, CONF_NAME +from homeassistant.const import ( + ATTR_DATE, + ATTR_TIME, + CONF_ICON, + CONF_NAME, + SERVICE_RELOAD, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.restore_state import RestoreEntity +import homeassistant.helpers.service from homeassistant.util import dt as dt_util - _LOGGER = logging.getLogger(__name__) DOMAIN = "input_datetime" @@ -52,26 +58,31 @@ CONFIG_SCHEMA = vol.Schema( }, extra=vol.ALLOW_EXTRA, ) +RELOAD_SERVICE_SCHEMA = vol.Schema({}) async def async_setup(hass, config): """Set up an input datetime.""" component = EntityComponent(_LOGGER, DOMAIN, hass) - entities = [] + entities = await _async_process_config(config) - for object_id, cfg in config[DOMAIN].items(): - name = cfg.get(CONF_NAME) - has_time = cfg.get(CONF_HAS_TIME) - has_date = cfg.get(CONF_HAS_DATE) - icon = cfg.get(CONF_ICON) - initial = cfg.get(CONF_INITIAL) - entities.append( - InputDatetime(object_id, name, has_date, has_time, icon, initial) - ) + async def reload_service_handler(service_call): + """Remove all entities and load new ones from config.""" + conf = await component.async_prepare_reload() + if conf is None: + return + new_entities = await _async_process_config(conf) + if new_entities: + await component.async_add_entities(new_entities) - if not entities: - return False + homeassistant.helpers.service.async_register_admin_service( + hass, + DOMAIN, + SERVICE_RELOAD, + reload_service_handler, + schema=RELOAD_SERVICE_SCHEMA, + ) async def async_set_datetime_service(entity, call): """Handle a call to the input datetime 'set datetime' service.""" @@ -108,10 +119,28 @@ async def async_setup(hass, config): async_set_datetime_service, ) - await component.async_add_entities(entities) + if entities: + await component.async_add_entities(entities) return True +async def _async_process_config(config): + """Process config and create list of entities.""" + entities = [] + + for object_id, cfg in config[DOMAIN].items(): + name = cfg.get(CONF_NAME) + has_time = cfg.get(CONF_HAS_TIME) + has_date = cfg.get(CONF_HAS_DATE) + icon = cfg.get(CONF_ICON) + initial = cfg.get(CONF_INITIAL) + entities.append( + InputDatetime(object_id, name, has_date, has_time, icon, initial) + ) + + return entities + + class InputDatetime(RestoreEntity): """Representation of a datetime input.""" diff --git a/homeassistant/components/input_datetime/services.yaml b/homeassistant/components/input_datetime/services.yaml index 8a40be47acd..472bd1b83b9 100644 --- a/homeassistant/components/input_datetime/services.yaml +++ b/homeassistant/components/input_datetime/services.yaml @@ -9,3 +9,6 @@ set_datetime: example: '"time": "05:30:00"'} datetime: {description: The target date & time the entity should be set to. Do not use with date or time., example: '"datetime": "2019-04-22 05:30:00"'} + +reload: + description: Reload the input_datetime configuration. diff --git a/tests/components/input_datetime/test_init.py b/tests/components/input_datetime/test_init.py index 2ddeddbefac..427433e22d2 100644 --- a/tests/components/input_datetime/test_init.py +++ b/tests/components/input_datetime/test_init.py @@ -2,20 +2,23 @@ # pylint: disable=protected-access import asyncio import datetime +from unittest.mock import patch import pytest import voluptuous as vol -from homeassistant.core import CoreState, State, Context -from homeassistant.setup import async_setup_component from homeassistant.components.input_datetime import ( - DOMAIN, ATTR_DATE, ATTR_DATETIME, ATTR_TIME, + DOMAIN, + SERVICE_RELOAD, SERVICE_SET_DATETIME, ) from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.core import Context, CoreState, State +from homeassistant.exceptions import Unauthorized +from homeassistant.setup import async_setup_component from tests.common import mock_restore_cache @@ -310,3 +313,65 @@ async def test_input_datetime_context(hass, hass_admin_user): assert state2 is not None assert state.state != state2.state assert state2.context.user_id == hass_admin_user.id + + +async def test_reload(hass, hass_admin_user, hass_read_only_user): + """Test reload service.""" + count_start = len(hass.states.async_entity_ids()) + + assert await async_setup_component( + hass, + DOMAIN, + { + DOMAIN: { + "dt1": {"has_time": False, "has_date": True, "initial": "2019-1-1"}, + } + }, + ) + + assert count_start + 1 == len(hass.states.async_entity_ids()) + + state_1 = hass.states.get("input_datetime.dt1") + state_2 = hass.states.get("input_datetime.dt2") + + dt_obj = datetime.datetime(2019, 1, 1, 0, 0) + assert state_1 is not None + assert state_2 is None + assert str(dt_obj.date()) == state_1.state + + with patch( + "homeassistant.config.load_yaml_config_file", + autospec=True, + return_value={ + DOMAIN: { + "dt1": {"has_time": True, "has_date": False, "initial": "23:32"}, + "dt2": {"has_time": True, "has_date": True}, + } + }, + ): + with patch("homeassistant.config.find_config_file", return_value=""): + with pytest.raises(Unauthorized): + await hass.services.async_call( + DOMAIN, + SERVICE_RELOAD, + blocking=True, + context=Context(user_id=hass_read_only_user.id), + ) + await hass.services.async_call( + DOMAIN, + SERVICE_RELOAD, + blocking=True, + context=Context(user_id=hass_admin_user.id), + ) + await hass.async_block_till_done() + + assert count_start + 2 == len(hass.states.async_entity_ids()) + + state_1 = hass.states.get("input_datetime.dt1") + state_2 = hass.states.get("input_datetime.dt2") + + dt_obj = datetime.datetime(2019, 1, 1, 23, 32) + assert state_1 is not None + assert state_2 is not None + assert str(dt_obj.time()) == state_1.state + assert str(datetime.datetime(1970, 1, 1, 0, 0)) == state_2.state From e360b1265f988ad192e782c981f96722a637a9e8 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Sat, 7 Dec 2019 15:26:06 -0500 Subject: [PATCH 062/677] Add input_number.reload admin service. (#29584) * Add input_number reload service. * Add test. * Allow platform setup without entities. We can reload and add entities later. --- .../components/input_number/__init__.py | 61 +++++++++++++------ .../components/input_number/services.yaml | 2 + tests/components/input_number/test_init.py | 61 ++++++++++++++++++- 3 files changed, 105 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/input_number/__init__.py b/homeassistant/components/input_number/__init__.py index 77625ffa7f8..a4438020886 100644 --- a/homeassistant/components/input_number/__init__.py +++ b/homeassistant/components/input_number/__init__.py @@ -3,16 +3,18 @@ import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.const import ( - ATTR_UNIT_OF_MEASUREMENT, ATTR_MODE, + ATTR_UNIT_OF_MEASUREMENT, CONF_ICON, - CONF_NAME, CONF_MODE, + CONF_NAME, + SERVICE_RELOAD, ) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.restore_state import RestoreEntity +import homeassistant.helpers.service _LOGGER = logging.getLogger(__name__) @@ -77,12 +79,49 @@ CONFIG_SCHEMA = vol.Schema( required=True, extra=vol.ALLOW_EXTRA, ) +RELOAD_SERVICE_SCHEMA = vol.Schema({}) async def async_setup(hass, config): """Set up an input slider.""" component = EntityComponent(_LOGGER, DOMAIN, hass) + entities = await _async_process_config(config) + + async def reload_service_handler(service_call): + """Remove all entities and load new ones from config.""" + conf = await component.async_prepare_reload() + if conf is None: + return + new_entities = await _async_process_config(conf) + if new_entities: + await component.async_add_entities(new_entities) + + homeassistant.helpers.service.async_register_admin_service( + hass, + DOMAIN, + SERVICE_RELOAD, + reload_service_handler, + schema=RELOAD_SERVICE_SCHEMA, + ) + + component.async_register_entity_service( + SERVICE_SET_VALUE, + {vol.Required(ATTR_VALUE): vol.Coerce(float)}, + "async_set_value", + ) + + component.async_register_entity_service(SERVICE_INCREMENT, {}, "async_increment") + + component.async_register_entity_service(SERVICE_DECREMENT, {}, "async_decrement") + + if entities: + await component.async_add_entities(entities) + return True + + +async def _async_process_config(config): + """Process config and create list of entities.""" entities = [] for object_id, cfg in config[DOMAIN].items(): @@ -101,21 +140,7 @@ async def async_setup(hass, config): ) ) - if not entities: - return False - - component.async_register_entity_service( - SERVICE_SET_VALUE, - {vol.Required(ATTR_VALUE): vol.Coerce(float)}, - "async_set_value", - ) - - component.async_register_entity_service(SERVICE_INCREMENT, {}, "async_increment") - - component.async_register_entity_service(SERVICE_DECREMENT, {}, "async_decrement") - - await component.async_add_entities(entities) - return True + return entities class InputNumber(RestoreEntity): diff --git a/homeassistant/components/input_number/services.yaml b/homeassistant/components/input_number/services.yaml index 650abc056a9..9cd1b913ccd 100644 --- a/homeassistant/components/input_number/services.yaml +++ b/homeassistant/components/input_number/services.yaml @@ -14,3 +14,5 @@ set_value: entity_id: {description: Entity id of the input number to set the new value., example: input_number.threshold} value: {description: The target value the entity should be set to., example: 42} +reload: + description: Reload the input_number configuration. diff --git a/tests/components/input_number/test_init.py b/tests/components/input_number/test_init.py index 02d59c367c9..a3b46212daf 100644 --- a/tests/components/input_number/test_init.py +++ b/tests/components/input_number/test_init.py @@ -1,16 +1,21 @@ """The tests for the Input number component.""" # pylint: disable=protected-access import asyncio +from unittest.mock import patch + +import pytest -from homeassistant.core import CoreState, State, Context from homeassistant.components.input_number import ( ATTR_VALUE, DOMAIN, SERVICE_DECREMENT, SERVICE_INCREMENT, + SERVICE_RELOAD, SERVICE_SET_VALUE, ) from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.core import Context, CoreState, State +from homeassistant.exceptions import Unauthorized from homeassistant.loader import bind_hass from homeassistant.setup import async_setup_component @@ -254,3 +259,57 @@ async def test_input_number_context(hass, hass_admin_user): assert state2 is not None assert state.state != state2.state assert state2.context.user_id == hass_admin_user.id + + +async def test_reload(hass, hass_admin_user, hass_read_only_user): + """Test reload service.""" + count_start = len(hass.states.async_entity_ids()) + + assert await async_setup_component( + hass, DOMAIN, {DOMAIN: {"test_1": {"initial": 50, "min": 0, "max": 51}}} + ) + + assert count_start + 1 == len(hass.states.async_entity_ids()) + + state_1 = hass.states.get("input_number.test_1") + state_2 = hass.states.get("input_number.test_2") + + assert state_1 is not None + assert state_2 is None + assert 50 == float(state_1.state) + + with patch( + "homeassistant.config.load_yaml_config_file", + autospec=True, + return_value={ + DOMAIN: { + "test_1": {"initial": 40, "min": 0, "max": 51}, + "test_2": {"initial": 20, "min": 10, "max": 30}, + } + }, + ): + with patch("homeassistant.config.find_config_file", return_value=""): + with pytest.raises(Unauthorized): + await hass.services.async_call( + DOMAIN, + SERVICE_RELOAD, + blocking=True, + context=Context(user_id=hass_read_only_user.id), + ) + await hass.services.async_call( + DOMAIN, + SERVICE_RELOAD, + blocking=True, + context=Context(user_id=hass_admin_user.id), + ) + await hass.async_block_till_done() + + assert count_start + 2 == len(hass.states.async_entity_ids()) + + state_1 = hass.states.get("input_number.test_1") + state_2 = hass.states.get("input_number.test_2") + + assert state_1 is not None + assert state_2 is not None + assert 40 == float(state_1.state) + assert 20 == float(state_2.state) From 0cdc315038e9621326c72ec79879cac2fb5ac2ad Mon Sep 17 00:00:00 2001 From: Marius <33354141+Mariusthvdb@users.noreply.github.com> Date: Sun, 8 Dec 2019 06:25:39 +0100 Subject: [PATCH 063/677] change icon for partly-cloudy-night (#29601) to the available mdi:weather-night-partly-cloudy --- homeassistant/components/darksky/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/darksky/sensor.py b/homeassistant/components/darksky/sensor.py index cd8417e3e84..82aaccc9590 100644 --- a/homeassistant/components/darksky/sensor.py +++ b/homeassistant/components/darksky/sensor.py @@ -385,7 +385,7 @@ CONDITION_PICTURES = { ], "partly-cloudy-night": [ "/static/images/darksky/weather-cloudy.svg", - "mdi:weather-partly-cloudy", + "mdi:weather-night-partly-cloudy", ], } From 7f4baab3f6f4a43991cedb47f808d71edb727a67 Mon Sep 17 00:00:00 2001 From: SukramJ Date: Sun, 8 Dec 2019 09:20:13 +0100 Subject: [PATCH 064/677] Add additional Magic Cube Model (#29598) --- homeassistant/components/deconz/device_trigger.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/deconz/device_trigger.py b/homeassistant/components/deconz/device_trigger.py index b6691548b87..d057de23d02 100644 --- a/homeassistant/components/deconz/device_trigger.py +++ b/homeassistant/components/deconz/device_trigger.py @@ -147,6 +147,7 @@ TRADFRI_WIRELESS_DIMMER = { } AQARA_CUBE_MODEL = "lumi.sensor_cube" +AQARA_CUBE_MODEL_ALT1 = "lumi.sensor_cube.aqgl01" AQARA_CUBE = { (CONF_ROTATE_FROM_SIDE_1, CONF_SIDE_2): 6002, (CONF_ROTATE_FROM_SIDE_1, CONF_SIDE_3): 3002, @@ -262,6 +263,7 @@ REMOTES = { TRADFRI_REMOTE_MODEL: TRADFRI_REMOTE, TRADFRI_WIRELESS_DIMMER_MODEL: TRADFRI_WIRELESS_DIMMER, AQARA_CUBE_MODEL: AQARA_CUBE, + AQARA_CUBE_MODEL_ALT1: AQARA_CUBE, AQARA_DOUBLE_WALL_SWITCH_MODEL: AQARA_DOUBLE_WALL_SWITCH, AQARA_DOUBLE_WALL_SWITCH_WXKG02LM_MODEL: AQARA_DOUBLE_WALL_SWITCH_WXKG02LM, AQARA_MINI_SWITCH_MODEL: AQARA_MINI_SWITCH, From cc9589cff2f8447674f39d06a7322abd6aa16c27 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sun, 8 Dec 2019 09:26:31 +0100 Subject: [PATCH 065/677] Add Elgato Key Light integration (#29592) * Add Elgato Key Light integration * Remove passing in of hass loop * Tweaks a comment * Tweaks a function name * Ensure domain namespace in data exists in entry setup --- CODEOWNERS | 1 + .../components/elgato/.translations/en.json | 27 ++ homeassistant/components/elgato/__init__.py | 55 ++++ .../components/elgato/config_flow.py | 146 +++++++++++ homeassistant/components/elgato/const.py | 17 ++ homeassistant/components/elgato/light.py | 158 ++++++++++++ homeassistant/components/elgato/manifest.json | 10 + homeassistant/components/elgato/strings.json | 27 ++ homeassistant/generated/config_flows.py | 1 + homeassistant/generated/zeroconf.py | 3 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/elgato/__init__.py | 49 ++++ tests/components/elgato/test_config_flow.py | 238 ++++++++++++++++++ tests/components/elgato/test_init.py | 33 +++ tests/components/elgato/test_light.py | 104 ++++++++ tests/fixtures/elgato/info.json | 9 + tests/fixtures/elgato/state.json | 10 + 18 files changed, 894 insertions(+) create mode 100644 homeassistant/components/elgato/.translations/en.json create mode 100644 homeassistant/components/elgato/__init__.py create mode 100644 homeassistant/components/elgato/config_flow.py create mode 100644 homeassistant/components/elgato/const.py create mode 100644 homeassistant/components/elgato/light.py create mode 100644 homeassistant/components/elgato/manifest.json create mode 100644 homeassistant/components/elgato/strings.json create mode 100644 tests/components/elgato/__init__.py create mode 100644 tests/components/elgato/test_config_flow.py create mode 100644 tests/components/elgato/test_init.py create mode 100644 tests/components/elgato/test_light.py create mode 100644 tests/fixtures/elgato/info.json create mode 100644 tests/fixtures/elgato/state.json diff --git a/CODEOWNERS b/CODEOWNERS index 8078aadf641..7f5ff17a043 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -85,6 +85,7 @@ homeassistant/components/ecobee/* @marthoc homeassistant/components/ecovacs/* @OverloadUT homeassistant/components/egardia/* @jeroenterheerdt homeassistant/components/eight_sleep/* @mezz64 +homeassistant/components/elgato/* @frenck homeassistant/components/elv/* @majuss homeassistant/components/emby/* @mezz64 homeassistant/components/emulated_hue/* @NobleKangaroo diff --git a/homeassistant/components/elgato/.translations/en.json b/homeassistant/components/elgato/.translations/en.json new file mode 100644 index 00000000000..03c46f02efc --- /dev/null +++ b/homeassistant/components/elgato/.translations/en.json @@ -0,0 +1,27 @@ +{ + "config": { + "title": "Elgato Key Light", + "flow_title": "Elgato Key Light: {serial_number}", + "step": { + "user": { + "title": "Link your Elgato Key Light", + "description": "Set up your Elgato Key Light to integrate with Home Assistant.", + "data": { + "host": "Host or IP address", + "port": "Port number" + } + }, + "zeroconf_confirm": { + "description": "Do you want to add the Elgato Key Light with serial number `{serial_number}` to Home Assistant?", + "title": "Discovered Elgato Key Light device" + } + }, + "error": { + "connection_error": "Failed to connect to Elgato Key Light device." + }, + "abort": { + "already_configured": "This Elgato Key Light device is already configured.", + "connection_error": "Failed to connect to Elgato Key Light device." + } + } +} diff --git a/homeassistant/components/elgato/__init__.py b/homeassistant/components/elgato/__init__.py new file mode 100644 index 00000000000..993748033b5 --- /dev/null +++ b/homeassistant/components/elgato/__init__.py @@ -0,0 +1,55 @@ +"""Support for Elgato Key Lights.""" +import logging + +from elgato import Elgato, ElgatoConnectionError + +from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_HOST, CONF_PORT +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.typing import ConfigType + +from .const import DATA_ELGATO_CLIENT, DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: + """Set up the Elgato Key Light components.""" + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up Elgato Key Light from a config entry.""" + session = async_get_clientsession(hass) + elgato = Elgato(entry.data[CONF_HOST], port=entry.data[CONF_PORT], session=session,) + + # Ensure we can connect to it + try: + await elgato.info() + except ElgatoConnectionError as exception: + raise ConfigEntryNotReady from exception + + hass.data.setdefault(DOMAIN, {}) + hass.data[DOMAIN][entry.entry_id] = {DATA_ELGATO_CLIENT: elgato} + + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, LIGHT_DOMAIN) + ) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload Elgato Key Light config entry.""" + # Unload entities for this entry/device. + await hass.config_entries.async_forward_entry_unload(entry, LIGHT_DOMAIN) + + # Cleanup + del hass.data[DOMAIN][entry.entry_id] + if not hass.data[DOMAIN]: + del hass.data[DOMAIN] + + return True diff --git a/homeassistant/components/elgato/config_flow.py b/homeassistant/components/elgato/config_flow.py new file mode 100644 index 00000000000..1d14fca18d2 --- /dev/null +++ b/homeassistant/components/elgato/config_flow.py @@ -0,0 +1,146 @@ +"""Config flow to configure the Elgato Key Light integration.""" +import logging +from typing import Any, Dict, Optional + +from elgato import Elgato, ElgatoError, Info +import voluptuous as vol + +from homeassistant.config_entries import CONN_CLASS_LOCAL_POLL, ConfigFlow +from homeassistant.const import CONF_HOST, CONF_PORT +from homeassistant.helpers import ConfigType +from homeassistant.helpers.aiohttp_client import async_get_clientsession + +from .const import CONF_SERIAL_NUMBER, DOMAIN # pylint: disable=unused-import + +_LOGGER = logging.getLogger(__name__) + + +class ElgatoFlowHandler(ConfigFlow, domain=DOMAIN): + """Handle a Elgato Key Light config flow.""" + + VERSION = 1 + CONNECTION_CLASS = CONN_CLASS_LOCAL_POLL + + async def async_step_user( + self, user_input: Optional[ConfigType] = None + ) -> Dict[str, Any]: + """Handle a flow initiated by the user.""" + if user_input is None: + return self._show_setup_form() + + try: + info = await self._get_elgato_info( + user_input[CONF_HOST], user_input[CONF_PORT] + ) + except ElgatoError: + return self._show_setup_form({"base": "connection_error"}) + + # Check if already configured + if await self._device_already_configured(info): + # This serial number is already configured + return self.async_abort(reason="already_configured") + + return self.async_create_entry( + title=info.serial_number, + data={ + CONF_HOST: user_input[CONF_HOST], + CONF_PORT: user_input[CONF_PORT], + CONF_SERIAL_NUMBER: info.serial_number, + }, + ) + + async def async_step_zeroconf( + self, user_input: Optional[ConfigType] = None + ) -> Dict[str, Any]: + """Handle zeroconf discovery.""" + if user_input is None: + return self.async_abort(reason="connection_error") + + # Hostname is format: my-ke.local. + host = user_input["hostname"].rstrip(".") + try: + info = await self._get_elgato_info(host, user_input[CONF_PORT]) + except ElgatoError: + return self.async_abort(reason="connection_error") + + # Check if already configured + if await self._device_already_configured(info): + # This serial number is already configured + return self.async_abort(reason="already_configured") + + # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 + self.context.update( + { + CONF_HOST: host, + CONF_PORT: user_input[CONF_PORT], + CONF_SERIAL_NUMBER: info.serial_number, + "title_placeholders": {"serial_number": info.serial_number}, + } + ) + + # Prepare configuration flow + return self._show_confirm_dialog() + + # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 + async def async_step_zeroconf_confirm( + self, user_input: ConfigType = None + ) -> Dict[str, Any]: + """Handle a flow initiated by zeroconf.""" + if user_input is None: + return self._show_confirm_dialog() + + try: + info = await self._get_elgato_info( + self.context.get(CONF_HOST), self.context.get(CONF_PORT) + ) + except ElgatoError: + return self.async_abort(reason="connection_error") + + # Check if already configured + if await self._device_already_configured(info): + # This serial number is already configured + return self.async_abort(reason="already_configured") + + return self.async_create_entry( + title=self.context.get(CONF_SERIAL_NUMBER), + data={ + CONF_HOST: self.context.get(CONF_HOST), + CONF_PORT: self.context.get(CONF_PORT), + CONF_SERIAL_NUMBER: self.context.get(CONF_SERIAL_NUMBER), + }, + ) + + def _show_setup_form(self, errors: Optional[Dict] = None) -> Dict[str, Any]: + """Show the setup form to the user.""" + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Required(CONF_HOST): str, + vol.Optional(CONF_PORT, default=9123): int, + } + ), + errors=errors or {}, + ) + + def _show_confirm_dialog(self) -> Dict[str, Any]: + """Show the confirm dialog to the user.""" + # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 + serial_number = self.context.get(CONF_SERIAL_NUMBER) + return self.async_show_form( + step_id="zeroconf_confirm", + description_placeholders={"serial_number": serial_number}, + ) + + async def _get_elgato_info(self, host: str, port: int) -> Info: + """Get device information from an Elgato Key Light device.""" + session = async_get_clientsession(self.hass) + elgato = Elgato(host, port=port, session=session,) + return await elgato.info() + + async def _device_already_configured(self, info: Info) -> bool: + """Return if a Elgato Key Light is already configured.""" + for entry in self._async_current_entries(): + if entry.data[CONF_SERIAL_NUMBER] == info.serial_number: + return True + return False diff --git a/homeassistant/components/elgato/const.py b/homeassistant/components/elgato/const.py new file mode 100644 index 00000000000..4983608f899 --- /dev/null +++ b/homeassistant/components/elgato/const.py @@ -0,0 +1,17 @@ +"""Constants for the Elgato Key Light integration.""" + +# Integration domain +DOMAIN = "elgato" + +# Hass data keys +DATA_ELGATO_CLIENT = "elgato_client" + +# Attributes +ATTR_IDENTIFIERS = "identifiers" +ATTR_MANUFACTURER = "manufacturer" +ATTR_MODEL = "model" +ATTR_ON = "on" +ATTR_SOFTWARE_VERSION = "sw_version" +ATTR_TEMPERATURE = "temperature" + +CONF_SERIAL_NUMBER = "serial_number" diff --git a/homeassistant/components/elgato/light.py b/homeassistant/components/elgato/light.py new file mode 100644 index 00000000000..99bca1ba20e --- /dev/null +++ b/homeassistant/components/elgato/light.py @@ -0,0 +1,158 @@ +"""Support for LED lights.""" +from datetime import timedelta +import logging +from typing import Any, Callable, Dict, List, Optional + +from elgato import Elgato, ElgatoError, Info, State + +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + ATTR_COLOR_TEMP, + SUPPORT_BRIGHTNESS, + SUPPORT_COLOR_TEMP, + Light, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ATTR_NAME +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.typing import HomeAssistantType + +from .const import ( + ATTR_IDENTIFIERS, + ATTR_MANUFACTURER, + ATTR_MODEL, + ATTR_ON, + ATTR_SOFTWARE_VERSION, + ATTR_TEMPERATURE, + DATA_ELGATO_CLIENT, + DOMAIN, +) + +_LOGGER = logging.getLogger(__name__) + +PARALLEL_UPDATES = 1 +SCAN_INTERVAL = timedelta(seconds=10) + + +async def async_setup_entry( + hass: HomeAssistantType, + entry: ConfigEntry, + async_add_entities: Callable[[List[Entity], bool], None], +) -> None: + """Set up Elgato Key Light based on a config entry.""" + elgato: Elgato = hass.data[DOMAIN][entry.entry_id][DATA_ELGATO_CLIENT] + info = await elgato.info() + async_add_entities([ElgatoLight(entry.entry_id, elgato, info)], True) + + +class ElgatoLight(Light): + """Defines a Elgato Key Light.""" + + def __init__( + self, entry_id: str, elgato: Elgato, info: Info, + ): + """Initialize Elgato Key Light.""" + self._brightness: Optional[int] = None + self._info: Info = info + self._state: Optional[bool] = None + self._temperature: Optional[int] = None + self._available = True + self.elgato = elgato + + @property + def name(self) -> str: + """Return the name of the entity.""" + # Return the product name, if display name is not set + if not self._info.display_name: + return self._info.product_name + return self._info.display_name + + @property + def available(self) -> bool: + """Return True if entity is available.""" + return self._available + + @property + def unique_id(self) -> str: + """Return the unique ID for this sensor.""" + return self._info.serial_number + + @property + def brightness(self) -> Optional[int]: + """Return the brightness of this light between 1..255.""" + return self._brightness + + @property + def color_temp(self): + """Return the CT color value in mireds.""" + return self._temperature + + @property + def min_mireds(self): + """Return the coldest color_temp that this light supports.""" + return 143 + + @property + def max_mireds(self): + """Return the warmest color_temp that this light supports.""" + return 344 + + @property + def supported_features(self) -> int: + """Flag supported features.""" + return SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP + + @property + def is_on(self) -> bool: + """Return the state of the light.""" + return bool(self._state) + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn off the light.""" + await self.async_turn_on(on=False) + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn on the light.""" + data = {} + + data[ATTR_ON] = True + if ATTR_ON in kwargs: + data[ATTR_ON] = kwargs[ATTR_ON] + + if ATTR_COLOR_TEMP in kwargs: + data[ATTR_TEMPERATURE] = kwargs[ATTR_COLOR_TEMP] + + if ATTR_BRIGHTNESS in kwargs: + data[ATTR_BRIGHTNESS] = round((kwargs[ATTR_BRIGHTNESS] / 255) * 100) + + try: + await self.elgato.light(**data) + except ElgatoError: + _LOGGER.error("An error occurred while updating the Elgato Key Light") + self._available = False + + async def async_update(self) -> None: + """Update Elgato entity.""" + try: + state: State = await self.elgato.state() + except ElgatoError: + if self._available: + _LOGGER.error("An error occurred while updating the Elgato Key Light") + self._available = False + return + + self._available = True + self._brightness = round((state.brightness * 255) / 100) + self._state = state.on + self._temperature = state.temperature + + @property + def device_info(self) -> Dict[str, Any]: + """Return device information about this Elgato Key Light.""" + return { + ATTR_IDENTIFIERS: {(DOMAIN, self._info.serial_number)}, + ATTR_NAME: self._info.product_name, + ATTR_MANUFACTURER: "Elgato", + ATTR_MODEL: self._info.product_name, + ATTR_SOFTWARE_VERSION: f"{self._info.firmware_version} ({self._info.firmware_build_number})", + } diff --git a/homeassistant/components/elgato/manifest.json b/homeassistant/components/elgato/manifest.json new file mode 100644 index 00000000000..bed28364fa1 --- /dev/null +++ b/homeassistant/components/elgato/manifest.json @@ -0,0 +1,10 @@ +{ + "domain": "elgato", + "name": "Elgato Key Light", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/elgato", + "requirements": ["elgato==0.1.0"], + "dependencies": [], + "zeroconf": ["_elg._tcp.local."], + "codeowners": ["@frenck"] +} diff --git a/homeassistant/components/elgato/strings.json b/homeassistant/components/elgato/strings.json new file mode 100644 index 00000000000..03c46f02efc --- /dev/null +++ b/homeassistant/components/elgato/strings.json @@ -0,0 +1,27 @@ +{ + "config": { + "title": "Elgato Key Light", + "flow_title": "Elgato Key Light: {serial_number}", + "step": { + "user": { + "title": "Link your Elgato Key Light", + "description": "Set up your Elgato Key Light to integrate with Home Assistant.", + "data": { + "host": "Host or IP address", + "port": "Port number" + } + }, + "zeroconf_confirm": { + "description": "Do you want to add the Elgato Key Light with serial number `{serial_number}` to Home Assistant?", + "title": "Discovered Elgato Key Light device" + } + }, + "error": { + "connection_error": "Failed to connect to Elgato Key Light device." + }, + "abort": { + "already_configured": "This Elgato Key Light device is already configured.", + "connection_error": "Failed to connect to Elgato Key Light device." + } + } +} diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 8d4be47f5f8..cf1c4b55e19 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -20,6 +20,7 @@ FLOWS = [ "deconz", "dialogflow", "ecobee", + "elgato", "emulated_roku", "esphome", "geofency", diff --git a/homeassistant/generated/zeroconf.py b/homeassistant/generated/zeroconf.py index 108fe38e647..306b3850a1b 100644 --- a/homeassistant/generated/zeroconf.py +++ b/homeassistant/generated/zeroconf.py @@ -12,6 +12,9 @@ ZEROCONF = { "_coap._udp.local.": [ "tradfri" ], + "_elg._tcp.local.": [ + "elgato" + ], "_esphomelib._tcp.local.": [ "esphome" ], diff --git a/requirements_all.txt b/requirements_all.txt index 094728834df..2d8a2b881d4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -459,6 +459,9 @@ ecoaliface==0.4.0 # homeassistant.components.ee_brightbox eebrightbox==0.0.4 +# homeassistant.components.elgato +elgato==0.1.0 + # homeassistant.components.eliqonline eliqonline==1.2.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6b5bcf96d2d..08174f87874 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -158,6 +158,9 @@ dsmr_parser==0.12 # homeassistant.components.ee_brightbox eebrightbox==0.0.4 +# homeassistant.components.elgato +elgato==0.1.0 + # homeassistant.components.emulated_roku emulated_roku==0.1.8 diff --git a/tests/components/elgato/__init__.py b/tests/components/elgato/__init__.py new file mode 100644 index 00000000000..1dae6cb1dac --- /dev/null +++ b/tests/components/elgato/__init__.py @@ -0,0 +1,49 @@ +"""Tests for the Elgato Key Light integration.""" + +from homeassistant.components.elgato.const import CONF_SERIAL_NUMBER, DOMAIN +from homeassistant.const import CONF_HOST, CONF_PORT +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry, load_fixture +from tests.test_util.aiohttp import AiohttpClientMocker + + +async def init_integration( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, skip_setup: bool = False, +) -> MockConfigEntry: + """Set up the Elgato Key Light integration in Home Assistant.""" + + aioclient_mock.get( + "http://example.local:9123/elgato/accessory-info", + text=load_fixture("elgato/info.json"), + headers={"Content-Type": "application/json"}, + ) + + aioclient_mock.put( + "http://example.local:9123/elgato/lights", + text=load_fixture("elgato/state.json"), + headers={"Content-Type": "application/json"}, + ) + + aioclient_mock.get( + "http://example.local:9123/elgato/lights", + text=load_fixture("elgato/state.json"), + headers={"Content-Type": "application/json"}, + ) + + entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_HOST: "example.local", + CONF_PORT: 9123, + CONF_SERIAL_NUMBER: "CN11A1A00001", + }, + ) + + entry.add_to_hass(hass) + + if not skip_setup: + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + return entry diff --git a/tests/components/elgato/test_config_flow.py b/tests/components/elgato/test_config_flow.py new file mode 100644 index 00000000000..f84b82527a2 --- /dev/null +++ b/tests/components/elgato/test_config_flow.py @@ -0,0 +1,238 @@ +"""Tests for the Elgato Key Light config flow.""" +import aiohttp + +from homeassistant import data_entry_flow +from homeassistant.components.elgato import config_flow +from homeassistant.components.elgato.const import CONF_SERIAL_NUMBER +from homeassistant.config_entries import SOURCE_USER, SOURCE_ZEROCONF +from homeassistant.const import CONF_HOST, CONF_PORT +from homeassistant.core import HomeAssistant + +from . import init_integration + +from tests.common import load_fixture +from tests.test_util.aiohttp import AiohttpClientMocker + + +async def test_show_user_form(hass: HomeAssistant) -> None: + """Test that the user set up form is served.""" + flow = config_flow.ElgatoFlowHandler() + flow.hass = hass + flow.context = {"source": SOURCE_USER} + result = await flow.async_step_user(user_input=None) + + assert result["step_id"] == "user" + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + + +async def test_show_zeroconf_confirm_form(hass: HomeAssistant) -> None: + """Test that the zeroconf confirmation form is served.""" + flow = config_flow.ElgatoFlowHandler() + flow.hass = hass + flow.context = {"source": SOURCE_ZEROCONF, CONF_SERIAL_NUMBER: "12345"} + result = await flow.async_step_zeroconf_confirm() + + assert result["description_placeholders"] == {CONF_SERIAL_NUMBER: "12345"} + assert result["step_id"] == "zeroconf_confirm" + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + + +async def test_show_zerconf_form( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test that the zeroconf confirmation form is served.""" + aioclient_mock.get( + "http://example.local:9123/elgato/accessory-info", + text=load_fixture("elgato/info.json"), + headers={"Content-Type": "application/json"}, + ) + + flow = config_flow.ElgatoFlowHandler() + flow.hass = hass + flow.context = {"source": SOURCE_ZEROCONF} + result = await flow.async_step_zeroconf( + {"hostname": "example.local.", "port": 9123} + ) + + assert flow.context[CONF_HOST] == "example.local" + assert flow.context[CONF_PORT] == 9123 + assert flow.context[CONF_SERIAL_NUMBER] == "CN11A1A00001" + assert result["description_placeholders"] == {CONF_SERIAL_NUMBER: "CN11A1A00001"} + assert result["step_id"] == "zeroconf_confirm" + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + + +async def test_connection_error( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test we show user form on Elgato Key Light connection error.""" + aioclient_mock.get( + "http://example.local/elgato/accessory-info", exc=aiohttp.ClientError + ) + + flow = config_flow.ElgatoFlowHandler() + flow.hass = hass + flow.context = {"source": SOURCE_USER} + result = await flow.async_step_user( + user_input={CONF_HOST: "example.local", CONF_PORT: 9123} + ) + + assert result["errors"] == {"base": "connection_error"} + assert result["step_id"] == "user" + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + + +async def test_zeroconf_connection_error( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test we abort zeroconf flow on Elgato Key Light connection error.""" + aioclient_mock.get( + "http://example.local/elgato/accessory-info", exc=aiohttp.ClientError + ) + + flow = config_flow.ElgatoFlowHandler() + flow.hass = hass + flow.context = {"source": SOURCE_ZEROCONF} + result = await flow.async_step_zeroconf( + user_input={"hostname": "example.local.", "port": 9123} + ) + + assert result["reason"] == "connection_error" + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + + +async def test_zeroconf_confirm_connection_error( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test we abort zeroconf flow on Elgato Key Light connection error.""" + aioclient_mock.get( + "http://example.local/elgato/accessory-info", exc=aiohttp.ClientError + ) + + flow = config_flow.ElgatoFlowHandler() + flow.hass = hass + flow.context = { + "source": SOURCE_ZEROCONF, + CONF_HOST: "example.local", + CONF_PORT: 9123, + } + result = await flow.async_step_zeroconf_confirm( + user_input={CONF_HOST: "example.local", CONF_PORT: 9123} + ) + + assert result["reason"] == "connection_error" + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + + +async def test_zeroconf_no_data( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test we abort if zeroconf provides no data.""" + flow = config_flow.ElgatoFlowHandler() + flow.hass = hass + result = await flow.async_step_zeroconf() + + assert result["reason"] == "connection_error" + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + + +async def test_user_device_exists_abort( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test we abort zeroconf flow if Elgato Key Light device already configured.""" + await init_integration(hass, aioclient_mock) + + flow = config_flow.ElgatoFlowHandler() + flow.hass = hass + flow.context = {"source": SOURCE_USER} + result = await flow.async_step_user({CONF_HOST: "example.local", CONF_PORT: 9123}) + + assert result["reason"] == "already_configured" + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + + +async def test_zeroconf_device_exists_abort( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test we abort zeroconf flow if Elgato Key Light device already configured.""" + await init_integration(hass, aioclient_mock) + + flow = config_flow.ElgatoFlowHandler() + flow.hass = hass + flow.context = {"source": SOURCE_ZEROCONF} + result = await flow.async_step_zeroconf( + {"hostname": "example.local.", "port": 9123} + ) + + assert result["reason"] == "already_configured" + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + + flow.context = {"source": SOURCE_ZEROCONF, CONF_HOST: "example.local", "port": 9123} + result = await flow.async_step_zeroconf_confirm( + {"hostname": "example.local.", "port": 9123} + ) + + assert result["reason"] == "already_configured" + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + + +async def test_full_user_flow_implementation( + hass: HomeAssistant, aioclient_mock +) -> None: + """Test the full manual user flow from start to finish.""" + aioclient_mock.get( + "http://example.local:9123/elgato/accessory-info", + text=load_fixture("elgato/info.json"), + headers={"Content-Type": "application/json"}, + ) + + flow = config_flow.ElgatoFlowHandler() + flow.hass = hass + flow.context = {"source": SOURCE_USER} + result = await flow.async_step_user(user_input=None) + + assert result["step_id"] == "user" + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + + result = await flow.async_step_user( + user_input={CONF_HOST: "example.local", CONF_PORT: 9123} + ) + assert result["data"][CONF_HOST] == "example.local" + assert result["data"][CONF_PORT] == 9123 + assert result["data"][CONF_SERIAL_NUMBER] == "CN11A1A00001" + assert result["title"] == "CN11A1A00001" + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + + +async def test_full_zeroconf_flow_implementation( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test the full manual user flow from start to finish.""" + aioclient_mock.get( + "http://example.local:9123/elgato/accessory-info", + text=load_fixture("elgato/info.json"), + headers={"Content-Type": "application/json"}, + ) + + flow = config_flow.ElgatoFlowHandler() + flow.hass = hass + flow.context = {"source": SOURCE_ZEROCONF} + result = await flow.async_step_zeroconf( + {"hostname": "example.local.", "port": 9123} + ) + + assert flow.context[CONF_HOST] == "example.local" + assert flow.context[CONF_PORT] == 9123 + assert flow.context[CONF_SERIAL_NUMBER] == "CN11A1A00001" + assert result["description_placeholders"] == {CONF_SERIAL_NUMBER: "CN11A1A00001"} + assert result["step_id"] == "zeroconf_confirm" + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + + result = await flow.async_step_zeroconf_confirm( + user_input={CONF_HOST: "example.local"} + ) + assert result["data"][CONF_HOST] == "example.local" + assert result["data"][CONF_PORT] == 9123 + assert result["data"][CONF_SERIAL_NUMBER] == "CN11A1A00001" + assert result["title"] == "CN11A1A00001" + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY diff --git a/tests/components/elgato/test_init.py b/tests/components/elgato/test_init.py new file mode 100644 index 00000000000..fd2f86fe2ea --- /dev/null +++ b/tests/components/elgato/test_init.py @@ -0,0 +1,33 @@ +"""Tests for the Elgato Key Light integration.""" +import aiohttp + +from homeassistant.components.elgato.const import DOMAIN +from homeassistant.config_entries import ENTRY_STATE_SETUP_RETRY +from homeassistant.core import HomeAssistant + +from tests.components.elgato import init_integration +from tests.test_util.aiohttp import AiohttpClientMocker + + +async def test_config_entry_not_ready( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test the Elgato Key Light configuration entry not ready.""" + aioclient_mock.get( + "http://example.local:9123/elgato/accessory-info", exc=aiohttp.ClientError + ) + + entry = await init_integration(hass, aioclient_mock) + assert entry.state == ENTRY_STATE_SETUP_RETRY + + +async def test_unload_config_entry( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test the Elgato Key Light configuration entry unloading.""" + entry = await init_integration(hass, aioclient_mock) + assert hass.data[DOMAIN] + + await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + assert not hass.data.get(DOMAIN) diff --git a/tests/components/elgato/test_light.py b/tests/components/elgato/test_light.py new file mode 100644 index 00000000000..13898dad757 --- /dev/null +++ b/tests/components/elgato/test_light.py @@ -0,0 +1,104 @@ +"""Tests for the Elgato Key Light light platform.""" +from unittest.mock import patch + +from homeassistant.components.elgato.light import ElgatoError +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + ATTR_COLOR_TEMP, + DOMAIN as LIGHT_DOMAIN, +) +from homeassistant.const import ( + ATTR_ENTITY_ID, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_ON, + STATE_UNAVAILABLE, +) +from homeassistant.core import HomeAssistant + +from tests.common import mock_coro +from tests.components.elgato import init_integration +from tests.test_util.aiohttp import AiohttpClientMocker + + +async def test_light_state( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test the creation and values of the Elgato Key Lights.""" + await init_integration(hass, aioclient_mock) + + entity_registry = await hass.helpers.entity_registry.async_get_registry() + + # First segment of the strip + state = hass.states.get("light.frenck") + assert state + assert state.attributes.get(ATTR_BRIGHTNESS) == 54 + assert state.attributes.get(ATTR_COLOR_TEMP) == 297 + assert state.state == STATE_ON + + entry = entity_registry.async_get("light.frenck") + assert entry + assert entry.unique_id == "CN11A1A00001" + + +async def test_light_change_state( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test the change of state of a Elgato Key Light device.""" + await init_integration(hass, aioclient_mock) + + state = hass.states.get("light.frenck") + assert state.state == STATE_ON + + with patch( + "homeassistant.components.elgato.light.Elgato.light", return_value=mock_coro(), + ) as mock_light: + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + { + ATTR_ENTITY_ID: "light.frenck", + ATTR_BRIGHTNESS: 255, + ATTR_COLOR_TEMP: 100, + }, + blocking=True, + ) + await hass.async_block_till_done() + assert len(mock_light.mock_calls) == 1 + mock_light.assert_called_with(on=True, brightness=100, temperature=100) + + with patch( + "homeassistant.components.elgato.light.Elgato.light", return_value=mock_coro(), + ) as mock_light: + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "light.frenck"}, + blocking=True, + ) + await hass.async_block_till_done() + assert len(mock_light.mock_calls) == 1 + mock_light.assert_called_with(on=False) + + +async def test_light_unavailable( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test error/unavailable handling of an Elgato Key Light.""" + await init_integration(hass, aioclient_mock) + with patch( + "homeassistant.components.elgato.light.Elgato.light", side_effect=ElgatoError, + ): + with patch( + "homeassistant.components.elgato.light.Elgato.state", + side_effect=ElgatoError, + ): + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "light.frenck"}, + blocking=True, + ) + await hass.async_block_till_done() + state = hass.states.get("light.frenck") + assert state.state == STATE_UNAVAILABLE diff --git a/tests/fixtures/elgato/info.json b/tests/fixtures/elgato/info.json new file mode 100644 index 00000000000..e2a816df26e --- /dev/null +++ b/tests/fixtures/elgato/info.json @@ -0,0 +1,9 @@ +{ + "productName": "Elgato Key Light", + "hardwareBoardType": 53, + "firmwareBuildNumber": 192, + "firmwareVersion": "1.0.3", + "serialNumber": "CN11A1A00001", + "displayName": "Frenck", + "features": ["lights"] +} diff --git a/tests/fixtures/elgato/state.json b/tests/fixtures/elgato/state.json new file mode 100644 index 00000000000..f6180e14238 --- /dev/null +++ b/tests/fixtures/elgato/state.json @@ -0,0 +1,10 @@ +{ + "numberOfLights": 1, + "lights": [ + { + "on": 1, + "brightness": 21, + "temperature": 297 + } + ] +} From b759d50900f025ebadec0197e2ef357a3c48395e Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Sun, 8 Dec 2019 09:45:13 +0100 Subject: [PATCH 066/677] Move imports to top for google_assistant (#29561) * Move imports to top for google_assistant * Fix pylint error caused by isorting the imports with noqa: F401 * Move back an import because of circular dependency, add annotations --- .../components/google_assistant/__init__.py | 27 ++++----- .../components/google_assistant/const.py | 2 +- .../components/google_assistant/helpers.py | 21 ++++--- .../components/google_assistant/http.py | 18 +++--- .../google_assistant/report_state.py | 4 +- .../components/google_assistant/smart_home.py | 9 ++- .../components/google_assistant/trait.py | 59 ++++++++++--------- 7 files changed, 71 insertions(+), 69 deletions(-) diff --git a/homeassistant/components/google_assistant/__init__.py b/homeassistant/components/google_assistant/__init__.py index ecb6d767817..f34a8e342c4 100644 --- a/homeassistant/components/google_assistant/__init__.py +++ b/homeassistant/components/google_assistant/__init__.py @@ -1,34 +1,33 @@ """Support for Actions on Google Assistant Smart Home Control.""" import logging -from typing import Dict, Any +from typing import Any, Dict import voluptuous as vol # Typing imports -from homeassistant.core import HomeAssistant, ServiceCall - from homeassistant.const import CONF_NAME +from homeassistant.core import HomeAssistant, ServiceCall from homeassistant.helpers import config_validation as cv from .const import ( - DOMAIN, - CONF_PROJECT_ID, - CONF_EXPOSE_BY_DEFAULT, - DEFAULT_EXPOSE_BY_DEFAULT, - CONF_EXPOSED_DOMAINS, - DEFAULT_EXPOSED_DOMAINS, + CONF_ALIASES, + CONF_ALLOW_UNLOCK, CONF_API_KEY, - SERVICE_REQUEST_SYNC, + CONF_CLIENT_EMAIL, CONF_ENTITY_CONFIG, CONF_EXPOSE, - CONF_ALIASES, + CONF_EXPOSE_BY_DEFAULT, + CONF_EXPOSED_DOMAINS, + CONF_PRIVATE_KEY, + CONF_PROJECT_ID, CONF_REPORT_STATE, CONF_ROOM_HINT, - CONF_ALLOW_UNLOCK, CONF_SECURE_DEVICES_PIN, CONF_SERVICE_ACCOUNT, - CONF_CLIENT_EMAIL, - CONF_PRIVATE_KEY, + DEFAULT_EXPOSE_BY_DEFAULT, + DEFAULT_EXPOSED_DOMAINS, + DOMAIN, + SERVICE_REQUEST_SYNC, ) from .const import EVENT_COMMAND_RECEIVED, EVENT_SYNC_RECEIVED # noqa: F401 from .const import EVENT_QUERY_RECEIVED # noqa: F401 diff --git a/homeassistant/components/google_assistant/const.py b/homeassistant/components/google_assistant/const.py index 35a04e0e08e..dcb87d1d93d 100644 --- a/homeassistant/components/google_assistant/const.py +++ b/homeassistant/components/google_assistant/const.py @@ -1,5 +1,6 @@ """Constants for Google Assistant.""" from homeassistant.components import ( + alarm_control_panel, binary_sensor, camera, climate, @@ -15,7 +16,6 @@ from homeassistant.components import ( sensor, switch, vacuum, - alarm_control_panel, ) DOMAIN = "google_assistant" diff --git a/homeassistant/components/google_assistant/helpers.py b/homeassistant/components/google_assistant/helpers.py index 09859c5d3d0..8a847eca705 100644 --- a/homeassistant/components/google_assistant/helpers.py +++ b/homeassistant/components/google_assistant/helpers.py @@ -7,26 +7,26 @@ from typing import List, Optional from aiohttp.web import json_response -from homeassistant.core import Context, callback, HomeAssistant, State -from homeassistant.helpers.event import async_call_later from homeassistant.components import webhook -from homeassistant.helpers.storage import Store from homeassistant.const import ( + ATTR_DEVICE_CLASS, + ATTR_SUPPORTED_FEATURES, + CLOUD_NEVER_EXPOSED_ENTITIES, CONF_NAME, STATE_UNAVAILABLE, - ATTR_SUPPORTED_FEATURES, - ATTR_DEVICE_CLASS, - CLOUD_NEVER_EXPOSED_ENTITIES, ) +from homeassistant.core import Context, HomeAssistant, State, callback +from homeassistant.helpers.event import async_call_later +from homeassistant.helpers.storage import Store from . import trait from .const import ( + CONF_ALIASES, + CONF_ROOM_HINT, + DEVICE_CLASS_TO_GOOGLE_TYPES, DOMAIN, DOMAIN_TO_GOOGLE_TYPES, - CONF_ALIASES, ERR_FUNCTION_NOT_SUPPORTED, - DEVICE_CLASS_TO_GOOGLE_TYPES, - CONF_ROOM_HINT, STORE_AGENT_USER_IDS, ) from .error import SmartHomeError @@ -119,6 +119,7 @@ class AbstractConfig: def async_enable_report_state(self): """Enable proactive mode.""" # Circular dep + # pylint: disable=import-outside-toplevel from .report_state import async_enable_report_state if self._unsub_report_state is None: @@ -213,6 +214,8 @@ class AbstractConfig: async def _handle_local_webhook(self, hass, webhook_id, request): """Handle an incoming local SDK message.""" + # Circular dep + # pylint: disable=import-outside-toplevel from . import smart_home payload = await request.json() diff --git a/homeassistant/components/google_assistant/http.py b/homeassistant/components/google_assistant/http.py index c3d0dd493a8..233923e97a9 100644 --- a/homeassistant/components/google_assistant/http.py +++ b/homeassistant/components/google_assistant/http.py @@ -3,10 +3,10 @@ import asyncio from datetime import timedelta import logging from uuid import uuid4 -import jwt -from aiohttp import ClientResponseError, ClientError +from aiohttp import ClientError, ClientResponseError from aiohttp.web import Request, Response +import jwt # Typing imports from homeassistant.components.http import HomeAssistantView @@ -15,24 +15,24 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.util import dt as dt_util from .const import ( - GOOGLE_ASSISTANT_API_ENDPOINT, CONF_API_KEY, - CONF_EXPOSE_BY_DEFAULT, - CONF_EXPOSED_DOMAINS, + CONF_CLIENT_EMAIL, CONF_ENTITY_CONFIG, CONF_EXPOSE, + CONF_EXPOSE_BY_DEFAULT, + CONF_EXPOSED_DOMAINS, + CONF_PRIVATE_KEY, CONF_REPORT_STATE, CONF_SECURE_DEVICES_PIN, CONF_SERVICE_ACCOUNT, - CONF_CLIENT_EMAIL, - CONF_PRIVATE_KEY, - HOMEGRAPH_TOKEN_URL, + GOOGLE_ASSISTANT_API_ENDPOINT, HOMEGRAPH_SCOPE, + HOMEGRAPH_TOKEN_URL, REPORT_STATE_BASE_URL, REQUEST_SYNC_BASE_URL, ) -from .smart_home import async_handle_message from .helpers import AbstractConfig +from .smart_home import async_handle_message _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/google_assistant/report_state.py b/homeassistant/components/google_assistant/report_state.py index 78a0f50e277..1e8b6c020de 100644 --- a/homeassistant/components/google_assistant/report_state.py +++ b/homeassistant/components/google_assistant/report_state.py @@ -1,12 +1,12 @@ """Google Report State implementation.""" import logging -from homeassistant.core import HomeAssistant, callback from homeassistant.const import MATCH_ALL +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.event import async_call_later -from .helpers import AbstractConfig, GoogleEntity, async_get_entities from .error import SmartHomeError +from .helpers import AbstractConfig, GoogleEntity, async_get_entities # Time to wait until the homegraph updates # https://github.com/actions-on-google/smart-home-nodejs/issues/196#issuecomment-439156639 diff --git a/homeassistant/components/google_assistant/smart_home.py b/homeassistant/components/google_assistant/smart_home.py index 0e5037ce13a..b111e6dc942 100644 --- a/homeassistant/components/google_assistant/smart_home.py +++ b/homeassistant/components/google_assistant/smart_home.py @@ -3,20 +3,19 @@ import asyncio from itertools import product import logging +from homeassistant.const import ATTR_ENTITY_ID, __version__ from homeassistant.util.decorator import Registry -from homeassistant.const import ATTR_ENTITY_ID, __version__ - from .const import ( - ERR_PROTOCOL_ERROR, ERR_DEVICE_OFFLINE, + ERR_PROTOCOL_ERROR, ERR_UNKNOWN_ERROR, EVENT_COMMAND_RECEIVED, - EVENT_SYNC_RECEIVED, EVENT_QUERY_RECEIVED, + EVENT_SYNC_RECEIVED, ) -from .helpers import RequestData, GoogleEntity, async_get_entities from .error import SmartHomeError +from .helpers import GoogleEntity, RequestData, async_get_entities HANDLERS = Registry() _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py index 5b089459d83..40def0cb464 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -2,66 +2,67 @@ import logging from homeassistant.components import ( + alarm_control_panel, binary_sensor, camera, cover, - group, fan, + group, input_boolean, - media_player, light, lock, + media_player, scene, script, sensor, switch, vacuum, - alarm_control_panel, ) from homeassistant.components.climate import const as climate from homeassistant.const import ( - ATTR_ENTITY_ID, + ATTR_ASSUMED_STATE, + ATTR_CODE, ATTR_DEVICE_CLASS, + ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, + ATTR_TEMPERATURE, + SERVICE_ALARM_ARM_AWAY, + SERVICE_ALARM_ARM_CUSTOM_BYPASS, + SERVICE_ALARM_ARM_HOME, + SERVICE_ALARM_ARM_NIGHT, + SERVICE_ALARM_DISARM, + SERVICE_ALARM_TRIGGER, SERVICE_TURN_OFF, SERVICE_TURN_ON, + STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_CUSTOM_BYPASS, + STATE_ALARM_ARMED_HOME, + STATE_ALARM_ARMED_NIGHT, + STATE_ALARM_DISARMED, + STATE_ALARM_PENDING, + STATE_ALARM_TRIGGERED, STATE_LOCKED, STATE_OFF, STATE_ON, + STATE_UNKNOWN, TEMP_CELSIUS, TEMP_FAHRENHEIT, - ATTR_SUPPORTED_FEATURES, - ATTR_TEMPERATURE, - ATTR_ASSUMED_STATE, - SERVICE_ALARM_DISARM, - SERVICE_ALARM_ARM_HOME, - SERVICE_ALARM_ARM_AWAY, - SERVICE_ALARM_ARM_NIGHT, - SERVICE_ALARM_ARM_CUSTOM_BYPASS, - SERVICE_ALARM_TRIGGER, - STATE_ALARM_ARMED_HOME, - STATE_ALARM_ARMED_AWAY, - STATE_ALARM_ARMED_NIGHT, - STATE_ALARM_ARMED_CUSTOM_BYPASS, - STATE_ALARM_DISARMED, - STATE_ALARM_TRIGGERED, - STATE_ALARM_PENDING, - ATTR_CODE, - STATE_UNKNOWN, ) from homeassistant.core import DOMAIN as HA_DOMAIN from homeassistant.util import color as color_util, temperature as temp_util + from .const import ( - ERR_VALUE_OUT_OF_RANGE, - ERR_NOT_SUPPORTED, - ERR_FUNCTION_NOT_SUPPORTED, - ERR_CHALLENGE_NOT_SETUP, CHALLENGE_ACK_NEEDED, - CHALLENGE_PIN_NEEDED, CHALLENGE_FAILED_PIN_NEEDED, - ERR_ALREADY_DISARMED, + CHALLENGE_PIN_NEEDED, ERR_ALREADY_ARMED, + ERR_ALREADY_DISARMED, + ERR_CHALLENGE_NOT_SETUP, + ERR_FUNCTION_NOT_SUPPORTED, + ERR_NOT_SUPPORTED, + ERR_VALUE_OUT_OF_RANGE, ) -from .error import SmartHomeError, ChallengeNeeded +from .error import ChallengeNeeded, SmartHomeError _LOGGER = logging.getLogger(__name__) From 957a2e99fdccaf8c8cf28915c5d9433398c97bad Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Sun, 8 Dec 2019 09:48:08 +0100 Subject: [PATCH 067/677] Move imports to top for tellduslive (#29550) --- .../components/tellduslive/__init__.py | 8 ++++---- .../components/tellduslive/config_flow.py | 3 +-- homeassistant/components/tellduslive/entry.py | 3 ++- .../tellduslive/test_config_flow.py | 20 +++++++++++-------- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/tellduslive/__init__.py b/homeassistant/components/tellduslive/__init__.py index 313699e6f1c..917b927691e 100644 --- a/homeassistant/components/tellduslive/__init__.py +++ b/homeassistant/components/tellduslive/__init__.py @@ -1,15 +1,17 @@ """Support for Telldus Live.""" import asyncio -import logging from functools import partial +import logging +from tellduslive import DIM, TURNON, UP, Session import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant import config_entries from homeassistant.const import CONF_SCAN_INTERVAL +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_call_later + from . import config_flow # noqa: F401 from .const import ( CONF_HOST, @@ -51,7 +53,6 @@ INTERVAL_TRACKER = f"{DOMAIN}_INTERVAL" async def async_setup_entry(hass, entry): """Create a tellduslive session.""" - from tellduslive import Session conf = entry.data[KEY_SESSION] @@ -159,7 +160,6 @@ class TelldusLiveClient: """Find out what type of HA component to create.""" if device.is_sensor: return "sensor" - from tellduslive import DIM, UP, TURNON if device.methods & DIM: return "light" diff --git a/homeassistant/components/tellduslive/config_flow.py b/homeassistant/components/tellduslive/config_flow.py index 19f82dd18f4..893f3b80456 100644 --- a/homeassistant/components/tellduslive/config_flow.py +++ b/homeassistant/components/tellduslive/config_flow.py @@ -4,6 +4,7 @@ import logging import os import async_timeout +from tellduslive import Session, supports_local_api import voluptuous as vol from homeassistant import config_entries @@ -43,7 +44,6 @@ class FlowHandler(config_entries.ConfigFlow): self._scan_interval = SCAN_INTERVAL def _get_auth_url(self): - from tellduslive import Session self._session = Session( public_key=PUBLIC_KEY, @@ -116,7 +116,6 @@ class FlowHandler(config_entries.ConfigFlow): async def async_step_discovery(self, user_input): """Run when a Tellstick is discovered.""" - from tellduslive import supports_local_api _LOGGER.info("Discovered tellstick device: %s", user_input) if supports_local_api(user_input[1]): diff --git a/homeassistant/components/tellduslive/entry.py b/homeassistant/components/tellduslive/entry.py index ecd428d3b15..50a219bf7a1 100644 --- a/homeassistant/components/tellduslive/entry.py +++ b/homeassistant/components/tellduslive/entry.py @@ -2,6 +2,8 @@ from datetime import datetime import logging +from tellduslive import BATTERY_LOW, BATTERY_OK, BATTERY_UNKNOWN + from homeassistant.const import ATTR_BATTERY_LEVEL, DEVICE_DEFAULT_NAME from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -91,7 +93,6 @@ class TelldusLiveEntity(Entity): @property def _battery_level(self): """Return the battery level of a device.""" - from tellduslive import BATTERY_LOW, BATTERY_UNKNOWN, BATTERY_OK if self.device.battery == BATTERY_LOW: return 1 diff --git a/tests/components/tellduslive/test_config_flow.py b/tests/components/tellduslive/test_config_flow.py index c615c8e6aea..f4972ada2c7 100644 --- a/tests/components/tellduslive/test_config_flow.py +++ b/tests/components/tellduslive/test_config_flow.py @@ -15,7 +15,7 @@ from homeassistant.components.tellduslive import ( ) from homeassistant.const import CONF_HOST -from tests.common import MockConfigEntry, MockDependency, mock_coro +from tests.common import MockConfigEntry, mock_coro def init_config_flow(hass, side_effect=None): @@ -42,13 +42,17 @@ def authorize(): @pytest.fixture def mock_tellduslive(supports_local_api, authorize): """Mock tellduslive.""" - with MockDependency("tellduslive") as mock_tellduslive_: - mock_tellduslive_.supports_local_api.return_value = supports_local_api - mock_tellduslive_.Session().authorize.return_value = authorize - mock_tellduslive_.Session().access_token = "token" - mock_tellduslive_.Session().access_token_secret = "token_secret" - mock_tellduslive_.Session().authorize_url = "https://example.com" - yield mock_tellduslive_ + with patch( + "homeassistant.components.tellduslive.config_flow.Session" + ) as Session, patch( + "homeassistant.components.tellduslive.config_flow.supports_local_api" + ) as tellduslive_supports_local_api: + tellduslive_supports_local_api.return_value = supports_local_api + Session().authorize.return_value = authorize + Session().access_token = "token" + Session().access_token_secret = "token_secret" + Session().authorize_url = "https://example.com" + yield Session, tellduslive_supports_local_api async def test_abort_if_already_setup(hass): From 94b6cbc571c41426ed725c6cc1a7b1617ac9f64f Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Sun, 8 Dec 2019 09:48:28 +0100 Subject: [PATCH 068/677] Move imports to top for stream (#29548) --- homeassistant/components/stream/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index 9304257f853..2bfd37a2641 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -23,7 +23,6 @@ from .const import ( from .core import PROVIDERS from .hls import async_setup_hls - _LOGGER = logging.getLogger(__name__) CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA) @@ -83,6 +82,7 @@ def request_stream(hass, stream_source, *, fmt="hls", keepalive=False, options=N async def async_setup(hass, config): """Set up stream.""" # Keep import here so that we can import stream integration without installing reqs + # pylint: disable=import-outside-toplevel from .recorder import async_setup_recorder hass.data[DOMAIN] = {} @@ -163,6 +163,7 @@ class Stream: def start(self): """Start a stream.""" # Keep import here so that we can import stream integration without installing reqs + # pylint: disable=import-outside-toplevel from .worker import stream_worker if self._thread is None or not self._thread.isAlive(): From de0db1601b0b25ce1c2873c12a528cf68235c72e Mon Sep 17 00:00:00 2001 From: Andrew McRae <37428808+aamcrae@users.noreply.github.com> Date: Sun, 8 Dec 2019 19:49:18 +1100 Subject: [PATCH 069/677] Add quarterly cycle for utility_meter component (#29534) * Add quarterly tariff period to utility_meter Many tariff cycles in Australia are 3 monthly (quarterly). Add quarterly tariff cycle handling to the utility_meter component. * Add quarterly tariff period to utility_meter Many tariff cycles in Australia are 3 monthly (quarterly). Add quarterly tariff cycle handling to the utility_meter component. * Change date for test for utility_meter * Add quarterly tariff period to utility_meter Many tariff cycles in Australia are 3 monthly (quarterly). Add quarterly tariff cycle handling to the utility_meter component. --- homeassistant/components/utility_meter/const.py | 3 ++- homeassistant/components/utility_meter/sensor.py | 9 ++++++++- tests/components/utility_meter/test_sensor.py | 7 +++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/utility_meter/const.py b/homeassistant/components/utility_meter/const.py index 87721dfbf81..23d39204f9c 100644 --- a/homeassistant/components/utility_meter/const.py +++ b/homeassistant/components/utility_meter/const.py @@ -5,9 +5,10 @@ HOURLY = "hourly" DAILY = "daily" WEEKLY = "weekly" MONTHLY = "monthly" +QUARTERLY = "quarterly" YEARLY = "yearly" -METER_TYPES = [HOURLY, DAILY, WEEKLY, MONTHLY, YEARLY] +METER_TYPES = [HOURLY, DAILY, WEEKLY, MONTHLY, QUARTERLY, YEARLY] DATA_UTILITY = "utility_meter_data" diff --git a/homeassistant/components/utility_meter/sensor.py b/homeassistant/components/utility_meter/sensor.py index 1ad4300b28b..41fe2cbcc0a 100644 --- a/homeassistant/components/utility_meter/sensor.py +++ b/homeassistant/components/utility_meter/sensor.py @@ -25,6 +25,7 @@ from .const import ( DAILY, WEEKLY, MONTHLY, + QUARTERLY, YEARLY, CONF_SOURCE_SENSOR, CONF_METER_TYPE, @@ -184,6 +185,12 @@ class UtilityMeterSensor(RestoreEntity): and now != date(now.year, now.month, 1) + self._period_offset ): return + if ( + self._period == QUARTERLY + and now + != date(now.year, (((now.month - 1) // 3) * 3 + 1), 1) + self._period_offset + ): + return if self._period == YEARLY and now != date(now.year, 1, 1) + self._period_offset: return await self.async_reset_meter(self._tariff_entity) @@ -209,7 +216,7 @@ class UtilityMeterSensor(RestoreEntity): minute=self._period_offset.seconds // 60, second=self._period_offset.seconds % 60, ) - elif self._period in [DAILY, WEEKLY, MONTHLY, YEARLY]: + elif self._period in [DAILY, WEEKLY, MONTHLY, QUARTERLY, YEARLY]: async_track_time_change( self.hass, self._async_reset_meter, diff --git a/tests/components/utility_meter/test_sensor.py b/tests/components/utility_meter/test_sensor.py index 1bdaa01c2e6..7bf10875b77 100644 --- a/tests/components/utility_meter/test_sensor.py +++ b/tests/components/utility_meter/test_sensor.py @@ -240,6 +240,13 @@ async def test_self_reset_monthly(hass): ) +async def test_self_reset_quarterly(hass): + """Test quarterly reset of meter.""" + await _test_self_reset( + hass, gen_config("quarterly"), "2017-03-31T23:59:00.000000+00:00" + ) + + async def test_self_reset_yearly(hass): """Test yearly reset of meter.""" await _test_self_reset( From 21cf6777bb254513734e42f3b62d7e55a680559f Mon Sep 17 00:00:00 2001 From: SNoof85 Date: Sun, 8 Dec 2019 09:49:43 +0100 Subject: [PATCH 070/677] bump freebox api version (#29527) --- homeassistant/components/freebox/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/freebox/__init__.py b/homeassistant/components/freebox/__init__.py index 96874ddeb94..58426334dea 100644 --- a/homeassistant/components/freebox/__init__.py +++ b/homeassistant/components/freebox/__init__.py @@ -60,7 +60,7 @@ async def async_setup_freebox(hass, config, host, port): } token_file = hass.config.path(FREEBOX_CONFIG_FILE) - api_version = "v4" + api_version = "v6" fbx = Freepybox(app_desc=app_desc, token_file=token_file, api_version=api_version) From 6de8072e8ac1d90dcc16653593eda29e73553c78 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Sun, 8 Dec 2019 12:19:15 +0100 Subject: [PATCH 071/677] Move imports to top for websocket_api (#29556) * Move imports to top for websocket_api * Move back an import because of circular dependency, add annotations --- .../components/websocket_api/__init__.py | 1 - homeassistant/components/websocket_api/auth.py | 3 +-- .../components/websocket_api/commands.py | 11 ++++++----- .../components/websocket_api/connection.py | 3 +-- homeassistant/components/websocket_api/const.py | 1 + .../components/websocket_api/decorators.py | 1 - homeassistant/components/websocket_api/http.py | 15 +++++++-------- .../components/websocket_api/messages.py | 1 - .../components/websocket_api/permissions.py | 12 ++++++------ homeassistant/components/websocket_api/sensor.py | 3 +-- 10 files changed, 23 insertions(+), 28 deletions(-) diff --git a/homeassistant/components/websocket_api/__init__.py b/homeassistant/components/websocket_api/__init__.py index 1ec758ebd4d..2beb2aa2788 100644 --- a/homeassistant/components/websocket_api/__init__.py +++ b/homeassistant/components/websocket_api/__init__.py @@ -4,7 +4,6 @@ from homeassistant.loader import bind_hass from . import commands, connection, const, decorators, http, messages - # mypy: allow-untyped-calls, allow-untyped-defs DOMAIN = const.DOMAIN diff --git a/homeassistant/components/websocket_api/auth.py b/homeassistant/components/websocket_api/auth.py index 3971d39ee73..9e33ed74fd4 100644 --- a/homeassistant/components/websocket_api/auth.py +++ b/homeassistant/components/websocket_api/auth.py @@ -3,13 +3,12 @@ import voluptuous as vol from voluptuous.humanize import humanize_error from homeassistant.auth.models import RefreshToken, User -from homeassistant.components.http.ban import process_wrong_login, process_success_login +from homeassistant.components.http.ban import process_success_login, process_wrong_login from homeassistant.const import __version__ from .connection import ActiveConnection from .error import Disconnect - # mypy: allow-untyped-calls, allow-untyped-defs TYPE_AUTH = "auth" diff --git a/homeassistant/components/websocket_api/commands.py b/homeassistant/components/websocket_api/commands.py index f30ee816914..93f926b537a 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -2,16 +2,15 @@ import voluptuous as vol from homeassistant.auth.permissions.const import POLICY_READ -from homeassistant.const import MATCH_ALL, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED -from homeassistant.core import callback, DOMAIN as HASS_DOMAIN -from homeassistant.exceptions import Unauthorized, ServiceNotFound, HomeAssistantError +from homeassistant.const import EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL +from homeassistant.core import DOMAIN as HASS_DOMAIN, callback +from homeassistant.exceptions import HomeAssistantError, ServiceNotFound, Unauthorized from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.service import async_get_all_descriptions from homeassistant.helpers.event import async_track_state_change +from homeassistant.helpers.service import async_get_all_descriptions from . import const, decorators, messages - # mypy: allow-untyped-calls, allow-untyped-defs @@ -45,6 +44,8 @@ def handle_subscribe_events(hass, connection, msg): Async friendly. """ + # Circular dep + # pylint: disable=import-outside-toplevel from .permissions import SUBSCRIBE_WHITELIST event_type = msg["event_type"] diff --git a/homeassistant/components/websocket_api/connection.py b/homeassistant/components/websocket_api/connection.py index 5a0284a34d4..ed24a70519d 100644 --- a/homeassistant/components/websocket_api/connection.py +++ b/homeassistant/components/websocket_api/connection.py @@ -4,12 +4,11 @@ from typing import Any, Callable, Dict, Hashable import voluptuous as vol -from homeassistant.core import callback, Context +from homeassistant.core import Context, callback from homeassistant.exceptions import Unauthorized from . import const, messages - # mypy: allow-untyped-calls, allow-untyped-defs diff --git a/homeassistant/components/websocket_api/const.py b/homeassistant/components/websocket_api/const.py index fe9792c4ab3..8ad9443a4d6 100644 --- a/homeassistant/components/websocket_api/const.py +++ b/homeassistant/components/websocket_api/const.py @@ -3,6 +3,7 @@ import asyncio from concurrent import futures from functools import partial import json + from homeassistant.helpers.json import JSONEncoder DOMAIN = "websocket_api" diff --git a/homeassistant/components/websocket_api/decorators.py b/homeassistant/components/websocket_api/decorators.py index 025131643e8..1a1330242bc 100644 --- a/homeassistant/components/websocket_api/decorators.py +++ b/homeassistant/components/websocket_api/decorators.py @@ -7,7 +7,6 @@ from homeassistant.exceptions import Unauthorized from . import messages - # mypy: allow-untyped-calls, allow-untyped-defs _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/websocket_api/http.py b/homeassistant/components/websocket_api/http.py index be1830aa07b..3921413fd28 100644 --- a/homeassistant/components/websocket_api/http.py +++ b/homeassistant/components/websocket_api/http.py @@ -4,28 +4,27 @@ from contextlib import suppress import logging from typing import Optional -from aiohttp import web, WSMsgType +from aiohttp import WSMsgType, web import async_timeout +from homeassistant.components.http import HomeAssistantView from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import callback -from homeassistant.components.http import HomeAssistantView +from .auth import AuthPhase, auth_required_message from .const import ( - MAX_PENDING_MSG, CANCELLATION_ERRORS, - URL, + DATA_CONNECTIONS, ERR_UNKNOWN_ERROR, + JSON_DUMP, + MAX_PENDING_MSG, SIGNAL_WEBSOCKET_CONNECTED, SIGNAL_WEBSOCKET_DISCONNECTED, - DATA_CONNECTIONS, - JSON_DUMP, + URL, ) -from .auth import AuthPhase, auth_required_message from .error import Disconnect from .messages import error_message - # mypy: allow-untyped-calls, allow-untyped-defs, no-check-untyped-defs diff --git a/homeassistant/components/websocket_api/messages.py b/homeassistant/components/websocket_api/messages.py index c8c760a6549..27d557e8110 100644 --- a/homeassistant/components/websocket_api/messages.py +++ b/homeassistant/components/websocket_api/messages.py @@ -6,7 +6,6 @@ from homeassistant.helpers import config_validation as cv from . import const - # mypy: allow-untyped-defs # Minimal requirements of a message diff --git a/homeassistant/components/websocket_api/permissions.py b/homeassistant/components/websocket_api/permissions.py index ffbb80fa19e..c270c0f0ccc 100644 --- a/homeassistant/components/websocket_api/permissions.py +++ b/homeassistant/components/websocket_api/permissions.py @@ -2,22 +2,22 @@ Separate file to avoid circular imports. """ +from homeassistant.components.frontend import EVENT_PANELS_UPDATED +from homeassistant.components.lovelace import EVENT_LOVELACE_UPDATED +from homeassistant.components.persistent_notification import ( + EVENT_PERSISTENT_NOTIFICATIONS_UPDATED, +) from homeassistant.const import ( EVENT_COMPONENT_LOADED, + EVENT_CORE_CONFIG_UPDATE, EVENT_SERVICE_REGISTERED, EVENT_SERVICE_REMOVED, EVENT_STATE_CHANGED, EVENT_THEMES_UPDATED, - EVENT_CORE_CONFIG_UPDATE, ) -from homeassistant.components.persistent_notification import ( - EVENT_PERSISTENT_NOTIFICATIONS_UPDATED, -) -from homeassistant.components.lovelace import EVENT_LOVELACE_UPDATED from homeassistant.helpers.area_registry import EVENT_AREA_REGISTRY_UPDATED from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED from homeassistant.helpers.entity_registry import EVENT_ENTITY_REGISTRY_UPDATED -from homeassistant.components.frontend import EVENT_PANELS_UPDATED # These are events that do not contain any sensitive data # Except for state_changed, which is handled accordingly. diff --git a/homeassistant/components/websocket_api/sensor.py b/homeassistant/components/websocket_api/sensor.py index f8f1257aefc..4ae39787335 100644 --- a/homeassistant/components/websocket_api/sensor.py +++ b/homeassistant/components/websocket_api/sensor.py @@ -4,12 +4,11 @@ from homeassistant.core import callback from homeassistant.helpers.entity import Entity from .const import ( + DATA_CONNECTIONS, SIGNAL_WEBSOCKET_CONNECTED, SIGNAL_WEBSOCKET_DISCONNECTED, - DATA_CONNECTIONS, ) - # mypy: allow-untyped-calls, allow-untyped-defs, no-check-untyped-defs From d752fe303311043367e9bfef688509212279b158 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Sun, 8 Dec 2019 12:20:53 +0100 Subject: [PATCH 072/677] Move imports to top for fido (#29557) * Move imports to top for fido * Fix tests for fido by using patch --- homeassistant/components/fido/sensor.py | 14 ++++----- tests/components/fido/test_sensor.py | 42 +++++++++++-------------- 2 files changed, 26 insertions(+), 30 deletions(-) diff --git a/homeassistant/components/fido/sensor.py b/homeassistant/components/fido/sensor.py index 8814a2406c5..086ae87a529 100644 --- a/homeassistant/components/fido/sensor.py +++ b/homeassistant/components/fido/sensor.py @@ -7,21 +7,23 @@ https://www.fido.ca/pages/#/my-account/wireless For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.fido/ """ -import logging from datetime import timedelta +import logging +from pyfido import FidoClient +from pyfido.client import PyFidoError import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_USERNAME, - CONF_PASSWORD, - CONF_NAME, CONF_MONITORED_VARIABLES, + CONF_NAME, + CONF_PASSWORD, + CONF_USERNAME, ) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle -import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) @@ -147,7 +149,6 @@ class FidoData: def __init__(self, username, password, httpsession): """Initialize the data object.""" - from pyfido import FidoClient self.client = FidoClient(username, password, REQUESTS_TIMEOUT, httpsession) self.data = {} @@ -155,7 +156,6 @@ class FidoData: @Throttle(MIN_TIME_BETWEEN_UPDATES) async def async_update(self): """Get the latest data from Fido.""" - from pyfido.client import PyFidoError try: await self.client.fetch_data() diff --git a/tests/components/fido/test_sensor.py b/tests/components/fido/test_sensor.py index 510d4321243..010896e086b 100644 --- a/tests/components/fido/test_sensor.py +++ b/tests/components/fido/test_sensor.py @@ -2,7 +2,7 @@ import asyncio import logging import sys -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch from homeassistant.bootstrap import async_setup_component from homeassistant.components.fido import sensor as fido @@ -66,29 +66,25 @@ def fake_async_add_entities(component, update_before_add=False): @asyncio.coroutine def test_fido_sensor(loop, hass): """Test the Fido number sensor.""" - sys.modules["pyfido"] = MagicMock() - sys.modules["pyfido.client"] = MagicMock() - sys.modules["pyfido.client.PyFidoError"] = PyFidoErrorMock - import pyfido.client - - pyfido.FidoClient = FidoClientMock - pyfido.client.PyFidoError = PyFidoErrorMock - config = { - "sensor": { - "platform": "fido", - "name": "fido", - "username": "myusername", - "password": "password", - "monitored_variables": ["balance", "data_remaining"], + with patch( + "homeassistant.components.fido.sensor.FidoClient", new=FidoClientMock + ), patch("homeassistant.components.fido.sensor.PyFidoError", new=PyFidoErrorMock): + config = { + "sensor": { + "platform": "fido", + "name": "fido", + "username": "myusername", + "password": "password", + "monitored_variables": ["balance", "data_remaining"], + } } - } - with assert_setup_component(1): - yield from async_setup_component(hass, "sensor", config) - state = hass.states.get("sensor.fido_1112223344_balance") - assert state.state == "160.12" - assert state.attributes.get("number") == "1112223344" - state = hass.states.get("sensor.fido_1112223344_data_remaining") - assert state.state == "100.33" + with assert_setup_component(1): + yield from async_setup_component(hass, "sensor", config) + state = hass.states.get("sensor.fido_1112223344_balance") + assert state.state == "160.12" + assert state.attributes.get("number") == "1112223344" + state = hass.states.get("sensor.fido_1112223344_data_remaining") + assert state.state == "100.33" @asyncio.coroutine From ef4515ed70e8d92ae471e348a5d094d3879911fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sun, 8 Dec 2019 13:21:48 +0200 Subject: [PATCH 073/677] Add Huawei LTE reboot and clear traffic statistics services (#29594) * Add clear traffic statistics service * Add reboot service * Register services as admin ones * Make URL optional when there's only one router configured * Eliminate one if/else indent level * Remove unnecessary .keys() with sorted() --- .../components/huawei_lte/__init__.py | 37 +++++++++++++++++++ homeassistant/components/huawei_lte/const.py | 3 ++ .../components/huawei_lte/services.yaml | 13 +++++++ 3 files changed, 53 insertions(+) create mode 100644 homeassistant/components/huawei_lte/services.yaml diff --git a/homeassistant/components/huawei_lte/__init__.py b/homeassistant/components/huawei_lte/__init__.py index ada1f0a8abd..30b8eb1bc6d 100644 --- a/homeassistant/components/huawei_lte/__init__.py +++ b/homeassistant/components/huawei_lte/__init__.py @@ -58,6 +58,8 @@ from .const import ( KEY_MONITORING_STATUS, KEY_MONITORING_TRAFFIC_STATISTICS, KEY_WLAN_HOST_LIST, + SERVICE_CLEAR_TRAFFIC_STATISTICS, + SERVICE_REBOOT, UPDATE_OPTIONS_SIGNAL, UPDATE_SIGNAL, ) @@ -103,6 +105,8 @@ CONFIG_SCHEMA = vol.Schema( extra=vol.ALLOW_EXTRA, ) +SERVICE_SCHEMA = vol.Schema({vol.Optional(CONF_URL): cv.url}) + CONFIG_ENTRY_PLATFORMS = ( BINARY_SENSOR_DOMAIN, DEVICE_TRACKER_DOMAIN, @@ -387,6 +391,39 @@ async def async_setup(hass: HomeAssistantType, config) -> bool: for router_config in config.get(DOMAIN, []): domain_config[url_normalize(router_config.pop(CONF_URL))] = router_config + def service_handler(service) -> None: + """Apply a service.""" + url = service.data.get(CONF_URL) + routers = hass.data[DOMAIN].routers + if url: + router = routers.get(url) + elif len(routers) == 1: + router = next(iter(routers.values())) + else: + _LOGGER.error( + "%s: more than one router configured, must specify one of URLs %s", + service.service, + sorted(routers), + ) + return + if not router: + _LOGGER.error("%s: router %s unavailable", service.service, url) + return + + if service.service == SERVICE_CLEAR_TRAFFIC_STATISTICS: + result = router.client.monitoring.set_clear_traffic() + _LOGGER.debug("%s: %s", service.service, result) + elif service.service == SERVICE_REBOOT: + result = router.client.device.reboot() + _LOGGER.debug("%s: %s", service.service, result) + else: + _LOGGER.error("%s: unsupported service", service.service) + + for service in (SERVICE_CLEAR_TRAFFIC_STATISTICS, SERVICE_REBOOT): + hass.helpers.service.async_register_admin_service( + DOMAIN, service, service_handler, schema=SERVICE_SCHEMA, + ) + for url, router_config in domain_config.items(): hass.async_create_task( hass.config_entries.flow.async_init( diff --git a/homeassistant/components/huawei_lte/const.py b/homeassistant/components/huawei_lte/const.py index b6e079576ac..c71b51435e1 100644 --- a/homeassistant/components/huawei_lte/const.py +++ b/homeassistant/components/huawei_lte/const.py @@ -12,6 +12,9 @@ UNIT_SECONDS = "s" CONNECTION_TIMEOUT = 10 +SERVICE_CLEAR_TRAFFIC_STATISTICS = "clear_traffic_statistics" +SERVICE_REBOOT = "reboot" + KEY_DEVICE_BASIC_INFORMATION = "device_basic_information" KEY_DEVICE_INFORMATION = "device_information" KEY_DEVICE_SIGNAL = "device_signal" diff --git a/homeassistant/components/huawei_lte/services.yaml b/homeassistant/components/huawei_lte/services.yaml new file mode 100644 index 00000000000..428745ee33e --- /dev/null +++ b/homeassistant/components/huawei_lte/services.yaml @@ -0,0 +1,13 @@ +clear_traffic_statistics: + description: Clear traffic statistics. + fields: + url: + description: URL of router to clear; optional when only one is configured. + example: http://192.168.100.1/ + +reboot: + description: Reboot router. + fields: + url: + description: URL of router to reboot; optional when only one is configured. + example: http://192.168.100.1/ From 700cecc8ef14c067e509be1ead206761b224ed7b Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 13:41:51 +0100 Subject: [PATCH 074/677] sort imports according to PEP8 for airly (#29615) --- homeassistant/components/airly/air_quality.py | 4 ++-- homeassistant/components/airly/config_flow.py | 6 +++--- tests/components/airly/test_config_flow.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/airly/air_quality.py b/homeassistant/components/airly/air_quality.py index 082344c14e3..e22fa7939c2 100644 --- a/homeassistant/components/airly/air_quality.py +++ b/homeassistant/components/airly/air_quality.py @@ -1,9 +1,9 @@ """Support for the Airly air_quality service.""" from homeassistant.components.air_quality import ( - AirQualityEntity, ATTR_AQI, - ATTR_PM_10, ATTR_PM_2_5, + ATTR_PM_10, + AirQualityEntity, ) from homeassistant.const import CONF_NAME diff --git a/homeassistant/components/airly/config_flow.py b/homeassistant/components/airly/config_flow.py index b361930fa7d..31cfec7e7aa 100644 --- a/homeassistant/components/airly/config_flow.py +++ b/homeassistant/components/airly/config_flow.py @@ -1,14 +1,14 @@ """Adds config flow for Airly.""" -import async_timeout -import voluptuous as vol from airly import Airly from airly.exceptions import AirlyError +import async_timeout +import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant import config_entries from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME from homeassistant.core import callback from homeassistant.helpers.aiohttp_client import async_get_clientsession +import homeassistant.helpers.config_validation as cv from .const import DEFAULT_NAME, DOMAIN, NO_AIRLY_SENSORS diff --git a/tests/components/airly/test_config_flow.py b/tests/components/airly/test_config_flow.py index 8b615b34c2a..a5ca3981a5a 100644 --- a/tests/components/airly/test_config_flow.py +++ b/tests/components/airly/test_config_flow.py @@ -5,11 +5,11 @@ from airly.exceptions import AirlyError from asynctest import patch from homeassistant import data_entry_flow -from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME from homeassistant.components.airly import config_flow from homeassistant.components.airly.const import DOMAIN +from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME -from tests.common import load_fixture, MockConfigEntry +from tests.common import MockConfigEntry, load_fixture CONFIG = { CONF_NAME: "abcd", From d020486929e66fd6fffe044556212bddbe13848d Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 13:42:31 +0100 Subject: [PATCH 075/677] sort imports according to PEP8 for alarm_control_panel (#29616) --- .../components/alarm_control_panel/__init__.py | 2 +- tests/components/alarm_control_panel/common.py | 12 ++++++------ .../alarm_control_panel/test_device_action.py | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py index dfac0fd192f..5fb44a18a0b 100644 --- a/homeassistant/components/alarm_control_panel/__init__.py +++ b/homeassistant/components/alarm_control_panel/__init__.py @@ -17,9 +17,9 @@ from homeassistant.const import ( ) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.config_validation import ( # noqa: F401 - make_entity_service_schema, PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE, + make_entity_service_schema, ) from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent diff --git a/tests/components/alarm_control_panel/common.py b/tests/components/alarm_control_panel/common.py index d06939dce9b..ce0bde0517c 100644 --- a/tests/components/alarm_control_panel/common.py +++ b/tests/components/alarm_control_panel/common.py @@ -7,13 +7,13 @@ from homeassistant.components.alarm_control_panel import DOMAIN from homeassistant.const import ( ATTR_CODE, ATTR_ENTITY_ID, - SERVICE_ALARM_TRIGGER, - SERVICE_ALARM_DISARM, - SERVICE_ALARM_ARM_HOME, - SERVICE_ALARM_ARM_AWAY, - SERVICE_ALARM_ARM_NIGHT, - SERVICE_ALARM_ARM_CUSTOM_BYPASS, ENTITY_MATCH_ALL, + SERVICE_ALARM_ARM_AWAY, + SERVICE_ALARM_ARM_CUSTOM_BYPASS, + SERVICE_ALARM_ARM_HOME, + SERVICE_ALARM_ARM_NIGHT, + SERVICE_ALARM_DISARM, + SERVICE_ALARM_TRIGGER, ) from homeassistant.loader import bind_hass diff --git a/tests/components/alarm_control_panel/test_device_action.py b/tests/components/alarm_control_panel/test_device_action.py index bc489dbe251..72754c3c96f 100644 --- a/tests/components/alarm_control_panel/test_device_action.py +++ b/tests/components/alarm_control_panel/test_device_action.py @@ -2,6 +2,7 @@ import pytest from homeassistant.components.alarm_control_panel import DOMAIN +import homeassistant.components.automation as automation from homeassistant.const import ( CONF_PLATFORM, STATE_ALARM_ARMED_AWAY, @@ -11,17 +12,16 @@ from homeassistant.const import ( STATE_ALARM_TRIGGERED, STATE_UNKNOWN, ) -from homeassistant.setup import async_setup_component -import homeassistant.components.automation as automation from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component from tests.common import ( MockConfigEntry, assert_lists_same, + async_get_device_automation_capabilities, + async_get_device_automations, mock_device_registry, mock_registry, - async_get_device_automations, - async_get_device_automation_capabilities, ) From e510c4ea1dcbdaff7924d981cfbf586390d1f30e Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 13:42:55 +0100 Subject: [PATCH 076/677] sort imports according to PEP8 for air_quality (#29614) --- homeassistant/components/air_quality/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/air_quality/__init__.py b/homeassistant/components/air_quality/__init__.py index 00308c40b36..29c6756260c 100644 --- a/homeassistant/components/air_quality/__init__.py +++ b/homeassistant/components/air_quality/__init__.py @@ -2,12 +2,12 @@ from datetime import timedelta import logging -from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.config_validation import ( # noqa: F401 PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE, ) from homeassistant.helpers.entity import Entity +from homeassistant.helpers.entity_component import EntityComponent _LOGGER = logging.getLogger(__name__) From f5a1b32be0b1aa2eef86df3cce7b616268e1f050 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 13:43:32 +0100 Subject: [PATCH 077/677] sort imports according to PEP8 for ads (#29613) --- homeassistant/components/ads/__init__.py | 13 ++++++------- homeassistant/components/ads/binary_sensor.py | 2 +- homeassistant/components/ads/cover.py | 16 ++++++++-------- homeassistant/components/ads/light.py | 2 +- homeassistant/components/ads/sensor.py | 2 +- homeassistant/components/ads/switch.py | 4 ++-- 6 files changed, 19 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/ads/__init__.py b/homeassistant/components/ads/__init__.py index ba4762da84a..adaaaa08b7f 100644 --- a/homeassistant/components/ads/__init__.py +++ b/homeassistant/components/ads/__init__.py @@ -1,14 +1,13 @@ """Support for Automation Device Specification (ADS).""" -import threading -import struct -import logging -import ctypes -from collections import namedtuple import asyncio +from collections import namedtuple +import ctypes +import logging +import struct +import threading + import async_timeout - import pyads - import voluptuous as vol from homeassistant.const import ( diff --git a/homeassistant/components/ads/binary_sensor.py b/homeassistant/components/ads/binary_sensor.py index 2afb163fc32..fd6d77873b5 100644 --- a/homeassistant/components/ads/binary_sensor.py +++ b/homeassistant/components/ads/binary_sensor.py @@ -11,7 +11,7 @@ from homeassistant.components.binary_sensor import ( from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME import homeassistant.helpers.config_validation as cv -from . import CONF_ADS_VAR, DATA_ADS, AdsEntity, STATE_KEY_STATE +from . import CONF_ADS_VAR, DATA_ADS, STATE_KEY_STATE, AdsEntity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/ads/cover.py b/homeassistant/components/ads/cover.py index b21c064e941..0fdcbc16ef8 100644 --- a/homeassistant/components/ads/cover.py +++ b/homeassistant/components/ads/cover.py @@ -4,25 +4,25 @@ import logging import voluptuous as vol from homeassistant.components.cover import ( - PLATFORM_SCHEMA, - SUPPORT_OPEN, - SUPPORT_CLOSE, - SUPPORT_STOP, - SUPPORT_SET_POSITION, ATTR_POSITION, DEVICE_CLASSES_SCHEMA, + PLATFORM_SCHEMA, + SUPPORT_CLOSE, + SUPPORT_OPEN, + SUPPORT_SET_POSITION, + SUPPORT_STOP, CoverDevice, ) -from homeassistant.const import CONF_NAME, CONF_DEVICE_CLASS +from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME import homeassistant.helpers.config_validation as cv from . import ( CONF_ADS_VAR, CONF_ADS_VAR_POSITION, DATA_ADS, - AdsEntity, - STATE_KEY_STATE, STATE_KEY_POSITION, + STATE_KEY_STATE, + AdsEntity, ) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/ads/light.py b/homeassistant/components/ads/light.py index f1e78ea132e..b9626b9e969 100644 --- a/homeassistant/components/ads/light.py +++ b/homeassistant/components/ads/light.py @@ -16,9 +16,9 @@ from . import ( CONF_ADS_VAR, CONF_ADS_VAR_BRIGHTNESS, DATA_ADS, - AdsEntity, STATE_KEY_BRIGHTNESS, STATE_KEY_STATE, + AdsEntity, ) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/ads/sensor.py b/homeassistant/components/ads/sensor.py index 1d956b1fba2..3bdcbfc95f8 100644 --- a/homeassistant/components/ads/sensor.py +++ b/homeassistant/components/ads/sensor.py @@ -8,7 +8,7 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_NAME, CONF_UNIT_OF_MEASUREMENT import homeassistant.helpers.config_validation as cv -from . import CONF_ADS_FACTOR, CONF_ADS_TYPE, CONF_ADS_VAR, AdsEntity, STATE_KEY_STATE +from . import CONF_ADS_FACTOR, CONF_ADS_TYPE, CONF_ADS_VAR, STATE_KEY_STATE, AdsEntity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/ads/switch.py b/homeassistant/components/ads/switch.py index 1b5a40debb6..3590b6af88e 100644 --- a/homeassistant/components/ads/switch.py +++ b/homeassistant/components/ads/switch.py @@ -3,11 +3,11 @@ import logging import voluptuous as vol -from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv -from . import CONF_ADS_VAR, DATA_ADS, AdsEntity, STATE_KEY_STATE +from . import CONF_ADS_VAR, DATA_ADS, STATE_KEY_STATE, AdsEntity _LOGGER = logging.getLogger(__name__) From a885670a9aede98b7074b0b29a070edda1adf2e7 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 13:44:12 +0100 Subject: [PATCH 078/677] sort imports according to PEP8 for abode (#29610) --- homeassistant/components/abode/__init__.py | 4 ++-- homeassistant/components/abode/config_flow.py | 2 +- tests/components/abode/test_config_flow.py | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/abode/__init__.py b/homeassistant/components/abode/__init__.py index 1689576bc7f..6d23701d088 100644 --- a/homeassistant/components/abode/__init__.py +++ b/homeassistant/components/abode/__init__.py @@ -20,14 +20,14 @@ from homeassistant.const import ( CONF_USERNAME, EVENT_HOMEASSISTANT_STOP, ) -from homeassistant.helpers.dispatcher import dispatcher_send from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.dispatcher import dispatcher_send from homeassistant.helpers.entity import Entity from .const import ( ATTRIBUTION, - DOMAIN, DEFAULT_CACHEDB, + DOMAIN, SIGNAL_CAPTURE_IMAGE, SIGNAL_TRIGGER_QUICK_ACTION, ) diff --git a/homeassistant/components/abode/config_flow.py b/homeassistant/components/abode/config_flow.py index b8e8548db31..89b389798f6 100644 --- a/homeassistant/components/abode/config_flow.py +++ b/homeassistant/components/abode/config_flow.py @@ -10,7 +10,7 @@ from homeassistant import config_entries from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import callback -from .const import DOMAIN, DEFAULT_CACHEDB # pylint: disable=unused-import +from .const import DEFAULT_CACHEDB, DOMAIN # pylint: disable=unused-import CONF_POLLING = "polling" diff --git a/tests/components/abode/test_config_flow.py b/tests/components/abode/test_config_flow.py index c3f5d170767..5e32c923245 100644 --- a/tests/components/abode/test_config_flow.py +++ b/tests/components/abode/test_config_flow.py @@ -6,6 +6,7 @@ from abodepy.exceptions import AbodeAuthenticationException from homeassistant import data_entry_flow from homeassistant.components.abode import config_flow from homeassistant.const import CONF_PASSWORD, CONF_USERNAME + from tests.common import MockConfigEntry CONF_POLLING = "polling" From cfe68d7e0068d6efcb52337c7c2a12436e7943f6 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 13:44:44 +0100 Subject: [PATCH 079/677] sort imports according to PEP8 for actiontec (#29612) --- homeassistant/components/actiontec/device_tracker.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/actiontec/device_tracker.py b/homeassistant/components/actiontec/device_tracker.py index e07dd2622be..302a8d56173 100644 --- a/homeassistant/components/actiontec/device_tracker.py +++ b/homeassistant/components/actiontec/device_tracker.py @@ -1,18 +1,19 @@ """Support for Actiontec MI424WR (Verizon FIOS) routers.""" +from collections import namedtuple import logging import re import telnetlib -from collections import namedtuple + import voluptuous as vol -import homeassistant.helpers.config_validation as cv -import homeassistant.util.dt as dt_util from homeassistant.components.device_tracker import ( DOMAIN, PLATFORM_SCHEMA, DeviceScanner, ) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME +import homeassistant.helpers.config_validation as cv +import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) From b4bcd477f817e2c2ce670a5f05e164690b8b639c Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 13:45:33 +0100 Subject: [PATCH 080/677] sort imports according to PEP8 for acer_projector (#29611) --- homeassistant/components/acer_projector/switch.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/acer_projector/switch.py b/homeassistant/components/acer_projector/switch.py index 39a79636c93..b28d67562d4 100644 --- a/homeassistant/components/acer_projector/switch.py +++ b/homeassistant/components/acer_projector/switch.py @@ -1,17 +1,17 @@ """Use serial protocol of Acer projector to obtain state of the projector.""" import logging import re -import serial +import serial import voluptuous as vol -from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice from homeassistant.const import ( - STATE_ON, - STATE_OFF, - STATE_UNKNOWN, - CONF_NAME, CONF_FILENAME, + CONF_NAME, + STATE_OFF, + STATE_ON, + STATE_UNKNOWN, ) import homeassistant.helpers.config_validation as cv From 74c0219d0c81c17cd7c29795bdaa02a62ca9ebd0 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 13:46:04 +0100 Subject: [PATCH 081/677] sort imports according to PEP8 for components (#29609) --- homeassistant/components/__init__.py | 1 - tests/components/conftest.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/__init__.py b/homeassistant/components/__init__.py index 7bb572dcf6b..90e0f32226c 100644 --- a/homeassistant/components/__init__.py +++ b/homeassistant/components/__init__.py @@ -11,7 +11,6 @@ import logging from homeassistant.core import split_entity_id - # mypy: allow-untyped-defs _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/conftest.py b/tests/components/conftest.py index 4f1f3e64e02..a589839c03f 100644 --- a/tests/components/conftest.py +++ b/tests/components/conftest.py @@ -3,13 +3,13 @@ from unittest.mock import patch import pytest -from homeassistant.setup import async_setup_component -from homeassistant.components.websocket_api.http import URL from homeassistant.components.websocket_api.auth import ( TYPE_AUTH, TYPE_AUTH_OK, TYPE_AUTH_REQUIRED, ) +from homeassistant.components.websocket_api.http import URL +from homeassistant.setup import async_setup_component from tests.common import mock_coro From 09ff0a5ac6fb28f69122e63f4d288253d558ee92 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 13:50:02 +0100 Subject: [PATCH 082/677] sort imports according to PEP8 for yweather (#29608) --- tests/components/yweather/test_sensor.py | 3 +-- tests/components/yweather/test_weather.py | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/components/yweather/test_sensor.py b/tests/components/yweather/test_sensor.py index f5f5852229b..25cb26d7c83 100644 --- a/tests/components/yweather/test_sensor.py +++ b/tests/components/yweather/test_sensor.py @@ -1,12 +1,11 @@ """The tests for the Yahoo weather sensor component.""" import json - import unittest from unittest.mock import patch from homeassistant.setup import setup_component -from tests.common import get_test_home_assistant, load_fixture, MockDependency +from tests.common import MockDependency, get_test_home_assistant, load_fixture VALID_CONFIG_MINIMAL = { "sensor": {"platform": "yweather", "monitored_conditions": ["weather"]} diff --git a/tests/components/yweather/test_weather.py b/tests/components/yweather/test_weather.py index 2c0435672e6..c9bf7bc89b6 100644 --- a/tests/components/yweather/test_weather.py +++ b/tests/components/yweather/test_weather.py @@ -1,6 +1,5 @@ """The tests for the Yahoo weather component.""" import json - import unittest from unittest.mock import patch @@ -11,10 +10,10 @@ from homeassistant.components.weather import ( ATTR_WEATHER_WIND_BEARING, ATTR_WEATHER_WIND_SPEED, ) -from homeassistant.util.unit_system import METRIC_SYSTEM from homeassistant.setup import setup_component +from homeassistant.util.unit_system import METRIC_SYSTEM -from tests.common import get_test_home_assistant, load_fixture, MockDependency +from tests.common import MockDependency, get_test_home_assistant, load_fixture def _yql_queryMock(yql): # pylint: disable=invalid-name From fa00808f6c5caa7befd0ec62f5bce9518301b8c6 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 8 Dec 2019 18:21:54 +0530 Subject: [PATCH 083/677] Upgrade keyring to 19.3.0 and keyrings.alt to 3.2.0 (#29607) --- homeassistant/scripts/keyring.py | 4 +--- requirements_all.txt | 4 ++-- requirements_test_all.txt | 4 ++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/homeassistant/scripts/keyring.py b/homeassistant/scripts/keyring.py index 0c5623a50ad..63b4fd1a37e 100644 --- a/homeassistant/scripts/keyring.py +++ b/homeassistant/scripts/keyring.py @@ -5,10 +5,8 @@ import os from homeassistant.util.yaml import _SECRET_NAMESPACE - # mypy: allow-untyped-defs - -REQUIREMENTS = ["keyring==19.2.0", "keyrings.alt==3.1.1"] +REQUIREMENTS = ["keyring==19.3.0", "keyrings.alt==3.2.0"] def run(args): diff --git a/requirements_all.txt b/requirements_all.txt index 2d8a2b881d4..992069c5045 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -742,10 +742,10 @@ kaiterra-async-client==0.0.2 keba-kecontact==0.2.0 # homeassistant.scripts.keyring -keyring==19.2.0 +keyring==19.3.0 # homeassistant.scripts.keyring -keyrings.alt==3.1.1 +keyrings.alt==3.2.0 # homeassistant.components.kiwi kiwiki-client==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 08174f87874..5fe6c4f6143 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -259,10 +259,10 @@ influxdb==5.2.3 jsonpath==0.82 # homeassistant.scripts.keyring -keyring==19.2.0 +keyring==19.3.0 # homeassistant.scripts.keyring -keyrings.alt==3.1.1 +keyrings.alt==3.2.0 # homeassistant.components.dyson libpurecool==0.5.0 From 6ad3b6426a46ed26e37eee35dce700572d765ddc Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 14:55:23 +0100 Subject: [PATCH 084/677] sort imports according to PEP8 for auth (#29619) --- homeassistant/components/auth/__init__.py | 14 ++++++-------- homeassistant/components/auth/indieauth.py | 6 +++--- homeassistant/components/auth/login_flow.py | 5 +++-- homeassistant/components/auth/mfa_setup_flow.py | 2 +- tests/components/auth/__init__.py | 1 - tests/components/auth/test_init.py | 6 +++--- tests/components/auth/test_mfa_setup_flow.py | 2 +- 7 files changed, 17 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/auth/__init__.py b/homeassistant/components/auth/__init__.py index d0da9d39fe8..888ef98a582 100644 --- a/homeassistant/components/auth/__init__.py +++ b/homeassistant/components/auth/__init__.py @@ -114,31 +114,29 @@ Result will be a long-lived access token: } """ +from datetime import timedelta import logging import uuid -from datetime import timedelta from aiohttp import web import voluptuous as vol from homeassistant.auth.models import ( - User, - Credentials, TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN, + Credentials, + User, ) -from homeassistant.loader import bind_hass from homeassistant.components import websocket_api from homeassistant.components.http import KEY_REAL_IP from homeassistant.components.http.auth import async_sign_path 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, HomeAssistant +from homeassistant.core import HomeAssistant, callback +from homeassistant.loader import bind_hass from homeassistant.util import dt as dt_util -from . import indieauth -from . import login_flow -from . import mfa_setup_flow +from . import indieauth, login_flow, mfa_setup_flow DOMAIN = "auth" WS_TYPE_CURRENT_USER = "auth/current_user" diff --git a/homeassistant/components/auth/indieauth.py b/homeassistant/components/auth/indieauth.py index 6a0a516bee2..c845f230bf3 100644 --- a/homeassistant/components/auth/indieauth.py +++ b/homeassistant/components/auth/indieauth.py @@ -1,9 +1,9 @@ """Helpers to resolve client ID/secret.""" -import logging import asyncio -from ipaddress import ip_address from html.parser import HTMLParser -from urllib.parse import urlparse, urljoin +from ipaddress import ip_address +import logging +from urllib.parse import urljoin, urlparse import aiohttp diff --git a/homeassistant/components/auth/login_flow.py b/homeassistant/components/auth/login_flow.py index d6844396ce7..6f8d2751018 100644 --- a/homeassistant/components/auth/login_flow.py +++ b/homeassistant/components/auth/login_flow.py @@ -73,12 +73,13 @@ import voluptuous_serialize from homeassistant import data_entry_flow from homeassistant.components.http import KEY_REAL_IP from homeassistant.components.http.ban import ( - process_wrong_login, - process_success_login, log_invalid_auth, + process_success_login, + process_wrong_login, ) from homeassistant.components.http.data_validator import RequestDataValidator from homeassistant.components.http.view import HomeAssistantView + from . import indieauth diff --git a/homeassistant/components/auth/mfa_setup_flow.py b/homeassistant/components/auth/mfa_setup_flow.py index 271e9ae1634..92926e2e7c5 100644 --- a/homeassistant/components/auth/mfa_setup_flow.py +++ b/homeassistant/components/auth/mfa_setup_flow.py @@ -6,7 +6,7 @@ import voluptuous_serialize from homeassistant import data_entry_flow from homeassistant.components import websocket_api -from homeassistant.core import callback, HomeAssistant +from homeassistant.core import HomeAssistant, callback WS_TYPE_SETUP_MFA = "auth/setup_mfa" SCHEMA_WS_SETUP_MFA = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( diff --git a/tests/components/auth/__init__.py b/tests/components/auth/__init__.py index 5114e18889b..7ce65964086 100644 --- a/tests/components/auth/__init__.py +++ b/tests/components/auth/__init__.py @@ -4,7 +4,6 @@ from homeassistant.setup import async_setup_component from tests.common import ensure_auth_manager_loaded - BASE_CONFIG = [ { "name": "Example", diff --git a/tests/components/auth/test_init.py b/tests/components/auth/test_init.py index de91613b74b..162569aa0e8 100644 --- a/tests/components/auth/test_init.py +++ b/tests/components/auth/test_init.py @@ -3,15 +3,15 @@ from datetime import timedelta from unittest.mock import patch from homeassistant.auth.models import Credentials +from homeassistant.components import auth from homeassistant.components.auth import RESULT_TYPE_USER from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow -from homeassistant.components import auth - -from tests.common import CLIENT_ID, CLIENT_REDIRECT_URI, MockUser from . import async_setup_auth +from tests.common import CLIENT_ID, CLIENT_REDIRECT_URI, MockUser + async def test_login_new_user_and_trying_refresh_token(hass, aiohttp_client): """Test logging in with new user and refreshing tokens.""" diff --git a/tests/components/auth/test_mfa_setup_flow.py b/tests/components/auth/test_mfa_setup_flow.py index ffce9266e57..3569d7d5233 100644 --- a/tests/components/auth/test_mfa_setup_flow.py +++ b/tests/components/auth/test_mfa_setup_flow.py @@ -4,7 +4,7 @@ from homeassistant.auth import auth_manager_from_config from homeassistant.components.auth import mfa_setup_flow from homeassistant.setup import async_setup_component -from tests.common import MockUser, CLIENT_ID, ensure_auth_manager_loaded +from tests.common import CLIENT_ID, MockUser, ensure_auth_manager_loaded async def test_ws_setup_depose_mfa(hass, hass_ws_client): From ba34922b03f40917041dff15be7ecbdb34128aa2 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 14:56:42 +0100 Subject: [PATCH 085/677] sort imports according to PEP8 for alexa (#29618) --- homeassistant/components/alexa/__init__.py | 11 +++---- homeassistant/components/alexa/auth.py | 3 +- .../components/alexa/capabilities.py | 10 +++--- homeassistant/components/alexa/const.py | 4 +-- homeassistant/components/alexa/entities.py | 26 +++++++-------- .../components/alexa/flash_briefings.py | 2 +- homeassistant/components/alexa/handlers.py | 8 ++--- homeassistant/components/alexa/smart_home.py | 2 +- .../components/alexa/smart_home_http.py | 2 +- .../components/alexa/state_report.py | 2 +- tests/components/alexa/__init__.py | 2 +- tests/components/alexa/test_auth.py | 1 + tests/components/alexa/test_capabilities.py | 33 ++++++++++--------- tests/components/alexa/test_entities.py | 3 +- .../components/alexa/test_flash_briefings.py | 4 +-- tests/components/alexa/test_intent.py | 4 +-- tests/components/alexa/test_smart_home.py | 18 +++++----- .../components/alexa/test_smart_home_http.py | 2 +- tests/components/alexa/test_state_report.py | 3 +- 19 files changed, 72 insertions(+), 68 deletions(-) diff --git a/homeassistant/components/alexa/__init__.py b/homeassistant/components/alexa/__init__.py index cb0d093bb48..e9bcccb3587 100644 --- a/homeassistant/components/alexa/__init__.py +++ b/homeassistant/components/alexa/__init__.py @@ -3,25 +3,24 @@ import logging import voluptuous as vol -from homeassistant.helpers import config_validation as cv -from homeassistant.helpers import entityfilter from homeassistant.const import CONF_NAME +from homeassistant.helpers import config_validation as cv, entityfilter from . import flash_briefings, intent, smart_home_http from .const import ( CONF_AUDIO, CONF_CLIENT_ID, CONF_CLIENT_SECRET, + CONF_DESCRIPTION, + CONF_DISPLAY_CATEGORIES, CONF_DISPLAY_URL, CONF_ENDPOINT, + CONF_ENTITY_CONFIG, + CONF_FILTER, CONF_TEXT, CONF_TITLE, CONF_UID, DOMAIN, - CONF_FILTER, - CONF_ENTITY_CONFIG, - CONF_DESCRIPTION, - CONF_DISPLAY_CATEGORIES, ) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/alexa/auth.py b/homeassistant/components/alexa/auth.py index 9f87a6d954e..33c25b73d7e 100644 --- a/homeassistant/components/alexa/auth.py +++ b/homeassistant/components/alexa/auth.py @@ -1,8 +1,9 @@ """Support for Alexa skill auth.""" import asyncio +from datetime import timedelta import json import logging -from datetime import timedelta + import aiohttp import async_timeout diff --git a/homeassistant/components/alexa/capabilities.py b/homeassistant/components/alexa/capabilities.py index 1f18cb7a590..38f769b7cd4 100644 --- a/homeassistant/components/alexa/capabilities.py +++ b/homeassistant/components/alexa/capabilities.py @@ -1,6 +1,10 @@ """Alexa capabilities.""" import logging +from homeassistant.components import cover, fan, light +from homeassistant.components.alarm_control_panel import ATTR_CODE_FORMAT, FORMAT_NUMBER +import homeassistant.components.climate.const as climate +import homeassistant.components.media_player.const as media_player from homeassistant.const import ( ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE, @@ -20,21 +24,17 @@ from homeassistant.const import ( STATE_UNKNOWN, STATE_UNLOCKED, ) -import homeassistant.components.climate.const as climate -import homeassistant.components.media_player.const as media_player -from homeassistant.components.alarm_control_panel import ATTR_CODE_FORMAT, FORMAT_NUMBER -from homeassistant.components import light, fan, cover import homeassistant.util.color as color_util import homeassistant.util.dt as dt_util from .const import ( - Catalog, API_TEMP_UNITS, API_THERMOSTAT_MODES, API_THERMOSTAT_PRESETS, DATE_FORMAT, PERCENTAGE_FAN_MAP, RANGE_FAN_MAP, + Catalog, Inputs, ) from .errors import UnsupportedProperty diff --git a/homeassistant/components/alexa/const.py b/homeassistant/components/alexa/const.py index 1aa9d4f2c1d..2c62e1a485a 100644 --- a/homeassistant/components/alexa/const.py +++ b/homeassistant/components/alexa/const.py @@ -1,9 +1,9 @@ """Constants for the Alexa integration.""" from collections import OrderedDict -from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT -from homeassistant.components.climate import const as climate from homeassistant.components import fan +from homeassistant.components.climate import const as climate +from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT DOMAIN = "alexa" diff --git a/homeassistant/components/alexa/entities.py b/homeassistant/components/alexa/entities.py index f9463949b58..733ea73f998 100644 --- a/homeassistant/components/alexa/entities.py +++ b/homeassistant/components/alexa/entities.py @@ -1,18 +1,6 @@ """Alexa entity adapters.""" from typing import List -from homeassistant.core import callback -from homeassistant.const import ( - ATTR_DEVICE_CLASS, - ATTR_SUPPORTED_FEATURES, - ATTR_UNIT_OF_MEASUREMENT, - CLOUD_NEVER_EXPOSED_ENTITIES, - CONF_NAME, - TEMP_CELSIUS, - TEMP_FAHRENHEIT, -) -from homeassistant.util.decorator import Registry -from homeassistant.components.climate import const as climate from homeassistant.components import ( alarm_control_panel, alert, @@ -30,8 +18,19 @@ from homeassistant.components import ( sensor, switch, ) +from homeassistant.components.climate import const as climate +from homeassistant.const import ( + ATTR_DEVICE_CLASS, + ATTR_SUPPORTED_FEATURES, + ATTR_UNIT_OF_MEASUREMENT, + CLOUD_NEVER_EXPOSED_ENTITIES, + CONF_NAME, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, +) +from homeassistant.core import callback +from homeassistant.util.decorator import Registry -from .const import CONF_DESCRIPTION, CONF_DISPLAY_CATEGORIES from .capabilities import ( Alexa, AlexaBrightnessController, @@ -60,6 +59,7 @@ from .capabilities import ( AlexaThermostatController, AlexaToggleController, ) +from .const import CONF_DESCRIPTION, CONF_DISPLAY_CATEGORIES ENTITY_ADAPTERS = Registry() diff --git a/homeassistant/components/alexa/flash_briefings.py b/homeassistant/components/alexa/flash_briefings.py index 0b5c1243764..45d31d6088a 100644 --- a/homeassistant/components/alexa/flash_briefings.py +++ b/homeassistant/components/alexa/flash_briefings.py @@ -3,10 +3,10 @@ import copy import logging import uuid -import homeassistant.util.dt as dt_util from homeassistant.components import http from homeassistant.core import callback from homeassistant.helpers import template +import homeassistant.util.dt as dt_util from .const import ( ATTR_MAIN_TEXT, diff --git a/homeassistant/components/alexa/handlers.py b/homeassistant/components/alexa/handlers.py index f1aa260e88e..efb4f59514d 100644 --- a/homeassistant/components/alexa/handlers.py +++ b/homeassistant/components/alexa/handlers.py @@ -34,20 +34,20 @@ from homeassistant.const import ( TEMP_FAHRENHEIT, ) import homeassistant.util.color as color_util -import homeassistant.util.dt as dt_util from homeassistant.util.decorator import Registry +import homeassistant.util.dt as dt_util from homeassistant.util.temperature import convert as convert_temperature from .const import ( API_TEMP_UNITS, - API_THERMOSTAT_MODES_CUSTOM, API_THERMOSTAT_MODES, + API_THERMOSTAT_MODES_CUSTOM, API_THERMOSTAT_PRESETS, - Cause, - Inputs, PERCENTAGE_FAN_MAP, RANGE_FAN_MAP, SPEED_FAN_MAP, + Cause, + Inputs, ) from .entities import async_get_entities from .errors import ( diff --git a/homeassistant/components/alexa/smart_home.py b/homeassistant/components/alexa/smart_home.py index 2c34542e25c..9b0955f8fca 100644 --- a/homeassistant/components/alexa/smart_home.py +++ b/homeassistant/components/alexa/smart_home.py @@ -4,7 +4,7 @@ import logging import homeassistant.core as ha from .const import API_DIRECTIVE, API_HEADER -from .errors import AlexaError, AlexaBridgeUnreachableError +from .errors import AlexaBridgeUnreachableError, AlexaError from .handlers import HANDLERS from .messages import AlexaDirective diff --git a/homeassistant/components/alexa/smart_home_http.py b/homeassistant/components/alexa/smart_home_http.py index ada00e8a326..08d33ffa09c 100644 --- a/homeassistant/components/alexa/smart_home_http.py +++ b/homeassistant/components/alexa/smart_home_http.py @@ -13,8 +13,8 @@ from .const import ( CONF_ENTITY_CONFIG, CONF_FILTER, ) -from .state_report import async_enable_proactive_mode from .smart_home import async_handle_message +from .state_report import async_enable_proactive_mode _LOGGER = logging.getLogger(__name__) SMART_HOME_HTTP_ENDPOINT = "/api/alexa/smart_home" diff --git a/homeassistant/components/alexa/state_report.py b/homeassistant/components/alexa/state_report.py index b5e1b741f0c..44e1b7f4f55 100644 --- a/homeassistant/components/alexa/state_report.py +++ b/homeassistant/components/alexa/state_report.py @@ -6,8 +6,8 @@ import logging import aiohttp import async_timeout -import homeassistant.util.dt as dt_util from homeassistant.const import MATCH_ALL, STATE_ON +import homeassistant.util.dt as dt_util from .const import API_CHANGE, Cause from .entities import ENTITY_ADAPTERS diff --git a/tests/components/alexa/__init__.py b/tests/components/alexa/__init__.py index 3752ad7d48f..79fdb86c3ef 100644 --- a/tests/components/alexa/__init__.py +++ b/tests/components/alexa/__init__.py @@ -1,8 +1,8 @@ """Tests for the Alexa integration.""" from uuid import uuid4 -from homeassistant.core import Context from homeassistant.components.alexa import config, smart_home +from homeassistant.core import Context from tests.common import async_mock_service diff --git a/tests/components/alexa/test_auth.py b/tests/components/alexa/test_auth.py index ecd8f1eb4b3..9d14fffe7e4 100644 --- a/tests/components/alexa/test_auth.py +++ b/tests/components/alexa/test_auth.py @@ -1,5 +1,6 @@ """Test Alexa auth endpoints.""" from homeassistant.components.alexa.auth import Auth + from . import TEST_TOKEN_URL diff --git a/tests/components/alexa/test_capabilities.py b/tests/components/alexa/test_capabilities.py index d2f6cddc522..952af543aab 100644 --- a/tests/components/alexa/test_capabilities.py +++ b/tests/components/alexa/test_capabilities.py @@ -1,37 +1,38 @@ """Test Alexa capabilities.""" import pytest -from homeassistant.const import ( - ATTR_UNIT_OF_MEASUREMENT, - TEMP_CELSIUS, - STATE_LOCKED, - STATE_UNLOCKED, - STATE_UNKNOWN, - STATE_UNAVAILABLE, - STATE_ALARM_DISARMED, - STATE_ALARM_ARMED_AWAY, - STATE_ALARM_ARMED_CUSTOM_BYPASS, - STATE_ALARM_ARMED_HOME, - STATE_ALARM_ARMED_NIGHT, -) -from homeassistant.components.climate import const as climate from homeassistant.components.alexa import smart_home from homeassistant.components.alexa.errors import UnsupportedProperty +from homeassistant.components.climate import const as climate from homeassistant.components.media_player.const import ( SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_STOP, ) -from tests.common import async_mock_service +from homeassistant.const import ( + ATTR_UNIT_OF_MEASUREMENT, + STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_CUSTOM_BYPASS, + STATE_ALARM_ARMED_HOME, + STATE_ALARM_ARMED_NIGHT, + STATE_ALARM_DISARMED, + STATE_LOCKED, + STATE_UNAVAILABLE, + STATE_UNKNOWN, + STATE_UNLOCKED, + TEMP_CELSIUS, +) from . import ( DEFAULT_CONFIG, - get_new_request, assert_request_calls_service, assert_request_fails, + get_new_request, reported_properties, ) +from tests.common import async_mock_service + @pytest.mark.parametrize("result,adjust", [(25, "-5"), (35, "5"), (0, "-80")]) async def test_api_adjust_brightness(hass, result, adjust): diff --git a/tests/components/alexa/test_entities.py b/tests/components/alexa/test_entities.py index 7436306fe25..8cae4fb9b77 100644 --- a/tests/components/alexa/test_entities.py +++ b/tests/components/alexa/test_entities.py @@ -1,6 +1,7 @@ """Test Alexa entity representation.""" from homeassistant.components.alexa import smart_home -from . import get_new_request, DEFAULT_CONFIG + +from . import DEFAULT_CONFIG, get_new_request async def test_unsupported_domain(hass): diff --git a/tests/components/alexa/test_flash_briefings.py b/tests/components/alexa/test_flash_briefings.py index 4c94046d41a..7a20c05ed86 100644 --- a/tests/components/alexa/test_flash_briefings.py +++ b/tests/components/alexa/test_flash_briefings.py @@ -5,10 +5,10 @@ import datetime import pytest -from homeassistant.core import callback -from homeassistant.setup import async_setup_component from homeassistant.components import alexa from homeassistant.components.alexa import const +from homeassistant.core import callback +from homeassistant.setup import async_setup_component SESSION_ID = "amzn1.echo-api.session.0000000-0000-0000-0000-00000000000" APPLICATION_ID = "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe" diff --git a/tests/components/alexa/test_intent.py b/tests/components/alexa/test_intent.py index 0b7fc5f22c8..ac41e6d3b4d 100644 --- a/tests/components/alexa/test_intent.py +++ b/tests/components/alexa/test_intent.py @@ -5,10 +5,10 @@ import json import pytest -from homeassistant.core import callback -from homeassistant.setup import async_setup_component from homeassistant.components import alexa from homeassistant.components.alexa import intent +from homeassistant.core import callback +from homeassistant.setup import async_setup_component SESSION_ID = "amzn1.echo-api.session.0000000-0000-0000-0000-00000000000" APPLICATION_ID = "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe" diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index 9a2eba21c0e..ea1e6f50fcf 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -1,9 +1,7 @@ """Test for smart home alexa support.""" import pytest -from homeassistant.core import Context, callback -from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT -from homeassistant.components.alexa import smart_home, messages +from homeassistant.components.alexa import messages, smart_home from homeassistant.components.media_player.const import ( SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, @@ -18,22 +16,24 @@ from homeassistant.components.media_player.const import ( SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, ) +from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT +from homeassistant.core import Context, callback from homeassistant.helpers import entityfilter -from tests.common import async_mock_service - from . import ( - get_new_request, - MockConfig, DEFAULT_CONFIG, - assert_request_calls_service, - assert_request_fails, + MockConfig, ReportedProperties, assert_power_controller_works, + assert_request_calls_service, + assert_request_fails, assert_scene_controller_works, + get_new_request, reported_properties, ) +from tests.common import async_mock_service + @pytest.fixture def events(hass): diff --git a/tests/components/alexa/test_smart_home_http.py b/tests/components/alexa/test_smart_home_http.py index 845c375be67..f242a421eac 100644 --- a/tests/components/alexa/test_smart_home_http.py +++ b/tests/components/alexa/test_smart_home_http.py @@ -1,8 +1,8 @@ """Test Smart Home HTTP endpoints.""" import json -from homeassistant.setup import async_setup_component from homeassistant.components.alexa import DOMAIN, smart_home_http +from homeassistant.setup import async_setup_component from . import get_new_request diff --git a/tests/components/alexa/test_state_report.py b/tests/components/alexa/test_state_report.py index 2c58d1ed45e..4cd2a18a833 100644 --- a/tests/components/alexa/test_state_report.py +++ b/tests/components/alexa/test_state_report.py @@ -1,6 +1,7 @@ """Test report state.""" from homeassistant.components.alexa import state_report -from . import TEST_URL, DEFAULT_CONFIG + +from . import DEFAULT_CONFIG, TEST_URL async def test_report_state(hass, aioclient_mock): From 663e2eaaffe48bec0987fc18d9b2ba9ff3548284 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 14:59:21 +0100 Subject: [PATCH 086/677] sort imports according to PEP8 for buienradar (#29623) --- homeassistant/components/buienradar/camera.py | 5 +---- homeassistant/components/buienradar/sensor.py | 2 -- homeassistant/components/buienradar/util.py | 5 +---- homeassistant/components/buienradar/weather.py | 2 +- tests/components/buienradar/test_camera.py | 4 ++-- tests/components/buienradar/test_sensor.py | 3 +-- tests/components/buienradar/test_weather.py | 1 - 7 files changed, 6 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/buienradar/camera.py b/homeassistant/components/buienradar/camera.py index cdf202bbafd..3d30e330bc9 100644 --- a/homeassistant/components/buienradar/camera.py +++ b/homeassistant/components/buienradar/camera.py @@ -1,7 +1,7 @@ """Provide animated GIF loops of Buienradar imagery.""" import asyncio -import logging from datetime import datetime, timedelta +import logging from typing import Optional import aiohttp @@ -9,13 +9,10 @@ import voluptuous as vol from homeassistant.components.camera import PLATFORM_SCHEMA, Camera from homeassistant.const import CONF_NAME - from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession - from homeassistant.util import dt as dt_util - CONF_DIMENSION = "dimension" CONF_DELTA = "delta" diff --git a/homeassistant/components/buienradar/sensor.py b/homeassistant/components/buienradar/sensor.py index 5fe97b6fb38..4011a928dfe 100644 --- a/homeassistant/components/buienradar/sensor.py +++ b/homeassistant/components/buienradar/sensor.py @@ -33,11 +33,9 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import dt as dt_util - from .const import DEFAULT_TIMEFRAME from .util import BrData - _LOGGER = logging.getLogger(__name__) MEASURED_LABEL = "Measured" diff --git a/homeassistant/components/buienradar/util.py b/homeassistant/components/buienradar/util.py index 579b3418271..2ef0713713b 100644 --- a/homeassistant/components/buienradar/util.py +++ b/homeassistant/components/buienradar/util.py @@ -5,7 +5,6 @@ import logging import aiohttp import async_timeout - from buienradar.buienradar import parse_data from buienradar.constants import ( ATTRIBUTION, @@ -31,9 +30,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.util import dt as dt_util - -from .const import SCHEDULE_OK, SCHEDULE_NOK - +from .const import SCHEDULE_NOK, SCHEDULE_OK _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/buienradar/weather.py b/homeassistant/components/buienradar/weather.py index c95e57807c4..98cbb2f5e43 100644 --- a/homeassistant/components/buienradar/weather.py +++ b/homeassistant/components/buienradar/weather.py @@ -28,8 +28,8 @@ from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, TEMP_C from homeassistant.helpers import config_validation as cv # Reuse data and API logic from the sensor implementation -from .util import BrData from .const import DEFAULT_TIMEFRAME +from .util import BrData _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/buienradar/test_camera.py b/tests/components/buienradar/test_camera.py index dcbbb4ae076..2dd63583741 100644 --- a/tests/components/buienradar/test_camera.py +++ b/tests/components/buienradar/test_camera.py @@ -1,10 +1,10 @@ """The tests for generic camera component.""" import asyncio + from aiohttp.client_exceptions import ClientResponseError -from homeassistant.util import dt as dt_util - from homeassistant.setup import async_setup_component +from homeassistant.util import dt as dt_util # An infinitesimally small time-delta. EPSILON_DELTA = 0.0000000001 diff --git a/tests/components/buienradar/test_sensor.py b/tests/components/buienradar/test_sensor.py index c1569e4576b..0c1cbd2a158 100644 --- a/tests/components/buienradar/test_sensor.py +++ b/tests/components/buienradar/test_sensor.py @@ -1,7 +1,6 @@ """The tests for the Buienradar sensor platform.""" -from homeassistant.setup import async_setup_component from homeassistant.components import sensor - +from homeassistant.setup import async_setup_component CONDITIONS = ["stationname", "temperature"] BASE_CONFIG = { diff --git a/tests/components/buienradar/test_weather.py b/tests/components/buienradar/test_weather.py index 1a8c94e1712..081616a1406 100644 --- a/tests/components/buienradar/test_weather.py +++ b/tests/components/buienradar/test_weather.py @@ -2,7 +2,6 @@ from homeassistant.components import weather from homeassistant.setup import async_setup_component - # Example config snippet from documentation. BASE_CONFIG = { "weather": [ From c78773970b8ee1fe084c5cc76b71639d6c594cd1 Mon Sep 17 00:00:00 2001 From: James Nimmo Date: Mon, 9 Dec 2019 03:09:16 +1300 Subject: [PATCH 087/677] Add IntesisHome Climate Platform (#25364) * Add IntesisHome Climate Platform * Add support for IntesisHome and Airconwithme devices * Implement requested changes from PR review * Improve error handling for IntesisHome component * Fix snake-case naming style * Update exception logging --- .coveragerc | 1 + CODEOWNERS | 1 + .../components/intesishome/__init__.py | 1 + .../components/intesishome/climate.py | 407 ++++++++++++++++++ .../components/intesishome/manifest.json | 8 + requirements_all.txt | 3 + 6 files changed, 421 insertions(+) create mode 100755 homeassistant/components/intesishome/__init__.py create mode 100755 homeassistant/components/intesishome/climate.py create mode 100755 homeassistant/components/intesishome/manifest.json diff --git a/.coveragerc b/.coveragerc index 7f519f8970a..778af8db89a 100644 --- a/.coveragerc +++ b/.coveragerc @@ -332,6 +332,7 @@ omit = homeassistant/components/influxdb/sensor.py homeassistant/components/insteon/* homeassistant/components/incomfort/* + homeassistant/components/intesishome/* homeassistant/components/ios/* homeassistant/components/iota/* homeassistant/components/iperf3/* diff --git a/CODEOWNERS b/CODEOWNERS index 7f5ff17a043..472798f72a6 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -162,6 +162,7 @@ homeassistant/components/input_select/* @home-assistant/core homeassistant/components/input_text/* @home-assistant/core homeassistant/components/integration/* @dgomes homeassistant/components/intent/* @home-assistant/core +homeassistant/components/intesishome/* @jnimmo homeassistant/components/ios/* @robbiet480 homeassistant/components/iperf3/* @rohankapoorcom homeassistant/components/ipma/* @dgomes diff --git a/homeassistant/components/intesishome/__init__.py b/homeassistant/components/intesishome/__init__.py new file mode 100755 index 00000000000..fd4ae1f03e3 --- /dev/null +++ b/homeassistant/components/intesishome/__init__.py @@ -0,0 +1 @@ +"""Intesishome platform.""" diff --git a/homeassistant/components/intesishome/climate.py b/homeassistant/components/intesishome/climate.py new file mode 100755 index 00000000000..669d1155d80 --- /dev/null +++ b/homeassistant/components/intesishome/climate.py @@ -0,0 +1,407 @@ +"""Support for IntesisHome and airconwithme Smart AC Controllers.""" +import logging +from random import randrange + +from pyintesishome import IHAuthenticationError, IHConnectionError, IntesisHome +import voluptuous as vol + +from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice +from homeassistant.components.climate.const import ( + ATTR_HVAC_MODE, + HVAC_MODE_COOL, + HVAC_MODE_DRY, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_OFF, + SUPPORT_FAN_MODE, + SUPPORT_SWING_MODE, + SUPPORT_TARGET_TEMPERATURE, + SWING_BOTH, + SWING_HORIZONTAL, + SWING_OFF, + SWING_VERTICAL, +) +from homeassistant.const import ( + ATTR_TEMPERATURE, + CONF_DEVICE, + CONF_PASSWORD, + CONF_USERNAME, + TEMP_CELSIUS, +) +from homeassistant.exceptions import PlatformNotReady +from homeassistant.helpers.aiohttp_client import async_get_clientsession +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.event import async_call_later + +_LOGGER = logging.getLogger(__name__) + +IH_DEVICE_INTESISHOME = "IntesisHome" +IH_DEVICE_AIRCONWITHME = "airconwithme" + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_DEVICE, default=IH_DEVICE_INTESISHOME): vol.In( + [IH_DEVICE_AIRCONWITHME, IH_DEVICE_INTESISHOME] + ), + } +) + +MAP_IH_TO_HVAC_MODE = { + "auto": HVAC_MODE_HEAT_COOL, + "cool": HVAC_MODE_COOL, + "dry": HVAC_MODE_DRY, + "fan": HVAC_MODE_FAN_ONLY, + "heat": HVAC_MODE_HEAT, + "off": HVAC_MODE_OFF, +} + +MAP_HVAC_MODE_TO_IH = {v: k for k, v in MAP_IH_TO_HVAC_MODE.items()} + +MAP_STATE_ICONS = { + HVAC_MODE_COOL: "mdi:snowflake", + HVAC_MODE_DRY: "mdi:water-off", + HVAC_MODE_FAN_ONLY: "mdi:fan", + HVAC_MODE_HEAT: "mdi:white-balance-sunny", + HVAC_MODE_HEAT_COOL: "mdi:cached", +} + +IH_HVAC_MODES = [ + HVAC_MODE_HEAT_COOL, + HVAC_MODE_COOL, + HVAC_MODE_HEAT, + HVAC_MODE_DRY, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_OFF, +] + + +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Create the IntesisHome climate devices.""" + ih_user = config[CONF_USERNAME] + ih_pass = config[CONF_PASSWORD] + device_type = config[CONF_DEVICE] + + controller = IntesisHome( + ih_user, + ih_pass, + hass.loop, + websession=async_get_clientsession(hass), + device_type=device_type, + ) + try: + await controller.poll_status() + except IHAuthenticationError: + _LOGGER.error("Invalid username or password") + return + except IHConnectionError: + _LOGGER.error("Error connecting to the %s server", device_type) + raise PlatformNotReady + + ih_devices = controller.get_devices() + if ih_devices: + async_add_entities( + [ + IntesisAC(ih_device_id, device, controller) + for ih_device_id, device in ih_devices.items() + ], + True, + ) + else: + _LOGGER.error( + "Error getting device list from %s API: %s", + device_type, + controller.error_message, + ) + await controller.stop() + + +class IntesisAC(ClimateDevice): + """Represents an Intesishome air conditioning device.""" + + def __init__(self, ih_device_id, ih_device, controller): + """Initialize the thermostat.""" + self._controller = controller + self._device_id = ih_device_id + self._ih_device = ih_device + self._device_name = ih_device.get("name") + self._device_type = controller.device_type + self._connected = None + self._setpoint_step = 1 + self._current_temp = None + self._max_temp = None + self._min_temp = None + self._target_temp = None + self._outdoor_temp = None + self._run_hours = None + self._rssi = None + self._swing = None + self._swing_list = None + self._vvane = None + self._hvane = None + self._power = False + self._fan_speed = None + self._hvac_mode = None + self._fan_modes = controller.get_fan_speed_list(ih_device_id) + self._support = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE + self._swing_list = [SWING_OFF] + + if ih_device.get("config_vertical_vanes"): + self._swing_list.append(SWING_VERTICAL) + if ih_device.get("config_horizontal_vanes"): + self._swing_list.append(SWING_HORIZONTAL) + if len(self._swing_list) == 3: + self._swing_list.append(SWING_BOTH) + self._support |= SUPPORT_SWING_MODE + elif len(self._swing_list) == 2: + self._support |= SUPPORT_SWING_MODE + + async def async_added_to_hass(self): + """Subscribe to event updates.""" + _LOGGER.debug("Added climate device with state: %s", repr(self._ih_device)) + await self._controller.add_update_callback(self.async_update_callback) + try: + await self._controller.connect() + except IHConnectionError as ex: + _LOGGER.error("Exception connecting to IntesisHome: %s", ex) + + @property + def name(self): + """Return the name of the AC device.""" + return self._device_name + + @property + def temperature_unit(self): + """Intesishome API uses celsius on the backend.""" + return TEMP_CELSIUS + + @property + def device_state_attributes(self): + """Return the device specific state attributes.""" + attrs = {} + if len(self._swing_list) > 1: + attrs["vertical_vane"] = self._vvane + attrs["horizontal_vane"] = self._hvane + if self._outdoor_temp: + attrs["outdoor_temp"] = self._outdoor_temp + return attrs + + @property + def unique_id(self): + """Return unique ID for this device.""" + return self._device_id + + @property + def target_temperature_step(self) -> float: + """Return whether setpoint should be whole or half degree precision.""" + return self._setpoint_step + + async def async_set_temperature(self, **kwargs): + """Set new target temperature.""" + temperature = kwargs.get(ATTR_TEMPERATURE) + hvac_mode = kwargs.get(ATTR_HVAC_MODE) + + if hvac_mode: + await self.async_set_hvac_mode(hvac_mode) + + if temperature: + _LOGGER.debug("Setting %s to %s degrees", self._device_type, temperature) + await self._controller.set_temperature(self._device_id, temperature) + self._target_temp = temperature + + # Write updated temperature to HA state to avoid flapping (API confirmation is slow) + self.async_write_ha_state() + + async def async_set_hvac_mode(self, hvac_mode): + """Set operation mode.""" + _LOGGER.debug("Setting %s to %s mode", self._device_type, hvac_mode) + if hvac_mode == HVAC_MODE_OFF: + self._power = False + await self._controller.set_power_off(self._device_id) + # Write changes to HA, API can be slow to push changes + self.async_write_ha_state() + return + + # First check device is turned on + if not self._controller.is_on(self._device_id): + self._power = True + await self._controller.set_power_on(self._device_id) + + # Set the mode + await self._controller.set_mode(self._device_id, MAP_HVAC_MODE_TO_IH[hvac_mode]) + + # Send the temperature again in case changing modes has changed it + if self._target_temp: + await self._controller.set_temperature(self._device_id, self._target_temp) + + # Updates can take longer than 2 seconds, so update locally + self._hvac_mode = hvac_mode + self.async_write_ha_state() + + async def async_set_fan_mode(self, fan_mode): + """Set fan mode (from quiet, low, medium, high, auto).""" + await self._controller.set_fan_speed(self._device_id, fan_mode) + + # Updates can take longer than 2 seconds, so update locally + self._fan_speed = fan_mode + self.async_write_ha_state() + + async def async_set_swing_mode(self, swing_mode): + """Set the vertical vane.""" + if swing_mode == SWING_OFF: + await self._controller.set_vertical_vane(self._device_id, "auto/stop") + await self._controller.set_horizontal_vane(self._device_id, "auto/stop") + elif swing_mode == SWING_BOTH: + await self._controller.set_vertical_vane(self._device_id, "swing") + await self._controller.set_horizontal_vane(self._device_id, "swing") + elif swing_mode == SWING_HORIZONTAL: + await self._controller.set_vertical_vane(self._device_id, "auto/stop") + await self._controller.set_horizontal_vane(self._device_id, "swing") + elif swing_mode == SWING_VERTICAL: + await self._controller.set_vertical_vane(self._device_id, "swing") + await self._controller.set_horizontal_vane(self._device_id, "auto/stop") + self._swing = swing_mode + + async def async_update(self): + """Copy values from controller dictionary to climate device.""" + # Update values from controller's device dictionary + self._connected = self._controller.is_connected + self._current_temp = self._controller.get_temperature(self._device_id) + self._fan_speed = self._controller.get_fan_speed(self._device_id) + self._power = self._controller.is_on(self._device_id) + self._min_temp = self._controller.get_min_setpoint(self._device_id) + self._max_temp = self._controller.get_max_setpoint(self._device_id) + self._rssi = self._controller.get_rssi(self._device_id) + self._run_hours = self._controller.get_run_hours(self._device_id) + self._target_temp = self._controller.get_setpoint(self._device_id) + self._outdoor_temp = self._controller.get_outdoor_temperature(self._device_id) + + # Operation mode + mode = self._controller.get_mode(self._device_id) + self._hvac_mode = MAP_IH_TO_HVAC_MODE.get(mode) + + # Swing mode + # Climate module only supports one swing setting. + self._vvane = self._controller.get_vertical_swing(self._device_id) + self._hvane = self._controller.get_horizontal_swing(self._device_id) + + if self._vvane == "swing" and self._hvane == "swing": + self._swing = SWING_BOTH + elif self._vvane == "swing": + self._swing = SWING_VERTICAL + elif self._hvane == "swing": + self._swing = SWING_HORIZONTAL + else: + self._swing = SWING_OFF + + async def async_will_remove_from_hass(self): + """Shutdown the controller when the device is being removed.""" + await self._controller.stop() + + @property + def icon(self): + """Return the icon for the current state.""" + icon = None + if self._power: + icon = MAP_STATE_ICONS.get(self._hvac_mode) + return icon + + async def async_update_callback(self, device_id=None): + """Let HA know there has been an update from the controller.""" + # Track changes in connection state + if not self._controller.is_connected and self._connected: + # Connection has dropped + self._connected = False + reconnect_minutes = 1 + randrange(10) + _LOGGER.error( + "Connection to %s API was lost. Reconnecting in %i minutes", + self._device_type, + reconnect_minutes, + ) + # Schedule reconnection + async_call_later( + self.hass, reconnect_minutes * 60, self._controller.connect() + ) + + if self._controller.is_connected and not self._connected: + # Connection has been restored + self._connected = True + _LOGGER.debug("Connection to %s API was restored", self._device_type) + + if not device_id or self._device_id == device_id: + # Update all devices if no device_id was specified + _LOGGER.debug( + "%s API sent a status update for device %s", + self._device_type, + device_id, + ) + self.async_schedule_update_ha_state(True) + + @property + def min_temp(self): + """Return the minimum temperature for the current mode of operation.""" + return self._min_temp + + @property + def max_temp(self): + """Return the maximum temperature for the current mode of operation.""" + return self._max_temp + + @property + def should_poll(self): + """Poll for updates if pyIntesisHome doesn't have a socket open.""" + return False + + @property + def hvac_modes(self): + """List of available operation modes.""" + return IH_HVAC_MODES + + @property + def fan_mode(self): + """Return whether the fan is on.""" + return self._fan_speed + + @property + def swing_mode(self): + """Return current swing mode.""" + return self._swing + + @property + def fan_modes(self): + """List of available fan modes.""" + return self._fan_modes + + @property + def swing_modes(self): + """List of available swing positions.""" + return self._swing_list + + @property + def available(self) -> bool: + """If the device hasn't been able to connect, mark as unavailable.""" + return self._connected or self._connected is None + + @property + def current_temperature(self): + """Return the current temperature.""" + return self._current_temp + + @property + def hvac_mode(self): + """Return the current mode of operation if unit is on.""" + if self._power: + return self._hvac_mode + return HVAC_MODE_OFF + + @property + def target_temperature(self): + """Return the current setpoint temperature if unit is on.""" + return self._target_temp + + @property + def supported_features(self): + """Return the list of supported features.""" + return self._support diff --git a/homeassistant/components/intesishome/manifest.json b/homeassistant/components/intesishome/manifest.json new file mode 100755 index 00000000000..025d08ac548 --- /dev/null +++ b/homeassistant/components/intesishome/manifest.json @@ -0,0 +1,8 @@ +{ + "domain": "intesishome", + "name": "IntesisHome", + "documentation": "https://www.home-assistant.io/integrations/intesishome", + "dependencies": [], + "codeowners": ["@jnimmo"], + "requirements": ["pyintesishome==1.5"] +} diff --git a/requirements_all.txt b/requirements_all.txt index 992069c5045..a50325ffd19 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1285,6 +1285,9 @@ pyialarm==0.3 # homeassistant.components.icloud pyicloud==0.9.1 +# homeassistant.components.intesishome +pyintesishome==1.5 + # homeassistant.components.ipma pyipma==1.2.1 From 94dec483e91e8a0f606cd1b2575322e7a2c07bbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Beye?= Date: Sun, 8 Dec 2019 15:18:46 +0100 Subject: [PATCH 088/677] Don't escape command parameters (#29504) * Don't escape command parameters Escaping should only be done when using the tcp socket cli interface which we aren't. * Updated comment to reflect the changes --- homeassistant/components/squeezebox/media_player.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/squeezebox/media_player.py b/homeassistant/components/squeezebox/media_player.py index b3fb82591c9..72a5772a14d 100644 --- a/homeassistant/components/squeezebox/media_player.py +++ b/homeassistant/components/squeezebox/media_player.py @@ -538,11 +538,11 @@ class SqueezeBoxDevice(MediaPlayerDevice): """ Call Squeezebox JSON/RPC method. - Escaped optional parameters are added to the command to form the list - of positional parameters (p0, p1..., pN) passed to JSON/RPC server. + Additional parameters are added to the command to form the list of + positional parameters (p0, p1..., pN) passed to JSON/RPC server. """ all_params = [command] if parameters: for parameter in parameters: - all_params.append(urllib.parse.quote(parameter, safe="+:=/?")) + all_params.append(parameter) return self.async_query(*all_params) From c4794572d43c5f420fd7ea7548a0b097ca7513d5 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 8 Dec 2019 20:13:10 +0530 Subject: [PATCH 089/677] Fix file permission (#29635) --- homeassistant/components/dsmr_reader/__init__.py | 0 homeassistant/components/dsmr_reader/manifest.json | 0 homeassistant/components/dsmr_reader/sensor.py | 0 homeassistant/components/here_travel_time/__init__.py | 0 homeassistant/components/here_travel_time/manifest.json | 0 homeassistant/components/here_travel_time/sensor.py | 0 homeassistant/components/tahoma/cover.py | 0 7 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 homeassistant/components/dsmr_reader/__init__.py mode change 100755 => 100644 homeassistant/components/dsmr_reader/manifest.json mode change 100755 => 100644 homeassistant/components/dsmr_reader/sensor.py mode change 100755 => 100644 homeassistant/components/here_travel_time/__init__.py mode change 100755 => 100644 homeassistant/components/here_travel_time/manifest.json mode change 100755 => 100644 homeassistant/components/here_travel_time/sensor.py mode change 100755 => 100644 homeassistant/components/tahoma/cover.py diff --git a/homeassistant/components/dsmr_reader/__init__.py b/homeassistant/components/dsmr_reader/__init__.py old mode 100755 new mode 100644 diff --git a/homeassistant/components/dsmr_reader/manifest.json b/homeassistant/components/dsmr_reader/manifest.json old mode 100755 new mode 100644 diff --git a/homeassistant/components/dsmr_reader/sensor.py b/homeassistant/components/dsmr_reader/sensor.py old mode 100755 new mode 100644 diff --git a/homeassistant/components/here_travel_time/__init__.py b/homeassistant/components/here_travel_time/__init__.py old mode 100755 new mode 100644 diff --git a/homeassistant/components/here_travel_time/manifest.json b/homeassistant/components/here_travel_time/manifest.json old mode 100755 new mode 100644 diff --git a/homeassistant/components/here_travel_time/sensor.py b/homeassistant/components/here_travel_time/sensor.py old mode 100755 new mode 100644 diff --git a/homeassistant/components/tahoma/cover.py b/homeassistant/components/tahoma/cover.py old mode 100755 new mode 100644 From b0d0060643699f3f0b1c28b62bf5ff45be4b21dc Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 15:44:04 +0100 Subject: [PATCH 090/677] sort imports according to PEP8 for axis (#29621) --- homeassistant/components/axis/device.py | 3 +-- tests/components/axis/test_binary_sensor.py | 3 +-- tests/components/axis/test_camera.py | 4 +--- tests/components/axis/test_config_flow.py | 2 +- tests/components/axis/test_device.py | 4 ++-- tests/components/axis/test_init.py | 4 ++-- tests/components/axis/test_switch.py | 5 ++--- 7 files changed, 10 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/axis/device.py b/homeassistant/components/axis/device.py index e42a758f3c4..b05c5b2fed0 100644 --- a/homeassistant/components/axis/device.py +++ b/homeassistant/components/axis/device.py @@ -1,8 +1,8 @@ """Axis network device abstraction.""" import asyncio -import async_timeout +import async_timeout import axis from axis.streammanager import SIGNAL_PLAYING @@ -21,7 +21,6 @@ from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.dispatcher import async_dispatcher_send from .const import CONF_CAMERA, CONF_EVENTS, CONF_MODEL, DOMAIN, LOGGER - from .errors import AuthenticationRequired, CannotConnect diff --git a/tests/components/axis/test_binary_sensor.py b/tests/components/axis/test_binary_sensor.py index 8e5a6f9675d..ca3e984c993 100644 --- a/tests/components/axis/test_binary_sensor.py +++ b/tests/components/axis/test_binary_sensor.py @@ -4,9 +4,8 @@ from unittest.mock import Mock from homeassistant import config_entries from homeassistant.components import axis -from homeassistant.setup import async_setup_component - import homeassistant.components.binary_sensor as binary_sensor +from homeassistant.setup import async_setup_component EVENTS = [ { diff --git a/tests/components/axis/test_camera.py b/tests/components/axis/test_camera.py index 027dc42748e..67ca7e3690a 100644 --- a/tests/components/axis/test_camera.py +++ b/tests/components/axis/test_camera.py @@ -4,10 +4,8 @@ from unittest.mock import Mock from homeassistant import config_entries from homeassistant.components import axis -from homeassistant.setup import async_setup_component - import homeassistant.components.camera as camera - +from homeassistant.setup import async_setup_component ENTRY_CONFIG = { axis.CONF_DEVICE: { diff --git a/tests/components/axis/test_config_flow.py b/tests/components/axis/test_config_flow.py index 5aec416961d..a29c270e0b8 100644 --- a/tests/components/axis/test_config_flow.py +++ b/tests/components/axis/test_config_flow.py @@ -4,7 +4,7 @@ from unittest.mock import Mock, patch from homeassistant.components import axis from homeassistant.components.axis import config_flow -from tests.common import mock_coro, MockConfigEntry +from tests.common import MockConfigEntry, mock_coro async def test_configured_devices(hass): diff --git a/tests/components/axis/test_device.py b/tests/components/axis/test_device.py index b15e9e68ada..a9f38cc4f3a 100644 --- a/tests/components/axis/test_device.py +++ b/tests/components/axis/test_device.py @@ -3,11 +3,11 @@ from unittest.mock import Mock, patch import pytest -from tests.common import mock_coro, MockConfigEntry - from homeassistant.components.axis import device, errors from homeassistant.components.axis.camera import AxisCamera +from tests.common import MockConfigEntry, mock_coro + DEVICE_DATA = { device.CONF_HOST: "1.2.3.4", device.CONF_USERNAME: "username", diff --git a/tests/components/axis/test_init.py b/tests/components/axis/test_init.py index b1317eb88e9..831d1d6ea08 100644 --- a/tests/components/axis/test_init.py +++ b/tests/components/axis/test_init.py @@ -1,10 +1,10 @@ """Test Axis component setup process.""" from unittest.mock import Mock, patch -from homeassistant.setup import async_setup_component from homeassistant.components import axis +from homeassistant.setup import async_setup_component -from tests.common import mock_coro, MockConfigEntry +from tests.common import MockConfigEntry, mock_coro async def test_setup(hass): diff --git a/tests/components/axis/test_switch.py b/tests/components/axis/test_switch.py index 3469106c436..406e3170ab2 100644 --- a/tests/components/axis/test_switch.py +++ b/tests/components/axis/test_switch.py @@ -1,12 +1,11 @@ """Axis switch platform tests.""" -from unittest.mock import call as mock_call, Mock +from unittest.mock import Mock, call as mock_call from homeassistant import config_entries from homeassistant.components import axis -from homeassistant.setup import async_setup_component - import homeassistant.components.switch as switch +from homeassistant.setup import async_setup_component EVENTS = [ { From 00dc7216093abd099b1e4a58a14876cc9ed2896e Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 16:33:22 +0100 Subject: [PATCH 091/677] sort imports according to PEP8 for hassio (#29634) --- homeassistant/components/hassio/__init__.py | 4 ++-- homeassistant/components/hassio/addon_panel.py | 2 +- homeassistant/components/hassio/auth.py | 6 +++--- homeassistant/components/hassio/discovery.py | 2 +- homeassistant/components/hassio/http.py | 2 +- homeassistant/components/hassio/ingress.py | 2 +- tests/components/hassio/conftest.py | 7 ++++--- tests/components/hassio/test_addon_panel.py | 2 +- tests/components/hassio/test_auth.py | 2 +- tests/components/hassio/test_discovery.py | 4 ++-- tests/components/hassio/test_init.py | 7 +++---- 11 files changed, 20 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index e0c0a57375a..dab0fadd922 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -10,9 +10,9 @@ from homeassistant.components.homeassistant import SERVICE_CHECK_CONFIG import homeassistant.config as conf_util from homeassistant.const import ( ATTR_NAME, + EVENT_CORE_CONFIG_UPDATE, SERVICE_HOMEASSISTANT_RESTART, SERVICE_HOMEASSISTANT_STOP, - EVENT_CORE_CONFIG_UPDATE, ) from homeassistant.core import DOMAIN as HASS_DOMAIN, callback from homeassistant.exceptions import HomeAssistantError @@ -20,8 +20,8 @@ import homeassistant.helpers.config_validation as cv from homeassistant.loader import bind_hass from homeassistant.util.dt import utcnow -from .auth import async_setup_auth_view from .addon_panel import async_setup_addon_panel +from .auth import async_setup_auth_view from .discovery import async_setup_discovery_view from .handler import HassIO, HassioAPIError from .http import HassIOView diff --git a/homeassistant/components/hassio/addon_panel.py b/homeassistant/components/hassio/addon_panel.py index b60c864a893..cb509cb19a1 100644 --- a/homeassistant/components/hassio/addon_panel.py +++ b/homeassistant/components/hassio/addon_panel.py @@ -7,7 +7,7 @@ from aiohttp import web from homeassistant.components.http import HomeAssistantView from homeassistant.helpers.typing import HomeAssistantType -from .const import ATTR_PANELS, ATTR_TITLE, ATTR_ICON, ATTR_ADMIN, ATTR_ENABLE +from .const import ATTR_ADMIN, ATTR_ENABLE, ATTR_ICON, ATTR_PANELS, ATTR_TITLE from .handler import HassioAPIError _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/hassio/auth.py b/homeassistant/components/hassio/auth.py index 19e4c63b5a5..800801b4350 100644 --- a/homeassistant/components/hassio/auth.py +++ b/homeassistant/components/hassio/auth.py @@ -1,18 +1,18 @@ """Implement the auth feature from Hass.io for Add-ons.""" +from ipaddress import ip_address import logging import os -from ipaddress import ip_address -import voluptuous as vol from aiohttp import web from aiohttp.web_exceptions import HTTPForbidden, HTTPNotFound +import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.http import HomeAssistantView from homeassistant.components.http.const import KEY_REAL_IP from homeassistant.components.http.data_validator import RequestDataValidator from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import HomeAssistantType from .const import ATTR_ADDON, ATTR_PASSWORD, ATTR_USERNAME diff --git a/homeassistant/components/hassio/discovery.py b/homeassistant/components/hassio/discovery.py index 55336735133..fc6efbe0e58 100644 --- a/homeassistant/components/hassio/discovery.py +++ b/homeassistant/components/hassio/discovery.py @@ -5,9 +5,9 @@ import logging from aiohttp import web from aiohttp.web_exceptions import HTTPServiceUnavailable +from homeassistant.components.http import HomeAssistantView from homeassistant.const import EVENT_HOMEASSISTANT_START from homeassistant.core import callback -from homeassistant.components.http import HomeAssistantView from .const import ( ATTR_ADDON, diff --git a/homeassistant/components/hassio/http.py b/homeassistant/components/hassio/http.py index 3b1b8374510..ddb9269219b 100644 --- a/homeassistant/components/hassio/http.py +++ b/homeassistant/components/hassio/http.py @@ -7,7 +7,7 @@ from typing import Dict, Union import aiohttp from aiohttp import web -from aiohttp.hdrs import CONTENT_TYPE, CONTENT_LENGTH +from aiohttp.hdrs import CONTENT_LENGTH, CONTENT_TYPE from aiohttp.web_exceptions import HTTPBadGateway import async_timeout diff --git a/homeassistant/components/hassio/ingress.py b/homeassistant/components/hassio/ingress.py index 53235f80dca..c69d2078468 100644 --- a/homeassistant/components/hassio/ingress.py +++ b/homeassistant/components/hassio/ingress.py @@ -1,8 +1,8 @@ """Hass.io Add-on ingress service.""" import asyncio +from ipaddress import ip_address import logging import os -from ipaddress import ip_address from typing import Dict, Union import aiohttp diff --git a/tests/components/hassio/conftest.py b/tests/components/hassio/conftest.py index 0e246cf1b46..d7ef853012e 100644 --- a/tests/components/hassio/conftest.py +++ b/tests/components/hassio/conftest.py @@ -1,15 +1,16 @@ """Fixtures for Hass.io.""" import os -from unittest.mock import patch, Mock +from unittest.mock import Mock, patch import pytest +from homeassistant.components.hassio.handler import HassIO, HassioAPIError from homeassistant.core import CoreState from homeassistant.setup import async_setup_component -from homeassistant.components.hassio.handler import HassIO, HassioAPIError + +from . import HASSIO_TOKEN from tests.common import mock_coro -from . import HASSIO_TOKEN @pytest.fixture diff --git a/tests/components/hassio/test_addon_panel.py b/tests/components/hassio/test_addon_panel.py index 480df508968..d2ad673111d 100644 --- a/tests/components/hassio/test_addon_panel.py +++ b/tests/components/hassio/test_addon_panel.py @@ -1,5 +1,5 @@ """Test add-on panel.""" -from unittest.mock import patch, Mock +from unittest.mock import Mock, patch import pytest diff --git a/tests/components/hassio/test_auth.py b/tests/components/hassio/test_auth.py index 1fb6d32ccf7..c7fe3459e41 100644 --- a/tests/components/hassio/test_auth.py +++ b/tests/components/hassio/test_auth.py @@ -1,5 +1,5 @@ """The tests for the hassio component.""" -from unittest.mock import patch, Mock +from unittest.mock import Mock, patch from homeassistant.exceptions import HomeAssistantError diff --git a/tests/components/hassio/test_discovery.py b/tests/components/hassio/test_discovery.py index a1b4ae2e900..2a2fdc4deaa 100644 --- a/tests/components/hassio/test_discovery.py +++ b/tests/components/hassio/test_discovery.py @@ -1,9 +1,9 @@ """Test config flow.""" -from unittest.mock import patch, Mock +from unittest.mock import Mock, patch -from homeassistant.setup import async_setup_component from homeassistant.components.hassio.handler import HassioAPIError from homeassistant.const import EVENT_HOMEASSISTANT_START +from homeassistant.setup import async_setup_component from tests.common import mock_coro diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py index 3d27931896f..c1e3d7ab2bf 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -1,18 +1,17 @@ """The tests for the hassio component.""" import asyncio import os -from unittest.mock import patch, Mock +from unittest.mock import Mock, patch import pytest from homeassistant.auth.const import GROUP_ID_ADMIN -from homeassistant.setup import async_setup_component -from homeassistant.components.hassio import STORAGE_KEY from homeassistant.components import frontend +from homeassistant.components.hassio import STORAGE_KEY +from homeassistant.setup import async_setup_component from tests.common import mock_coro - MOCK_ENVIRON = {"HASSIO": "127.0.0.1", "HASSIO_TOKEN": "abcdefgh"} From 57a3f7d5c8ad3dc71b6c29767bd625096767e7d5 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sun, 8 Dec 2019 16:53:34 +0100 Subject: [PATCH 092/677] Pass in parameters explicitly to DeconzSession (#29617) Dont pass in loop to DeconzSession Services will use new refresh state method --- homeassistant/components/deconz/const.py | 8 +-- homeassistant/components/deconz/gateway.py | 45 ++++++------ homeassistant/components/deconz/manifest.json | 2 +- homeassistant/components/deconz/services.py | 22 ++++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/deconz/test_binary_sensor.py | 13 +++- tests/components/deconz/test_climate.py | 72 ++++++++++++------- tests/components/deconz/test_cover.py | 27 ++++--- tests/components/deconz/test_gateway.py | 14 ++-- tests/components/deconz/test_init.py | 4 +- tests/components/deconz/test_light.py | 43 ++++++----- tests/components/deconz/test_scene.py | 6 +- tests/components/deconz/test_sensor.py | 23 ++++-- tests/components/deconz/test_services.py | 28 ++++---- tests/components/deconz/test_switch.py | 45 ++++++++---- 16 files changed, 219 insertions(+), 137 deletions(-) diff --git a/homeassistant/components/deconz/const.py b/homeassistant/components/deconz/const.py index ad23a564272..a663f99bf73 100644 --- a/homeassistant/components/deconz/const.py +++ b/homeassistant/components/deconz/const.py @@ -26,10 +26,10 @@ SUPPORTED_PLATFORMS = [ "switch", ] -NEW_GROUP = "group" -NEW_LIGHT = "light" -NEW_SCENE = "scene" -NEW_SENSOR = "sensor" +NEW_GROUP = "groups" +NEW_LIGHT = "lights" +NEW_SCENE = "scenes" +NEW_SENSOR = "sensors" NEW_DEVICE = { NEW_GROUP: "deconz_new_group_{}", diff --git a/homeassistant/components/deconz/gateway.py b/homeassistant/components/deconz/gateway.py index 0c77285a6fe..083af2dca6f 100644 --- a/homeassistant/components/deconz/gateway.py +++ b/homeassistant/components/deconz/gateway.py @@ -4,7 +4,7 @@ import asyncio import async_timeout from pydeconz import DeconzSession, errors -from homeassistant.const import CONF_HOST +from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT from homeassistant.core import callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import aiohttp_client @@ -42,13 +42,13 @@ def get_gateway_from_config_entry(hass, config_entry): class DeconzGateway: """Manages a single deCONZ gateway.""" - def __init__(self, hass, config_entry): + def __init__(self, hass, config_entry) -> None: """Initialize the system.""" self.hass = hass self.config_entry = config_entry + self.available = True self.api = None - self.deconz_ids = {} self.events = [] self.listeners = [] @@ -77,7 +77,7 @@ class DeconzGateway: CONF_ALLOW_DECONZ_GROUPS, DEFAULT_ALLOW_DECONZ_GROUPS ) - async def async_update_device_registry(self): + async def async_update_device_registry(self) -> None: """Update device registry.""" device_registry = await self.hass.helpers.device_registry.async_get_registry() device_registry.async_get_or_create( @@ -90,7 +90,7 @@ class DeconzGateway: sw_version=self.api.config.swversion, ) - async def async_setup(self): + async def async_setup(self) -> bool: """Set up a deCONZ gateway.""" hass = self.hass @@ -105,8 +105,8 @@ class DeconzGateway: except CannotConnect: raise ConfigEntryNotReady - except Exception: # pylint: disable=broad-except - _LOGGER.error("Error connecting with deCONZ gateway") + except Exception as err: # pylint: disable=broad-except + _LOGGER.error("Error connecting with deCONZ gateway: %s", err) return False for component in SUPPORTED_PLATFORMS: @@ -124,7 +124,7 @@ class DeconzGateway: return True @staticmethod - async def async_new_address(hass, entry): + async def async_new_address(hass, entry) -> None: """Handle signals of gateway getting new address. This is a static method because a class method (bound method), @@ -137,23 +137,23 @@ class DeconzGateway: gateway.api.start() @property - def signal_reachable(self): + def signal_reachable(self) -> str: """Gateway specific event to signal a change in connection status.""" return f"deconz-reachable-{self.bridgeid}" @callback - def async_connection_status_callback(self, available): + def async_connection_status_callback(self, available) -> None: """Handle signals of gateway connection status.""" self.available = available async_dispatcher_send(self.hass, self.signal_reachable, True) @property - def signal_options_update(self): + def signal_options_update(self) -> str: """Event specific per deCONZ entry to signal new options.""" return f"deconz-options-{self.bridgeid}" @staticmethod - async def async_options_updated(hass, entry): + async def async_options_updated(hass, entry) -> None: """Triggered by config entry options updates.""" gateway = get_gateway_from_config_entry(hass, entry) @@ -161,12 +161,12 @@ class DeconzGateway: async_dispatcher_send(hass, gateway.signal_options_update, registry) @callback - def async_signal_new_device(self, device_type): + def async_signal_new_device(self, device_type) -> str: """Gateway specific event to signal new device.""" return NEW_DEVICE[device_type].format(self.bridgeid) @callback - def async_add_device_callback(self, device_type, device): + def async_add_device_callback(self, device_type, device) -> None: """Handle event of new device creation in deCONZ.""" if not isinstance(device, list): device = [device] @@ -175,7 +175,7 @@ class DeconzGateway: ) @callback - def shutdown(self, event): + def shutdown(self, event) -> None: """Wrap the call to deconz.close. Used as an argument to EventBus.async_listen_once. @@ -206,20 +206,21 @@ class DeconzGateway: async def get_gateway( hass, config, async_add_device_callback, async_connection_status_callback -): +) -> DeconzSession: """Create a gateway object and verify configuration.""" session = aiohttp_client.async_get_clientsession(hass) deconz = DeconzSession( - hass.loop, session, - **config, + config[CONF_HOST], + config[CONF_PORT], + config[CONF_API_KEY], async_add_device=async_add_device_callback, connection_status=async_connection_status_callback, ) try: with async_timeout.timeout(10): - await deconz.async_load_parameters() + await deconz.initialize() return deconz except errors.Unauthorized: @@ -234,7 +235,7 @@ async def get_gateway( class DeconzEntityHandler: """Platform entity handler to help with updating disabled by.""" - def __init__(self, gateway): + def __init__(self, gateway) -> None: """Create an entity handler.""" self.gateway = gateway self._entities = [] @@ -246,12 +247,12 @@ class DeconzEntityHandler: ) @callback - def add_entity(self, entity): + def add_entity(self, entity) -> None: """Add a new entity to handler.""" self._entities.append(entity) @callback - def update_entity_registry(self, entity_registry): + def update_entity_registry(self, entity_registry) -> None: """Update entity registry disabled by status.""" for entity in self._entities: diff --git a/homeassistant/components/deconz/manifest.json b/homeassistant/components/deconz/manifest.json index 64902002600..30b00600331 100644 --- a/homeassistant/components/deconz/manifest.json +++ b/homeassistant/components/deconz/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/deconz", "requirements": [ - "pydeconz==64" + "pydeconz==65" ], "ssdp": [ { diff --git a/homeassistant/components/deconz/services.py b/homeassistant/components/deconz/services.py index 8efdc2718bc..9d133acdb1d 100644 --- a/homeassistant/components/deconz/services.py +++ b/homeassistant/components/deconz/services.py @@ -4,7 +4,15 @@ import voluptuous as vol from homeassistant.helpers import config_validation as cv from .config_flow import get_master_gateway -from .const import _LOGGER, CONF_BRIDGEID, DOMAIN +from .const import ( + _LOGGER, + CONF_BRIDGEID, + DOMAIN, + NEW_GROUP, + NEW_LIGHT, + NEW_SCENE, + NEW_SENSOR, +) DECONZ_SERVICES = "deconz_services" @@ -105,7 +113,7 @@ async def async_configure_service(hass, data): _LOGGER.error("Could not find the entity %s", entity_id) return - await gateway.api.async_put_state(field, data) + await gateway.api.request("put", field, json=data) async def async_refresh_devices_service(hass, data): @@ -119,10 +127,10 @@ async def async_refresh_devices_service(hass, data): scenes = set(gateway.api.scenes.keys()) sensors = set(gateway.api.sensors.keys()) - await gateway.api.async_load_parameters() + await gateway.api.refresh_state() gateway.async_add_device_callback( - "group", + NEW_GROUP, [ group for group_id, group in gateway.api.groups.items() @@ -131,7 +139,7 @@ async def async_refresh_devices_service(hass, data): ) gateway.async_add_device_callback( - "light", + NEW_LIGHT, [ light for light_id, light in gateway.api.lights.items() @@ -140,7 +148,7 @@ async def async_refresh_devices_service(hass, data): ) gateway.async_add_device_callback( - "scene", + NEW_SCENE, [ scene for scene_id, scene in gateway.api.scenes.items() @@ -149,7 +157,7 @@ async def async_refresh_devices_service(hass, data): ) gateway.async_add_device_callback( - "sensor", + NEW_SENSOR, [ sensor for sensor_id, sensor in gateway.api.sensors.items() diff --git a/requirements_all.txt b/requirements_all.txt index a50325ffd19..360898df055 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1180,7 +1180,7 @@ pydaikin==1.6.1 pydanfossair==0.1.0 # homeassistant.components.deconz -pydeconz==64 +pydeconz==65 # homeassistant.components.delijn pydelijn==0.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5fe6c4f6143..fff9af5b32d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -406,7 +406,7 @@ pycoolmasternet==0.0.4 pydaikin==1.6.1 # homeassistant.components.deconz -pydeconz==64 +pydeconz==65 # homeassistant.components.zwave pydispatcher==2.0.5 diff --git a/tests/components/deconz/test_binary_sensor.py b/tests/components/deconz/test_binary_sensor.py index 2f42193291c..c340018b641 100644 --- a/tests/components/deconz/test_binary_sensor.py +++ b/tests/components/deconz/test_binary_sensor.py @@ -95,7 +95,14 @@ async def test_binary_sensors(hass): vibration_sensor = hass.states.get("binary_sensor.vibration_sensor") assert vibration_sensor.state == "on" - gateway.api.sensors["1"].async_update({"state": {"presence": True}}) + state_changed_event = { + "t": "event", + "e": "changed", + "r": "sensors", + "id": "1", + "state": {"presence": True}, + } + gateway.api.async_event_handler(state_changed_event) await hass.async_block_till_done() presence_sensor = hass.states.get("binary_sensor.presence_sensor") @@ -143,14 +150,14 @@ async def test_add_new_binary_sensor(hass): ) assert len(gateway.deconz_ids) == 0 - state_added = { + state_added_event = { "t": "event", "e": "added", "r": "sensors", "id": "1", "sensor": deepcopy(SENSORS["1"]), } - gateway.api.async_event_handler(state_added) + gateway.api.async_event_handler(state_added_event) await hass.async_block_till_done() assert "binary_sensor.presence_sensor" in gateway.deconz_ids diff --git a/tests/components/deconz/test_climate.py b/tests/components/deconz/test_climate.py index cee91f00c42..9536929ff3c 100644 --- a/tests/components/deconz/test_climate.py +++ b/tests/components/deconz/test_climate.py @@ -94,21 +94,41 @@ async def test_climate_devices(hass): clip_thermostat = hass.states.get("climate.clip_thermostat") assert clip_thermostat is None - thermostat_device = gateway.api.sensors["1"] - - thermostat_device.async_update({"config": {"mode": "off"}}) + state_changed_event = { + "t": "event", + "e": "changed", + "r": "sensors", + "id": "1", + "config": {"mode": "off"}, + } + gateway.api.async_event_handler(state_changed_event) await hass.async_block_till_done() thermostat = hass.states.get("climate.thermostat") assert thermostat.state == "off" - thermostat_device.async_update({"config": {"mode": "other"}, "state": {"on": True}}) + state_changed_event = { + "t": "event", + "e": "changed", + "r": "sensors", + "id": "1", + "config": {"mode": "other"}, + "state": {"on": True}, + } + gateway.api.async_event_handler(state_changed_event) await hass.async_block_till_done() thermostat = hass.states.get("climate.thermostat") assert thermostat.state == "heat" - thermostat_device.async_update({"state": {"on": False}}) + state_changed_event = { + "t": "event", + "e": "changed", + "r": "sensors", + "id": "1", + "state": {"on": False}, + } + gateway.api.async_event_handler(state_changed_event) await hass.async_block_till_done() thermostat = hass.states.get("climate.thermostat") @@ -116,9 +136,9 @@ async def test_climate_devices(hass): # Verify service calls - with patch.object( - thermostat_device, "_async_set_callback", return_value=True - ) as set_callback: + thermostat_device = gateway.api.sensors["1"] + + with patch.object(thermostat_device, "_request", return_value=True) as set_callback: await hass.services.async_call( climate.DOMAIN, climate.SERVICE_SET_HVAC_MODE, @@ -126,11 +146,11 @@ async def test_climate_devices(hass): blocking=True, ) await hass.async_block_till_done() - set_callback.assert_called_with("/sensors/1/config", {"mode": "auto"}) + set_callback.assert_called_with( + "put", "/sensors/1/config", json={"mode": "auto"} + ) - with patch.object( - thermostat_device, "_async_set_callback", return_value=True - ) as set_callback: + with patch.object(thermostat_device, "_request", return_value=True) as set_callback: await hass.services.async_call( climate.DOMAIN, climate.SERVICE_SET_HVAC_MODE, @@ -138,29 +158,31 @@ async def test_climate_devices(hass): blocking=True, ) await hass.async_block_till_done() - set_callback.assert_called_with("/sensors/1/config", {"mode": "heat"}) + set_callback.assert_called_with( + "put", "/sensors/1/config", json={"mode": "heat"} + ) - with patch.object( - thermostat_device, "_async_set_callback", return_value=True - ) as set_callback: + with patch.object(thermostat_device, "_request", return_value=True) as set_callback: await hass.services.async_call( climate.DOMAIN, climate.SERVICE_SET_HVAC_MODE, {"entity_id": "climate.thermostat", "hvac_mode": "off"}, blocking=True, ) - set_callback.assert_called_with("/sensors/1/config", {"mode": "off"}) + set_callback.assert_called_with( + "put", "/sensors/1/config", json={"mode": "off"} + ) - with patch.object( - thermostat_device, "_async_set_callback", return_value=True - ) as set_callback: + with patch.object(thermostat_device, "_request", return_value=True) as set_callback: await hass.services.async_call( climate.DOMAIN, climate.SERVICE_SET_TEMPERATURE, {"entity_id": "climate.thermostat", "temperature": 20}, blocking=True, ) - set_callback.assert_called_with("/sensors/1/config", {"heatsetpoint": 2000.0}) + set_callback.assert_called_with( + "put", "/sensors/1/config", json={"heatsetpoint": 2000.0} + ) await gateway.async_reset() @@ -212,14 +234,14 @@ async def test_verify_state_update(hass): thermostat = hass.states.get("climate.thermostat") assert thermostat.state == "auto" - state_update = { + state_changed_event = { "t": "event", "e": "changed", "r": "sensors", "id": "1", "state": {"on": False}, } - gateway.api.async_event_handler(state_update) + gateway.api.async_event_handler(state_changed_event) await hass.async_block_till_done() thermostat = hass.states.get("climate.thermostat") @@ -235,14 +257,14 @@ async def test_add_new_climate_device(hass): ) assert len(gateway.deconz_ids) == 0 - state_added = { + state_added_event = { "t": "event", "e": "added", "r": "sensors", "id": "1", "sensor": deepcopy(SENSORS["1"]), } - gateway.api.async_event_handler(state_added) + gateway.api.async_event_handler(state_added_event) await hass.async_block_till_done() assert "climate.thermostat" in gateway.deconz_ids diff --git a/tests/components/deconz/test_cover.py b/tests/components/deconz/test_cover.py index 5c7ee48a78a..31819888404 100644 --- a/tests/components/deconz/test_cover.py +++ b/tests/components/deconz/test_cover.py @@ -73,16 +73,23 @@ async def test_cover(hass): level_controllable_cover = hass.states.get("cover.level_controllable_cover") assert level_controllable_cover.state == "open" - level_controllable_cover_device = gateway.api.lights["1"] - - level_controllable_cover_device.async_update({"state": {"on": True}}) + state_changed_event = { + "t": "event", + "e": "changed", + "r": "lights", + "id": "1", + "state": {"on": True}, + } + gateway.api.async_event_handler(state_changed_event) await hass.async_block_till_done() level_controllable_cover = hass.states.get("cover.level_controllable_cover") assert level_controllable_cover.state == "closed" + level_controllable_cover_device = gateway.api.lights["1"] + with patch.object( - level_controllable_cover_device, "_async_set_callback", return_value=True + level_controllable_cover_device, "_request", return_value=True ) as set_callback: await hass.services.async_call( cover.DOMAIN, @@ -91,10 +98,10 @@ async def test_cover(hass): blocking=True, ) await hass.async_block_till_done() - set_callback.assert_called_with("/lights/1/state", {"on": False}) + set_callback.assert_called_with("put", "/lights/1/state", json={"on": False}) with patch.object( - level_controllable_cover_device, "_async_set_callback", return_value=True + level_controllable_cover_device, "_request", return_value=True ) as set_callback: await hass.services.async_call( cover.DOMAIN, @@ -103,10 +110,12 @@ async def test_cover(hass): blocking=True, ) await hass.async_block_till_done() - set_callback.assert_called_with("/lights/1/state", {"on": True, "bri": 255}) + set_callback.assert_called_with( + "put", "/lights/1/state", json={"on": True, "bri": 255} + ) with patch.object( - level_controllable_cover_device, "_async_set_callback", return_value=True + level_controllable_cover_device, "_request", return_value=True ) as set_callback: await hass.services.async_call( cover.DOMAIN, @@ -115,7 +124,7 @@ async def test_cover(hass): blocking=True, ) await hass.async_block_till_done() - set_callback.assert_called_with("/lights/1/state", {"bri_inc": 0}) + set_callback.assert_called_with("put", "/lights/1/state", json={"bri_inc": 0}) await gateway.async_reset() diff --git a/tests/components/deconz/test_gateway.py b/tests/components/deconz/test_gateway.py index b98681b6fc9..f9290cc0e05 100644 --- a/tests/components/deconz/test_gateway.py +++ b/tests/components/deconz/test_gateway.py @@ -51,8 +51,12 @@ async def setup_deconz_integration(hass, config, options, get_state_response): entry_id="1", ) + for resource in ("groups", "lights", "sensors"): + if resource not in get_state_response: + get_state_response[resource] = {} + with patch( - "pydeconz.DeconzSession.async_get_state", return_value=get_state_response + "pydeconz.DeconzSession.request", return_value=get_state_response ), patch("pydeconz.DeconzSession.start", return_value=True): await deconz.async_setup_entry(hass, config_entry) await hass.async_block_till_done() @@ -172,15 +176,14 @@ async def test_reset_after_successful_setup(hass): async def test_get_gateway(hass): """Successful call.""" - with patch("pydeconz.DeconzSession.async_load_parameters", return_value=True): + with patch("pydeconz.DeconzSession.initialize", return_value=True): assert await deconz.gateway.get_gateway(hass, ENTRY_CONFIG, Mock(), Mock()) async def test_get_gateway_fails_unauthorized(hass): """Failed call.""" with patch( - "pydeconz.DeconzSession.async_load_parameters", - side_effect=pydeconz.errors.Unauthorized, + "pydeconz.DeconzSession.initialize", side_effect=pydeconz.errors.Unauthorized, ), pytest.raises(deconz.errors.AuthenticationRequired): assert ( await deconz.gateway.get_gateway(hass, ENTRY_CONFIG, Mock(), Mock()) @@ -191,8 +194,7 @@ async def test_get_gateway_fails_unauthorized(hass): async def test_get_gateway_fails_cannot_connect(hass): """Failed call.""" with patch( - "pydeconz.DeconzSession.async_load_parameters", - side_effect=pydeconz.errors.RequestError, + "pydeconz.DeconzSession.initialize", side_effect=pydeconz.errors.RequestError, ), pytest.raises(deconz.errors.CannotConnect): assert ( await deconz.gateway.get_gateway(hass, ENTRY_CONFIG, Mock(), Mock()) diff --git a/tests/components/deconz/test_init.py b/tests/components/deconz/test_init.py index 986e01a1599..d21859197be 100644 --- a/tests/components/deconz/test_init.py +++ b/tests/components/deconz/test_init.py @@ -41,7 +41,7 @@ async def test_setup_entry_fails(hass): deconz.config_flow.CONF_PORT: ENTRY1_PORT, deconz.config_flow.CONF_API_KEY: ENTRY1_API_KEY, } - with patch("pydeconz.DeconzSession.async_load_parameters", side_effect=Exception): + with patch("pydeconz.DeconzSession.initialize", side_effect=Exception): await deconz.async_setup_entry(hass, entry) @@ -54,7 +54,7 @@ async def test_setup_entry_no_available_bridge(hass): deconz.config_flow.CONF_API_KEY: ENTRY1_API_KEY, } with patch( - "pydeconz.DeconzSession.async_load_parameters", side_effect=asyncio.TimeoutError + "pydeconz.DeconzSession.initialize", side_effect=asyncio.TimeoutError ), pytest.raises(ConfigEntryNotReady): await deconz.async_setup_entry(hass, entry) diff --git a/tests/components/deconz/test_light.py b/tests/components/deconz/test_light.py index 14dc5cc8eac..438b2209b1d 100644 --- a/tests/components/deconz/test_light.py +++ b/tests/components/deconz/test_light.py @@ -117,17 +117,22 @@ async def test_lights_and_groups(hass): empty_group = hass.states.get("light.empty_group") assert empty_group is None - rgb_light_device = gateway.api.lights["1"] - - rgb_light_device.async_update({"state": {"on": False}}) + state_changed_event = { + "t": "event", + "e": "changed", + "r": "lights", + "id": "1", + "state": {"on": False}, + } + gateway.api.async_event_handler(state_changed_event) await hass.async_block_till_done() rgb_light = hass.states.get("light.rgb_light") assert rgb_light.state == "off" - with patch.object( - rgb_light_device, "_async_set_callback", return_value=True - ) as set_callback: + rgb_light_device = gateway.api.lights["1"] + + with patch.object(rgb_light_device, "_request", return_value=True) as set_callback: await hass.services.async_call( light.DOMAIN, light.SERVICE_TURN_ON, @@ -143,8 +148,9 @@ async def test_lights_and_groups(hass): ) await hass.async_block_till_done() set_callback.assert_called_with( + "put", "/lights/1/state", - { + json={ "ct": 2500, "bri": 200, "transitiontime": 50, @@ -153,9 +159,7 @@ async def test_lights_and_groups(hass): }, ) - with patch.object( - rgb_light_device, "_async_set_callback", return_value=True - ) as set_callback: + with patch.object(rgb_light_device, "_request", return_value=True) as set_callback: await hass.services.async_call( light.DOMAIN, light.SERVICE_TURN_ON, @@ -169,13 +173,12 @@ async def test_lights_and_groups(hass): ) await hass.async_block_till_done() set_callback.assert_called_with( + "put", "/lights/1/state", - {"xy": (0.411, 0.351), "alert": "lselect", "effect": "none"}, + json={"xy": (0.411, 0.351), "alert": "lselect", "effect": "none"}, ) - with patch.object( - rgb_light_device, "_async_set_callback", return_value=True - ) as set_callback: + with patch.object(rgb_light_device, "_request", return_value=True) as set_callback: await hass.services.async_call( light.DOMAIN, light.SERVICE_TURN_OFF, @@ -184,12 +187,12 @@ async def test_lights_and_groups(hass): ) await hass.async_block_till_done() set_callback.assert_called_with( - "/lights/1/state", {"bri": 0, "transitiontime": 50, "alert": "select"} + "put", + "/lights/1/state", + json={"bri": 0, "transitiontime": 50, "alert": "select"}, ) - with patch.object( - rgb_light_device, "_async_set_callback", return_value=True - ) as set_callback: + with patch.object(rgb_light_device, "_request", return_value=True) as set_callback: await hass.services.async_call( light.DOMAIN, light.SERVICE_TURN_OFF, @@ -197,7 +200,9 @@ async def test_lights_and_groups(hass): blocking=True, ) await hass.async_block_till_done() - set_callback.assert_called_with("/lights/1/state", {"alert": "lselect"}) + set_callback.assert_called_with( + "put", "/lights/1/state", json={"alert": "lselect"} + ) await gateway.async_reset() diff --git a/tests/components/deconz/test_scene.py b/tests/components/deconz/test_scene.py index dcc8ba500c3..ca7493a41f1 100644 --- a/tests/components/deconz/test_scene.py +++ b/tests/components/deconz/test_scene.py @@ -60,14 +60,12 @@ async def test_scenes(hass): group_scene = gateway.api.groups["1"].scenes["1"] - with patch.object( - group_scene, "_async_set_state_callback", return_value=True - ) as set_callback: + with patch.object(group_scene, "_request", return_value=True) as set_callback: await hass.services.async_call( "scene", "turn_on", {"entity_id": "scene.light_group_scene"}, blocking=True ) await hass.async_block_till_done() - set_callback.assert_called_with("/groups/1/scenes/1/recall", {}) + set_callback.assert_called_with("put", "/groups/1/scenes/1/recall", json={}) await gateway.async_reset() diff --git a/tests/components/deconz/test_sensor.py b/tests/components/deconz/test_sensor.py index 7b6ae41086b..b678ee13644 100644 --- a/tests/components/deconz/test_sensor.py +++ b/tests/components/deconz/test_sensor.py @@ -143,8 +143,23 @@ async def test_sensors(hass): consumption_sensor = hass.states.get("sensor.consumption_sensor") assert consumption_sensor.state == "0.002" - gateway.api.sensors["1"].async_update({"state": {"lightlevel": 2000}}) - gateway.api.sensors["4"].async_update({"config": {"battery": 75}}) + state_changed_event = { + "t": "event", + "e": "changed", + "r": "sensors", + "id": "1", + "state": {"lightlevel": 2000}, + } + gateway.api.async_event_handler(state_changed_event) + + state_changed_event = { + "t": "event", + "e": "changed", + "r": "sensors", + "id": "4", + "config": {"battery": 75}, + } + gateway.api.async_event_handler(state_changed_event) await hass.async_block_till_done() light_level_sensor = hass.states.get("sensor.light_level_sensor") @@ -219,14 +234,14 @@ async def test_add_new_sensor(hass): ) assert len(gateway.deconz_ids) == 0 - state_added = { + state_added_event = { "t": "event", "e": "added", "r": "sensors", "id": "1", "sensor": deepcopy(SENSORS["1"]), } - gateway.api.async_event_handler(state_added) + gateway.api.async_event_handler(state_added_event) await hass.async_block_till_done() assert "sensor.light_level_sensor" in gateway.deconz_ids diff --git a/tests/components/deconz/test_services.py b/tests/components/deconz/test_services.py index 533d85eef7c..fae0ed41d93 100644 --- a/tests/components/deconz/test_services.py +++ b/tests/components/deconz/test_services.py @@ -104,14 +104,14 @@ async def test_configure_service_with_field(hass): deconz.services.SERVICE_DATA: {"on": True, "attr1": 10, "attr2": 20}, } - with patch( - "pydeconz.DeconzSession.async_put_state", return_value=Mock(True) - ) as put_state: + with patch("pydeconz.DeconzSession.request", return_value=Mock(True)) as put_state: await hass.services.async_call( deconz.DOMAIN, deconz.services.SERVICE_CONFIGURE_DEVICE, service_data=data ) await hass.async_block_till_done() - put_state.assert_called_with("/light/2", {"on": True, "attr1": 10, "attr2": 20}) + put_state.assert_called_with( + "put", "/light/2", json={"on": True, "attr1": 10, "attr2": 20} + ) async def test_configure_service_with_entity(hass): @@ -127,14 +127,14 @@ async def test_configure_service_with_entity(hass): deconz.services.SERVICE_DATA: {"on": True, "attr1": 10, "attr2": 20}, } - with patch( - "pydeconz.DeconzSession.async_put_state", return_value=Mock(True) - ) as put_state: + with patch("pydeconz.DeconzSession.request", return_value=Mock(True)) as put_state: await hass.services.async_call( deconz.DOMAIN, deconz.services.SERVICE_CONFIGURE_DEVICE, service_data=data ) await hass.async_block_till_done() - put_state.assert_called_with("/light/1", {"on": True, "attr1": 10, "attr2": 20}) + put_state.assert_called_with( + "put", "/light/1", json={"on": True, "attr1": 10, "attr2": 20} + ) async def test_configure_service_with_entity_and_field(hass): @@ -151,15 +151,13 @@ async def test_configure_service_with_entity_and_field(hass): deconz.services.SERVICE_DATA: {"on": True, "attr1": 10, "attr2": 20}, } - with patch( - "pydeconz.DeconzSession.async_put_state", return_value=Mock(True) - ) as put_state: + with patch("pydeconz.DeconzSession.request", return_value=Mock(True)) as put_state: await hass.services.async_call( deconz.DOMAIN, deconz.services.SERVICE_CONFIGURE_DEVICE, service_data=data ) await hass.async_block_till_done() put_state.assert_called_with( - "/light/1/state", {"on": True, "attr1": 10, "attr2": 20} + "put", "/light/1/state", json={"on": True, "attr1": 10, "attr2": 20} ) @@ -191,9 +189,7 @@ async def test_configure_service_with_faulty_entity(hass): deconz.services.SERVICE_DATA: {}, } - with patch( - "pydeconz.DeconzSession.async_put_state", return_value=Mock(True) - ) as put_state: + with patch("pydeconz.DeconzSession.request", return_value=Mock(True)) as put_state: await hass.services.async_call( deconz.DOMAIN, deconz.services.SERVICE_CONFIGURE_DEVICE, service_data=data ) @@ -211,7 +207,7 @@ async def test_service_refresh_devices(hass): data = {deconz.CONF_BRIDGEID: BRIDGEID} with patch( - "pydeconz.DeconzSession.async_get_state", + "pydeconz.DeconzSession.request", return_value={"groups": GROUP, "lights": LIGHT, "sensors": SENSOR}, ): await hass.services.async_call( diff --git a/tests/components/deconz/test_switch.py b/tests/components/deconz/test_switch.py index 262bd7001f5..0c66ace5640 100644 --- a/tests/components/deconz/test_switch.py +++ b/tests/components/deconz/test_switch.py @@ -85,11 +85,22 @@ async def test_switches(hass): warning_device = hass.states.get("switch.warning_device") assert warning_device.state == "on" - on_off_switch_device = gateway.api.lights["1"] - warning_device_device = gateway.api.lights["3"] - - on_off_switch_device.async_update({"state": {"on": False}}) - warning_device_device.async_update({"state": {"alert": None}}) + state_changed_event = { + "t": "event", + "e": "changed", + "r": "lights", + "id": "1", + "state": {"on": False}, + } + gateway.api.async_event_handler(state_changed_event) + state_changed_event = { + "t": "event", + "e": "changed", + "r": "lights", + "id": "3", + "state": {"alert": None}, + } + gateway.api.async_event_handler(state_changed_event) await hass.async_block_till_done() on_off_switch = hass.states.get("switch.on_off_switch") @@ -98,8 +109,10 @@ async def test_switches(hass): warning_device = hass.states.get("switch.warning_device") assert warning_device.state == "off" + on_off_switch_device = gateway.api.lights["1"] + with patch.object( - on_off_switch_device, "_async_set_callback", return_value=True + on_off_switch_device, "_request", return_value=True ) as set_callback: await hass.services.async_call( switch.DOMAIN, @@ -108,10 +121,10 @@ async def test_switches(hass): blocking=True, ) await hass.async_block_till_done() - set_callback.assert_called_with("/lights/1/state", {"on": True}) + set_callback.assert_called_with("put", "/lights/1/state", json={"on": True}) with patch.object( - on_off_switch_device, "_async_set_callback", return_value=True + on_off_switch_device, "_request", return_value=True ) as set_callback: await hass.services.async_call( switch.DOMAIN, @@ -120,10 +133,12 @@ async def test_switches(hass): blocking=True, ) await hass.async_block_till_done() - set_callback.assert_called_with("/lights/1/state", {"on": False}) + set_callback.assert_called_with("put", "/lights/1/state", json={"on": False}) + + warning_device_device = gateway.api.lights["3"] with patch.object( - warning_device_device, "_async_set_callback", return_value=True + warning_device_device, "_request", return_value=True ) as set_callback: await hass.services.async_call( switch.DOMAIN, @@ -132,10 +147,12 @@ async def test_switches(hass): blocking=True, ) await hass.async_block_till_done() - set_callback.assert_called_with("/lights/3/state", {"alert": "lselect"}) + set_callback.assert_called_with( + "put", "/lights/3/state", json={"alert": "lselect"} + ) with patch.object( - warning_device_device, "_async_set_callback", return_value=True + warning_device_device, "_request", return_value=True ) as set_callback: await hass.services.async_call( switch.DOMAIN, @@ -144,7 +161,9 @@ async def test_switches(hass): blocking=True, ) await hass.async_block_till_done() - set_callback.assert_called_with("/lights/3/state", {"alert": "none"}) + set_callback.assert_called_with( + "put", "/lights/3/state", json={"alert": "none"} + ) await gateway.async_reset() From a38f3ac9c650ca8050ffdc51d974d3135a330ff0 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 16:58:47 +0100 Subject: [PATCH 093/677] use isort to sort imports according to PEP8 for fan (#29632) --- homeassistant/components/fan/__init__.py | 10 +++++----- homeassistant/components/fan/device_action.py | 12 +++++++----- homeassistant/components/fan/device_condition.py | 8 +++++--- homeassistant/components/fan/device_trigger.py | 16 +++++++++------- homeassistant/components/fan/reproduce_state.py | 6 +++--- tests/components/fan/common.py | 6 +++--- tests/components/fan/test_device_action.py | 6 +++--- tests/components/fan/test_device_condition.py | 8 ++++---- tests/components/fan/test_device_trigger.py | 8 ++++---- tests/components/fan/test_init.py | 3 ++- 10 files changed, 45 insertions(+), 38 deletions(-) diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index 51aecc3e7c2..c3111a3aeb7 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -7,15 +7,15 @@ from typing import Optional import voluptuous as vol from homeassistant.components import group -from homeassistant.const import SERVICE_TURN_ON, SERVICE_TOGGLE, SERVICE_TURN_OFF -from homeassistant.loader import bind_hass -from homeassistant.helpers.entity import ToggleEntity -from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.const import SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.config_validation import ( # noqa: F401 PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE, ) -import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import ToggleEntity +from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.loader import bind_hass _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/fan/device_action.py b/homeassistant/components/fan/device_action.py index b26f632a775..a5d35d741b6 100644 --- a/homeassistant/components/fan/device_action.py +++ b/homeassistant/components/fan/device_action.py @@ -1,19 +1,21 @@ """Provides device automations for Fan.""" -from typing import Optional, List +from typing import List, Optional + import voluptuous as vol from homeassistant.const import ( ATTR_ENTITY_ID, - CONF_DOMAIN, - CONF_TYPE, CONF_DEVICE_ID, + CONF_DOMAIN, CONF_ENTITY_ID, - SERVICE_TURN_ON, + CONF_TYPE, SERVICE_TURN_OFF, + SERVICE_TURN_ON, ) -from homeassistant.core import HomeAssistant, Context +from homeassistant.core import Context, HomeAssistant from homeassistant.helpers import entity_registry import homeassistant.helpers.config_validation as cv + from . import DOMAIN ACTION_TYPES = {"turn_on", "turn_off"} diff --git a/homeassistant/components/fan/device_condition.py b/homeassistant/components/fan/device_condition.py index 8b567fcd4c9..c69f28c10e9 100644 --- a/homeassistant/components/fan/device_condition.py +++ b/homeassistant/components/fan/device_condition.py @@ -1,21 +1,23 @@ """Provide the device automations for Fan.""" from typing import Dict, List + import voluptuous as vol from homeassistant.const import ( ATTR_ENTITY_ID, CONF_CONDITION, - CONF_DOMAIN, - CONF_TYPE, CONF_DEVICE_ID, + CONF_DOMAIN, CONF_ENTITY_ID, + CONF_TYPE, STATE_OFF, STATE_ON, ) from homeassistant.core import HomeAssistant from homeassistant.helpers import condition, config_validation as cv, entity_registry -from homeassistant.helpers.typing import ConfigType, TemplateVarsType from homeassistant.helpers.config_validation import DEVICE_CONDITION_BASE_SCHEMA +from homeassistant.helpers.typing import ConfigType, TemplateVarsType + from . import DOMAIN CONDITION_TYPES = {"is_on", "is_off"} diff --git a/homeassistant/components/fan/device_trigger.py b/homeassistant/components/fan/device_trigger.py index 3e917e0ae79..3bfeb5ee36b 100644 --- a/homeassistant/components/fan/device_trigger.py +++ b/homeassistant/components/fan/device_trigger.py @@ -1,21 +1,23 @@ """Provides device automations for Fan.""" from typing import List + import voluptuous as vol +from homeassistant.components.automation import AutomationActionType, state +from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA from homeassistant.const import ( - CONF_DOMAIN, - CONF_TYPE, - CONF_PLATFORM, CONF_DEVICE_ID, + CONF_DOMAIN, CONF_ENTITY_ID, - STATE_ON, + CONF_PLATFORM, + CONF_TYPE, STATE_OFF, + STATE_ON, ) -from homeassistant.core import HomeAssistant, CALLBACK_TYPE +from homeassistant.core import CALLBACK_TYPE, HomeAssistant from homeassistant.helpers import config_validation as cv, entity_registry from homeassistant.helpers.typing import ConfigType -from homeassistant.components.automation import state, AutomationActionType -from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA + from . import DOMAIN TRIGGER_TYPES = {"turned_on", "turned_off"} diff --git a/homeassistant/components/fan/reproduce_state.py b/homeassistant/components/fan/reproduce_state.py index 1053861e2bf..2692ac7ee5c 100644 --- a/homeassistant/components/fan/reproduce_state.py +++ b/homeassistant/components/fan/reproduce_state.py @@ -6,19 +6,19 @@ from typing import Iterable, Optional from homeassistant.const import ( ATTR_ENTITY_ID, - STATE_ON, - STATE_OFF, SERVICE_TURN_OFF, SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, ) from homeassistant.core import Context, State from homeassistant.helpers.typing import HomeAssistantType from . import ( - DOMAIN, ATTR_DIRECTION, ATTR_OSCILLATING, ATTR_SPEED, + DOMAIN, SERVICE_OSCILLATE, SERVICE_SET_DIRECTION, SERVICE_SET_SPEED, diff --git a/tests/components/fan/common.py b/tests/components/fan/common.py index de645dac42e..70a2c7e43d3 100644 --- a/tests/components/fan/common.py +++ b/tests/components/fan/common.py @@ -5,8 +5,8 @@ components. Instead call the service directly. """ from homeassistant.components.fan import ( ATTR_DIRECTION, - ATTR_SPEED, ATTR_OSCILLATING, + ATTR_SPEED, DOMAIN, SERVICE_OSCILLATE, SERVICE_SET_DIRECTION, @@ -14,9 +14,9 @@ from homeassistant.components.fan import ( ) from homeassistant.const import ( ATTR_ENTITY_ID, - SERVICE_TURN_ON, - SERVICE_TURN_OFF, ENTITY_MATCH_ALL, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, ) diff --git a/tests/components/fan/test_device_action.py b/tests/components/fan/test_device_action.py index 928fd353dd5..70da4bd1fca 100644 --- a/tests/components/fan/test_device_action.py +++ b/tests/components/fan/test_device_action.py @@ -1,18 +1,18 @@ """The tests for Fan device actions.""" import pytest -from homeassistant.components.fan import DOMAIN -from homeassistant.setup import async_setup_component import homeassistant.components.automation as automation +from homeassistant.components.fan import DOMAIN from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component from tests.common import ( MockConfigEntry, assert_lists_same, + async_get_device_automations, async_mock_service, mock_device_registry, mock_registry, - async_get_device_automations, ) diff --git a/tests/components/fan/test_device_condition.py b/tests/components/fan/test_device_condition.py index ea87e36b636..e665f9d5ddc 100644 --- a/tests/components/fan/test_device_condition.py +++ b/tests/components/fan/test_device_condition.py @@ -1,19 +1,19 @@ """The tests for Fan device conditions.""" import pytest -from homeassistant.components.fan import DOMAIN -from homeassistant.const import STATE_ON, STATE_OFF -from homeassistant.setup import async_setup_component import homeassistant.components.automation as automation +from homeassistant.components.fan import DOMAIN +from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component from tests.common import ( MockConfigEntry, assert_lists_same, + async_get_device_automations, async_mock_service, mock_device_registry, mock_registry, - async_get_device_automations, ) diff --git a/tests/components/fan/test_device_trigger.py b/tests/components/fan/test_device_trigger.py index fa41749cf36..3d4f4229965 100644 --- a/tests/components/fan/test_device_trigger.py +++ b/tests/components/fan/test_device_trigger.py @@ -1,19 +1,19 @@ """The tests for Fan device triggers.""" import pytest -from homeassistant.components.fan import DOMAIN -from homeassistant.const import STATE_ON, STATE_OFF -from homeassistant.setup import async_setup_component import homeassistant.components.automation as automation +from homeassistant.components.fan import DOMAIN +from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component from tests.common import ( MockConfigEntry, assert_lists_same, + async_get_device_automations, async_mock_service, mock_device_registry, mock_registry, - async_get_device_automations, ) diff --git a/tests/components/fan/test_init.py b/tests/components/fan/test_init.py index fec4d5b2495..316504381ec 100644 --- a/tests/components/fan/test_init.py +++ b/tests/components/fan/test_init.py @@ -2,9 +2,10 @@ import unittest -from homeassistant.components.fan import FanEntity import pytest +from homeassistant.components.fan import FanEntity + class BaseFan(FanEntity): """Implementation of the abstract FanEntity.""" From d3f67c3841a586eefe8b69beb85e4a3f6f1755bf Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 17:29:39 +0100 Subject: [PATCH 094/677] use isort to sort imports according to PEP8 for automation (#29620) --- homeassistant/components/automation/__init__.py | 3 +-- homeassistant/components/automation/config.py | 2 +- homeassistant/components/automation/device.py | 1 - homeassistant/components/automation/event.py | 3 +-- .../components/automation/geo_location.py | 3 +-- .../components/automation/homeassistant.py | 5 ++--- homeassistant/components/automation/litejet.py | 5 ++--- homeassistant/components/automation/mqtt.py | 5 ++--- .../components/automation/numeric_state.py | 13 ++++++------- .../components/automation/reproduce_state.py | 4 ++-- homeassistant/components/automation/state.py | 7 +++---- homeassistant/components/automation/sun.py | 5 ++--- homeassistant/components/automation/template.py | 8 +++----- homeassistant/components/automation/time.py | 3 +-- .../components/automation/time_pattern.py | 3 +-- homeassistant/components/automation/webhook.py | 3 +-- homeassistant/components/automation/zone.py | 9 ++++----- tests/components/automation/common.py | 8 ++++---- tests/components/automation/test_event.py | 5 ++--- tests/components/automation/test_geo_location.py | 3 +-- tests/components/automation/test_homeassistant.py | 4 ++-- tests/components/automation/test_litejet.py | 5 +++-- tests/components/automation/test_mqtt.py | 10 ++++++---- tests/components/automation/test_numeric_state.py | 7 ++++--- tests/components/automation/test_state.py | 14 +++++++++----- tests/components/automation/test_sun.py | 10 +++++----- tests/components/automation/test_template.py | 10 +++++++--- tests/components/automation/test_time.py | 10 +++++++--- tests/components/automation/test_time_pattern.py | 5 ++--- tests/components/automation/test_zone.py | 4 ++-- 30 files changed, 87 insertions(+), 90 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 2b775e3a602..4441b028565 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -27,13 +27,12 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.config_validation import make_entity_service_schema from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity_component import EntityComponent -from homeassistant.helpers.service import async_register_admin_service from homeassistant.helpers.restore_state import RestoreEntity +from homeassistant.helpers.service import async_register_admin_service from homeassistant.helpers.typing import TemplateVarsType from homeassistant.loader import bind_hass from homeassistant.util.dt import parse_datetime, utcnow - # mypy: allow-untyped-calls, allow-untyped-defs # mypy: no-check-untyped-defs, no-warn-return-any diff --git a/homeassistant/components/automation/config.py b/homeassistant/components/automation/config.py index 5733cd2e83e..d11472a2128 100644 --- a/homeassistant/components/automation/config.py +++ b/homeassistant/components/automation/config.py @@ -7,8 +7,8 @@ import voluptuous as vol from homeassistant.components.device_automation.exceptions import ( InvalidDeviceAutomationConfig, ) -from homeassistant.const import CONF_PLATFORM from homeassistant.config import async_log_exception, config_without_domain +from homeassistant.const import CONF_PLATFORM from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import condition, config_per_platform, script from homeassistant.loader import IntegrationNotFound diff --git a/homeassistant/components/automation/device.py b/homeassistant/components/automation/device.py index ced8f65cbf5..b2892d1abaa 100644 --- a/homeassistant/components/automation/device.py +++ b/homeassistant/components/automation/device.py @@ -7,7 +7,6 @@ from homeassistant.components.device_automation import ( ) from homeassistant.const import CONF_DOMAIN - # mypy: allow-untyped-defs, no-check-untyped-defs TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend({}, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/automation/event.py b/homeassistant/components/automation/event.py index 26dacac974d..9fc78746a7c 100644 --- a/homeassistant/components/automation/event.py +++ b/homeassistant/components/automation/event.py @@ -3,11 +3,10 @@ import logging import voluptuous as vol -from homeassistant.core import callback from homeassistant.const import CONF_PLATFORM +from homeassistant.core import callback from homeassistant.helpers import config_validation as cv - # mypy: allow-untyped-defs CONF_EVENT_TYPE = "event_type" diff --git a/homeassistant/components/automation/geo_location.py b/homeassistant/components/automation/geo_location.py index 0ef0884d329..5dc4f3c80f6 100644 --- a/homeassistant/components/automation/geo_location.py +++ b/homeassistant/components/automation/geo_location.py @@ -2,7 +2,6 @@ import voluptuous as vol from homeassistant.components.geo_location import DOMAIN -from homeassistant.core import callback from homeassistant.const import ( CONF_EVENT, CONF_PLATFORM, @@ -10,10 +9,10 @@ from homeassistant.const import ( CONF_ZONE, EVENT_STATE_CHANGED, ) +from homeassistant.core import callback from homeassistant.helpers import condition, config_validation as cv from homeassistant.helpers.config_validation import entity_domain - # mypy: allow-untyped-defs, no-check-untyped-defs EVENT_ENTER = "enter" diff --git a/homeassistant/components/automation/homeassistant.py b/homeassistant/components/automation/homeassistant.py index e4eb029d5aa..743b169c86c 100644 --- a/homeassistant/components/automation/homeassistant.py +++ b/homeassistant/components/automation/homeassistant.py @@ -3,9 +3,8 @@ import logging import voluptuous as vol -from homeassistant.core import callback, CoreState -from homeassistant.const import CONF_PLATFORM, CONF_EVENT, EVENT_HOMEASSISTANT_STOP - +from homeassistant.const import CONF_EVENT, CONF_PLATFORM, EVENT_HOMEASSISTANT_STOP +from homeassistant.core import CoreState, callback # mypy: allow-untyped-defs diff --git a/homeassistant/components/automation/litejet.py b/homeassistant/components/automation/litejet.py index 9512db8261d..466fc941a9a 100644 --- a/homeassistant/components/automation/litejet.py +++ b/homeassistant/components/automation/litejet.py @@ -3,12 +3,11 @@ import logging import voluptuous as vol -from homeassistant.core import callback from homeassistant.const import CONF_PLATFORM +from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -import homeassistant.util.dt as dt_util from homeassistant.helpers.event import track_point_in_utc_time - +import homeassistant.util.dt as dt_util # mypy: allow-untyped-defs, no-check-untyped-defs diff --git a/homeassistant/components/automation/mqtt.py b/homeassistant/components/automation/mqtt.py index 135a421f72e..fb0073c78d5 100644 --- a/homeassistant/components/automation/mqtt.py +++ b/homeassistant/components/automation/mqtt.py @@ -3,12 +3,11 @@ import json import voluptuous as vol -from homeassistant.core import callback from homeassistant.components import mqtt -from homeassistant.const import CONF_PLATFORM, CONF_PAYLOAD +from homeassistant.const import CONF_PAYLOAD, CONF_PLATFORM +from homeassistant.core import callback import homeassistant.helpers.config_validation as cv - # mypy: allow-untyped-defs CONF_ENCODING = "encoding" diff --git a/homeassistant/components/automation/numeric_state.py b/homeassistant/components/automation/numeric_state.py index 0c8ab3d9c8b..e944b66751b 100644 --- a/homeassistant/components/automation/numeric_state.py +++ b/homeassistant/components/automation/numeric_state.py @@ -4,18 +4,17 @@ import logging import voluptuous as vol from homeassistant import exceptions -from homeassistant.core import CALLBACK_TYPE, callback from homeassistant.const import ( - CONF_VALUE_TEMPLATE, - CONF_PLATFORM, - CONF_ENTITY_ID, - CONF_BELOW, CONF_ABOVE, + CONF_BELOW, + CONF_ENTITY_ID, CONF_FOR, + CONF_PLATFORM, + CONF_VALUE_TEMPLATE, ) -from homeassistant.helpers.event import async_track_state_change, async_track_same_state +from homeassistant.core import CALLBACK_TYPE, callback from homeassistant.helpers import condition, config_validation as cv, template - +from homeassistant.helpers.event import async_track_same_state, async_track_state_change # mypy: allow-incomplete-defs, allow-untyped-calls, allow-untyped-defs # mypy: no-check-untyped-defs diff --git a/homeassistant/components/automation/reproduce_state.py b/homeassistant/components/automation/reproduce_state.py index 553d6871087..4cfe519d585 100644 --- a/homeassistant/components/automation/reproduce_state.py +++ b/homeassistant/components/automation/reproduce_state.py @@ -5,10 +5,10 @@ from typing import Iterable, Optional from homeassistant.const import ( ATTR_ENTITY_ID, - STATE_ON, - STATE_OFF, SERVICE_TURN_OFF, SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, ) from homeassistant.core import Context, State from homeassistant.helpers.typing import HomeAssistantType diff --git a/homeassistant/components/automation/state.py b/homeassistant/components/automation/state.py index 47c44587b08..fc3fff47514 100644 --- a/homeassistant/components/automation/state.py +++ b/homeassistant/components/automation/state.py @@ -6,11 +6,10 @@ from typing import Dict import voluptuous as vol from homeassistant import exceptions -from homeassistant.core import HomeAssistant, CALLBACK_TYPE, callback -from homeassistant.const import MATCH_ALL, CONF_PLATFORM, CONF_FOR +from homeassistant.const import CONF_FOR, CONF_PLATFORM, MATCH_ALL +from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.helpers import config_validation as cv, template -from homeassistant.helpers.event import async_track_state_change, async_track_same_state - +from homeassistant.helpers.event import async_track_same_state, async_track_state_change # mypy: allow-incomplete-defs, allow-untyped-calls, allow-untyped-defs # mypy: no-check-untyped-defs diff --git a/homeassistant/components/automation/sun.py b/homeassistant/components/automation/sun.py index 66892784a54..c416742f397 100644 --- a/homeassistant/components/automation/sun.py +++ b/homeassistant/components/automation/sun.py @@ -4,16 +4,15 @@ import logging import voluptuous as vol -from homeassistant.core import callback from homeassistant.const import ( CONF_EVENT, CONF_OFFSET, CONF_PLATFORM, SUN_EVENT_SUNRISE, ) -from homeassistant.helpers.event import async_track_sunrise, async_track_sunset +from homeassistant.core import callback import homeassistant.helpers.config_validation as cv - +from homeassistant.helpers.event import async_track_sunrise, async_track_sunset # mypy: allow-untyped-defs, no-check-untyped-defs diff --git a/homeassistant/components/automation/template.py b/homeassistant/components/automation/template.py index 95b6b857c9d..ee4484410cd 100644 --- a/homeassistant/components/automation/template.py +++ b/homeassistant/components/automation/template.py @@ -3,13 +3,11 @@ import logging import voluptuous as vol -from homeassistant.core import callback -from homeassistant.const import CONF_VALUE_TEMPLATE, CONF_PLATFORM, CONF_FOR from homeassistant import exceptions -from homeassistant.helpers import condition +from homeassistant.const import CONF_FOR, CONF_PLATFORM, CONF_VALUE_TEMPLATE +from homeassistant.core import callback +from homeassistant.helpers import condition, config_validation as cv, template from homeassistant.helpers.event import async_track_same_state, async_track_template -from homeassistant.helpers import config_validation as cv, template - # mypy: allow-untyped-defs, no-check-untyped-defs diff --git a/homeassistant/components/automation/time.py b/homeassistant/components/automation/time.py index 231bc346e14..5f461952960 100644 --- a/homeassistant/components/automation/time.py +++ b/homeassistant/components/automation/time.py @@ -3,12 +3,11 @@ import logging import voluptuous as vol -from homeassistant.core import callback from homeassistant.const import CONF_AT, CONF_PLATFORM +from homeassistant.core import callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.event import async_track_time_change - # mypy: allow-untyped-defs, no-check-untyped-defs _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/automation/time_pattern.py b/homeassistant/components/automation/time_pattern.py index ee092916112..65d44f5b1ca 100644 --- a/homeassistant/components/automation/time_pattern.py +++ b/homeassistant/components/automation/time_pattern.py @@ -3,12 +3,11 @@ import logging import voluptuous as vol -from homeassistant.core import callback from homeassistant.const import CONF_PLATFORM +from homeassistant.core import callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.event import async_track_time_change - # mypy: allow-untyped-defs, no-check-untyped-defs CONF_HOURS = "hours" diff --git a/homeassistant/components/automation/webhook.py b/homeassistant/components/automation/webhook.py index bbcf9bd9ddc..5d01c6454a8 100644 --- a/homeassistant/components/automation/webhook.py +++ b/homeassistant/components/automation/webhook.py @@ -5,13 +5,12 @@ import logging from aiohttp import hdrs import voluptuous as vol -from homeassistant.core import callback from homeassistant.const import CONF_PLATFORM, CONF_WEBHOOK_ID +from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from . import DOMAIN as AUTOMATION_DOMAIN - # mypy: allow-untyped-defs DEPENDENCIES = ("webhook",) diff --git a/homeassistant/components/automation/zone.py b/homeassistant/components/automation/zone.py index 535ef298a2a..3dba1a4df35 100644 --- a/homeassistant/components/automation/zone.py +++ b/homeassistant/components/automation/zone.py @@ -1,17 +1,16 @@ """Offer zone automation rules.""" import voluptuous as vol -from homeassistant.core import callback from homeassistant.const import ( - CONF_EVENT, CONF_ENTITY_ID, + CONF_EVENT, + CONF_PLATFORM, CONF_ZONE, MATCH_ALL, - CONF_PLATFORM, ) -from homeassistant.helpers.event import async_track_state_change +from homeassistant.core import callback from homeassistant.helpers import condition, config_validation as cv, location - +from homeassistant.helpers.event import async_track_state_change # mypy: allow-untyped-defs, no-check-untyped-defs diff --git a/tests/components/automation/common.py b/tests/components/automation/common.py index 729a6bb7212..95b156bcb14 100644 --- a/tests/components/automation/common.py +++ b/tests/components/automation/common.py @@ -6,11 +6,11 @@ components. Instead call the service directly. from homeassistant.components.automation import DOMAIN, SERVICE_TRIGGER from homeassistant.const import ( ATTR_ENTITY_ID, - SERVICE_TURN_ON, - SERVICE_TURN_OFF, - SERVICE_TOGGLE, - SERVICE_RELOAD, ENTITY_MATCH_ALL, + SERVICE_RELOAD, + SERVICE_TOGGLE, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, ) from homeassistant.loader import bind_hass diff --git a/tests/components/automation/test_event.py b/tests/components/automation/test_event.py index e8d6089f500..26d19d6fa47 100644 --- a/tests/components/automation/test_event.py +++ b/tests/components/automation/test_event.py @@ -1,13 +1,12 @@ """The tests for the Event automation.""" import pytest +import homeassistant.components.automation as automation from homeassistant.core import Context from homeassistant.setup import async_setup_component -import homeassistant.components.automation as automation -from tests.common import mock_component +from tests.common import async_mock_service, mock_component from tests.components.automation import common -from tests.common import async_mock_service @pytest.fixture diff --git a/tests/components/automation/test_geo_location.py b/tests/components/automation/test_geo_location.py index d1ded8da1c6..05e30458ef3 100644 --- a/tests/components/automation/test_geo_location.py +++ b/tests/components/automation/test_geo_location.py @@ -5,9 +5,8 @@ from homeassistant.components import automation, zone from homeassistant.core import Context from homeassistant.setup import async_setup_component -from tests.common import mock_component +from tests.common import async_mock_service, mock_component from tests.components.automation import common -from tests.common import async_mock_service @pytest.fixture diff --git a/tests/components/automation/test_homeassistant.py b/tests/components/automation/test_homeassistant.py index 003e900babc..d5bd4c6dd5b 100644 --- a/tests/components/automation/test_homeassistant.py +++ b/tests/components/automation/test_homeassistant.py @@ -1,9 +1,9 @@ """The tests for the Event automation.""" -from unittest.mock import patch, Mock +from unittest.mock import Mock, patch +import homeassistant.components.automation as automation from homeassistant.core import CoreState from homeassistant.setup import async_setup_component -import homeassistant.components.automation as automation from tests.common import async_mock_service, mock_coro diff --git a/tests/components/automation/test_litejet.py b/tests/components/automation/test_litejet.py index 4c916d8ed96..75fbc03a589 100644 --- a/tests/components/automation/test_litejet.py +++ b/tests/components/automation/test_litejet.py @@ -1,13 +1,14 @@ """The tests for the litejet component.""" +from datetime import timedelta import logging from unittest import mock -from datetime import timedelta + import pytest from homeassistant import setup -import homeassistant.util.dt as dt_util from homeassistant.components import litejet import homeassistant.components.automation as automation +import homeassistant.util.dt as dt_util from tests.common import async_fire_time_changed, async_mock_service diff --git a/tests/components/automation/test_mqtt.py b/tests/components/automation/test_mqtt.py index 7c6db978f5c..9dbe93a7998 100644 --- a/tests/components/automation/test_mqtt.py +++ b/tests/components/automation/test_mqtt.py @@ -1,14 +1,16 @@ """The tests for the MQTT automation.""" -import pytest from unittest import mock -from homeassistant.setup import async_setup_component +import pytest + import homeassistant.components.automation as automation +from homeassistant.setup import async_setup_component + from tests.common import ( async_fire_mqtt_message, - mock_component, - async_mock_service, async_mock_mqtt_component, + async_mock_service, + mock_component, ) from tests.components.automation import common diff --git a/tests/components/automation/test_numeric_state.py b/tests/components/automation/test_numeric_state.py index 3cb8e2588fc..c6c1fd83184 100644 --- a/tests/components/automation/test_numeric_state.py +++ b/tests/components/automation/test_numeric_state.py @@ -1,18 +1,19 @@ """The tests for numeric state automation.""" from datetime import timedelta -import pytest from unittest.mock import patch +import pytest + import homeassistant.components.automation as automation from homeassistant.core import Context from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from tests.common import ( - mock_component, - async_fire_time_changed, assert_setup_component, + async_fire_time_changed, async_mock_service, + mock_component, ) from tests.components.automation import common diff --git a/tests/components/automation/test_state.py b/tests/components/automation/test_state.py index 9d84fb3e8ce..b6f9a50cf9d 100644 --- a/tests/components/automation/test_state.py +++ b/tests/components/automation/test_state.py @@ -1,17 +1,21 @@ """The test for state automation.""" from datetime import timedelta - -import pytest from unittest.mock import patch +import pytest + +import homeassistant.components.automation as automation from homeassistant.core import Context from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util -import homeassistant.components.automation as automation -from tests.common import async_fire_time_changed, assert_setup_component, mock_component +from tests.common import ( + assert_setup_component, + async_fire_time_changed, + async_mock_service, + mock_component, +) from tests.components.automation import common -from tests.common import async_mock_service @pytest.fixture diff --git a/tests/components/automation/test_sun.py b/tests/components/automation/test_sun.py index 2668ac97053..3468c9e9480 100644 --- a/tests/components/automation/test_sun.py +++ b/tests/components/automation/test_sun.py @@ -1,16 +1,16 @@ """The tests for the sun automation.""" from datetime import datetime - -import pytest from unittest.mock import patch -from homeassistant.const import SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET -from homeassistant.setup import async_setup_component +import pytest + from homeassistant.components import sun import homeassistant.components.automation as automation +from homeassistant.const import SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET +from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util -from tests.common import async_fire_time_changed, mock_component, async_mock_service +from tests.common import async_fire_time_changed, async_mock_service, mock_component from tests.components.automation import common ORIG_TIME_ZONE = dt_util.DEFAULT_TIME_ZONE diff --git a/tests/components/automation/test_template.py b/tests/components/automation/test_template.py index d7726b7ffd8..d9566b8f464 100644 --- a/tests/components/automation/test_template.py +++ b/tests/components/automation/test_template.py @@ -4,14 +4,18 @@ from unittest import mock import pytest +import homeassistant.components.automation as automation from homeassistant.core import Context from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util -import homeassistant.components.automation as automation -from tests.common import async_fire_time_changed, assert_setup_component, mock_component +from tests.common import ( + assert_setup_component, + async_fire_time_changed, + async_mock_service, + mock_component, +) from tests.components.automation import common -from tests.common import async_mock_service @pytest.fixture diff --git a/tests/components/automation/test_time.py b/tests/components/automation/test_time.py index fa931d06bfc..e12ce6684d2 100644 --- a/tests/components/automation/test_time.py +++ b/tests/components/automation/test_time.py @@ -4,12 +4,16 @@ from unittest.mock import patch import pytest +import homeassistant.components.automation as automation from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util -import homeassistant.components.automation as automation -from tests.common import async_fire_time_changed, assert_setup_component, mock_component -from tests.common import async_mock_service +from tests.common import ( + assert_setup_component, + async_fire_time_changed, + async_mock_service, + mock_component, +) @pytest.fixture diff --git a/tests/components/automation/test_time_pattern.py b/tests/components/automation/test_time_pattern.py index 479bab1c78e..70d647a1241 100644 --- a/tests/components/automation/test_time_pattern.py +++ b/tests/components/automation/test_time_pattern.py @@ -1,13 +1,12 @@ """The tests for the time_pattern automation.""" import pytest +import homeassistant.components.automation as automation from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util -import homeassistant.components.automation as automation -from tests.common import async_fire_time_changed, mock_component +from tests.common import async_fire_time_changed, async_mock_service, mock_component from tests.components.automation import common -from tests.common import async_mock_service @pytest.fixture diff --git a/tests/components/automation/test_zone.py b/tests/components/automation/test_zone.py index 07be09a8454..44ad20e16f0 100644 --- a/tests/components/automation/test_zone.py +++ b/tests/components/automation/test_zone.py @@ -1,12 +1,12 @@ """The tests for the location automation.""" import pytest +from homeassistant.components import automation, zone from homeassistant.core import Context from homeassistant.setup import async_setup_component -from homeassistant.components import automation, zone -from tests.components.automation import common from tests.common import async_mock_service, mock_component +from tests.components.automation import common @pytest.fixture From f5288db93c91d6910005fc66dfbc5116e6fa42dc Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 17:30:57 +0100 Subject: [PATCH 095/677] use isort to sort imports according to PEP8 for cast (#29624) --- homeassistant/components/cast/discovery.py | 2 +- .../components/cast/home_assistant_cast.py | 3 +-- homeassistant/components/cast/media_player.py | 16 ++++++++-------- .../components/cast/test_home_assistant_cast.py | 1 + tests/components/cast/test_init.py | 2 +- tests/components/cast/test_media_player.py | 8 ++++---- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/cast/discovery.py b/homeassistant/components/cast/discovery.py index d3097b3cc29..54f165889af 100644 --- a/homeassistant/components/cast/discovery.py +++ b/homeassistant/components/cast/discovery.py @@ -9,9 +9,9 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.dispatcher import dispatcher_send from .const import ( + INTERNAL_DISCOVERY_RUNNING_KEY, KNOWN_CHROMECAST_INFO_KEY, SIGNAL_CAST_DISCOVERED, - INTERNAL_DISCOVERY_RUNNING_KEY, SIGNAL_CAST_REMOVED, ) from .helpers import ChromecastInfo, ChromeCastZeroconf diff --git a/homeassistant/components/cast/home_assistant_cast.py b/homeassistant/components/cast/home_assistant_cast.py index d5d35ba7c9f..0b8633e1916 100644 --- a/homeassistant/components/cast/home_assistant_cast.py +++ b/homeassistant/components/cast/home_assistant_cast.py @@ -1,9 +1,8 @@ """Home Assistant Cast integration for Cast.""" from typing import Optional -import voluptuous as vol - from pychromecast.controllers.homeassistant import HomeAssistantController +import voluptuous as vol from homeassistant import auth, config_entries, core from homeassistant.const import ATTR_ENTITY_ID diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index c2d847fd09b..03174134502 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -4,12 +4,12 @@ import logging from typing import Optional import pychromecast +from pychromecast.controllers.homeassistant import HomeAssistantController +from pychromecast.controllers.multizone import MultizoneManager from pychromecast.socket_client import ( CONNECTION_STATUS_CONNECTED, CONNECTION_STATUS_DISCONNECTED, ) -from pychromecast.controllers.multizone import MultizoneManager -from pychromecast.controllers.homeassistant import HomeAssistantController import voluptuous as vol from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice @@ -46,22 +46,22 @@ import homeassistant.util.dt as dt_util from homeassistant.util.logging import async_create_catching_coro from .const import ( - DOMAIN as CAST_DOMAIN, ADDED_CAST_DEVICES_KEY, - SIGNAL_CAST_DISCOVERED, - KNOWN_CHROMECAST_INFO_KEY, CAST_MULTIZONE_MANAGER_KEY, DEFAULT_PORT, + DOMAIN as CAST_DOMAIN, + KNOWN_CHROMECAST_INFO_KEY, + SIGNAL_CAST_DISCOVERED, SIGNAL_CAST_REMOVED, SIGNAL_HASS_CAST_SHOW_VIEW, ) +from .discovery import discover_chromecast, setup_internal_discovery from .helpers import ( - ChromecastInfo, CastStatusListener, - DynamicGroupCastStatusListener, + ChromecastInfo, ChromeCastZeroconf, + DynamicGroupCastStatusListener, ) -from .discovery import setup_internal_discovery, discover_chromecast _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/cast/test_home_assistant_cast.py b/tests/components/cast/test_home_assistant_cast.py index 8db6fd4609e..10dd253704e 100644 --- a/tests/components/cast/test_home_assistant_cast.py +++ b/tests/components/cast/test_home_assistant_cast.py @@ -1,5 +1,6 @@ """Test Home Assistant Cast.""" from unittest.mock import Mock, patch + from homeassistant.components.cast import home_assistant_cast from tests.common import MockConfigEntry, async_mock_signal diff --git a/tests/components/cast/test_init.py b/tests/components/cast/test_init.py index 9062e521bef..6971c071353 100644 --- a/tests/components/cast/test_init.py +++ b/tests/components/cast/test_init.py @@ -2,8 +2,8 @@ from unittest.mock import patch from homeassistant import config_entries, data_entry_flow -from homeassistant.setup import async_setup_component from homeassistant.components import cast +from homeassistant.setup import async_setup_component from tests.common import MockDependency, mock_coro diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index 8f33709fb2d..fd565ab59d9 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -2,18 +2,18 @@ # pylint: disable=protected-access import asyncio from typing import Optional -from unittest.mock import patch, MagicMock, Mock +from unittest.mock import MagicMock, Mock, patch from uuid import UUID import attr import pytest -from homeassistant.exceptions import PlatformNotReady -from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.components.cast import media_player as cast from homeassistant.components.cast.media_player import ChromecastInfo from homeassistant.const import EVENT_HOMEASSISTANT_STOP +from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.components.cast import media_player as cast +from homeassistant.helpers.typing import HomeAssistantType from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry, mock_coro From 8f5e8c72c60bc04202a1c075dc7c3ecf117bd09d Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 17:32:08 +0100 Subject: [PATCH 096/677] use isort to sort imports according to PEP8 for binary_sensor (#29622) --- .../components/binary_sensor/__init__.py | 7 +++---- .../components/binary_sensor/device_condition.py | 5 +++-- .../components/binary_sensor/device_trigger.py | 5 ++--- .../binary_sensor/test_device_condition.py | 15 ++++++++------- .../binary_sensor/test_device_trigger.py | 13 +++++++------ tests/components/binary_sensor/test_init.py | 2 +- 6 files changed, 24 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py index e5f5dc94ff1..73d5e0be458 100644 --- a/homeassistant/components/binary_sensor/__init__.py +++ b/homeassistant/components/binary_sensor/__init__.py @@ -5,14 +5,13 @@ import logging import voluptuous as vol -from homeassistant.helpers.entity_component import EntityComponent -from homeassistant.helpers.entity import Entity -from homeassistant.const import STATE_ON, STATE_OFF +from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.helpers.config_validation import ( # noqa: F401 PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE, ) - +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.entity_component import EntityComponent # mypy: allow-untyped-defs, no-check-untyped-defs diff --git a/homeassistant/components/binary_sensor/device_condition.py b/homeassistant/components/binary_sensor/device_condition.py index 0766d82c727..842790e0178 100644 --- a/homeassistant/components/binary_sensor/device_condition.py +++ b/homeassistant/components/binary_sensor/device_condition.py @@ -1,10 +1,11 @@ """Implemenet device conditions for binary sensor.""" from typing import Dict, List + import voluptuous as vol -from homeassistant.core import HomeAssistant from homeassistant.components.device_automation.const import CONF_IS_OFF, CONF_IS_ON from homeassistant.const import ATTR_DEVICE_CLASS, CONF_ENTITY_ID, CONF_FOR, CONF_TYPE +from homeassistant.core import HomeAssistant from homeassistant.helpers import condition, config_validation as cv from homeassistant.helpers.entity_registry import ( async_entries_for_device, @@ -13,7 +14,6 @@ from homeassistant.helpers.entity_registry import ( from homeassistant.helpers.typing import ConfigType from . import ( - DOMAIN, DEVICE_CLASS_BATTERY, DEVICE_CLASS_COLD, DEVICE_CLASS_CONNECTIVITY, @@ -37,6 +37,7 @@ from . import ( DEVICE_CLASS_SOUND, DEVICE_CLASS_VIBRATION, DEVICE_CLASS_WINDOW, + DOMAIN, ) DEVICE_CLASS_NONE = "none" diff --git a/homeassistant/components/binary_sensor/device_trigger.py b/homeassistant/components/binary_sensor/device_trigger.py index c51b9749288..288cc101d93 100644 --- a/homeassistant/components/binary_sensor/device_trigger.py +++ b/homeassistant/components/binary_sensor/device_trigger.py @@ -8,11 +8,10 @@ from homeassistant.components.device_automation.const import ( CONF_TURNED_ON, ) from homeassistant.const import ATTR_DEVICE_CLASS, CONF_ENTITY_ID, CONF_FOR, CONF_TYPE -from homeassistant.helpers.entity_registry import async_entries_for_device from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.entity_registry import async_entries_for_device from . import ( - DOMAIN, DEVICE_CLASS_BATTERY, DEVICE_CLASS_COLD, DEVICE_CLASS_CONNECTIVITY, @@ -36,9 +35,9 @@ from . import ( DEVICE_CLASS_SOUND, DEVICE_CLASS_VIBRATION, DEVICE_CLASS_WINDOW, + DOMAIN, ) - # mypy: allow-untyped-defs, no-check-untyped-defs DEVICE_CLASS_NONE = "none" diff --git a/tests/components/binary_sensor/test_device_condition.py b/tests/components/binary_sensor/test_device_condition.py index 34cf4030a50..ecf5e86bdad 100644 --- a/tests/components/binary_sensor/test_device_condition.py +++ b/tests/components/binary_sensor/test_device_condition.py @@ -1,23 +1,24 @@ """The test for binary_sensor device automation.""" from datetime import timedelta -import pytest from unittest.mock import patch -from homeassistant.components.binary_sensor import DOMAIN, DEVICE_CLASSES -from homeassistant.components.binary_sensor.device_condition import ENTITY_CONDITIONS -from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM -from homeassistant.setup import async_setup_component +import pytest + import homeassistant.components.automation as automation +from homeassistant.components.binary_sensor import DEVICE_CLASSES, DOMAIN +from homeassistant.components.binary_sensor.device_condition import ENTITY_CONDITIONS +from homeassistant.const import CONF_PLATFORM, STATE_OFF, STATE_ON from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from tests.common import ( MockConfigEntry, + async_get_device_automation_capabilities, + async_get_device_automations, async_mock_service, mock_device_registry, mock_registry, - async_get_device_automations, - async_get_device_automation_capabilities, ) diff --git a/tests/components/binary_sensor/test_device_trigger.py b/tests/components/binary_sensor/test_device_trigger.py index 9bab1ff1f36..404def66491 100644 --- a/tests/components/binary_sensor/test_device_trigger.py +++ b/tests/components/binary_sensor/test_device_trigger.py @@ -1,23 +1,24 @@ """The test for binary_sensor device automation.""" from datetime import timedelta + import pytest -from homeassistant.components.binary_sensor import DOMAIN, DEVICE_CLASSES -from homeassistant.components.binary_sensor.device_trigger import ENTITY_TRIGGERS -from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM -from homeassistant.setup import async_setup_component import homeassistant.components.automation as automation +from homeassistant.components.binary_sensor import DEVICE_CLASSES, DOMAIN +from homeassistant.components.binary_sensor.device_trigger import ENTITY_TRIGGERS +from homeassistant.const import CONF_PLATFORM, STATE_OFF, STATE_ON from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from tests.common import ( MockConfigEntry, async_fire_time_changed, + async_get_device_automation_capabilities, + async_get_device_automations, async_mock_service, mock_device_registry, mock_registry, - async_get_device_automations, - async_get_device_automation_capabilities, ) diff --git a/tests/components/binary_sensor/test_init.py b/tests/components/binary_sensor/test_init.py index 0aa1a798487..9759d3281e6 100644 --- a/tests/components/binary_sensor/test_init.py +++ b/tests/components/binary_sensor/test_init.py @@ -3,7 +3,7 @@ import unittest from unittest import mock from homeassistant.components import binary_sensor -from homeassistant.const import STATE_ON, STATE_OFF +from homeassistant.const import STATE_OFF, STATE_ON class TestBinarySensor(unittest.TestCase): From 3b5da9c44a0370f9b615f5fbc63bb630e2b6b95b Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Sun, 8 Dec 2019 17:50:57 +0100 Subject: [PATCH 097/677] Move imports to top for homekit_controller (#29564) * Move imports to top for homekit_controller * Fix IpPairing mock in two test files --- .../components/homekit_controller/__init__.py | 7 +++---- .../components/homekit_controller/climate.py | 14 +++++++------- .../components/homekit_controller/config_flow.py | 7 +++---- .../components/homekit_controller/connection.py | 7 +++---- .../components/homekit_controller/cover.py | 2 +- .../components/homekit_controller/storage.py | 2 +- tests/components/homekit_controller/common.py | 2 +- .../specific_devices/test_ecobee3.py | 2 +- .../homekit_controller/test_config_flow.py | 16 ++++++++++++---- 9 files changed, 32 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/homekit_controller/__init__.py b/homeassistant/components/homekit_controller/__init__.py index 6b53301e877..c863da14a3a 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -5,15 +5,14 @@ import homekit from homekit.model.characteristics import CharacteristicsTypes from homeassistant.core import callback -from homeassistant.helpers.entity import Entity from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry as dr +from homeassistant.helpers.entity import Entity # We need an import from .config_flow, without it .config_flow is never loaded. from .config_flow import HomekitControllerFlowHandler # noqa: F401 -from .connection import get_accessory_information, HKDevice -from .const import CONTROLLER, ENTITY_MAP, KNOWN_DEVICES -from .const import DOMAIN +from .connection import HKDevice, get_accessory_information +from .const import CONTROLLER, DOMAIN, ENTITY_MAP, KNOWN_DEVICES from .storage import EntityMapStorage _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/homekit_controller/climate.py b/homeassistant/components/homekit_controller/climate.py index 1f9118ff838..d0ab7bd2e99 100644 --- a/homeassistant/components/homekit_controller/climate.py +++ b/homeassistant/components/homekit_controller/climate.py @@ -4,20 +4,20 @@ import logging from homekit.model.characteristics import CharacteristicsTypes from homeassistant.components.climate import ( - ClimateDevice, - DEFAULT_MIN_HUMIDITY, DEFAULT_MAX_HUMIDITY, + DEFAULT_MIN_HUMIDITY, + ClimateDevice, ) from homeassistant.components.climate.const import ( - HVAC_MODE_HEAT_COOL, + CURRENT_HVAC_COOL, + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, HVAC_MODE_COOL, HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, - CURRENT_HVAC_IDLE, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_COOL, - SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_HUMIDITY, + SUPPORT_TARGET_TEMPERATURE, ) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS diff --git a/homeassistant/components/homekit_controller/config_flow.py b/homeassistant/components/homekit_controller/config_flow.py index 40bf87d6f0a..f4eb1190727 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -1,17 +1,17 @@ """Config flow to configure homekit_controller.""" -import os import json import logging +import os import homekit +from homekit.controller.ip_implementation import IpPairing import voluptuous as vol from homeassistant import config_entries from homeassistant.core import callback +from .connection import get_accessory_name, get_bridge_information from .const import DOMAIN, KNOWN_DEVICES -from .connection import get_bridge_information, get_accessory_name - HOMEKIT_IGNORE = ["Home Assistant Bridge"] HOMEKIT_DIR = ".homekit" @@ -194,7 +194,6 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow): async def async_import_legacy_pairing(self, discovery_props, pairing_data): """Migrate a legacy pairing to config entries.""" - from homekit.controller.ip_implementation import IpPairing hkid = discovery_props["id"] diff --git a/homeassistant/components/homekit_controller/connection.py b/homeassistant/components/homekit_controller/connection.py index 1cb2131fb8f..3ccfa8b0139 100644 --- a/homeassistant/components/homekit_controller/connection.py +++ b/homeassistant/components/homekit_controller/connection.py @@ -3,18 +3,18 @@ import asyncio import datetime import logging +from homekit.controller.ip_implementation import IpPairing from homekit.exceptions import ( AccessoryDisconnectedError, AccessoryNotFoundError, EncryptionError, ) -from homekit.model.services import ServicesTypes from homekit.model.characteristics import CharacteristicsTypes +from homekit.model.services import ServicesTypes from homeassistant.helpers.event import async_track_time_interval -from .const import DOMAIN, HOMEKIT_ACCESSORY_DISPATCH, ENTITY_MAP - +from .const import DOMAIN, ENTITY_MAP, HOMEKIT_ACCESSORY_DISPATCH DEFAULT_SCAN_INTERVAL = datetime.timedelta(seconds=60) RETRY_INTERVAL = 60 # seconds @@ -57,7 +57,6 @@ class HKDevice: def __init__(self, hass, config_entry, pairing_data): """Initialise a generic HomeKit device.""" - from homekit.controller.ip_implementation import IpPairing self.hass = hass self.config_entry = config_entry diff --git a/homeassistant/components/homekit_controller/cover.py b/homeassistant/components/homekit_controller/cover.py index 0606778acb5..7e5591d9505 100644 --- a/homeassistant/components/homekit_controller/cover.py +++ b/homeassistant/components/homekit_controller/cover.py @@ -11,8 +11,8 @@ from homeassistant.components.cover import ( SUPPORT_OPEN, SUPPORT_OPEN_TILT, SUPPORT_SET_POSITION, - SUPPORT_STOP, SUPPORT_SET_TILT_POSITION, + SUPPORT_STOP, CoverDevice, ) from homeassistant.const import STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_OPENING diff --git a/homeassistant/components/homekit_controller/storage.py b/homeassistant/components/homekit_controller/storage.py index 46d095b5631..ffc2da5fbf2 100644 --- a/homeassistant/components/homekit_controller/storage.py +++ b/homeassistant/components/homekit_controller/storage.py @@ -1,7 +1,7 @@ """Helpers for HomeKit data stored in HA storage.""" -from homeassistant.helpers.storage import Store from homeassistant.core import callback +from homeassistant.helpers.storage import Store from .const import DOMAIN diff --git a/tests/components/homekit_controller/common.py b/tests/components/homekit_controller/common.py index 2e1cfd7a77d..9a404701c54 100644 --- a/tests/components/homekit_controller/common.py +++ b/tests/components/homekit_controller/common.py @@ -250,7 +250,7 @@ async def setup_test_accessories(hass, accessories): config_entry.add_to_hass(hass) - pairing_cls_loc = "homekit.controller.ip_implementation.IpPairing" + pairing_cls_loc = "homeassistant.components.homekit_controller.connection.IpPairing" with mock.patch(pairing_cls_loc) as pairing_cls: pairing_cls.return_value = pairing await config_entry.async_setup(hass) diff --git a/tests/components/homekit_controller/specific_devices/test_ecobee3.py b/tests/components/homekit_controller/specific_devices/test_ecobee3.py index 8473d235278..8531712a5d3 100644 --- a/tests/components/homekit_controller/specific_devices/test_ecobee3.py +++ b/tests/components/homekit_controller/specific_devices/test_ecobee3.py @@ -154,7 +154,7 @@ async def test_ecobee3_setup_connection_failure(hass): # make sure the IpPairing mock is in place or we'll try to connect to # a real device. Normally this mocking is done by the helper in # setup_test_accessories. - pairing_cls_loc = "homekit.controller.ip_implementation.IpPairing" + pairing_cls_loc = "homeassistant.components.homekit_controller.connection.IpPairing" with mock.patch(pairing_cls_loc) as pairing_cls: pairing_cls.return_value = pairing await time_changed(hass, 5 * 60) diff --git a/tests/components/homekit_controller/test_config_flow.py b/tests/components/homekit_controller/test_config_flow.py index a8428375ae6..81d5ac9a751 100644 --- a/tests/components/homekit_controller/test_config_flow.py +++ b/tests/components/homekit_controller/test_config_flow.py @@ -554,7 +554,9 @@ async def test_import_works(hass): flow = _setup_flow_handler(hass) - pairing_cls_imp = "homekit.controller.ip_implementation.IpPairing" + pairing_cls_imp = ( + "homeassistant.components.homekit_controller.config_flow.IpPairing" + ) with mock.patch(pairing_cls_imp) as pairing_cls: pairing_cls.return_value = pairing @@ -694,7 +696,9 @@ async def test_parse_new_homekit_json(hass): flow = _setup_flow_handler(hass) - pairing_cls_imp = "homekit.controller.ip_implementation.IpPairing" + pairing_cls_imp = ( + "homeassistant.components.homekit_controller.config_flow.IpPairing" + ) with mock.patch(pairing_cls_imp) as pairing_cls: pairing_cls.return_value = pairing @@ -742,7 +746,9 @@ async def test_parse_old_homekit_json(hass): flow = _setup_flow_handler(hass) - pairing_cls_imp = "homekit.controller.ip_implementation.IpPairing" + pairing_cls_imp = ( + "homeassistant.components.homekit_controller.config_flow.IpPairing" + ) with mock.patch(pairing_cls_imp) as pairing_cls: pairing_cls.return_value = pairing @@ -798,7 +804,9 @@ async def test_parse_overlapping_homekit_json(hass): flow = _setup_flow_handler(hass) - pairing_cls_imp = "homekit.controller.ip_implementation.IpPairing" + pairing_cls_imp = ( + "homeassistant.components.homekit_controller.config_flow.IpPairing" + ) with mock.patch(pairing_cls_imp) as pairing_cls: pairing_cls.return_value = pairing From 954813b47832ab91d9dc400862769abef3817b3e Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 17:55:01 +0100 Subject: [PATCH 098/677] use isort to sort imports according to PEP8 for climate (#29625) --- homeassistant/components/climate/__init__.py | 4 +-- .../components/climate/device_action.py | 10 +++--- .../components/climate/device_condition.py | 8 +++-- .../components/climate/device_trigger.py | 32 ++++++++++--------- .../components/climate/reproduce_state.py | 18 +++++------ tests/components/climate/common.py | 2 +- .../components/climate/test_device_action.py | 8 ++--- .../climate/test_device_condition.py | 8 ++--- .../components/climate/test_device_trigger.py | 10 +++--- tests/components/climate/test_init.py | 7 ++-- .../climate/test_reproduce_state.py | 2 +- 11 files changed, 58 insertions(+), 51 deletions(-) diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index 6006b2a9a3b..e2bf555cc49 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -19,9 +19,9 @@ from homeassistant.const import ( ) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.config_validation import ( # noqa: F401 - make_entity_service_schema, PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE, + make_entity_service_schema, ) from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent @@ -68,8 +68,8 @@ from .const import ( SUPPORT_PRESET_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_HUMIDITY, - SUPPORT_TARGET_TEMPERATURE_RANGE, SUPPORT_TARGET_TEMPERATURE, + SUPPORT_TARGET_TEMPERATURE_RANGE, ) DEFAULT_MIN_TEMP = 7 diff --git a/homeassistant/components/climate/device_action.py b/homeassistant/components/climate/device_action.py index 836e2277461..6f7725ac835 100644 --- a/homeassistant/components/climate/device_action.py +++ b/homeassistant/components/climate/device_action.py @@ -1,17 +1,19 @@ """Provides device automations for Climate.""" -from typing import Optional, List +from typing import List, Optional + import voluptuous as vol from homeassistant.const import ( ATTR_ENTITY_ID, - CONF_DOMAIN, - CONF_TYPE, CONF_DEVICE_ID, + CONF_DOMAIN, CONF_ENTITY_ID, + CONF_TYPE, ) -from homeassistant.core import HomeAssistant, Context +from homeassistant.core import Context, HomeAssistant from homeassistant.helpers import entity_registry import homeassistant.helpers.config_validation as cv + from . import DOMAIN, const ACTION_TYPES = {"set_hvac_mode", "set_preset_mode"} diff --git a/homeassistant/components/climate/device_condition.py b/homeassistant/components/climate/device_condition.py index 3a075233942..cf393a035ec 100644 --- a/homeassistant/components/climate/device_condition.py +++ b/homeassistant/components/climate/device_condition.py @@ -1,19 +1,21 @@ """Provide the device automations for Climate.""" from typing import Dict, List + import voluptuous as vol from homeassistant.const import ( ATTR_ENTITY_ID, CONF_CONDITION, - CONF_DOMAIN, - CONF_TYPE, CONF_DEVICE_ID, + CONF_DOMAIN, CONF_ENTITY_ID, + CONF_TYPE, ) from homeassistant.core import HomeAssistant from homeassistant.helpers import condition, config_validation as cv, entity_registry -from homeassistant.helpers.typing import ConfigType, TemplateVarsType from homeassistant.helpers.config_validation import DEVICE_CONDITION_BASE_SCHEMA +from homeassistant.helpers.typing import ConfigType, TemplateVarsType + from . import DOMAIN, const CONDITION_TYPES = {"is_hvac_mode", "is_preset_mode"} diff --git a/homeassistant/components/climate/device_trigger.py b/homeassistant/components/climate/device_trigger.py index e814bdc88de..4c5dcb0ee04 100644 --- a/homeassistant/components/climate/device_trigger.py +++ b/homeassistant/components/climate/device_trigger.py @@ -1,26 +1,28 @@ """Provides device automations for Climate.""" from typing import List + import voluptuous as vol -from homeassistant.const import ( - CONF_DOMAIN, - CONF_TYPE, - CONF_PLATFORM, - CONF_DEVICE_ID, - CONF_ENTITY_ID, - CONF_FOR, - CONF_ABOVE, - CONF_BELOW, -) -from homeassistant.core import HomeAssistant, CALLBACK_TYPE -from homeassistant.helpers import config_validation as cv, entity_registry -from homeassistant.helpers.typing import ConfigType from homeassistant.components.automation import ( - state as state_automation, - numeric_state as numeric_state_automation, AutomationActionType, + numeric_state as numeric_state_automation, + state as state_automation, ) from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA +from homeassistant.const import ( + CONF_ABOVE, + CONF_BELOW, + CONF_DEVICE_ID, + CONF_DOMAIN, + CONF_ENTITY_ID, + CONF_FOR, + CONF_PLATFORM, + CONF_TYPE, +) +from homeassistant.core import CALLBACK_TYPE, HomeAssistant +from homeassistant.helpers import config_validation as cv, entity_registry +from homeassistant.helpers.typing import ConfigType + from . import DOMAIN, const TRIGGER_TYPES = { diff --git a/homeassistant/components/climate/reproduce_state.py b/homeassistant/components/climate/reproduce_state.py index 34e72a27c92..82ca4f4e85c 100644 --- a/homeassistant/components/climate/reproduce_state.py +++ b/homeassistant/components/climate/reproduce_state.py @@ -8,20 +8,20 @@ from homeassistant.helpers.typing import HomeAssistantType from .const import ( ATTR_AUX_HEAT, + ATTR_HUMIDITY, + ATTR_HVAC_MODE, + ATTR_PRESET_MODE, + ATTR_SWING_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, - ATTR_PRESET_MODE, - ATTR_HVAC_MODE, - ATTR_SWING_MODE, - ATTR_HUMIDITY, + DOMAIN, HVAC_MODES, SERVICE_SET_AUX_HEAT, - SERVICE_SET_TEMPERATURE, - SERVICE_SET_PRESET_MODE, - SERVICE_SET_HVAC_MODE, - SERVICE_SET_SWING_MODE, SERVICE_SET_HUMIDITY, - DOMAIN, + SERVICE_SET_HVAC_MODE, + SERVICE_SET_PRESET_MODE, + SERVICE_SET_SWING_MODE, + SERVICE_SET_TEMPERATURE, ) diff --git a/tests/components/climate/common.py b/tests/components/climate/common.py index a5ea182f2b6..5b75dc98e69 100644 --- a/tests/components/climate/common.py +++ b/tests/components/climate/common.py @@ -25,9 +25,9 @@ from homeassistant.components.climate.const import ( from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_TEMPERATURE, + ENTITY_MATCH_ALL, SERVICE_TURN_OFF, SERVICE_TURN_ON, - ENTITY_MATCH_ALL, ) from homeassistant.loader import bind_hass diff --git a/tests/components/climate/test_device_action.py b/tests/components/climate/test_device_action.py index 46e8b3395c4..ff78b837591 100644 --- a/tests/components/climate/test_device_action.py +++ b/tests/components/climate/test_device_action.py @@ -2,18 +2,18 @@ import pytest import voluptuous_serialize -from homeassistant.components.climate import DOMAIN, const, device_action -from homeassistant.setup import async_setup_component import homeassistant.components.automation as automation -from homeassistant.helpers import device_registry, config_validation as cv +from homeassistant.components.climate import DOMAIN, const, device_action +from homeassistant.helpers import config_validation as cv, device_registry +from homeassistant.setup import async_setup_component from tests.common import ( MockConfigEntry, assert_lists_same, + async_get_device_automations, async_mock_service, mock_device_registry, mock_registry, - async_get_device_automations, ) diff --git a/tests/components/climate/test_device_condition.py b/tests/components/climate/test_device_condition.py index b0a9c6c283a..c8aaf0e1967 100644 --- a/tests/components/climate/test_device_condition.py +++ b/tests/components/climate/test_device_condition.py @@ -2,18 +2,18 @@ import pytest import voluptuous_serialize -from homeassistant.components.climate import DOMAIN, const, device_condition -from homeassistant.setup import async_setup_component import homeassistant.components.automation as automation -from homeassistant.helpers import device_registry, config_validation as cv +from homeassistant.components.climate import DOMAIN, const, device_condition +from homeassistant.helpers import config_validation as cv, device_registry +from homeassistant.setup import async_setup_component from tests.common import ( MockConfigEntry, assert_lists_same, + async_get_device_automations, async_mock_service, mock_device_registry, mock_registry, - async_get_device_automations, ) diff --git a/tests/components/climate/test_device_trigger.py b/tests/components/climate/test_device_trigger.py index 3b497912c52..d9bfd6d5ba4 100644 --- a/tests/components/climate/test_device_trigger.py +++ b/tests/components/climate/test_device_trigger.py @@ -1,19 +1,19 @@ """The tests for Climate device triggers.""" -import voluptuous_serialize import pytest +import voluptuous_serialize -from homeassistant.components.climate import DOMAIN, const, device_trigger -from homeassistant.setup import async_setup_component import homeassistant.components.automation as automation -from homeassistant.helpers import device_registry, config_validation as cv +from homeassistant.components.climate import DOMAIN, const, device_trigger +from homeassistant.helpers import config_validation as cv, device_registry +from homeassistant.setup import async_setup_component from tests.common import ( MockConfigEntry, assert_lists_same, + async_get_device_automations, async_mock_service, mock_device_registry, mock_registry, - async_get_device_automations, ) diff --git a/tests/components/climate/test_init.py b/tests/components/climate/test_init.py index b044753c891..4345ecedcf7 100644 --- a/tests/components/climate/test_init.py +++ b/tests/components/climate/test_init.py @@ -1,16 +1,17 @@ """The tests for the climate component.""" -from unittest.mock import MagicMock from typing import List +from unittest.mock import MagicMock import pytest import voluptuous as vol from homeassistant.components.climate import ( - SET_TEMPERATURE_SCHEMA, - ClimateDevice, HVAC_MODE_HEAT, HVAC_MODE_OFF, + SET_TEMPERATURE_SCHEMA, + ClimateDevice, ) + from tests.common import async_mock_service diff --git a/tests/components/climate/test_reproduce_state.py b/tests/components/climate/test_reproduce_state.py index fe995868840..df0b6314d63 100644 --- a/tests/components/climate/test_reproduce_state.py +++ b/tests/components/climate/test_reproduce_state.py @@ -2,7 +2,6 @@ import pytest -from homeassistant.components.climate.reproduce_state import async_reproduce_states from homeassistant.components.climate.const import ( ATTR_AUX_HEAT, ATTR_HUMIDITY, @@ -21,6 +20,7 @@ from homeassistant.components.climate.const import ( SERVICE_SET_SWING_MODE, SERVICE_SET_TEMPERATURE, ) +from homeassistant.components.climate.reproduce_state import async_reproduce_states from homeassistant.const import ATTR_TEMPERATURE from homeassistant.core import Context, State From 73c373a0f2fc880516821853eb09acbd872c3d54 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 17:55:57 +0100 Subject: [PATCH 099/677] use isort to sort imports according to PEP8 for command_line (#29627) --- homeassistant/components/command_line/cover.py | 4 ++-- homeassistant/components/command_line/notify.py | 3 +-- homeassistant/components/command_line/switch.py | 13 ++++++------- tests/components/command_line/test_binary_sensor.py | 2 +- tests/components/command_line/test_cover.py | 2 +- tests/components/command_line/test_notify.py | 3 ++- tests/components/command_line/test_sensor.py | 3 ++- tests/components/command_line/test_switch.py | 6 +++--- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/command_line/cover.py b/homeassistant/components/command_line/cover.py index c4413e78a00..1d996614caa 100644 --- a/homeassistant/components/command_line/cover.py +++ b/homeassistant/components/command_line/cover.py @@ -4,15 +4,15 @@ import subprocess import voluptuous as vol -from homeassistant.components.cover import CoverDevice, PLATFORM_SCHEMA +from homeassistant.components.cover import PLATFORM_SCHEMA, CoverDevice from homeassistant.const import ( CONF_COMMAND_CLOSE, CONF_COMMAND_OPEN, CONF_COMMAND_STATE, CONF_COMMAND_STOP, CONF_COVERS, - CONF_VALUE_TEMPLATE, CONF_FRIENDLY_NAME, + CONF_VALUE_TEMPLATE, ) import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/command_line/notify.py b/homeassistant/components/command_line/notify.py index e2581c8f065..21653171f34 100644 --- a/homeassistant/components/command_line/notify.py +++ b/homeassistant/components/command_line/notify.py @@ -4,11 +4,10 @@ import subprocess import voluptuous as vol +from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationService from homeassistant.const import CONF_COMMAND, CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationService - _LOGGER = logging.getLogger(__name__) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( diff --git a/homeassistant/components/command_line/switch.py b/homeassistant/components/command_line/switch.py index 937e859197a..62dcbe2f15a 100644 --- a/homeassistant/components/command_line/switch.py +++ b/homeassistant/components/command_line/switch.py @@ -4,21 +4,20 @@ import subprocess import voluptuous as vol -import homeassistant.helpers.config_validation as cv - from homeassistant.components.switch import ( - SwitchDevice, - PLATFORM_SCHEMA, ENTITY_ID_FORMAT, + PLATFORM_SCHEMA, + SwitchDevice, ) from homeassistant.const import ( - CONF_FRIENDLY_NAME, - CONF_SWITCHES, - CONF_VALUE_TEMPLATE, CONF_COMMAND_OFF, CONF_COMMAND_ON, CONF_COMMAND_STATE, + CONF_FRIENDLY_NAME, + CONF_SWITCHES, + CONF_VALUE_TEMPLATE, ) +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/command_line/test_binary_sensor.py b/tests/components/command_line/test_binary_sensor.py index 333404cf904..33c28b7d65a 100644 --- a/tests/components/command_line/test_binary_sensor.py +++ b/tests/components/command_line/test_binary_sensor.py @@ -1,8 +1,8 @@ """The tests for the Command line Binary sensor platform.""" import unittest -from homeassistant.const import STATE_ON, STATE_OFF from homeassistant.components.command_line import binary_sensor as command_line +from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.helpers import template from tests.common import get_test_home_assistant diff --git a/tests/components/command_line/test_cover.py b/tests/components/command_line/test_cover.py index 05b77a1c85d..662ab0c969c 100644 --- a/tests/components/command_line/test_cover.py +++ b/tests/components/command_line/test_cover.py @@ -5,8 +5,8 @@ from unittest import mock import pytest -from homeassistant.components.cover import DOMAIN import homeassistant.components.command_line.cover as cmd_rs +from homeassistant.components.cover import DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, SERVICE_CLOSE_COVER, diff --git a/tests/components/command_line/test_notify.py b/tests/components/command_line/test_notify.py index 964a053f403..8bdc4ba0a01 100644 --- a/tests/components/command_line/test_notify.py +++ b/tests/components/command_line/test_notify.py @@ -4,8 +4,9 @@ import tempfile import unittest from unittest.mock import patch -from homeassistant.setup import setup_component import homeassistant.components.notify as notify +from homeassistant.setup import setup_component + from tests.common import assert_setup_component, get_test_home_assistant diff --git a/tests/components/command_line/test_sensor.py b/tests/components/command_line/test_sensor.py index 58a170cb37a..e51c0187460 100644 --- a/tests/components/command_line/test_sensor.py +++ b/tests/components/command_line/test_sensor.py @@ -2,8 +2,9 @@ import unittest from unittest.mock import patch -from homeassistant.helpers.template import Template from homeassistant.components.command_line import sensor as command_line +from homeassistant.helpers.template import Template + from tests.common import get_test_home_assistant diff --git a/tests/components/command_line/test_switch.py b/tests/components/command_line/test_switch.py index ef010081958..497fb0c2523 100644 --- a/tests/components/command_line/test_switch.py +++ b/tests/components/command_line/test_switch.py @@ -4,10 +4,10 @@ import os import tempfile import unittest -from homeassistant.setup import setup_component -from homeassistant.const import STATE_ON, STATE_OFF -import homeassistant.components.switch as switch import homeassistant.components.command_line.switch as command_line +import homeassistant.components.switch as switch +from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.setup import setup_component from tests.common import get_test_home_assistant from tests.components.switch import common From f355570f17c05798e4380565e00b278ca01df625 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 17:57:28 +0100 Subject: [PATCH 100/677] use isort to sort imports according to PEP8 for config (#29628) --- homeassistant/components/config/__init__.py | 4 ++-- homeassistant/components/config/area_registry.py | 1 - homeassistant/components/config/auth.py | 1 - .../components/config/auth_provider_homeassistant.py | 1 - homeassistant/components/config/automation.py | 2 +- homeassistant/components/config/core.py | 4 ++-- homeassistant/components/config/entity_registry.py | 4 ++-- homeassistant/components/config/group.py | 2 +- homeassistant/components/config/scene.py | 2 +- homeassistant/components/config/script.py | 2 +- tests/components/config/test_area_registry.py | 1 + tests/components/config/test_auth.py | 2 +- tests/components/config/test_config_entries.py | 6 +++--- tests/components/config/test_core.py | 1 + tests/components/config/test_device_registry.py | 1 + tests/components/config/test_entity_registry.py | 5 +++-- tests/components/config/test_group.py | 3 +-- tests/components/config/test_init.py | 6 +++--- tests/components/config/test_zwave.py | 3 +-- 19 files changed, 25 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/config/__init__.py b/homeassistant/components/config/__init__.py index 5a66c1fc5d4..5873cdc3271 100644 --- a/homeassistant/components/config/__init__.py +++ b/homeassistant/components/config/__init__.py @@ -6,11 +6,11 @@ import os import voluptuous as vol from homeassistant.components.http import HomeAssistantView -from homeassistant.const import EVENT_COMPONENT_LOADED, CONF_ID +from homeassistant.const import CONF_ID, EVENT_COMPONENT_LOADED from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError from homeassistant.setup import ATTR_COMPONENT -from homeassistant.util.yaml import load_yaml, dump +from homeassistant.util.yaml import dump, load_yaml DOMAIN = "config" SECTIONS = ( diff --git a/homeassistant/components/config/area_registry.py b/homeassistant/components/config/area_registry.py index 9c8853ac782..81daf35339e 100644 --- a/homeassistant/components/config/area_registry.py +++ b/homeassistant/components/config/area_registry.py @@ -9,7 +9,6 @@ from homeassistant.components.websocket_api.decorators import ( from homeassistant.core import callback from homeassistant.helpers.area_registry import async_get_registry - WS_TYPE_LIST = "config/area_registry/list" SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( {vol.Required("type"): WS_TYPE_LIST} diff --git a/homeassistant/components/config/auth.py b/homeassistant/components/config/auth.py index 977bae36083..361367ffb4d 100644 --- a/homeassistant/components/config/auth.py +++ b/homeassistant/components/config/auth.py @@ -3,7 +3,6 @@ import voluptuous as vol from homeassistant.components import websocket_api - WS_TYPE_LIST = "config/auth/list" SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( {vol.Required("type"): WS_TYPE_LIST} diff --git a/homeassistant/components/config/auth_provider_homeassistant.py b/homeassistant/components/config/auth_provider_homeassistant.py index 817675db238..dec7fb24d27 100644 --- a/homeassistant/components/config/auth_provider_homeassistant.py +++ b/homeassistant/components/config/auth_provider_homeassistant.py @@ -4,7 +4,6 @@ import voluptuous as vol from homeassistant.auth.providers import homeassistant as auth_ha from homeassistant.components import websocket_api - WS_TYPE_CREATE = "config/auth_provider/homeassistant/create" SCHEMA_WS_CREATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( { diff --git a/homeassistant/components/config/automation.py b/homeassistant/components/config/automation.py index 0e9b4053b7b..d7bb1ef9883 100644 --- a/homeassistant/components/config/automation.py +++ b/homeassistant/components/config/automation.py @@ -4,8 +4,8 @@ import uuid from homeassistant.components.automation import DOMAIN, PLATFORM_SCHEMA from homeassistant.components.automation.config import async_validate_config_item -from homeassistant.const import CONF_ID, SERVICE_RELOAD from homeassistant.config import AUTOMATION_CONFIG_PATH +from homeassistant.const import CONF_ID, SERVICE_RELOAD import homeassistant.helpers.config_validation as cv from . import EditIdBasedConfigView diff --git a/homeassistant/components/config/core.py b/homeassistant/components/config/core.py index 073f8f23d6c..e9ceb7eac57 100644 --- a/homeassistant/components/config/core.py +++ b/homeassistant/components/config/core.py @@ -2,10 +2,10 @@ import voluptuous as vol +from homeassistant.components import websocket_api from homeassistant.components.http import HomeAssistantView from homeassistant.config import async_check_ha_config_file -from homeassistant.components import websocket_api -from homeassistant.const import CONF_UNIT_SYSTEM_METRIC, CONF_UNIT_SYSTEM_IMPERIAL +from homeassistant.const import CONF_UNIT_SYSTEM_IMPERIAL, CONF_UNIT_SYSTEM_METRIC from homeassistant.helpers import config_validation as cv from homeassistant.util import location diff --git a/homeassistant/components/config/entity_registry.py b/homeassistant/components/config/entity_registry.py index 125b2260f08..458a9dd3ecb 100644 --- a/homeassistant/components/config/entity_registry.py +++ b/homeassistant/components/config/entity_registry.py @@ -1,15 +1,15 @@ """HTTP views to interact with the entity registry.""" import voluptuous as vol -from homeassistant.core import callback -from homeassistant.helpers.entity_registry import async_get_registry from homeassistant.components import websocket_api from homeassistant.components.websocket_api.const import ERR_NOT_FOUND from homeassistant.components.websocket_api.decorators import ( async_response, require_admin, ) +from homeassistant.core import callback from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.entity_registry import async_get_registry async def async_setup(hass): diff --git a/homeassistant/components/config/group.py b/homeassistant/components/config/group.py index d104cd2e1df..d95891af655 100644 --- a/homeassistant/components/config/group.py +++ b/homeassistant/components/config/group.py @@ -1,7 +1,7 @@ """Provide configuration end points for Groups.""" from homeassistant.components.group import DOMAIN, GROUP_SCHEMA -from homeassistant.const import SERVICE_RELOAD from homeassistant.config import GROUP_CONFIG_PATH +from homeassistant.const import SERVICE_RELOAD import homeassistant.helpers.config_validation as cv from . import EditKeyBasedConfigView diff --git a/homeassistant/components/config/scene.py b/homeassistant/components/config/scene.py index 6e77dae0826..79a30177e47 100644 --- a/homeassistant/components/config/scene.py +++ b/homeassistant/components/config/scene.py @@ -3,8 +3,8 @@ from collections import OrderedDict import uuid from homeassistant.components.scene import DOMAIN, PLATFORM_SCHEMA -from homeassistant.const import CONF_ID, SERVICE_RELOAD from homeassistant.config import SCENE_CONFIG_PATH +from homeassistant.const import CONF_ID, SERVICE_RELOAD import homeassistant.helpers.config_validation as cv from . import EditIdBasedConfigView diff --git a/homeassistant/components/config/script.py b/homeassistant/components/config/script.py index e63651d8f2a..032774de473 100644 --- a/homeassistant/components/config/script.py +++ b/homeassistant/components/config/script.py @@ -1,7 +1,7 @@ """Provide configuration end points for scripts.""" from homeassistant.components.script import DOMAIN, SCRIPT_ENTRY_SCHEMA -from homeassistant.const import SERVICE_RELOAD from homeassistant.config import SCRIPT_CONFIG_PATH +from homeassistant.const import SERVICE_RELOAD import homeassistant.helpers.config_validation as cv from . import EditKeyBasedConfigView diff --git a/tests/components/config/test_area_registry.py b/tests/components/config/test_area_registry.py index 0505597c6ff..f66e16e606f 100644 --- a/tests/components/config/test_area_registry.py +++ b/tests/components/config/test_area_registry.py @@ -2,6 +2,7 @@ import pytest from homeassistant.components.config import area_registry + from tests.common import mock_area_registry diff --git a/tests/components/config/test_auth.py b/tests/components/config/test_auth.py index 4d90f345657..b07df39a8fe 100644 --- a/tests/components/config/test_auth.py +++ b/tests/components/config/test_auth.py @@ -4,7 +4,7 @@ import pytest from homeassistant.auth import models as auth_models from homeassistant.components.config import auth as auth_config -from tests.common import MockGroup, MockUser, CLIENT_ID +from tests.common import CLIENT_ID, MockGroup, MockUser @pytest.fixture(autouse=True) diff --git a/tests/components/config/test_config_entries.py b/tests/components/config/test_config_entries.py index 3d22d3ac1a7..6176bd73c52 100644 --- a/tests/components/config/test_config_entries.py +++ b/tests/components/config/test_config_entries.py @@ -8,18 +8,18 @@ import pytest import voluptuous as vol from homeassistant import config_entries as core_ce, data_entry_flow +from homeassistant.components.config import config_entries from homeassistant.config_entries import HANDLERS from homeassistant.core import callback -from homeassistant.setup import async_setup_component -from homeassistant.components.config import config_entries from homeassistant.generated import config_flows +from homeassistant.setup import async_setup_component from tests.common import ( MockConfigEntry, MockModule, mock_coro_func, - mock_integration, mock_entity_platform, + mock_integration, ) diff --git a/tests/components/config/test_core.py b/tests/components/config/test_core.py index 050190d8dbe..8caa0f3e6fb 100644 --- a/tests/components/config/test_core.py +++ b/tests/components/config/test_core.py @@ -8,6 +8,7 @@ from homeassistant.components import config from homeassistant.components.websocket_api.const import TYPE_RESULT from homeassistant.const import CONF_UNIT_SYSTEM, CONF_UNIT_SYSTEM_IMPERIAL from homeassistant.util import dt as dt_util, location + from tests.common import mock_coro ORIG_TIME_ZONE = dt_util.DEFAULT_TIME_ZONE diff --git a/tests/components/config/test_device_registry.py b/tests/components/config/test_device_registry.py index cd305e5b567..a3710d48b94 100644 --- a/tests/components/config/test_device_registry.py +++ b/tests/components/config/test_device_registry.py @@ -2,6 +2,7 @@ import pytest from homeassistant.components.config import device_registry + from tests.common import mock_device_registry diff --git a/tests/components/config/test_entity_registry.py b/tests/components/config/test_entity_registry.py index 9472d888254..133c88d9ceb 100644 --- a/tests/components/config/test_entity_registry.py +++ b/tests/components/config/test_entity_registry.py @@ -3,9 +3,10 @@ from collections import OrderedDict import pytest -from homeassistant.helpers.entity_registry import RegistryEntry from homeassistant.components.config import entity_registry -from tests.common import mock_registry, MockEntity, MockEntityPlatform +from homeassistant.helpers.entity_registry import RegistryEntry + +from tests.common import MockEntity, MockEntityPlatform, mock_registry @pytest.fixture diff --git a/tests/components/config/test_group.py b/tests/components/config/test_group.py index 3240dbe9c13..3a4a145105a 100644 --- a/tests/components/config/test_group.py +++ b/tests/components/config/test_group.py @@ -1,12 +1,11 @@ """Test Group config panel.""" import asyncio import json -from unittest.mock import patch, MagicMock +from unittest.mock import MagicMock, patch from homeassistant.bootstrap import async_setup_component from homeassistant.components import config - VIEW_NAME = "api:config:group:config" diff --git a/tests/components/config/test_init.py b/tests/components/config/test_init.py index 8e5fab494eb..21af45a1203 100644 --- a/tests/components/config/test_init.py +++ b/tests/components/config/test_init.py @@ -2,11 +2,11 @@ import asyncio from unittest.mock import patch -from homeassistant.const import EVENT_COMPONENT_LOADED -from homeassistant.setup import async_setup_component, ATTR_COMPONENT from homeassistant.components import config +from homeassistant.const import EVENT_COMPONENT_LOADED +from homeassistant.setup import ATTR_COMPONENT, async_setup_component -from tests.common import mock_coro, mock_component +from tests.common import mock_component, mock_coro @asyncio.coroutine diff --git a/tests/components/config/test_zwave.py b/tests/components/config/test_zwave.py index 9f62c3f9ac2..c2490de23ea 100644 --- a/tests/components/config/test_zwave.py +++ b/tests/components/config/test_zwave.py @@ -7,10 +7,9 @@ import pytest from homeassistant.bootstrap import async_setup_component from homeassistant.components import config - from homeassistant.components.zwave import DATA_NETWORK, const -from tests.mock.zwave import MockNode, MockValue, MockEntityValues +from tests.mock.zwave import MockEntityValues, MockNode, MockValue VIEW_NAME = "api:config:zwave:device_config" From a3b605bb7db37eb51d512766a022f9917786dd15 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 17:58:00 +0100 Subject: [PATCH 101/677] use isort to sort imports according to PEP8 for cover (#29629) --- homeassistant/components/cover/__init__.py | 41 +++++++++---------- .../components/cover/device_condition.py | 12 +++--- .../components/cover/device_trigger.py | 22 +++++----- homeassistant/components/cover/intent.py | 2 +- .../components/cover/test_device_condition.py | 12 +++--- tests/components/cover/test_device_trigger.py | 8 ++-- tests/components/cover/test_intent.py | 3 +- .../components/cover/test_reproduce_state.py | 1 + 8 files changed, 53 insertions(+), 48 deletions(-) diff --git a/homeassistant/components/cover/__init__.py b/homeassistant/components/cover/__init__.py index 0b8fbfa9dd2..3c842067cca 100644 --- a/homeassistant/components/cover/__init__.py +++ b/homeassistant/components/cover/__init__.py @@ -6,31 +6,30 @@ from typing import Any import voluptuous as vol -from homeassistant.loader import bind_hass -from homeassistant.helpers.entity_component import EntityComponent -from homeassistant.helpers.entity import Entity +from homeassistant.components import group +from homeassistant.const import ( + SERVICE_CLOSE_COVER, + SERVICE_CLOSE_COVER_TILT, + SERVICE_OPEN_COVER, + SERVICE_OPEN_COVER_TILT, + SERVICE_SET_COVER_POSITION, + SERVICE_SET_COVER_TILT_POSITION, + SERVICE_STOP_COVER, + SERVICE_STOP_COVER_TILT, + SERVICE_TOGGLE, + SERVICE_TOGGLE_COVER_TILT, + STATE_CLOSED, + STATE_CLOSING, + STATE_OPEN, + STATE_OPENING, +) from homeassistant.helpers.config_validation import ( # noqa: F401 PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE, ) -from homeassistant.components import group -from homeassistant.const import ( - SERVICE_OPEN_COVER, - SERVICE_CLOSE_COVER, - SERVICE_SET_COVER_POSITION, - SERVICE_STOP_COVER, - SERVICE_TOGGLE, - SERVICE_OPEN_COVER_TILT, - SERVICE_CLOSE_COVER_TILT, - SERVICE_STOP_COVER_TILT, - SERVICE_SET_COVER_TILT_POSITION, - SERVICE_TOGGLE_COVER_TILT, - STATE_OPEN, - STATE_CLOSED, - STATE_OPENING, - STATE_CLOSING, -) - +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.loader import bind_hass # mypy: allow-untyped-calls, allow-untyped-defs, no-check-untyped-defs diff --git a/homeassistant/components/cover/device_condition.py b/homeassistant/components/cover/device_condition.py index 487f815afb5..ec6da84e5f6 100644 --- a/homeassistant/components/cover/device_condition.py +++ b/homeassistant/components/cover/device_condition.py @@ -1,5 +1,6 @@ """Provides device automations for Cover.""" from typing import Any, Dict, List + import voluptuous as vol from homeassistant.const import ( @@ -8,14 +9,14 @@ from homeassistant.const import ( CONF_ABOVE, CONF_BELOW, CONF_CONDITION, - CONF_DOMAIN, - CONF_TYPE, CONF_DEVICE_ID, + CONF_DOMAIN, CONF_ENTITY_ID, - STATE_OPEN, + CONF_TYPE, STATE_CLOSED, - STATE_OPENING, STATE_CLOSING, + STATE_OPEN, + STATE_OPENING, ) from homeassistant.core import HomeAssistant from homeassistant.helpers import ( @@ -24,8 +25,9 @@ from homeassistant.helpers import ( entity_registry, template, ) -from homeassistant.helpers.typing import ConfigType, TemplateVarsType from homeassistant.helpers.config_validation import DEVICE_CONDITION_BASE_SCHEMA +from homeassistant.helpers.typing import ConfigType, TemplateVarsType + from . import ( DOMAIN, SUPPORT_CLOSE, diff --git a/homeassistant/components/cover/device_trigger.py b/homeassistant/components/cover/device_trigger.py index 4f256a87dc5..988427003e7 100644 --- a/homeassistant/components/cover/device_trigger.py +++ b/homeassistant/components/cover/device_trigger.py @@ -1,30 +1,32 @@ """Provides device automations for Cover.""" from typing import List + import voluptuous as vol +from homeassistant.components.automation import ( + AutomationActionType, + numeric_state as numeric_state_automation, + state as state_automation, +) +from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA from homeassistant.const import ( ATTR_SUPPORTED_FEATURES, CONF_ABOVE, CONF_BELOW, - CONF_DOMAIN, - CONF_TYPE, - CONF_PLATFORM, CONF_DEVICE_ID, + CONF_DOMAIN, CONF_ENTITY_ID, + CONF_PLATFORM, + CONF_TYPE, STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_OPENING, ) -from homeassistant.core import HomeAssistant, CALLBACK_TYPE +from homeassistant.core import CALLBACK_TYPE, HomeAssistant from homeassistant.helpers import config_validation as cv, entity_registry from homeassistant.helpers.typing import ConfigType -from homeassistant.components.automation import ( - state as state_automation, - numeric_state as numeric_state_automation, - AutomationActionType, -) -from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA + from . import ( DOMAIN, SUPPORT_CLOSE, diff --git a/homeassistant/components/cover/intent.py b/homeassistant/components/cover/intent.py index f8d13e6a90e..36402025bfa 100644 --- a/homeassistant/components/cover/intent.py +++ b/homeassistant/components/cover/intent.py @@ -2,7 +2,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import intent -from . import DOMAIN, SERVICE_OPEN_COVER, SERVICE_CLOSE_COVER +from . import DOMAIN, SERVICE_CLOSE_COVER, SERVICE_OPEN_COVER INTENT_OPEN_COVER = "HassOpenCover" INTENT_CLOSE_COVER = "HassCloseCover" diff --git a/tests/components/cover/test_device_condition.py b/tests/components/cover/test_device_condition.py index 8ca912b640b..13c6fd8701f 100644 --- a/tests/components/cover/test_device_condition.py +++ b/tests/components/cover/test_device_condition.py @@ -1,26 +1,26 @@ """The tests for Cover device conditions.""" import pytest +import homeassistant.components.automation as automation from homeassistant.components.cover import DOMAIN from homeassistant.const import ( CONF_PLATFORM, - STATE_OPEN, STATE_CLOSED, - STATE_OPENING, STATE_CLOSING, + STATE_OPEN, + STATE_OPENING, ) -from homeassistant.setup import async_setup_component -import homeassistant.components.automation as automation from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component from tests.common import ( MockConfigEntry, assert_lists_same, + async_get_device_automation_capabilities, + async_get_device_automations, async_mock_service, mock_device_registry, mock_registry, - async_get_device_automations, - async_get_device_automation_capabilities, ) diff --git a/tests/components/cover/test_device_trigger.py b/tests/components/cover/test_device_trigger.py index 4f50c0639c0..3f82babc2ed 100644 --- a/tests/components/cover/test_device_trigger.py +++ b/tests/components/cover/test_device_trigger.py @@ -1,6 +1,7 @@ """The tests for Cover device triggers.""" import pytest +import homeassistant.components.automation as automation from homeassistant.components.cover import DOMAIN from homeassistant.const import ( CONF_PLATFORM, @@ -9,18 +10,17 @@ from homeassistant.const import ( STATE_OPEN, STATE_OPENING, ) -from homeassistant.setup import async_setup_component -import homeassistant.components.automation as automation from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component from tests.common import ( MockConfigEntry, assert_lists_same, + async_get_device_automation_capabilities, + async_get_device_automations, async_mock_service, mock_device_registry, mock_registry, - async_get_device_automations, - async_get_device_automation_capabilities, ) diff --git a/tests/components/cover/test_intent.py b/tests/components/cover/test_intent.py index ce01e882941..29d3378a0f9 100644 --- a/tests/components/cover/test_intent.py +++ b/tests/components/cover/test_intent.py @@ -1,11 +1,12 @@ """The tests for the cover platform.""" from homeassistant.components.cover import ( - SERVICE_OPEN_COVER, SERVICE_CLOSE_COVER, + SERVICE_OPEN_COVER, intent as cover_intent, ) from homeassistant.helpers import intent + from tests.common import async_mock_service diff --git a/tests/components/cover/test_reproduce_state.py b/tests/components/cover/test_reproduce_state.py index 39fdf3d3992..2e2d0f63467 100644 --- a/tests/components/cover/test_reproduce_state.py +++ b/tests/components/cover/test_reproduce_state.py @@ -16,6 +16,7 @@ from homeassistant.const import ( STATE_OPEN, ) from homeassistant.core import State + from tests.common import async_mock_service From e4e9cdce7303f4412c5213a7db6ac5d61f0bde47 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 17:59:27 +0100 Subject: [PATCH 102/677] use isort to sort imports according to PEP8 for demo (#29630) --- homeassistant/components/demo/__init__.py | 2 +- .../components/demo/binary_sensor.py | 1 + homeassistant/components/demo/calendar.py | 3 +-- homeassistant/components/demo/climate.py | 4 +++- homeassistant/components/demo/cover.py | 4 ++-- homeassistant/components/demo/fan.py | 3 +-- homeassistant/components/demo/geo_location.py | 3 +-- .../components/demo/image_processing.py | 6 ++--- homeassistant/components/demo/lock.py | 3 +-- homeassistant/components/demo/sensor.py | 3 ++- homeassistant/components/demo/switch.py | 1 + homeassistant/components/demo/water_heater.py | 3 +-- tests/components/demo/test_camera.py | 2 +- tests/components/demo/test_cover.py | 14 +++++------ tests/components/demo/test_fan.py | 2 +- tests/components/demo/test_geo_location.py | 7 +++--- tests/components/demo/test_init.py | 2 +- tests/components/demo/test_light.py | 2 +- tests/components/demo/test_lock.py | 2 +- tests/components/demo/test_media_player.py | 4 ++-- tests/components/demo/test_notify.py | 4 ++-- tests/components/demo/test_remote.py | 4 ++-- tests/components/demo/test_stt.py | 2 +- tests/components/demo/test_vacuum.py | 23 +++++++++---------- tests/components/demo/test_water_heater.py | 5 ++-- 25 files changed, 54 insertions(+), 55 deletions(-) diff --git a/homeassistant/components/demo/__init__.py b/homeassistant/components/demo/__init__.py index 05febfad603..b6845d9d6a4 100644 --- a/homeassistant/components/demo/__init__.py +++ b/homeassistant/components/demo/__init__.py @@ -4,8 +4,8 @@ import logging import time from homeassistant import bootstrap, config_entries -import homeassistant.core as ha from homeassistant.const import ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_START +import homeassistant.core as ha DOMAIN = "demo" _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/demo/binary_sensor.py b/homeassistant/components/demo/binary_sensor.py index c1e42807f6d..0f6dfa9f357 100644 --- a/homeassistant/components/demo/binary_sensor.py +++ b/homeassistant/components/demo/binary_sensor.py @@ -1,5 +1,6 @@ """Demo platform that has two fake binary sensors.""" from homeassistant.components.binary_sensor import BinarySensorDevice + from . import DOMAIN diff --git a/homeassistant/components/demo/calendar.py b/homeassistant/components/demo/calendar.py index 4ae836466f0..42cb2b137a1 100644 --- a/homeassistant/components/demo/calendar.py +++ b/homeassistant/components/demo/calendar.py @@ -1,9 +1,8 @@ """Demo platform that has two fake binary sensors.""" import copy -import homeassistant.util.dt as dt_util - from homeassistant.components.calendar import CalendarEventDevice, get_date +import homeassistant.util.dt as dt_util def setup_platform(hass, config, add_entities, discovery_info=None): diff --git a/homeassistant/components/demo/climate.py b/homeassistant/components/demo/climate.py index f4affed7ced..0edcf618ba6 100644 --- a/homeassistant/components/demo/climate.py +++ b/homeassistant/components/demo/climate.py @@ -1,11 +1,13 @@ """Demo platform that offers a fake climate device.""" import logging + from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, CURRENT_HVAC_COOL, CURRENT_HVAC_HEAT, + HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, @@ -18,9 +20,9 @@ from homeassistant.components.climate.const import ( SUPPORT_TARGET_HUMIDITY, SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_RANGE, - HVAC_MODE_AUTO, ) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT + from . import DOMAIN SUPPORT_FLAGS = 0 diff --git a/homeassistant/components/demo/cover.py b/homeassistant/components/demo/cover.py index 20a8747aaa5..20e3a52aa8d 100644 --- a/homeassistant/components/demo/cover.py +++ b/homeassistant/components/demo/cover.py @@ -1,6 +1,4 @@ """Demo platform for the cover component.""" -from homeassistant.helpers.event import track_utc_time_change - from homeassistant.components.cover import ( ATTR_POSITION, ATTR_TILT_POSITION, @@ -8,6 +6,8 @@ from homeassistant.components.cover import ( SUPPORT_OPEN, CoverDevice, ) +from homeassistant.helpers.event import track_utc_time_change + from . import DOMAIN diff --git a/homeassistant/components/demo/fan.py b/homeassistant/components/demo/fan.py index 500d5f6a5ce..966ba51cacb 100644 --- a/homeassistant/components/demo/fan.py +++ b/homeassistant/components/demo/fan.py @@ -1,6 +1,4 @@ """Demo fan platform that has a fake fan.""" -from homeassistant.const import STATE_OFF - from homeassistant.components.fan import ( SPEED_HIGH, SPEED_LOW, @@ -10,6 +8,7 @@ from homeassistant.components.fan import ( SUPPORT_SET_SPEED, FanEntity, ) +from homeassistant.const import STATE_OFF FULL_SUPPORT = SUPPORT_SET_SPEED | SUPPORT_OSCILLATE | SUPPORT_DIRECTION LIMITED_SUPPORT = SUPPORT_SET_SPEED diff --git a/homeassistant/components/demo/geo_location.py b/homeassistant/components/demo/geo_location.py index 6a7aa7ddce1..6fc8e9c2e89 100644 --- a/homeassistant/components/demo/geo_location.py +++ b/homeassistant/components/demo/geo_location.py @@ -5,9 +5,8 @@ from math import cos, pi, radians, sin import random from typing import Optional -from homeassistant.helpers.event import track_time_interval - from homeassistant.components.geo_location import GeolocationEvent +from homeassistant.helpers.event import track_time_interval _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/demo/image_processing.py b/homeassistant/components/demo/image_processing.py index 348045e47b2..9183609509e 100644 --- a/homeassistant/components/demo/image_processing.py +++ b/homeassistant/components/demo/image_processing.py @@ -1,10 +1,10 @@ """Support for the demo image processing.""" from homeassistant.components.image_processing import ( - ImageProcessingFaceEntity, - ATTR_CONFIDENCE, - ATTR_NAME, ATTR_AGE, + ATTR_CONFIDENCE, ATTR_GENDER, + ATTR_NAME, + ImageProcessingFaceEntity, ) from homeassistant.components.openalpr_local.image_processing import ( ImageProcessingAlprEntity, diff --git a/homeassistant/components/demo/lock.py b/homeassistant/components/demo/lock.py index 923469f045c..5074741d83d 100644 --- a/homeassistant/components/demo/lock.py +++ b/homeassistant/components/demo/lock.py @@ -1,7 +1,6 @@ """Demo lock platform that has two fake locks.""" -from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED - from homeassistant.components.lock import SUPPORT_OPEN, LockDevice +from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): diff --git a/homeassistant/components/demo/sensor.py b/homeassistant/components/demo/sensor.py index bf5df94e74c..d2b2464468b 100644 --- a/homeassistant/components/demo/sensor.py +++ b/homeassistant/components/demo/sensor.py @@ -1,11 +1,12 @@ """Demo platform that has a couple of fake sensors.""" from homeassistant.const import ( ATTR_BATTERY_LEVEL, - TEMP_CELSIUS, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, + TEMP_CELSIUS, ) from homeassistant.helpers.entity import Entity + from . import DOMAIN diff --git a/homeassistant/components/demo/switch.py b/homeassistant/components/demo/switch.py index 23006cff875..5c651198f5c 100644 --- a/homeassistant/components/demo/switch.py +++ b/homeassistant/components/demo/switch.py @@ -1,6 +1,7 @@ """Demo platform that has two fake switches.""" from homeassistant.components.switch import SwitchDevice from homeassistant.const import DEVICE_DEFAULT_NAME + from . import DOMAIN diff --git a/homeassistant/components/demo/water_heater.py b/homeassistant/components/demo/water_heater.py index c3fff26c992..f9aca141245 100644 --- a/homeassistant/components/demo/water_heater.py +++ b/homeassistant/components/demo/water_heater.py @@ -1,12 +1,11 @@ """Demo platform that offers a fake water heater device.""" -from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT - from homeassistant.components.water_heater import ( SUPPORT_AWAY_MODE, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, WaterHeaterDevice, ) +from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT SUPPORT_FLAGS_HEATER = ( SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE | SUPPORT_AWAY_MODE diff --git a/tests/components/demo/test_camera.py b/tests/components/demo/test_camera.py index 7a3cf426dac..286a1c8ca22 100644 --- a/tests/components/demo/test_camera.py +++ b/tests/components/demo/test_camera.py @@ -4,7 +4,7 @@ from unittest.mock import mock_open, patch import pytest from homeassistant.components import camera -from homeassistant.components.camera import STATE_STREAMING, STATE_IDLE +from homeassistant.components.camera import STATE_IDLE, STATE_STREAMING from homeassistant.exceptions import HomeAssistantError from homeassistant.setup import async_setup_component diff --git a/tests/components/demo/test_cover.py b/tests/components/demo/test_cover.py index 45a194eab7c..e2478f64f3a 100644 --- a/tests/components/demo/test_cover.py +++ b/tests/components/demo/test_cover.py @@ -4,29 +4,29 @@ from datetime import timedelta import pytest from homeassistant.components.cover import ( - ATTR_POSITION, ATTR_CURRENT_POSITION, ATTR_CURRENT_TILT_POSITION, + ATTR_POSITION, ATTR_TILT_POSITION, DOMAIN, ) from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, - STATE_OPEN, - STATE_OPENING, - STATE_CLOSED, - STATE_CLOSING, - SERVICE_TOGGLE, SERVICE_CLOSE_COVER, SERVICE_CLOSE_COVER_TILT, - SERVICE_TOGGLE_COVER_TILT, SERVICE_OPEN_COVER, SERVICE_OPEN_COVER_TILT, SERVICE_SET_COVER_POSITION, SERVICE_SET_COVER_TILT_POSITION, SERVICE_STOP_COVER, SERVICE_STOP_COVER_TILT, + SERVICE_TOGGLE, + SERVICE_TOGGLE_COVER_TILT, + STATE_CLOSED, + STATE_CLOSING, + STATE_OPEN, + STATE_OPENING, ) from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util diff --git a/tests/components/demo/test_fan.py b/tests/components/demo/test_fan.py index 3139c2a45db..71ec5c385dc 100644 --- a/tests/components/demo/test_fan.py +++ b/tests/components/demo/test_fan.py @@ -1,9 +1,9 @@ """Test cases around the demo fan platform.""" import pytest -from homeassistant.setup import async_setup_component from homeassistant.components import fan from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.setup import async_setup_component from tests.components.fan import common diff --git a/tests/components/demo/test_geo_location.py b/tests/components/demo/test_geo_location.py index a95d7e4e807..b7b11a7b46d 100644 --- a/tests/components/demo/test_geo_location.py +++ b/tests/components/demo/test_geo_location.py @@ -4,17 +4,18 @@ from unittest.mock import patch from homeassistant.components import geo_location from homeassistant.components.demo.geo_location import ( - NUMBER_OF_DEMO_DEVICES, DEFAULT_UNIT_OF_MEASUREMENT, DEFAULT_UPDATE_INTERVAL, + NUMBER_OF_DEMO_DEVICES, ) from homeassistant.setup import setup_component +import homeassistant.util.dt as dt_util + from tests.common import ( - get_test_home_assistant, assert_setup_component, fire_time_changed, + get_test_home_assistant, ) -import homeassistant.util.dt as dt_util CONFIG = {geo_location.DOMAIN: [{"platform": "demo"}]} diff --git a/tests/components/demo/test_init.py b/tests/components/demo/test_init.py index 5a420f76882..422ca55b399 100644 --- a/tests/components/demo/test_init.py +++ b/tests/components/demo/test_init.py @@ -4,10 +4,10 @@ import os import pytest -from homeassistant.setup import async_setup_component from homeassistant.components import demo from homeassistant.components.device_tracker.legacy import YAML_DEVICES from homeassistant.helpers.json import JSONEncoder +from homeassistant.setup import async_setup_component @pytest.fixture(autouse=True) diff --git a/tests/components/demo/test_light.py b/tests/components/demo/test_light.py index 34b340b600a..48409d6cc37 100644 --- a/tests/components/demo/test_light.py +++ b/tests/components/demo/test_light.py @@ -1,8 +1,8 @@ """The tests for the demo light component.""" import pytest -from homeassistant.setup import async_setup_component from homeassistant.components import light +from homeassistant.setup import async_setup_component from tests.components.light import common diff --git a/tests/components/demo/test_lock.py b/tests/components/demo/test_lock.py index 1c4264f1b53..279bd35d12a 100644 --- a/tests/components/demo/test_lock.py +++ b/tests/components/demo/test_lock.py @@ -1,8 +1,8 @@ """The tests for the Demo lock platform.""" import unittest -from homeassistant.setup import setup_component from homeassistant.components import lock +from homeassistant.setup import setup_component from tests.common import get_test_home_assistant, mock_service from tests.components.lock import common diff --git a/tests/components/demo/test_media_player.py b/tests/components/demo/test_media_player.py index 8ff96082f25..60402844d24 100644 --- a/tests/components/demo/test_media_player.py +++ b/tests/components/demo/test_media_player.py @@ -1,14 +1,14 @@ """The tests for the Demo Media player platform.""" +import asyncio import unittest from unittest.mock import patch -import asyncio import pytest import voluptuous as vol -from homeassistant.setup import setup_component, async_setup_component import homeassistant.components.media_player as mp from homeassistant.helpers.aiohttp_client import DATA_CLIENTSESSION +from homeassistant.setup import async_setup_component, setup_component from tests.common import get_test_home_assistant from tests.components.media_player import common diff --git a/tests/components/demo/test_notify.py b/tests/components/demo/test_notify.py index becfb49d2c1..30fb49be47d 100644 --- a/tests/components/demo/test_notify.py +++ b/tests/components/demo/test_notify.py @@ -5,11 +5,11 @@ from unittest.mock import patch import pytest import voluptuous as vol -import homeassistant.components.notify as notify -from homeassistant.setup import setup_component import homeassistant.components.demo.notify as demo +import homeassistant.components.notify as notify from homeassistant.core import callback from homeassistant.helpers import discovery, script +from homeassistant.setup import setup_component from tests.common import assert_setup_component, get_test_home_assistant from tests.components.notify import common diff --git a/tests/components/demo/test_remote.py b/tests/components/demo/test_remote.py index a1a0e541b05..b83eaca4c9c 100644 --- a/tests/components/demo/test_remote.py +++ b/tests/components/demo/test_remote.py @@ -2,9 +2,9 @@ # pylint: disable=protected-access import unittest -from homeassistant.setup import setup_component import homeassistant.components.remote as remote -from homeassistant.const import STATE_ON, STATE_OFF +from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.setup import setup_component from tests.common import get_test_home_assistant from tests.components.remote import common diff --git a/tests/components/demo/test_stt.py b/tests/components/demo/test_stt.py index 5933b976460..3fe4e223961 100644 --- a/tests/components/demo/test_stt.py +++ b/tests/components/demo/test_stt.py @@ -1,8 +1,8 @@ """The tests for the demo stt component.""" import pytest -from homeassistant.setup import async_setup_component from homeassistant.components import stt +from homeassistant.setup import async_setup_component @pytest.fixture(autouse=True) diff --git a/tests/components/demo/test_vacuum.py b/tests/components/demo/test_vacuum.py index c18241847fc..13f1b1e352c 100644 --- a/tests/components/demo/test_vacuum.py +++ b/tests/components/demo/test_vacuum.py @@ -2,6 +2,15 @@ import unittest from homeassistant.components import vacuum +from homeassistant.components.demo.vacuum import ( + DEMO_VACUUM_BASIC, + DEMO_VACUUM_COMPLETE, + DEMO_VACUUM_MINIMAL, + DEMO_VACUUM_MOST, + DEMO_VACUUM_NONE, + DEMO_VACUUM_STATE, + FAN_SPEEDS, +) from homeassistant.components.vacuum import ( ATTR_BATTERY_LEVEL, ATTR_COMMAND, @@ -13,21 +22,12 @@ from homeassistant.components.vacuum import ( ENTITY_ID_ALL_VACUUMS, SERVICE_SEND_COMMAND, SERVICE_SET_FAN_SPEED, - STATE_DOCKED, STATE_CLEANING, - STATE_PAUSED, + STATE_DOCKED, STATE_IDLE, + STATE_PAUSED, STATE_RETURNING, ) -from homeassistant.components.demo.vacuum import ( - DEMO_VACUUM_BASIC, - DEMO_VACUUM_COMPLETE, - DEMO_VACUUM_MINIMAL, - DEMO_VACUUM_MOST, - DEMO_VACUUM_NONE, - DEMO_VACUUM_STATE, - FAN_SPEEDS, -) from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, @@ -40,7 +40,6 @@ from homeassistant.setup import setup_component from tests.common import get_test_home_assistant, mock_service from tests.components.vacuum import common - ENTITY_VACUUM_BASIC = "{}.{}".format(DOMAIN, DEMO_VACUUM_BASIC).lower() ENTITY_VACUUM_COMPLETE = "{}.{}".format(DOMAIN, DEMO_VACUUM_COMPLETE).lower() ENTITY_VACUUM_MINIMAL = "{}.{}".format(DOMAIN, DEMO_VACUUM_MINIMAL).lower() diff --git a/tests/components/demo/test_water_heater.py b/tests/components/demo/test_water_heater.py index 8a57c3c61f0..97efd48be4a 100644 --- a/tests/components/demo/test_water_heater.py +++ b/tests/components/demo/test_water_heater.py @@ -4,14 +4,13 @@ import unittest import pytest import voluptuous as vol -from homeassistant.util.unit_system import IMPERIAL_SYSTEM -from homeassistant.setup import setup_component from homeassistant.components import water_heater +from homeassistant.setup import setup_component +from homeassistant.util.unit_system import IMPERIAL_SYSTEM from tests.common import get_test_home_assistant from tests.components.water_heater import common - ENTITY_WATER_HEATER = "water_heater.demo_water_heater" ENTITY_WATER_HEATER_CELSIUS = "water_heater.demo_water_heater_celsius" From fbd4cf1089d4459a9ddacf7db6ee877d842119ff Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 18:01:12 +0100 Subject: [PATCH 103/677] use isort to sort imports according to PEP8 for cloud (#29626) --- .../components/cloud/account_link.py | 2 +- .../components/cloud/alexa_config.py | 17 +++++------ .../components/cloud/binary_sensor.py | 1 - homeassistant/components/cloud/client.py | 17 +++++------ .../components/cloud/google_config.py | 8 ++--- homeassistant/components/cloud/prefs.py | 30 +++++++++---------- homeassistant/components/cloud/tts.py | 2 +- homeassistant/components/cloud/utils.py | 2 +- tests/components/cloud/__init__.py | 2 +- tests/components/cloud/test_account_link.py | 6 ++-- tests/components/cloud/test_alexa_config.py | 7 +++-- tests/components/cloud/test_binary_sensor.py | 2 +- tests/components/cloud/test_client.py | 13 ++++---- tests/components/cloud/test_google_config.py | 8 ++--- tests/components/cloud/test_http_api.py | 18 +++++------ tests/components/cloud/test_init.py | 5 ++-- tests/components/cloud/test_prefs.py | 2 +- 17 files changed, 71 insertions(+), 71 deletions(-) diff --git a/homeassistant/components/cloud/account_link.py b/homeassistant/components/cloud/account_link.py index 9ec1fe634d7..1d0de26918d 100644 --- a/homeassistant/components/cloud/account_link.py +++ b/homeassistant/components/cloud/account_link.py @@ -7,7 +7,7 @@ from hass_nabucasa import account_link from homeassistant.const import MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers import event, config_entry_oauth2_flow +from homeassistant.helpers import config_entry_oauth2_flow, event from .const import DOMAIN diff --git a/homeassistant/components/cloud/alexa_config.py b/homeassistant/components/cloud/alexa_config.py index a1432f196bf..45e1fab1101 100644 --- a/homeassistant/components/cloud/alexa_config.py +++ b/homeassistant/components/cloud/alexa_config.py @@ -7,24 +7,23 @@ import aiohttp import async_timeout from hass_nabucasa import cloud_api -from homeassistant.core import callback +from homeassistant.components.alexa import ( + config as alexa_config, + entities as alexa_entities, + errors as alexa_errors, + state_report as alexa_state_report, +) from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES +from homeassistant.core import callback from homeassistant.helpers import entity_registry from homeassistant.helpers.event import async_call_later from homeassistant.util.dt import utcnow -from homeassistant.components.alexa import ( - config as alexa_config, - errors as alexa_errors, - entities as alexa_entities, - state_report as alexa_state_report, -) - from .const import ( CONF_ENTITY_CONFIG, CONF_FILTER, - PREF_SHOULD_EXPOSE, DEFAULT_SHOULD_EXPOSE, + PREF_SHOULD_EXPOSE, RequireRelink, ) diff --git a/homeassistant/components/cloud/binary_sensor.py b/homeassistant/components/cloud/binary_sensor.py index 2192eec8923..056105f8071 100644 --- a/homeassistant/components/cloud/binary_sensor.py +++ b/homeassistant/components/cloud/binary_sensor.py @@ -6,7 +6,6 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from .const import DISPATCHER_REMOTE_UPDATE, DOMAIN - WAIT_UNTIL_CHANGE = 3 diff --git a/homeassistant/components/cloud/client.py b/homeassistant/components/cloud/client.py index 956d35caf2d..24947ed7952 100644 --- a/homeassistant/components/cloud/client.py +++ b/homeassistant/components/cloud/client.py @@ -1,27 +1,26 @@ """Interface implementation for cloud client.""" import asyncio +import logging from pathlib import Path from typing import Any, Dict -import logging import aiohttp from hass_nabucasa.client import CloudClient as Interface -from homeassistant.core import callback, Context -from homeassistant.components.google_assistant import smart_home as ga -from homeassistant.helpers.typing import HomeAssistantType -from homeassistant.helpers.dispatcher import async_dispatcher_send -from homeassistant.util.aiohttp import MockRequest from homeassistant.components.alexa import ( - smart_home as alexa_sh, errors as alexa_errors, + smart_home as alexa_sh, ) +from homeassistant.components.google_assistant import smart_home as ga +from homeassistant.core import Context, callback +from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.util.aiohttp import MockRequest -from . import utils, alexa_config, google_config +from . import alexa_config, google_config, utils from .const import DISPATCHER_REMOTE_UPDATE from .prefs import CloudPreferences - _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/cloud/google_config.py b/homeassistant/components/cloud/google_config.py index 3df06c140a0..1ff87bf95f5 100644 --- a/homeassistant/components/cloud/google_config.py +++ b/homeassistant/components/cloud/google_config.py @@ -5,16 +5,16 @@ import logging import async_timeout from hass_nabucasa.google_report_state import ErrorResponse -from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES from homeassistant.components.google_assistant.helpers import AbstractConfig +from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES from homeassistant.helpers import entity_registry from .const import ( - PREF_SHOULD_EXPOSE, - DEFAULT_SHOULD_EXPOSE, CONF_ENTITY_CONFIG, - PREF_DISABLE_2FA, DEFAULT_DISABLE_2FA, + DEFAULT_SHOULD_EXPOSE, + PREF_DISABLE_2FA, + PREF_SHOULD_EXPOSE, ) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/cloud/prefs.py b/homeassistant/components/cloud/prefs.py index e96ee9527fb..a7d1b59fd39 100644 --- a/homeassistant/components/cloud/prefs.py +++ b/homeassistant/components/cloud/prefs.py @@ -2,31 +2,31 @@ from ipaddress import ip_address from typing import Optional -from homeassistant.core import callback -from homeassistant.auth.models import User from homeassistant.auth.const import GROUP_ID_ADMIN +from homeassistant.auth.models import User +from homeassistant.core import callback from homeassistant.util.logging import async_create_catching_coro from .const import ( + DEFAULT_ALEXA_REPORT_STATE, + DEFAULT_GOOGLE_REPORT_STATE, DOMAIN, + PREF_ALEXA_ENTITY_CONFIGS, + PREF_ALEXA_REPORT_STATE, + PREF_ALIASES, + PREF_CLOUD_USER, + PREF_CLOUDHOOKS, + PREF_DISABLE_2FA, PREF_ENABLE_ALEXA, PREF_ENABLE_GOOGLE, PREF_ENABLE_REMOTE, - PREF_GOOGLE_SECURE_DEVICES_PIN, - PREF_CLOUDHOOKS, - PREF_CLOUD_USER, PREF_GOOGLE_ENTITY_CONFIGS, - PREF_OVERRIDE_NAME, - PREF_DISABLE_2FA, - PREF_ALIASES, - PREF_SHOULD_EXPOSE, - PREF_ALEXA_ENTITY_CONFIGS, - PREF_ALEXA_REPORT_STATE, - PREF_USERNAME, - DEFAULT_ALEXA_REPORT_STATE, - PREF_GOOGLE_REPORT_STATE, PREF_GOOGLE_LOCAL_WEBHOOK_ID, - DEFAULT_GOOGLE_REPORT_STATE, + PREF_GOOGLE_REPORT_STATE, + PREF_GOOGLE_SECURE_DEVICES_PIN, + PREF_OVERRIDE_NAME, + PREF_SHOULD_EXPOSE, + PREF_USERNAME, InvalidTrustedNetworks, InvalidTrustedProxies, ) diff --git a/homeassistant/components/cloud/tts.py b/homeassistant/components/cloud/tts.py index 338b97d2bd9..ea769c6a054 100644 --- a/homeassistant/components/cloud/tts.py +++ b/homeassistant/components/cloud/tts.py @@ -1,7 +1,7 @@ """Support for the cloud for text to speech service.""" -from hass_nabucasa.voice import VoiceError from hass_nabucasa import Cloud +from hass_nabucasa.voice import VoiceError import voluptuous as vol from homeassistant.components.tts import CONF_LANG, PLATFORM_SCHEMA, Provider diff --git a/homeassistant/components/cloud/utils.py b/homeassistant/components/cloud/utils.py index 5040baada9a..36599b42ad3 100644 --- a/homeassistant/components/cloud/utils.py +++ b/homeassistant/components/cloud/utils.py @@ -1,7 +1,7 @@ """Helper functions for cloud components.""" from typing import Any, Dict -from aiohttp import web, payload +from aiohttp import payload, web def aiohttp_serialize_response(response: web.Response) -> Dict[str, Any]: diff --git a/tests/components/cloud/__init__.py b/tests/components/cloud/__init__.py index 45ea4e43ee4..571b73e8d09 100644 --- a/tests/components/cloud/__init__.py +++ b/tests/components/cloud/__init__.py @@ -1,9 +1,9 @@ """Tests for the cloud component.""" from unittest.mock import patch -from homeassistant.setup import async_setup_component from homeassistant.components import cloud from homeassistant.components.cloud import const +from homeassistant.setup import async_setup_component from tests.common import mock_coro diff --git a/tests/components/cloud/test_account_link.py b/tests/components/cloud/test_account_link.py index 60116895beb..a8c247cc985 100644 --- a/tests/components/cloud/test_account_link.py +++ b/tests/components/cloud/test_account_link.py @@ -6,12 +6,12 @@ from unittest.mock import Mock, patch import pytest -from homeassistant import data_entry_flow, config_entries -from homeassistant.helpers import config_entry_oauth2_flow +from homeassistant import config_entries, data_entry_flow from homeassistant.components.cloud import account_link +from homeassistant.helpers import config_entry_oauth2_flow from homeassistant.util.dt import utcnow -from tests.common import mock_coro, async_fire_time_changed, mock_platform +from tests.common import async_fire_time_changed, mock_coro, mock_platform TEST_DOMAIN = "oauth2_test" diff --git a/tests/components/cloud/test_alexa_config.py b/tests/components/cloud/test_alexa_config.py index a7c8898659a..508626b43f0 100644 --- a/tests/components/cloud/test_alexa_config.py +++ b/tests/components/cloud/test_alexa_config.py @@ -1,11 +1,12 @@ """Test Alexa config.""" import contextlib -from unittest.mock import patch, Mock +from unittest.mock import Mock, patch from homeassistant.components.cloud import ALEXA_SCHEMA, alexa_config -from homeassistant.util.dt import utcnow from homeassistant.helpers.entity_registry import EVENT_ENTITY_REGISTRY_UPDATED -from tests.common import mock_coro, async_fire_time_changed +from homeassistant.util.dt import utcnow + +from tests.common import async_fire_time_changed, mock_coro async def test_alexa_config_expose_entity_prefs(hass, cloud_prefs): diff --git a/tests/components/cloud/test_binary_sensor.py b/tests/components/cloud/test_binary_sensor.py index 99ae2f43bc5..24b0563890b 100644 --- a/tests/components/cloud/test_binary_sensor.py +++ b/tests/components/cloud/test_binary_sensor.py @@ -1,8 +1,8 @@ """Tests for the cloud binary sensor.""" from unittest.mock import Mock -from homeassistant.setup import async_setup_component from homeassistant.components.cloud.const import DISPATCHER_REMOTE_UPDATE +from homeassistant.setup import async_setup_component async def test_remote_connection_sensor(hass): diff --git a/tests/components/cloud/test_client.py b/tests/components/cloud/test_client.py index 955923c1e68..b3bfebb0ee7 100644 --- a/tests/components/cloud/test_client.py +++ b/tests/components/cloud/test_client.py @@ -1,18 +1,19 @@ """Test the cloud.iot module.""" -from unittest.mock import patch, MagicMock +from unittest.mock import MagicMock, patch from aiohttp import web import pytest -from homeassistant.core import State -from homeassistant.setup import async_setup_component from homeassistant.components.cloud import DOMAIN from homeassistant.components.cloud.client import CloudClient from homeassistant.components.cloud.const import PREF_ENABLE_ALEXA, PREF_ENABLE_GOOGLE -from tests.components.alexa import test_smart_home as test_alexa -from tests.common import mock_coro +from homeassistant.core import State +from homeassistant.setup import async_setup_component -from . import mock_cloud_prefs, mock_cloud +from . import mock_cloud, mock_cloud_prefs + +from tests.common import mock_coro +from tests.components.alexa import test_smart_home as test_alexa @pytest.fixture diff --git a/tests/components/cloud/test_google_config.py b/tests/components/cloud/test_google_config.py index 3510b4b8abd..830751029d7 100644 --- a/tests/components/cloud/test_google_config.py +++ b/tests/components/cloud/test_google_config.py @@ -1,13 +1,13 @@ """Test the Cloud Google Config.""" -from unittest.mock import patch, Mock +from unittest.mock import Mock, patch -from homeassistant.components.google_assistant import helpers as ga_helpers from homeassistant.components.cloud import GACTIONS_SCHEMA from homeassistant.components.cloud.google_config import CloudGoogleConfig -from homeassistant.util.dt import utcnow +from homeassistant.components.google_assistant import helpers as ga_helpers from homeassistant.helpers.entity_registry import EVENT_ENTITY_REGISTRY_UPDATED +from homeassistant.util.dt import utcnow -from tests.common import mock_coro, async_fire_time_changed +from tests.common import async_fire_time_changed, mock_coro async def test_google_update_report_state(hass, cloud_prefs): diff --git a/tests/components/cloud/test_http_api.py b/tests/components/cloud/test_http_api.py index 440ad7a9c89..515489035fb 100644 --- a/tests/components/cloud/test_http_api.py +++ b/tests/components/cloud/test_http_api.py @@ -1,26 +1,26 @@ """Tests for the HTTP API for the cloud component.""" import asyncio -from unittest.mock import patch, MagicMock from ipaddress import ip_network +from unittest.mock import MagicMock, patch -import pytest -from jose import jwt +from hass_nabucasa import thingtalk from hass_nabucasa.auth import Unauthenticated, UnknownError from hass_nabucasa.const import STATE_CONNECTED -from hass_nabucasa import thingtalk +from jose import jwt +import pytest -from homeassistant.core import State from homeassistant.auth.providers import trusted_networks as tn_auth +from homeassistant.components.alexa import errors as alexa_errors +from homeassistant.components.alexa.entities import LightCapabilities from homeassistant.components.cloud.const import DOMAIN, RequireRelink from homeassistant.components.google_assistant.helpers import GoogleEntity -from homeassistant.components.alexa.entities import LightCapabilities -from homeassistant.components.alexa import errors as alexa_errors +from homeassistant.core import State + +from . import mock_cloud, mock_cloud_prefs from tests.common import mock_coro from tests.components.google_assistant import MockConfig -from . import mock_cloud, mock_cloud_prefs - GOOGLE_ACTIONS_SYNC_URL = "https://api-test.hass.io/google_actions_sync" SUBSCRIPTION_INFO_URL = "https://api-test.hass.io/subscription_info" diff --git a/tests/components/cloud/test_init.py b/tests/components/cloud/test_init.py index d039cdd1b0b..5d0ba76f80b 100644 --- a/tests/components/cloud/test_init.py +++ b/tests/components/cloud/test_init.py @@ -3,13 +3,14 @@ from unittest.mock import patch import pytest -from homeassistant.core import Context -from homeassistant.exceptions import Unauthorized from homeassistant.components import cloud from homeassistant.components.cloud.const import DOMAIN from homeassistant.components.cloud.prefs import STORAGE_KEY from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP +from homeassistant.core import Context +from homeassistant.exceptions import Unauthorized from homeassistant.setup import async_setup_component + from tests.common import mock_coro diff --git a/tests/components/cloud/test_prefs.py b/tests/components/cloud/test_prefs.py index 1678757e52c..d1b6f9ed867 100644 --- a/tests/components/cloud/test_prefs.py +++ b/tests/components/cloud/test_prefs.py @@ -2,7 +2,7 @@ from unittest.mock import patch from homeassistant.auth.const import GROUP_ID_ADMIN -from homeassistant.components.cloud.prefs import CloudPreferences, STORAGE_KEY +from homeassistant.components.cloud.prefs import STORAGE_KEY, CloudPreferences async def test_set_username(hass): From 0d5de6a464dcdbc1b5768d6c7b140aac5b2429bc Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 18:01:50 +0100 Subject: [PATCH 104/677] use isort to sort imports according to PEP8 for ecobee (#29631) --- homeassistant/components/ecobee/__init__.py | 6 +-- .../components/ecobee/binary_sensor.py | 4 +- homeassistant/components/ecobee/climate.py | 38 +++++++++---------- .../components/ecobee/config_flow.py | 9 ++--- homeassistant/components/ecobee/notify.py | 2 +- homeassistant/components/ecobee/sensor.py | 2 +- homeassistant/components/ecobee/util.py | 1 + homeassistant/components/ecobee/weather.py | 2 +- tests/components/ecobee/test_climate.py | 3 +- tests/components/ecobee/test_config_flow.py | 3 +- 10 files changed, 36 insertions(+), 34 deletions(-) diff --git a/homeassistant/components/ecobee/__init__.py b/homeassistant/components/ecobee/__init__.py index eb65a7ed426..80c3be7954b 100644 --- a/homeassistant/components/ecobee/__init__.py +++ b/homeassistant/components/ecobee/__init__.py @@ -1,9 +1,9 @@ """Support for ecobee.""" import asyncio from datetime import timedelta -import voluptuous as vol -from pyecobee import Ecobee, ECOBEE_API_KEY, ECOBEE_REFRESH_TOKEN, ExpiredTokenError +from pyecobee import ECOBEE_API_KEY, ECOBEE_REFRESH_TOKEN, Ecobee, ExpiredTokenError +import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import CONF_API_KEY @@ -11,11 +11,11 @@ from homeassistant.helpers import config_validation as cv from homeassistant.util import Throttle from .const import ( + _LOGGER, CONF_REFRESH_TOKEN, DATA_ECOBEE_CONFIG, DOMAIN, ECOBEE_PLATFORMS, - _LOGGER, ) MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=180) diff --git a/homeassistant/components/ecobee/binary_sensor.py b/homeassistant/components/ecobee/binary_sensor.py index 06289572aea..f7a24886b84 100644 --- a/homeassistant/components/ecobee/binary_sensor.py +++ b/homeassistant/components/ecobee/binary_sensor.py @@ -1,10 +1,10 @@ """Support for Ecobee binary sensors.""" from homeassistant.components.binary_sensor import ( - BinarySensorDevice, DEVICE_CLASS_OCCUPANCY, + BinarySensorDevice, ) -from .const import DOMAIN, ECOBEE_MODEL_TO_NAME, MANUFACTURER, _LOGGER +from .const import _LOGGER, DOMAIN, ECOBEE_MODEL_TO_NAME, MANUFACTURER async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): diff --git a/homeassistant/components/ecobee/climate.py b/homeassistant/components/ecobee/climate.py index c583f9696d2..83a1453a23a 100644 --- a/homeassistant/components/ecobee/climate.py +++ b/homeassistant/components/ecobee/climate.py @@ -6,37 +6,37 @@ import voluptuous as vol from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( - HVAC_MODE_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_AUTO, - HVAC_MODE_OFF, - ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_HIGH, - SUPPORT_TARGET_TEMPERATURE, - SUPPORT_AUX_HEAT, - SUPPORT_TARGET_TEMPERATURE_RANGE, - SUPPORT_FAN_MODE, - PRESET_AWAY, + ATTR_TARGET_TEMP_LOW, + CURRENT_HVAC_COOL, + CURRENT_HVAC_DRY, + CURRENT_HVAC_FAN, + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, FAN_AUTO, FAN_ON, - CURRENT_HVAC_IDLE, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_COOL, - SUPPORT_PRESET_MODE, + HVAC_MODE_AUTO, + HVAC_MODE_COOL, + HVAC_MODE_HEAT, + HVAC_MODE_OFF, + PRESET_AWAY, PRESET_NONE, - CURRENT_HVAC_FAN, - CURRENT_HVAC_DRY, + SUPPORT_AUX_HEAT, + SUPPORT_FAN_MODE, + SUPPORT_PRESET_MODE, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_TARGET_TEMPERATURE_RANGE, ) from homeassistant.const import ( ATTR_ENTITY_ID, - STATE_ON, ATTR_TEMPERATURE, + STATE_ON, TEMP_FAHRENHEIT, ) -from homeassistant.util.temperature import convert import homeassistant.helpers.config_validation as cv +from homeassistant.util.temperature import convert -from .const import DOMAIN, ECOBEE_MODEL_TO_NAME, MANUFACTURER, _LOGGER +from .const import _LOGGER, DOMAIN, ECOBEE_MODEL_TO_NAME, MANUFACTURER from .util import ecobee_date, ecobee_time ATTR_COOL_TEMP = "cool_temp" diff --git a/homeassistant/components/ecobee/config_flow.py b/homeassistant/components/ecobee/config_flow.py index 56ce13f7701..bb406d81e3a 100644 --- a/homeassistant/components/ecobee/config_flow.py +++ b/homeassistant/components/ecobee/config_flow.py @@ -1,19 +1,18 @@ """Config flow to configure ecobee.""" -import voluptuous as vol - from pyecobee import ( - Ecobee, - ECOBEE_CONFIG_FILENAME, ECOBEE_API_KEY, + ECOBEE_CONFIG_FILENAME, ECOBEE_REFRESH_TOKEN, + Ecobee, ) +import voluptuous as vol from homeassistant import config_entries from homeassistant.const import CONF_API_KEY from homeassistant.core import HomeAssistantError from homeassistant.util.json import load_json -from .const import CONF_REFRESH_TOKEN, DATA_ECOBEE_CONFIG, DOMAIN, _LOGGER +from .const import _LOGGER, CONF_REFRESH_TOKEN, DATA_ECOBEE_CONFIG, DOMAIN class EcobeeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): diff --git a/homeassistant/components/ecobee/notify.py b/homeassistant/components/ecobee/notify.py index c7b3f47d29c..a8f53a027b3 100644 --- a/homeassistant/components/ecobee/notify.py +++ b/homeassistant/components/ecobee/notify.py @@ -1,8 +1,8 @@ """Support for Ecobee Send Message service.""" import voluptuous as vol +from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationService import homeassistant.helpers.config_validation as cv -from homeassistant.components.notify import BaseNotificationService, PLATFORM_SCHEMA from .const import CONF_INDEX, DOMAIN diff --git a/homeassistant/components/ecobee/sensor.py b/homeassistant/components/ecobee/sensor.py index 76945080bfa..37201ec2121 100644 --- a/homeassistant/components/ecobee/sensor.py +++ b/homeassistant/components/ecobee/sensor.py @@ -8,7 +8,7 @@ from homeassistant.const import ( ) from homeassistant.helpers.entity import Entity -from .const import DOMAIN, ECOBEE_MODEL_TO_NAME, MANUFACTURER, _LOGGER +from .const import _LOGGER, DOMAIN, ECOBEE_MODEL_TO_NAME, MANUFACTURER SENSOR_TYPES = { "temperature": ["Temperature", TEMP_FAHRENHEIT], diff --git a/homeassistant/components/ecobee/util.py b/homeassistant/components/ecobee/util.py index 3acc3e5676d..2f5d194fec0 100644 --- a/homeassistant/components/ecobee/util.py +++ b/homeassistant/components/ecobee/util.py @@ -1,5 +1,6 @@ """Validation utility functions for ecobee services.""" from datetime import datetime + import voluptuous as vol diff --git a/homeassistant/components/ecobee/weather.py b/homeassistant/components/ecobee/weather.py index 7b057f09a0c..a571e854f73 100644 --- a/homeassistant/components/ecobee/weather.py +++ b/homeassistant/components/ecobee/weather.py @@ -15,11 +15,11 @@ from homeassistant.components.weather import ( from homeassistant.const import TEMP_FAHRENHEIT from .const import ( + _LOGGER, DOMAIN, ECOBEE_MODEL_TO_NAME, ECOBEE_WEATHER_SYMBOL_TO_HASS, MANUFACTURER, - _LOGGER, ) diff --git a/tests/components/ecobee/test_climate.py b/tests/components/ecobee/test_climate.py index 90a9a641776..0c0ca785026 100644 --- a/tests/components/ecobee/test_climate.py +++ b/tests/components/ecobee/test_climate.py @@ -1,8 +1,9 @@ """The test for the Ecobee thermostat module.""" import unittest from unittest import mock -import homeassistant.const as const + from homeassistant.components.ecobee import climate as ecobee +import homeassistant.const as const from homeassistant.const import STATE_OFF diff --git a/tests/components/ecobee/test_config_flow.py b/tests/components/ecobee/test_config_flow.py index 64f0e3df0e7..6b53af5daa8 100644 --- a/tests/components/ecobee/test_config_flow.py +++ b/tests/components/ecobee/test_config_flow.py @@ -1,8 +1,8 @@ """Tests for the ecobee config flow.""" -import pytest from unittest.mock import patch from pyecobee import ECOBEE_API_KEY, ECOBEE_REFRESH_TOKEN +import pytest from homeassistant import data_entry_flow from homeassistant.components.ecobee import config_flow @@ -12,6 +12,7 @@ from homeassistant.components.ecobee.const import ( DOMAIN, ) from homeassistant.const import CONF_API_KEY + from tests.common import MockConfigEntry, mock_coro From d2c1e5d45c748ec645c88a14755ae8a9e6b4bde6 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 18:14:26 +0100 Subject: [PATCH 105/677] Sort imports according to PEP8 for homekit_controller (#29646) --- tests/components/homekit_controller/common.py | 13 +++++++------ .../components/homekit_controller/test_climate.py | 8 ++++---- .../homekit_controller/test_config_flow.py | 2 +- tests/components/homekit_controller/test_light.py | 1 - .../components/homekit_controller/test_storage.py | 14 +++++++------- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/tests/components/homekit_controller/common.py b/tests/components/homekit_controller/common.py index 9a404701c54..b743f84f73c 100644 --- a/tests/components/homekit_controller/common.py +++ b/tests/components/homekit_controller/common.py @@ -1,28 +1,29 @@ """Code to support homekit_controller tests.""" +from datetime import timedelta import json import os -from datetime import timedelta from unittest import mock -from homekit.model.services import AbstractService, ServicesTypes +from homekit.exceptions import AccessoryNotFoundError +from homekit.model import Accessory, get_id from homekit.model.characteristics import ( AbstractCharacteristic, CharacteristicPermissions, CharacteristicsTypes, ) -from homekit.model import Accessory, get_id -from homekit.exceptions import AccessoryNotFoundError +from homekit.model.services import AbstractService, ServicesTypes from homeassistant import config_entries +from homeassistant.components.homekit_controller import config_flow from homeassistant.components.homekit_controller.const import ( CONTROLLER, DOMAIN, HOMEKIT_ACCESSORY_DISPATCH, ) -from homeassistant.components.homekit_controller import config_flow from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util -from tests.common import async_fire_time_changed, load_fixture, MockConfigEntry + +from tests.common import MockConfigEntry, async_fire_time_changed, load_fixture class FakePairing: diff --git a/tests/components/homekit_controller/test_climate.py b/tests/components/homekit_controller/test_climate.py index 0d3544a6f55..e076b2975e2 100644 --- a/tests/components/homekit_controller/test_climate.py +++ b/tests/components/homekit_controller/test_climate.py @@ -1,16 +1,16 @@ """Basic checks for HomeKitclimate.""" from homeassistant.components.climate.const import ( DOMAIN, - SERVICE_SET_HVAC_MODE, - SERVICE_SET_TEMPERATURE, - HVAC_MODE_HEAT_COOL, HVAC_MODE_COOL, HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, SERVICE_SET_HUMIDITY, + SERVICE_SET_HVAC_MODE, + SERVICE_SET_TEMPERATURE, ) -from tests.components.homekit_controller.common import FakeService, setup_test_component +from tests.components.homekit_controller.common import FakeService, setup_test_component HEATING_COOLING_TARGET = ("thermostat", "heating-cooling.target") HEATING_COOLING_CURRENT = ("thermostat", "heating-cooling.current") diff --git a/tests/components/homekit_controller/test_config_flow.py b/tests/components/homekit_controller/test_config_flow.py index 81d5ac9a751..22b486e1d51 100644 --- a/tests/components/homekit_controller/test_config_flow.py +++ b/tests/components/homekit_controller/test_config_flow.py @@ -7,6 +7,7 @@ import pytest from homeassistant.components.homekit_controller import config_flow from homeassistant.components.homekit_controller.const import KNOWN_DEVICES + from tests.common import MockConfigEntry from tests.components.homekit_controller.common import ( Accessory, @@ -14,7 +15,6 @@ from tests.components.homekit_controller.common import ( setup_platform, ) - PAIRING_START_FORM_ERRORS = [ (homekit.BusyError, "busy_error"), (homekit.MaxTriesError, "max_tries_error"), diff --git a/tests/components/homekit_controller/test_light.py b/tests/components/homekit_controller/test_light.py index 1608f097ed0..b558160a9f2 100644 --- a/tests/components/homekit_controller/test_light.py +++ b/tests/components/homekit_controller/test_light.py @@ -3,7 +3,6 @@ from homeassistant.components.homekit_controller.const import KNOWN_DEVICES from tests.components.homekit_controller.common import FakeService, setup_test_component - LIGHT_ON = ("lightbulb", "on") LIGHT_BRIGHTNESS = ("lightbulb", "brightness") LIGHT_HUE = ("lightbulb", "hue") diff --git a/tests/components/homekit_controller/test_storage.py b/tests/components/homekit_controller/test_storage.py index 4fcf035ae48..39b0d9d8250 100644 --- a/tests/components/homekit_controller/test_storage.py +++ b/tests/components/homekit_controller/test_storage.py @@ -1,15 +1,15 @@ """Basic checks for entity map storage.""" -from tests.common import flush_store -from tests.components.homekit_controller.common import ( - FakeService, - setup_test_component, - setup_platform, -) - from homeassistant import config_entries from homeassistant.components.homekit_controller import async_remove_entry from homeassistant.components.homekit_controller.const import ENTITY_MAP +from tests.common import flush_store +from tests.components.homekit_controller.common import ( + FakeService, + setup_platform, + setup_test_component, +) + async def test_load_from_storage(hass, hass_storage): """Test that entity map can be correctly loaded from cache.""" From ce5072fc9171aed7b27eecffb29a8ed1f977486c Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 18:15:26 +0100 Subject: [PATCH 106/677] use isort to sort imports according to PEP8 for mqtt (#29649) --- homeassistant/components/mqtt/__init__.py | 12 ++++++------ homeassistant/components/mqtt/camera.py | 2 +- homeassistant/components/mqtt/climate.py | 4 ++-- homeassistant/components/mqtt/device_tracker.py | 2 +- homeassistant/components/mqtt/models.py | 2 +- tests/components/mqtt/test_binary_sensor.py | 1 - tests/components/mqtt/test_camera.py | 2 +- tests/components/mqtt/test_climate.py | 16 ++++++++-------- tests/components/mqtt/test_cover.py | 4 ++-- tests/components/mqtt/test_discovery.py | 1 - tests/components/mqtt/test_init.py | 2 +- tests/components/mqtt/test_sensor.py | 2 +- tests/components/mqtt/test_state_vacuum.py | 2 +- 13 files changed, 25 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index ad9166e2410..d8dc584ae30 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -1,6 +1,5 @@ """Support for MQTT message handling.""" import asyncio -import sys from functools import partial, wraps import inspect from itertools import groupby @@ -10,6 +9,7 @@ from operator import attrgetter import os import socket import ssl +import sys import time from typing import Any, Callable, List, Optional, Union @@ -32,9 +32,9 @@ from homeassistant.const import ( ) from homeassistant.core import Event, ServiceCall, callback from homeassistant.exceptions import ( + ConfigEntryNotReady, HomeAssistantError, Unauthorized, - ConfigEntryNotReady, ) from homeassistant.helpers import config_validation as cv, template from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -47,16 +47,16 @@ from homeassistant.util.logging import catch_log_exception # Loading the config flow file will register the flow from . import config_flow, discovery, server # noqa: F401 pylint: disable=unused-import from .const import ( + ATTR_DISCOVERY_HASH, CONF_BROKER, CONF_DISCOVERY, - DEFAULT_DISCOVERY, CONF_STATE_TOPIC, - ATTR_DISCOVERY_HASH, - PROTOCOL_311, + DEFAULT_DISCOVERY, DEFAULT_QOS, + PROTOCOL_311, ) from .discovery import MQTT_DISCOVERY_UPDATED, clear_discovery_hash -from .models import PublishPayloadType, Message, MessageCallbackType +from .models import Message, MessageCallbackType, PublishPayloadType from .subscription import async_subscribe_topics, async_unsubscribe_topics _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/mqtt/camera.py b/homeassistant/components/mqtt/camera.py index f3ae36c5746..831c47c3621 100644 --- a/homeassistant/components/mqtt/camera.py +++ b/homeassistant/components/mqtt/camera.py @@ -7,7 +7,7 @@ import voluptuous as vol from homeassistant.components import camera, mqtt from homeassistant.components.camera import PLATFORM_SCHEMA, Camera -from homeassistant.const import CONF_NAME, CONF_DEVICE +from homeassistant.const import CONF_DEVICE, CONF_NAME from homeassistant.core import callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_connect diff --git a/homeassistant/components/mqtt/climate.py b/homeassistant/components/mqtt/climate.py index 9b46057a414..a51590e0b59 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -20,14 +20,14 @@ from homeassistant.components.climate.const import ( HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, HVAC_MODE_OFF, + PRESET_AWAY, + PRESET_NONE, SUPPORT_AUX_HEAT, SUPPORT_FAN_MODE, SUPPORT_PRESET_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE, - PRESET_AWAY, SUPPORT_TARGET_TEMPERATURE_RANGE, - PRESET_NONE, ) from homeassistant.components.fan import SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM from homeassistant.const import ( diff --git a/homeassistant/components/mqtt/device_tracker.py b/homeassistant/components/mqtt/device_tracker.py index d25d7ce21d3..bcc969f0354 100644 --- a/homeassistant/components/mqtt/device_tracker.py +++ b/homeassistant/components/mqtt/device_tracker.py @@ -5,9 +5,9 @@ import voluptuous as vol from homeassistant.components import mqtt from homeassistant.components.device_tracker import PLATFORM_SCHEMA, SOURCE_TYPES +from homeassistant.const import CONF_DEVICES, STATE_HOME, STATE_NOT_HOME from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.const import CONF_DEVICES, STATE_NOT_HOME, STATE_HOME from . import CONF_QOS diff --git a/homeassistant/components/mqtt/models.py b/homeassistant/components/mqtt/models.py index 46aaa23732f..cfdecd3383d 100644 --- a/homeassistant/components/mqtt/models.py +++ b/homeassistant/components/mqtt/models.py @@ -1,5 +1,5 @@ """Modesl used by multiple MQTT modules.""" -from typing import Union, Callable +from typing import Callable, Union import attr diff --git a/tests/components/mqtt/test_binary_sensor.py b/tests/components/mqtt/test_binary_sensor.py index 28f1a7e9720..3e8f342ea94 100644 --- a/tests/components/mqtt/test_binary_sensor.py +++ b/tests/components/mqtt/test_binary_sensor.py @@ -1,7 +1,6 @@ """The tests for the MQTT binary sensor platform.""" from datetime import datetime, timedelta import json - from unittest.mock import ANY, patch from homeassistant.components import binary_sensor, mqtt diff --git a/tests/components/mqtt/test_camera.py b/tests/components/mqtt/test_camera.py index 70b5e941fe3..0e7d8ada759 100644 --- a/tests/components/mqtt/test_camera.py +++ b/tests/components/mqtt/test_camera.py @@ -1,6 +1,6 @@ """The tests for mqtt camera component.""" -from unittest.mock import ANY import json +from unittest.mock import ANY from homeassistant.components import camera, mqtt from homeassistant.components.mqtt.discovery import async_start diff --git a/tests/components/mqtt/test_climate.py b/tests/components/mqtt/test_climate.py index 648448a6494..2db368d0311 100644 --- a/tests/components/mqtt/test_climate.py +++ b/tests/components/mqtt/test_climate.py @@ -11,19 +11,19 @@ from homeassistant.components import mqtt from homeassistant.components.climate import DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP from homeassistant.components.climate.const import ( DOMAIN as CLIMATE_DOMAIN, - SUPPORT_AUX_HEAT, - SUPPORT_PRESET_MODE, - SUPPORT_FAN_MODE, - SUPPORT_SWING_MODE, - SUPPORT_TARGET_TEMPERATURE, HVAC_MODE_AUTO, HVAC_MODE_COOL, - HVAC_MODE_HEAT, HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY, - SUPPORT_TARGET_TEMPERATURE_RANGE, - PRESET_NONE, + HVAC_MODE_HEAT, PRESET_ECO, + PRESET_NONE, + SUPPORT_AUX_HEAT, + SUPPORT_FAN_MODE, + SUPPORT_PRESET_MODE, + SUPPORT_SWING_MODE, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_TARGET_TEMPERATURE_RANGE, ) from homeassistant.components.mqtt.discovery import async_start from homeassistant.const import STATE_OFF, STATE_UNAVAILABLE diff --git a/tests/components/mqtt/test_cover.py b/tests/components/mqtt/test_cover.py index bb734d2c03d..b15518961a4 100644 --- a/tests/components/mqtt/test_cover.py +++ b/tests/components/mqtt/test_cover.py @@ -16,11 +16,11 @@ from homeassistant.const import ( SERVICE_SET_COVER_POSITION, SERVICE_SET_COVER_TILT_POSITION, SERVICE_STOP_COVER, + SERVICE_TOGGLE, + SERVICE_TOGGLE_COVER_TILT, STATE_CLOSED, STATE_OPEN, STATE_UNAVAILABLE, - SERVICE_TOGGLE, - SERVICE_TOGGLE_COVER_TILT, STATE_UNKNOWN, ) from homeassistant.setup import async_setup_component diff --git a/tests/components/mqtt/test_discovery.py b/tests/components/mqtt/test_discovery.py index 860ef52a98a..2b6c65b919e 100644 --- a/tests/components/mqtt/test_discovery.py +++ b/tests/components/mqtt/test_discovery.py @@ -1,7 +1,6 @@ """The tests for the MQTT discovery.""" from pathlib import Path import re - from unittest.mock import patch from homeassistant.components import mqtt diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 6f2a87d1caa..3d8e261fdb2 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -15,8 +15,8 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP, ) from homeassistant.core import callback -from homeassistant.setup import async_setup_component from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.setup import async_setup_component from tests.common import ( MockConfigEntry, diff --git a/tests/components/mqtt/test_sensor.py b/tests/components/mqtt/test_sensor.py index cd55a08482d..4e8e5f9bfd0 100644 --- a/tests/components/mqtt/test_sensor.py +++ b/tests/components/mqtt/test_sensor.py @@ -14,8 +14,8 @@ import homeassistant.util.dt as dt_util from tests.common import ( MockConfigEntry, async_fire_mqtt_message, - async_mock_mqtt_component, async_fire_time_changed, + async_mock_mqtt_component, mock_registry, ) diff --git a/tests/components/mqtt/test_state_vacuum.py b/tests/components/mqtt/test_state_vacuum.py index 7919e07767d..eb3071eb120 100644 --- a/tests/components/mqtt/test_state_vacuum.py +++ b/tests/components/mqtt/test_state_vacuum.py @@ -26,9 +26,9 @@ from homeassistant.components.vacuum import ( from homeassistant.const import ( CONF_NAME, CONF_PLATFORM, + ENTITY_MATCH_ALL, STATE_UNAVAILABLE, STATE_UNKNOWN, - ENTITY_MATCH_ALL, ) from homeassistant.setup import async_setup_component From aeff27680bfb975fce948f33381f631c7d6413b3 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 18:16:23 +0100 Subject: [PATCH 107/677] use isort to sort imports according to PEP8 for light (#29648) --- homeassistant/components/light/__init__.py | 3 +- .../components/light/device_action.py | 7 ++-- .../components/light/device_condition.py | 7 ++-- .../components/light/device_trigger.py | 5 ++- homeassistant/components/light/intent.py | 11 +++-- .../components/light/reproduce_state.py | 6 +-- .../components/mqtt/light/__init__.py | 3 +- .../components/mqtt/light/schema_basic.py | 42 +++++++++---------- .../components/mqtt/light/schema_template.py | 23 +++++----- tests/components/light/common.py | 2 +- tests/components/light/test_device_action.py | 8 ++-- .../components/light/test_device_condition.py | 13 +++--- tests/components/light/test_device_trigger.py | 11 ++--- tests/components/light/test_init.py | 24 +++++------ tests/components/light/test_intent.py | 6 +-- 15 files changed, 86 insertions(+), 85 deletions(-) diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index f01258e2ab4..8a2a61d0421 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -17,7 +17,7 @@ from homeassistant.const import ( SERVICE_TURN_ON, STATE_ON, ) -from homeassistant.exceptions import UnknownUser, Unauthorized +from homeassistant.exceptions import Unauthorized, UnknownUser import homeassistant.helpers.config_validation as cv from homeassistant.helpers.config_validation import ( # noqa: F401 PLATFORM_SCHEMA, @@ -29,7 +29,6 @@ from homeassistant.helpers.entity_component import EntityComponent from homeassistant.loader import bind_hass import homeassistant.util.color as color_util - # mypy: allow-untyped-defs, no-check-untyped-defs DOMAIN = "light" diff --git a/homeassistant/components/light/device_action.py b/homeassistant/components/light/device_action.py index 9d8ef6bceaf..c436ce7886a 100644 --- a/homeassistant/components/light/device_action.py +++ b/homeassistant/components/light/device_action.py @@ -1,13 +1,14 @@ """Provides device actions for lights.""" from typing import List + import voluptuous as vol -from homeassistant.core import HomeAssistant, Context from homeassistant.components.device_automation import toggle_entity from homeassistant.const import CONF_DOMAIN -from homeassistant.helpers.typing import TemplateVarsType, ConfigType -from . import DOMAIN +from homeassistant.core import Context, HomeAssistant +from homeassistant.helpers.typing import ConfigType, TemplateVarsType +from . import DOMAIN ACTION_SCHEMA = toggle_entity.ACTION_SCHEMA.extend({vol.Required(CONF_DOMAIN): DOMAIN}) diff --git a/homeassistant/components/light/device_condition.py b/homeassistant/components/light/device_condition.py index e87ae3bf945..d27953749f6 100644 --- a/homeassistant/components/light/device_condition.py +++ b/homeassistant/components/light/device_condition.py @@ -1,14 +1,15 @@ """Provides device conditions for lights.""" from typing import Dict, List + import voluptuous as vol -from homeassistant.core import HomeAssistant from homeassistant.components.device_automation import toggle_entity from homeassistant.const import CONF_DOMAIN -from homeassistant.helpers.typing import ConfigType +from homeassistant.core import HomeAssistant from homeassistant.helpers.condition import ConditionCheckerType -from . import DOMAIN +from homeassistant.helpers.typing import ConfigType +from . import DOMAIN CONDITION_SCHEMA = toggle_entity.CONDITION_SCHEMA.extend( {vol.Required(CONF_DOMAIN): DOMAIN} diff --git a/homeassistant/components/light/device_trigger.py b/homeassistant/components/light/device_trigger.py index 432d24d3c14..066d1f4c020 100644 --- a/homeassistant/components/light/device_trigger.py +++ b/homeassistant/components/light/device_trigger.py @@ -1,14 +1,15 @@ """Provides device trigger for lights.""" from typing import List + import voluptuous as vol -from homeassistant.core import HomeAssistant, CALLBACK_TYPE from homeassistant.components.automation import AutomationActionType from homeassistant.components.device_automation import toggle_entity from homeassistant.const import CONF_DOMAIN +from homeassistant.core import CALLBACK_TYPE, HomeAssistant from homeassistant.helpers.typing import ConfigType -from . import DOMAIN +from . import DOMAIN TRIGGER_SCHEMA = toggle_entity.TRIGGER_SCHEMA.extend( {vol.Required(CONF_DOMAIN): DOMAIN} diff --git a/homeassistant/components/light/intent.py b/homeassistant/components/light/intent.py index 93b9748fc4a..ea8899c44fc 100644 --- a/homeassistant/components/light/intent.py +++ b/homeassistant/components/light/intent.py @@ -3,20 +3,19 @@ import voluptuous as vol from homeassistant.core import HomeAssistant from homeassistant.helpers import intent -import homeassistant.util.color as color_util import homeassistant.helpers.config_validation as cv +import homeassistant.util.color as color_util from . import ( - ATTR_ENTITY_ID, - SUPPORT_COLOR, - ATTR_RGB_COLOR, ATTR_BRIGHTNESS_PCT, - SUPPORT_BRIGHTNESS, + ATTR_ENTITY_ID, + ATTR_RGB_COLOR, DOMAIN, SERVICE_TURN_ON, + SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, ) - INTENT_SET = "HassLightSet" diff --git a/homeassistant/components/light/reproduce_state.py b/homeassistant/components/light/reproduce_state.py index 90d14c2a19f..59a4b0306d0 100644 --- a/homeassistant/components/light/reproduce_state.py +++ b/homeassistant/components/light/reproduce_state.py @@ -6,16 +6,15 @@ from typing import Iterable, Optional from homeassistant.const import ( ATTR_ENTITY_ID, - STATE_ON, - STATE_OFF, SERVICE_TURN_OFF, SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, ) from homeassistant.core import Context, State from homeassistant.helpers.typing import HomeAssistantType from . import ( - DOMAIN, ATTR_BRIGHTNESS, ATTR_BRIGHTNESS_PCT, ATTR_COLOR_NAME, @@ -29,6 +28,7 @@ from . import ( ATTR_TRANSITION, ATTR_WHITE_VALUE, ATTR_XY_COLOR, + DOMAIN, ) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/mqtt/light/__init__.py b/homeassistant/components/mqtt/light/__init__.py index 95a850fb9e8..a72008c059f 100644 --- a/homeassistant/components/mqtt/light/__init__.py +++ b/homeassistant/components/mqtt/light/__init__.py @@ -15,7 +15,8 @@ from homeassistant.components.mqtt.discovery import ( clear_discovery_hash, ) from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.typing import HomeAssistantType, ConfigType +from homeassistant.helpers.typing import ConfigType, HomeAssistantType + from .schema import CONF_SCHEMA, MQTT_LIGHT_SCHEMA_SCHEMA from .schema_basic import PLATFORM_SCHEMA_BASIC, async_setup_entity_basic from .schema_json import PLATFORM_SCHEMA_JSON, async_setup_entity_json diff --git a/homeassistant/components/mqtt/light/schema_basic.py b/homeassistant/components/mqtt/light/schema_basic.py index 829809dd9c3..ff57db7c8c1 100644 --- a/homeassistant/components/mqtt/light/schema_basic.py +++ b/homeassistant/components/mqtt/light/schema_basic.py @@ -8,7 +8,6 @@ import logging import voluptuous as vol -from homeassistant.core import callback from homeassistant.components import mqtt from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -16,29 +15,12 @@ from homeassistant.components.light import ( ATTR_EFFECT, ATTR_HS_COLOR, ATTR_WHITE_VALUE, - Light, SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, - SUPPORT_COLOR, SUPPORT_WHITE_VALUE, -) -from homeassistant.const import ( - CONF_BRIGHTNESS, - CONF_COLOR_TEMP, - CONF_DEVICE, - CONF_EFFECT, - CONF_HS, - CONF_NAME, - CONF_OPTIMISTIC, - CONF_PAYLOAD_OFF, - CONF_PAYLOAD_ON, - STATE_ON, - CONF_RGB, - CONF_STATE, - CONF_VALUE_TEMPLATE, - CONF_WHITE_VALUE, - CONF_XY, + Light, ) from homeassistant.components.mqtt import ( CONF_COMMAND_TOPIC, @@ -52,8 +34,26 @@ from homeassistant.components.mqtt import ( MqttEntityDeviceInfo, subscription, ) -from homeassistant.helpers.restore_state import RestoreEntity +from homeassistant.const import ( + CONF_BRIGHTNESS, + CONF_COLOR_TEMP, + CONF_DEVICE, + CONF_EFFECT, + CONF_HS, + CONF_NAME, + CONF_OPTIMISTIC, + CONF_PAYLOAD_OFF, + CONF_PAYLOAD_ON, + CONF_RGB, + CONF_STATE, + CONF_VALUE_TEMPLATE, + CONF_WHITE_VALUE, + CONF_XY, + STATE_ON, +) +from homeassistant.core import callback import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.restore_state import RestoreEntity import homeassistant.util.color as color_util from .schema import MQTT_LIGHT_SCHEMA_SCHEMA diff --git a/homeassistant/components/mqtt/light/schema_template.py b/homeassistant/components/mqtt/light/schema_template.py index c80ab2f95a7..dd69a8e87d6 100644 --- a/homeassistant/components/mqtt/light/schema_template.py +++ b/homeassistant/components/mqtt/light/schema_template.py @@ -5,9 +5,9 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/light.mqtt_template/ """ import logging + import voluptuous as vol -from homeassistant.core import callback from homeassistant.components import mqtt from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -17,21 +17,14 @@ from homeassistant.components.light import ( ATTR_HS_COLOR, ATTR_TRANSITION, ATTR_WHITE_VALUE, - Light, SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, SUPPORT_FLASH, - SUPPORT_COLOR, SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE, -) -from homeassistant.const import ( - CONF_DEVICE, - CONF_NAME, - CONF_OPTIMISTIC, - STATE_ON, - STATE_OFF, + Light, ) from homeassistant.components.mqtt import ( CONF_COMMAND_TOPIC, @@ -45,9 +38,17 @@ from homeassistant.components.mqtt import ( MqttEntityDeviceInfo, subscription, ) +from homeassistant.const import ( + CONF_DEVICE, + CONF_NAME, + CONF_OPTIMISTIC, + STATE_OFF, + STATE_ON, +) +from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -import homeassistant.util.color as color_util from homeassistant.helpers.restore_state import RestoreEntity +import homeassistant.util.color as color_util from .schema import MQTT_LIGHT_SCHEMA_SCHEMA diff --git a/tests/components/light/common.py b/tests/components/light/common.py index 32678bf4daa..aa1e62db5bf 100644 --- a/tests/components/light/common.py +++ b/tests/components/light/common.py @@ -21,10 +21,10 @@ from homeassistant.components.light import ( ) from homeassistant.const import ( ATTR_ENTITY_ID, + ENTITY_MATCH_ALL, SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON, - ENTITY_MATCH_ALL, ) from homeassistant.loader import bind_hass diff --git a/tests/components/light/test_device_action.py b/tests/components/light/test_device_action.py index bb50778db52..a3cf57a7dbe 100644 --- a/tests/components/light/test_device_action.py +++ b/tests/components/light/test_device_action.py @@ -1,18 +1,18 @@ """The test for light device automation.""" import pytest -from homeassistant.components.light import DOMAIN -from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM -from homeassistant.setup import async_setup_component import homeassistant.components.automation as automation +from homeassistant.components.light import DOMAIN +from homeassistant.const import CONF_PLATFORM, STATE_OFF, STATE_ON from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component from tests.common import ( MockConfigEntry, + async_get_device_automations, async_mock_service, mock_device_registry, mock_registry, - async_get_device_automations, ) diff --git a/tests/components/light/test_device_condition.py b/tests/components/light/test_device_condition.py index a9f4adddfab..7a560dd781d 100644 --- a/tests/components/light/test_device_condition.py +++ b/tests/components/light/test_device_condition.py @@ -1,22 +1,23 @@ """The test for light device automation.""" from datetime import timedelta -import pytest from unittest.mock import patch -from homeassistant.components.light import DOMAIN -from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM -from homeassistant.setup import async_setup_component +import pytest + import homeassistant.components.automation as automation +from homeassistant.components.light import DOMAIN +from homeassistant.const import CONF_PLATFORM, STATE_OFF, STATE_ON from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from tests.common import ( MockConfigEntry, + async_get_device_automation_capabilities, + async_get_device_automations, async_mock_service, mock_device_registry, mock_registry, - async_get_device_automations, - async_get_device_automation_capabilities, ) diff --git a/tests/components/light/test_device_trigger.py b/tests/components/light/test_device_trigger.py index a6437ef9ee0..dd8320c166e 100644 --- a/tests/components/light/test_device_trigger.py +++ b/tests/components/light/test_device_trigger.py @@ -1,22 +1,23 @@ """The test for light device automation.""" from datetime import timedelta + import pytest -from homeassistant.components.light import DOMAIN -from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM -from homeassistant.setup import async_setup_component import homeassistant.components.automation as automation +from homeassistant.components.light import DOMAIN +from homeassistant.const import CONF_PLATFORM, STATE_OFF, STATE_ON from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from tests.common import ( MockConfigEntry, async_fire_time_changed, + async_get_device_automation_capabilities, + async_get_device_automations, async_mock_service, mock_device_registry, mock_registry, - async_get_device_automations, - async_get_device_automation_capabilities, ) diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index 2cf13369bd9..18cc032bd98 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -1,31 +1,27 @@ """The tests for the Light component.""" # pylint: disable=protected-access +from io import StringIO +import os import unittest import unittest.mock as mock -import os -from io import StringIO import pytest from homeassistant import core -from homeassistant.exceptions import Unauthorized -from homeassistant.setup import setup_component, async_setup_component +from homeassistant.components import light from homeassistant.const import ( ATTR_ENTITY_ID, - STATE_ON, - STATE_OFF, CONF_PLATFORM, - SERVICE_TURN_ON, - SERVICE_TURN_OFF, SERVICE_TOGGLE, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, ) -from homeassistant.components import light +from homeassistant.exceptions import Unauthorized +from homeassistant.setup import async_setup_component, setup_component -from tests.common import ( - mock_service, - get_test_home_assistant, - mock_storage, -) +from tests.common import get_test_home_assistant, mock_service, mock_storage from tests.components.light import common diff --git a/tests/components/light/test_intent.py b/tests/components/light/test_intent.py index 594c9a5d1fc..4adba921d5e 100644 --- a/tests/components/light/test_intent.py +++ b/tests/components/light/test_intent.py @@ -1,9 +1,9 @@ """Tests for the light intents.""" -from homeassistant.helpers.intent import IntentHandleError - -from homeassistant.const import ATTR_SUPPORTED_FEATURES, SERVICE_TURN_ON, ATTR_ENTITY_ID from homeassistant.components import light from homeassistant.components.light import intent +from homeassistant.const import ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, SERVICE_TURN_ON +from homeassistant.helpers.intent import IntentHandleError + from tests.common import async_mock_service From b731ddabde71b54fe295e76c26896b884b71ea9e Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 18:16:49 +0100 Subject: [PATCH 108/677] Sort imports according to PEP8 for homekit (#29645) --- homeassistant/components/homekit/__init__.py | 3 +-- .../components/homekit/type_media_players.py | 20 +++++++++---------- .../components/homekit/type_thermostats.py | 4 ++-- tests/components/homekit/conftest.py | 3 +-- tests/components/homekit/test_accessories.py | 8 ++++---- .../homekit/test_get_accessories.py | 10 +++++----- tests/components/homekit/test_homekit.py | 14 ++++++------- tests/components/homekit/test_type_lights.py | 4 ++-- .../homekit/test_type_media_players.py | 2 +- tests/components/homekit/test_type_sensors.py | 4 ++-- .../homekit/test_type_thermostats.py | 4 ++-- 11 files changed, 36 insertions(+), 40 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 525c091e177..cfd59ad61a4 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -45,8 +45,8 @@ from .const import ( DEVICE_CLASS_PM25, DOMAIN, HOMEKIT_FILE, - SERVICE_HOMEKIT_START, SERVICE_HOMEKIT_RESET_ACCESSORY, + SERVICE_HOMEKIT_START, TYPE_FAUCET, TYPE_OUTLET, TYPE_SHOWER, @@ -54,7 +54,6 @@ from .const import ( TYPE_SWITCH, TYPE_VALVE, ) - from .util import ( show_setup_message, validate_entity_config, diff --git a/homeassistant/components/homekit/type_media_players.py b/homeassistant/components/homekit/type_media_players.py index d9b24782610..450ae818ec8 100644 --- a/homeassistant/components/homekit/type_media_players.py +++ b/homeassistant/components/homekit/type_media_players.py @@ -6,16 +6,16 @@ from pyhap.const import CATEGORY_SWITCH, CATEGORY_TELEVISION from homeassistant.components.media_player import ( ATTR_INPUT_SOURCE, ATTR_INPUT_SOURCE_LIST, - ATTR_MEDIA_VOLUME_MUTED, ATTR_MEDIA_VOLUME_LEVEL, - SERVICE_SELECT_SOURCE, + ATTR_MEDIA_VOLUME_MUTED, DOMAIN, + SERVICE_SELECT_SOURCE, SUPPORT_PAUSE, SUPPORT_PLAY, + SUPPORT_SELECT_SOURCE, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP, - SUPPORT_SELECT_SOURCE, ) from homeassistant.const import ( ATTR_ENTITY_ID, @@ -26,13 +26,13 @@ from homeassistant.const import ( SERVICE_MEDIA_STOP, SERVICE_TURN_OFF, SERVICE_TURN_ON, - SERVICE_VOLUME_MUTE, - SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN, + SERVICE_VOLUME_MUTE, SERVICE_VOLUME_SET, + SERVICE_VOLUME_UP, STATE_OFF, - STATE_PLAYING, STATE_PAUSED, + STATE_PLAYING, STATE_UNKNOWN, ) @@ -46,23 +46,23 @@ from .const import ( CHAR_IDENTIFIER, CHAR_INPUT_SOURCE_TYPE, CHAR_IS_CONFIGURED, - CHAR_NAME, - CHAR_SLEEP_DISCOVER_MODE, CHAR_MUTE, + CHAR_NAME, CHAR_ON, CHAR_REMOTE_KEY, + CHAR_SLEEP_DISCOVER_MODE, + CHAR_VOLUME, CHAR_VOLUME_CONTROL_TYPE, CHAR_VOLUME_SELECTOR, - CHAR_VOLUME, CONF_FEATURE_LIST, FEATURE_ON_OFF, FEATURE_PLAY_PAUSE, FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE, + SERV_INPUT_SOURCE, SERV_SWITCH, SERV_TELEVISION, SERV_TELEVISION_SPEAKER, - SERV_INPUT_SOURCE, ) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/homekit/type_thermostats.py b/homeassistant/components/homekit/type_thermostats.py index b6e1e75d3c6..79a9d156f10 100644 --- a/homeassistant/components/homekit/type_thermostats.py +++ b/homeassistant/components/homekit/type_thermostats.py @@ -20,12 +20,12 @@ from homeassistant.components.climate.const import ( DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, DOMAIN as DOMAIN_CLIMATE, + HVAC_MODE_AUTO, HVAC_MODE_COOL, + HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, - HVAC_MODE_AUTO, - HVAC_MODE_FAN_ONLY, SERVICE_SET_HVAC_MODE as SERVICE_SET_HVAC_MODE_THERMOSTAT, SERVICE_SET_TEMPERATURE as SERVICE_SET_TEMPERATURE_THERMOSTAT, SUPPORT_TARGET_TEMPERATURE_RANGE, diff --git a/tests/components/homekit/conftest.py b/tests/components/homekit/conftest.py index c7e749fbad9..ef534d0e472 100644 --- a/tests/components/homekit/conftest.py +++ b/tests/components/homekit/conftest.py @@ -1,13 +1,12 @@ """HomeKit session fixtures.""" from unittest.mock import patch +from pyhap.accessory_driver import AccessoryDriver import pytest from homeassistant.components.homekit.const import EVENT_HOMEKIT_CHANGED from homeassistant.core import callback as ha_callback -from pyhap.accessory_driver import AccessoryDriver - @pytest.fixture(scope="session") def hk_driver(): diff --git a/tests/components/homekit/test_accessories.py b/tests/components/homekit/test_accessories.py index 883d84339e5..0c5810a5b10 100644 --- a/tests/components/homekit/test_accessories.py +++ b/tests/components/homekit/test_accessories.py @@ -3,15 +3,15 @@ This includes tests for all mock object types. """ from datetime import datetime, timedelta -from unittest.mock import patch, Mock +from unittest.mock import Mock, patch import pytest from homeassistant.components.homekit.accessories import ( - debounce, HomeAccessory, HomeBridge, HomeDriver, + debounce, ) from homeassistant.components.homekit.const import ( ATTR_DISPLAY_NAME, @@ -30,13 +30,13 @@ from homeassistant.components.homekit.const import ( SERV_ACCESSORY_INFO, ) from homeassistant.const import ( - __version__, ATTR_BATTERY_CHARGING, ATTR_BATTERY_LEVEL, ATTR_ENTITY_ID, - ATTR_SERVICE, ATTR_NOW, + ATTR_SERVICE, EVENT_TIME_CHANGED, + __version__, ) import homeassistant.util.dt as dt_util diff --git a/tests/components/homekit/test_get_accessories.py b/tests/components/homekit/test_get_accessories.py index eb15b461461..e6bf185c93b 100644 --- a/tests/components/homekit/test_get_accessories.py +++ b/tests/components/homekit/test_get_accessories.py @@ -1,13 +1,11 @@ """Package to test the get_accessory method.""" -from unittest.mock import patch, Mock +from unittest.mock import Mock, patch import pytest -from homeassistant.core import State -import homeassistant.components.cover as cover import homeassistant.components.climate as climate -import homeassistant.components.media_player.const as media_player_c -from homeassistant.components.homekit import get_accessory, TYPES +import homeassistant.components.cover as cover +from homeassistant.components.homekit import TYPES, get_accessory from homeassistant.components.homekit.const import ( CONF_FEATURE_LIST, FEATURE_ON_OFF, @@ -18,6 +16,7 @@ from homeassistant.components.homekit.const import ( TYPE_SWITCH, TYPE_VALVE, ) +import homeassistant.components.media_player.const as media_player_c from homeassistant.const import ( ATTR_CODE, ATTR_DEVICE_CLASS, @@ -28,6 +27,7 @@ from homeassistant.const import ( TEMP_CELSIUS, TEMP_FAHRENHEIT, ) +from homeassistant.core import State def test_not_supported(caplog): diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index 97838eaa852..de6aaf0f11e 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -1,35 +1,34 @@ """Tests for the HomeKit component.""" -from unittest.mock import patch, ANY, Mock +from unittest.mock import ANY, Mock, patch import pytest from homeassistant import setup - from homeassistant.components.homekit import ( - generate_aid, - HomeKit, MAX_DEVICES, STATUS_READY, STATUS_RUNNING, STATUS_STOPPED, STATUS_WAIT, + HomeKit, + generate_aid, ) from homeassistant.components.homekit.accessories import HomeBridge from homeassistant.components.homekit.const import ( + BRIDGE_NAME, CONF_AUTO_START, CONF_SAFE_MODE, - BRIDGE_NAME, DEFAULT_PORT, DEFAULT_SAFE_MODE, DOMAIN, HOMEKIT_FILE, - SERVICE_HOMEKIT_START, SERVICE_HOMEKIT_RESET_ACCESSORY, + SERVICE_HOMEKIT_START, ) from homeassistant.const import ( ATTR_ENTITY_ID, - CONF_NAME, CONF_IP_ADDRESS, + CONF_NAME, CONF_PORT, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, @@ -39,7 +38,6 @@ from homeassistant.helpers.entityfilter import generate_filter from tests.components.homekit.common import patch_debounce - IP_ADDRESS = "127.0.0.1" PATH_HOMEKIT = "homeassistant.components.homekit" diff --git a/tests/components/homekit/test_type_lights.py b/tests/components/homekit/test_type_lights.py index 784a6c82346..3444b2778f6 100644 --- a/tests/components/homekit/test_type_lights.py +++ b/tests/components/homekit/test_type_lights.py @@ -11,14 +11,14 @@ from homeassistant.components.light import ( ATTR_HS_COLOR, DOMAIN, SUPPORT_BRIGHTNESS, - SUPPORT_COLOR_TEMP, SUPPORT_COLOR, + SUPPORT_COLOR_TEMP, ) from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, - STATE_ON, STATE_OFF, + STATE_ON, STATE_UNKNOWN, ) diff --git a/tests/components/homekit/test_type_media_players.py b/tests/components/homekit/test_type_media_players.py index f2682fc86b0..aa007b4d04c 100644 --- a/tests/components/homekit/test_type_media_players.py +++ b/tests/components/homekit/test_type_media_players.py @@ -8,11 +8,11 @@ from homeassistant.components.homekit.const import ( FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE, ) -from homeassistant.components.media_player import DEVICE_CLASS_TV from homeassistant.components.homekit.type_media_players import ( MediaPlayer, TelevisionMediaPlayer, ) +from homeassistant.components.media_player import DEVICE_CLASS_TV from homeassistant.components.media_player.const import ( ATTR_INPUT_SOURCE, ATTR_INPUT_SOURCE_LIST, diff --git a/tests/components/homekit/test_type_sensors.py b/tests/components/homekit/test_type_sensors.py index c2ed4873286..43533840cc6 100644 --- a/tests/components/homekit/test_type_sensors.py +++ b/tests/components/homekit/test_type_sensors.py @@ -5,14 +5,14 @@ from homeassistant.components.homekit.const import ( THRESHOLD_CO2, ) from homeassistant.components.homekit.type_sensors import ( + BINARY_SENSOR_SERVICE_MAP, AirQualitySensor, BinarySensor, - CarbonMonoxideSensor, CarbonDioxideSensor, + CarbonMonoxideSensor, HumiditySensor, LightSensor, TemperatureSensor, - BINARY_SENSOR_SERVICE_MAP, ) from homeassistant.const import ( ATTR_DEVICE_CLASS, diff --git a/tests/components/homekit/test_type_thermostats.py b/tests/components/homekit/test_type_thermostats.py index 9f9ebcdfd32..174b72f780a 100644 --- a/tests/components/homekit/test_type_thermostats.py +++ b/tests/components/homekit/test_type_thermostats.py @@ -20,12 +20,12 @@ from homeassistant.components.climate.const import ( DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, DOMAIN as DOMAIN_CLIMATE, + HVAC_MODE_AUTO, HVAC_MODE_COOL, + HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_AUTO, ) from homeassistant.components.homekit.const import ( ATTR_VALUE, From 0a4979549d9e7a803589883c1715a1a8c982fb83 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 18:17:01 +0100 Subject: [PATCH 109/677] use isort to sort imports according to PEP8 for vacuum (#29650) --- .../components/mqtt/vacuum/__init__.py | 5 ++- .../components/mqtt/vacuum/schema_legacy.py | 19 ++++----- .../components/mqtt/vacuum/schema_state.py | 41 +++++++++---------- homeassistant/components/vacuum/__init__.py | 9 ++-- .../components/vacuum/device_action.py | 12 +++--- .../components/vacuum/device_condition.py | 10 +++-- .../components/vacuum/device_trigger.py | 14 ++++--- tests/components/vacuum/common.py | 4 +- tests/components/vacuum/test_device_action.py | 6 +-- .../vacuum/test_device_condition.py | 6 +-- .../components/vacuum/test_device_trigger.py | 6 +-- 11 files changed, 68 insertions(+), 64 deletions(-) diff --git a/homeassistant/components/mqtt/vacuum/__init__.py b/homeassistant/components/mqtt/vacuum/__init__.py index 12fd4c51693..84f564e5c7e 100644 --- a/homeassistant/components/mqtt/vacuum/__init__.py +++ b/homeassistant/components/mqtt/vacuum/__init__.py @@ -8,14 +8,15 @@ import logging import voluptuous as vol -from homeassistant.components.vacuum import DOMAIN from homeassistant.components.mqtt import ATTR_DISCOVERY_HASH from homeassistant.components.mqtt.discovery import ( MQTT_DISCOVERY_NEW, clear_discovery_hash, ) +from homeassistant.components.vacuum import DOMAIN from homeassistant.helpers.dispatcher import async_dispatcher_connect -from .schema import CONF_SCHEMA, LEGACY, STATE, MQTT_VACUUM_SCHEMA + +from .schema import CONF_SCHEMA, LEGACY, MQTT_VACUUM_SCHEMA, STATE from .schema_legacy import PLATFORM_SCHEMA_LEGACY, async_setup_entity_legacy from .schema_state import PLATFORM_SCHEMA_STATE, async_setup_entity_state diff --git a/homeassistant/components/mqtt/vacuum/schema_legacy.py b/homeassistant/components/mqtt/vacuum/schema_legacy.py index d770cfbb7f8..6c08b18bc9c 100644 --- a/homeassistant/components/mqtt/vacuum/schema_legacy.py +++ b/homeassistant/components/mqtt/vacuum/schema_legacy.py @@ -1,10 +1,18 @@ """Support for Legacy MQTT vacuum.""" -import logging import json +import logging import voluptuous as vol from homeassistant.components import mqtt +from homeassistant.components.mqtt import ( + CONF_UNIQUE_ID, + MqttAttributes, + MqttAvailability, + MqttDiscoveryUpdate, + MqttEntityDeviceInfo, + subscription, +) from homeassistant.components.vacuum import ( SUPPORT_BATTERY, SUPPORT_CLEAN_SPOT, @@ -24,15 +32,6 @@ from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.icon import icon_for_battery_level -from homeassistant.components.mqtt import ( - CONF_UNIQUE_ID, - MqttAttributes, - MqttAvailability, - MqttDiscoveryUpdate, - MqttEntityDeviceInfo, - subscription, -) - from .schema import MQTT_VACUUM_SCHEMA, services_to_strings, strings_to_services _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/mqtt/vacuum/schema_state.py b/homeassistant/components/mqtt/vacuum/schema_state.py index 40b3eeb752c..9dd5053d019 100644 --- a/homeassistant/components/mqtt/vacuum/schema_state.py +++ b/homeassistant/components/mqtt/vacuum/schema_state.py @@ -1,27 +1,39 @@ """Support for a State MQTT vacuum.""" -import logging import json +import logging import voluptuous as vol from homeassistant.components import mqtt +from homeassistant.components.mqtt import ( + CONF_COMMAND_TOPIC, + CONF_QOS, + CONF_RETAIN, + CONF_STATE_TOPIC, + CONF_UNIQUE_ID, + MqttAttributes, + MqttAvailability, + MqttDiscoveryUpdate, + MqttEntityDeviceInfo, + subscription, +) from homeassistant.components.vacuum import ( + STATE_CLEANING, + STATE_DOCKED, + STATE_ERROR, + STATE_IDLE, + STATE_PAUSED, + STATE_RETURNING, SUPPORT_BATTERY, SUPPORT_CLEAN_SPOT, SUPPORT_FAN_SPEED, - SUPPORT_START, SUPPORT_LOCATE, SUPPORT_PAUSE, SUPPORT_RETURN_HOME, SUPPORT_SEND_COMMAND, + SUPPORT_START, SUPPORT_STATUS, SUPPORT_STOP, - STATE_CLEANING, - STATE_DOCKED, - STATE_PAUSED, - STATE_IDLE, - STATE_RETURNING, - STATE_ERROR, StateVacuumDevice, ) from homeassistant.const import ( @@ -33,19 +45,6 @@ from homeassistant.const import ( from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.components.mqtt import ( - CONF_UNIQUE_ID, - MqttAttributes, - MqttAvailability, - MqttDiscoveryUpdate, - MqttEntityDeviceInfo, - subscription, - CONF_COMMAND_TOPIC, - CONF_RETAIN, - CONF_STATE_TOPIC, - CONF_QOS, -) - from .schema import MQTT_VACUUM_SCHEMA, services_to_strings, strings_to_services _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/vacuum/__init__.py b/homeassistant/components/vacuum/__init__.py index 85b3d665e17..62eac6e39f5 100644 --- a/homeassistant/components/vacuum/__init__.py +++ b/homeassistant/components/vacuum/__init__.py @@ -12,21 +12,20 @@ from homeassistant.const import ( # noqa: F401 # STATE_PAUSED/IDLE are API SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON, + STATE_IDLE, STATE_ON, STATE_PAUSED, - STATE_IDLE, ) -from homeassistant.loader import bind_hass import homeassistant.helpers.config_validation as cv from homeassistant.helpers.config_validation import ( # noqa: F401 - make_entity_service_schema, PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE, + make_entity_service_schema, ) +from homeassistant.helpers.entity import Entity, ToggleEntity from homeassistant.helpers.entity_component import EntityComponent -from homeassistant.helpers.entity import ToggleEntity, Entity from homeassistant.helpers.icon import icon_for_battery_level - +from homeassistant.loader import bind_hass # mypy: allow-untyped-defs, no-check-untyped-defs diff --git a/homeassistant/components/vacuum/device_action.py b/homeassistant/components/vacuum/device_action.py index e5f8c162fbd..ed25289da10 100644 --- a/homeassistant/components/vacuum/device_action.py +++ b/homeassistant/components/vacuum/device_action.py @@ -1,18 +1,20 @@ """Provides device automations for Vacuum.""" -from typing import Optional, List +from typing import List, Optional + import voluptuous as vol from homeassistant.const import ( ATTR_ENTITY_ID, - CONF_DOMAIN, - CONF_TYPE, CONF_DEVICE_ID, + CONF_DOMAIN, CONF_ENTITY_ID, + CONF_TYPE, ) -from homeassistant.core import HomeAssistant, Context +from homeassistant.core import Context, HomeAssistant from homeassistant.helpers import entity_registry import homeassistant.helpers.config_validation as cv -from . import DOMAIN, SERVICE_START, SERVICE_RETURN_TO_BASE + +from . import DOMAIN, SERVICE_RETURN_TO_BASE, SERVICE_START ACTION_TYPES = {"clean", "dock"} diff --git a/homeassistant/components/vacuum/device_condition.py b/homeassistant/components/vacuum/device_condition.py index 6a41fe0490e..5a2eefd94f2 100644 --- a/homeassistant/components/vacuum/device_condition.py +++ b/homeassistant/components/vacuum/device_condition.py @@ -1,20 +1,22 @@ """Provide the device automations for Vacuum.""" from typing import Dict, List + import voluptuous as vol from homeassistant.const import ( ATTR_ENTITY_ID, CONF_CONDITION, - CONF_DOMAIN, - CONF_TYPE, CONF_DEVICE_ID, + CONF_DOMAIN, CONF_ENTITY_ID, + CONF_TYPE, ) from homeassistant.core import HomeAssistant from homeassistant.helpers import condition, config_validation as cv, entity_registry -from homeassistant.helpers.typing import ConfigType, TemplateVarsType from homeassistant.helpers.config_validation import DEVICE_CONDITION_BASE_SCHEMA -from . import DOMAIN, STATE_DOCKED, STATE_CLEANING, STATE_RETURNING +from homeassistant.helpers.typing import ConfigType, TemplateVarsType + +from . import DOMAIN, STATE_CLEANING, STATE_DOCKED, STATE_RETURNING CONDITION_TYPES = {"is_cleaning", "is_docked"} diff --git a/homeassistant/components/vacuum/device_trigger.py b/homeassistant/components/vacuum/device_trigger.py index 328db54b1b9..ee225ab3caa 100644 --- a/homeassistant/components/vacuum/device_trigger.py +++ b/homeassistant/components/vacuum/device_trigger.py @@ -1,19 +1,21 @@ """Provides device automations for Vacuum.""" from typing import List + import voluptuous as vol +from homeassistant.components.automation import AutomationActionType, state +from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA from homeassistant.const import ( - CONF_DOMAIN, - CONF_TYPE, - CONF_PLATFORM, CONF_DEVICE_ID, + CONF_DOMAIN, CONF_ENTITY_ID, + CONF_PLATFORM, + CONF_TYPE, ) -from homeassistant.core import HomeAssistant, CALLBACK_TYPE +from homeassistant.core import CALLBACK_TYPE, HomeAssistant from homeassistant.helpers import config_validation as cv, entity_registry from homeassistant.helpers.typing import ConfigType -from homeassistant.components.automation import state, AutomationActionType -from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA + from . import DOMAIN, STATE_CLEANING, STATE_DOCKED, STATES TRIGGER_TYPES = {"cleaning", "docked"} diff --git a/tests/components/vacuum/common.py b/tests/components/vacuum/common.py index 59f600590b1..6cecbda9968 100644 --- a/tests/components/vacuum/common.py +++ b/tests/components/vacuum/common.py @@ -10,20 +10,20 @@ from homeassistant.components.vacuum import ( SERVICE_CLEAN_SPOT, SERVICE_LOCATE, SERVICE_PAUSE, + SERVICE_RETURN_TO_BASE, SERVICE_SEND_COMMAND, SERVICE_SET_FAN_SPEED, SERVICE_START, SERVICE_START_PAUSE, SERVICE_STOP, - SERVICE_RETURN_TO_BASE, ) from homeassistant.const import ( ATTR_COMMAND, ATTR_ENTITY_ID, + ENTITY_MATCH_ALL, SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON, - ENTITY_MATCH_ALL, ) from homeassistant.loader import bind_hass diff --git a/tests/components/vacuum/test_device_action.py b/tests/components/vacuum/test_device_action.py index a7c0859408f..47ce5423f7d 100644 --- a/tests/components/vacuum/test_device_action.py +++ b/tests/components/vacuum/test_device_action.py @@ -1,18 +1,18 @@ """The tests for Vacuum device actions.""" import pytest -from homeassistant.components.vacuum import DOMAIN -from homeassistant.setup import async_setup_component import homeassistant.components.automation as automation +from homeassistant.components.vacuum import DOMAIN from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component from tests.common import ( MockConfigEntry, assert_lists_same, + async_get_device_automations, async_mock_service, mock_device_registry, mock_registry, - async_get_device_automations, ) diff --git a/tests/components/vacuum/test_device_condition.py b/tests/components/vacuum/test_device_condition.py index 80e7b72c36f..7be944305da 100644 --- a/tests/components/vacuum/test_device_condition.py +++ b/tests/components/vacuum/test_device_condition.py @@ -1,23 +1,23 @@ """The tests for Vacuum device conditions.""" import pytest +import homeassistant.components.automation as automation from homeassistant.components.vacuum import ( DOMAIN, STATE_CLEANING, STATE_DOCKED, STATE_RETURNING, ) -from homeassistant.setup import async_setup_component -import homeassistant.components.automation as automation from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component from tests.common import ( MockConfigEntry, assert_lists_same, + async_get_device_automations, async_mock_service, mock_device_registry, mock_registry, - async_get_device_automations, ) diff --git a/tests/components/vacuum/test_device_trigger.py b/tests/components/vacuum/test_device_trigger.py index 680b6482186..1f9666b1774 100644 --- a/tests/components/vacuum/test_device_trigger.py +++ b/tests/components/vacuum/test_device_trigger.py @@ -1,18 +1,18 @@ """The tests for Vacuum device triggers.""" import pytest -from homeassistant.components.vacuum import DOMAIN, STATE_DOCKED, STATE_CLEANING -from homeassistant.setup import async_setup_component import homeassistant.components.automation as automation +from homeassistant.components.vacuum import DOMAIN, STATE_CLEANING, STATE_DOCKED from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component from tests.common import ( MockConfigEntry, assert_lists_same, + async_get_device_automations, async_mock_service, mock_device_registry, mock_registry, - async_get_device_automations, ) From b832749e3c68a8e731ab0c686a8211d5539fa7d6 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 8 Dec 2019 23:11:48 +0530 Subject: [PATCH 110/677] Fix file permission (#29660) --- homeassistant/components/intesishome/__init__.py | 0 homeassistant/components/intesishome/climate.py | 0 homeassistant/components/intesishome/manifest.json | 0 3 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 homeassistant/components/intesishome/__init__.py mode change 100755 => 100644 homeassistant/components/intesishome/climate.py mode change 100755 => 100644 homeassistant/components/intesishome/manifest.json diff --git a/homeassistant/components/intesishome/__init__.py b/homeassistant/components/intesishome/__init__.py old mode 100755 new mode 100644 diff --git a/homeassistant/components/intesishome/climate.py b/homeassistant/components/intesishome/climate.py old mode 100755 new mode 100644 diff --git a/homeassistant/components/intesishome/manifest.json b/homeassistant/components/intesishome/manifest.json old mode 100755 new mode 100644 From 3d6440589629c5f6bd2c025fc16c127ee20ac1ce Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 18:48:18 +0100 Subject: [PATCH 111/677] Sort imports according to PEP8 for recorder (#29652) --- homeassistant/components/recorder/__init__.py | 8 ++++---- homeassistant/components/recorder/migration.py | 2 +- homeassistant/components/recorder/models.py | 4 ++-- homeassistant/components/recorder/purge.py | 2 +- tests/components/recorder/models_original.py | 4 ++-- tests/components/recorder/test_init.py | 8 ++++---- tests/components/recorder/test_migrate.py | 5 +++-- tests/components/recorder/test_models.py | 6 +++--- tests/components/recorder/test_purge.py | 5 +++-- tests/components/recorder/test_util.py | 3 ++- 10 files changed, 25 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index 10b1d04304f..7ae1cb1a220 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -5,18 +5,19 @@ import concurrent.futures from datetime import datetime, timedelta import logging import queue +from sqlite3 import Connection import threading import time from typing import Any, Dict, Optional -from sqlite3 import Connection -import voluptuous as vol -from sqlalchemy import exc, create_engine +from sqlalchemy import create_engine, exc from sqlalchemy.engine import Engine from sqlalchemy.event import listens_for from sqlalchemy.orm import scoped_session, sessionmaker from sqlalchemy.pool import StaticPool +import voluptuous as vol +from homeassistant.components import persistent_notification from homeassistant.const import ( ATTR_ENTITY_ID, CONF_DOMAINS, @@ -29,7 +30,6 @@ from homeassistant.const import ( EVENT_TIME_CHANGED, MATCH_ALL, ) -from homeassistant.components import persistent_notification from homeassistant.core import CoreState, HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entityfilter import generate_filter diff --git a/homeassistant/components/recorder/migration.py b/homeassistant/components/recorder/migration.py index 33a01ea1ac0..3a5ef2729be 100644 --- a/homeassistant/components/recorder/migration.py +++ b/homeassistant/components/recorder/migration.py @@ -6,7 +6,7 @@ from sqlalchemy import Table, text from sqlalchemy.engine import reflection from sqlalchemy.exc import OperationalError, SQLAlchemyError -from .models import SchemaChanges, SCHEMA_VERSION, Base +from .models import SCHEMA_VERSION, Base, SchemaChanges from .util import session_scope _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/recorder/models.py b/homeassistant/components/recorder/models.py index b512bfc8204..f3e80a9a739 100644 --- a/homeassistant/components/recorder/models.py +++ b/homeassistant/components/recorder/models.py @@ -1,6 +1,6 @@ """Models for SQLAlchemy.""" -import json from datetime import datetime +import json import logging from sqlalchemy import ( @@ -17,9 +17,9 @@ from sqlalchemy import ( from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm.session import Session -import homeassistant.util.dt as dt_util from homeassistant.core import Context, Event, EventOrigin, State, split_entity_id from homeassistant.helpers.json import JSONEncoder +import homeassistant.util.dt as dt_util # SQLAlchemy Schema # pylint: disable=invalid-name diff --git a/homeassistant/components/recorder/purge.py b/homeassistant/components/recorder/purge.py index 089476245fe..b4b1f612fac 100644 --- a/homeassistant/components/recorder/purge.py +++ b/homeassistant/components/recorder/purge.py @@ -5,8 +5,8 @@ import logging from sqlalchemy.exc import SQLAlchemyError import homeassistant.util.dt as dt_util -from .models import Events, States +from .models import Events, States from .util import session_scope _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/recorder/models_original.py b/tests/components/recorder/models_original.py index 526116aae7b..25978ef6d55 100644 --- a/tests/components/recorder/models_original.py +++ b/tests/components/recorder/models_original.py @@ -4,8 +4,8 @@ This file contains the original models definitions before schema tracking was implemented. It is used to test the schema migration logic. """ -import json from datetime import datetime +import json import logging from sqlalchemy import ( @@ -21,9 +21,9 @@ from sqlalchemy import ( ) from sqlalchemy.ext.declarative import declarative_base -import homeassistant.util.dt as dt_util from homeassistant.core import Event, EventOrigin, State, split_entity_id from homeassistant.helpers.json import JSONEncoder +import homeassistant.util.dt as dt_util # SQLAlchemy Schema # pylint: disable=invalid-name diff --git a/tests/components/recorder/test_init.py b/tests/components/recorder/test_init.py index 0f5ee24e6e8..2ee3126b9fa 100644 --- a/tests/components/recorder/test_init.py +++ b/tests/components/recorder/test_init.py @@ -5,13 +5,13 @@ from unittest.mock import patch import pytest -from homeassistant.core import callback -from homeassistant.const import MATCH_ALL -from homeassistant.setup import async_setup_component from homeassistant.components.recorder import Recorder from homeassistant.components.recorder.const import DATA_INSTANCE +from homeassistant.components.recorder.models import Events, States from homeassistant.components.recorder.util import session_scope -from homeassistant.components.recorder.models import States, Events +from homeassistant.const import MATCH_ALL +from homeassistant.core import callback +from homeassistant.setup import async_setup_component from tests.common import get_test_home_assistant, init_recorder_component diff --git a/tests/components/recorder/test_migrate.py b/tests/components/recorder/test_migrate.py index 81e0423a723..7947ba5ccef 100644 --- a/tests/components/recorder/test_migrate.py +++ b/tests/components/recorder/test_migrate.py @@ -1,13 +1,14 @@ """The tests for the Recorder component.""" # pylint: disable=protected-access -from unittest.mock import patch, call +from unittest.mock import call, patch import pytest from sqlalchemy import create_engine from sqlalchemy.pool import StaticPool from homeassistant.bootstrap import async_setup_component -from homeassistant.components.recorder import migration, const, models +from homeassistant.components.recorder import const, migration, models + from tests.components.recorder import models_original diff --git a/tests/components/recorder/test_models.py b/tests/components/recorder/test_models.py index 5d4ac46102e..276194b5d6c 100644 --- a/tests/components/recorder/test_models.py +++ b/tests/components/recorder/test_models.py @@ -1,14 +1,14 @@ """The tests for the Recorder component.""" -import unittest from datetime import datetime +import unittest from sqlalchemy import create_engine from sqlalchemy.orm import scoped_session, sessionmaker -import homeassistant.core as ha +from homeassistant.components.recorder.models import Base, Events, RecorderRuns, States from homeassistant.const import EVENT_STATE_CHANGED +import homeassistant.core as ha from homeassistant.util import dt -from homeassistant.components.recorder.models import Base, Events, States, RecorderRuns ENGINE = None SESSION = None diff --git a/tests/components/recorder/test_purge.py b/tests/components/recorder/test_purge.py index 7e06dcd1e5e..e0993b8cffc 100644 --- a/tests/components/recorder/test_purge.py +++ b/tests/components/recorder/test_purge.py @@ -1,14 +1,15 @@ """Test data purging.""" -import json from datetime import datetime, timedelta +import json import unittest from unittest.mock import patch from homeassistant.components import recorder from homeassistant.components.recorder.const import DATA_INSTANCE +from homeassistant.components.recorder.models import Events, States from homeassistant.components.recorder.purge import purge_old_data -from homeassistant.components.recorder.models import States, Events from homeassistant.components.recorder.util import session_scope + from tests.common import get_test_home_assistant, init_recorder_component diff --git a/tests/components/recorder/test_util.py b/tests/components/recorder/test_util.py index 8dcfac3c001..47f2bf4beca 100644 --- a/tests/components/recorder/test_util.py +++ b/tests/components/recorder/test_util.py @@ -1,10 +1,11 @@ """Test util methods.""" -from unittest.mock import patch, MagicMock +from unittest.mock import MagicMock, patch import pytest from homeassistant.components.recorder import util from homeassistant.components.recorder.const import DATA_INSTANCE + from tests.common import get_test_home_assistant, init_recorder_component From e0b82440ad9f64919f3bddb2c2c487666fda12a1 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 18:50:17 +0100 Subject: [PATCH 112/677] Sort imports according to PEP8 for switch (#29654) --- homeassistant/components/switch/__init__.py | 21 +++++++++---------- .../components/switch/device_action.py | 7 ++++--- .../components/switch/device_condition.py | 7 ++++--- .../components/switch/device_trigger.py | 5 +++-- homeassistant/components/switch/light.py | 6 ++---- .../components/switch/reproduce_state.py | 4 ++-- tests/components/switch/common.py | 2 +- tests/components/switch/test_device_action.py | 6 +++--- .../switch/test_device_condition.py | 13 ++++++------ .../components/switch/test_device_trigger.py | 11 +++++----- tests/components/switch/test_init.py | 4 ++-- tests/components/switch/test_light.py | 1 + 12 files changed, 45 insertions(+), 42 deletions(-) diff --git a/homeassistant/components/switch/__init__.py b/homeassistant/components/switch/__init__.py index 26d5658d668..78c57e001aa 100644 --- a/homeassistant/components/switch/__init__.py +++ b/homeassistant/components/switch/__init__.py @@ -4,21 +4,20 @@ import logging import voluptuous as vol -from homeassistant.loader import bind_hass -from homeassistant.helpers.entity_component import EntityComponent -from homeassistant.helpers.entity import ToggleEntity +from homeassistant.components import group +from homeassistant.const import ( + SERVICE_TOGGLE, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_ON, +) from homeassistant.helpers.config_validation import ( # noqa: F401 PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE, ) -from homeassistant.const import ( - STATE_ON, - SERVICE_TURN_ON, - SERVICE_TURN_OFF, - SERVICE_TOGGLE, -) -from homeassistant.components import group - +from homeassistant.helpers.entity import ToggleEntity +from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.loader import bind_hass # mypy: allow-untyped-defs, no-check-untyped-defs diff --git a/homeassistant/components/switch/device_action.py b/homeassistant/components/switch/device_action.py index a65c1acc512..a50131f094c 100644 --- a/homeassistant/components/switch/device_action.py +++ b/homeassistant/components/switch/device_action.py @@ -1,13 +1,14 @@ """Provides device actions for switches.""" from typing import List + import voluptuous as vol -from homeassistant.core import HomeAssistant, Context from homeassistant.components.device_automation import toggle_entity from homeassistant.const import CONF_DOMAIN -from homeassistant.helpers.typing import TemplateVarsType, ConfigType -from . import DOMAIN +from homeassistant.core import Context, HomeAssistant +from homeassistant.helpers.typing import ConfigType, TemplateVarsType +from . import DOMAIN ACTION_SCHEMA = toggle_entity.ACTION_SCHEMA.extend({vol.Required(CONF_DOMAIN): DOMAIN}) diff --git a/homeassistant/components/switch/device_condition.py b/homeassistant/components/switch/device_condition.py index 56f8f6c196e..87aefdb616d 100644 --- a/homeassistant/components/switch/device_condition.py +++ b/homeassistant/components/switch/device_condition.py @@ -1,14 +1,15 @@ """Provides device conditions for switches.""" from typing import Dict, List + import voluptuous as vol -from homeassistant.core import HomeAssistant from homeassistant.components.device_automation import toggle_entity from homeassistant.const import CONF_DOMAIN -from homeassistant.helpers.typing import ConfigType +from homeassistant.core import HomeAssistant from homeassistant.helpers.condition import ConditionCheckerType -from . import DOMAIN +from homeassistant.helpers.typing import ConfigType +from . import DOMAIN CONDITION_SCHEMA = toggle_entity.CONDITION_SCHEMA.extend( {vol.Required(CONF_DOMAIN): DOMAIN} diff --git a/homeassistant/components/switch/device_trigger.py b/homeassistant/components/switch/device_trigger.py index 7f0458b3e9f..cb5d5f7aa0e 100644 --- a/homeassistant/components/switch/device_trigger.py +++ b/homeassistant/components/switch/device_trigger.py @@ -1,14 +1,15 @@ """Provides device triggers for switches.""" from typing import List + import voluptuous as vol -from homeassistant.core import HomeAssistant, CALLBACK_TYPE from homeassistant.components.automation import AutomationActionType from homeassistant.components.device_automation import toggle_entity from homeassistant.const import CONF_DOMAIN +from homeassistant.core import CALLBACK_TYPE, HomeAssistant from homeassistant.helpers.typing import ConfigType -from . import DOMAIN +from . import DOMAIN TRIGGER_SCHEMA = toggle_entity.TRIGGER_SCHEMA.extend( {vol.Required(CONF_DOMAIN): DOMAIN} diff --git a/homeassistant/components/switch/light.py b/homeassistant/components/switch/light.py index 1bdc1d39083..5486b8d880c 100644 --- a/homeassistant/components/switch/light.py +++ b/homeassistant/components/switch/light.py @@ -1,10 +1,11 @@ """Light support for switch entities.""" import logging -from typing import cast, Callable, Dict, Optional, Sequence +from typing import Callable, Dict, Optional, Sequence, cast import voluptuous as vol from homeassistant.components import switch +from homeassistant.components.light import PLATFORM_SCHEMA, Light from homeassistant.const import ( ATTR_ENTITY_ID, CONF_ENTITY_ID, @@ -18,9 +19,6 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers.typing import ConfigType, HomeAssistantType -from homeassistant.components.light import PLATFORM_SCHEMA, Light - - # mypy: allow-untyped-calls, allow-untyped-defs, no-check-untyped-defs _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/switch/reproduce_state.py b/homeassistant/components/switch/reproduce_state.py index 7ed1f70cb97..d2bfc569956 100644 --- a/homeassistant/components/switch/reproduce_state.py +++ b/homeassistant/components/switch/reproduce_state.py @@ -5,10 +5,10 @@ from typing import Iterable, Optional from homeassistant.const import ( ATTR_ENTITY_ID, - STATE_ON, - STATE_OFF, SERVICE_TURN_OFF, SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, ) from homeassistant.core import Context, State from homeassistant.helpers.typing import HomeAssistantType diff --git a/tests/components/switch/common.py b/tests/components/switch/common.py index 2c6b1ccd0d2..1123b1de6c1 100644 --- a/tests/components/switch/common.py +++ b/tests/components/switch/common.py @@ -6,9 +6,9 @@ components. Instead call the service directly. from homeassistant.components.switch import DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, + ENTITY_MATCH_ALL, SERVICE_TURN_OFF, SERVICE_TURN_ON, - ENTITY_MATCH_ALL, ) from homeassistant.loader import bind_hass diff --git a/tests/components/switch/test_device_action.py b/tests/components/switch/test_device_action.py index 888e06e0214..06ad7323ead 100644 --- a/tests/components/switch/test_device_action.py +++ b/tests/components/switch/test_device_action.py @@ -1,14 +1,14 @@ """The test for switch device automation.""" import pytest -from homeassistant.components.switch import DOMAIN -from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM -from homeassistant.setup import async_setup_component import homeassistant.components.automation as automation from homeassistant.components.device_automation import ( _async_get_device_automations as async_get_device_automations, ) +from homeassistant.components.switch import DOMAIN +from homeassistant.const import CONF_PLATFORM, STATE_OFF, STATE_ON from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component from tests.common import ( MockConfigEntry, diff --git a/tests/components/switch/test_device_condition.py b/tests/components/switch/test_device_condition.py index e673527fada..d51a00ddf79 100644 --- a/tests/components/switch/test_device_condition.py +++ b/tests/components/switch/test_device_condition.py @@ -1,22 +1,23 @@ """The test for switch device automation.""" from datetime import timedelta -import pytest from unittest.mock import patch -from homeassistant.components.switch import DOMAIN -from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM -from homeassistant.setup import async_setup_component +import pytest + import homeassistant.components.automation as automation +from homeassistant.components.switch import DOMAIN +from homeassistant.const import CONF_PLATFORM, STATE_OFF, STATE_ON from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from tests.common import ( MockConfigEntry, + async_get_device_automation_capabilities, + async_get_device_automations, async_mock_service, mock_device_registry, mock_registry, - async_get_device_automations, - async_get_device_automation_capabilities, ) diff --git a/tests/components/switch/test_device_trigger.py b/tests/components/switch/test_device_trigger.py index 31fb6d30f60..19588ebfba0 100644 --- a/tests/components/switch/test_device_trigger.py +++ b/tests/components/switch/test_device_trigger.py @@ -1,22 +1,23 @@ """The test for switch device automation.""" from datetime import timedelta + import pytest -from homeassistant.components.switch import DOMAIN -from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM -from homeassistant.setup import async_setup_component import homeassistant.components.automation as automation +from homeassistant.components.switch import DOMAIN +from homeassistant.const import CONF_PLATFORM, STATE_OFF, STATE_ON from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from tests.common import ( MockConfigEntry, async_fire_time_changed, + async_get_device_automation_capabilities, + async_get_device_automations, async_mock_service, mock_device_registry, mock_registry, - async_get_device_automations, - async_get_device_automation_capabilities, ) diff --git a/tests/components/switch/test_init.py b/tests/components/switch/test_init.py index a9463cb78f4..bebebafc763 100644 --- a/tests/components/switch/test_init.py +++ b/tests/components/switch/test_init.py @@ -2,10 +2,10 @@ # pylint: disable=protected-access import unittest -from homeassistant.setup import setup_component, async_setup_component from homeassistant import core from homeassistant.components import switch -from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM +from homeassistant.const import CONF_PLATFORM, STATE_OFF, STATE_ON +from homeassistant.setup import async_setup_component, setup_component from tests.common import get_test_home_assistant, mock_entity_platform from tests.components.switch import common diff --git a/tests/components/switch/test_light.py b/tests/components/switch/test_light.py index e5c5e5c0aed..3034877c6d6 100644 --- a/tests/components/switch/test_light.py +++ b/tests/components/switch/test_light.py @@ -1,6 +1,7 @@ """The tests for the Light Switch platform.""" from homeassistant.setup import async_setup_component + from tests.components.light import common from tests.components.switch import common as switch_common From 415176e350d64ebe0523bedd5b4101c194a5f7de Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 21:05:08 +0100 Subject: [PATCH 113/677] Sort imports according to PEP8 for template (#29655) --- homeassistant/components/template/__init__.py | 3 +- .../components/template/binary_sensor.py | 19 ++++----- homeassistant/components/template/cover.py | 39 ++++++++++--------- homeassistant/components/template/fan.py | 29 +++++++------- homeassistant/components/template/light.py | 19 ++++----- homeassistant/components/template/lock.py | 12 +++--- homeassistant/components/template/sensor.py | 22 +++++------ homeassistant/components/template/switch.py | 15 +++---- homeassistant/components/template/vacuum.py | 23 +++++------ .../components/template/test_binary_sensor.py | 16 ++++---- tests/components/template/test_cover.py | 11 +++--- tests/components/template/test_fan.py | 16 ++++---- tests/components/template/test_light.py | 6 +-- tests/components/template/test_lock.py | 7 ++-- tests/components/template/test_sensor.py | 12 ++++-- tests/components/template/test_switch.py | 6 +-- tests/components/template/test_vacuum.py | 5 ++- 17 files changed, 135 insertions(+), 125 deletions(-) diff --git a/homeassistant/components/template/__init__.py b/homeassistant/components/template/__init__.py index 80421b8e3f8..f100d663d8c 100644 --- a/homeassistant/components/template/__init__.py +++ b/homeassistant/components/template/__init__.py @@ -1,11 +1,10 @@ """The template component.""" +from itertools import chain import logging -from itertools import chain from homeassistant.const import MATCH_ALL - _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/template/binary_sensor.py b/homeassistant/components/template/binary_sensor.py index 116862abc79..3ca25a33d64 100644 --- a/homeassistant/components/template/binary_sensor.py +++ b/homeassistant/components/template/binary_sensor.py @@ -3,28 +3,29 @@ import logging import voluptuous as vol -from homeassistant.core import callback from homeassistant.components.binary_sensor import ( - BinarySensorDevice, + DEVICE_CLASSES_SCHEMA, ENTITY_ID_FORMAT, PLATFORM_SCHEMA, - DEVICE_CLASSES_SCHEMA, + BinarySensorDevice, ) from homeassistant.const import ( - ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, - CONF_VALUE_TEMPLATE, - CONF_ICON_TEMPLATE, - CONF_ENTITY_PICTURE_TEMPLATE, - CONF_SENSORS, + ATTR_FRIENDLY_NAME, CONF_DEVICE_CLASS, + CONF_ENTITY_PICTURE_TEMPLATE, + CONF_ICON_TEMPLATE, + CONF_SENSORS, + CONF_VALUE_TEMPLATE, EVENT_HOMEASSISTANT_START, MATCH_ALL, ) +from homeassistant.core import callback from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id -from homeassistant.helpers.event import async_track_state_change, async_track_same_state +from homeassistant.helpers.event import async_track_same_state, async_track_state_change + from . import extract_entities, initialise_templates from .const import CONF_AVAILABILITY_TEMPLATE diff --git a/homeassistant/components/template/cover.py b/homeassistant/components/template/cover.py index 22035af24ec..c55c3f97df5 100644 --- a/homeassistant/components/template/cover.py +++ b/homeassistant/components/template/cover.py @@ -3,40 +3,41 @@ import logging import voluptuous as vol -from homeassistant.core import callback from homeassistant.components.cover import ( - ENTITY_ID_FORMAT, - CoverDevice, - PLATFORM_SCHEMA, - DEVICE_CLASSES_SCHEMA, - SUPPORT_OPEN_TILT, - SUPPORT_CLOSE_TILT, - SUPPORT_STOP_TILT, - SUPPORT_SET_TILT_POSITION, - SUPPORT_OPEN, - SUPPORT_CLOSE, - SUPPORT_STOP, - SUPPORT_SET_POSITION, ATTR_POSITION, ATTR_TILT_POSITION, + DEVICE_CLASSES_SCHEMA, + ENTITY_ID_FORMAT, + PLATFORM_SCHEMA, + SUPPORT_CLOSE, + SUPPORT_CLOSE_TILT, + SUPPORT_OPEN, + SUPPORT_OPEN_TILT, + SUPPORT_SET_POSITION, + SUPPORT_SET_TILT_POSITION, + SUPPORT_STOP, + SUPPORT_STOP_TILT, + CoverDevice, ) from homeassistant.const import ( - CONF_FRIENDLY_NAME, - CONF_ENTITY_ID, - EVENT_HOMEASSISTANT_START, - CONF_VALUE_TEMPLATE, - CONF_ICON_TEMPLATE, CONF_DEVICE_CLASS, + CONF_ENTITY_ID, CONF_ENTITY_PICTURE_TEMPLATE, + CONF_FRIENDLY_NAME, + CONF_ICON_TEMPLATE, CONF_OPTIMISTIC, - STATE_OPEN, + CONF_VALUE_TEMPLATE, + EVENT_HOMEASSISTANT_START, STATE_CLOSED, + STATE_OPEN, ) +from homeassistant.core import callback from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers.script import Script + from . import extract_entities, initialise_templates from .const import CONF_AVAILABILITY_TEMPLATE diff --git a/homeassistant/components/template/fan.py b/homeassistant/components/template/fan.py index ebb9bcc8b14..89f54444376 100644 --- a/homeassistant/components/template/fan.py +++ b/homeassistant/components/template/fan.py @@ -3,35 +3,36 @@ import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.fan import ( - SPEED_LOW, - SPEED_MEDIUM, - SPEED_HIGH, - SUPPORT_SET_SPEED, - SUPPORT_OSCILLATE, - FanEntity, - ATTR_SPEED, + ATTR_DIRECTION, ATTR_OSCILLATING, - ENTITY_ID_FORMAT, - SUPPORT_DIRECTION, + ATTR_SPEED, DIRECTION_FORWARD, DIRECTION_REVERSE, - ATTR_DIRECTION, + ENTITY_ID_FORMAT, + SPEED_HIGH, + SPEED_LOW, + SPEED_MEDIUM, + SUPPORT_DIRECTION, + SUPPORT_OSCILLATE, + SUPPORT_SET_SPEED, + FanEntity, ) from homeassistant.const import ( + CONF_ENTITY_ID, CONF_FRIENDLY_NAME, CONF_VALUE_TEMPLATE, - CONF_ENTITY_ID, - STATE_ON, - STATE_OFF, EVENT_HOMEASSISTANT_START, + STATE_OFF, + STATE_ON, STATE_UNKNOWN, ) from homeassistant.core import callback from homeassistant.exceptions import TemplateError +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.script import Script + from . import extract_entities, initialise_templates from .const import CONF_AVAILABILITY_TEMPLATE diff --git a/homeassistant/components/template/light.py b/homeassistant/components/template/light.py index b71aadd0155..e18833aae39 100644 --- a/homeassistant/components/template/light.py +++ b/homeassistant/components/template/light.py @@ -3,30 +3,31 @@ import logging import voluptuous as vol -from homeassistant.core import callback from homeassistant.components.light import ( ATTR_BRIGHTNESS, ENTITY_ID_FORMAT, - Light, SUPPORT_BRIGHTNESS, + Light, ) from homeassistant.const import ( - CONF_VALUE_TEMPLATE, - CONF_ICON_TEMPLATE, - CONF_ENTITY_PICTURE_TEMPLATE, CONF_ENTITY_ID, + CONF_ENTITY_PICTURE_TEMPLATE, CONF_FRIENDLY_NAME, - STATE_ON, - STATE_OFF, - EVENT_HOMEASSISTANT_START, + CONF_ICON_TEMPLATE, CONF_LIGHTS, + CONF_VALUE_TEMPLATE, + EVENT_HOMEASSISTANT_START, + STATE_OFF, + STATE_ON, ) -from homeassistant.helpers.config_validation import PLATFORM_SCHEMA +from homeassistant.core import callback 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 async_generate_entity_id from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers.script import Script + from . import extract_entities, initialise_templates from .const import CONF_AVAILABILITY_TEMPLATE diff --git a/homeassistant/components/template/lock.py b/homeassistant/components/template/lock.py index 71e9cc6642d..f4a6b55dd18 100644 --- a/homeassistant/components/template/lock.py +++ b/homeassistant/components/template/lock.py @@ -3,22 +3,22 @@ import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv - -from homeassistant.core import callback -from homeassistant.components.lock import LockDevice, PLATFORM_SCHEMA +from homeassistant.components.lock import PLATFORM_SCHEMA, LockDevice from homeassistant.const import ( CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE, EVENT_HOMEASSISTANT_START, - STATE_ON, - STATE_LOCKED, MATCH_ALL, + STATE_LOCKED, + STATE_ON, ) +from homeassistant.core import callback from homeassistant.exceptions import TemplateError +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers.script import Script + from . import extract_entities, initialise_templates from .const import CONF_AVAILABILITY_TEMPLATE diff --git a/homeassistant/components/template/sensor.py b/homeassistant/components/template/sensor.py index 4ea7daa54f6..a4e28265e1d 100644 --- a/homeassistant/components/template/sensor.py +++ b/homeassistant/components/template/sensor.py @@ -4,30 +4,30 @@ from typing import Optional import voluptuous as vol -from homeassistant.core import callback from homeassistant.components.sensor import ( + DEVICE_CLASSES_SCHEMA, ENTITY_ID_FORMAT, PLATFORM_SCHEMA, - DEVICE_CLASSES_SCHEMA, ) from homeassistant.const import ( + ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, - CONF_VALUE_TEMPLATE, - CONF_ICON_TEMPLATE, - CONF_ENTITY_PICTURE_TEMPLATE, - ATTR_ENTITY_ID, - CONF_SENSORS, - EVENT_HOMEASSISTANT_START, - CONF_FRIENDLY_NAME_TEMPLATE, - MATCH_ALL, CONF_DEVICE_CLASS, + CONF_ENTITY_PICTURE_TEMPLATE, + CONF_FRIENDLY_NAME_TEMPLATE, + CONF_ICON_TEMPLATE, + CONF_SENSORS, + CONF_VALUE_TEMPLATE, + EVENT_HOMEASSISTANT_START, + MATCH_ALL, ) - +from homeassistant.core import callback from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity, async_generate_entity_id from homeassistant.helpers.event import async_track_state_change + from . import extract_entities, initialise_templates from .const import CONF_AVAILABILITY_TEMPLATE diff --git a/homeassistant/components/template/switch.py b/homeassistant/components/template/switch.py index e06ca0c8d54..f44f7256e19 100644 --- a/homeassistant/components/template/switch.py +++ b/homeassistant/components/template/switch.py @@ -3,28 +3,29 @@ import logging import voluptuous as vol -from homeassistant.core import callback from homeassistant.components.switch import ( ENTITY_ID_FORMAT, - SwitchDevice, PLATFORM_SCHEMA, + SwitchDevice, ) from homeassistant.const import ( + ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, - CONF_VALUE_TEMPLATE, - CONF_ICON_TEMPLATE, CONF_ENTITY_PICTURE_TEMPLATE, + CONF_ICON_TEMPLATE, + CONF_SWITCHES, + CONF_VALUE_TEMPLATE, + EVENT_HOMEASSISTANT_START, STATE_OFF, STATE_ON, - ATTR_ENTITY_ID, - CONF_SWITCHES, - EVENT_HOMEASSISTANT_START, ) +from homeassistant.core import callback from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers.script import Script + from . import extract_entities, initialise_templates from .const import CONF_AVAILABILITY_TEMPLATE diff --git a/homeassistant/components/template/vacuum.py b/homeassistant/components/template/vacuum.py index 8201842e131..4946d54edc3 100644 --- a/homeassistant/components/template/vacuum.py +++ b/homeassistant/components/template/vacuum.py @@ -3,7 +3,6 @@ import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.vacuum import ( ATTR_FAN_SPEED, DOMAIN, @@ -14,35 +13,37 @@ from homeassistant.components.vacuum import ( SERVICE_SET_FAN_SPEED, SERVICE_START, SERVICE_STOP, + STATE_CLEANING, + STATE_DOCKED, + STATE_ERROR, + STATE_IDLE, + STATE_PAUSED, + STATE_RETURNING, SUPPORT_BATTERY, SUPPORT_CLEAN_SPOT, SUPPORT_FAN_SPEED, SUPPORT_LOCATE, SUPPORT_PAUSE, SUPPORT_RETURN_HOME, - SUPPORT_STOP, - SUPPORT_STATE, SUPPORT_START, + SUPPORT_STATE, + SUPPORT_STOP, StateVacuumDevice, - STATE_CLEANING, - STATE_DOCKED, - STATE_PAUSED, - STATE_IDLE, - STATE_RETURNING, - STATE_ERROR, ) from homeassistant.const import ( + CONF_ENTITY_ID, CONF_FRIENDLY_NAME, CONF_VALUE_TEMPLATE, - CONF_ENTITY_ID, - MATCH_ALL, EVENT_HOMEASSISTANT_START, + MATCH_ALL, STATE_UNKNOWN, ) from homeassistant.core import callback from homeassistant.exceptions import TemplateError +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.script import Script + from . import extract_entities, initialise_templates from .const import CONF_AVAILABILITY_TEMPLATE diff --git a/tests/components/template/test_binary_sensor.py b/tests/components/template/test_binary_sensor.py index b17b98f2f10..b024bbc311f 100644 --- a/tests/components/template/test_binary_sensor.py +++ b/tests/components/template/test_binary_sensor.py @@ -3,24 +3,24 @@ from datetime import timedelta import unittest from unittest import mock -from homeassistant.const import ( - MATCH_ALL, - EVENT_HOMEASSISTANT_START, - STATE_UNAVAILABLE, - STATE_ON, - STATE_OFF, -) from homeassistant import setup from homeassistant.components.template import binary_sensor as template +from homeassistant.const import ( + EVENT_HOMEASSISTANT_START, + MATCH_ALL, + STATE_OFF, + STATE_ON, + STATE_UNAVAILABLE, +) from homeassistant.exceptions import TemplateError from homeassistant.helpers import template as template_hlpr from homeassistant.util.async_ import run_callback_threadsafe import homeassistant.util.dt as dt_util from tests.common import ( - get_test_home_assistant, assert_setup_component, async_fire_time_changed, + get_test_home_assistant, ) diff --git a/tests/components/template/test_cover.py b/tests/components/template/test_cover.py index d3be01cbdc3..c3e1f2843fd 100644 --- a/tests/components/template/test_cover.py +++ b/tests/components/template/test_cover.py @@ -1,5 +1,6 @@ """The tests the cover command line platform.""" import logging + import pytest from homeassistant import setup @@ -8,18 +9,18 @@ from homeassistant.const import ( ATTR_ENTITY_ID, SERVICE_CLOSE_COVER, SERVICE_CLOSE_COVER_TILT, - SERVICE_TOGGLE, - SERVICE_TOGGLE_COVER_TILT, SERVICE_OPEN_COVER, SERVICE_OPEN_COVER_TILT, SERVICE_SET_COVER_POSITION, SERVICE_SET_COVER_TILT_POSITION, SERVICE_STOP_COVER, + SERVICE_TOGGLE, + SERVICE_TOGGLE_COVER_TILT, STATE_CLOSED, - STATE_UNAVAILABLE, - STATE_OPEN, - STATE_ON, STATE_OFF, + STATE_ON, + STATE_OPEN, + STATE_UNAVAILABLE, ) from tests.common import assert_setup_component, async_mock_service diff --git a/tests/components/template/test_fan.py b/tests/components/template/test_fan.py index 5753684795b..981b87ff43e 100644 --- a/tests/components/template/test_fan.py +++ b/tests/components/template/test_fan.py @@ -1,23 +1,23 @@ """The tests for the Template fan platform.""" import logging -import pytest +import pytest import voluptuous as vol from homeassistant import setup -from homeassistant.const import STATE_ON, STATE_OFF, STATE_UNAVAILABLE from homeassistant.components.fan import ( - ATTR_SPEED, - ATTR_OSCILLATING, - SPEED_LOW, - SPEED_MEDIUM, - SPEED_HIGH, ATTR_DIRECTION, + ATTR_OSCILLATING, + ATTR_SPEED, DIRECTION_FORWARD, DIRECTION_REVERSE, + SPEED_HIGH, + SPEED_LOW, + SPEED_MEDIUM, ) +from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE -from tests.common import async_mock_service, assert_setup_component +from tests.common import assert_setup_component, async_mock_service from tests.components.fan import common _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/template/test_light.py b/tests/components/template/test_light.py index c2dd49a76fb..62d377a9337 100644 --- a/tests/components/template/test_light.py +++ b/tests/components/template/test_light.py @@ -1,12 +1,12 @@ """The tests for the Template light platform.""" import logging -from homeassistant.core import callback from homeassistant import setup from homeassistant.components.light import ATTR_BRIGHTNESS -from homeassistant.const import STATE_ON, STATE_OFF, STATE_UNAVAILABLE +from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE +from homeassistant.core import callback -from tests.common import get_test_home_assistant, assert_setup_component +from tests.common import assert_setup_component, get_test_home_assistant from tests.components.light import common _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/template/test_lock.py b/tests/components/template/test_lock.py index 32a34411b33..10f959efed8 100644 --- a/tests/components/template/test_lock.py +++ b/tests/components/template/test_lock.py @@ -1,13 +1,12 @@ """The tests for the Template lock platform.""" import logging -from homeassistant.core import callback from homeassistant import setup from homeassistant.components import lock -from homeassistant.const import ATTR_ENTITY_ID -from homeassistant.const import STATE_ON, STATE_OFF, STATE_UNAVAILABLE +from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON, STATE_UNAVAILABLE +from homeassistant.core import callback -from tests.common import get_test_home_assistant, assert_setup_component +from tests.common import assert_setup_component, get_test_home_assistant _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/template/test_sensor.py b/tests/components/template/test_sensor.py index 7ae34b04c00..e9033b3dfe3 100644 --- a/tests/components/template/test_sensor.py +++ b/tests/components/template/test_sensor.py @@ -1,9 +1,13 @@ """The test for the Template sensor platform.""" -from homeassistant.const import EVENT_HOMEASSISTANT_START -from homeassistant.setup import setup_component, async_setup_component +from homeassistant.const import ( + EVENT_HOMEASSISTANT_START, + STATE_OFF, + STATE_ON, + STATE_UNAVAILABLE, +) +from homeassistant.setup import async_setup_component, setup_component -from tests.common import get_test_home_assistant, assert_setup_component -from homeassistant.const import STATE_UNAVAILABLE, STATE_ON, STATE_OFF +from tests.common import assert_setup_component, get_test_home_assistant class TestTemplateSensor: diff --git a/tests/components/template/test_switch.py b/tests/components/template/test_switch.py index 3adc5dcad46..a66028a318f 100644 --- a/tests/components/template/test_switch.py +++ b/tests/components/template/test_switch.py @@ -1,9 +1,9 @@ """The tests for the Template switch platform.""" -from homeassistant.core import callback from homeassistant import setup -from homeassistant.const import STATE_ON, STATE_OFF, STATE_UNAVAILABLE +from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE +from homeassistant.core import callback -from tests.common import get_test_home_assistant, assert_setup_component +from tests.common import assert_setup_component, get_test_home_assistant from tests.components.switch import common diff --git a/tests/components/template/test_vacuum.py b/tests/components/template/test_vacuum.py index da0e8e59ede..4080b75f46a 100644 --- a/tests/components/template/test_vacuum.py +++ b/tests/components/template/test_vacuum.py @@ -1,9 +1,9 @@ """The tests for the Template vacuum platform.""" import logging + import pytest from homeassistant import setup -from homeassistant.const import STATE_ON, STATE_OFF, STATE_UNKNOWN, STATE_UNAVAILABLE from homeassistant.components.vacuum import ( ATTR_BATTERY_LEVEL, STATE_CLEANING, @@ -12,8 +12,9 @@ from homeassistant.components.vacuum import ( STATE_PAUSED, STATE_RETURNING, ) +from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE, STATE_UNKNOWN -from tests.common import async_mock_service, assert_setup_component +from tests.common import assert_setup_component, async_mock_service from tests.components.vacuum import common _LOGGER = logging.getLogger(__name__) From 51ece97e0d2c60c94ba63b25e9207e3f2c2daca2 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 21:06:05 +0100 Subject: [PATCH 114/677] Sort imports according to PEP8 for hive (#29669) --- homeassistant/components/hive/binary_sensor.py | 2 +- homeassistant/components/hive/climate.py | 9 ++++----- homeassistant/components/hive/light.py | 2 +- homeassistant/components/hive/sensor.py | 2 +- homeassistant/components/hive/switch.py | 2 +- homeassistant/components/hive/water_heater.py | 3 ++- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/hive/binary_sensor.py b/homeassistant/components/hive/binary_sensor.py index ce7e53b77a5..fa91d6862a2 100644 --- a/homeassistant/components/hive/binary_sensor.py +++ b/homeassistant/components/hive/binary_sensor.py @@ -1,7 +1,7 @@ """Support for the Hive binary sensors.""" from homeassistant.components.binary_sensor import BinarySensorDevice -from . import DOMAIN, DATA_HIVE, HiveEntity +from . import DATA_HIVE, DOMAIN, HiveEntity DEVICETYPE_DEVICE_CLASS = {"motionsensor": "motion", "contactsensor": "opening"} diff --git a/homeassistant/components/hive/climate.py b/homeassistant/components/hive/climate.py index ed13e3019ce..202cea7bf8e 100644 --- a/homeassistant/components/hive/climate.py +++ b/homeassistant/components/hive/climate.py @@ -1,6 +1,9 @@ """Support for the Hive climate devices.""" from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, + CURRENT_HVAC_OFF, HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF, @@ -8,14 +11,10 @@ from homeassistant.components.climate.const import ( PRESET_NONE, SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE, - CURRENT_HVAC_IDLE, - CURRENT_HVAC_OFF, - CURRENT_HVAC_HEAT, ) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS - -from . import DOMAIN, DATA_HIVE, HiveEntity, refresh_system +from . import DATA_HIVE, DOMAIN, HiveEntity, refresh_system HIVE_TO_HASS_STATE = { "SCHEDULE": HVAC_MODE_AUTO, diff --git a/homeassistant/components/hive/light.py b/homeassistant/components/hive/light.py index 41fc286d13b..33175de543d 100644 --- a/homeassistant/components/hive/light.py +++ b/homeassistant/components/hive/light.py @@ -10,7 +10,7 @@ from homeassistant.components.light import ( ) import homeassistant.util.color as color_util -from . import DOMAIN, DATA_HIVE, HiveEntity, refresh_system +from . import DATA_HIVE, DOMAIN, HiveEntity, refresh_system def setup_platform(hass, config, add_entities, discovery_info=None): diff --git a/homeassistant/components/hive/sensor.py b/homeassistant/components/hive/sensor.py index ccd635015de..360fb61bfbe 100644 --- a/homeassistant/components/hive/sensor.py +++ b/homeassistant/components/hive/sensor.py @@ -2,7 +2,7 @@ from homeassistant.const import TEMP_CELSIUS from homeassistant.helpers.entity import Entity -from . import DOMAIN, DATA_HIVE, HiveEntity +from . import DATA_HIVE, DOMAIN, HiveEntity FRIENDLY_NAMES = { "Hub_OnlineStatus": "Hive Hub Status", diff --git a/homeassistant/components/hive/switch.py b/homeassistant/components/hive/switch.py index 1447f5483a4..53e1ec6a069 100644 --- a/homeassistant/components/hive/switch.py +++ b/homeassistant/components/hive/switch.py @@ -1,7 +1,7 @@ """Support for the Hive switches.""" from homeassistant.components.switch import SwitchDevice -from . import DOMAIN, DATA_HIVE, HiveEntity, refresh_system +from . import DATA_HIVE, DOMAIN, HiveEntity, refresh_system def setup_platform(hass, config, add_entities, discovery_info=None): diff --git a/homeassistant/components/hive/water_heater.py b/homeassistant/components/hive/water_heater.py index c60a9ec01d1..d7d98426df5 100644 --- a/homeassistant/components/hive/water_heater.py +++ b/homeassistant/components/hive/water_heater.py @@ -7,7 +7,8 @@ from homeassistant.components.water_heater import ( WaterHeaterDevice, ) from homeassistant.const import TEMP_CELSIUS -from . import DOMAIN, DATA_HIVE, HiveEntity, refresh_system + +from . import DATA_HIVE, DOMAIN, HiveEntity, refresh_system SUPPORT_FLAGS_HEATER = SUPPORT_OPERATION_MODE From 55559f3e306d9b50e5a02b9cbbd7f61ab07b4e58 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 21:09:48 +0100 Subject: [PATCH 115/677] Sort imports according to PEP8 for starline (#29653) --- homeassistant/components/starline/__init__.py | 9 ++++---- homeassistant/components/starline/account.py | 15 ++++++------- .../components/starline/binary_sensor.py | 5 +++-- .../components/starline/config_flow.py | 21 ++++++++++--------- .../components/starline/device_tracker.py | 1 + homeassistant/components/starline/entity.py | 2 ++ homeassistant/components/starline/lock.py | 1 + homeassistant/components/starline/sensor.py | 1 + homeassistant/components/starline/switch.py | 1 + tests/components/starline/test_config_flow.py | 1 + 10 files changed, 34 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/starline/__init__.py b/homeassistant/components/starline/__init__.py index 22772282a7c..303507b1491 100644 --- a/homeassistant/components/starline/__init__.py +++ b/homeassistant/components/starline/__init__.py @@ -1,17 +1,18 @@ """The StarLine component.""" import voluptuous as vol + from homeassistant.config_entries import ConfigEntry from homeassistant.core import Config, HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from .account import StarlineAccount from .const import ( - DOMAIN, - PLATFORMS, - SERVICE_UPDATE_STATE, - SERVICE_SET_SCAN_INTERVAL, CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL, + DOMAIN, + PLATFORMS, + SERVICE_SET_SCAN_INTERVAL, + SERVICE_UPDATE_STATE, ) diff --git a/homeassistant/components/starline/account.py b/homeassistant/components/starline/account.py index 2e7653eb380..aee88c0bd3f 100644 --- a/homeassistant/components/starline/account.py +++ b/homeassistant/components/starline/account.py @@ -1,6 +1,7 @@ """StarLine Account.""" -from datetime import timedelta, datetime -from typing import Callable, Optional, Dict, Any +from datetime import datetime, timedelta +from typing import Any, Callable, Dict, Optional + from starline import StarlineApi, StarlineDevice from homeassistant.config_entries import ConfigEntry @@ -8,13 +9,13 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.event import async_track_time_interval from .const import ( + DATA_EXPIRES, + DATA_SLID_TOKEN, + DATA_SLNET_TOKEN, + DATA_USER_ID, + DEFAULT_SCAN_INTERVAL, DOMAIN, LOGGER, - DEFAULT_SCAN_INTERVAL, - DATA_USER_ID, - DATA_SLNET_TOKEN, - DATA_SLID_TOKEN, - DATA_EXPIRES, ) diff --git a/homeassistant/components/starline/binary_sensor.py b/homeassistant/components/starline/binary_sensor.py index fd28ff74cf4..21074069135 100644 --- a/homeassistant/components/starline/binary_sensor.py +++ b/homeassistant/components/starline/binary_sensor.py @@ -1,11 +1,12 @@ """Reads vehicle status from StarLine API.""" from homeassistant.components.binary_sensor import ( - BinarySensorDevice, DEVICE_CLASS_DOOR, DEVICE_CLASS_LOCK, - DEVICE_CLASS_PROBLEM, DEVICE_CLASS_POWER, + DEVICE_CLASS_PROBLEM, + BinarySensorDevice, ) + from .account import StarlineAccount, StarlineDevice from .const import DOMAIN from .entity import StarlineEntity diff --git a/homeassistant/components/starline/config_flow.py b/homeassistant/components/starline/config_flow.py index 2253cc3cd22..fa559f62913 100644 --- a/homeassistant/components/starline/config_flow.py +++ b/homeassistant/components/starline/config_flow.py @@ -1,25 +1,26 @@ """Config flow to configure StarLine component.""" from typing import Optional + from starline import StarlineAuth import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import CONF_USERNAME, CONF_PASSWORD +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from .const import ( # pylint: disable=unused-import - DOMAIN, CONF_APP_ID, CONF_APP_SECRET, - CONF_MFA_CODE, CONF_CAPTCHA_CODE, - LOGGER, - ERROR_AUTH_APP, - ERROR_AUTH_USER, - ERROR_AUTH_MFA, - DATA_USER_ID, - DATA_SLNET_TOKEN, - DATA_SLID_TOKEN, + CONF_MFA_CODE, DATA_EXPIRES, + DATA_SLID_TOKEN, + DATA_SLNET_TOKEN, + DATA_USER_ID, + DOMAIN, + ERROR_AUTH_APP, + ERROR_AUTH_MFA, + ERROR_AUTH_USER, + LOGGER, ) diff --git a/homeassistant/components/starline/device_tracker.py b/homeassistant/components/starline/device_tracker.py index b5254c761d8..6f202bbae52 100644 --- a/homeassistant/components/starline/device_tracker.py +++ b/homeassistant/components/starline/device_tracker.py @@ -2,6 +2,7 @@ from homeassistant.components.device_tracker.config_entry import TrackerEntity from homeassistant.components.device_tracker.const import SOURCE_TYPE_GPS from homeassistant.helpers.restore_state import RestoreEntity + from .account import StarlineAccount, StarlineDevice from .const import DOMAIN from .entity import StarlineEntity diff --git a/homeassistant/components/starline/entity.py b/homeassistant/components/starline/entity.py index b0d948ae2c8..31d1c79b9f0 100644 --- a/homeassistant/components/starline/entity.py +++ b/homeassistant/components/starline/entity.py @@ -1,6 +1,8 @@ """StarLine base entity.""" from typing import Callable, Optional + from homeassistant.helpers.entity import Entity + from .account import StarlineAccount, StarlineDevice diff --git a/homeassistant/components/starline/lock.py b/homeassistant/components/starline/lock.py index 0a20a36ae8b..804e8c8df2d 100644 --- a/homeassistant/components/starline/lock.py +++ b/homeassistant/components/starline/lock.py @@ -1,5 +1,6 @@ """Support for StarLine lock.""" from homeassistant.components.lock import LockDevice + from .account import StarlineAccount, StarlineDevice from .const import DOMAIN from .entity import StarlineEntity diff --git a/homeassistant/components/starline/sensor.py b/homeassistant/components/starline/sensor.py index 2507aba4955..0629a03e148 100644 --- a/homeassistant/components/starline/sensor.py +++ b/homeassistant/components/starline/sensor.py @@ -2,6 +2,7 @@ from homeassistant.components.sensor import DEVICE_CLASS_TEMPERATURE from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level, icon_for_signal_level + from .account import StarlineAccount, StarlineDevice from .const import DOMAIN from .entity import StarlineEntity diff --git a/homeassistant/components/starline/switch.py b/homeassistant/components/starline/switch.py index 92dec10b9d3..920fe686d9a 100644 --- a/homeassistant/components/starline/switch.py +++ b/homeassistant/components/starline/switch.py @@ -1,5 +1,6 @@ """Support for StarLine switch.""" from homeassistant.components.switch import SwitchDevice + from .account import StarlineAccount, StarlineDevice from .const import DOMAIN from .entity import StarlineEntity diff --git a/tests/components/starline/test_config_flow.py b/tests/components/starline/test_config_flow.py index 31bdf98b404..3ca52f849bc 100644 --- a/tests/components/starline/test_config_flow.py +++ b/tests/components/starline/test_config_flow.py @@ -1,5 +1,6 @@ """Tests for StarLine config flow.""" import requests_mock + from homeassistant.components.starline import config_flow TEST_APP_ID = "666" From 3d25ed7994ec604e03acd21a6ac73ba39adfd864 Mon Sep 17 00:00:00 2001 From: Gerald Hansen Date: Sun, 8 Dec 2019 22:27:18 +0100 Subject: [PATCH 116/677] Change state values for Worx Landroid sensor (#27453) The obj["state"] contains already several named return states as following: "grass cutting", "trapped recovery", "searching wire", "following wire", "searching home", "home", "idle" And with the batteryChargerState also the "charging" Fixes #455 --- homeassistant/components/worxlandroid/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/worxlandroid/sensor.py b/homeassistant/components/worxlandroid/sensor.py index ad583d6d943..dd74c5b7d17 100644 --- a/homeassistant/components/worxlandroid/sensor.py +++ b/homeassistant/components/worxlandroid/sensor.py @@ -141,16 +141,9 @@ class WorxLandroidSensor(Entity): state = self.get_error(obj) if state is None: - state_obj = obj["settaggi"] + if obj["batteryChargerState"] == "charging": + return obj["batteryChargerState"] - if state_obj[14] == 1: - return "manual-stop" - if state_obj[5] == 1 and state_obj[13] == 0: - return "charging" - if state_obj[5] == 1 and state_obj[13] == 1: - return "charging-complete" - if state_obj[15] == 1: - return "going-home" - return "mowing" + return obj["state"] return state From e6eed4755f6851d7b44c6ef8989a9ab319cfc0a7 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 23:31:56 +0100 Subject: [PATCH 117/677] Sort imports according to PEP8 for plex (#29708) --- homeassistant/components/plex/__init__.py | 4 ++-- homeassistant/components/plex/config_flow.py | 12 ++++++------ tests/components/plex/mock_classes.py | 2 +- tests/components/plex/test_config_flow.py | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/plex/__init__.py b/homeassistant/components/plex/__init__.py index 97103230288..89659769192 100644 --- a/homeassistant/components/plex/__init__.py +++ b/homeassistant/components/plex/__init__.py @@ -27,10 +27,10 @@ from homeassistant.helpers.dispatcher import ( ) from .const import ( - CONF_USE_EPISODE_ART, - CONF_SHOW_ALL_CONTROLS, CONF_SERVER, CONF_SERVER_IDENTIFIER, + CONF_SHOW_ALL_CONTROLS, + CONF_USE_EPISODE_ART, DEFAULT_PORT, DEFAULT_SSL, DEFAULT_VERIFY_SSL, diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index 350f1b3d577..d38d13c847e 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -8,12 +8,12 @@ from plexauth import PlexAuth import requests.exceptions import voluptuous as vol -from homeassistant.components.http.view import HomeAssistantView -from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant import config_entries +from homeassistant.components.http.view import HomeAssistantView from homeassistant.components.media_player import DOMAIN as MP_DOMAIN -from homeassistant.const import CONF_URL, CONF_TOKEN, CONF_SSL, CONF_VERIFY_SSL +from homeassistant.const import CONF_SSL, CONF_TOKEN, CONF_URL, CONF_VERIFY_SSL from homeassistant.core import callback +from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.util.json import load_json from .const import ( # pylint: disable=unused-import @@ -22,16 +22,16 @@ from .const import ( # pylint: disable=unused-import CONF_CLIENT_IDENTIFIER, CONF_SERVER, CONF_SERVER_IDENTIFIER, - CONF_USE_EPISODE_ART, CONF_SHOW_ALL_CONTROLS, + CONF_USE_EPISODE_ART, DEFAULT_VERIFY_SSL, DOMAIN, PLEX_CONFIG_FILE, PLEX_SERVER_CONFIG, X_PLEX_DEVICE_NAME, - X_PLEX_VERSION, - X_PLEX_PRODUCT, X_PLEX_PLATFORM, + X_PLEX_PRODUCT, + X_PLEX_VERSION, ) from .errors import NoServersFound, ServerNotSpecified from .server import PlexServer diff --git a/tests/components/plex/mock_classes.py b/tests/components/plex/mock_classes.py index 1a680e6af0f..de6ffa51170 100644 --- a/tests/components/plex/mock_classes.py +++ b/tests/components/plex/mock_classes.py @@ -1,6 +1,6 @@ """Mock classes used in tests.""" -from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.components.plex.const import CONF_SERVER, CONF_SERVER_IDENTIFIER +from homeassistant.const import CONF_HOST, CONF_PORT MOCK_SERVERS = [ { diff --git a/tests/components/plex/test_config_flow.py b/tests/components/plex/test_config_flow.py index 668ac3b2a17..0fb1f850809 100644 --- a/tests/components/plex/test_config_flow.py +++ b/tests/components/plex/test_config_flow.py @@ -9,10 +9,10 @@ from homeassistant.components.plex import config_flow from homeassistant.const import CONF_HOST, CONF_PORT, CONF_SSL, CONF_TOKEN, CONF_URL from homeassistant.setup import async_setup_component -from tests.common import MockConfigEntry - from .mock_classes import MOCK_SERVERS, MockPlexAccount, MockPlexServer +from tests.common import MockConfigEntry + MOCK_TOKEN = "secret_token" MOCK_FILE_CONTENTS = { f"{MOCK_SERVERS[0][CONF_HOST]}:{MOCK_SERVERS[0][CONF_PORT]}": { From cce3077df369aa28ad41ec45e3980dca784157b2 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 23:32:50 +0100 Subject: [PATCH 118/677] Sort imports according to PEP8 for cert_expiry (#29705) --- homeassistant/components/cert_expiry/config_flow.py | 5 +++-- homeassistant/components/cert_expiry/sensor.py | 10 +++++----- tests/components/cert_expiry/test_config_flow.py | 7 ++++--- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/cert_expiry/config_flow.py b/homeassistant/components/cert_expiry/config_flow.py index 78450d247b9..14532aea65f 100644 --- a/homeassistant/components/cert_expiry/config_flow.py +++ b/homeassistant/components/cert_expiry/config_flow.py @@ -2,13 +2,14 @@ import logging import socket import ssl + import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import CONF_PORT, CONF_NAME, CONF_HOST +from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT from homeassistant.core import HomeAssistant, callback -from .const import DOMAIN, DEFAULT_PORT, DEFAULT_NAME +from .const import DEFAULT_NAME, DEFAULT_PORT, DOMAIN from .helper import get_cert _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/cert_expiry/sensor.py b/homeassistant/components/cert_expiry/sensor.py index 3022c7bd42b..3a76575dfdd 100644 --- a/homeassistant/components/cert_expiry/sensor.py +++ b/homeassistant/components/cert_expiry/sensor.py @@ -1,24 +1,24 @@ """Counter for the days until an HTTPS (TLS) certificate will expire.""" +from datetime import datetime, timedelta import logging import socket import ssl -from datetime import datetime, timedelta import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( - CONF_NAME, CONF_HOST, + CONF_NAME, CONF_PORT, EVENT_HOMEASSISTANT_START, ) from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -from .const import DOMAIN, DEFAULT_NAME, DEFAULT_PORT +from .const import DEFAULT_NAME, DEFAULT_PORT, DOMAIN from .helper import get_cert _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/cert_expiry/test_config_flow.py b/tests/components/cert_expiry/test_config_flow.py index 3754551c230..bcd1482195d 100644 --- a/tests/components/cert_expiry/test_config_flow.py +++ b/tests/components/cert_expiry/test_config_flow.py @@ -1,13 +1,14 @@ """Tests for the Cert Expiry config flow.""" -import pytest -import ssl import socket +import ssl from unittest.mock import patch +import pytest + from homeassistant import data_entry_flow from homeassistant.components.cert_expiry import config_flow from homeassistant.components.cert_expiry.const import DEFAULT_NAME, DEFAULT_PORT -from homeassistant.const import CONF_PORT, CONF_NAME, CONF_HOST +from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT from tests.common import MockConfigEntry, mock_coro From 6a1753d6db8dbef2c76c9a706ead70034ac10093 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 23:33:42 +0100 Subject: [PATCH 119/677] Sort imports according to PEP8 for geonetnz_volcano (#29716) --- .../components/geonetnz_volcano/__init__.py | 12 ++++++------ homeassistant/components/geonetnz_volcano/sensor.py | 6 +++--- tests/components/geonetnz_volcano/conftest.py | 3 ++- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/geonetnz_volcano/__init__.py b/homeassistant/components/geonetnz_volcano/__init__.py index f0887da9c06..e24de7fdc5d 100644 --- a/homeassistant/components/geonetnz_volcano/__init__.py +++ b/homeassistant/components/geonetnz_volcano/__init__.py @@ -1,27 +1,27 @@ """The GeoNet NZ Volcano integration.""" import asyncio +from datetime import datetime, timedelta import logging -from datetime import timedelta, datetime from typing import Optional -import voluptuous as vol from aio_geojson_geonetnz_volcano import GeonetnzVolcanoFeedManager +import voluptuous as vol -from homeassistant.core import callback -from homeassistant.util.unit_system import METRIC_SYSTEM from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( CONF_LATITUDE, CONF_LONGITUDE, CONF_RADIUS, CONF_SCAN_INTERVAL, - CONF_UNIT_SYSTEM_IMPERIAL, CONF_UNIT_SYSTEM, + CONF_UNIT_SYSTEM_IMPERIAL, LENGTH_MILES, ) -from homeassistant.helpers import config_validation as cv, aiohttp_client +from homeassistant.core import callback +from homeassistant.helpers import aiohttp_client, config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_track_time_interval +from homeassistant.util.unit_system import METRIC_SYSTEM from .config_flow import configured_instances from .const import ( diff --git a/homeassistant/components/geonetnz_volcano/sensor.py b/homeassistant/components/geonetnz_volcano/sensor.py index 364ee416be4..f87ea88fc1c 100644 --- a/homeassistant/components/geonetnz_volcano/sensor.py +++ b/homeassistant/components/geonetnz_volcano/sensor.py @@ -3,11 +3,11 @@ import logging from typing import Optional from homeassistant.const import ( + ATTR_ATTRIBUTION, + ATTR_LATITUDE, + ATTR_LONGITUDE, CONF_UNIT_SYSTEM_IMPERIAL, LENGTH_KILOMETERS, - ATTR_ATTRIBUTION, - ATTR_LONGITUDE, - ATTR_LATITUDE, ) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect diff --git a/tests/components/geonetnz_volcano/conftest.py b/tests/components/geonetnz_volcano/conftest.py index 55231cd3120..33a299eeb79 100644 --- a/tests/components/geonetnz_volcano/conftest.py +++ b/tests/components/geonetnz_volcano/conftest.py @@ -6,9 +6,10 @@ from homeassistant.const import ( CONF_LATITUDE, CONF_LONGITUDE, CONF_RADIUS, - CONF_UNIT_SYSTEM, CONF_SCAN_INTERVAL, + CONF_UNIT_SYSTEM, ) + from tests.common import MockConfigEntry From 81a482332d6bd90f0cef193ebb6b8a79a6d09ca3 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 23:34:18 +0100 Subject: [PATCH 120/677] Sort imports according to PEP8 for eufy (#29715) --- homeassistant/components/eufy/__init__.py | 2 +- homeassistant/components/eufy/light.py | 7 +++---- homeassistant/components/eufy/switch.py | 1 + 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/eufy/__init__.py b/homeassistant/components/eufy/__init__.py index 191d6ab5315..eca637ec371 100644 --- a/homeassistant/components/eufy/__init__.py +++ b/homeassistant/components/eufy/__init__.py @@ -1,7 +1,7 @@ """Support for Eufy devices.""" import logging -import lakeside +import lakeside import voluptuous as vol from homeassistant.const import ( diff --git a/homeassistant/components/eufy/light.py b/homeassistant/components/eufy/light.py index 21c26606bdd..570f690307f 100644 --- a/homeassistant/components/eufy/light.py +++ b/homeassistant/components/eufy/light.py @@ -1,5 +1,6 @@ """Support for Eufy lights.""" import logging + import lakeside from homeassistant.components.light import ( @@ -7,16 +8,14 @@ from homeassistant.components.light import ( ATTR_COLOR_TEMP, ATTR_HS_COLOR, SUPPORT_BRIGHTNESS, - SUPPORT_COLOR_TEMP, SUPPORT_COLOR, + SUPPORT_COLOR_TEMP, Light, ) - import homeassistant.util.color as color_util - from homeassistant.util.color import ( - color_temperature_mired_to_kelvin as mired_to_kelvin, color_temperature_kelvin_to_mired as kelvin_to_mired, + color_temperature_mired_to_kelvin as mired_to_kelvin, ) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/eufy/switch.py b/homeassistant/components/eufy/switch.py index 2e13886dd2a..cbc09f4101c 100644 --- a/homeassistant/components/eufy/switch.py +++ b/homeassistant/components/eufy/switch.py @@ -1,5 +1,6 @@ """Support for Eufy switches.""" import logging + import lakeside from homeassistant.components.switch import SwitchDevice From 39887c46c09c45f3db99944797e838a190dba8fe Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 23:40:06 +0100 Subject: [PATCH 121/677] Sort imports according to PEP8 for dialogflow (#29714) --- homeassistant/components/dialogflow/__init__.py | 5 ++--- homeassistant/components/dialogflow/config_flow.py | 2 +- tests/components/dialogflow/test_init.py | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/dialogflow/__init__.py b/homeassistant/components/dialogflow/__init__.py index 45fee0f867e..ae3c0288aed 100644 --- a/homeassistant/components/dialogflow/__init__.py +++ b/homeassistant/components/dialogflow/__init__.py @@ -1,16 +1,15 @@ """Support for Dialogflow webhook.""" import logging -import voluptuous as vol from aiohttp import web +import voluptuous as vol from homeassistant.const import CONF_WEBHOOK_ID from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import intent, template, config_entry_flow +from homeassistant.helpers import config_entry_flow, intent, template from .const import DOMAIN - _LOGGER = logging.getLogger(__name__) SOURCE = "Home Assistant Dialogflow" diff --git a/homeassistant/components/dialogflow/config_flow.py b/homeassistant/components/dialogflow/config_flow.py index 4f785392ffc..fee99898ccc 100644 --- a/homeassistant/components/dialogflow/config_flow.py +++ b/homeassistant/components/dialogflow/config_flow.py @@ -1,7 +1,7 @@ """Config flow for DialogFlow.""" from homeassistant.helpers import config_entry_flow -from .const import DOMAIN +from .const import DOMAIN config_entry_flow.register_webhook_flow( DOMAIN, diff --git a/tests/components/dialogflow/test_init.py b/tests/components/dialogflow/test_init.py index 18a03ff2603..aaec1ee67cf 100644 --- a/tests/components/dialogflow/test_init.py +++ b/tests/components/dialogflow/test_init.py @@ -1,6 +1,6 @@ """The tests for the Dialogflow component.""" -import json import copy +import json from unittest.mock import Mock import pytest From 076e0273a2cda5a9b46ceb7abbfa4f0e86c161e0 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 8 Dec 2019 23:42:04 +0100 Subject: [PATCH 122/677] Sort imports according to PEP8 for kodi (#29721) --- homeassistant/components/kodi/__init__.py | 6 +++--- homeassistant/components/kodi/media_player.py | 10 ++++------ homeassistant/components/kodi/notify.py | 16 +++++++--------- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/kodi/__init__.py b/homeassistant/components/kodi/__init__.py index 5bbffc5df1d..1f2d3cb5cd0 100644 --- a/homeassistant/components/kodi/__init__.py +++ b/homeassistant/components/kodi/__init__.py @@ -1,13 +1,13 @@ """The kodi component.""" import asyncio + import voluptuous as vol -from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM -from homeassistant.helpers import config_validation as cv from homeassistant.components.kodi.const import DOMAIN from homeassistant.components.media_player.const import DOMAIN as MP_DOMAIN - +from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM +from homeassistant.helpers import config_validation as cv SERVICE_ADD_MEDIA = "add_to_playlist" SERVICE_CALL_METHOD = "call_method" diff --git a/homeassistant/components/kodi/media_player.py b/homeassistant/components/kodi/media_player.py index 9b2ba01e90a..9721ea2d31f 100644 --- a/homeassistant/components/kodi/media_player.py +++ b/homeassistant/components/kodi/media_player.py @@ -7,15 +7,14 @@ import socket import urllib import aiohttp -import jsonrpc_base import jsonrpc_async +import jsonrpc_base import jsonrpc_websocket - import voluptuous as vol from homeassistant.components.kodi import SERVICE_CALL_METHOD from homeassistant.components.kodi.const import DOMAIN -from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice from homeassistant.components.media_player.const import ( MEDIA_TYPE_CHANNEL, MEDIA_TYPE_MOVIE, @@ -52,12 +51,11 @@ from homeassistant.const import ( STATE_PLAYING, ) from homeassistant.core import callback -from homeassistant.helpers import config_validation as cv -from homeassistant.helpers import script +from homeassistant.helpers import config_validation as cv, script from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.template import Template -from homeassistant.util.yaml import dump import homeassistant.util.dt as dt_util +from homeassistant.util.yaml import dump _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/kodi/notify.py b/homeassistant/components/kodi/notify.py index 1072cf1b732..6f370ffad98 100644 --- a/homeassistant/components/kodi/notify.py +++ b/homeassistant/components/kodi/notify.py @@ -3,9 +3,15 @@ import logging import aiohttp import jsonrpc_async - import voluptuous as vol +from homeassistant.components.notify import ( + ATTR_DATA, + ATTR_TITLE, + ATTR_TITLE_DEFAULT, + PLATFORM_SCHEMA, + BaseNotificationService, +) from homeassistant.const import ( ATTR_ICON, CONF_HOST, @@ -17,14 +23,6 @@ from homeassistant.const import ( from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.components.notify import ( - ATTR_DATA, - ATTR_TITLE, - ATTR_TITLE_DEFAULT, - PLATFORM_SCHEMA, - BaseNotificationService, -) - _LOGGER = logging.getLogger(__name__) DEFAULT_PORT = 8080 From e577f047f70c074149febc0bc36467348e5db4e8 Mon Sep 17 00:00:00 2001 From: Robert Van Gorkom Date: Sun, 8 Dec 2019 15:19:38 -0800 Subject: [PATCH 123/677] Add tests for vera component (#28340) * Adding tests for vera component. Fixing update bug in the vera climate platform. * Updating requrements file. * Moving vera stop to a job. Sorting imports. * Addressing simple PR feedback. * Splitting tests into platforms. * Mocking controller instead of using requests_mock. * Updating pyvera to use version that stops threads quickly. * Updating requirements files. * Mocking the pyvera module, not the API. * Addressing PR feedback. Handling start/stop of patch in fixture. Removing unecessary code. * Using generator --- .coveragerc | 1 - homeassistant/components/vera/climate.py | 6 + homeassistant/components/vera/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 3 + tests/components/vera/__init__.py | 1 + tests/components/vera/common.py | 61 ++++++ tests/components/vera/conftest.py | 13 ++ tests/components/vera/test_binary_sensor.py | 38 ++++ tests/components/vera/test_climate.py | 155 +++++++++++++++ tests/components/vera/test_cover.py | 76 ++++++++ tests/components/vera/test_init.py | 78 ++++++++ tests/components/vera/test_light.py | 76 ++++++++ tests/components/vera/test_lock.py | 49 +++++ tests/components/vera/test_scene.py | 27 +++ tests/components/vera/test_sensor.py | 198 ++++++++++++++++++++ tests/components/vera/test_switch.py | 48 +++++ 17 files changed, 831 insertions(+), 3 deletions(-) create mode 100644 tests/components/vera/__init__.py create mode 100644 tests/components/vera/common.py create mode 100644 tests/components/vera/conftest.py create mode 100644 tests/components/vera/test_binary_sensor.py create mode 100644 tests/components/vera/test_climate.py create mode 100644 tests/components/vera/test_cover.py create mode 100644 tests/components/vera/test_init.py create mode 100644 tests/components/vera/test_light.py create mode 100644 tests/components/vera/test_lock.py create mode 100644 tests/components/vera/test_scene.py create mode 100644 tests/components/vera/test_sensor.py create mode 100644 tests/components/vera/test_switch.py diff --git a/.coveragerc b/.coveragerc index 778af8db89a..e85c97ef80c 100644 --- a/.coveragerc +++ b/.coveragerc @@ -750,7 +750,6 @@ omit = homeassistant/components/velbus/switch.py homeassistant/components/velux/* homeassistant/components/venstar/climate.py - homeassistant/components/vera/* homeassistant/components/verisure/* homeassistant/components/versasense/* homeassistant/components/vesync/__init__.py diff --git a/homeassistant/components/vera/climate.py b/homeassistant/components/vera/climate.py index 50c897d0fc1..60e73d48978 100644 --- a/homeassistant/components/vera/climate.py +++ b/homeassistant/components/vera/climate.py @@ -92,6 +92,8 @@ class VeraThermostat(VeraDevice, ClimateDevice): else: self.vera_device.fan_auto() + self.schedule_update_ha_state() + @property def current_power_w(self): """Return the current power usage in W.""" @@ -129,6 +131,8 @@ class VeraThermostat(VeraDevice, ClimateDevice): if kwargs.get(ATTR_TEMPERATURE) is not None: self.vera_device.set_temperature(kwargs.get(ATTR_TEMPERATURE)) + self.schedule_update_ha_state() + def set_hvac_mode(self, hvac_mode): """Set new target hvac mode.""" if hvac_mode == HVAC_MODE_OFF: @@ -139,3 +143,5 @@ class VeraThermostat(VeraDevice, ClimateDevice): self.vera_device.turn_cool_on() elif hvac_mode == HVAC_MODE_HEAT: self.vera_device.turn_heat_on() + + self.schedule_update_ha_state() diff --git a/homeassistant/components/vera/manifest.json b/homeassistant/components/vera/manifest.json index 120ec241d60..70abc098431 100644 --- a/homeassistant/components/vera/manifest.json +++ b/homeassistant/components/vera/manifest.json @@ -3,7 +3,7 @@ "name": "Vera", "documentation": "https://www.home-assistant.io/integrations/vera", "requirements": [ - "pyvera==0.3.6" + "pyvera==0.3.7" ], "dependencies": [], "codeowners": [] diff --git a/requirements_all.txt b/requirements_all.txt index 360898df055..87ca2a46177 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1670,7 +1670,7 @@ pyuptimerobot==0.0.5 # pyuserinput==0.1.11 # homeassistant.components.vera -pyvera==0.3.6 +pyvera==0.3.7 # homeassistant.components.versasense pyversasense==0.0.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fff9af5b32d..405e632c0cc 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -536,6 +536,9 @@ pytraccar==0.9.0 # homeassistant.components.tradfri pytradfri[async]==6.4.0 +# homeassistant.components.vera +pyvera==0.3.7 + # homeassistant.components.vesync pyvesync==1.1.0 diff --git a/tests/components/vera/__init__.py b/tests/components/vera/__init__.py new file mode 100644 index 00000000000..4e0919be042 --- /dev/null +++ b/tests/components/vera/__init__.py @@ -0,0 +1 @@ +"""Tests for the Vera component.""" diff --git a/tests/components/vera/common.py b/tests/components/vera/common.py new file mode 100644 index 00000000000..d78a536e95c --- /dev/null +++ b/tests/components/vera/common.py @@ -0,0 +1,61 @@ +"""Common code for tests.""" + +from typing import Callable, NamedTuple, Tuple + +from mock import MagicMock +from pyvera import VeraController, VeraDevice, VeraScene + +from homeassistant.components.vera import CONF_CONTROLLER, DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + +ComponentData = NamedTuple("ComponentData", (("controller", VeraController),)) + + +class ComponentFactory: + """Factory class.""" + + def __init__(self, init_controller_mock): + """Constructor.""" + self.init_controller_mock = init_controller_mock + + async def configure_component( + self, + hass: HomeAssistant, + devices: Tuple[VeraDevice] = (), + scenes: Tuple[VeraScene] = (), + setup_callback: Callable[[VeraController], None] = None, + ) -> ComponentData: + """Configure the component with specific mock data.""" + controller_url = "http://127.0.0.1:123" + + hass_config = { + DOMAIN: {CONF_CONTROLLER: controller_url}, + } + + controller = MagicMock(spec=VeraController) # type: VeraController + controller.base_url = controller_url + controller.register = MagicMock() + controller.get_devices = MagicMock(return_value=devices or ()) + controller.get_scenes = MagicMock(return_value=scenes or ()) + + for vera_obj in controller.get_devices() + controller.get_scenes(): + vera_obj.vera_controller = controller + + controller.get_devices.reset_mock() + controller.get_scenes.reset_mock() + + if setup_callback: + setup_callback(controller, hass_config) + + def init_controller(base_url: str) -> list: + nonlocal controller + return [controller, True] + + self.init_controller_mock.side_effect = init_controller + + # Setup home assistant. + assert await async_setup_component(hass, DOMAIN, hass_config) + await hass.async_block_till_done() + + return ComponentData(controller=controller) diff --git a/tests/components/vera/conftest.py b/tests/components/vera/conftest.py new file mode 100644 index 00000000000..b94a40135d8 --- /dev/null +++ b/tests/components/vera/conftest.py @@ -0,0 +1,13 @@ +"""Fixtures for tests.""" + +from mock import patch +import pytest + +from .common import ComponentFactory + + +@pytest.fixture() +def vera_component_factory(): + """Return a factory for initializing the vera component.""" + with patch("pyvera.init_controller") as init_controller_mock: + yield ComponentFactory(init_controller_mock) diff --git a/tests/components/vera/test_binary_sensor.py b/tests/components/vera/test_binary_sensor.py new file mode 100644 index 00000000000..2c2e2b86388 --- /dev/null +++ b/tests/components/vera/test_binary_sensor.py @@ -0,0 +1,38 @@ +"""Vera tests.""" +from unittest.mock import MagicMock + +from pyvera import VeraBinarySensor + +from homeassistant.core import HomeAssistant + +from .common import ComponentFactory + + +async def test_binary_sensor( + hass: HomeAssistant, vera_component_factory: ComponentFactory +) -> None: + """Test function.""" + vera_device = MagicMock(spec=VeraBinarySensor) # type: VeraBinarySensor + vera_device.device_id = 1 + vera_device.name = "dev1" + vera_device.is_tripped = False + entity_id = "binary_sensor.dev1_1" + + component_data = await vera_component_factory.configure_component( + hass=hass, devices=(vera_device,) + ) + controller = component_data.controller + + update_callback = controller.register.call_args_list[0][0][1] + + vera_device.is_tripped = False + update_callback(vera_device) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == "off" + controller.register.reset_mock() + + vera_device.is_tripped = True + update_callback(vera_device) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == "on" + controller.register.reset_mock() diff --git a/tests/components/vera/test_climate.py b/tests/components/vera/test_climate.py new file mode 100644 index 00000000000..c27a72865fd --- /dev/null +++ b/tests/components/vera/test_climate.py @@ -0,0 +1,155 @@ +"""Vera tests.""" +from unittest.mock import MagicMock + +from pyvera import CATEGORY_THERMOSTAT, VeraController, VeraThermostat + +from homeassistant.components.climate.const import ( + FAN_AUTO, + FAN_ON, + HVAC_MODE_COOL, + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_OFF, +) +from homeassistant.core import HomeAssistant + +from .common import ComponentFactory + + +async def test_climate( + hass: HomeAssistant, vera_component_factory: ComponentFactory +) -> None: + """Test function.""" + vera_device = MagicMock(spec=VeraThermostat) # type: VeraThermostat + vera_device.device_id = 1 + vera_device.name = "dev1" + vera_device.category = CATEGORY_THERMOSTAT + vera_device.power = 10 + vera_device.get_current_temperature.return_value = 71 + vera_device.get_hvac_mode.return_value = "Off" + vera_device.get_current_goal_temperature.return_value = 72 + entity_id = "climate.dev1_1" + + component_data = await vera_component_factory.configure_component( + hass=hass, devices=(vera_device,), + ) + controller = component_data.controller + update_callback = controller.register.call_args_list[0][0][1] + + assert hass.states.get(entity_id).state == HVAC_MODE_OFF + + await hass.services.async_call( + "climate", + "set_hvac_mode", + {"entity_id": entity_id, "hvac_mode": HVAC_MODE_COOL}, + ) + await hass.async_block_till_done() + vera_device.turn_cool_on.assert_called() + vera_device.get_hvac_mode.return_value = "CoolOn" + update_callback(vera_device) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == HVAC_MODE_COOL + + await hass.services.async_call( + "climate", + "set_hvac_mode", + {"entity_id": entity_id, "hvac_mode": HVAC_MODE_HEAT}, + ) + await hass.async_block_till_done() + vera_device.turn_heat_on.assert_called() + vera_device.get_hvac_mode.return_value = "HeatOn" + update_callback(vera_device) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == HVAC_MODE_HEAT + + await hass.services.async_call( + "climate", + "set_hvac_mode", + {"entity_id": entity_id, "hvac_mode": HVAC_MODE_HEAT_COOL}, + ) + await hass.async_block_till_done() + vera_device.turn_auto_on.assert_called() + vera_device.get_hvac_mode.return_value = "AutoChangeOver" + update_callback(vera_device) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == HVAC_MODE_HEAT_COOL + + await hass.services.async_call( + "climate", + "set_hvac_mode", + {"entity_id": entity_id, "hvac_mode": HVAC_MODE_OFF}, + ) + await hass.async_block_till_done() + vera_device.turn_auto_on.assert_called() + vera_device.get_hvac_mode.return_value = "Off" + update_callback(vera_device) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == HVAC_MODE_OFF + + await hass.services.async_call( + "climate", "set_fan_mode", {"entity_id": entity_id, "fan_mode": "on"}, + ) + await hass.async_block_till_done() + vera_device.turn_auto_on.assert_called() + vera_device.get_fan_mode.return_value = "ContinuousOn" + update_callback(vera_device) + await hass.async_block_till_done() + assert hass.states.get(entity_id).attributes["fan_mode"] == FAN_ON + + await hass.services.async_call( + "climate", "set_fan_mode", {"entity_id": entity_id, "fan_mode": "off"}, + ) + await hass.async_block_till_done() + vera_device.turn_auto_on.assert_called() + vera_device.get_fan_mode.return_value = "Auto" + update_callback(vera_device) + await hass.async_block_till_done() + assert hass.states.get(entity_id).attributes["fan_mode"] == FAN_AUTO + + await hass.services.async_call( + "climate", "set_temperature", {"entity_id": entity_id, "temperature": 30}, + ) + await hass.async_block_till_done() + vera_device.set_temperature.assert_called_with(30) + vera_device.get_current_goal_temperature.return_value = 30 + vera_device.get_current_temperature.return_value = 25 + update_callback(vera_device) + await hass.async_block_till_done() + assert hass.states.get(entity_id).attributes["current_temperature"] == 25 + assert hass.states.get(entity_id).attributes["temperature"] == 30 + + +async def test_climate_f( + hass: HomeAssistant, vera_component_factory: ComponentFactory +) -> None: + """Test function.""" + vera_device = MagicMock(spec=VeraThermostat) # type: VeraThermostat + vera_device.device_id = 1 + vera_device.name = "dev1" + vera_device.category = CATEGORY_THERMOSTAT + vera_device.power = 10 + vera_device.get_current_temperature.return_value = 71 + vera_device.get_hvac_mode.return_value = "Off" + vera_device.get_current_goal_temperature.return_value = 72 + entity_id = "climate.dev1_1" + + def setup_callback(controller: VeraController, hass_config: dict) -> None: + controller.temperature_units = "F" + + component_data = await vera_component_factory.configure_component( + hass=hass, devices=(vera_device,), setup_callback=setup_callback + ) + controller = component_data.controller + update_callback = controller.register.call_args_list[0][0][1] + + await hass.services.async_call( + "climate", "set_temperature", {"entity_id": entity_id, "temperature": 30}, + ) + await hass.async_block_till_done() + vera_device.set_temperature.assert_called_with(86) + vera_device.get_current_goal_temperature.return_value = 30 + vera_device.get_current_temperature.return_value = 25 + update_callback(vera_device) + await hass.async_block_till_done() + assert hass.states.get(entity_id).attributes["current_temperature"] == -3.9 + assert hass.states.get(entity_id).attributes["temperature"] == -1.1 diff --git a/tests/components/vera/test_cover.py b/tests/components/vera/test_cover.py new file mode 100644 index 00000000000..79cb4adedfb --- /dev/null +++ b/tests/components/vera/test_cover.py @@ -0,0 +1,76 @@ +"""Vera tests.""" +from unittest.mock import MagicMock + +from pyvera import CATEGORY_CURTAIN, VeraCurtain + +from homeassistant.core import HomeAssistant + +from .common import ComponentFactory + + +async def test_cover( + hass: HomeAssistant, vera_component_factory: ComponentFactory +) -> None: + """Test function.""" + vera_device = MagicMock(spec=VeraCurtain) # type: VeraCurtain + vera_device.device_id = 1 + vera_device.name = "dev1" + vera_device.category = CATEGORY_CURTAIN + vera_device.is_closed = False + vera_device.get_level.return_value = 0 + entity_id = "cover.dev1_1" + + component_data = await vera_component_factory.configure_component( + hass=hass, devices=(vera_device,), + ) + controller = component_data.controller + update_callback = controller.register.call_args_list[0][0][1] + + assert hass.states.get(entity_id).state == "closed" + assert hass.states.get(entity_id).attributes["current_position"] == 0 + + await hass.services.async_call( + "cover", "open_cover", {"entity_id": entity_id}, + ) + await hass.async_block_till_done() + vera_device.open.assert_called() + vera_device.is_open.return_value = True + vera_device.get_level.return_value = 100 + update_callback(vera_device) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == "open" + assert hass.states.get(entity_id).attributes["current_position"] == 100 + + await hass.services.async_call( + "cover", "set_cover_position", {"entity_id": entity_id, "position": 50}, + ) + await hass.async_block_till_done() + vera_device.set_level.assert_called_with(50) + vera_device.is_open.return_value = True + vera_device.get_level.return_value = 50 + update_callback(vera_device) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == "open" + assert hass.states.get(entity_id).attributes["current_position"] == 50 + + await hass.services.async_call( + "cover", "stop_cover", {"entity_id": entity_id}, + ) + await hass.async_block_till_done() + vera_device.stop.assert_called() + update_callback(vera_device) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == "open" + assert hass.states.get(entity_id).attributes["current_position"] == 50 + + await hass.services.async_call( + "cover", "close_cover", {"entity_id": entity_id}, + ) + await hass.async_block_till_done() + vera_device.close.assert_called() + vera_device.is_open.return_value = False + vera_device.get_level.return_value = 00 + update_callback(vera_device) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == "closed" + assert hass.states.get(entity_id).attributes["current_position"] == 00 diff --git a/tests/components/vera/test_init.py b/tests/components/vera/test_init.py new file mode 100644 index 00000000000..9ff6cb4058b --- /dev/null +++ b/tests/components/vera/test_init.py @@ -0,0 +1,78 @@ +"""Vera tests.""" +from unittest.mock import MagicMock + +from pyvera import ( + VeraArmableDevice, + VeraBinarySensor, + VeraController, + VeraCurtain, + VeraDevice, + VeraDimmer, + VeraLock, + VeraScene, + VeraSceneController, + VeraSensor, + VeraSwitch, + VeraThermostat, +) + +from homeassistant.components.vera import ( + CONF_EXCLUDE, + CONF_LIGHTS, + DOMAIN, + VERA_DEVICES, +) +from homeassistant.core import HomeAssistant + +from .common import ComponentFactory + + +def new_vera_device(cls, device_id: int) -> VeraDevice: + """Create new mocked vera device..""" + vera_device = MagicMock(spec=cls) # type: VeraDevice + vera_device.device_id = device_id + vera_device.name = f"dev${device_id}" + return vera_device + + +def assert_hass_vera_devices(hass: HomeAssistant, platform: str, arr_len: int) -> None: + """Assert vera devices are present..""" + assert hass.data[VERA_DEVICES][platform] + assert len(hass.data[VERA_DEVICES][platform]) == arr_len + + +async def test_init( + hass: HomeAssistant, vera_component_factory: ComponentFactory +) -> None: + """Test function.""" + + def setup_callback(controller: VeraController, hass_config: dict) -> None: + hass_config[DOMAIN][CONF_EXCLUDE] = [11] + hass_config[DOMAIN][CONF_LIGHTS] = [10] + + await vera_component_factory.configure_component( + hass=hass, + devices=( + new_vera_device(VeraDimmer, 1), + new_vera_device(VeraBinarySensor, 2), + new_vera_device(VeraSensor, 3), + new_vera_device(VeraArmableDevice, 4), + new_vera_device(VeraLock, 5), + new_vera_device(VeraThermostat, 6), + new_vera_device(VeraCurtain, 7), + new_vera_device(VeraSceneController, 8), + new_vera_device(VeraSwitch, 9), + new_vera_device(VeraSwitch, 10), + new_vera_device(VeraSwitch, 11), + ), + scenes=(MagicMock(spec=VeraScene),), + setup_callback=setup_callback, + ) + + assert_hass_vera_devices(hass, "light", 2) + assert_hass_vera_devices(hass, "binary_sensor", 1) + assert_hass_vera_devices(hass, "sensor", 2) + assert_hass_vera_devices(hass, "switch", 2) + assert_hass_vera_devices(hass, "lock", 1) + assert_hass_vera_devices(hass, "climate", 1) + assert_hass_vera_devices(hass, "cover", 1) diff --git a/tests/components/vera/test_light.py b/tests/components/vera/test_light.py new file mode 100644 index 00000000000..fa63ce63454 --- /dev/null +++ b/tests/components/vera/test_light.py @@ -0,0 +1,76 @@ +"""Vera tests.""" +from unittest.mock import MagicMock + +from pyvera import CATEGORY_DIMMER, VeraDimmer + +from homeassistant.components.light import ATTR_BRIGHTNESS, ATTR_HS_COLOR +from homeassistant.core import HomeAssistant + +from .common import ComponentFactory + + +async def test_light( + hass: HomeAssistant, vera_component_factory: ComponentFactory +) -> None: + """Test function.""" + vera_device = MagicMock(spec=VeraDimmer) # type: VeraDimmer + vera_device.device_id = 1 + vera_device.name = "dev1" + vera_device.category = CATEGORY_DIMMER + vera_device.is_switched_on = MagicMock(return_value=False) + vera_device.get_brightness = MagicMock(return_value=0) + vera_device.get_color = MagicMock(return_value=[0, 0, 0]) + vera_device.is_dimmable = True + entity_id = "light.dev1_1" + + component_data = await vera_component_factory.configure_component( + hass=hass, devices=(vera_device,), + ) + controller = component_data.controller + update_callback = controller.register.call_args_list[0][0][1] + + assert hass.states.get(entity_id).state == "off" + + await hass.services.async_call( + "light", "turn_on", {"entity_id": entity_id}, + ) + await hass.async_block_till_done() + vera_device.switch_on.assert_called() + vera_device.is_switched_on.return_value = True + update_callback(vera_device) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == "on" + + await hass.services.async_call( + "light", "turn_on", {"entity_id": entity_id, ATTR_HS_COLOR: [300, 70]}, + ) + await hass.async_block_till_done() + vera_device.set_color.assert_called_with((255, 76, 255)) + vera_device.is_switched_on.return_value = True + vera_device.get_color.return_value = (255, 76, 255) + update_callback(vera_device) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == "on" + assert hass.states.get(entity_id).attributes["hs_color"] == (300.0, 70.196) + + await hass.services.async_call( + "light", "turn_on", {"entity_id": entity_id, ATTR_BRIGHTNESS: 55}, + ) + await hass.async_block_till_done() + vera_device.set_brightness.assert_called_with(55) + vera_device.is_switched_on.return_value = True + vera_device.get_brightness.return_value = 55 + update_callback(vera_device) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == "on" + assert hass.states.get(entity_id).attributes["brightness"] == 55 + + await hass.services.async_call( + "light", "turn_off", {"entity_id": entity_id}, + ) + await hass.async_block_till_done() + vera_device.switch_off.assert_called() + vera_device.is_switched_on.return_value = False + update_callback(vera_device) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == "off" diff --git a/tests/components/vera/test_lock.py b/tests/components/vera/test_lock.py new file mode 100644 index 00000000000..362bdbeddc0 --- /dev/null +++ b/tests/components/vera/test_lock.py @@ -0,0 +1,49 @@ +"""Vera tests.""" +from unittest.mock import MagicMock + +from pyvera import CATEGORY_LOCK, VeraLock + +from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED +from homeassistant.core import HomeAssistant + +from .common import ComponentFactory + + +async def test_lock( + hass: HomeAssistant, vera_component_factory: ComponentFactory +) -> None: + """Test function.""" + vera_device = MagicMock(spec=VeraLock) # type: VeraLock + vera_device.device_id = 1 + vera_device.name = "dev1" + vera_device.category = CATEGORY_LOCK + vera_device.is_locked = MagicMock(return_value=False) + entity_id = "lock.dev1_1" + + component_data = await vera_component_factory.configure_component( + hass=hass, devices=(vera_device,), + ) + controller = component_data.controller + update_callback = controller.register.call_args_list[0][0][1] + + assert hass.states.get(entity_id).state == STATE_UNLOCKED + + await hass.services.async_call( + "lock", "lock", {"entity_id": entity_id}, + ) + await hass.async_block_till_done() + vera_device.lock.assert_called() + vera_device.is_locked.return_value = True + update_callback(vera_device) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == STATE_LOCKED + + await hass.services.async_call( + "lock", "unlock", {"entity_id": entity_id}, + ) + await hass.async_block_till_done() + vera_device.unlock.assert_called() + vera_device.is_locked.return_value = False + update_callback(vera_device) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == STATE_UNLOCKED diff --git a/tests/components/vera/test_scene.py b/tests/components/vera/test_scene.py new file mode 100644 index 00000000000..136227ffa71 --- /dev/null +++ b/tests/components/vera/test_scene.py @@ -0,0 +1,27 @@ +"""Vera tests.""" +from unittest.mock import MagicMock + +from pyvera import VeraScene + +from homeassistant.core import HomeAssistant + +from .common import ComponentFactory + + +async def test_scene( + hass: HomeAssistant, vera_component_factory: ComponentFactory +) -> None: + """Test function.""" + vera_scene = MagicMock(spec=VeraScene) # type: VeraScene + vera_scene.scene_id = 1 + vera_scene.name = "dev1" + entity_id = "scene.dev1_1" + + await vera_component_factory.configure_component( + hass=hass, scenes=(vera_scene,), + ) + + await hass.services.async_call( + "scene", "turn_on", {"entity_id": entity_id}, + ) + await hass.async_block_till_done() diff --git a/tests/components/vera/test_sensor.py b/tests/components/vera/test_sensor.py new file mode 100644 index 00000000000..da28fc225e0 --- /dev/null +++ b/tests/components/vera/test_sensor.py @@ -0,0 +1,198 @@ +"""Vera tests.""" +from typing import Any, Callable, Tuple +from unittest.mock import MagicMock + +from pyvera import ( + CATEGORY_HUMIDITY_SENSOR, + CATEGORY_LIGHT_SENSOR, + CATEGORY_POWER_METER, + CATEGORY_SCENE_CONTROLLER, + CATEGORY_TEMPERATURE_SENSOR, + CATEGORY_UV_SENSOR, + VeraController, + VeraSensor, +) + +from homeassistant.core import HomeAssistant + +from .common import ComponentFactory + + +async def run_sensor_test( + hass: HomeAssistant, + vera_component_factory: ComponentFactory, + category: int, + class_property: str, + assert_states: Tuple[Tuple[Any, Any]], + assert_unit_of_measurement: str = None, + setup_callback: Callable[[VeraController], None] = None, +) -> None: + """Test generic sensor.""" + vera_device = MagicMock(spec=VeraSensor) # type: VeraSensor + vera_device.device_id = 1 + vera_device.name = "dev1" + vera_device.category = category + setattr(vera_device, class_property, "33") + entity_id = "sensor.dev1_1" + + component_data = await vera_component_factory.configure_component( + hass=hass, devices=(vera_device,), setup_callback=setup_callback + ) + controller = component_data.controller + update_callback = controller.register.call_args_list[0][0][1] + + for (initial_value, state_value) in assert_states: + setattr(vera_device, class_property, initial_value) + update_callback(vera_device) + await hass.async_block_till_done() + state = hass.states.get(entity_id) + assert state.state == state_value + if assert_unit_of_measurement: + assert state.attributes["unit_of_measurement"] == assert_unit_of_measurement + + +async def test_temperature_sensor_f( + hass: HomeAssistant, vera_component_factory: ComponentFactory +) -> None: + """Test function.""" + + def setup_callback(controller: VeraController, hass_config: dict) -> None: + controller.temperature_units = "F" + + await run_sensor_test( + hass=hass, + vera_component_factory=vera_component_factory, + category=CATEGORY_TEMPERATURE_SENSOR, + class_property="temperature", + assert_states=(("33", "1"), ("44", "7"),), + setup_callback=setup_callback, + ) + + +async def test_temperature_sensor_c( + hass: HomeAssistant, vera_component_factory: ComponentFactory +) -> None: + """Test function.""" + await run_sensor_test( + hass=hass, + vera_component_factory=vera_component_factory, + category=CATEGORY_TEMPERATURE_SENSOR, + class_property="temperature", + assert_states=(("33", "33"), ("44", "44"),), + ) + + +async def test_light_sensor( + hass: HomeAssistant, vera_component_factory: ComponentFactory +) -> None: + """Test function.""" + await run_sensor_test( + hass=hass, + vera_component_factory=vera_component_factory, + category=CATEGORY_LIGHT_SENSOR, + class_property="light", + assert_states=(("12", "12"), ("13", "13"),), + assert_unit_of_measurement="lx", + ) + + +async def test_uv_sensor( + hass: HomeAssistant, vera_component_factory: ComponentFactory +) -> None: + """Test function.""" + await run_sensor_test( + hass=hass, + vera_component_factory=vera_component_factory, + category=CATEGORY_UV_SENSOR, + class_property="light", + assert_states=(("12", "12"), ("13", "13"),), + assert_unit_of_measurement="level", + ) + + +async def test_humidity_sensor( + hass: HomeAssistant, vera_component_factory: ComponentFactory +) -> None: + """Test function.""" + await run_sensor_test( + hass=hass, + vera_component_factory=vera_component_factory, + category=CATEGORY_HUMIDITY_SENSOR, + class_property="humidity", + assert_states=(("12", "12"), ("13", "13"),), + assert_unit_of_measurement="%", + ) + + +async def test_power_meter_sensor( + hass: HomeAssistant, vera_component_factory: ComponentFactory +) -> None: + """Test function.""" + await run_sensor_test( + hass=hass, + vera_component_factory=vera_component_factory, + category=CATEGORY_POWER_METER, + class_property="power", + assert_states=(("12", "12"), ("13", "13"),), + assert_unit_of_measurement="watts", + ) + + +async def test_trippable_sensor( + hass: HomeAssistant, vera_component_factory: ComponentFactory +) -> None: + """Test function.""" + + def setup_callback(controller: VeraController, hass_config: dict) -> None: + controller.get_devices()[0].is_trippable = True + + await run_sensor_test( + hass=hass, + vera_component_factory=vera_component_factory, + category=999, + class_property="is_tripped", + assert_states=((True, "Tripped"), (False, "Not Tripped"), (True, "Tripped"),), + setup_callback=setup_callback, + ) + + +async def test_unknown_sensor( + hass: HomeAssistant, vera_component_factory: ComponentFactory +) -> None: + """Test function.""" + + def setup_callback(controller: VeraController, hass_config: dict) -> None: + controller.get_devices()[0].is_trippable = False + + await run_sensor_test( + hass=hass, + vera_component_factory=vera_component_factory, + category=999, + class_property="is_tripped", + assert_states=((True, "Unknown"), (False, "Unknown"), (True, "Unknown"),), + setup_callback=setup_callback, + ) + + +async def test_scene_controller_sensor( + hass: HomeAssistant, vera_component_factory: ComponentFactory +) -> None: + """Test function.""" + vera_device = MagicMock(spec=VeraSensor) # type: VeraSensor + vera_device.device_id = 1 + vera_device.name = "dev1" + vera_device.category = CATEGORY_SCENE_CONTROLLER + vera_device.get_last_scene_id = MagicMock(return_value="id0") + vera_device.get_last_scene_time = MagicMock(return_value="0000") + entity_id = "sensor.dev1_1" + + component_data = await vera_component_factory.configure_component( + hass=hass, devices=(vera_device,), + ) + controller = component_data.controller + update_callback = controller.register.call_args_list[0][0][1] + + vera_device.get_last_scene_time = "1111" + update_callback(vera_device) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == "id0" diff --git a/tests/components/vera/test_switch.py b/tests/components/vera/test_switch.py new file mode 100644 index 00000000000..ba09068e7e6 --- /dev/null +++ b/tests/components/vera/test_switch.py @@ -0,0 +1,48 @@ +"""Vera tests.""" +from unittest.mock import MagicMock + +from pyvera import CATEGORY_SWITCH, VeraSwitch + +from homeassistant.core import HomeAssistant + +from .common import ComponentFactory + + +async def test_switch( + hass: HomeAssistant, vera_component_factory: ComponentFactory +) -> None: + """Test function.""" + vera_device = MagicMock(spec=VeraSwitch) # type: VeraSwitch + vera_device.device_id = 1 + vera_device.name = "dev1" + vera_device.category = CATEGORY_SWITCH + vera_device.is_switched_on = MagicMock(return_value=False) + entity_id = "switch.dev1_1" + + component_data = await vera_component_factory.configure_component( + hass=hass, devices=(vera_device,), + ) + controller = component_data.controller + update_callback = controller.register.call_args_list[0][0][1] + + assert hass.states.get(entity_id).state == "off" + + await hass.services.async_call( + "switch", "turn_on", {"entity_id": entity_id}, + ) + await hass.async_block_till_done() + vera_device.switch_on.assert_called() + vera_device.is_switched_on.return_value = True + update_callback(vera_device) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == "on" + + await hass.services.async_call( + "switch", "turn_off", {"entity_id": entity_id}, + ) + await hass.async_block_till_done() + vera_device.switch_off.assert_called() + vera_device.is_switched_on.return_value = False + update_callback(vera_device) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == "off" From d451e54e3418b7210c93b11315526895ed0488a3 Mon Sep 17 00:00:00 2001 From: Bernhard B Date: Mon, 9 Dec 2019 00:27:06 +0100 Subject: [PATCH 124/677] Add Signal Messenger integration (#28537) * added signalmessenger integration * allows to send a message (with an attachment) to one or more recipients * added signalmessenger documentation to manifest file * remove debug logging from signalmessenger integration * add signalmessenger to .coveragerc * fixed typo in signalmessenger manifes * moved service specific code to own pypi library * updated pysignalclirestapi dependeny in manifest.json * added pysignalclirestapi requirement for signalmessenger component * fixed typo in codeowners * reworked signalmessenger integration based on code review input * updated requirements for signalmessenger * small code improvements in signalmessenger integration * no need to use the get() method to access dict parameters that are required * small changes in signalmessenger integration * re-ordered import statements * removed empty "requirements" list (not needed) * changed import order in signalmessenger integration according to PEP 8 * used isort to order includes in signalmessenger integration * renamed signalmessenger to signal_messenger * renamed signalmessenger to signal_messenger in CODEOWNERS file * changed documentation url in signal_messenger integration to new name * changed signal messenger naming in .coveragerc --- .coveragerc | 2 + CODEOWNERS | 1 + .../components/signal_messenger/__init__.py | 1 + .../components/signal_messenger/manifest.json | 10 +++ .../components/signal_messenger/notify.py | 71 +++++++++++++++++++ requirements_all.txt | 3 + 6 files changed, 88 insertions(+) create mode 100644 homeassistant/components/signal_messenger/__init__.py create mode 100644 homeassistant/components/signal_messenger/manifest.json create mode 100644 homeassistant/components/signal_messenger/notify.py diff --git a/.coveragerc b/.coveragerc index e85c97ef80c..b974356e0fe 100644 --- a/.coveragerc +++ b/.coveragerc @@ -611,6 +611,8 @@ omit = homeassistant/components/shodan/sensor.py homeassistant/components/sht31/sensor.py homeassistant/components/sigfox/sensor.py + homeassistant/components/signal_messenger/__init__.py + homeassistant/components/signal_messenger/notify.py homeassistant/components/simplepush/notify.py homeassistant/components/simplisafe/__init__.py homeassistant/components/simplisafe/alarm_control_panel.py diff --git a/CODEOWNERS b/CODEOWNERS index 472798f72a6..6723244c089 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -281,6 +281,7 @@ homeassistant/components/seventeentrack/* @bachya homeassistant/components/shell_command/* @home-assistant/core homeassistant/components/shiftr/* @fabaff homeassistant/components/shodan/* @fabaff +homeassistant/components/signal_messenger/* @bbernhard homeassistant/components/simplisafe/* @bachya homeassistant/components/sinch/* @bendikrb homeassistant/components/slide/* @ualex73 diff --git a/homeassistant/components/signal_messenger/__init__.py b/homeassistant/components/signal_messenger/__init__.py new file mode 100644 index 00000000000..045eb95f1b3 --- /dev/null +++ b/homeassistant/components/signal_messenger/__init__.py @@ -0,0 +1 @@ +"""The signalmessenger component.""" diff --git a/homeassistant/components/signal_messenger/manifest.json b/homeassistant/components/signal_messenger/manifest.json new file mode 100644 index 00000000000..b3494ce8bab --- /dev/null +++ b/homeassistant/components/signal_messenger/manifest.json @@ -0,0 +1,10 @@ +{ + "domain": "signal_messenger", + "name": "signal_messenger", + "documentation": "https://www.home-assistant.io/integrations/signal_messenger", + "dependencies": [], + "codeowners": ["@bbernhard"], + "requirements": [ + "pysignalclirestapi==0.1.4" + ] +} diff --git a/homeassistant/components/signal_messenger/notify.py b/homeassistant/components/signal_messenger/notify.py new file mode 100644 index 00000000000..8fbf9c70873 --- /dev/null +++ b/homeassistant/components/signal_messenger/notify.py @@ -0,0 +1,71 @@ +"""Signal Messenger for notify component.""" +import logging + +from pysignalclirestapi import SignalCliRestApi, SignalCliRestApiError +import voluptuous as vol + +from homeassistant.components.notify import ( + ATTR_DATA, + PLATFORM_SCHEMA, + BaseNotificationService, +) +import homeassistant.helpers.config_validation as cv + +_LOGGER = logging.getLogger(__name__) + +CONF_SENDER_NR = "number" +CONF_RECP_NR = "recipients" +CONF_SIGNAL_CLI_REST_API = "url" +ATTR_FILENAME = "attachment" + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_SENDER_NR): cv.string, + vol.Required(CONF_SIGNAL_CLI_REST_API): cv.string, + vol.Required(CONF_RECP_NR): vol.All(cv.ensure_list, [cv.string]), + } +) + + +def get_service(hass, config, discovery_info=None): + """Get the SignalMessenger notification service.""" + + sender_nr = config[CONF_SENDER_NR] + recp_nrs = config[CONF_RECP_NR] + signal_cli_rest_api_url = config[CONF_SIGNAL_CLI_REST_API] + + signal_cli_rest_api = SignalCliRestApi( + signal_cli_rest_api_url, sender_nr, api_version=1 + ) + + return SignalNotificationService(recp_nrs, signal_cli_rest_api) + + +class SignalNotificationService(BaseNotificationService): + """Implement the notification service for SignalMessenger.""" + + def __init__(self, recp_nrs, signal_cli_rest_api): + """Initialize the service.""" + + self._recp_nrs = recp_nrs + self._signal_cli_rest_api = signal_cli_rest_api + + def send_message(self, message="", **kwargs): + """Send a message to a one or more recipients. + + Additionally a file can be attached. + """ + + _LOGGER.debug("Sending signal message") + + data = kwargs.get(ATTR_DATA) + + filename = None + if data is not None and ATTR_FILENAME in data: + filename = data[ATTR_FILENAME] + + try: + self._signal_cli_rest_api.send_message(message, self._recp_nrs, filename) + except SignalCliRestApiError as ex: + _LOGGER.error("%s", ex) + raise ex diff --git a/requirements_all.txt b/requirements_all.txt index 87ca2a46177..e5983972243 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1482,6 +1482,9 @@ pysesame2==1.0.1 # homeassistant.components.goalfeed pysher==1.0.1 +# homeassistant.components.signal_messenger +pysignalclirestapi==0.1.4 + # homeassistant.components.sma pysma==0.3.4 From 8dea7f0f98df8c93d4a628d4cba470a64b263877 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Mon, 9 Dec 2019 00:32:08 +0000 Subject: [PATCH 125/677] [ci skip] Translation update --- .../components/elgato/.translations/en.json | 50 +++++++++---------- .../components/elgato/.translations/no.json | 11 ++++ .../elgato/.translations/zh-Hant.json | 27 ++++++++++ 3 files changed, 63 insertions(+), 25 deletions(-) create mode 100644 homeassistant/components/elgato/.translations/no.json create mode 100644 homeassistant/components/elgato/.translations/zh-Hant.json diff --git a/homeassistant/components/elgato/.translations/en.json b/homeassistant/components/elgato/.translations/en.json index 03c46f02efc..d52003d10e1 100644 --- a/homeassistant/components/elgato/.translations/en.json +++ b/homeassistant/components/elgato/.translations/en.json @@ -1,27 +1,27 @@ { - "config": { - "title": "Elgato Key Light", - "flow_title": "Elgato Key Light: {serial_number}", - "step": { - "user": { - "title": "Link your Elgato Key Light", - "description": "Set up your Elgato Key Light to integrate with Home Assistant.", - "data": { - "host": "Host or IP address", - "port": "Port number" - } - }, - "zeroconf_confirm": { - "description": "Do you want to add the Elgato Key Light with serial number `{serial_number}` to Home Assistant?", - "title": "Discovered Elgato Key Light device" - } - }, - "error": { - "connection_error": "Failed to connect to Elgato Key Light device." - }, - "abort": { - "already_configured": "This Elgato Key Light device is already configured.", - "connection_error": "Failed to connect to Elgato Key Light device." + "config": { + "abort": { + "already_configured": "This Elgato Key Light device is already configured.", + "connection_error": "Failed to connect to Elgato Key Light device." + }, + "error": { + "connection_error": "Failed to connect to Elgato Key Light device." + }, + "flow_title": "Elgato Key Light: {serial_number}", + "step": { + "user": { + "data": { + "host": "Host or IP address", + "port": "Port number" + }, + "description": "Set up your Elgato Key Light to integrate with Home Assistant.", + "title": "Link your Elgato Key Light" + }, + "zeroconf_confirm": { + "description": "Do you want to add the Elgato Key Light with serial number `{serial_number}` to Home Assistant?", + "title": "Discovered Elgato Key Light device" + } + }, + "title": "Elgato Key Light" } - } -} +} \ No newline at end of file diff --git a/homeassistant/components/elgato/.translations/no.json b/homeassistant/components/elgato/.translations/no.json new file mode 100644 index 00000000000..df7d6db2621 --- /dev/null +++ b/homeassistant/components/elgato/.translations/no.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "Portnummer" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elgato/.translations/zh-Hant.json b/homeassistant/components/elgato/.translations/zh-Hant.json new file mode 100644 index 00000000000..b187abc5ccd --- /dev/null +++ b/homeassistant/components/elgato/.translations/zh-Hant.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Elgato Key \u7167\u660e\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3002", + "connection_error": "Elgato Key \u7167\u660e\u8a2d\u5099\u9023\u7dda\u5931\u6557\u3002" + }, + "error": { + "connection_error": "Elgato Key \u7167\u660e\u8a2d\u5099\u9023\u7dda\u5931\u6557\u3002" + }, + "flow_title": "Elgato Key \u7167\u660e\uff1a{serial_number}", + "step": { + "user": { + "data": { + "host": "\u4e3b\u6a5f\u6216 IP \u4f4d\u5740", + "port": "\u901a\u8a0a\u57e0" + }, + "description": "\u8a2d\u5b9a Elgato Key \u7167\u660e\u4ee5\u6574\u5408\u81f3 Home Assistant\u3002", + "title": "\u9023\u7d50 Elgato Key \u7167\u660e\u3002" + }, + "zeroconf_confirm": { + "description": "\u662f\u5426\u8981\u5c07 Elgato Key \u7167\u660e\u5e8f\u865f `{serial_number}` \u65b0\u589e\u81f3 Home Assistant\uff1f", + "title": "\u767c\u73fe\u5230 Elgato Key \u7167\u660e\u8a2d\u5099" + } + }, + "title": "Elgato Key \u7167\u660e" + } +} \ No newline at end of file From 6996ad35415c0ec6e8ea5fe0efadab1471da4efd Mon Sep 17 00:00:00 2001 From: gjbadros Date: Mon, 9 Dec 2019 00:14:55 -0800 Subject: [PATCH 126/677] Protect Doorbird platform from failing when individual doorbird fails (#29374) * Wrap connection attempt by try/except block so an individual downed doorbird does not fail the whole platform * Fixed lint warning * Disable too-broad-exception pylint warning since the whole point of this is to catch all exceptions and log them while letting processing continue; I don't disable a lint warning lightly, but this is the entire intent of this PR. * Fixed name of pylint warning to broad-except * Use _LOGGER.exception to get the stack trace too, per PEP8 recommendation for a bare Exception being caught. * per @balloob, switch to just catching OSError on a narrow block; I'm not sure this protects all problems with Doorbird device startup, but it protects against the primary one I experience. --- homeassistant/components/doorbird/__init__.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/doorbird/__init__.py b/homeassistant/components/doorbird/__init__.py index d92ff3d3692..680ee1354eb 100644 --- a/homeassistant/components/doorbird/__init__.py +++ b/homeassistant/components/doorbird/__init__.py @@ -67,8 +67,14 @@ def setup(hass, config): token = doorstation_config.get(CONF_TOKEN) name = doorstation_config.get(CONF_NAME) or "DoorBird {}".format(index + 1) - device = DoorBird(device_ip, username, password) - status = device.ready() + try: + device = DoorBird(device_ip, username, password) + status = device.ready() + except OSError as oserr: + _LOGGER.error( + "Failed to setup doorbird at %s: %s; not retrying", device_ip, oserr + ) + continue if status[0]: doorstation = ConfiguredDoorBird(device, name, events, custom_url, token) From 8c1a8b502d319d2f8f3452db0215116c9ec2f481 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 09:36:42 +0100 Subject: [PATCH 127/677] Sort imports according to PEP8 for velbus (#29676) --- homeassistant/components/velbus/__init__.py | 5 +++-- homeassistant/components/velbus/binary_sensor.py | 2 +- homeassistant/components/velbus/climate.py | 2 +- homeassistant/components/velbus/config_flow.py | 2 +- homeassistant/components/velbus/cover.py | 4 ++-- homeassistant/components/velbus/sensor.py | 2 +- tests/components/velbus/test_config_flow.py | 4 ++-- 7 files changed, 11 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/velbus/__init__.py b/homeassistant/components/velbus/__init__.py index 9946f06446f..317c305254b 100644 --- a/homeassistant/components/velbus/__init__.py +++ b/homeassistant/components/velbus/__init__.py @@ -1,13 +1,14 @@ """Support for Velbus devices.""" import asyncio import logging + import velbus import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.const import CONF_PORT, CONF_NAME +from homeassistant.const import CONF_NAME, CONF_PORT from homeassistant.exceptions import ConfigEntryNotReady +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import HomeAssistantType diff --git a/homeassistant/components/velbus/binary_sensor.py b/homeassistant/components/velbus/binary_sensor.py index 9230632e442..505303ded24 100644 --- a/homeassistant/components/velbus/binary_sensor.py +++ b/homeassistant/components/velbus/binary_sensor.py @@ -3,8 +3,8 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from .const import DOMAIN from . import VelbusEntity +from .const import DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/velbus/climate.py b/homeassistant/components/velbus/climate.py index eb5ed00c395..812e4605d95 100644 --- a/homeassistant/components/velbus/climate.py +++ b/homeassistant/components/velbus/climate.py @@ -10,8 +10,8 @@ from homeassistant.components.climate.const import ( ) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT -from .const import DOMAIN from . import VelbusEntity +from .const import DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/velbus/config_flow.py b/homeassistant/components/velbus/config_flow.py index e9cbe14ce25..9325acf0608 100644 --- a/homeassistant/components/velbus/config_flow.py +++ b/homeassistant/components/velbus/config_flow.py @@ -3,7 +3,7 @@ import velbus import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import CONF_PORT, CONF_NAME +from homeassistant.const import CONF_NAME, CONF_PORT from homeassistant.core import HomeAssistant, callback from homeassistant.util import slugify diff --git a/homeassistant/components/velbus/cover.py b/homeassistant/components/velbus/cover.py index cf73af593b8..aea02331ead 100644 --- a/homeassistant/components/velbus/cover.py +++ b/homeassistant/components/velbus/cover.py @@ -4,14 +4,14 @@ import logging from velbus.util import VelbusException from homeassistant.components.cover import ( - CoverDevice, SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_STOP, + CoverDevice, ) -from .const import DOMAIN from . import VelbusEntity +from .const import DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/velbus/sensor.py b/homeassistant/components/velbus/sensor.py index 3b7f2b6f5bc..8af5df9e165 100644 --- a/homeassistant/components/velbus/sensor.py +++ b/homeassistant/components/velbus/sensor.py @@ -1,8 +1,8 @@ """Support for Velbus sensors.""" import logging -from .const import DOMAIN from . import VelbusEntity +from .const import DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/velbus/test_config_flow.py b/tests/components/velbus/test_config_flow.py index 271f0b3dd3a..66273e01f43 100644 --- a/tests/components/velbus/test_config_flow.py +++ b/tests/components/velbus/test_config_flow.py @@ -1,11 +1,11 @@ """Tests for the Velbus config flow.""" -from unittest.mock import patch, Mock +from unittest.mock import Mock, patch import pytest from homeassistant import data_entry_flow from homeassistant.components.velbus import config_flow -from homeassistant.const import CONF_PORT, CONF_NAME +from homeassistant.const import CONF_NAME, CONF_PORT from tests.common import MockConfigEntry From 942f654d926c484ebd25f5fcb11a5a9b64145268 Mon Sep 17 00:00:00 2001 From: Bruno Filipe Date: Mon, 9 Dec 2019 05:37:15 -0300 Subject: [PATCH 128/677] Proactively report Alexa Endpoint Health properties (#29736) --- homeassistant/components/alexa/capabilities.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/alexa/capabilities.py b/homeassistant/components/alexa/capabilities.py index 38f769b7cd4..9f217d2e9c9 100644 --- a/homeassistant/components/alexa/capabilities.py +++ b/homeassistant/components/alexa/capabilities.py @@ -262,7 +262,7 @@ class AlexaEndpointHealth(AlexaCapability): def properties_proactively_reported(self): """Return True if properties asynchronously reported.""" - return False + return True def properties_retrievable(self): """Return True if properties can be retrieved.""" From 236a21c76ea7f223fa3b1f83ab4dc06e68b07cf8 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 09:38:14 +0100 Subject: [PATCH 129/677] Sort imports according to PEP8 for pi_hole (#29726) --- homeassistant/components/pi_hole/__init__.py | 8 ++++---- homeassistant/components/pi_hole/sensor.py | 4 ++-- tests/components/pi_hole/test_init.py | 4 +++- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/pi_hole/__init__.py b/homeassistant/components/pi_hole/__init__.py index 95351083b5a..8ee21af7858 100644 --- a/homeassistant/components/pi_hole/__init__.py +++ b/homeassistant/components/pi_hole/__init__.py @@ -1,31 +1,31 @@ """The pi_hole component.""" import logging -import voluptuous as vol from hole import Hole from hole.exceptions import HoleError +import voluptuous as vol +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.const import ( + CONF_API_KEY, CONF_HOST, CONF_NAME, - CONF_API_KEY, CONF_SSL, CONF_VERIFY_SSL, ) -from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.discovery import async_load_platform from homeassistant.util import Throttle from .const import ( - DOMAIN, CONF_LOCATION, DEFAULT_HOST, DEFAULT_LOCATION, DEFAULT_NAME, DEFAULT_SSL, DEFAULT_VERIFY_SSL, + DOMAIN, MIN_TIME_BETWEEN_UPDATES, SERVICE_DISABLE, SERVICE_DISABLE_ATTR_DURATION, diff --git a/homeassistant/components/pi_hole/sensor.py b/homeassistant/components/pi_hole/sensor.py index 4e80e9767a6..f76e756aec4 100644 --- a/homeassistant/components/pi_hole/sensor.py +++ b/homeassistant/components/pi_hole/sensor.py @@ -4,10 +4,10 @@ import logging from homeassistant.helpers.entity import Entity from .const import ( - DOMAIN as PIHOLE_DOMAIN, ATTR_BLOCKED_DOMAINS, - SENSOR_LIST, + DOMAIN as PIHOLE_DOMAIN, SENSOR_DICT, + SENSOR_LIST, ) LOGGER = logging.getLogger(__name__) diff --git a/tests/components/pi_hole/test_init.py b/tests/components/pi_hole/test_init.py index f30422bfea9..b2f4b3f28af 100644 --- a/tests/components/pi_hole/test_init.py +++ b/tests/components/pi_hole/test_init.py @@ -1,11 +1,13 @@ """Test pi_hole component.""" +from unittest.mock import patch + from asynctest import CoroutineMock from hole import Hole from homeassistant.components import pi_hole + from tests.common import async_setup_component -from unittest.mock import patch def mock_pihole_data_call(Hole): From 2abc9005ccefb0e942bd03df31aa5c1bb4485033 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 09:38:24 +0100 Subject: [PATCH 130/677] use isort to sort imports according to PEP8 for homeassistant (#29718) --- .../components/homeassistant/__init__.py | 21 ++++++----- .../components/homeassistant/scene.py | 10 +++--- tests/components/homeassistant/test_init.py | 36 +++++++++---------- 3 files changed, 33 insertions(+), 34 deletions(-) diff --git a/homeassistant/components/homeassistant/__init__.py b/homeassistant/components/homeassistant/__init__.py index d2d6abdadb5..8aa1d7e020a 100644 --- a/homeassistant/components/homeassistant/__init__.py +++ b/homeassistant/components/homeassistant/__init__.py @@ -6,23 +6,22 @@ from typing import Awaitable import voluptuous as vol -import homeassistant.core as ha import homeassistant.config as conf_util -from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers.service import async_extract_entity_ids -from homeassistant.helpers import intent from homeassistant.const import ( ATTR_ENTITY_ID, - SERVICE_TURN_ON, - SERVICE_TURN_OFF, - SERVICE_TOGGLE, - SERVICE_HOMEASSISTANT_STOP, - SERVICE_HOMEASSISTANT_RESTART, - RESTART_EXIT_CODE, ATTR_LATITUDE, ATTR_LONGITUDE, + RESTART_EXIT_CODE, + SERVICE_HOMEASSISTANT_RESTART, + SERVICE_HOMEASSISTANT_STOP, + SERVICE_TOGGLE, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, ) -from homeassistant.helpers import config_validation as cv +import homeassistant.core as ha +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers import config_validation as cv, intent +from homeassistant.helpers.service import async_extract_entity_ids _LOGGER = logging.getLogger(__name__) DOMAIN = ha.DOMAIN diff --git a/homeassistant/components/homeassistant/scene.py b/homeassistant/components/homeassistant/scene.py index 576bf540e00..af271a069fe 100644 --- a/homeassistant/components/homeassistant/scene.py +++ b/homeassistant/components/homeassistant/scene.py @@ -4,6 +4,8 @@ import logging import voluptuous as vol +from homeassistant import config as conf_util +from homeassistant.components.scene import DOMAIN as SCENE_DOMAIN, STATES, Scene from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_STATE, @@ -11,21 +13,19 @@ from homeassistant.const import ( CONF_ID, CONF_NAME, CONF_PLATFORM, + SERVICE_RELOAD, STATE_OFF, STATE_ON, - SERVICE_RELOAD, ) -from homeassistant.core import State, DOMAIN as HA_DOMAIN -from homeassistant import config as conf_util +from homeassistant.core import DOMAIN as HA_DOMAIN, State from homeassistant.exceptions import HomeAssistantError -from homeassistant.loader import async_get_integration from homeassistant.helpers import ( config_per_platform, config_validation as cv, entity_platform, ) from homeassistant.helpers.state import async_reproduce_state -from homeassistant.components.scene import DOMAIN as SCENE_DOMAIN, STATES, Scene +from homeassistant.loader import async_get_integration def _convert_states(states): diff --git a/tests/components/homeassistant/test_init.py b/tests/components/homeassistant/test_init.py index 7a97de0f68e..6c2b7f78e24 100644 --- a/tests/components/homeassistant/test_init.py +++ b/tests/components/homeassistant/test_init.py @@ -2,40 +2,40 @@ # pylint: disable=protected-access import asyncio import unittest -from unittest.mock import patch, Mock +from unittest.mock import Mock, patch import yaml -import homeassistant.core as ha from homeassistant import config -from homeassistant.const import ( - ATTR_ENTITY_ID, - STATE_ON, - STATE_OFF, - SERVICE_HOMEASSISTANT_RESTART, - SERVICE_HOMEASSISTANT_STOP, - SERVICE_TURN_ON, - SERVICE_TURN_OFF, - SERVICE_TOGGLE, - EVENT_CORE_CONFIG_UPDATE, -) import homeassistant.components as comps -from homeassistant.setup import async_setup_component from homeassistant.components.homeassistant import ( SERVICE_CHECK_CONFIG, SERVICE_RELOAD_CORE_CONFIG, ) -import homeassistant.helpers.intent as intent +from homeassistant.const import ( + ATTR_ENTITY_ID, + EVENT_CORE_CONFIG_UPDATE, + SERVICE_HOMEASSISTANT_RESTART, + SERVICE_HOMEASSISTANT_STOP, + SERVICE_TOGGLE, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, +) +import homeassistant.core as ha from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity +import homeassistant.helpers.intent as intent +from homeassistant.setup import async_setup_component from tests.common import ( + async_capture_events, + async_mock_service, get_test_home_assistant, + mock_coro, mock_service, patch_yaml_files, - mock_coro, - async_mock_service, - async_capture_events, ) From 179003676760d44dbcd353c921229502606a059b Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 09:38:38 +0100 Subject: [PATCH 131/677] use isort to sort imports according to PEP8 for zone (#29712) --- homeassistant/components/zone/__init__.py | 17 ++++++++--------- homeassistant/components/zone/config_flow.py | 7 +++---- tests/components/zone/test_config_flow.py | 4 ++-- tests/components/zone/test_init.py | 3 +-- 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/zone/__init__.py b/homeassistant/components/zone/__init__.py index 6ae62be3eb9..357d4eac172 100644 --- a/homeassistant/components/zone/__init__.py +++ b/homeassistant/components/zone/__init__.py @@ -4,29 +4,28 @@ from typing import Set, cast import voluptuous as vol -from homeassistant.core import callback, State -from homeassistant.loader import bind_hass -import homeassistant.helpers.config_validation as cv from homeassistant.const import ( - CONF_NAME, + ATTR_LATITUDE, + ATTR_LONGITUDE, + CONF_ICON, CONF_LATITUDE, CONF_LONGITUDE, - CONF_ICON, + CONF_NAME, CONF_RADIUS, EVENT_CORE_CONFIG_UPDATE, ) +from homeassistant.core import State, callback from homeassistant.helpers import config_per_platform +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id +from homeassistant.loader import bind_hass from homeassistant.util import slugify -from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE from homeassistant.util.location import distance - from .config_flow import configured_zones -from .const import CONF_PASSIVE, DOMAIN, HOME_ZONE, ATTR_PASSIVE, ATTR_RADIUS +from .const import ATTR_PASSIVE, ATTR_RADIUS, CONF_PASSIVE, DOMAIN, HOME_ZONE from .zone import Zone - # mypy: allow-untyped-calls, allow-untyped-defs _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/zone/config_flow.py b/homeassistant/components/zone/config_flow.py index 39633754772..4531ff7b834 100644 --- a/homeassistant/components/zone/config_flow.py +++ b/homeassistant/components/zone/config_flow.py @@ -4,22 +4,21 @@ from typing import Set import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant import config_entries from homeassistant.const import ( - CONF_NAME, + CONF_ICON, CONF_LATITUDE, CONF_LONGITUDE, - CONF_ICON, + CONF_NAME, CONF_RADIUS, ) from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import slugify from .const import CONF_PASSIVE, DOMAIN, HOME_ZONE - # mypy: allow-untyped-defs, no-check-untyped-defs diff --git a/tests/components/zone/test_config_flow.py b/tests/components/zone/test_config_flow.py index 1e1bca9cdea..5f57e8b4064 100644 --- a/tests/components/zone/test_config_flow.py +++ b/tests/components/zone/test_config_flow.py @@ -3,10 +3,10 @@ from homeassistant.components.zone import config_flow from homeassistant.components.zone.const import CONF_PASSIVE, DOMAIN, HOME_ZONE from homeassistant.const import ( - CONF_NAME, + CONF_ICON, CONF_LATITUDE, CONF_LONGITUDE, - CONF_ICON, + CONF_NAME, CONF_RADIUS, ) diff --git a/tests/components/zone/test_init.py b/tests/components/zone/test_init.py index b5b975ae4bd..d4a76463c18 100644 --- a/tests/components/zone/test_init.py +++ b/tests/components/zone/test_init.py @@ -6,8 +6,7 @@ from unittest.mock import Mock from homeassistant import setup from homeassistant.components import zone -from tests.common import get_test_home_assistant -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, get_test_home_assistant async def test_setup_entry_successful(hass): From d0c7db548f9cffebe895fd7cdc54abf784de29b5 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 09:39:26 +0100 Subject: [PATCH 132/677] use isort to sort imports according to PEP8 for group (#29713) --- tests/components/group/test_cover.py | 6 ++--- tests/components/group/test_init.py | 26 +++++++++---------- tests/components/group/test_notify.py | 6 ++--- .../components/group/test_reproduce_state.py | 1 + 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/tests/components/group/test_cover.py b/tests/components/group/test_cover.py index e687f105e20..42345536120 100644 --- a/tests/components/group/test_cover.py +++ b/tests/components/group/test_cover.py @@ -19,16 +19,16 @@ from homeassistant.const import ( CONF_ENTITIES, SERVICE_CLOSE_COVER, SERVICE_CLOSE_COVER_TILT, - SERVICE_TOGGLE, - SERVICE_TOGGLE_COVER_TILT, SERVICE_OPEN_COVER, SERVICE_OPEN_COVER_TILT, SERVICE_SET_COVER_POSITION, SERVICE_SET_COVER_TILT_POSITION, SERVICE_STOP_COVER, SERVICE_STOP_COVER_TILT, - STATE_OPEN, + SERVICE_TOGGLE, + SERVICE_TOGGLE_COVER_TILT, STATE_CLOSED, + STATE_OPEN, ) from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util diff --git a/tests/components/group/test_init.py b/tests/components/group/test_init.py index 44f5448ec8f..5c826dbe85d 100644 --- a/tests/components/group/test_init.py +++ b/tests/components/group/test_init.py @@ -5,21 +5,21 @@ from collections import OrderedDict import unittest from unittest.mock import patch -from homeassistant.setup import setup_component, async_setup_component -from homeassistant.const import ( - STATE_ON, - STATE_OFF, - STATE_HOME, - STATE_UNKNOWN, - ATTR_ICON, - ATTR_HIDDEN, - ATTR_ASSUMED_STATE, - STATE_NOT_HOME, - ATTR_FRIENDLY_NAME, -) import homeassistant.components.group as group +from homeassistant.const import ( + ATTR_ASSUMED_STATE, + ATTR_FRIENDLY_NAME, + ATTR_HIDDEN, + ATTR_ICON, + STATE_HOME, + STATE_NOT_HOME, + STATE_OFF, + STATE_ON, + STATE_UNKNOWN, +) +from homeassistant.setup import async_setup_component, setup_component -from tests.common import get_test_home_assistant, assert_setup_component +from tests.common import assert_setup_component, get_test_home_assistant from tests.components.group import common diff --git a/tests/components/group/test_notify.py b/tests/components/group/test_notify.py index d7b7496573b..f029ec9d2fa 100644 --- a/tests/components/group/test_notify.py +++ b/tests/components/group/test_notify.py @@ -3,10 +3,10 @@ import asyncio import unittest from unittest.mock import MagicMock, patch -from homeassistant.setup import setup_component -import homeassistant.components.notify as notify -import homeassistant.components.group.notify as group import homeassistant.components.demo.notify as demo +import homeassistant.components.group.notify as group +import homeassistant.components.notify as notify +from homeassistant.setup import setup_component from tests.common import assert_setup_component, get_test_home_assistant diff --git a/tests/components/group/test_reproduce_state.py b/tests/components/group/test_reproduce_state.py index f3a56a46472..5cc1c862cd3 100644 --- a/tests/components/group/test_reproduce_state.py +++ b/tests/components/group/test_reproduce_state.py @@ -2,6 +2,7 @@ from asyncio import Future from unittest.mock import patch + from homeassistant.components.group.reproduce_state import async_reproduce_states from homeassistant.core import Context, State From c399fe2837b38a99e949da07ed33a747522e47ff Mon Sep 17 00:00:00 2001 From: Santobert Date: Mon, 9 Dec 2019 10:46:25 +0100 Subject: [PATCH 133/677] Change source of device_info (#29570) --- homeassistant/components/neato/vacuum.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/neato/vacuum.py b/homeassistant/components/neato/vacuum.py index 9cac0cd24ce..d8a3e4ded45 100644 --- a/homeassistant/components/neato/vacuum.py +++ b/homeassistant/components/neato/vacuum.py @@ -165,7 +165,7 @@ class NeatoConnectedVacuum(StateVacuumDevice): _LOGGER.debug("Running Neato Vacuums update") try: if self._robot_stats is None: - self._robot_stats = self.robot.get_robot_info().json() + self._robot_stats = self.robot.get_general_info().json().get("data") except NeatoRobotException: _LOGGER.warning("Couldn't fetch robot information of %s", self._name) @@ -319,10 +319,9 @@ class NeatoConnectedVacuum(StateVacuumDevice): """Device info for neato robot.""" info = {"identifiers": {(NEATO_DOMAIN, self._robot_serial)}, "name": self._name} if self._robot_stats: - info["manufacturer"] = self._robot_stats["data"]["mfg_name"] - info["model"] = self._robot_stats["data"]["modelName"] - if self._state: - info["sw_version"] = self._state["meta"]["firmware"] + info["manufacturer"] = self._robot_stats["battery"]["vendor"] + info["model"] = self._robot_stats["model"] + info["sw_version"] = self._robot_stats["firmware"] def start(self): """Start cleaning or resume cleaning.""" From 791dc5809ff657659994498e79fa1d296d2824ec Mon Sep 17 00:00:00 2001 From: Nikolay Vasilchuk Date: Mon, 9 Dec 2019 12:56:57 +0300 Subject: [PATCH 134/677] Fix unit_of_measurement for Starline temperature sensors (#29740) --- homeassistant/components/starline/sensor.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/starline/sensor.py b/homeassistant/components/starline/sensor.py index 0629a03e148..874fc5a9a7e 100644 --- a/homeassistant/components/starline/sensor.py +++ b/homeassistant/components/starline/sensor.py @@ -1,5 +1,6 @@ """Reads vehicle status from StarLine API.""" from homeassistant.components.sensor import DEVICE_CLASS_TEMPERATURE +from homeassistant.const import TEMP_CELSIUS from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level, icon_for_signal_level @@ -10,8 +11,8 @@ from .entity import StarlineEntity SENSOR_TYPES = { "battery": ["Battery", None, "V", None], "balance": ["Balance", None, None, "mdi:cash-multiple"], - "ctemp": ["Interior Temperature", DEVICE_CLASS_TEMPERATURE, None, None], - "etemp": ["Engine Temperature", DEVICE_CLASS_TEMPERATURE, None, None], + "ctemp": ["Interior Temperature", DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, None], + "etemp": ["Engine Temperature", DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, None], "gsm_lvl": ["GSM Signal", None, "%", None], } From 9df71ecae2a8750ac0e32f5cc8e02327674c69e7 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:05:22 +0100 Subject: [PATCH 135/677] Sort imports according to PEP8 for neato (#29724) --- homeassistant/components/neato/__init__.py | 4 ++-- tests/components/neato/test_config_flow.py | 2 +- tests/components/neato/test_init.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/neato/__init__.py b/homeassistant/components/neato/__init__.py index 5a697e7b9ad..ad4eb02eccc 100644 --- a/homeassistant/components/neato/__init__.py +++ b/homeassistant/components/neato/__init__.py @@ -1,11 +1,11 @@ """Support for Neato botvac connected vacuum cleaners.""" import asyncio -import logging from datetime import timedelta +import logging -import voluptuous as vol from pybotvac import Account, Neato, Vorwerk from pybotvac.exceptions import NeatoException, NeatoLoginException, NeatoRobotException +import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import CONF_PASSWORD, CONF_USERNAME diff --git a/tests/components/neato/test_config_flow.py b/tests/components/neato/test_config_flow.py index 3f4bd90d0c1..59db79c1052 100644 --- a/tests/components/neato/test_config_flow.py +++ b/tests/components/neato/test_config_flow.py @@ -1,8 +1,8 @@ """Tests for the Neato config flow.""" -import pytest from unittest.mock import patch from pybotvac.exceptions import NeatoLoginException, NeatoRobotException +import pytest from homeassistant import data_entry_flow from homeassistant.components.neato import config_flow diff --git a/tests/components/neato/test_init.py b/tests/components/neato/test_init.py index 444cbe8cc5d..8fa6ad05945 100644 --- a/tests/components/neato/test_init.py +++ b/tests/components/neato/test_init.py @@ -1,8 +1,8 @@ """Tests for the Neato init file.""" -import pytest from unittest.mock import patch from pybotvac.exceptions import NeatoLoginException +import pytest from homeassistant.components.neato.const import CONF_VENDOR, NEATO_DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_USERNAME From cbf59fb33dfbe23fda43b9e7b40a2028561edf57 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:06:05 +0100 Subject: [PATCH 136/677] Sort imports according to PEP8 for input_text (#29719) --- homeassistant/components/input_text/__init__.py | 6 +++--- homeassistant/components/input_text/reproduce_state.py | 2 +- tests/components/input_text/test_init.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/input_text/__init__.py b/homeassistant/components/input_text/__init__.py index d43e47c11ca..2d5a23e2a76 100644 --- a/homeassistant/components/input_text/__init__.py +++ b/homeassistant/components/input_text/__init__.py @@ -3,14 +3,14 @@ import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.const import ( - ATTR_UNIT_OF_MEASUREMENT, ATTR_MODE, + ATTR_UNIT_OF_MEASUREMENT, CONF_ICON, - CONF_NAME, CONF_MODE, + CONF_NAME, ) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.restore_state import RestoreEntity diff --git a/homeassistant/components/input_text/reproduce_state.py b/homeassistant/components/input_text/reproduce_state.py index f64c5c019f6..28a2f27ee84 100644 --- a/homeassistant/components/input_text/reproduce_state.py +++ b/homeassistant/components/input_text/reproduce_state.py @@ -7,7 +7,7 @@ from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import Context, State from homeassistant.helpers.typing import HomeAssistantType -from . import DOMAIN, SERVICE_SET_VALUE, ATTR_VALUE +from . import ATTR_VALUE, DOMAIN, SERVICE_SET_VALUE _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/input_text/test_init.py b/tests/components/input_text/test_init.py index 4888994d788..b758b245092 100644 --- a/tests/components/input_text/test_init.py +++ b/tests/components/input_text/test_init.py @@ -4,7 +4,7 @@ import asyncio from homeassistant.components.input_text import ATTR_VALUE, DOMAIN, SERVICE_SET_VALUE from homeassistant.const import ATTR_ENTITY_ID -from homeassistant.core import CoreState, State, Context +from homeassistant.core import Context, CoreState, State from homeassistant.loader import bind_hass from homeassistant.setup import async_setup_component From 6a67532a2dc531aadc6fa9514bf376225936f21a Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:07:50 +0100 Subject: [PATCH 137/677] Sort imports according to PEP8 for linky (#29722) --- homeassistant/components/linky/config_flow.py | 2 +- homeassistant/components/linky/sensor.py | 5 ++--- tests/components/linky/test_config_flow.py | 5 +++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/linky/config_flow.py b/homeassistant/components/linky/config_flow.py index 3b882eed2ad..8a2d307ceab 100644 --- a/homeassistant/components/linky/config_flow.py +++ b/homeassistant/components/linky/config_flow.py @@ -1,7 +1,6 @@ """Config flow to configure the Linky integration.""" import logging -import voluptuous as vol from pylinky.client import LinkyClient from pylinky.exceptions import ( PyLinkyAccessException, @@ -9,6 +8,7 @@ from pylinky.exceptions import ( PyLinkyException, PyLinkyWrongLoginException, ) +import voluptuous as vol from homeassistant import config_entries from homeassistant.const import CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME diff --git a/homeassistant/components/linky/sensor.py b/homeassistant/components/linky/sensor.py index 489e66c2b12..4b5f9ab6cad 100644 --- a/homeassistant/components/linky/sensor.py +++ b/homeassistant/components/linky/sensor.py @@ -1,10 +1,9 @@ """Support for Linky.""" +from datetime import timedelta import json import logging -from datetime import timedelta -from pylinky.client import DAILY, MONTHLY, YEARLY, LinkyClient -from pylinky.client import PyLinkyException +from pylinky.client import DAILY, MONTHLY, YEARLY, LinkyClient, PyLinkyException from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( diff --git a/tests/components/linky/test_config_flow.py b/tests/components/linky/test_config_flow.py index f18ce72c1c3..2b90c778a8f 100644 --- a/tests/components/linky/test_config_flow.py +++ b/tests/components/linky/test_config_flow.py @@ -1,16 +1,17 @@ """Tests for the Linky config flow.""" -import pytest from unittest.mock import patch + from pylinky.exceptions import ( PyLinkyAccessException, PyLinkyEnedisException, PyLinkyException, PyLinkyWrongLoginException, ) +import pytest from homeassistant import data_entry_flow from homeassistant.components.linky import config_flow -from homeassistant.components.linky.const import DOMAIN, DEFAULT_TIMEOUT +from homeassistant.components.linky.const import DEFAULT_TIMEOUT, DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME from tests.common import MockConfigEntry From 7128396f1a632494620521f99fc539ee68ce8d28 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:08:29 +0100 Subject: [PATCH 138/677] Sort imports according to PEP8 for minio (#29723) --- homeassistant/components/minio/__init__.py | 4 ++-- homeassistant/components/minio/minio_helper.py | 4 ++-- tests/components/minio/test_minio.py | 14 +++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/minio/__init__.py b/homeassistant/components/minio/__init__.py index d411d913082..4f5159ed9d5 100644 --- a/homeassistant/components/minio/__init__.py +++ b/homeassistant/components/minio/__init__.py @@ -1,8 +1,8 @@ """Minio component.""" import logging import os -import threading from queue import Queue +import threading from typing import List import voluptuous as vol @@ -10,7 +10,7 @@ import voluptuous as vol from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP import homeassistant.helpers.config_validation as cv -from .minio_helper import create_minio_client, MinioEventThread +from .minio_helper import MinioEventThread, create_minio_client _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/minio/minio_helper.py b/homeassistant/components/minio/minio_helper.py index bd7b15d27d4..2aaba9d4085 100644 --- a/homeassistant/components/minio/minio_helper.py +++ b/homeassistant/components/minio/minio_helper.py @@ -1,11 +1,11 @@ """Minio helper methods.""" -import time from collections.abc import Iterable import json import logging +from queue import Queue import re import threading -from queue import Queue +import time from typing import Iterator, List from urllib.parse import unquote diff --git a/tests/components/minio/test_minio.py b/tests/components/minio/test_minio.py index 836b456dc9b..e9b5759097c 100644 --- a/tests/components/minio/test_minio.py +++ b/tests/components/minio/test_minio.py @@ -3,19 +3,19 @@ import asyncio import json from unittest.mock import MagicMock +from asynctest import call, patch import pytest -from asynctest import patch, call from homeassistant.components.minio import ( - QueueListener, - DOMAIN, - CONF_HOST, - CONF_PORT, CONF_ACCESS_KEY, - CONF_SECRET_KEY, - CONF_SECURE, + CONF_HOST, CONF_LISTEN, CONF_LISTEN_BUCKET, + CONF_PORT, + CONF_SECRET_KEY, + CONF_SECURE, + DOMAIN, + QueueListener, ) from homeassistant.core import callback from homeassistant.setup import async_setup_component From c3d7ab6a7fb9011ed456f618d8574694a8c61db7 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:13:37 +0100 Subject: [PATCH 139/677] Sort imports according to PEP8 for netgear_lte (#29725) --- homeassistant/components/netgear_lte/__init__.py | 8 ++++---- homeassistant/components/netgear_lte/notify.py | 2 +- homeassistant/components/netgear_lte/sensor.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/netgear_lte/__init__.py b/homeassistant/components/netgear_lte/__init__.py index 4758a13c391..ac36cc1eb44 100644 --- a/homeassistant/components/netgear_lte/__init__.py +++ b/homeassistant/components/netgear_lte/__init__.py @@ -8,6 +8,9 @@ import attr import eternalegypt import voluptuous as vol +from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN +from homeassistant.components.notify import DOMAIN as NOTIFY_DOMAIN +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.const import ( CONF_HOST, CONF_MONITORED_CONDITIONS, @@ -17,14 +20,11 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP, ) from homeassistant.core import callback -from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN -from homeassistant.components.notify import DOMAIN as NOTIFY_DOMAIN -from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.helpers import config_validation as cv, discovery from homeassistant.helpers.aiohttp_client import async_create_clientsession from homeassistant.helpers.dispatcher import ( - async_dispatcher_send, async_dispatcher_connect, + async_dispatcher_send, ) from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_time_interval diff --git a/homeassistant/components/netgear_lte/notify.py b/homeassistant/components/netgear_lte/notify.py index 9700ee3c715..3e91394aa4f 100644 --- a/homeassistant/components/netgear_lte/notify.py +++ b/homeassistant/components/netgear_lte/notify.py @@ -4,7 +4,7 @@ import logging import attr import eternalegypt -from homeassistant.components.notify import ATTR_TARGET, BaseNotificationService, DOMAIN +from homeassistant.components.notify import ATTR_TARGET, DOMAIN, BaseNotificationService from . import CONF_RECIPIENT, DATA_KEY diff --git a/homeassistant/components/netgear_lte/sensor.py b/homeassistant/components/netgear_lte/sensor.py index 5435df88727..49301a61e4f 100644 --- a/homeassistant/components/netgear_lte/sensor.py +++ b/homeassistant/components/netgear_lte/sensor.py @@ -5,7 +5,7 @@ from homeassistant.components.sensor import DOMAIN from homeassistant.exceptions import PlatformNotReady from . import CONF_MONITORED_CONDITIONS, DATA_KEY, LTEEntity -from .sensor_types import SENSOR_SMS, SENSOR_SMS_TOTAL, SENSOR_USAGE, SENSOR_UNITS +from .sensor_types import SENSOR_SMS, SENSOR_SMS_TOTAL, SENSOR_UNITS, SENSOR_USAGE _LOGGER = logging.getLogger(__name__) From c5316dbc39a73f300a9e8c4e48764222a077ee1c Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:14:08 +0100 Subject: [PATCH 140/677] Sort imports according to PEP8 for iqvia (#29720) --- homeassistant/components/iqvia/__init__.py | 3 +-- homeassistant/components/iqvia/config_flow.py | 2 +- homeassistant/components/iqvia/sensor.py | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/iqvia/__init__.py b/homeassistant/components/iqvia/__init__.py index e3add21c3a4..397cffe6d8c 100644 --- a/homeassistant/components/iqvia/__init__.py +++ b/homeassistant/components/iqvia/__init__.py @@ -4,8 +4,7 @@ from datetime import timedelta import logging from pyiqvia import Client -from pyiqvia.errors import IQVIAError, InvalidZipError - +from pyiqvia.errors import InvalidZipError, IQVIAError import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT diff --git a/homeassistant/components/iqvia/config_flow.py b/homeassistant/components/iqvia/config_flow.py index f6776a833ee..abec8eff09a 100644 --- a/homeassistant/components/iqvia/config_flow.py +++ b/homeassistant/components/iqvia/config_flow.py @@ -1,10 +1,10 @@ """Config flow to configure the IQVIA component.""" from collections import OrderedDict -import voluptuous as vol from pyiqvia import Client from pyiqvia.errors import InvalidZipError +import voluptuous as vol from homeassistant import config_entries from homeassistant.core import callback diff --git a/homeassistant/components/iqvia/sensor.py b/homeassistant/components/iqvia/sensor.py index 90aa89f06d1..21c31bbff08 100644 --- a/homeassistant/components/iqvia/sensor.py +++ b/homeassistant/components/iqvia/sensor.py @@ -9,8 +9,8 @@ from homeassistant.components.iqvia import ( DOMAIN, SENSORS, TYPE_ALLERGY_FORECAST, - TYPE_ALLERGY_OUTLOOK, TYPE_ALLERGY_INDEX, + TYPE_ALLERGY_OUTLOOK, TYPE_ALLERGY_TODAY, TYPE_ALLERGY_TOMORROW, TYPE_ASTHMA_FORECAST, From ebb2722d039b9167b2259ff73b7b1688e75cb39e Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:15:02 +0100 Subject: [PATCH 141/677] Sort imports according to PEP8 for gpslogger (#29717) --- .../components/gpslogger/__init__.py | 19 +++++++++++-------- .../components/gpslogger/config_flow.py | 2 +- .../components/gpslogger/device_tracker.py | 6 +++--- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/gpslogger/__init__.py b/homeassistant/components/gpslogger/__init__.py index 3ac09457d81..aa95d17cbfc 100644 --- a/homeassistant/components/gpslogger/__init__.py +++ b/homeassistant/components/gpslogger/__init__.py @@ -1,30 +1,33 @@ """Support for GPSLogger.""" import logging -import voluptuous as vol from aiohttp import web +import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.components.device_tracker import ATTR_BATTERY +from homeassistant.components.device_tracker import ( + ATTR_BATTERY, + DOMAIN as DEVICE_TRACKER, +) from homeassistant.const import ( - HTTP_UNPROCESSABLE_ENTITY, - HTTP_OK, ATTR_LATITUDE, ATTR_LONGITUDE, CONF_WEBHOOK_ID, + HTTP_OK, + HTTP_UNPROCESSABLE_ENTITY, ) from homeassistant.helpers import config_entry_flow +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send -from homeassistant.components.device_tracker import DOMAIN as DEVICE_TRACKER + from .const import ( - DOMAIN, - ATTR_ALTITUDE, ATTR_ACCURACY, ATTR_ACTIVITY, + ATTR_ALTITUDE, ATTR_DEVICE, ATTR_DIRECTION, ATTR_PROVIDER, ATTR_SPEED, + DOMAIN, ) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/gpslogger/config_flow.py b/homeassistant/components/gpslogger/config_flow.py index 5173c02e7ff..ef90a8d1607 100644 --- a/homeassistant/components/gpslogger/config_flow.py +++ b/homeassistant/components/gpslogger/config_flow.py @@ -1,7 +1,7 @@ """Config flow for GPSLogger.""" from homeassistant.helpers import config_entry_flow -from .const import DOMAIN +from .const import DOMAIN config_entry_flow.register_webhook_flow( DOMAIN, diff --git a/homeassistant/components/gpslogger/device_tracker.py b/homeassistant/components/gpslogger/device_tracker.py index c9dbbfee026..d8afc377d40 100644 --- a/homeassistant/components/gpslogger/device_tracker.py +++ b/homeassistant/components/gpslogger/device_tracker.py @@ -1,15 +1,15 @@ """Support for the GPSLogger device tracking.""" import logging -from homeassistant.core import callback +from homeassistant.components.device_tracker import SOURCE_TYPE_GPS +from homeassistant.components.device_tracker.config_entry import TrackerEntity from homeassistant.const import ( ATTR_BATTERY_LEVEL, ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE, ) -from homeassistant.components.device_tracker import SOURCE_TYPE_GPS -from homeassistant.components.device_tracker.config_entry import TrackerEntity +from homeassistant.core import callback from homeassistant.helpers import device_registry from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.restore_state import RestoreEntity From 6a11e6aa72bf2789fd1b674ab847d9f2dd099ffe Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:19:23 +0100 Subject: [PATCH 142/677] Sort imports according to PEP8 for soma (#29709) --- homeassistant/components/soma/__init__.py | 10 ++++------ homeassistant/components/soma/config_flow.py | 3 ++- homeassistant/components/soma/cover.py | 5 ++--- tests/components/soma/test_config_flow.py | 4 ++-- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/soma/__init__.py b/homeassistant/components/soma/__init__.py index b4daa28b5b2..93ee4fc9b8f 100644 --- a/homeassistant/components/soma/__init__.py +++ b/homeassistant/components/soma/__init__.py @@ -1,20 +1,18 @@ """Support for Soma Smartshades.""" import logging -import voluptuous as vol from api.soma_api import SomaApi from requests import RequestException +import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant import config_entries from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_HOST, CONF_PORT +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import HomeAssistantType -from homeassistant.const import CONF_HOST, CONF_PORT - -from .const import DOMAIN, HOST, PORT, API - +from .const import API, DOMAIN, HOST, PORT DEVICES = "devices" diff --git a/homeassistant/components/soma/config_flow.py b/homeassistant/components/soma/config_flow.py index e2f89273520..f46066e23ca 100644 --- a/homeassistant/components/soma/config_flow.py +++ b/homeassistant/components/soma/config_flow.py @@ -1,12 +1,13 @@ """Config flow for Soma.""" import logging -import voluptuous as vol from api.soma_api import SomaApi from requests import RequestException +import voluptuous as vol from homeassistant import config_entries from homeassistant.const import CONF_HOST, CONF_PORT + from .const import DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/soma/cover.py b/homeassistant/components/soma/cover.py index 1577b7f2911..d23cc9ec5d0 100644 --- a/homeassistant/components/soma/cover.py +++ b/homeassistant/components/soma/cover.py @@ -2,9 +2,8 @@ import logging -from homeassistant.components.cover import CoverDevice, ATTR_POSITION -from homeassistant.components.soma import DOMAIN, SomaEntity, DEVICES, API - +from homeassistant.components.cover import ATTR_POSITION, CoverDevice +from homeassistant.components.soma import API, DEVICES, DOMAIN, SomaEntity _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/soma/test_config_flow.py b/tests/components/soma/test_config_flow.py index 764a18d1b8b..15f90516c17 100644 --- a/tests/components/soma/test_config_flow.py +++ b/tests/components/soma/test_config_flow.py @@ -5,9 +5,9 @@ from api.soma_api import SomaApi from requests import RequestException from homeassistant import data_entry_flow -from homeassistant.components.soma import config_flow, DOMAIN -from tests.common import MockConfigEntry +from homeassistant.components.soma import DOMAIN, config_flow +from tests.common import MockConfigEntry MOCK_HOST = "123.45.67.89" MOCK_PORT = 3000 From 9228ed7c4060148c7b04baa3384705802334b11c Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:21:16 +0100 Subject: [PATCH 143/677] Sort imports according to PEP8 for verisure (#29711) --- homeassistant/components/verisure/__init__.py | 5 ++--- homeassistant/components/verisure/binary_sensor.py | 2 +- tests/components/verisure/test_ethernet_status.py | 2 +- tests/components/verisure/test_lock.py | 8 ++++---- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/verisure/__init__.py b/homeassistant/components/verisure/__init__.py index 3ab98a6560e..32735bf06c1 100644 --- a/homeassistant/components/verisure/__init__.py +++ b/homeassistant/components/verisure/__init__.py @@ -1,11 +1,10 @@ """Support for Verisure devices.""" +from datetime import timedelta import logging import threading -from datetime import timedelta from jsonpath import jsonpath import verisure - import voluptuous as vol from homeassistant.const import ( @@ -15,8 +14,8 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP, ) from homeassistant.helpers import discovery -from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv +from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/verisure/binary_sensor.py b/homeassistant/components/verisure/binary_sensor.py index 47ec3c536b3..bbdd9f54e83 100644 --- a/homeassistant/components/verisure/binary_sensor.py +++ b/homeassistant/components/verisure/binary_sensor.py @@ -2,8 +2,8 @@ import logging from homeassistant.components.binary_sensor import ( - BinarySensorDevice, DEVICE_CLASS_CONNECTIVITY, + BinarySensorDevice, ) from . import CONF_DOOR_WINDOW, HUB as hub diff --git a/tests/components/verisure/test_ethernet_status.py b/tests/components/verisure/test_ethernet_status.py index 71c7df94ae5..611adde19d9 100644 --- a/tests/components/verisure/test_ethernet_status.py +++ b/tests/components/verisure/test_ethernet_status.py @@ -2,9 +2,9 @@ from contextlib import contextmanager from unittest.mock import patch +from homeassistant.components.verisure import DOMAIN as VERISURE_DOMAIN from homeassistant.const import STATE_UNAVAILABLE from homeassistant.setup import async_setup_component -from homeassistant.components.verisure import DOMAIN as VERISURE_DOMAIN CONFIG = { "verisure": { diff --git a/tests/components/verisure/test_lock.py b/tests/components/verisure/test_lock.py index ac03e0d9fb6..2f69c183d7d 100644 --- a/tests/components/verisure/test_lock.py +++ b/tests/components/verisure/test_lock.py @@ -1,16 +1,16 @@ """Tests for the Verisure platform.""" from contextlib import contextmanager -from unittest.mock import patch, call -from homeassistant.const import STATE_UNLOCKED -from homeassistant.setup import async_setup_component +from unittest.mock import call, patch + from homeassistant.components.lock import ( DOMAIN as LOCK_DOMAIN, SERVICE_LOCK, SERVICE_UNLOCK, ) from homeassistant.components.verisure import DOMAIN as VERISURE_DOMAIN - +from homeassistant.const import STATE_UNLOCKED +from homeassistant.setup import async_setup_component NO_DEFAULT_LOCK_CODE_CONFIG = { "verisure": { From f9e9a5e4cb9e3423825467630b374db3aec7a677 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:22:04 +0100 Subject: [PATCH 144/677] Sort imports according to PEP8 for darksky (#29706) --- homeassistant/components/darksky/sensor.py | 8 ++++---- tests/components/darksky/test_sensor.py | 9 ++++----- tests/components/darksky/test_weather.py | 7 +++---- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/darksky/sensor.py b/homeassistant/components/darksky/sensor.py index 82aaccc9590..5b6da5d11bb 100644 --- a/homeassistant/components/darksky/sensor.py +++ b/homeassistant/components/darksky/sensor.py @@ -1,12 +1,11 @@ """Support for Dark Sky weather service.""" -import logging from datetime import timedelta +import logging import forecastio -import voluptuous as vol from requests.exceptions import ConnectionError as ConnectError, HTTPError, Timeout +import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( ATTR_ATTRIBUTION, @@ -15,9 +14,10 @@ from homeassistant.const import ( CONF_LONGITUDE, CONF_MONITORED_CONDITIONS, CONF_NAME, - UNIT_UV_INDEX, CONF_SCAN_INTERVAL, + UNIT_UV_INDEX, ) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle diff --git a/tests/components/darksky/test_sensor.py b/tests/components/darksky/test_sensor.py index be66b74c186..bb716ed17ec 100644 --- a/tests/components/darksky/test_sensor.py +++ b/tests/components/darksky/test_sensor.py @@ -1,18 +1,17 @@ """The tests for the Dark Sky platform.""" +from datetime import timedelta import re import unittest from unittest.mock import MagicMock, patch -from datetime import timedelta - -from requests.exceptions import HTTPError -import requests_mock import forecastio +from requests.exceptions import HTTPError +import requests_mock from homeassistant.components.darksky import sensor as darksky from homeassistant.setup import setup_component -from tests.common import load_fixture, get_test_home_assistant, MockDependency +from tests.common import MockDependency, get_test_home_assistant, load_fixture VALID_CONFIG_MINIMAL = { "sensor": { diff --git a/tests/components/darksky/test_weather.py b/tests/components/darksky/test_weather.py index ca328f45839..09ffe7bdc90 100644 --- a/tests/components/darksky/test_weather.py +++ b/tests/components/darksky/test_weather.py @@ -4,15 +4,14 @@ import unittest from unittest.mock import patch import forecastio +from requests.exceptions import ConnectionError import requests_mock -from requests.exceptions import ConnectionError - from homeassistant.components import weather -from homeassistant.util.unit_system import METRIC_SYSTEM from homeassistant.setup import setup_component +from homeassistant.util.unit_system import METRIC_SYSTEM -from tests.common import load_fixture, get_test_home_assistant +from tests.common import get_test_home_assistant, load_fixture class TestDarkSky(unittest.TestCase): From ea39d5b42868db6c5041374f92fbc29deceb7753 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:23:21 +0100 Subject: [PATCH 145/677] Sort imports according to PEP8 for aws (#29704) --- homeassistant/components/aws/__init__.py | 3 +-- homeassistant/components/aws/notify.py | 3 ++- tests/components/aws/test_init.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/aws/__init__.py b/homeassistant/components/aws/__init__.py index b553b7eafd6..600874b0d25 100644 --- a/homeassistant/components/aws/__init__.py +++ b/homeassistant/components/aws/__init__.py @@ -1,10 +1,9 @@ """Support for Amazon Web Services (AWS).""" import asyncio -import logging from collections import OrderedDict +import logging import aiobotocore - import voluptuous as vol from homeassistant import config_entries diff --git a/homeassistant/components/aws/notify.py b/homeassistant/components/aws/notify.py index 2afa9a3a402..13fa189a318 100644 --- a/homeassistant/components/aws/notify.py +++ b/homeassistant/components/aws/notify.py @@ -12,8 +12,9 @@ from homeassistant.components.notify import ( ATTR_TITLE_DEFAULT, BaseNotificationService, ) -from homeassistant.const import CONF_PLATFORM, CONF_NAME +from homeassistant.const import CONF_NAME, CONF_PLATFORM from homeassistant.helpers.json import JSONEncoder + from .const import ( CONF_CONTEXT, CONF_CREDENTIAL_NAME, diff --git a/tests/components/aws/test_init.py b/tests/components/aws/test_init.py index a9701ec7ff9..c7fa9d0a5c1 100644 --- a/tests/components/aws/test_init.py +++ b/tests/components/aws/test_init.py @@ -1,5 +1,5 @@ """Tests for the aws component config and setup.""" -from asynctest import patch as async_patch, MagicMock, CoroutineMock +from asynctest import CoroutineMock, MagicMock, patch as async_patch from homeassistant.components import aws from homeassistant.setup import async_setup_component From 2cd55bbb87bd5c537f1f3eada86a44ea1cd33424 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:24:49 +0100 Subject: [PATCH 146/677] Sort imports according to PEP8 for device_automation (#29707) --- homeassistant/components/device_automation/__init__.py | 7 +++---- .../components/device_automation/toggle_entity.py | 9 +++++---- tests/components/device_automation/test_init.py | 5 ++--- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/device_automation/__init__.py b/homeassistant/components/device_automation/__init__.py index 80e64033295..872a4af6cd6 100644 --- a/homeassistant/components/device_automation/__init__.py +++ b/homeassistant/components/device_automation/__init__.py @@ -1,22 +1,21 @@ """Helpers for device automations.""" import asyncio import logging -from typing import Any, List, MutableMapping from types import ModuleType +from typing import Any, List, MutableMapping import voluptuous as vol import voluptuous_serialize -from homeassistant.const import CONF_PLATFORM, CONF_DOMAIN, CONF_DEVICE_ID from homeassistant.components import websocket_api +from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_PLATFORM from homeassistant.core import HomeAssistant from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity_registry import async_entries_for_device -from homeassistant.loader import async_get_integration, IntegrationNotFound +from homeassistant.loader import IntegrationNotFound, async_get_integration from .exceptions import InvalidDeviceAutomationConfig - # mypy: allow-untyped-calls, allow-untyped-defs DOMAIN = "device_automation" diff --git a/homeassistant/components/device_automation/toggle_entity.py b/homeassistant/components/device_automation/toggle_entity.py index 5f01f4d9d71..7d84eb921e9 100644 --- a/homeassistant/components/device_automation/toggle_entity.py +++ b/homeassistant/components/device_automation/toggle_entity.py @@ -1,11 +1,11 @@ """Device automation helpers for toggle entity.""" from typing import Any, Dict, List + import voluptuous as vol -from homeassistant.core import Context, HomeAssistant, CALLBACK_TYPE from homeassistant.components.automation import ( - state as state_automation, AutomationActionType, + state as state_automation, ) from homeassistant.components.device_automation.const import ( CONF_IS_OFF, @@ -24,11 +24,12 @@ from homeassistant.const import ( CONF_PLATFORM, CONF_TYPE, ) -from homeassistant.helpers.entity_registry import async_entries_for_device +from homeassistant.core import CALLBACK_TYPE, Context, HomeAssistant from homeassistant.helpers import condition, config_validation as cv +from homeassistant.helpers.entity_registry import async_entries_for_device from homeassistant.helpers.typing import ConfigType, TemplateVarsType -from . import TRIGGER_BASE_SCHEMA +from . import TRIGGER_BASE_SCHEMA # mypy: allow-untyped-calls, allow-untyped-defs diff --git a/tests/components/device_automation/test_init.py b/tests/components/device_automation/test_init.py index bddef3286ac..5d997a485a5 100644 --- a/tests/components/device_automation/test_init.py +++ b/tests/components/device_automation/test_init.py @@ -1,12 +1,11 @@ """The test for light device automation.""" import pytest -from homeassistant.setup import async_setup_component import homeassistant.components.automation as automation from homeassistant.components.websocket_api.const import TYPE_RESULT -from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM +from homeassistant.const import CONF_PLATFORM, STATE_OFF, STATE_ON from homeassistant.helpers import device_registry - +from homeassistant.setup import async_setup_component from tests.common import ( MockConfigEntry, From 852996700fcb3533b2c7d757f07d0f0b095c7356 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:25:29 +0100 Subject: [PATCH 147/677] Sort imports according to PEP8 for arest (#29703) --- homeassistant/components/arest/binary_sensor.py | 10 +++++----- homeassistant/components/arest/sensor.py | 10 +++++----- homeassistant/components/arest/switch.py | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/arest/binary_sensor.py b/homeassistant/components/arest/binary_sensor.py index 669a28b7078..caabe3333f8 100644 --- a/homeassistant/components/arest/binary_sensor.py +++ b/homeassistant/components/arest/binary_sensor.py @@ -1,18 +1,18 @@ """Support for an exposed aREST RESTful API of a device.""" -import logging from datetime import timedelta +import logging import requests import voluptuous as vol from homeassistant.components.binary_sensor import ( - BinarySensorDevice, - PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA, + PLATFORM_SCHEMA, + BinarySensorDevice, ) -from homeassistant.const import CONF_RESOURCE, CONF_PIN, CONF_NAME, CONF_DEVICE_CLASS -from homeassistant.util import Throttle +from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME, CONF_PIN, CONF_RESOURCE import homeassistant.helpers.config_validation as cv +from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/arest/sensor.py b/homeassistant/components/arest/sensor.py index 2416eeb0ebb..270a3cda269 100644 --- a/homeassistant/components/arest/sensor.py +++ b/homeassistant/components/arest/sensor.py @@ -1,22 +1,22 @@ """Support for an exposed aREST RESTful API of a device.""" -import logging from datetime import timedelta +import logging import requests import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_UNIT_OF_MEASUREMENT, - CONF_VALUE_TEMPLATE, - CONF_RESOURCE, CONF_MONITORED_VARIABLES, CONF_NAME, + CONF_RESOURCE, + CONF_UNIT_OF_MEASUREMENT, + CONF_VALUE_TEMPLATE, ) from homeassistant.exceptions import TemplateError +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle -import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/arest/switch.py b/homeassistant/components/arest/switch.py index e1a7edacb7e..b3db6684cf2 100644 --- a/homeassistant/components/arest/switch.py +++ b/homeassistant/components/arest/switch.py @@ -5,7 +5,7 @@ import logging import requests import voluptuous as vol -from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice from homeassistant.const import CONF_NAME, CONF_RESOURCE import homeassistant.helpers.config_validation as cv From 69f790f6cc0fafd5fff677f47225725d27a8b149 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:26:03 +0100 Subject: [PATCH 148/677] Sort imports according to PEP8 for arduino (#29702) --- homeassistant/components/arduino/__init__.py | 10 ++++++---- homeassistant/components/arduino/sensor.py | 4 ++-- homeassistant/components/arduino/switch.py | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/arduino/__init__.py b/homeassistant/components/arduino/__init__.py index f973ec136e3..61b03a3160d 100644 --- a/homeassistant/components/arduino/__init__.py +++ b/homeassistant/components/arduino/__init__.py @@ -1,13 +1,15 @@ """Support for Arduino boards running with the Firmata firmware.""" import logging +from PyMata.pymata import PyMata import serial import voluptuous as vol -from PyMata.pymata import PyMata - -from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP -from homeassistant.const import CONF_PORT +from homeassistant.const import ( + CONF_PORT, + EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP, +) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/arduino/sensor.py b/homeassistant/components/arduino/sensor.py index a92432537ca..c5863475512 100644 --- a/homeassistant/components/arduino/sensor.py +++ b/homeassistant/components/arduino/sensor.py @@ -3,11 +3,11 @@ import logging import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.components import arduino +from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_NAME -from homeassistant.helpers.entity import Entity import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/arduino/switch.py b/homeassistant/components/arduino/switch.py index 63d83c8575e..5b5b161a24a 100644 --- a/homeassistant/components/arduino/switch.py +++ b/homeassistant/components/arduino/switch.py @@ -4,7 +4,7 @@ import logging import voluptuous as vol from homeassistant.components import arduino -from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv From 790881fa7ba99a638a9c9921b8cd8214cc9449a0 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:27:16 +0100 Subject: [PATCH 149/677] Sort imports according to PEP8 for almond (#29688) --- homeassistant/components/almond/__init__.py | 20 +++++++++---------- .../components/almond/config_flow.py | 10 +++++----- tests/components/almond/test_config_flow.py | 6 ++---- tests/components/almond/test_init.py | 6 +++--- 4 files changed, 20 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/almond/__init__.py b/homeassistant/components/almond/__init__.py index 66d4b2fc9af..8877107b984 100644 --- a/homeassistant/components/almond/__init__.py +++ b/homeassistant/components/almond/__init__.py @@ -5,26 +5,26 @@ import logging import time from typing import Optional +from aiohttp import ClientError, ClientSession import async_timeout -from aiohttp import ClientSession, ClientError -from pyalmond import AlmondLocalAuth, AbstractAlmondWebAuth, WebAlmondAPI +from pyalmond import AbstractAlmondWebAuth, AlmondLocalAuth, WebAlmondAPI import voluptuous as vol -from homeassistant.core import HomeAssistant, CoreState, Context -from homeassistant.const import CONF_TYPE, CONF_HOST, EVENT_HOMEASSISTANT_START -from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant import config_entries from homeassistant.auth.const import GROUP_ID_ADMIN +from homeassistant.components import conversation +from homeassistant.const import CONF_HOST, CONF_TYPE, EVENT_HOMEASSISTANT_START +from homeassistant.core import Context, CoreState, HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import ( - config_validation as cv, + aiohttp_client, config_entry_oauth2_flow, + config_validation as cv, event, intent, - aiohttp_client, - storage, network, + storage, ) -from homeassistant import config_entries -from homeassistant.components import conversation from . import config_flow from .const import DOMAIN, TYPE_LOCAL, TYPE_OAUTH2 diff --git a/homeassistant/components/almond/config_flow.py b/homeassistant/components/almond/config_flow.py index d79bf6bd605..42f9318a06f 100644 --- a/homeassistant/components/almond/config_flow.py +++ b/homeassistant/components/almond/config_flow.py @@ -2,14 +2,14 @@ import asyncio import logging -import async_timeout from aiohttp import ClientError -from yarl import URL -import voluptuous as vol +import async_timeout from pyalmond import AlmondLocalAuth, WebAlmondAPI +import voluptuous as vol +from yarl import URL -from homeassistant import data_entry_flow, config_entries, core -from homeassistant.helpers import config_entry_oauth2_flow, aiohttp_client +from homeassistant import config_entries, core, data_entry_flow +from homeassistant.helpers import aiohttp_client, config_entry_oauth2_flow from .const import DOMAIN, TYPE_LOCAL, TYPE_OAUTH2 diff --git a/tests/components/almond/test_config_flow.py b/tests/components/almond/test_config_flow.py index afbe25dff5f..0b402ed407d 100644 --- a/tests/components/almond/test_config_flow.py +++ b/tests/components/almond/test_config_flow.py @@ -1,12 +1,10 @@ """Test the Almond config flow.""" import asyncio - from unittest.mock import patch - -from homeassistant import config_entries, setup, data_entry_flow -from homeassistant.components.almond.const import DOMAIN +from homeassistant import config_entries, data_entry_flow, setup from homeassistant.components.almond import config_flow +from homeassistant.components.almond.const import DOMAIN from homeassistant.helpers import config_entry_oauth2_flow from tests.common import MockConfigEntry, mock_coro diff --git a/tests/components/almond/test_init.py b/tests/components/almond/test_init.py index dd44ea1c8f0..f13ad7dd859 100644 --- a/tests/components/almond/test_init.py +++ b/tests/components/almond/test_init.py @@ -1,16 +1,16 @@ """Tests for Almond set up.""" -from unittest.mock import patch from time import time +from unittest.mock import patch import pytest from homeassistant import config_entries, core +from homeassistant.components.almond import const from homeassistant.const import EVENT_HOMEASSISTANT_START from homeassistant.setup import async_setup_component -from homeassistant.components.almond import const from homeassistant.util.dt import utcnow -from tests.common import MockConfigEntry, mock_coro, async_fire_time_changed +from tests.common import MockConfigEntry, async_fire_time_changed, mock_coro @pytest.fixture(autouse=True) From 425a1814d9dd4d93a71458a1ed57233604f2f64a Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:28:07 +0100 Subject: [PATCH 150/677] Sort imports according to PEP8 for geonetnz_quakes (#29668) --- .../components/geonetnz_quakes/__init__.py | 14 +++++----- .../components/geonetnz_quakes/config_flow.py | 4 +-- .../geonetnz_quakes/geo_location.py | 2 +- .../geonetnz_quakes/test_config_flow.py | 13 ++++----- .../geonetnz_quakes/test_geo_location.py | 27 ++++++++++--------- .../components/geonetnz_quakes/test_sensor.py | 19 ++++++------- 6 files changed, 41 insertions(+), 38 deletions(-) diff --git a/homeassistant/components/geonetnz_quakes/__init__.py b/homeassistant/components/geonetnz_quakes/__init__.py index 069c9ab7daa..141d0506847 100644 --- a/homeassistant/components/geonetnz_quakes/__init__.py +++ b/homeassistant/components/geonetnz_quakes/__init__.py @@ -1,30 +1,29 @@ """The GeoNet NZ Quakes integration.""" import asyncio -import logging from datetime import timedelta +import logging -import voluptuous as vol from aio_geojson_geonetnz_quakes import GeonetnzQuakesFeedManager +import voluptuous as vol -from homeassistant.core import callback -from homeassistant.util.unit_system import METRIC_SYSTEM from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( CONF_LATITUDE, CONF_LONGITUDE, CONF_RADIUS, CONF_SCAN_INTERVAL, - CONF_UNIT_SYSTEM_IMPERIAL, CONF_UNIT_SYSTEM, + CONF_UNIT_SYSTEM_IMPERIAL, LENGTH_MILES, ) -from homeassistant.helpers import config_validation as cv, aiohttp_client +from homeassistant.core import callback +from homeassistant.helpers import aiohttp_client, config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_track_time_interval +from homeassistant.util.unit_system import METRIC_SYSTEM from .config_flow import configured_instances from .const import ( - PLATFORMS, CONF_MINIMUM_MAGNITUDE, CONF_MMI, DEFAULT_FILTER_TIME_INTERVAL, @@ -34,6 +33,7 @@ from .const import ( DEFAULT_SCAN_INTERVAL, DOMAIN, FEED, + PLATFORMS, SIGNAL_DELETE_ENTITY, SIGNAL_NEW_GEOLOCATION, SIGNAL_STATUS, diff --git a/homeassistant/components/geonetnz_quakes/config_flow.py b/homeassistant/components/geonetnz_quakes/config_flow.py index bd93f08c72b..cc40f31f1fb 100644 --- a/homeassistant/components/geonetnz_quakes/config_flow.py +++ b/homeassistant/components/geonetnz_quakes/config_flow.py @@ -17,13 +17,13 @@ from homeassistant.core import callback from homeassistant.helpers import config_validation as cv from .const import ( + CONF_MINIMUM_MAGNITUDE, CONF_MMI, + DEFAULT_MINIMUM_MAGNITUDE, DEFAULT_MMI, DEFAULT_RADIUS, DEFAULT_SCAN_INTERVAL, DOMAIN, - DEFAULT_MINIMUM_MAGNITUDE, - CONF_MINIMUM_MAGNITUDE, ) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/geonetnz_quakes/geo_location.py b/homeassistant/components/geonetnz_quakes/geo_location.py index 1ee7c287c61..ae8b8fef48d 100644 --- a/homeassistant/components/geonetnz_quakes/geo_location.py +++ b/homeassistant/components/geonetnz_quakes/geo_location.py @@ -5,10 +5,10 @@ from typing import Optional from homeassistant.components.geo_location import GeolocationEvent from homeassistant.const import ( ATTR_ATTRIBUTION, + ATTR_TIME, CONF_UNIT_SYSTEM_IMPERIAL, LENGTH_KILOMETERS, LENGTH_MILES, - ATTR_TIME, ) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect diff --git a/tests/components/geonetnz_quakes/test_config_flow.py b/tests/components/geonetnz_quakes/test_config_flow.py index 2d8e3750648..494ceaa542d 100644 --- a/tests/components/geonetnz_quakes/test_config_flow.py +++ b/tests/components/geonetnz_quakes/test_config_flow.py @@ -1,26 +1,27 @@ """Define tests for the GeoNet NZ Quakes config flow.""" from datetime import timedelta +from asynctest import CoroutineMock, patch import pytest -from asynctest import patch, CoroutineMock from homeassistant import data_entry_flow from homeassistant.components.geonetnz_quakes import ( - async_setup_entry, - config_flow, - CONF_MMI, CONF_MINIMUM_MAGNITUDE, + CONF_MMI, DOMAIN, - async_unload_entry, FEED, + async_setup_entry, + async_unload_entry, + config_flow, ) from homeassistant.const import ( CONF_LATITUDE, CONF_LONGITUDE, CONF_RADIUS, - CONF_UNIT_SYSTEM, CONF_SCAN_INTERVAL, + CONF_UNIT_SYSTEM, ) + from tests.common import MockConfigEntry diff --git a/tests/components/geonetnz_quakes/test_geo_location.py b/tests/components/geonetnz_quakes/test_geo_location.py index 04bbdc9dcf0..0132a07c745 100644 --- a/tests/components/geonetnz_quakes/test_geo_location.py +++ b/tests/components/geonetnz_quakes/test_geo_location.py @@ -1,34 +1,35 @@ """The tests for the GeoNet NZ Quakes Feed integration.""" import datetime -from asynctest import patch, CoroutineMock +from asynctest import CoroutineMock, patch from homeassistant.components import geonetnz_quakes from homeassistant.components.geo_location import ATTR_SOURCE from homeassistant.components.geonetnz_quakes import DEFAULT_SCAN_INTERVAL from homeassistant.components.geonetnz_quakes.geo_location import ( - ATTR_EXTERNAL_ID, - ATTR_MAGNITUDE, - ATTR_LOCALITY, - ATTR_MMI, ATTR_DEPTH, + ATTR_EXTERNAL_ID, + ATTR_LOCALITY, + ATTR_MAGNITUDE, + ATTR_MMI, ATTR_QUALITY, ) from homeassistant.const import ( - EVENT_HOMEASSISTANT_START, - CONF_RADIUS, + ATTR_ATTRIBUTION, + ATTR_FRIENDLY_NAME, + ATTR_ICON, ATTR_LATITUDE, ATTR_LONGITUDE, - ATTR_FRIENDLY_NAME, - ATTR_UNIT_OF_MEASUREMENT, - ATTR_ATTRIBUTION, ATTR_TIME, - ATTR_ICON, + ATTR_UNIT_OF_MEASUREMENT, + CONF_RADIUS, + EVENT_HOMEASSISTANT_START, ) from homeassistant.setup import async_setup_component -from homeassistant.util.unit_system import IMPERIAL_SYSTEM -from tests.common import async_fire_time_changed import homeassistant.util.dt as dt_util +from homeassistant.util.unit_system import IMPERIAL_SYSTEM + +from tests.common import async_fire_time_changed from tests.components.geonetnz_quakes import _generate_mock_feed_entry CONFIG = {geonetnz_quakes.DOMAIN: {CONF_RADIUS: 200}} diff --git a/tests/components/geonetnz_quakes/test_sensor.py b/tests/components/geonetnz_quakes/test_sensor.py index 518e08f02bb..aecd012ba1c 100644 --- a/tests/components/geonetnz_quakes/test_sensor.py +++ b/tests/components/geonetnz_quakes/test_sensor.py @@ -1,27 +1,28 @@ """The tests for the GeoNet NZ Quakes Feed integration.""" import datetime -from asynctest import patch, CoroutineMock +from asynctest import CoroutineMock, patch from homeassistant.components import geonetnz_quakes from homeassistant.components.geonetnz_quakes import DEFAULT_SCAN_INTERVAL from homeassistant.components.geonetnz_quakes.sensor import ( - ATTR_STATUS, - ATTR_LAST_UPDATE, ATTR_CREATED, - ATTR_UPDATED, - ATTR_REMOVED, + ATTR_LAST_UPDATE, ATTR_LAST_UPDATE_SUCCESSFUL, + ATTR_REMOVED, + ATTR_STATUS, + ATTR_UPDATED, ) from homeassistant.const import ( - EVENT_HOMEASSISTANT_START, - CONF_RADIUS, - ATTR_UNIT_OF_MEASUREMENT, ATTR_ICON, + ATTR_UNIT_OF_MEASUREMENT, + CONF_RADIUS, + EVENT_HOMEASSISTANT_START, ) from homeassistant.setup import async_setup_component -from tests.common import async_fire_time_changed import homeassistant.util.dt as dt_util + +from tests.common import async_fire_time_changed from tests.components.geonetnz_quakes import _generate_mock_feed_entry CONFIG = {geonetnz_quakes.DOMAIN: {CONF_RADIUS: 200}} From 202522fbca4b7d668e64dfde95a3b3c0a5fc29f4 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Mon, 9 Dec 2019 11:29:36 +0100 Subject: [PATCH 151/677] Move imports to top for nsw_fuel_station (#29538) * Move imports to top for nsw_fuel_station * Correct patch path in test_sensor.py * Fix tests by removing the unused argument mock_nsw_fuel --- .../components/nsw_fuel_station/sensor.py | 5 ++--- .../components/nsw_fuel_station/test_sensor.py | 18 +++++++++++------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/nsw_fuel_station/sensor.py b/homeassistant/components/nsw_fuel_station/sensor.py index 3c900b46be0..b4cd7bd161e 100644 --- a/homeassistant/components/nsw_fuel_station/sensor.py +++ b/homeassistant/components/nsw_fuel_station/sensor.py @@ -3,11 +3,12 @@ import datetime import logging from typing import Optional +from nsw_fuel import FuelCheckClient, FuelCheckError import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ATTR_ATTRIBUTION +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle @@ -52,7 +53,6 @@ NOTIFICATION_TITLE = "NSW Fuel Station Sensor Setup" def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the NSW Fuel Station sensor.""" - from nsw_fuel import FuelCheckClient station_id = config[CONF_STATION_ID] fuel_types = config[CONF_FUEL_TYPES] @@ -97,7 +97,6 @@ class StationPriceData: @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): """Update the internal data using the API client.""" - from nsw_fuel import FuelCheckError if self._reference_data is None: try: diff --git a/tests/components/nsw_fuel_station/test_sensor.py b/tests/components/nsw_fuel_station/test_sensor.py index c65c1fe5091..4f1753c0518 100644 --- a/tests/components/nsw_fuel_station/test_sensor.py +++ b/tests/components/nsw_fuel_station/test_sensor.py @@ -4,7 +4,7 @@ from unittest.mock import patch from homeassistant.components import sensor from homeassistant.setup import setup_component -from tests.common import get_test_home_assistant, assert_setup_component, MockDependency +from tests.common import get_test_home_assistant, assert_setup_component VALID_CONFIG = { "platform": "nsw_fuel_station", @@ -83,9 +83,11 @@ class TestNSWFuelStation(unittest.TestCase): """Stop everything that was started.""" self.hass.stop() - @MockDependency("nsw_fuel") - @patch("nsw_fuel.FuelCheckClient", new=FuelCheckClientMock) - def test_setup(self, mock_nsw_fuel): + @patch( + "homeassistant.components.nsw_fuel_station.sensor.FuelCheckClient", + new=FuelCheckClientMock, + ) + def test_setup(self): """Test the setup with custom settings.""" with assert_setup_component(1, sensor.DOMAIN): assert setup_component(self.hass, sensor.DOMAIN, {"sensor": VALID_CONFIG}) @@ -96,9 +98,11 @@ class TestNSWFuelStation(unittest.TestCase): state = self.hass.states.get("sensor.{}".format(entity_id)) assert state is not None - @MockDependency("nsw_fuel") - @patch("nsw_fuel.FuelCheckClient", new=FuelCheckClientMock) - def test_sensor_values(self, mock_nsw_fuel): + @patch( + "homeassistant.components.nsw_fuel_station.sensor.FuelCheckClient", + new=FuelCheckClientMock, + ) + def test_sensor_values(self): """Test retrieval of sensor values.""" assert setup_component(self.hass, sensor.DOMAIN, {"sensor": VALID_CONFIG}) From 0fbb450838aa78185186c550d66b78715ecd1f77 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:30:31 +0100 Subject: [PATCH 152/677] Sort imports according to PEP8 for jewish_calendar (#29697) * use isort to sort imports according to PEP8 for jewish_calendar * fix order somehow isort did the wrong thing --- homeassistant/components/jewish_calendar/__init__.py | 5 ++--- tests/components/jewish_calendar/__init__.py | 3 +-- .../components/jewish_calendar/test_binary_sensor.py | 11 +++++------ tests/components/jewish_calendar/test_sensor.py | 10 +++++----- 4 files changed, 13 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/jewish_calendar/__init__.py b/homeassistant/components/jewish_calendar/__init__.py index bbe0c1d24fd..21c19da7b35 100644 --- a/homeassistant/components/jewish_calendar/__init__.py +++ b/homeassistant/components/jewish_calendar/__init__.py @@ -1,13 +1,12 @@ """The jewish_calendar component.""" import logging -import voluptuous as vol import hdate +import voluptuous as vol from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME -from homeassistant.helpers.discovery import async_load_platform import homeassistant.helpers.config_validation as cv - +from homeassistant.helpers.discovery import async_load_platform _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/jewish_calendar/__init__.py b/tests/components/jewish_calendar/__init__.py index 54589a640cc..592086461d3 100644 --- a/tests/components/jewish_calendar/__init__.py +++ b/tests/components/jewish_calendar/__init__.py @@ -1,13 +1,12 @@ """Tests for the jewish_calendar component.""" -from datetime import datetime from collections import namedtuple from contextlib import contextmanager +from datetime import datetime from unittest.mock import patch from homeassistant.components import jewish_calendar import homeassistant.util.dt as dt_util - _LatLng = namedtuple("_LatLng", ["lat", "lng"]) NYC_LATLNG = _LatLng(40.7128, -74.0060) diff --git a/tests/components/jewish_calendar/test_binary_sensor.py b/tests/components/jewish_calendar/test_binary_sensor.py index 64745d8929f..0daa7c68993 100644 --- a/tests/components/jewish_calendar/test_binary_sensor.py +++ b/tests/components/jewish_calendar/test_binary_sensor.py @@ -1,17 +1,16 @@ """The tests for the Jewish calendar binary sensors.""" -from datetime import timedelta -from datetime import datetime as dt +from datetime import datetime as dt, timedelta import pytest -from homeassistant.const import STATE_ON, STATE_OFF -import homeassistant.util.dt as dt_util -from homeassistant.setup import async_setup_component from homeassistant.components import jewish_calendar +from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.setup import async_setup_component +import homeassistant.util.dt as dt_util from tests.common import async_fire_time_changed -from . import alter_time, make_nyc_test_params, make_jerusalem_test_params +from . import alter_time, make_jerusalem_test_params, make_nyc_test_params MELACHA_PARAMS = [ make_nyc_test_params(dt(2018, 9, 1, 16, 0), STATE_ON), diff --git a/tests/components/jewish_calendar/test_sensor.py b/tests/components/jewish_calendar/test_sensor.py index 60ffdea8c70..7ed3fdccb62 100644 --- a/tests/components/jewish_calendar/test_sensor.py +++ b/tests/components/jewish_calendar/test_sensor.py @@ -1,15 +1,15 @@ """The tests for the Jewish calendar sensors.""" -from datetime import timedelta -from datetime import datetime as dt +from datetime import datetime as dt, timedelta import pytest -import homeassistant.util.dt as dt_util -from homeassistant.setup import async_setup_component from homeassistant.components import jewish_calendar +from homeassistant.setup import async_setup_component +import homeassistant.util.dt as dt_util + from tests.common import async_fire_time_changed -from . import alter_time, make_nyc_test_params, make_jerusalem_test_params +from . import alter_time, make_jerusalem_test_params, make_nyc_test_params async def test_jewish_calendar_min_config(hass): From 0e71c29e008fbb5ea62c2381637378ca6fe14936 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:34:06 +0100 Subject: [PATCH 153/677] Sort imports according to PEP8 for opentherm_gw (#29671) --- homeassistant/components/opentherm_gw/__init__.py | 12 +++++------- .../components/opentherm_gw/binary_sensor.py | 1 - homeassistant/components/opentherm_gw/climate.py | 5 ++--- homeassistant/components/opentherm_gw/config_flow.py | 3 +-- homeassistant/components/opentherm_gw/sensor.py | 1 - tests/components/opentherm_gw/test_config_flow.py | 11 ++++++----- 6 files changed, 14 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/opentherm_gw/__init__.py b/homeassistant/components/opentherm_gw/__init__.py index 3a1255e3697..c6cf14bfdce 100644 --- a/homeassistant/components/opentherm_gw/__init__.py +++ b/homeassistant/components/opentherm_gw/__init__.py @@ -1,16 +1,16 @@ """Support for OpenTherm Gateway devices.""" import asyncio +from datetime import date, datetime import logging -from datetime import datetime, date import pyotgw import pyotgw.vars as gw_vars import voluptuous as vol -from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.components.binary_sensor import DOMAIN as COMP_BINARY_SENSOR from homeassistant.components.climate import DOMAIN as COMP_CLIMATE from homeassistant.components.sensor import DOMAIN as COMP_SENSOR +from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( ATTR_DATE, ATTR_ID, @@ -25,14 +25,13 @@ from homeassistant.const import ( PRECISION_TENTHS, PRECISION_WHOLE, ) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send -import homeassistant.helpers.config_validation as cv - from .const import ( + ATTR_DHW_OVRD, ATTR_GW_ID, ATTR_LEVEL, - ATTR_DHW_OVRD, CONF_CLIMATE, CONF_FLOOR_TEMP, CONF_PRECISION, @@ -42,15 +41,14 @@ from .const import ( SERVICE_RESET_GATEWAY, SERVICE_SET_CLOCK, SERVICE_SET_CONTROL_SETPOINT, - SERVICE_SET_HOT_WATER_OVRD, SERVICE_SET_GPIO_MODE, + SERVICE_SET_HOT_WATER_OVRD, SERVICE_SET_LED_MODE, SERVICE_SET_MAX_MOD, SERVICE_SET_OAT, SERVICE_SET_SB_TEMP, ) - _LOGGER = logging.getLogger(__name__) CLIMATE_SCHEMA = vol.Schema( diff --git a/homeassistant/components/opentherm_gw/binary_sensor.py b/homeassistant/components/opentherm_gw/binary_sensor.py index 39fd78f5fe8..eff11554a39 100644 --- a/homeassistant/components/opentherm_gw/binary_sensor.py +++ b/homeassistant/components/opentherm_gw/binary_sensor.py @@ -10,7 +10,6 @@ from homeassistant.helpers.entity import async_generate_entity_id from . import DOMAIN from .const import BINARY_SENSOR_INFO, DATA_GATEWAYS, DATA_OPENTHERM_GW - _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/opentherm_gw/climate.py b/homeassistant/components/opentherm_gw/climate.py index 8c21c6560c1..2db20662a77 100644 --- a/homeassistant/components/opentherm_gw/climate.py +++ b/homeassistant/components/opentherm_gw/climate.py @@ -3,17 +3,17 @@ import logging from pyotgw import vars as gw_vars -from homeassistant.components.climate import ClimateDevice, ENTITY_ID_FORMAT +from homeassistant.components.climate import ENTITY_ID_FORMAT, ClimateDevice from homeassistant.components.climate.const import ( CURRENT_HVAC_COOL, CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, HVAC_MODE_COOL, HVAC_MODE_HEAT, - SUPPORT_TARGET_TEMPERATURE, PRESET_AWAY, PRESET_NONE, SUPPORT_PRESET_MODE, + SUPPORT_TARGET_TEMPERATURE, ) from homeassistant.const import ( ATTR_TEMPERATURE, @@ -30,7 +30,6 @@ from homeassistant.helpers.entity import async_generate_entity_id from . import DOMAIN from .const import CONF_FLOOR_TEMP, CONF_PRECISION, DATA_GATEWAYS, DATA_OPENTHERM_GW - _LOGGER = logging.getLogger(__name__) DEFAULT_FLOOR_TEMP = False diff --git a/homeassistant/components/opentherm_gw/config_flow.py b/homeassistant/components/opentherm_gw/config_flow.py index 2d7a65bbd84..b52641105e4 100644 --- a/homeassistant/components/opentherm_gw/config_flow.py +++ b/homeassistant/components/opentherm_gw/config_flow.py @@ -1,8 +1,8 @@ """OpenTherm Gateway config flow.""" import asyncio -from serial import SerialException import pyotgw +from serial import SerialException import voluptuous as vol from homeassistant import config_entries @@ -15,7 +15,6 @@ from homeassistant.const import ( PRECISION_WHOLE, ) from homeassistant.core import callback - import homeassistant.helpers.config_validation as cv from . import DOMAIN diff --git a/homeassistant/components/opentherm_gw/sensor.py b/homeassistant/components/opentherm_gw/sensor.py index cd9ce9fb095..3739f77e69d 100644 --- a/homeassistant/components/opentherm_gw/sensor.py +++ b/homeassistant/components/opentherm_gw/sensor.py @@ -10,7 +10,6 @@ from homeassistant.helpers.entity import Entity, async_generate_entity_id from . import DOMAIN from .const import DATA_GATEWAYS, DATA_OPENTHERM_GW, SENSOR_INFO - _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/opentherm_gw/test_config_flow.py b/tests/components/opentherm_gw/test_config_flow.py index 89f2783cf71..26048543a22 100644 --- a/tests/components/opentherm_gw/test_config_flow.py +++ b/tests/components/opentherm_gw/test_config_flow.py @@ -1,18 +1,19 @@ """Test the Opentherm Gateway config flow.""" import asyncio -from serial import SerialException from unittest.mock import patch +from pyotgw import OTGW_ABOUT +from serial import SerialException + from homeassistant import config_entries, data_entry_flow, setup -from homeassistant.const import CONF_DEVICE, CONF_ID, CONF_NAME, PRECISION_HALVES from homeassistant.components.opentherm_gw.const import ( - DOMAIN, CONF_FLOOR_TEMP, CONF_PRECISION, + DOMAIN, ) +from homeassistant.const import CONF_DEVICE, CONF_ID, CONF_NAME, PRECISION_HALVES -from pyotgw import OTGW_ABOUT -from tests.common import mock_coro, MockConfigEntry +from tests.common import MockConfigEntry, mock_coro async def test_form_user(hass): From c7b2c09a613b757b73a6e3bd9ece16fe1e3b3694 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:42:18 +0100 Subject: [PATCH 154/677] Sort imports according to PEP8 for xiaomi_miio (#29677) --- .../components/xiaomi_miio/air_quality.py | 4 +- homeassistant/components/xiaomi_miio/fan.py | 42 +++++++++---------- homeassistant/components/xiaomi_miio/light.py | 14 +++---- .../components/xiaomi_miio/switch.py | 4 +- .../components/xiaomi_miio/vacuum.py | 2 +- 5 files changed, 33 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/air_quality.py b/homeassistant/components/xiaomi_miio/air_quality.py index 3824c5b88cd..f5e7e476ac5 100644 --- a/homeassistant/components/xiaomi_miio/air_quality.py +++ b/homeassistant/components/xiaomi_miio/air_quality.py @@ -3,9 +3,9 @@ from miio import AirQualityMonitor, Device, DeviceException import voluptuous as vol from homeassistant.components.air_quality import ( - AirQualityEntity, - PLATFORM_SCHEMA, _LOGGER, + PLATFORM_SCHEMA, + AirQualityEntity, ) from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN from homeassistant.exceptions import PlatformNotReady diff --git a/homeassistant/components/xiaomi_miio/fan.py b/homeassistant/components/xiaomi_miio/fan.py index 91b18aaf364..bf3691eb486 100644 --- a/homeassistant/components/xiaomi_miio/fan.py +++ b/homeassistant/components/xiaomi_miio/fan.py @@ -4,7 +4,6 @@ from enum import Enum from functools import partial import logging -import voluptuous as vol from miio import ( # pylint: disable=import-error AirFresh, AirHumidifier, @@ -12,19 +11,19 @@ from miio import ( # pylint: disable=import-error Device, DeviceException, ) - -from miio.airfresh import ( # pylint: disable=import-error; pylint: disable=import-error +from miio.airfresh import ( # pylint: disable=import-error, import-error LedBrightness as AirfreshLedBrightness, OperationMode as AirfreshOperationMode, ) -from miio.airhumidifier import ( # pylint: disable=import-error; pylint: disable=import-error +from miio.airhumidifier import ( # pylint: disable=import-error, import-error LedBrightness as AirhumidifierLedBrightness, OperationMode as AirhumidifierOperationMode, ) -from miio.airpurifier import ( # pylint: disable=import-error; pylint: disable=import-error +from miio.airpurifier import ( # pylint: disable=import-error, import-error LedBrightness as AirpurifierLedBrightness, OperationMode as AirpurifierOperationMode, ) +import voluptuous as vol from homeassistant.components.fan import PLATFORM_SCHEMA, SUPPORT_SET_SPEED, FanEntity from homeassistant.const import ( @@ -39,26 +38,27 @@ import homeassistant.helpers.config_validation as cv from .const import ( DOMAIN, - SERVICE_SET_BUZZER_ON, - SERVICE_SET_BUZZER_OFF, - SERVICE_SET_LED_ON, - SERVICE_SET_LED_OFF, - SERVICE_SET_CHILD_LOCK_ON, - SERVICE_SET_CHILD_LOCK_OFF, - SERVICE_SET_LED_BRIGHTNESS, - SERVICE_SET_FAVORITE_LEVEL, - SERVICE_SET_AUTO_DETECT_ON, - SERVICE_SET_AUTO_DETECT_OFF, - SERVICE_SET_LEARN_MODE_ON, - SERVICE_SET_LEARN_MODE_OFF, - SERVICE_SET_VOLUME, SERVICE_RESET_FILTER, - SERVICE_SET_EXTRA_FEATURES, - SERVICE_SET_TARGET_HUMIDITY, - SERVICE_SET_DRY_ON, + SERVICE_SET_AUTO_DETECT_OFF, + SERVICE_SET_AUTO_DETECT_ON, + SERVICE_SET_BUZZER_OFF, + SERVICE_SET_BUZZER_ON, + SERVICE_SET_CHILD_LOCK_OFF, + SERVICE_SET_CHILD_LOCK_ON, SERVICE_SET_DRY_OFF, + SERVICE_SET_DRY_ON, + SERVICE_SET_EXTRA_FEATURES, + SERVICE_SET_FAVORITE_LEVEL, + SERVICE_SET_LEARN_MODE_OFF, + SERVICE_SET_LEARN_MODE_ON, + SERVICE_SET_LED_BRIGHTNESS, + SERVICE_SET_LED_OFF, + SERVICE_SET_LED_ON, + SERVICE_SET_TARGET_HUMIDITY, + SERVICE_SET_VOLUME, ) + _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = "Xiaomi Miio Device" diff --git a/homeassistant/components/xiaomi_miio/light.py b/homeassistant/components/xiaomi_miio/light.py index 2343a6787c2..5a7b743b362 100644 --- a/homeassistant/components/xiaomi_miio/light.py +++ b/homeassistant/components/xiaomi_miio/light.py @@ -34,14 +34,14 @@ from homeassistant.util import color, dt from .const import ( DOMAIN, - SERVICE_SET_SCENE, - SERVICE_SET_DELAYED_TURN_OFF, - SERVICE_REMINDER_ON, - SERVICE_REMINDER_OFF, - SERVICE_NIGHT_LIGHT_MODE_ON, - SERVICE_NIGHT_LIGHT_MODE_OFF, - SERVICE_EYECARE_MODE_ON, SERVICE_EYECARE_MODE_OFF, + SERVICE_EYECARE_MODE_ON, + SERVICE_NIGHT_LIGHT_MODE_OFF, + SERVICE_NIGHT_LIGHT_MODE_ON, + SERVICE_REMINDER_OFF, + SERVICE_REMINDER_ON, + SERVICE_SET_DELAYED_TURN_OFF, + SERVICE_SET_SCENE, ) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/xiaomi_miio/switch.py b/homeassistant/components/xiaomi_miio/switch.py index f9a06924b5c..63229b851d0 100644 --- a/homeassistant/components/xiaomi_miio/switch.py +++ b/homeassistant/components/xiaomi_miio/switch.py @@ -26,10 +26,10 @@ import homeassistant.helpers.config_validation as cv from .const import ( DOMAIN, - SERVICE_SET_WIFI_LED_ON, - SERVICE_SET_WIFI_LED_OFF, SERVICE_SET_POWER_MODE, SERVICE_SET_POWER_PRICE, + SERVICE_SET_WIFI_LED_OFF, + SERVICE_SET_WIFI_LED_ON, ) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/xiaomi_miio/vacuum.py b/homeassistant/components/xiaomi_miio/vacuum.py index f1845f534bb..bc703c769a5 100644 --- a/homeassistant/components/xiaomi_miio/vacuum.py +++ b/homeassistant/components/xiaomi_miio/vacuum.py @@ -39,11 +39,11 @@ import homeassistant.helpers.config_validation as cv from .const import ( DOMAIN, + SERVICE_CLEAN_ZONE, SERVICE_MOVE_REMOTE_CONTROL, SERVICE_MOVE_REMOTE_CONTROL_STEP, SERVICE_START_REMOTE_CONTROL, SERVICE_STOP_REMOTE_CONTROL, - SERVICE_CLEAN_ZONE, ) _LOGGER = logging.getLogger(__name__) From 642655b6d7cd9114cbb86ebb4c818bd6108d5ac6 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:43:00 +0100 Subject: [PATCH 155/677] Sort imports according to PEP8 for met (#29699) --- homeassistant/components/met/__init__.py | 1 + homeassistant/components/met/config_flow.py | 2 +- homeassistant/components/met/weather.py | 4 ++-- tests/components/met/test_config_flow.py | 6 +++--- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/met/__init__.py b/homeassistant/components/met/__init__.py index 305038c3e6a..7ef3c5f8796 100644 --- a/homeassistant/components/met/__init__.py +++ b/homeassistant/components/met/__init__.py @@ -1,5 +1,6 @@ """The met component.""" from homeassistant.core import Config, HomeAssistant + from .config_flow import MetFlowHandler # noqa: F401 from .const import DOMAIN # noqa: F401 diff --git a/homeassistant/components/met/config_flow.py b/homeassistant/components/met/config_flow.py index c7ff4973c7d..759f7f6fc89 100644 --- a/homeassistant/components/met/config_flow.py +++ b/homeassistant/components/met/config_flow.py @@ -6,7 +6,7 @@ from homeassistant.const import CONF_ELEVATION, CONF_LATITUDE, CONF_LONGITUDE, C from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from .const import DOMAIN, HOME_LOCATION_NAME, CONF_TRACK_HOME +from .const import CONF_TRACK_HOME, DOMAIN, HOME_LOCATION_NAME @callback diff --git a/homeassistant/components/met/weather.py b/homeassistant/components/met/weather.py index 2f9ddc5a67c..d99573a985e 100644 --- a/homeassistant/components/met/weather.py +++ b/homeassistant/components/met/weather.py @@ -5,16 +5,16 @@ from random import randrange import metno import voluptuous as vol -from homeassistant.core import callback from homeassistant.components.weather import PLATFORM_SCHEMA, WeatherEntity from homeassistant.const import ( CONF_ELEVATION, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, - TEMP_CELSIUS, EVENT_CORE_CONFIG_UPDATE, + TEMP_CELSIUS, ) +from homeassistant.core import callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.event import async_call_later diff --git a/tests/components/met/test_config_flow.py b/tests/components/met/test_config_flow.py index 32f3be676e0..73c6c819817 100644 --- a/tests/components/met/test_config_flow.py +++ b/tests/components/met/test_config_flow.py @@ -1,10 +1,10 @@ """Tests for Met.no config flow.""" from unittest.mock import Mock, patch -from tests.common import MockConfigEntry, mock_coro - -from homeassistant.const import CONF_ELEVATION, CONF_LATITUDE, CONF_LONGITUDE from homeassistant.components.met import config_flow +from homeassistant.const import CONF_ELEVATION, CONF_LATITUDE, CONF_LONGITUDE + +from tests.common import MockConfigEntry, mock_coro async def test_show_config_form(): From a78fe25871f90bc988ac89e5a8a0653f5f45182f Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:43:46 +0100 Subject: [PATCH 156/677] Sort imports according to PEP8 for locative (#29698) --- homeassistant/components/locative/__init__.py | 10 +++++----- homeassistant/components/locative/config_flow.py | 2 +- homeassistant/components/locative/device_tracker.py | 2 +- tests/components/locative/test_init.py | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/locative/__init__.py b/homeassistant/components/locative/__init__.py index ed8bcb6e7e5..ea36aa9f7fb 100644 --- a/homeassistant/components/locative/__init__.py +++ b/homeassistant/components/locative/__init__.py @@ -2,21 +2,21 @@ import logging from typing import Dict -import voluptuous as vol from aiohttp import web +import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.device_tracker import DOMAIN as DEVICE_TRACKER from homeassistant.const import ( - HTTP_UNPROCESSABLE_ENTITY, + ATTR_ID, ATTR_LATITUDE, ATTR_LONGITUDE, - STATE_NOT_HOME, CONF_WEBHOOK_ID, - ATTR_ID, HTTP_OK, + HTTP_UNPROCESSABLE_ENTITY, + STATE_NOT_HOME, ) from homeassistant.helpers import config_entry_flow +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/locative/config_flow.py b/homeassistant/components/locative/config_flow.py index b4fb43d5e4e..a1ac8263416 100644 --- a/homeassistant/components/locative/config_flow.py +++ b/homeassistant/components/locative/config_flow.py @@ -1,7 +1,7 @@ """Config flow for Locative.""" from homeassistant.helpers import config_entry_flow -from .const import DOMAIN +from .const import DOMAIN config_entry_flow.register_webhook_flow( DOMAIN, diff --git a/homeassistant/components/locative/device_tracker.py b/homeassistant/components/locative/device_tracker.py index c92847930a0..ef247954171 100644 --- a/homeassistant/components/locative/device_tracker.py +++ b/homeassistant/components/locative/device_tracker.py @@ -1,9 +1,9 @@ """Support for the Locative platform.""" import logging -from homeassistant.core import callback from homeassistant.components.device_tracker import SOURCE_TYPE_GPS from homeassistant.components.device_tracker.config_entry import TrackerEntity +from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from . import DOMAIN as LT_DOMAIN, TRACKER_UPDATE diff --git a/tests/components/locative/test_init.py b/tests/components/locative/test_init.py index 1abbf25d433..009a3d469c5 100644 --- a/tests/components/locative/test_init.py +++ b/tests/components/locative/test_init.py @@ -1,5 +1,5 @@ """The tests the for Locative device tracker platform.""" -from unittest.mock import patch, Mock +from unittest.mock import Mock, patch import pytest From fa6b75f2c1de8cf66027d9189ffbc8b0aac4595f Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:45:11 +0100 Subject: [PATCH 157/677] Sort imports according to PEP8 for file (#29694) --- homeassistant/components/file/notify.py | 7 +++---- homeassistant/components/file/sensor.py | 6 +++--- tests/components/file/test_notify.py | 2 +- tests/components/file/test_sensor.py | 2 +- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/file/notify.py b/homeassistant/components/file/notify.py index b190bf5d121..4cd83e64a83 100644 --- a/homeassistant/components/file/notify.py +++ b/homeassistant/components/file/notify.py @@ -4,16 +4,15 @@ import os import voluptuous as vol -from homeassistant.const import CONF_FILENAME -import homeassistant.helpers.config_validation as cv -import homeassistant.util.dt as dt_util - from homeassistant.components.notify import ( ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, BaseNotificationService, ) +from homeassistant.const import CONF_FILENAME +import homeassistant.helpers.config_validation as cv +import homeassistant.util.dt as dt_util CONF_TIMESTAMP = "timestamp" diff --git a/homeassistant/components/file/sensor.py b/homeassistant/components/file/sensor.py index 60f04b18f24..96ae885ca77 100644 --- a/homeassistant/components/file/sensor.py +++ b/homeassistant/components/file/sensor.py @@ -1,12 +1,12 @@ """Support for sensor value(s) stored in local files.""" -import os import logging +import os import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONF_VALUE_TEMPLATE, CONF_NAME, CONF_UNIT_OF_MEASUREMENT +from homeassistant.const import CONF_NAME, CONF_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/file/test_notify.py b/tests/components/file/test_notify.py index 16f4c72658e..52524d5b189 100644 --- a/tests/components/file/test_notify.py +++ b/tests/components/file/test_notify.py @@ -3,9 +3,9 @@ import os import unittest from unittest.mock import call, mock_open, patch -from homeassistant.setup import setup_component import homeassistant.components.notify as notify from homeassistant.components.notify import ATTR_TITLE_DEFAULT +from homeassistant.setup import setup_component import homeassistant.util.dt as dt_util from tests.common import assert_setup_component, get_test_home_assistant diff --git a/tests/components/file/test_sensor.py b/tests/components/file/test_sensor.py index 912f11aab3b..3afdd8284fc 100644 --- a/tests/components/file/test_sensor.py +++ b/tests/components/file/test_sensor.py @@ -6,8 +6,8 @@ from unittest.mock import Mock, patch # https://bugs.python.org/issue23004 from mock_open import MockOpen -from homeassistant.setup import setup_component from homeassistant.const import STATE_UNKNOWN +from homeassistant.setup import setup_component from tests.common import get_test_home_assistant, mock_registry From 2511f5edb40880286ca20ff95cd1b4e664d70271 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:46:08 +0100 Subject: [PATCH 158/677] Sort imports according to PEP8 for ifttt (#29696) --- homeassistant/components/ifttt/__init__.py | 1 + homeassistant/components/ifttt/alarm_control_panel.py | 4 ++-- homeassistant/components/ifttt/config_flow.py | 2 +- tests/components/ifttt/test_init.py | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/ifttt/__init__.py b/homeassistant/components/ifttt/__init__.py index 362b01bb5d8..3011f5a2a0a 100644 --- a/homeassistant/components/ifttt/__init__.py +++ b/homeassistant/components/ifttt/__init__.py @@ -9,6 +9,7 @@ import voluptuous as vol from homeassistant.const import CONF_WEBHOOK_ID from homeassistant.helpers import config_entry_flow import homeassistant.helpers.config_validation as cv + from .const import DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/ifttt/alarm_control_panel.py b/homeassistant/components/ifttt/alarm_control_panel.py index 9c9ec88ccc7..2c281e58c48 100644 --- a/homeassistant/components/ifttt/alarm_control_panel.py +++ b/homeassistant/components/ifttt/alarm_control_panel.py @@ -5,11 +5,11 @@ import re import voluptuous as vol from homeassistant.components.alarm_control_panel import ( - AlarmControlPanel, FORMAT_NUMBER, FORMAT_TEXT, + PLATFORM_SCHEMA, + AlarmControlPanel, ) -from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA from homeassistant.components.alarm_control_panel.const import ( SUPPORT_ALARM_ARM_AWAY, SUPPORT_ALARM_ARM_HOME, diff --git a/homeassistant/components/ifttt/config_flow.py b/homeassistant/components/ifttt/config_flow.py index ae9be6b698c..dc28f6bbaa2 100644 --- a/homeassistant/components/ifttt/config_flow.py +++ b/homeassistant/components/ifttt/config_flow.py @@ -1,7 +1,7 @@ """Config flow for IFTTT.""" from homeassistant.helpers import config_entry_flow -from .const import DOMAIN +from .const import DOMAIN config_entry_flow.register_webhook_flow( DOMAIN, diff --git a/tests/components/ifttt/test_init.py b/tests/components/ifttt/test_init.py index a71e2921888..74d12ba44f4 100644 --- a/tests/components/ifttt/test_init.py +++ b/tests/components/ifttt/test_init.py @@ -2,8 +2,8 @@ from unittest.mock import patch from homeassistant import data_entry_flow -from homeassistant.core import callback from homeassistant.components import ifttt +from homeassistant.core import callback async def test_config_flow_registers_webhook(hass, aiohttp_client): From 2261bb60e09eec318017283699402d7305d8f6ee Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:46:49 +0100 Subject: [PATCH 159/677] Sort imports according to PEP8 for geofency (#29695) --- homeassistant/components/geofency/__init__.py | 2 +- homeassistant/components/geofency/config_flow.py | 2 +- homeassistant/components/geofency/device_tracker.py | 6 +++--- tests/components/geofency/test_init.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/geofency/__init__.py b/homeassistant/components/geofency/__init__.py index 9d8e0b29f5d..9afc9a8bfac 100644 --- a/homeassistant/components/geofency/__init__.py +++ b/homeassistant/components/geofency/__init__.py @@ -18,8 +18,8 @@ from homeassistant.helpers import config_entry_flow import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.util import slugify -from .const import DOMAIN +from .const import DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/geofency/config_flow.py b/homeassistant/components/geofency/config_flow.py index 1a87502df2a..2d8bce86d74 100644 --- a/homeassistant/components/geofency/config_flow.py +++ b/homeassistant/components/geofency/config_flow.py @@ -1,7 +1,7 @@ """Config flow for Geofency.""" from homeassistant.helpers import config_entry_flow -from .const import DOMAIN +from .const import DOMAIN config_entry_flow.register_webhook_flow( DOMAIN, diff --git a/homeassistant/components/geofency/device_tracker.py b/homeassistant/components/geofency/device_tracker.py index 09e9d46ce6d..49bd70192ef 100644 --- a/homeassistant/components/geofency/device_tracker.py +++ b/homeassistant/components/geofency/device_tracker.py @@ -1,13 +1,13 @@ """Support for the Geofency device tracker platform.""" import logging -from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE -from homeassistant.core import callback from homeassistant.components.device_tracker import SOURCE_TYPE_GPS from homeassistant.components.device_tracker.config_entry import TrackerEntity +from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE +from homeassistant.core import callback +from homeassistant.helpers import device_registry from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.restore_state import RestoreEntity -from homeassistant.helpers import device_registry from . import DOMAIN as GF_DOMAIN, TRACKER_UPDATE diff --git a/tests/components/geofency/test_init.py b/tests/components/geofency/test_init.py index 6254fd4a504..319a79966fd 100644 --- a/tests/components/geofency/test_init.py +++ b/tests/components/geofency/test_init.py @@ -1,6 +1,6 @@ """The tests for the Geofency device tracker platform.""" # pylint: disable=redefined-outer-name -from unittest.mock import patch, Mock +from unittest.mock import Mock, patch import pytest From 38657c0055fbab6f1b4ce285cf093a7fa678429d Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:47:35 +0100 Subject: [PATCH 160/677] Sort imports according to PEP8 for counter (#29692) --- homeassistant/components/counter/__init__.py | 3 +-- homeassistant/components/counter/reproduce_state.py | 2 +- tests/components/counter/common.py | 2 +- tests/components/counter/test_init.py | 1 + 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/counter/__init__.py b/homeassistant/components/counter/__init__.py index c2f61d0c1b4..98329bc417a 100644 --- a/homeassistant/components/counter/__init__.py +++ b/homeassistant/components/counter/__init__.py @@ -3,8 +3,7 @@ import logging import voluptuous as vol -from homeassistant.const import CONF_ICON, CONF_NAME, CONF_MAXIMUM, CONF_MINIMUM - +from homeassistant.const import CONF_ICON, CONF_MAXIMUM, CONF_MINIMUM, CONF_NAME import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.restore_state import RestoreEntity diff --git a/homeassistant/components/counter/reproduce_state.py b/homeassistant/components/counter/reproduce_state.py index ac5045d68e7..b37fcea719e 100644 --- a/homeassistant/components/counter/reproduce_state.py +++ b/homeassistant/components/counter/reproduce_state.py @@ -12,9 +12,9 @@ from . import ( ATTR_MAXIMUM, ATTR_MINIMUM, ATTR_STEP, - VALUE, DOMAIN, SERVICE_CONFIGURE, + VALUE, ) _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/counter/common.py b/tests/components/counter/common.py index 0f735e52f9f..5f47e4faa77 100644 --- a/tests/components/counter/common.py +++ b/tests/components/counter/common.py @@ -3,13 +3,13 @@ All containing methods are legacy helpers that should not be used by new components. Instead call the service directly. """ -from homeassistant.const import ATTR_ENTITY_ID from homeassistant.components.counter import ( DOMAIN, SERVICE_DECREMENT, SERVICE_INCREMENT, SERVICE_RESET, ) +from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import callback from homeassistant.loader import bind_hass diff --git a/tests/components/counter/test_init.py b/tests/components/counter/test_init.py index 8ce90e164b6..35512129aed 100644 --- a/tests/components/counter/test_init.py +++ b/tests/components/counter/test_init.py @@ -14,6 +14,7 @@ from homeassistant.components.counter import ( from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_ICON from homeassistant.core import Context, CoreState, State from homeassistant.setup import async_setup_component + from tests.common import mock_restore_cache from tests.components.counter.common import ( async_decrement, From 9c1236b6de49a11e8c9d35c91b251c4b44183e5d Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:48:22 +0100 Subject: [PATCH 161/677] Sort imports according to PEP8 for ffmpeg (#29693) --- homeassistant/components/ffmpeg/__init__.py | 12 ++++++------ homeassistant/components/ffmpeg/camera.py | 6 +++--- tests/components/ffmpeg/test_init.py | 4 ++-- tests/components/ffmpeg/test_sensor.py | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/ffmpeg/__init__.py b/homeassistant/components/ffmpeg/__init__.py index 673a34230fc..bc402b46fb2 100644 --- a/homeassistant/components/ffmpeg/__init__.py +++ b/homeassistant/components/ffmpeg/__init__.py @@ -2,20 +2,20 @@ import logging import re -import voluptuous as vol from haffmpeg.tools import FFVersion +import voluptuous as vol -from homeassistant.core import callback from homeassistant.const import ( ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, ) -from homeassistant.helpers.dispatcher import ( - async_dispatcher_send, - async_dispatcher_connect, -) +from homeassistant.core import callback import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, + async_dispatcher_send, +) from homeassistant.helpers.entity import Entity DOMAIN = "ffmpeg" diff --git a/homeassistant/components/ffmpeg/camera.py b/homeassistant/components/ffmpeg/camera.py index 0f500176933..db3eb5621ff 100644 --- a/homeassistant/components/ffmpeg/camera.py +++ b/homeassistant/components/ffmpeg/camera.py @@ -2,11 +2,11 @@ import asyncio import logging -import voluptuous as vol from haffmpeg.camera import CameraMjpeg -from haffmpeg.tools import ImageFrame, IMAGE_JPEG +from haffmpeg.tools import IMAGE_JPEG, ImageFrame +import voluptuous as vol -from homeassistant.components.camera import PLATFORM_SCHEMA, Camera, SUPPORT_STREAM +from homeassistant.components.camera import PLATFORM_SCHEMA, SUPPORT_STREAM, Camera from homeassistant.const import CONF_NAME from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream import homeassistant.helpers.config_validation as cv diff --git a/tests/components/ffmpeg/test_init.py b/tests/components/ffmpeg/test_init.py index e0b68cd61b1..1b21f07cd36 100644 --- a/tests/components/ffmpeg/test_init.py +++ b/tests/components/ffmpeg/test_init.py @@ -11,9 +11,9 @@ from homeassistant.components.ffmpeg import ( ) from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import callback -from homeassistant.setup import setup_component, async_setup_component +from homeassistant.setup import async_setup_component, setup_component -from tests.common import get_test_home_assistant, assert_setup_component +from tests.common import assert_setup_component, get_test_home_assistant @callback diff --git a/tests/components/ffmpeg/test_sensor.py b/tests/components/ffmpeg/test_sensor.py index 175da3a1ab0..5a89daa624c 100644 --- a/tests/components/ffmpeg/test_sensor.py +++ b/tests/components/ffmpeg/test_sensor.py @@ -3,7 +3,7 @@ from unittest.mock import patch from homeassistant.setup import setup_component -from tests.common import get_test_home_assistant, assert_setup_component, mock_coro +from tests.common import assert_setup_component, get_test_home_assistant, mock_coro class TestFFmpegNoiseSetup: From bfa58f671a825d2175ecc47fa423af35ba974337 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:48:52 +0100 Subject: [PATCH 162/677] use isort to sort imports according to PEP8 for broadlink (#29690) --- homeassistant/components/broadlink/__init__.py | 2 +- homeassistant/components/broadlink/sensor.py | 9 ++++----- homeassistant/components/broadlink/switch.py | 3 +-- tests/components/broadlink/test_init.py | 6 +++--- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/broadlink/__init__.py b/homeassistant/components/broadlink/__init__.py index 521cd68780c..3f9b5cd4597 100644 --- a/homeassistant/components/broadlink/__init__.py +++ b/homeassistant/components/broadlink/__init__.py @@ -2,11 +2,11 @@ import asyncio from base64 import b64decode, b64encode from binascii import unhexlify +from datetime import timedelta import logging import re import socket -from datetime import timedelta import voluptuous as vol from homeassistant.const import CONF_HOST diff --git a/homeassistant/components/broadlink/sensor.py b/homeassistant/components/broadlink/sensor.py index 6374f35c503..9f3087335c8 100644 --- a/homeassistant/components/broadlink/sensor.py +++ b/homeassistant/components/broadlink/sensor.py @@ -1,23 +1,22 @@ """Support for the Broadlink RM2 Pro (only temperature) and A1 devices.""" import binascii -import logging from datetime import timedelta +import logging import broadlink - import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( CONF_HOST, CONF_MAC, CONF_MONITORED_CONDITIONS, CONF_NAME, - TEMP_CELSIUS, - CONF_TIMEOUT, CONF_SCAN_INTERVAL, + CONF_TIMEOUT, + TEMP_CELSIUS, ) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle diff --git a/homeassistant/components/broadlink/switch.py b/homeassistant/components/broadlink/switch.py index bfb6dc4f42e..78738870aaa 100644 --- a/homeassistant/components/broadlink/switch.py +++ b/homeassistant/components/broadlink/switch.py @@ -5,7 +5,6 @@ import logging import socket import broadlink - import voluptuous as vol from homeassistant.components.switch import ( @@ -25,8 +24,8 @@ from homeassistant.const import ( STATE_ON, ) import homeassistant.helpers.config_validation as cv -from homeassistant.util import Throttle, slugify from homeassistant.helpers.restore_state import RestoreEntity +from homeassistant.util import Throttle, slugify from . import async_setup_service, data_packet diff --git a/tests/components/broadlink/test_init.py b/tests/components/broadlink/test_init.py index c15ef12125f..d4e3c993cd0 100644 --- a/tests/components/broadlink/test_init.py +++ b/tests/components/broadlink/test_init.py @@ -1,13 +1,13 @@ """The tests for the broadlink component.""" -from datetime import timedelta from base64 import b64decode -from unittest.mock import MagicMock, patch, call +from datetime import timedelta +from unittest.mock import MagicMock, call, patch import pytest -from homeassistant.util.dt import utcnow from homeassistant.components.broadlink import async_setup_service, data_packet from homeassistant.components.broadlink.const import DOMAIN, SERVICE_LEARN, SERVICE_SEND +from homeassistant.util.dt import utcnow DUMMY_IR_PACKET = ( "JgBGAJKVETkRORA6ERQRFBEUERQRFBE5ETkQOhAVEBUQFREUEBUQ" From 0b4ca9ecacacc8e2710bc2bfc6a0a7f7455c94a2 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:49:35 +0100 Subject: [PATCH 163/677] Sort imports according to PEP8 for ambiclimate (#29689) --- homeassistant/components/ambiclimate/__init__.py | 2 +- homeassistant/components/ambiclimate/climate.py | 5 +++-- homeassistant/components/ambiclimate/config_flow.py | 3 ++- tests/components/ambiclimate/test_config_flow.py | 6 ++++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/ambiclimate/__init__.py b/homeassistant/components/ambiclimate/__init__.py index 962c8c8a82d..e15f6dea2ec 100644 --- a/homeassistant/components/ambiclimate/__init__.py +++ b/homeassistant/components/ambiclimate/__init__.py @@ -4,10 +4,10 @@ import logging import voluptuous as vol from homeassistant.helpers import config_validation as cv + from . import config_flow from .const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, DOMAIN - _LOGGER = logging.getLogger(__name__) CONFIG_SCHEMA = vol.Schema( diff --git a/homeassistant/components/ambiclimate/climate.py b/homeassistant/components/ambiclimate/climate.py index bb3e5ab2b25..a8ed166903e 100644 --- a/homeassistant/components/ambiclimate/climate.py +++ b/homeassistant/components/ambiclimate/climate.py @@ -7,13 +7,14 @@ import voluptuous as vol from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( - SUPPORT_TARGET_TEMPERATURE, - HVAC_MODE_OFF, HVAC_MODE_HEAT, + HVAC_MODE_OFF, + SUPPORT_TARGET_TEMPERATURE, ) from homeassistant.const import ATTR_NAME, ATTR_TEMPERATURE, TEMP_CELSIUS from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession + from .const import ( ATTR_VALUE, CONF_CLIENT_ID, diff --git a/homeassistant/components/ambiclimate/config_flow.py b/homeassistant/components/ambiclimate/config_flow.py index 99563dcb97d..4996a458a1f 100644 --- a/homeassistant/components/ambiclimate/config_flow.py +++ b/homeassistant/components/ambiclimate/config_flow.py @@ -7,14 +7,15 @@ from homeassistant import config_entries from homeassistant.components.http import HomeAssistantView from homeassistant.core import callback from homeassistant.helpers.aiohttp_client import async_get_clientsession + from .const import ( AUTH_CALLBACK_NAME, AUTH_CALLBACK_PATH, CONF_CLIENT_ID, CONF_CLIENT_SECRET, DOMAIN, - STORAGE_VERSION, STORAGE_KEY, + STORAGE_VERSION, ) DATA_AMBICLIMATE_IMPL = "ambiclimate_flow_implementation" diff --git a/tests/components/ambiclimate/test_config_flow.py b/tests/components/ambiclimate/test_config_flow.py index c0940fcc354..acf3717b898 100644 --- a/tests/components/ambiclimate/test_config_flow.py +++ b/tests/components/ambiclimate/test_config_flow.py @@ -1,11 +1,13 @@ """Tests for the Ambiclimate config flow.""" -import ambiclimate from unittest.mock import Mock, patch +import ambiclimate + +from homeassistant import data_entry_flow from homeassistant.components.ambiclimate import config_flow from homeassistant.setup import async_setup_component from homeassistant.util import aiohttp -from homeassistant import data_entry_flow + from tests.common import mock_coro From 4c5c34919d535ef3e2f21dc6a668c4a270ee1758 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:50:48 +0100 Subject: [PATCH 164/677] Sort imports according to PEP8 for camera (#29691) --- homeassistant/components/camera/__init__.py | 55 ++++++++++----------- homeassistant/components/camera/prefs.py | 1 - tests/components/camera/common.py | 4 +- tests/components/camera/test_init.py | 14 +++--- 4 files changed, 36 insertions(+), 38 deletions(-) diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 58b6db139f5..b3d5935784f 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -4,55 +4,54 @@ import base64 import collections from contextlib import suppress from datetime import timedelta -import logging import hashlib +import logging from random import SystemRandom -import attr from aiohttp import web import async_timeout +import attr import voluptuous as vol -from homeassistant.core import callback +from homeassistant.components import websocket_api +from homeassistant.components.http import KEY_AUTHENTICATED, HomeAssistantView +from homeassistant.components.media_player.const import ( + ATTR_MEDIA_CONTENT_ID, + ATTR_MEDIA_CONTENT_TYPE, + DOMAIN as DOMAIN_MP, + SERVICE_PLAY_MEDIA, +) +from homeassistant.components.stream import request_stream +from homeassistant.components.stream.const import ( + CONF_DURATION, + CONF_LOOKBACK, + CONF_STREAM_SOURCE, + DOMAIN as DOMAIN_STREAM, + FORMAT_CONTENT_TYPE, + OUTPUT_FORMATS, + SERVICE_RECORD, +) from homeassistant.const import ( ATTR_ENTITY_ID, + CONF_FILENAME, SERVICE_TURN_OFF, SERVICE_TURN_ON, - CONF_FILENAME, ) +from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError -from homeassistant.loader import bind_hass -from homeassistant.helpers.entity import Entity -from homeassistant.helpers.entity_component import EntityComponent +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.config_validation import ( # noqa: F401 PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE, ) -from homeassistant.components.http import HomeAssistantView, KEY_AUTHENTICATED -from homeassistant.components.media_player.const import ( - ATTR_MEDIA_CONTENT_ID, - ATTR_MEDIA_CONTENT_TYPE, - SERVICE_PLAY_MEDIA, - DOMAIN as DOMAIN_MP, -) -from homeassistant.components.stream import request_stream -from homeassistant.components.stream.const import ( - OUTPUT_FORMATS, - FORMAT_CONTENT_TYPE, - CONF_STREAM_SOURCE, - CONF_LOOKBACK, - CONF_DURATION, - SERVICE_RECORD, - DOMAIN as DOMAIN_STREAM, -) -from homeassistant.components import websocket_api -import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.loader import bind_hass from homeassistant.setup import async_when_setup -from .const import DOMAIN, DATA_CAMERA_PREFS +from .const import DATA_CAMERA_PREFS, DOMAIN from .prefs import CameraPreferences - # mypy: allow-untyped-calls, allow-untyped-defs _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/camera/prefs.py b/homeassistant/components/camera/prefs.py index d83e0b55c96..ae182c62dc6 100644 --- a/homeassistant/components/camera/prefs.py +++ b/homeassistant/components/camera/prefs.py @@ -1,7 +1,6 @@ """Preference management for camera component.""" from .const import DOMAIN, PREF_PRELOAD_STREAM - # mypy: allow-untyped-defs, no-check-untyped-defs STORAGE_KEY = DOMAIN diff --git a/tests/components/camera/common.py b/tests/components/camera/common.py index 971d723d2d5..8c05295b5d1 100644 --- a/tests/components/camera/common.py +++ b/tests/components/camera/common.py @@ -9,15 +9,15 @@ from homeassistant.components.camera import ( SERVICE_SNAPSHOT, ) from homeassistant.components.camera.const import ( - DOMAIN, DATA_CAMERA_PREFS, + DOMAIN, PREF_PRELOAD_STREAM, ) from homeassistant.const import ( ATTR_ENTITY_ID, + ENTITY_MATCH_ALL, SERVICE_TURN_OFF, SERVICE_TURN_ON, - ENTITY_MATCH_ALL, ) from homeassistant.core import callback from homeassistant.loader import bind_hass diff --git a/tests/components/camera/test_init.py b/tests/components/camera/test_init.py index 17bcaadb92b..89a19d0458a 100644 --- a/tests/components/camera/test_init.py +++ b/tests/components/camera/test_init.py @@ -2,26 +2,26 @@ import asyncio import base64 import io -from unittest.mock import patch, mock_open, PropertyMock +from unittest.mock import PropertyMock, mock_open, patch import pytest -from homeassistant.setup import setup_component, async_setup_component +from homeassistant.components import camera, http +from homeassistant.components.camera.const import DOMAIN, PREF_PRELOAD_STREAM +from homeassistant.components.camera.prefs import CameraEntityPreferences +from homeassistant.components.websocket_api.const import TYPE_RESULT from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE, EVENT_HOMEASSISTANT_START, ) -from homeassistant.components import camera, http -from homeassistant.components.camera.const import DOMAIN, PREF_PRELOAD_STREAM -from homeassistant.components.camera.prefs import CameraEntityPreferences -from homeassistant.components.websocket_api.const import TYPE_RESULT from homeassistant.exceptions import HomeAssistantError +from homeassistant.setup import async_setup_component, setup_component from tests.common import ( + assert_setup_component, get_test_home_assistant, get_test_instance_port, - assert_setup_component, mock_coro, ) from tests.components.camera import common From c54135486e771ed6f7e8703e39a575b6143c9ac1 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:50:59 +0100 Subject: [PATCH 165/677] use isort to sort imports according to PEP8 for netatmo (#29682) --- homeassistant/components/netatmo/__init__.py | 8 ++++---- .../components/netatmo/binary_sensor.py | 2 +- homeassistant/components/netatmo/camera.py | 12 ++++++------ homeassistant/components/netatmo/climate.py | 18 +++++++++--------- homeassistant/components/netatmo/sensor.py | 11 ++++++----- 5 files changed, 26 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/netatmo/__init__.py b/homeassistant/components/netatmo/__init__.py index 9edeb0937a1..6becedde611 100644 --- a/homeassistant/components/netatmo/__init__.py +++ b/homeassistant/components/netatmo/__init__.py @@ -1,6 +1,6 @@ """Support for the Netatmo devices.""" -import logging from datetime import timedelta +import logging from urllib.error import HTTPError import pyatmo @@ -8,17 +8,17 @@ import voluptuous as vol from homeassistant.const import ( CONF_API_KEY, - CONF_PASSWORD, - CONF_USERNAME, CONF_DISCOVERY, + CONF_PASSWORD, CONF_URL, + CONF_USERNAME, EVENT_HOMEASSISTANT_STOP, ) from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle -from .const import DOMAIN, DATA_NETATMO_AUTH +from .const import DATA_NETATMO_AUTH, DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/netatmo/binary_sensor.py b/homeassistant/components/netatmo/binary_sensor.py index 1a40d3952e9..06097ed852d 100644 --- a/homeassistant/components/netatmo/binary_sensor.py +++ b/homeassistant/components/netatmo/binary_sensor.py @@ -8,8 +8,8 @@ from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensor from homeassistant.const import CONF_TIMEOUT from homeassistant.helpers import config_validation as cv -from .const import DATA_NETATMO_AUTH from . import CameraData +from .const import DATA_NETATMO_AUTH _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/netatmo/camera.py b/homeassistant/components/netatmo/camera.py index f3bf6a6784c..1713265a014 100644 --- a/homeassistant/components/netatmo/camera.py +++ b/homeassistant/components/netatmo/camera.py @@ -6,20 +6,20 @@ import requests import voluptuous as vol from homeassistant.components.camera import ( - PLATFORM_SCHEMA, - Camera, - SUPPORT_STREAM, CAMERA_SERVICE_SCHEMA, + PLATFORM_SCHEMA, + SUPPORT_STREAM, + Camera, ) -from homeassistant.const import CONF_VERIFY_SSL, STATE_ON, STATE_OFF +from homeassistant.const import CONF_VERIFY_SSL, STATE_OFF, STATE_ON from homeassistant.helpers import config_validation as cv from homeassistant.helpers.dispatcher import ( - async_dispatcher_send, async_dispatcher_connect, + async_dispatcher_send, ) -from .const import DATA_NETATMO_AUTH, DOMAIN from . import CameraData +from .const import DATA_NETATMO_AUTH, DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index 8ba13a03889..9e320c303c8 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -1,34 +1,34 @@ """Support for Netatmo Smart thermostats.""" from datetime import timedelta import logging -from typing import Optional, List +from typing import List, Optional import pyatmo import requests import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA +from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice from homeassistant.components.climate.const import ( + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, + DEFAULT_MIN_TEMP, HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF, PRESET_AWAY, PRESET_BOOST, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - SUPPORT_TARGET_TEMPERATURE, SUPPORT_PRESET_MODE, - DEFAULT_MIN_TEMP, + SUPPORT_TARGET_TEMPERATURE, ) from homeassistant.const import ( - TEMP_CELSIUS, + ATTR_BATTERY_LEVEL, ATTR_TEMPERATURE, CONF_NAME, PRECISION_HALVES, STATE_OFF, - ATTR_BATTERY_LEVEL, + TEMP_CELSIUS, ) +import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle from .const import DATA_NETATMO_AUTH diff --git a/homeassistant/components/netatmo/sensor.py b/homeassistant/components/netatmo/sensor.py index 1ae076c6560..d4d624061f5 100644 --- a/homeassistant/components/netatmo/sensor.py +++ b/homeassistant/components/netatmo/sensor.py @@ -1,7 +1,7 @@ """Support for the Netatmo Weather Service.""" +from datetime import timedelta import logging import threading -from datetime import timedelta from time import time import pyatmo @@ -9,19 +9,20 @@ import requests import urllib3 import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_NAME, CONF_MODE, - TEMP_CELSIUS, + CONF_NAME, + DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, - DEVICE_CLASS_BATTERY, + TEMP_CELSIUS, ) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import call_later from homeassistant.util import Throttle + from .const import DATA_NETATMO_AUTH, DOMAIN _LOGGER = logging.getLogger(__name__) From 776d8cfdc9886968d038aae9f42811b642de1d48 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:51:36 +0100 Subject: [PATCH 166/677] Sort imports according to PEP8 for specific_devices (#29687) --- .../specific_devices/test_aqara_gateway.py | 3 ++- .../homekit_controller/specific_devices/test_ecobee3.py | 7 +++---- .../specific_devices/test_hue_bridge.py | 2 +- .../specific_devices/test_koogeek_ls1.py | 9 +++++---- .../specific_devices/test_lennox_e30.py | 3 ++- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/tests/components/homekit_controller/specific_devices/test_aqara_gateway.py b/tests/components/homekit_controller/specific_devices/test_aqara_gateway.py index 6e41a88b299..292c4169688 100644 --- a/tests/components/homekit_controller/specific_devices/test_aqara_gateway.py +++ b/tests/components/homekit_controller/specific_devices/test_aqara_gateway.py @@ -5,10 +5,11 @@ https://github.com/home-assistant/home-assistant/issues/20957 """ from homeassistant.components.light import SUPPORT_BRIGHTNESS, SUPPORT_COLOR + from tests.components.homekit_controller.common import ( + Helper, setup_accessories_from_file, setup_test_accessories, - Helper, ) diff --git a/tests/components/homekit_controller/specific_devices/test_ecobee3.py b/tests/components/homekit_controller/specific_devices/test_ecobee3.py index 8531712a5d3..bb7695840f0 100644 --- a/tests/components/homekit_controller/specific_devices/test_ecobee3.py +++ b/tests/components/homekit_controller/specific_devices/test_ecobee3.py @@ -8,19 +8,18 @@ from unittest import mock from homekit import AccessoryDisconnectedError -from homeassistant.config_entries import ENTRY_STATE_SETUP_RETRY from homeassistant.components.climate.const import ( - SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_HUMIDITY, + SUPPORT_TARGET_TEMPERATURE, ) - +from homeassistant.config_entries import ENTRY_STATE_SETUP_RETRY from tests.components.homekit_controller.common import ( FakePairing, + Helper, device_config_changed, setup_accessories_from_file, setup_test_accessories, - Helper, time_changed, ) diff --git a/tests/components/homekit_controller/specific_devices/test_hue_bridge.py b/tests/components/homekit_controller/specific_devices/test_hue_bridge.py index f3e4baa4b1f..0b6ebc00eba 100644 --- a/tests/components/homekit_controller/specific_devices/test_hue_bridge.py +++ b/tests/components/homekit_controller/specific_devices/test_hue_bridge.py @@ -1,9 +1,9 @@ """Tests for handling accessories on a Hue bridge via HomeKit.""" from tests.components.homekit_controller.common import ( + Helper, setup_accessories_from_file, setup_test_accessories, - Helper, ) diff --git a/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py b/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py index 89c5bb4de3c..52339bb6635 100644 --- a/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py +++ b/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py @@ -3,17 +3,18 @@ from datetime import timedelta from unittest import mock +from homekit.exceptions import AccessoryDisconnectedError, EncryptionError import pytest -from homekit.exceptions import AccessoryDisconnectedError, EncryptionError -import homeassistant.util.dt as dt_util from homeassistant.components.light import SUPPORT_BRIGHTNESS, SUPPORT_COLOR +import homeassistant.util.dt as dt_util + from tests.common import async_fire_time_changed from tests.components.homekit_controller.common import ( - setup_accessories_from_file, - setup_test_accessories, FakePairing, Helper, + setup_accessories_from_file, + setup_test_accessories, ) LIGHT_ON = ("lightbulb", "on") diff --git a/tests/components/homekit_controller/specific_devices/test_lennox_e30.py b/tests/components/homekit_controller/specific_devices/test_lennox_e30.py index ef0d35166db..3209139ae1e 100644 --- a/tests/components/homekit_controller/specific_devices/test_lennox_e30.py +++ b/tests/components/homekit_controller/specific_devices/test_lennox_e30.py @@ -5,10 +5,11 @@ https://github.com/home-assistant/home-assistant/issues/20885 """ from homeassistant.components.climate.const import SUPPORT_TARGET_TEMPERATURE + from tests.components.homekit_controller.common import ( + Helper, setup_accessories_from_file, setup_test_accessories, - Helper, ) From b6cd6135c2a217800fe1fb9a9efc537d7ecd56c9 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:53:51 +0100 Subject: [PATCH 167/677] Sort imports according to PEP8 for wemo (#29685) --- homeassistant/components/wemo/__init__.py | 5 ++--- homeassistant/components/wemo/config_flow.py | 2 +- homeassistant/components/wemo/fan.py | 14 +++++++------- homeassistant/components/wemo/light.py | 6 +++--- homeassistant/components/wemo/switch.py | 4 ++-- 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/wemo/__init__.py b/homeassistant/components/wemo/__init__.py index fe63f10aeba..9b1c4cd465f 100644 --- a/homeassistant/components/wemo/__init__.py +++ b/homeassistant/components/wemo/__init__.py @@ -7,10 +7,9 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.components.discovery import SERVICE_WEMO -from homeassistant.helpers import config_validation as cv -from homeassistant.helpers import discovery - from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP +from homeassistant.helpers import config_validation as cv, discovery + from .const import DOMAIN # Mapping from Wemo model_name to component. diff --git a/homeassistant/components/wemo/config_flow.py b/homeassistant/components/wemo/config_flow.py index 21c911a66ce..9ad7dda10ba 100644 --- a/homeassistant/components/wemo/config_flow.py +++ b/homeassistant/components/wemo/config_flow.py @@ -2,8 +2,8 @@ import pywemo -from homeassistant.helpers import config_entry_flow from homeassistant import config_entries +from homeassistant.helpers import config_entry_flow from . import DOMAIN diff --git a/homeassistant/components/wemo/fan.py b/homeassistant/components/wemo/fan.py index 4a8be4fba81..5974a9eae8c 100644 --- a/homeassistant/components/wemo/fan.py +++ b/homeassistant/components/wemo/fan.py @@ -1,24 +1,24 @@ """Support for WeMo humidifier.""" import asyncio -import logging from datetime import timedelta +import logging import async_timeout from pywemo import discovery import requests import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.fan import ( - SUPPORT_SET_SPEED, - FanEntity, - SPEED_OFF, + SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, - SPEED_HIGH, + SPEED_OFF, + SUPPORT_SET_SPEED, + FanEntity, ) -from homeassistant.exceptions import PlatformNotReady from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.exceptions import PlatformNotReady +import homeassistant.helpers.config_validation as cv from . import SUBSCRIPTION_REGISTRY from .const import DOMAIN, SERVICE_RESET_FILTER_LIFE, SERVICE_SET_HUMIDITY diff --git a/homeassistant/components/wemo/light.py b/homeassistant/components/wemo/light.py index dab96eb8c94..37113a09bd1 100644 --- a/homeassistant/components/wemo/light.py +++ b/homeassistant/components/wemo/light.py @@ -1,7 +1,7 @@ """Support for Belkin WeMo lights.""" import asyncio -import logging from datetime import timedelta +import logging import async_timeout from pywemo import discovery @@ -9,15 +9,15 @@ import requests 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_COLOR_TEMP, SUPPORT_TRANSITION, + Light, ) from homeassistant.exceptions import PlatformNotReady import homeassistant.util.color as color_util diff --git a/homeassistant/components/wemo/switch.py b/homeassistant/components/wemo/switch.py index 1c0606b489d..432a0ddf2cc 100644 --- a/homeassistant/components/wemo/switch.py +++ b/homeassistant/components/wemo/switch.py @@ -1,16 +1,16 @@ """Support for WeMo switches.""" import asyncio -import logging from datetime import datetime, timedelta +import logging import async_timeout from pywemo import discovery import requests from homeassistant.components.switch import SwitchDevice +from homeassistant.const import STATE_OFF, STATE_ON, STATE_STANDBY, STATE_UNKNOWN from homeassistant.exceptions import PlatformNotReady from homeassistant.util import convert -from homeassistant.const import STATE_OFF, STATE_ON, STATE_STANDBY, STATE_UNKNOWN from . import SUBSCRIPTION_REGISTRY from .const import DOMAIN From e4e4f78eb0aec339a030d65fbbe59ae346a82ba8 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:54:56 +0100 Subject: [PATCH 168/677] Sort imports according to PEP8 for utility_meter (#29710) --- .../components/utility_meter/__init__.py | 37 ++++++++++--------- .../components/utility_meter/sensor.py | 35 +++++++++--------- tests/components/utility_meter/test_init.py | 15 ++++---- tests/components/utility_meter/test_sensor.py | 20 +++++----- 4 files changed, 54 insertions(+), 53 deletions(-) diff --git a/homeassistant/components/utility_meter/__init__.py b/homeassistant/components/utility_meter/__init__.py index 04e472a7828..ef9d9b1ddce 100644 --- a/homeassistant/components/utility_meter/__init__.py +++ b/homeassistant/components/utility_meter/__init__.py @@ -1,33 +1,34 @@ """Support for tracking consumption over given periods of time.""" -import logging from datetime import timedelta +import logging import voluptuous as vol +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.const import CONF_NAME -import homeassistant.helpers.config_validation as cv from homeassistant.helpers import discovery +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.restore_state import RestoreEntity -from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN + from .const import ( - DOMAIN, - SIGNAL_RESET_METER, - METER_TYPES, - CONF_METER_TYPE, - CONF_METER_OFFSET, - CONF_METER_NET_CONSUMPTION, - CONF_SOURCE_SENSOR, - CONF_TARIFF_ENTITY, - CONF_TARIFF, - CONF_TARIFFS, - CONF_METER, - DATA_UTILITY, - SERVICE_RESET, - SERVICE_SELECT_TARIFF, - SERVICE_SELECT_NEXT_TARIFF, ATTR_TARIFF, + CONF_METER, + CONF_METER_NET_CONSUMPTION, + CONF_METER_OFFSET, + CONF_METER_TYPE, + CONF_SOURCE_SENSOR, + CONF_TARIFF, + CONF_TARIFF_ENTITY, + CONF_TARIFFS, + DATA_UTILITY, + DOMAIN, + METER_TYPES, + SERVICE_RESET, + SERVICE_SELECT_NEXT_TARIFF, + SERVICE_SELECT_TARIFF, + SIGNAL_RESET_METER, ) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/utility_meter/sensor.py b/homeassistant/components/utility_meter/sensor.py index 41fe2cbcc0a..3dab92b89f8 100644 --- a/homeassistant/components/utility_meter/sensor.py +++ b/homeassistant/components/utility_meter/sensor.py @@ -1,39 +1,40 @@ """Utility meter from sensors providing raw data.""" -import logging from datetime import date, timedelta from decimal import Decimal, DecimalException +import logging -import homeassistant.util.dt as dt_util from homeassistant.const import ( - CONF_NAME, ATTR_UNIT_OF_MEASUREMENT, + CONF_NAME, EVENT_HOMEASSISTANT_START, - STATE_UNKNOWN, STATE_UNAVAILABLE, + STATE_UNKNOWN, ) from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.event import ( async_track_state_change, async_track_time_change, ) -from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.restore_state import RestoreEntity +import homeassistant.util.dt as dt_util + from .const import ( - DATA_UTILITY, - SIGNAL_RESET_METER, - HOURLY, - DAILY, - WEEKLY, - MONTHLY, - QUARTERLY, - YEARLY, - CONF_SOURCE_SENSOR, - CONF_METER_TYPE, - CONF_METER_OFFSET, + CONF_METER, CONF_METER_NET_CONSUMPTION, + CONF_METER_OFFSET, + CONF_METER_TYPE, + CONF_SOURCE_SENSOR, CONF_TARIFF, CONF_TARIFF_ENTITY, - CONF_METER, + DAILY, + DATA_UTILITY, + HOURLY, + MONTHLY, + QUARTERLY, + SIGNAL_RESET_METER, + WEEKLY, + YEARLY, ) _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/utility_meter/test_init.py b/tests/components/utility_meter/test_init.py index 784a3647b8d..719ea9445cc 100644 --- a/tests/components/utility_meter/test_init.py +++ b/tests/components/utility_meter/test_init.py @@ -1,20 +1,19 @@ """The tests for the utility_meter component.""" -import logging - from datetime import timedelta +import logging from unittest.mock import patch -from homeassistant.const import EVENT_HOMEASSISTANT_START, ATTR_ENTITY_ID +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.utility_meter.const import ( - SERVICE_RESET, - SERVICE_SELECT_TARIFF, - SERVICE_SELECT_NEXT_TARIFF, ATTR_TARIFF, + DOMAIN, + SERVICE_RESET, + SERVICE_SELECT_NEXT_TARIFF, + SERVICE_SELECT_TARIFF, ) +from homeassistant.const import ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_START from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util -from homeassistant.components.utility_meter.const import DOMAIN -from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/utility_meter/test_sensor.py b/tests/components/utility_meter/test_sensor.py index 7bf10875b77..fcfe97804e4 100644 --- a/tests/components/utility_meter/test_sensor.py +++ b/tests/components/utility_meter/test_sensor.py @@ -1,20 +1,20 @@ """The tests for the utility_meter sensor platform.""" -import logging - -from datetime import timedelta -from unittest.mock import patch from contextlib import contextmanager +from datetime import timedelta +import logging +from unittest.mock import patch -from tests.common import async_fire_time_changed -from homeassistant.const import EVENT_HOMEASSISTANT_START, ATTR_ENTITY_ID -from homeassistant.setup import async_setup_component -import homeassistant.util.dt as dt_util +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.utility_meter.const import ( + ATTR_TARIFF, DOMAIN, SERVICE_SELECT_TARIFF, - ATTR_TARIFF, ) -from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.const import ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_START +from homeassistant.setup import async_setup_component +import homeassistant.util.dt as dt_util + +from tests.common import async_fire_time_changed _LOGGER = logging.getLogger(__name__) From b54c8641b4de7916d1a23b1566a7abcf6a52cea7 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:56:02 +0100 Subject: [PATCH 169/677] Sort imports according to PEP8 for vesync (#29684) --- homeassistant/components/vesync/__init__.py | 15 +++++++++------ homeassistant/components/vesync/common.py | 2 ++ homeassistant/components/vesync/config_flow.py | 9 ++++++--- homeassistant/components/vesync/switch.py | 6 ++++-- tests/components/vesync/test_config_flow.py | 6 ++++-- 5 files changed, 25 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/vesync/__init__.py b/homeassistant/components/vesync/__init__.py index 9ed71dbc5ee..0f905b8d7ef 100644 --- a/homeassistant/components/vesync/__init__.py +++ b/homeassistant/components/vesync/__init__.py @@ -1,20 +1,23 @@ """Etekcity VeSync integration.""" import logging -import voluptuous as vol + from pyvesync import VeSync -from homeassistant.const import CONF_USERNAME, CONF_PASSWORD +import voluptuous as vol + +from homeassistant.config_entries import SOURCE_IMPORT +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers import config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send -from homeassistant.config_entries import SOURCE_IMPORT + from .common import async_process_devices from .config_flow import configured_instances from .const import ( DOMAIN, - VS_DISPATCHERS, - VS_DISCOVERY, - VS_SWITCHES, SERVICE_UPDATE_DEVS, + VS_DISCOVERY, + VS_DISPATCHERS, VS_MANAGER, + VS_SWITCHES, ) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/vesync/common.py b/homeassistant/components/vesync/common.py index 361b3913283..d2ffa5281e9 100644 --- a/homeassistant/components/vesync/common.py +++ b/homeassistant/components/vesync/common.py @@ -1,6 +1,8 @@ """Common utilities for VeSync Component.""" import logging + from homeassistant.helpers.entity import ToggleEntity + from .const import VS_SWITCHES _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/vesync/config_flow.py b/homeassistant/components/vesync/config_flow.py index 168a3568392..8b0e8ae6781 100644 --- a/homeassistant/components/vesync/config_flow.py +++ b/homeassistant/components/vesync/config_flow.py @@ -1,11 +1,14 @@ """Config flow utilities.""" -import logging from collections import OrderedDict -import voluptuous as vol +import logging + from pyvesync import VeSync +import voluptuous as vol + from homeassistant import config_entries +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import callback -from homeassistant.const import CONF_USERNAME, CONF_PASSWORD + from .const import DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/vesync/switch.py b/homeassistant/components/vesync/switch.py index 5ca76a77254..6ab5c0c4368 100644 --- a/homeassistant/components/vesync/switch.py +++ b/homeassistant/components/vesync/switch.py @@ -1,10 +1,12 @@ """Support for Etekcity VeSync switches.""" import logging -from homeassistant.core import callback + from homeassistant.components.switch import SwitchDevice +from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from .const import VS_DISCOVERY, VS_DISPATCHERS, VS_SWITCHES, DOMAIN + from .common import VeSyncDevice +from .const import DOMAIN, VS_DISCOVERY, VS_DISPATCHERS, VS_SWITCHES _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/vesync/test_config_flow.py b/tests/components/vesync/test_config_flow.py index 205ce80b4b1..39b847effc5 100644 --- a/tests/components/vesync/test_config_flow.py +++ b/tests/components/vesync/test_config_flow.py @@ -1,8 +1,10 @@ """Test for vesync config flow.""" from unittest.mock import patch + from homeassistant import data_entry_flow -from homeassistant.components.vesync import config_flow, DOMAIN -from homeassistant.const import CONF_USERNAME, CONF_PASSWORD +from homeassistant.components.vesync import DOMAIN, config_flow +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME + from tests.common import MockConfigEntry From 9cf3ff319e34c04839929d2f639293f13119e643 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:56:51 +0100 Subject: [PATCH 170/677] Sort imports according to PEP8 for iaqualink (#29681) --- homeassistant/components/iaqualink/__init__.py | 6 ++---- homeassistant/components/iaqualink/binary_sensor.py | 2 +- homeassistant/components/iaqualink/climate.py | 2 +- homeassistant/components/iaqualink/config_flow.py | 3 +-- tests/components/iaqualink/test_config_flow.py | 1 + 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/iaqualink/__init__.py b/homeassistant/components/iaqualink/__init__.py index fc1eb3b248a..16c8deac72e 100644 --- a/homeassistant/components/iaqualink/__init__.py +++ b/homeassistant/components/iaqualink/__init__.py @@ -5,8 +5,6 @@ import logging from typing import Any, Dict import aiohttp.client_exceptions -import voluptuous as vol - from iaqualink import ( AqualinkBinarySensor, AqualinkClient, @@ -17,6 +15,7 @@ from iaqualink import ( AqualinkThermostat, AqualinkToggle, ) +import voluptuous as vol from homeassistant import config_entries from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN @@ -29,18 +28,17 @@ from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, ) from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_time_interval -import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType, HomeAssistantType from .const import DOMAIN, UPDATE_INTERVAL - _LOGGER = logging.getLogger(__name__) ATTR_CONFIG = "config" diff --git a/homeassistant/components/iaqualink/binary_sensor.py b/homeassistant/components/iaqualink/binary_sensor.py index 09c9322a587..30d419c1bce 100644 --- a/homeassistant/components/iaqualink/binary_sensor.py +++ b/homeassistant/components/iaqualink/binary_sensor.py @@ -2,9 +2,9 @@ import logging from homeassistant.components.binary_sensor import ( - BinarySensorDevice, DEVICE_CLASS_COLD, DOMAIN, + BinarySensorDevice, ) from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType diff --git a/homeassistant/components/iaqualink/climate.py b/homeassistant/components/iaqualink/climate.py index f41d17837c2..36f3303774a 100644 --- a/homeassistant/components/iaqualink/climate.py +++ b/homeassistant/components/iaqualink/climate.py @@ -22,7 +22,7 @@ from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.helpers.typing import HomeAssistantType from . import AqualinkEntity, refresh_system -from .const import DOMAIN as AQUALINK_DOMAIN, CLIMATE_SUPPORTED_MODES +from .const import CLIMATE_SUPPORTED_MODES, DOMAIN as AQUALINK_DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/iaqualink/config_flow.py b/homeassistant/components/iaqualink/config_flow.py index ec83477d253..d577fe448aa 100644 --- a/homeassistant/components/iaqualink/config_flow.py +++ b/homeassistant/components/iaqualink/config_flow.py @@ -1,9 +1,8 @@ """Config flow to configure zone component.""" from typing import Optional -import voluptuous as vol - from iaqualink import AqualinkClient, AqualinkLoginException +import voluptuous as vol from homeassistant import config_entries from homeassistant.const import CONF_PASSWORD, CONF_USERNAME diff --git a/tests/components/iaqualink/test_config_flow.py b/tests/components/iaqualink/test_config_flow.py index 5c4d75ee3c1..d2fa4633d80 100644 --- a/tests/components/iaqualink/test_config_flow.py +++ b/tests/components/iaqualink/test_config_flow.py @@ -5,6 +5,7 @@ import iaqualink import pytest from homeassistant.components.iaqualink import config_flow + from tests.common import MockConfigEntry, mock_coro DATA = {"username": "test@example.com", "password": "pass"} From df85a50f3bc19e17d5ce8ed3d4c1d17287c9e1c8 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:57:49 +0100 Subject: [PATCH 171/677] Sort imports according to PEP8 for sensor (#29683) --- homeassistant/components/sensor/__init__.py | 1 - homeassistant/components/sensor/device_condition.py | 6 +++--- homeassistant/components/sensor/device_trigger.py | 3 +-- tests/components/sensor/test_device_condition.py | 10 +++++----- tests/components/sensor/test_device_trigger.py | 11 ++++++----- 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index 53e4b0ffcf7..83711a07759 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -21,7 +21,6 @@ from homeassistant.helpers.config_validation import ( # noqa: F401 ) from homeassistant.helpers.entity_component import EntityComponent - # mypy: allow-untyped-defs, no-check-untyped-defs _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/device_condition.py b/homeassistant/components/sensor/device_condition.py index 259fb5dbab9..7417765f9f4 100644 --- a/homeassistant/components/sensor/device_condition.py +++ b/homeassistant/components/sensor/device_condition.py @@ -1,11 +1,11 @@ """Provides device conditions for sensors.""" from typing import Dict, List + import voluptuous as vol from homeassistant.components.device_automation.exceptions import ( InvalidDeviceAutomationConfig, ) -from homeassistant.core import HomeAssistant from homeassistant.const import ( ATTR_DEVICE_CLASS, ATTR_UNIT_OF_MEASUREMENT, @@ -22,16 +22,16 @@ from homeassistant.const import ( DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TIMESTAMP, ) +from homeassistant.core import HomeAssistant +from homeassistant.helpers import condition, config_validation as cv from homeassistant.helpers.entity_registry import ( async_entries_for_device, async_get_registry, ) -from homeassistant.helpers import condition, config_validation as cv from homeassistant.helpers.typing import ConfigType from . import DOMAIN - # mypy: allow-untyped-defs, no-check-untyped-defs DEVICE_CLASS_NONE = "none" diff --git a/homeassistant/components/sensor/device_trigger.py b/homeassistant/components/sensor/device_trigger.py index 73e55340da9..1af8a5e4ab0 100644 --- a/homeassistant/components/sensor/device_trigger.py +++ b/homeassistant/components/sensor/device_trigger.py @@ -23,12 +23,11 @@ from homeassistant.const import ( DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TIMESTAMP, ) -from homeassistant.helpers.entity_registry import async_entries_for_device from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.entity_registry import async_entries_for_device from . import DOMAIN - # mypy: allow-untyped-defs, no-check-untyped-defs DEVICE_CLASS_NONE = "none" diff --git a/tests/components/sensor/test_device_condition.py b/tests/components/sensor/test_device_condition.py index f3ff15c3ad9..bd6a6ce4928 100644 --- a/tests/components/sensor/test_device_condition.py +++ b/tests/components/sensor/test_device_condition.py @@ -1,20 +1,20 @@ """The test for sensor device automation.""" import pytest +import homeassistant.components.automation as automation from homeassistant.components.sensor import DOMAIN from homeassistant.components.sensor.device_condition import ENTITY_CONDITIONS -from homeassistant.const import STATE_UNKNOWN, CONF_PLATFORM -from homeassistant.setup import async_setup_component -import homeassistant.components.automation as automation +from homeassistant.const import CONF_PLATFORM, STATE_UNKNOWN from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component from tests.common import ( MockConfigEntry, + async_get_device_automation_capabilities, + async_get_device_automations, async_mock_service, mock_device_registry, mock_registry, - async_get_device_automations, - async_get_device_automation_capabilities, ) from tests.testing_config.custom_components.test.sensor import DEVICE_CLASSES diff --git a/tests/components/sensor/test_device_trigger.py b/tests/components/sensor/test_device_trigger.py index b7a921fff18..7bb69388c1d 100644 --- a/tests/components/sensor/test_device_trigger.py +++ b/tests/components/sensor/test_device_trigger.py @@ -1,23 +1,24 @@ """The test for sensor device automation.""" from datetime import timedelta + import pytest +import homeassistant.components.automation as automation from homeassistant.components.sensor import DOMAIN from homeassistant.components.sensor.device_trigger import ENTITY_TRIGGERS -from homeassistant.const import STATE_UNKNOWN, CONF_PLATFORM -from homeassistant.setup import async_setup_component -import homeassistant.components.automation as automation +from homeassistant.const import CONF_PLATFORM, STATE_UNKNOWN from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from tests.common import ( MockConfigEntry, async_fire_time_changed, + async_get_device_automation_capabilities, + async_get_device_automations, async_mock_service, mock_device_registry, mock_registry, - async_get_device_automations, - async_get_device_automation_capabilities, ) from tests.testing_config.custom_components.test.sensor import DEVICE_CLASSES From c7d61279bd6abce2223ab436d2614c98728c7c23 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:58:40 +0100 Subject: [PATCH 172/677] Sort imports according to PEP8 for ring (#29680) --- tests/components/ring/common.py | 2 +- tests/components/ring/conftest.py | 7 ++++--- tests/components/ring/test_binary_sensor.py | 5 +++-- tests/components/ring/test_init.py | 4 +++- tests/components/ring/test_light.py | 4 +++- tests/components/ring/test_sensor.py | 6 ++++-- tests/components/ring/test_switch.py | 4 +++- 7 files changed, 21 insertions(+), 11 deletions(-) diff --git a/tests/components/ring/common.py b/tests/components/ring/common.py index 1228f998618..e5042a935d6 100644 --- a/tests/components/ring/common.py +++ b/tests/components/ring/common.py @@ -1,6 +1,6 @@ """Common methods used across the tests for ring devices.""" -from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, CONF_SCAN_INTERVAL from homeassistant.components.ring import DOMAIN +from homeassistant.const import CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME from homeassistant.setup import async_setup_component diff --git a/tests/components/ring/conftest.py b/tests/components/ring/conftest.py index 14a29f78aae..b61840769a2 100644 --- a/tests/components/ring/conftest.py +++ b/tests/components/ring/conftest.py @@ -1,8 +1,9 @@ """Configuration for Ring tests.""" -import requests_mock -import pytest -from tests.common import load_fixture from asynctest import patch +import pytest +import requests_mock + +from tests.common import load_fixture @pytest.fixture(name="ring_mock") diff --git a/tests/components/ring/test_binary_sensor.py b/tests/components/ring/test_binary_sensor.py index d144a630863..c0b538b8eff 100644 --- a/tests/components/ring/test_binary_sensor.py +++ b/tests/components/ring/test_binary_sensor.py @@ -1,13 +1,14 @@ """The tests for the Ring binary sensor platform.""" import os import unittest + import requests_mock -from homeassistant.components.ring import binary_sensor as ring from homeassistant.components import ring as base_ring +from homeassistant.components.ring import binary_sensor as ring -from tests.components.ring.test_init import ATTRIBUTION, VALID_CONFIG from tests.common import get_test_config_dir, get_test_home_assistant, load_fixture +from tests.components.ring.test_init import ATTRIBUTION, VALID_CONFIG class TestRingBinarySensorSetup(unittest.TestCase): diff --git a/tests/components/ring/test_init.py b/tests/components/ring/test_init.py index 1a0d0dfec16..4d3fede89a9 100644 --- a/tests/components/ring/test_init.py +++ b/tests/components/ring/test_init.py @@ -1,9 +1,11 @@ """The tests for the Ring component.""" from copy import deepcopy +from datetime import timedelta import os import unittest + import requests_mock -from datetime import timedelta + from homeassistant import setup import homeassistant.components.ring as ring diff --git a/tests/components/ring/test_light.py b/tests/components/ring/test_light.py index e07867c19b2..56d39173d63 100644 --- a/tests/components/ring/test_light.py +++ b/tests/components/ring/test_light.py @@ -1,8 +1,10 @@ """The tests for the Ring light platform.""" from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN -from tests.common import load_fixture + from .common import setup_platform +from tests.common import load_fixture + async def test_entity_registry(hass, requests_mock): """Tests that the devices are registed in the entity registry.""" diff --git a/tests/components/ring/test_sensor.py b/tests/components/ring/test_sensor.py index 8559f1acc68..dd9d36f80a1 100644 --- a/tests/components/ring/test_sensor.py +++ b/tests/components/ring/test_sensor.py @@ -1,13 +1,15 @@ """The tests for the Ring sensor platform.""" import os import unittest + import requests_mock -import homeassistant.components.ring.sensor as ring from homeassistant.components import ring as base_ring +import homeassistant.components.ring.sensor as ring from homeassistant.helpers.icon import icon_for_battery_level -from tests.components.ring.test_init import ATTRIBUTION, VALID_CONFIG + from tests.common import get_test_config_dir, get_test_home_assistant, load_fixture +from tests.components.ring.test_init import ATTRIBUTION, VALID_CONFIG class TestRingSensorSetup(unittest.TestCase): diff --git a/tests/components/ring/test_switch.py b/tests/components/ring/test_switch.py index 864d16466da..15f4dd86a39 100644 --- a/tests/components/ring/test_switch.py +++ b/tests/components/ring/test_switch.py @@ -1,8 +1,10 @@ """The tests for the Ring switch platform.""" from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN -from tests.common import load_fixture + from .common import setup_platform +from tests.common import load_fixture + async def test_entity_registry(hass, requests_mock): """Tests that the devices are registed in the entity registry.""" From 3d10bb3647d050f6006f4725685fc5ad8f85ad59 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 11:59:38 +0100 Subject: [PATCH 173/677] Sort imports according to PEP8 for http (#29679) --- tests/components/http/__init__.py | 1 - tests/components/http/test_auth.py | 7 ++++--- tests/components/http/test_ban.py | 13 ++++++------- tests/components/http/test_cors.py | 5 ++--- tests/components/http/test_init.py | 5 ++--- tests/components/http/test_real_ip.py | 7 ++++--- tests/components/http/test_view.py | 2 +- 7 files changed, 19 insertions(+), 21 deletions(-) diff --git a/tests/components/http/__init__.py b/tests/components/http/__init__.py index db5e1ea5c7a..e96f4a7fcf2 100644 --- a/tests/components/http/__init__.py +++ b/tests/components/http/__init__.py @@ -5,7 +5,6 @@ from aiohttp import web from homeassistant.components.http.const import KEY_REAL_IP - # Relic from the past. Kept here so we can run negative tests. HTTP_HEADER_HA_AUTH = "X-HA-access" diff --git a/tests/components/http/test_auth.py b/tests/components/http/test_auth.py index 499ceab1556..3617690eb3b 100644 --- a/tests/components/http/test_auth.py +++ b/tests/components/http/test_auth.py @@ -3,16 +3,17 @@ from datetime import timedelta from ipaddress import ip_network from unittest.mock import patch -import pytest from aiohttp import BasicAuth, web from aiohttp.web_exceptions import HTTPUnauthorized +import pytest from homeassistant.auth.providers import trusted_networks -from homeassistant.components.http.auth import setup_auth, async_sign_path +from homeassistant.components.http.auth import async_sign_path, setup_auth from homeassistant.components.http.const import KEY_AUTHENTICATED from homeassistant.components.http.real_ip import setup_real_ip from homeassistant.setup import async_setup_component -from . import mock_real_ip, HTTP_HEADER_HA_AUTH + +from . import HTTP_HEADER_HA_AUTH, mock_real_ip API_PASSWORD = "test-password" diff --git a/tests/components/http/test_ban.py b/tests/components/http/test_ban.py index f50afcef8a8..8d9d19b6a12 100644 --- a/tests/components/http/test_ban.py +++ b/tests/components/http/test_ban.py @@ -1,29 +1,28 @@ """The tests for the Home Assistant HTTP component.""" # pylint: disable=protected-access from ipaddress import ip_address -from unittest.mock import patch, mock_open, Mock +from unittest.mock import Mock, mock_open, patch 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 import KEY_AUTHENTICATED from homeassistant.components.http.ban import ( - IpBan, IP_BANS_FILE, - setup_bans, KEY_BANNED_IPS, KEY_FAILED_LOGIN_ATTEMPTS, + IpBan, + setup_bans, ) +from homeassistant.components.http.view import request_handler_factory +from homeassistant.setup import async_setup_component from . import mock_real_ip from tests.common import mock_coro - BANNED_IPS = ["200.201.202.203", "100.64.0.2"] diff --git a/tests/components/http/test_cors.py b/tests/components/http/test_cors.py index 1cea900d971..04447191fd5 100644 --- a/tests/components/http/test_cors.py +++ b/tests/components/http/test_cors.py @@ -4,8 +4,8 @@ from unittest.mock import patch from aiohttp import web from aiohttp.hdrs import ( - ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_ALLOW_HEADERS, + ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_REQUEST_HEADERS, ACCESS_CONTROL_REQUEST_METHOD, AUTHORIZATION, @@ -13,13 +13,12 @@ from aiohttp.hdrs import ( ) import pytest -from homeassistant.setup import async_setup_component from homeassistant.components.http.cors import setup_cors from homeassistant.components.http.view import HomeAssistantView +from homeassistant.setup import async_setup_component from . import HTTP_HEADER_HA_AUTH - TRUSTED_ORIGIN = "https://home-assistant.io" diff --git a/tests/components/http/test_init.py b/tests/components/http/test_init.py index ad8e3ac10fd..212ae7499ab 100644 --- a/tests/components/http/test_init.py +++ b/tests/components/http/test_init.py @@ -3,10 +3,9 @@ import logging import unittest from unittest.mock import patch -from homeassistant.setup import async_setup_component - import homeassistant.components.http as http -from homeassistant.util.ssl import server_context_modern, server_context_intermediate +from homeassistant.setup import async_setup_component +from homeassistant.util.ssl import server_context_intermediate, server_context_modern class TestView(http.HomeAssistantView): diff --git a/tests/components/http/test_real_ip.py b/tests/components/http/test_real_ip.py index 581624e0c0a..2cb74df3176 100644 --- a/tests/components/http/test_real_ip.py +++ b/tests/components/http/test_real_ip.py @@ -1,10 +1,11 @@ """Test real IP middleware.""" -from aiohttp import web -from aiohttp.hdrs import X_FORWARDED_FOR from ipaddress import ip_network -from homeassistant.components.http.real_ip import setup_real_ip +from aiohttp import web +from aiohttp.hdrs import X_FORWARDED_FOR + from homeassistant.components.http.const import KEY_REAL_IP +from homeassistant.components.http.real_ip import setup_real_ip async def mock_handler(request): diff --git a/tests/components/http/test_view.py b/tests/components/http/test_view.py index 1cbe4c5a030..414ad4e8cb0 100644 --- a/tests/components/http/test_view.py +++ b/tests/components/http/test_view.py @@ -2,8 +2,8 @@ from unittest.mock import Mock from aiohttp.web_exceptions import ( - HTTPInternalServerError, HTTPBadRequest, + HTTPInternalServerError, HTTPUnauthorized, ) import pytest From 69b113c539e52fc64f0aa295ca21647a7f5ca29b Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 12:04:40 +0100 Subject: [PATCH 174/677] Sort imports according to PEP8 for dyson (#29678) --- tests/components/dyson/test_air_quality.py | 6 +++--- tests/components/dyson/test_climate.py | 3 ++- tests/components/dyson/test_fan.py | 15 ++++++++------- tests/components/dyson/test_init.py | 1 + tests/components/dyson/test_sensor.py | 3 ++- tests/components/dyson/test_vacuum.py | 1 + 6 files changed, 17 insertions(+), 12 deletions(-) diff --git a/tests/components/dyson/test_air_quality.py b/tests/components/dyson/test_air_quality.py index d1ef0c73a51..ed2fbed34f3 100644 --- a/tests/components/dyson/test_air_quality.py +++ b/tests/components/dyson/test_air_quality.py @@ -6,14 +6,14 @@ import asynctest from libpurecool.dyson_pure_cool import DysonPureCool from libpurecool.dyson_pure_state_v2 import DysonEnvironmentalSensorV2State -import homeassistant.components.dyson.air_quality as dyson from homeassistant.components import dyson as dyson_parent from homeassistant.components.air_quality import ( - DOMAIN as AIQ_DOMAIN, + ATTR_NO2, ATTR_PM_2_5, ATTR_PM_10, - ATTR_NO2, + DOMAIN as AIQ_DOMAIN, ) +import homeassistant.components.dyson.air_quality as dyson from homeassistant.helpers import discovery from homeassistant.setup import async_setup_component diff --git a/tests/components/dyson/test_climate.py b/tests/components/dyson/test_climate.py index c3786f47507..dbc477203a1 100644 --- a/tests/components/dyson/test_climate.py +++ b/tests/components/dyson/test_climate.py @@ -9,8 +9,9 @@ from libpurecool.dyson_pure_state import DysonPureHotCoolState from homeassistant.components import dyson as dyson_parent from homeassistant.components.dyson import climate as dyson -from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE +from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS from homeassistant.setup import async_setup_component + from tests.common import get_test_home_assistant diff --git a/tests/components/dyson/test_fan.py b/tests/components/dyson/test_fan.py index 03fa09110a8..5f6b124a3d5 100644 --- a/tests/components/dyson/test_fan.py +++ b/tests/components/dyson/test_fan.py @@ -4,27 +4,28 @@ import unittest from unittest import mock import asynctest -from libpurecool.const import FanSpeed, FanMode, NightMode, Oscillation +from libpurecool.const import FanMode, FanSpeed, NightMode, Oscillation from libpurecool.dyson_pure_cool import DysonPureCool from libpurecool.dyson_pure_cool_link import DysonPureCoolLink from libpurecool.dyson_pure_state import DysonPureCoolState from libpurecool.dyson_pure_state_v2 import DysonPureCoolV2State -import homeassistant.components.dyson.fan as dyson from homeassistant.components import dyson as dyson_parent from homeassistant.components.dyson import DYSON_DEVICES +import homeassistant.components.dyson.fan as dyson from homeassistant.components.fan import ( - DOMAIN, - ATTR_SPEED, ATTR_OSCILLATING, + ATTR_SPEED, + DOMAIN, + SERVICE_OSCILLATE, + SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, - SPEED_HIGH, - SERVICE_OSCILLATE, ) -from homeassistant.const import SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID +from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON from homeassistant.helpers import discovery from homeassistant.setup import async_setup_component + from tests.common import get_test_home_assistant diff --git a/tests/components/dyson/test_init.py b/tests/components/dyson/test_init.py index fc4ede75e32..067f6a360ed 100644 --- a/tests/components/dyson/test_init.py +++ b/tests/components/dyson/test_init.py @@ -3,6 +3,7 @@ import unittest from unittest import mock from homeassistant.components import dyson + from tests.common import get_test_home_assistant diff --git a/tests/components/dyson/test_sensor.py b/tests/components/dyson/test_sensor.py index 8ea9d1ff5ec..442ea913b46 100644 --- a/tests/components/dyson/test_sensor.py +++ b/tests/components/dyson/test_sensor.py @@ -8,9 +8,10 @@ from libpurecool.dyson_pure_cool_link import DysonPureCoolLink from homeassistant.components import dyson as dyson_parent from homeassistant.components.dyson import sensor as dyson -from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT, STATE_OFF +from homeassistant.const import STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.helpers import discovery from homeassistant.setup import async_setup_component + from tests.common import get_test_home_assistant diff --git a/tests/components/dyson/test_vacuum.py b/tests/components/dyson/test_vacuum.py index 3e11e929b32..fc801cbe649 100644 --- a/tests/components/dyson/test_vacuum.py +++ b/tests/components/dyson/test_vacuum.py @@ -7,6 +7,7 @@ from libpurecool.dyson_360_eye import Dyson360Eye from homeassistant.components.dyson import vacuum as dyson from homeassistant.components.dyson.vacuum import Dyson360EyeDevice + from tests.common import get_test_home_assistant From ad58e607dfe96e65fe2d18d08bc8ae5d265451e3 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 12:07:10 +0100 Subject: [PATCH 175/677] Sort imports according to PEP8 for somfy (#29675) --- homeassistant/components/somfy/__init__.py | 6 +++--- homeassistant/components/somfy/api.py | 2 +- homeassistant/components/somfy/config_flow.py | 1 + homeassistant/components/somfy/cover.py | 7 ++++--- homeassistant/components/somfy/switch.py | 3 ++- tests/components/somfy/test_config_flow.py | 4 ++-- 6 files changed, 13 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/somfy/__init__.py b/homeassistant/components/somfy/__init__.py index 1368725777b..365c6839300 100644 --- a/homeassistant/components/somfy/__init__.py +++ b/homeassistant/components/somfy/__init__.py @@ -5,15 +5,15 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/integrations/somfy/ """ import asyncio -import logging from datetime import timedelta +import logging -import voluptuous as vol from requests import HTTPError +import voluptuous as vol -from homeassistant.helpers import config_validation as cv, config_entry_oauth2_flow from homeassistant.components.somfy import config_flow from homeassistant.config_entries import ConfigEntry +from homeassistant.helpers import config_entry_oauth2_flow, config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import Throttle diff --git a/homeassistant/components/somfy/api.py b/homeassistant/components/somfy/api.py index 1cfea8ff7d8..b2516cb36c4 100644 --- a/homeassistant/components/somfy/api.py +++ b/homeassistant/components/somfy/api.py @@ -4,7 +4,7 @@ from typing import Dict, Union from pymfy.api import somfy_api -from homeassistant import core, config_entries +from homeassistant import config_entries, core from homeassistant.helpers import config_entry_oauth2_flow diff --git a/homeassistant/components/somfy/config_flow.py b/homeassistant/components/somfy/config_flow.py index cb180d4e247..2d143fbd196 100644 --- a/homeassistant/components/somfy/config_flow.py +++ b/homeassistant/components/somfy/config_flow.py @@ -3,6 +3,7 @@ import logging from homeassistant import config_entries from homeassistant.helpers import config_entry_oauth2_flow + from .const import DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/somfy/cover.py b/homeassistant/components/somfy/cover.py index 12f12676f92..5eabe7ee07a 100644 --- a/homeassistant/components/somfy/cover.py +++ b/homeassistant/components/somfy/cover.py @@ -1,13 +1,14 @@ """Support for Somfy Covers.""" -from pymfy.api.devices.category import Category from pymfy.api.devices.blind import Blind +from pymfy.api.devices.category import Category from homeassistant.components.cover import ( - CoverDevice, ATTR_POSITION, ATTR_TILT_POSITION, + CoverDevice, ) -from . import DOMAIN, SomfyEntity, DEVICES, API + +from . import API, DEVICES, DOMAIN, SomfyEntity async def async_setup_entry(hass, config_entry, async_add_entities): diff --git a/homeassistant/components/somfy/switch.py b/homeassistant/components/somfy/switch.py index 58ad2e5905d..bc31d68ec1d 100644 --- a/homeassistant/components/somfy/switch.py +++ b/homeassistant/components/somfy/switch.py @@ -3,7 +3,8 @@ from pymfy.api.devices.camera_protect import CameraProtect from pymfy.api.devices.category import Category from homeassistant.components.switch import SwitchDevice -from . import DOMAIN, SomfyEntity, DEVICES, API + +from . import API, DEVICES, DOMAIN, SomfyEntity async def async_setup_entry(hass, config_entry, async_add_entities): diff --git a/tests/components/somfy/test_config_flow.py b/tests/components/somfy/test_config_flow.py index d42e7b8e367..f195b640240 100644 --- a/tests/components/somfy/test_config_flow.py +++ b/tests/components/somfy/test_config_flow.py @@ -4,8 +4,8 @@ from unittest.mock import patch import pytest -from homeassistant import data_entry_flow, setup, config_entries -from homeassistant.components.somfy import config_flow, DOMAIN +from homeassistant import config_entries, data_entry_flow, setup +from homeassistant.components.somfy import DOMAIN, config_flow from homeassistant.helpers import config_entry_oauth2_flow from tests.common import MockConfigEntry From 186799794d39ecab4df5bd92b5c771986305712c Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 12:08:51 +0100 Subject: [PATCH 176/677] Sort imports according to PEP8 for nest (#29670) --- homeassistant/components/nest/__init__.py | 8 ++++---- homeassistant/components/nest/camera.py | 4 ++-- homeassistant/components/nest/climate.py | 14 +++++++------- homeassistant/components/nest/config_flow.py | 1 - homeassistant/components/nest/local_auth.py | 3 ++- tests/components/nest/test_config_flow.py | 2 +- tests/components/nest/test_local_auth.py | 4 ++-- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/nest/__init__.py b/homeassistant/components/nest/__init__.py index 32bbd009417..e2a1479595e 100644 --- a/homeassistant/components/nest/__init__.py +++ b/homeassistant/components/nest/__init__.py @@ -1,11 +1,11 @@ """Support for Nest devices.""" +from datetime import datetime, timedelta import logging import socket -from datetime import datetime, timedelta import threading from nest import Nest -from nest.nest import AuthorizationError, APIError +from nest.nest import APIError, AuthorizationError import voluptuous as vol from homeassistant import config_entries @@ -20,11 +20,11 @@ from homeassistant.const import ( ) from homeassistant.core import callback from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.dispatcher import dispatcher_send, async_dispatcher_connect +from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send from homeassistant.helpers.entity import Entity -from .const import DOMAIN from . import local_auth +from .const import DOMAIN _CONFIGURING = {} _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/nest/camera.py b/homeassistant/components/nest/camera.py index efc0bfbc822..34b4f6c5693 100644 --- a/homeassistant/components/nest/camera.py +++ b/homeassistant/components/nest/camera.py @@ -1,11 +1,11 @@ """Support for Nest Cameras.""" -import logging from datetime import timedelta +import logging import requests from homeassistant.components import nest -from homeassistant.components.camera import PLATFORM_SCHEMA, Camera, SUPPORT_ON_OFF +from homeassistant.components.camera import PLATFORM_SCHEMA, SUPPORT_ON_OFF, Camera from homeassistant.util.dt import utcnow _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/nest/climate.py b/homeassistant/components/nest/climate.py index 795ce5c80e9..f75e3a692f3 100644 --- a/homeassistant/components/nest/climate.py +++ b/homeassistant/components/nest/climate.py @@ -8,22 +8,22 @@ from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice from homeassistant.components.climate.const import ( ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, + CURRENT_HVAC_COOL, + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, FAN_AUTO, FAN_ON, HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF, - SUPPORT_PRESET_MODE, - SUPPORT_FAN_MODE, - SUPPORT_TARGET_TEMPERATURE, - SUPPORT_TARGET_TEMPERATURE_RANGE, PRESET_AWAY, PRESET_ECO, PRESET_NONE, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - CURRENT_HVAC_COOL, + SUPPORT_FAN_MODE, + SUPPORT_PRESET_MODE, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_TARGET_TEMPERATURE_RANGE, ) from homeassistant.const import ( ATTR_TEMPERATURE, diff --git a/homeassistant/components/nest/config_flow.py b/homeassistant/components/nest/config_flow.py index b78896b0499..b8fa2ad93f5 100644 --- a/homeassistant/components/nest/config_flow.py +++ b/homeassistant/components/nest/config_flow.py @@ -14,7 +14,6 @@ from homeassistant.util.json import load_json from .const import DOMAIN - DATA_FLOW_IMPL = "nest_flow_implementation" _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/nest/local_auth.py b/homeassistant/components/nest/local_auth.py index 38d1827326d..8b0af5011ec 100644 --- a/homeassistant/components/nest/local_auth.py +++ b/homeassistant/components/nest/local_auth.py @@ -2,9 +2,10 @@ import asyncio from functools import partial -from nest.nest import NestAuth, AUTHORIZE_URL, AuthorizationError +from nest.nest import AUTHORIZE_URL, AuthorizationError, NestAuth from homeassistant.core import callback + from . import config_flow from .const import DOMAIN diff --git a/tests/components/nest/test_config_flow.py b/tests/components/nest/test_config_flow.py index 4018a94b666..ec6218fb0d7 100644 --- a/tests/components/nest/test_config_flow.py +++ b/tests/components/nest/test_config_flow.py @@ -3,8 +3,8 @@ import asyncio from unittest.mock import Mock, patch from homeassistant import data_entry_flow +from homeassistant.components.nest import DOMAIN, config_flow from homeassistant.setup import async_setup_component -from homeassistant.components.nest import config_flow, DOMAIN from tests.common import mock_coro diff --git a/tests/components/nest/test_local_auth.py b/tests/components/nest/test_local_auth.py index bf3c3e4230a..491b9bd9e07 100644 --- a/tests/components/nest/test_local_auth.py +++ b/tests/components/nest/test_local_auth.py @@ -1,11 +1,11 @@ """Test Nest local auth.""" -from homeassistant.components.nest import const, config_flow, local_auth from urllib.parse import parse_qsl import pytest - import requests_mock as rmock +from homeassistant.components.nest import config_flow, const, local_auth + @pytest.fixture def registered_flow(hass): From 04225ba802fa70267ee6974956c7f8906bbae0c2 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 12:09:49 +0100 Subject: [PATCH 177/677] Sort imports according to PEP8 for rest (#29674) --- homeassistant/components/rest/notify.py | 17 ++++++++--------- homeassistant/components/rest/sensor.py | 14 +++++++------- homeassistant/components/rest/switch.py | 6 +++--- tests/components/rest/test_binary_sensor.py | 14 +++++++------- tests/components/rest/test_sensor.py | 16 ++++++++-------- tests/components/rest/test_switch.py | 5 +++-- 6 files changed, 36 insertions(+), 36 deletions(-) diff --git a/homeassistant/components/rest/notify.py b/homeassistant/components/rest/notify.py index f2bfcf3aba7..4f3de14b731 100644 --- a/homeassistant/components/rest/notify.py +++ b/homeassistant/components/rest/notify.py @@ -4,6 +4,14 @@ import logging import requests import voluptuous as vol +from homeassistant.components.notify import ( + ATTR_MESSAGE, + ATTR_TARGET, + ATTR_TITLE, + ATTR_TITLE_DEFAULT, + PLATFORM_SCHEMA, + BaseNotificationService, +) from homeassistant.const import ( CONF_AUTHENTICATION, CONF_HEADERS, @@ -18,15 +26,6 @@ from homeassistant.const import ( ) import homeassistant.helpers.config_validation as cv -from homeassistant.components.notify import ( - ATTR_TARGET, - ATTR_TITLE, - ATTR_TITLE_DEFAULT, - ATTR_MESSAGE, - PLATFORM_SCHEMA, - BaseNotificationService, -) - CONF_DATA = "data" CONF_DATA_TEMPLATE = "data_template" CONF_MESSAGE_PARAMETER_NAME = "message_param_name" diff --git a/homeassistant/components/rest/sensor.py b/homeassistant/components/rest/sensor.py index 6fdf5ce7221..51120cb350c 100644 --- a/homeassistant/components/rest/sensor.py +++ b/homeassistant/components/rest/sensor.py @@ -1,34 +1,34 @@ """Support for RESTful API sensors.""" -import logging import json +import logging -import voluptuous as vol import requests from requests.auth import HTTPBasicAuth, HTTPDigestAuth +import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA +from homeassistant.components.sensor import DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA from homeassistant.const import ( CONF_AUTHENTICATION, + CONF_DEVICE_CLASS, CONF_FORCE_UPDATE, CONF_HEADERS, - CONF_NAME, CONF_METHOD, + CONF_NAME, CONF_PASSWORD, CONF_PAYLOAD, CONF_RESOURCE, CONF_RESOURCE_TEMPLATE, + CONF_TIMEOUT, CONF_UNIT_OF_MEASUREMENT, CONF_USERNAME, - CONF_TIMEOUT, CONF_VALUE_TEMPLATE, CONF_VERIFY_SSL, - CONF_DEVICE_CLASS, HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION, ) from homeassistant.exceptions import PlatformNotReady -from homeassistant.helpers.entity import Entity import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/rest/switch.py b/homeassistant/components/rest/switch.py index a02a8507194..fe409a46be7 100644 --- a/homeassistant/components/rest/switch.py +++ b/homeassistant/components/rest/switch.py @@ -6,15 +6,15 @@ import aiohttp import async_timeout import voluptuous as vol -from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice from homeassistant.const import ( CONF_HEADERS, + CONF_METHOD, CONF_NAME, + CONF_PASSWORD, CONF_RESOURCE, CONF_TIMEOUT, - CONF_METHOD, CONF_USERNAME, - CONF_PASSWORD, CONF_VERIFY_SSL, ) from homeassistant.helpers.aiohttp_client import async_get_clientsession diff --git a/tests/components/rest/test_binary_sensor.py b/tests/components/rest/test_binary_sensor.py index 8993be6a7a1..a4850793ca7 100644 --- a/tests/components/rest/test_binary_sensor.py +++ b/tests/components/rest/test_binary_sensor.py @@ -1,21 +1,21 @@ """The tests for the REST binary sensor platform.""" import unittest -from pytest import raises -from unittest.mock import patch, Mock +from unittest.mock import Mock, patch +import pytest +from pytest import raises import requests from requests.exceptions import Timeout import requests_mock -from homeassistant.exceptions import PlatformNotReady -from homeassistant.setup import setup_component import homeassistant.components.binary_sensor as binary_sensor import homeassistant.components.rest.binary_sensor as rest -from homeassistant.const import STATE_ON, STATE_OFF +from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers import template +from homeassistant.setup import setup_component -from tests.common import get_test_home_assistant, assert_setup_component -import pytest +from tests.common import assert_setup_component, get_test_home_assistant class TestRestBinarySensorSetup(unittest.TestCase): diff --git a/tests/components/rest/test_sensor.py b/tests/components/rest/test_sensor.py index d770f21a403..7edbfa065ad 100644 --- a/tests/components/rest/test_sensor.py +++ b/tests/components/rest/test_sensor.py @@ -1,20 +1,20 @@ """The tests for the REST sensor platform.""" import unittest -from pytest import raises -from unittest.mock import patch, Mock +from unittest.mock import Mock, patch +import pytest +from pytest import raises import requests -from requests.exceptions import Timeout, RequestException +from requests.exceptions import RequestException, Timeout import requests_mock -from homeassistant.exceptions import PlatformNotReady -from homeassistant.setup import setup_component -import homeassistant.components.sensor as sensor import homeassistant.components.rest.sensor as rest +import homeassistant.components.sensor as sensor +from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.config_validation import template +from homeassistant.setup import setup_component -from tests.common import get_test_home_assistant, assert_setup_component -import pytest +from tests.common import assert_setup_component, get_test_home_assistant class TestRestSensorSetup(unittest.TestCase): diff --git a/tests/components/rest/test_switch.py b/tests/components/rest/test_switch.py index 81430cff349..d1e4ac05514 100644 --- a/tests/components/rest/test_switch.py +++ b/tests/components/rest/test_switch.py @@ -4,9 +4,10 @@ import asyncio import aiohttp import homeassistant.components.rest.switch as rest -from homeassistant.setup import setup_component from homeassistant.helpers.template import Template -from tests.common import get_test_home_assistant, assert_setup_component +from homeassistant.setup import setup_component + +from tests.common import assert_setup_component, get_test_home_assistant class TestRestSwitchSetup: From d62993c5af2e43d8639c388412b6c68040b62a00 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 12:10:38 +0100 Subject: [PATCH 178/677] Sort imports according to PEP8 for pilight (#29673) --- homeassistant/components/pilight/__init__.py | 19 +++++++++---------- .../components/pilight/binary_sensor.py | 2 +- homeassistant/components/pilight/sensor.py | 6 +++--- homeassistant/components/pilight/switch.py | 10 +++++----- tests/components/pilight/test_init.py | 8 ++++---- tests/components/pilight/test_sensor.py | 6 +++--- 6 files changed, 25 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/pilight/__init__.py b/homeassistant/components/pilight/__init__.py index e8cc0862a53..50ee1b248b0 100644 --- a/homeassistant/components/pilight/__init__.py +++ b/homeassistant/components/pilight/__init__.py @@ -1,25 +1,24 @@ """Component to create an interface to a Pilight daemon.""" -import logging +from datetime import timedelta import functools +import logging import socket import threading -from datetime import timedelta - -import voluptuous as vol from pilight import pilight +import voluptuous as vol -from homeassistant.helpers.event import track_point_in_utc_time -from homeassistant.util import dt as dt_util -import homeassistant.helpers.config_validation as cv from homeassistant.const import ( - EVENT_HOMEASSISTANT_START, - EVENT_HOMEASSISTANT_STOP, CONF_HOST, CONF_PORT, - CONF_WHITELIST, CONF_PROTOCOL, + CONF_WHITELIST, + EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP, ) +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.event import track_point_in_utc_time +from homeassistant.util import dt as dt_util _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/pilight/binary_sensor.py b/homeassistant/components/pilight/binary_sensor.py index a8f7d84b9b1..ae6d562725d 100644 --- a/homeassistant/components/pilight/binary_sensor.py +++ b/homeassistant/components/pilight/binary_sensor.py @@ -3,6 +3,7 @@ import datetime import logging import voluptuous as vol + from homeassistant.components import pilight from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice from homeassistant.const import ( @@ -16,7 +17,6 @@ from homeassistant.helpers import config_validation as cv from homeassistant.helpers.event import track_point_in_time from homeassistant.util import dt as dt_util - _LOGGER = logging.getLogger(__name__) CONF_VARIABLE = "variable" diff --git a/homeassistant/components/pilight/sensor.py b/homeassistant/components/pilight/sensor.py index 006bebf74bb..e8c7b4bd4b6 100644 --- a/homeassistant/components/pilight/sensor.py +++ b/homeassistant/components/pilight/sensor.py @@ -3,11 +3,11 @@ import logging import voluptuous as vol -from homeassistant.const import CONF_NAME, CONF_UNIT_OF_MEASUREMENT, CONF_PAYLOAD -from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.helpers.entity import Entity from homeassistant.components import pilight +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import CONF_NAME, CONF_PAYLOAD, CONF_UNIT_OF_MEASUREMENT import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/pilight/switch.py b/homeassistant/components/pilight/switch.py index fb95b91dfd8..8be199921dc 100644 --- a/homeassistant/components/pilight/switch.py +++ b/homeassistant/components/pilight/switch.py @@ -3,17 +3,17 @@ import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components import pilight -from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice from homeassistant.const import ( - CONF_NAME, CONF_ID, - CONF_SWITCHES, - CONF_STATE, + CONF_NAME, CONF_PROTOCOL, + CONF_STATE, + CONF_SWITCHES, STATE_ON, ) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.restore_state import RestoreEntity _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/pilight/test_init.py b/tests/components/pilight/test_init.py index e45c7ddc02f..2a2342a90dc 100644 --- a/tests/components/pilight/test_init.py +++ b/tests/components/pilight/test_init.py @@ -1,18 +1,18 @@ """The tests for the pilight component.""" +from datetime import timedelta import logging +import socket import unittest from unittest.mock import patch -import socket -from datetime import timedelta import pytest from homeassistant import core as ha -from homeassistant.setup import setup_component from homeassistant.components import pilight +from homeassistant.setup import setup_component from homeassistant.util import dt as dt_util -from tests.common import get_test_home_assistant, assert_setup_component +from tests.common import assert_setup_component, get_test_home_assistant _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/pilight/test_sensor.py b/tests/components/pilight/test_sensor.py index 721b04386fe..4bc1e80b07a 100644 --- a/tests/components/pilight/test_sensor.py +++ b/tests/components/pilight/test_sensor.py @@ -1,11 +1,11 @@ """The tests for the Pilight sensor platform.""" import logging -from homeassistant.setup import setup_component -import homeassistant.components.sensor as sensor from homeassistant.components import pilight +import homeassistant.components.sensor as sensor +from homeassistant.setup import setup_component -from tests.common import get_test_home_assistant, assert_setup_component, mock_component +from tests.common import assert_setup_component, get_test_home_assistant, mock_component HASS = None From 05daa817f5899b18e3c8018acb8ec05aa03b5e1f Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 12:11:27 +0100 Subject: [PATCH 179/677] Sort imports according to PEP8 for owntracks (#29672) --- .../components/owntracks/__init__.py | 2 +- .../components/owntracks/config_flow.py | 2 +- .../components/owntracks/device_tracker.py | 19 ++++++++++--------- .../components/owntracks/messages.py | 5 ++--- .../components/owntracks/test_config_flow.py | 6 +++--- tests/components/owntracks/test_helper.py | 1 + tests/components/owntracks/test_init.py | 5 +++-- 7 files changed, 21 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/owntracks/__init__.py b/homeassistant/components/owntracks/__init__.py index 8556e8a7556..b75be465aa1 100644 --- a/homeassistant/components/owntracks/__init__.py +++ b/homeassistant/components/owntracks/__init__.py @@ -14,8 +14,8 @@ from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.setup import async_when_setup -from .const import DOMAIN from .config_flow import CONF_SECRET +from .const import DOMAIN from .messages import async_handle_message _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/owntracks/config_flow.py b/homeassistant/components/owntracks/config_flow.py index 1a8bb838e18..82f89de6363 100644 --- a/homeassistant/components/owntracks/config_flow.py +++ b/homeassistant/components/owntracks/config_flow.py @@ -1,7 +1,7 @@ """Config flow for OwnTracks.""" from homeassistant import config_entries -from homeassistant.const import CONF_WEBHOOK_ID from homeassistant.auth.util import generate_secret +from homeassistant.const import CONF_WEBHOOK_ID from .const import DOMAIN # noqa pylint: disable=unused-import from .helper import supports_encryption diff --git a/homeassistant/components/owntracks/device_tracker.py b/homeassistant/components/owntracks/device_tracker.py index 6d3254eda99..00fa023d6c1 100644 --- a/homeassistant/components/owntracks/device_tracker.py +++ b/homeassistant/components/owntracks/device_tracker.py @@ -1,21 +1,22 @@ """Device tracker platform that adds support for OwnTracks over MQTT.""" import logging -from homeassistant.core import callback +from homeassistant.components.device_tracker.config_entry import TrackerEntity +from homeassistant.components.device_tracker.const import ( + ATTR_SOURCE_TYPE, + ENTITY_ID_FORMAT, + SOURCE_TYPE_GPS, +) from homeassistant.const import ( + ATTR_BATTERY_LEVEL, ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE, - ATTR_BATTERY_LEVEL, ) -from homeassistant.components.device_tracker.const import ( - ENTITY_ID_FORMAT, - ATTR_SOURCE_TYPE, - SOURCE_TYPE_GPS, -) -from homeassistant.components.device_tracker.config_entry import TrackerEntity -from homeassistant.helpers.restore_state import RestoreEntity +from homeassistant.core import callback from homeassistant.helpers import device_registry +from homeassistant.helpers.restore_state import RestoreEntity + from . import DOMAIN as OT_DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/owntracks/messages.py b/homeassistant/components/owntracks/messages.py index 7c388f9eb17..d357843c42e 100644 --- a/homeassistant/components/owntracks/messages.py +++ b/homeassistant/components/owntracks/messages.py @@ -2,15 +2,14 @@ import json import logging -from nacl.secret import SecretBox from nacl.encoding import Base64Encoder +from nacl.secret import SecretBox from homeassistant.components import zone as zone_comp from homeassistant.components.device_tracker import ( - SOURCE_TYPE_GPS, SOURCE_TYPE_BLUETOOTH_LE, + SOURCE_TYPE_GPS, ) - from homeassistant.const import STATE_HOME from homeassistant.util import decorator, slugify diff --git a/tests/components/owntracks/test_config_flow.py b/tests/components/owntracks/test_config_flow.py index 54e33a1ab61..d48c3c43a25 100644 --- a/tests/components/owntracks/test_config_flow.py +++ b/tests/components/owntracks/test_config_flow.py @@ -1,16 +1,16 @@ """Tests for OwnTracks config flow.""" from unittest.mock import Mock, patch + import pytest from homeassistant import data_entry_flow -from homeassistant.const import CONF_WEBHOOK_ID from homeassistant.components.owntracks import config_flow from homeassistant.components.owntracks.config_flow import CONF_CLOUDHOOK, CONF_SECRET from homeassistant.components.owntracks.const import DOMAIN +from homeassistant.const import CONF_WEBHOOK_ID from homeassistant.setup import async_setup_component -from tests.common import mock_coro, MockConfigEntry - +from tests.common import MockConfigEntry, mock_coro CONF_WEBHOOK_URL = "webhook_url" diff --git a/tests/components/owntracks/test_helper.py b/tests/components/owntracks/test_helper.py index f870ce82dd3..2c06ac0c4e7 100644 --- a/tests/components/owntracks/test_helper.py +++ b/tests/components/owntracks/test_helper.py @@ -1,5 +1,6 @@ """Test the owntracks_http platform.""" from unittest.mock import patch + import pytest from homeassistant.components.owntracks import helper diff --git a/tests/components/owntracks/test_init.py b/tests/components/owntracks/test_init.py index aab772d64e3..78ae687e208 100644 --- a/tests/components/owntracks/test_init.py +++ b/tests/components/owntracks/test_init.py @@ -3,9 +3,10 @@ import asyncio import pytest -from homeassistant.setup import async_setup_component from homeassistant.components import owntracks -from tests.common import mock_component, MockConfigEntry +from homeassistant.setup import async_setup_component + +from tests.common import MockConfigEntry, mock_component MINIMAL_LOCATION_MESSAGE = { "_type": "location", From 27bd6ca1db80d6883b6e26cbc5288451d9fe3396 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 12:12:41 +0100 Subject: [PATCH 180/677] Sort imports according to PEP8 for emulated_hue (#29667) --- .../components/emulated_hue/__init__.py | 16 +++++----- .../components/emulated_hue/hue_api.py | 6 ++-- homeassistant/components/emulated_hue/upnp.py | 4 +-- tests/components/emulated_hue/test_hue_api.py | 29 +++++++++---------- tests/components/emulated_hue/test_init.py | 2 +- tests/components/emulated_hue/test_upnp.py | 10 +++---- 6 files changed, 33 insertions(+), 34 deletions(-) diff --git a/homeassistant/components/emulated_hue/__init__.py b/homeassistant/components/emulated_hue/__init__.py index 41c23707a03..c40a22df451 100644 --- a/homeassistant/components/emulated_hue/__init__.py +++ b/homeassistant/components/emulated_hue/__init__.py @@ -5,22 +5,22 @@ from aiohttp import web import voluptuous as vol from homeassistant import util +from homeassistant.components.http import real_ip from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers.deprecation import get_deprecated import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.deprecation import get_deprecated from homeassistant.util.json import load_json, save_json -from homeassistant.components.http import real_ip from .hue_api import ( - HueUsernameView, - HueUnauthorizedUser, - HueAllLightsStateView, - HueOneLightStateView, - HueOneLightChangeView, - HueGroupView, HueAllGroupsStateView, + HueAllLightsStateView, HueFullStateView, + HueGroupView, + HueOneLightChangeView, + HueOneLightStateView, + HueUnauthorizedUser, + HueUsernameView, ) from .upnp import DescriptionXmlView, UPNPResponderThread diff --git a/homeassistant/components/emulated_hue/hue_api.py b/homeassistant/components/emulated_hue/hue_api.py index d7db6bb2fe3..41c6329446c 100644 --- a/homeassistant/components/emulated_hue/hue_api.py +++ b/homeassistant/components/emulated_hue/hue_api.py @@ -1,6 +1,6 @@ """Support for a Hue API to control Home Assistant.""" -import logging import hashlib +import logging from homeassistant import core from homeassistant.components import ( @@ -34,8 +34,8 @@ from homeassistant.components.http import HomeAssistantView from homeassistant.components.http.const import KEY_REAL_IP from homeassistant.components.light import ( ATTR_BRIGHTNESS, - ATTR_HS_COLOR, ATTR_COLOR_TEMP, + ATTR_HS_COLOR, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, @@ -49,8 +49,8 @@ from homeassistant.const import ( ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE, HTTP_BAD_REQUEST, - HTTP_UNAUTHORIZED, HTTP_NOT_FOUND, + HTTP_UNAUTHORIZED, SERVICE_CLOSE_COVER, SERVICE_OPEN_COVER, SERVICE_TURN_OFF, diff --git a/homeassistant/components/emulated_hue/upnp.py b/homeassistant/components/emulated_hue/upnp.py index 412dfdd673e..0583ae9a1b6 100644 --- a/homeassistant/components/emulated_hue/upnp.py +++ b/homeassistant/components/emulated_hue/upnp.py @@ -1,8 +1,8 @@ """Support UPNP discovery method that mimics Hue hubs.""" -import threading -import socket import logging import select +import socket +import threading from aiohttp import web diff --git a/tests/components/emulated_hue/test_hue_api.py b/tests/components/emulated_hue/test_hue_api.py index 749493a6ca8..32543602f89 100644 --- a/tests/components/emulated_hue/test_hue_api.py +++ b/tests/components/emulated_hue/test_hue_api.py @@ -1,43 +1,42 @@ """The tests for the emulated Hue component.""" import asyncio -import json +from datetime import timedelta from ipaddress import ip_address +import json from unittest.mock import patch from aiohttp.hdrs import CONTENT_TYPE import pytest -from tests.common import get_test_instance_port from homeassistant import const, setup from homeassistant.components import ( + climate, + cover, + emulated_hue, fan, http, light, - script, - emulated_hue, media_player, - cover, - climate, + script, ) from homeassistant.components.emulated_hue import Config from homeassistant.components.emulated_hue.hue_api import ( - HUE_API_STATE_ON, HUE_API_STATE_BRI, HUE_API_STATE_HUE, + HUE_API_STATE_ON, HUE_API_STATE_SAT, HUE_API_USERNAME, - HueUsernameView, - HueOneLightStateView, - HueAllLightsStateView, - HueOneLightChangeView, HueAllGroupsStateView, + HueAllLightsStateView, HueFullStateView, + HueOneLightChangeView, + HueOneLightStateView, + HueUsernameView, ) -from homeassistant.const import STATE_ON, STATE_OFF - +from homeassistant.const import STATE_OFF, STATE_ON import homeassistant.util.dt as dt_util -from datetime import timedelta -from tests.common import async_fire_time_changed + +from tests.common import async_fire_time_changed, get_test_instance_port HTTP_SERVER_PORT = get_test_instance_port() BRIDGE_SERVER_PORT = get_test_instance_port() diff --git a/tests/components/emulated_hue/test_init.py b/tests/components/emulated_hue/test_init.py index a8798daeba2..09c0731e4cd 100644 --- a/tests/components/emulated_hue/test_init.py +++ b/tests/components/emulated_hue/test_init.py @@ -1,5 +1,5 @@ """Test the Emulated Hue component.""" -from unittest.mock import patch, Mock, MagicMock +from unittest.mock import MagicMock, Mock, patch from homeassistant.components.emulated_hue import Config diff --git a/tests/components/emulated_hue/test_upnp.py b/tests/components/emulated_hue/test_upnp.py index 4cbc79f68a5..2fc9d903d3b 100644 --- a/tests/components/emulated_hue/test_upnp.py +++ b/tests/components/emulated_hue/test_upnp.py @@ -1,15 +1,15 @@ """The tests for the emulated Hue component.""" import json - import unittest from unittest.mock import patch -import requests -from aiohttp.hdrs import CONTENT_TYPE -from homeassistant import setup, const +from aiohttp.hdrs import CONTENT_TYPE +import requests + +from homeassistant import const, setup from homeassistant.components import emulated_hue, http -from tests.common import get_test_instance_port, get_test_home_assistant +from tests.common import get_test_home_assistant, get_test_instance_port HTTP_SERVER_PORT = get_test_instance_port() BRIDGE_SERVER_PORT = get_test_instance_port() From 01d651c67d35fba0f00e0fae7970b6d63a227df7 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 12:14:38 +0100 Subject: [PATCH 181/677] Sort imports according to PEP8 for device_tracker (#29666) --- .../components/device_tracker/__init__.py | 11 ++++----- .../components/device_tracker/config_entry.py | 6 ++--- .../device_tracker/device_condition.py | 10 ++++---- .../components/device_tracker/setup.py | 23 +++++++++---------- tests/components/device_tracker/common.py | 6 ++--- .../device_tracker/test_device_condition.py | 6 ++--- .../device_tracker/test_entities.py | 5 ++-- 7 files changed, 34 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index 25e33d2a2db..a160e580c57 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -3,21 +3,19 @@ import asyncio import voluptuous as vol -from homeassistant.loader import bind_hass from homeassistant.components import group +from homeassistant.const import ATTR_GPS_ACCURACY, STATE_HOME from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.typing import GPSType, ConfigType, HomeAssistantType - from homeassistant.helpers.event import async_track_utc_time_change -from homeassistant.const import ATTR_GPS_ACCURACY, STATE_HOME +from homeassistant.helpers.typing import ConfigType, GPSType, HomeAssistantType +from homeassistant.loader import bind_hass from . import legacy, setup from .config_entry import ( # noqa: F401 pylint: disable=unused-import async_setup_entry, async_unload_entry, ) -from .legacy import DeviceScanner # noqa: F401 pylint: disable=unused-import from .const import ( ATTR_ATTRIBUTES, ATTR_BATTERY, @@ -38,11 +36,12 @@ from .const import ( DEFAULT_TRACK_NEW, DOMAIN, PLATFORM_TYPE_LEGACY, - SOURCE_TYPE_BLUETOOTH_LE, SOURCE_TYPE_BLUETOOTH, + SOURCE_TYPE_BLUETOOTH_LE, SOURCE_TYPE_GPS, SOURCE_TYPE_ROUTER, ) +from .legacy import DeviceScanner # noqa: F401 pylint: disable=unused-import ENTITY_ID_ALL_DEVICES = group.ENTITY_ID_FORMAT.format("all_devices") diff --git a/homeassistant/components/device_tracker/config_entry.py b/homeassistant/components/device_tracker/config_entry.py index 9e53c2e0cea..6c5cacac591 100644 --- a/homeassistant/components/device_tracker/config_entry.py +++ b/homeassistant/components/device_tracker/config_entry.py @@ -3,12 +3,12 @@ from typing import Optional from homeassistant.components import zone from homeassistant.const import ( - STATE_NOT_HOME, - STATE_HOME, + ATTR_BATTERY_LEVEL, ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE, - ATTR_BATTERY_LEVEL, + STATE_HOME, + STATE_NOT_HOME, ) from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent diff --git a/homeassistant/components/device_tracker/device_condition.py b/homeassistant/components/device_tracker/device_condition.py index 6379aca6c0b..9bdfc12db39 100644 --- a/homeassistant/components/device_tracker/device_condition.py +++ b/homeassistant/components/device_tracker/device_condition.py @@ -1,21 +1,23 @@ """Provides device automations for Device tracker.""" from typing import Dict, List + import voluptuous as vol from homeassistant.const import ( ATTR_ENTITY_ID, CONF_CONDITION, - CONF_DOMAIN, - CONF_TYPE, CONF_DEVICE_ID, + CONF_DOMAIN, CONF_ENTITY_ID, - STATE_NOT_HOME, + CONF_TYPE, STATE_HOME, + STATE_NOT_HOME, ) from homeassistant.core import HomeAssistant from homeassistant.helpers import condition, config_validation as cv, entity_registry -from homeassistant.helpers.typing import ConfigType, TemplateVarsType from homeassistant.helpers.config_validation import DEVICE_CONDITION_BASE_SCHEMA +from homeassistant.helpers.typing import ConfigType, TemplateVarsType + from . import DOMAIN CONDITION_TYPES = {"is_home", "is_not_home"} diff --git a/homeassistant/components/device_tracker/setup.py b/homeassistant/components/device_tracker/setup.py index 6c9f05dead7..42751b1a784 100644 --- a/homeassistant/components/device_tracker/setup.py +++ b/homeassistant/components/device_tracker/setup.py @@ -1,27 +1,26 @@ """Device tracker helpers.""" import asyncio -from typing import Dict, Any, Callable, Optional from types import ModuleType +from typing import Any, Callable, Dict, Optional import attr -from homeassistant.core import callback -from homeassistant.setup import async_prepare_setup_platform -from homeassistant.helpers import config_per_platform -from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers.typing import ConfigType, HomeAssistantType -from homeassistant.helpers.event import async_track_time_interval -from homeassistant.util import dt as dt_util from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE - +from homeassistant.core import callback +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers import config_per_platform +from homeassistant.helpers.event import async_track_time_interval +from homeassistant.helpers.typing import ConfigType, HomeAssistantType +from homeassistant.setup import async_prepare_setup_platform +from homeassistant.util import dt as dt_util from .const import ( - DOMAIN, - PLATFORM_TYPE_LEGACY, CONF_SCAN_INTERVAL, + DOMAIN, + LOGGER, + PLATFORM_TYPE_LEGACY, SCAN_INTERVAL, SOURCE_TYPE_ROUTER, - LOGGER, ) diff --git a/tests/components/device_tracker/common.py b/tests/components/device_tracker/common.py index 3a2df751d6f..1326174c6be 100644 --- a/tests/components/device_tracker/common.py +++ b/tests/components/device_tracker/common.py @@ -4,15 +4,15 @@ All containing methods are legacy helpers that should not be used by new components. Instead call the service directly. """ from homeassistant.components.device_tracker import ( - DOMAIN, ATTR_ATTRIBUTES, ATTR_BATTERY, + ATTR_DEV_ID, ATTR_GPS, ATTR_GPS_ACCURACY, + ATTR_HOST_NAME, ATTR_LOCATION_NAME, ATTR_MAC, - ATTR_DEV_ID, - ATTR_HOST_NAME, + DOMAIN, SERVICE_SEE, ) from homeassistant.core import callback diff --git a/tests/components/device_tracker/test_device_condition.py b/tests/components/device_tracker/test_device_condition.py index 732abccd8ca..15cd28e8fae 100644 --- a/tests/components/device_tracker/test_device_condition.py +++ b/tests/components/device_tracker/test_device_condition.py @@ -1,19 +1,19 @@ """The tests for Device tracker device conditions.""" import pytest +import homeassistant.components.automation as automation from homeassistant.components.device_tracker import DOMAIN from homeassistant.const import STATE_HOME, STATE_NOT_HOME -from homeassistant.setup import async_setup_component -import homeassistant.components.automation as automation from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component from tests.common import ( MockConfigEntry, assert_lists_same, + async_get_device_automations, async_mock_service, mock_device_registry, mock_registry, - async_get_device_automations, ) diff --git a/tests/components/device_tracker/test_entities.py b/tests/components/device_tracker/test_entities.py index 031e961cd2d..a0b2553543d 100644 --- a/tests/components/device_tracker/test_entities.py +++ b/tests/components/device_tracker/test_entities.py @@ -6,11 +6,12 @@ from homeassistant.components.device_tracker.config_entry import ( ScannerEntity, ) from homeassistant.components.device_tracker.const import ( - SOURCE_TYPE_ROUTER, ATTR_SOURCE_TYPE, DOMAIN, + SOURCE_TYPE_ROUTER, ) -from homeassistant.const import STATE_HOME, STATE_NOT_HOME, ATTR_BATTERY_LEVEL +from homeassistant.const import ATTR_BATTERY_LEVEL, STATE_HOME, STATE_NOT_HOME + from tests.common import MockConfigEntry From eb47c2b1487f0e99a577b41532b0ffd3501d1edc Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 12:17:41 +0100 Subject: [PATCH 182/677] Sort imports according to PEP8 for media_player (#29665) --- .../components/media_player/__init__.py | 1 - .../components/media_player/device_condition.py | 10 ++++++---- .../components/media_player/reproduce_state.py | 17 ++++++++--------- tests/components/media_player/common.py | 2 +- .../media_player/test_async_helpers.py | 10 +++++----- .../media_player/test_device_condition.py | 10 +++++----- tests/components/media_player/test_init.py | 2 +- .../media_player/test_reproduce_state.py | 2 +- 8 files changed, 27 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index a6cd4dda4ea..1375a0ed429 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -97,7 +97,6 @@ from .const import ( SUPPORT_VOLUME_STEP, ) - # mypy: allow-untyped-defs, no-check-untyped-defs _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/media_player/device_condition.py b/homeassistant/components/media_player/device_condition.py index a67a084a94f..a8091a6aed8 100644 --- a/homeassistant/components/media_player/device_condition.py +++ b/homeassistant/components/media_player/device_condition.py @@ -1,24 +1,26 @@ """Provides device automations for Media player.""" from typing import Dict, List + import voluptuous as vol from homeassistant.const import ( ATTR_ENTITY_ID, CONF_CONDITION, - CONF_DOMAIN, - CONF_TYPE, CONF_DEVICE_ID, + CONF_DOMAIN, CONF_ENTITY_ID, + CONF_TYPE, + STATE_IDLE, STATE_OFF, STATE_ON, - STATE_IDLE, STATE_PAUSED, STATE_PLAYING, ) from homeassistant.core import HomeAssistant from homeassistant.helpers import condition, config_validation as cv, entity_registry -from homeassistant.helpers.typing import ConfigType, TemplateVarsType from homeassistant.helpers.config_validation import DEVICE_CONDITION_BASE_SCHEMA +from homeassistant.helpers.typing import ConfigType, TemplateVarsType + from . import DOMAIN CONDITION_TYPES = {"is_on", "is_off", "is_idle", "is_paused", "is_playing"} diff --git a/homeassistant/components/media_player/reproduce_state.py b/homeassistant/components/media_player/reproduce_state.py index dac08afe471..dc9078d3ffd 100644 --- a/homeassistant/components/media_player/reproduce_state.py +++ b/homeassistant/components/media_player/reproduce_state.py @@ -21,21 +21,20 @@ from homeassistant.core import Context, State from homeassistant.helpers.typing import HomeAssistantType from .const import ( + ATTR_INPUT_SOURCE, + ATTR_MEDIA_CONTENT_ID, + ATTR_MEDIA_CONTENT_TYPE, + ATTR_MEDIA_ENQUEUE, + ATTR_MEDIA_SEEK_POSITION, ATTR_MEDIA_VOLUME_LEVEL, ATTR_MEDIA_VOLUME_MUTED, - ATTR_MEDIA_SEEK_POSITION, - ATTR_INPUT_SOURCE, ATTR_SOUND_MODE, - ATTR_MEDIA_CONTENT_TYPE, - ATTR_MEDIA_CONTENT_ID, - ATTR_MEDIA_ENQUEUE, - SERVICE_PLAY_MEDIA, - SERVICE_SELECT_SOURCE, - SERVICE_SELECT_SOUND_MODE, DOMAIN, + SERVICE_PLAY_MEDIA, + SERVICE_SELECT_SOUND_MODE, + SERVICE_SELECT_SOURCE, ) - # mypy: allow-untyped-defs diff --git a/tests/components/media_player/common.py b/tests/components/media_player/common.py index 177f8169ff9..5be9a292829 100644 --- a/tests/components/media_player/common.py +++ b/tests/components/media_player/common.py @@ -18,6 +18,7 @@ from homeassistant.components.media_player.const import ( ) from homeassistant.const import ( ATTR_ENTITY_ID, + ENTITY_MATCH_ALL, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, @@ -32,7 +33,6 @@ from homeassistant.const import ( SERVICE_VOLUME_MUTE, SERVICE_VOLUME_SET, SERVICE_VOLUME_UP, - ENTITY_MATCH_ALL, ) from homeassistant.loader import bind_hass diff --git a/tests/components/media_player/test_async_helpers.py b/tests/components/media_player/test_async_helpers.py index 4a2e4fed6c5..2e1ded3f084 100644 --- a/tests/components/media_player/test_async_helpers.py +++ b/tests/components/media_player/test_async_helpers.py @@ -1,14 +1,14 @@ """The tests for the Async Media player helper functions.""" -import unittest import asyncio +import unittest import homeassistant.components.media_player as mp from homeassistant.const import ( - STATE_PLAYING, - STATE_PAUSED, - STATE_ON, - STATE_OFF, STATE_IDLE, + STATE_OFF, + STATE_ON, + STATE_PAUSED, + STATE_PLAYING, ) from tests.common import get_test_home_assistant diff --git a/tests/components/media_player/test_device_condition.py b/tests/components/media_player/test_device_condition.py index 6216cc0e2b0..333cc4a2b13 100644 --- a/tests/components/media_player/test_device_condition.py +++ b/tests/components/media_player/test_device_condition.py @@ -1,25 +1,25 @@ """The tests for Media player device conditions.""" import pytest +import homeassistant.components.automation as automation from homeassistant.components.media_player import DOMAIN from homeassistant.const import ( - STATE_ON, - STATE_OFF, STATE_IDLE, + STATE_OFF, + STATE_ON, STATE_PAUSED, STATE_PLAYING, ) -from homeassistant.setup import async_setup_component -import homeassistant.components.automation as automation from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component from tests.common import ( MockConfigEntry, assert_lists_same, + async_get_device_automations, async_mock_service, mock_device_registry, mock_registry, - async_get_device_automations, ) diff --git a/tests/components/media_player/test_init.py b/tests/components/media_player/test_init.py index 7e36206a635..3db92cda42d 100644 --- a/tests/components/media_player/test_init.py +++ b/tests/components/media_player/test_init.py @@ -2,8 +2,8 @@ import base64 from unittest.mock import patch -from homeassistant.setup import async_setup_component from homeassistant.components.websocket_api.const import TYPE_RESULT +from homeassistant.setup import async_setup_component from tests.common import mock_coro diff --git a/tests/components/media_player/test_reproduce_state.py b/tests/components/media_player/test_reproduce_state.py index ddc5d6cf0ca..cbc5684f5d5 100644 --- a/tests/components/media_player/test_reproduce_state.py +++ b/tests/components/media_player/test_reproduce_state.py @@ -2,7 +2,6 @@ import pytest -from homeassistant.components.media_player.reproduce_state import async_reproduce_states from homeassistant.components.media_player.const import ( ATTR_INPUT_SOURCE, ATTR_MEDIA_CONTENT_ID, @@ -17,6 +16,7 @@ from homeassistant.components.media_player.const import ( SERVICE_SELECT_SOUND_MODE, SERVICE_SELECT_SOURCE, ) +from homeassistant.components.media_player.reproduce_state import async_reproduce_states from homeassistant.const import ( SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, From 3b0f29fe957f349d414fcc479e4a5f5c35464abb Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 12:19:11 +0100 Subject: [PATCH 183/677] sort imports according to PEP8 for lock (#29663) --- homeassistant/components/lock/__init__.py | 27 +++++++++---------- .../components/lock/device_action.py | 10 ++++--- .../components/lock/device_condition.py | 8 +++--- .../components/lock/device_trigger.py | 14 +++++----- .../components/lock/reproduce_state.py | 4 +-- tests/components/lock/common.py | 6 ++--- tests/components/lock/test_device_action.py | 6 ++--- .../components/lock/test_device_condition.py | 6 ++--- tests/components/lock/test_device_trigger.py | 6 ++--- 9 files changed, 46 insertions(+), 41 deletions(-) diff --git a/homeassistant/components/lock/__init__.py b/homeassistant/components/lock/__init__.py index c443a5219d7..a50f687238f 100644 --- a/homeassistant/components/lock/__init__.py +++ b/homeassistant/components/lock/__init__.py @@ -5,26 +5,25 @@ import logging import voluptuous as vol -from homeassistant.loader import bind_hass -from homeassistant.helpers.entity_component import EntityComponent -from homeassistant.helpers.entity import Entity -from homeassistant.helpers.config_validation import ( # noqa: F401 - make_entity_service_schema, - PLATFORM_SCHEMA, - PLATFORM_SCHEMA_BASE, -) -import homeassistant.helpers.config_validation as cv +from homeassistant.components import group from homeassistant.const import ( ATTR_CODE, ATTR_CODE_FORMAT, + SERVICE_LOCK, + SERVICE_OPEN, + SERVICE_UNLOCK, STATE_LOCKED, STATE_UNLOCKED, - SERVICE_LOCK, - SERVICE_UNLOCK, - SERVICE_OPEN, ) -from homeassistant.components import group - +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.config_validation import ( # noqa: F401 + PLATFORM_SCHEMA, + PLATFORM_SCHEMA_BASE, + make_entity_service_schema, +) +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.loader import bind_hass # mypy: allow-untyped-defs, no-check-untyped-defs diff --git a/homeassistant/components/lock/device_action.py b/homeassistant/components/lock/device_action.py index c678bfc17cf..efdb5e352cf 100644 --- a/homeassistant/components/lock/device_action.py +++ b/homeassistant/components/lock/device_action.py @@ -1,21 +1,23 @@ """Provides device automations for Lock.""" -from typing import Optional, List +from typing import List, Optional + import voluptuous as vol from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, - CONF_DOMAIN, - CONF_TYPE, CONF_DEVICE_ID, + CONF_DOMAIN, CONF_ENTITY_ID, + CONF_TYPE, SERVICE_LOCK, SERVICE_OPEN, SERVICE_UNLOCK, ) -from homeassistant.core import HomeAssistant, Context +from homeassistant.core import Context, HomeAssistant from homeassistant.helpers import entity_registry import homeassistant.helpers.config_validation as cv + from . import DOMAIN, SUPPORT_OPEN ACTION_TYPES = {"lock", "unlock", "open"} diff --git a/homeassistant/components/lock/device_condition.py b/homeassistant/components/lock/device_condition.py index 328da6ad450..44791320669 100644 --- a/homeassistant/components/lock/device_condition.py +++ b/homeassistant/components/lock/device_condition.py @@ -1,21 +1,23 @@ """Provides device automations for Lock.""" from typing import List + import voluptuous as vol from homeassistant.const import ( ATTR_ENTITY_ID, CONF_CONDITION, - CONF_DOMAIN, - CONF_TYPE, CONF_DEVICE_ID, + CONF_DOMAIN, CONF_ENTITY_ID, + CONF_TYPE, STATE_LOCKED, STATE_UNLOCKED, ) from homeassistant.core import HomeAssistant from homeassistant.helpers import condition, config_validation as cv, entity_registry -from homeassistant.helpers.typing import ConfigType, TemplateVarsType from homeassistant.helpers.config_validation import DEVICE_CONDITION_BASE_SCHEMA +from homeassistant.helpers.typing import ConfigType, TemplateVarsType + from . import DOMAIN CONDITION_TYPES = {"is_locked", "is_unlocked"} diff --git a/homeassistant/components/lock/device_trigger.py b/homeassistant/components/lock/device_trigger.py index 8732cca29f0..9db2822a591 100644 --- a/homeassistant/components/lock/device_trigger.py +++ b/homeassistant/components/lock/device_trigger.py @@ -1,21 +1,23 @@ """Provides device automations for Lock.""" from typing import List + import voluptuous as vol +from homeassistant.components.automation import AutomationActionType, state +from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA from homeassistant.const import ( - CONF_DOMAIN, - CONF_TYPE, - CONF_PLATFORM, CONF_DEVICE_ID, + CONF_DOMAIN, CONF_ENTITY_ID, + CONF_PLATFORM, + CONF_TYPE, STATE_LOCKED, STATE_UNLOCKED, ) -from homeassistant.core import HomeAssistant, CALLBACK_TYPE +from homeassistant.core import CALLBACK_TYPE, HomeAssistant from homeassistant.helpers import config_validation as cv, entity_registry from homeassistant.helpers.typing import ConfigType -from homeassistant.components.automation import state, AutomationActionType -from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA + from . import DOMAIN TRIGGER_TYPES = {"locked", "unlocked"} diff --git a/homeassistant/components/lock/reproduce_state.py b/homeassistant/components/lock/reproduce_state.py index dc1bee85839..b8b469f943f 100644 --- a/homeassistant/components/lock/reproduce_state.py +++ b/homeassistant/components/lock/reproduce_state.py @@ -5,10 +5,10 @@ from typing import Iterable, Optional from homeassistant.const import ( ATTR_ENTITY_ID, - STATE_LOCKED, - STATE_UNLOCKED, SERVICE_LOCK, SERVICE_UNLOCK, + STATE_LOCKED, + STATE_UNLOCKED, ) from homeassistant.core import Context, State from homeassistant.helpers.typing import HomeAssistantType diff --git a/tests/components/lock/common.py b/tests/components/lock/common.py index a658befca55..ec477cba78d 100644 --- a/tests/components/lock/common.py +++ b/tests/components/lock/common.py @@ -7,10 +7,10 @@ from homeassistant.components.lock import DOMAIN from homeassistant.const import ( ATTR_CODE, ATTR_ENTITY_ID, - SERVICE_LOCK, - SERVICE_UNLOCK, - SERVICE_OPEN, ENTITY_MATCH_ALL, + SERVICE_LOCK, + SERVICE_OPEN, + SERVICE_UNLOCK, ) from homeassistant.loader import bind_hass diff --git a/tests/components/lock/test_device_action.py b/tests/components/lock/test_device_action.py index 2006f9b3ff1..0fc98d9460e 100644 --- a/tests/components/lock/test_device_action.py +++ b/tests/components/lock/test_device_action.py @@ -1,19 +1,19 @@ """The tests for Lock device actions.""" import pytest +import homeassistant.components.automation as automation from homeassistant.components.lock import DOMAIN from homeassistant.const import CONF_PLATFORM -from homeassistant.setup import async_setup_component -import homeassistant.components.automation as automation from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component from tests.common import ( MockConfigEntry, assert_lists_same, + async_get_device_automations, async_mock_service, mock_device_registry, mock_registry, - async_get_device_automations, ) diff --git a/tests/components/lock/test_device_condition.py b/tests/components/lock/test_device_condition.py index 675f402e770..638a7edf5d7 100644 --- a/tests/components/lock/test_device_condition.py +++ b/tests/components/lock/test_device_condition.py @@ -1,19 +1,19 @@ """The tests for Lock device conditions.""" import pytest +import homeassistant.components.automation as automation from homeassistant.components.lock import DOMAIN from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED -from homeassistant.setup import async_setup_component -import homeassistant.components.automation as automation from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component from tests.common import ( MockConfigEntry, assert_lists_same, + async_get_device_automations, async_mock_service, mock_device_registry, mock_registry, - async_get_device_automations, ) diff --git a/tests/components/lock/test_device_trigger.py b/tests/components/lock/test_device_trigger.py index 572e28c44a6..781ed03307b 100644 --- a/tests/components/lock/test_device_trigger.py +++ b/tests/components/lock/test_device_trigger.py @@ -1,19 +1,19 @@ """The tests for Lock device triggers.""" import pytest +import homeassistant.components.automation as automation from homeassistant.components.lock import DOMAIN from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED -from homeassistant.setup import async_setup_component -import homeassistant.components.automation as automation from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component from tests.common import ( MockConfigEntry, assert_lists_same, + async_get_device_automations, async_mock_service, mock_device_registry, mock_registry, - async_get_device_automations, ) From 2da3848f892b9f9d56c7572edf956e3094d65d38 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 12:19:34 +0100 Subject: [PATCH 184/677] Sort imports according to PEP8 for unifi (#29656) --- homeassistant/components/unifi/__init__.py | 3 +-- homeassistant/components/unifi/config_flow.py | 4 ++-- homeassistant/components/unifi/controller.py | 16 +++++++--------- homeassistant/components/unifi/device_tracker.py | 3 +-- homeassistant/components/unifi/switch.py | 2 +- tests/components/unifi/test_config_flow.py | 4 +--- tests/components/unifi/test_controller.py | 5 ++--- tests/components/unifi/test_device_tracker.py | 3 +-- tests/components/unifi/test_init.py | 4 +--- tests/components/unifi/test_sensor.py | 3 +-- tests/components/unifi/test_switch.py | 3 +-- 11 files changed, 19 insertions(+), 31 deletions(-) diff --git a/homeassistant/components/unifi/__init__.py b/homeassistant/components/unifi/__init__.py index 4f3edf9ce79..65015b357a7 100644 --- a/homeassistant/components/unifi/__init__.py +++ b/homeassistant/components/unifi/__init__.py @@ -3,9 +3,8 @@ import voluptuous as vol from homeassistant.const import CONF_HOST from homeassistant.core import callback -from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC - import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from .config_flow import get_controller_id_from_config_entry from .const import ( diff --git a/homeassistant/components/unifi/config_flow.py b/homeassistant/components/unifi/config_flow.py index 01b97a78366..52ecab08856 100644 --- a/homeassistant/components/unifi/config_flow.py +++ b/homeassistant/components/unifi/config_flow.py @@ -2,7 +2,6 @@ import voluptuous as vol from homeassistant import config_entries -from homeassistant.core import callback from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -10,6 +9,7 @@ from homeassistant.const import ( CONF_USERNAME, CONF_VERIFY_SSL, ) +from homeassistant.core import callback from .const import ( CONF_ALLOW_BANDWIDTH_SENSORS, @@ -21,10 +21,10 @@ from .const import ( CONF_TRACK_WIRED_CLIENTS, CONTROLLER_ID, DEFAULT_ALLOW_BANDWIDTH_SENSORS, + DEFAULT_DETECTION_TIME, DEFAULT_TRACK_CLIENTS, DEFAULT_TRACK_DEVICES, DEFAULT_TRACK_WIRED_CLIENTS, - DEFAULT_DETECTION_TIME, DOMAIN, LOGGER, ) diff --git a/homeassistant/components/unifi/controller.py b/homeassistant/components/unifi/controller.py index 3deb2e9040a..826491f6ba6 100644 --- a/homeassistant/components/unifi/controller.py +++ b/homeassistant/components/unifi/controller.py @@ -1,16 +1,14 @@ """UniFi Controller abstraction.""" -from datetime import timedelta - import asyncio +from datetime import timedelta import ssl -import async_timeout from aiohttp import CookieJar - import aiounifi +import async_timeout -from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.const import CONF_HOST +from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import aiohttp_client from homeassistant.helpers.dispatcher import async_dispatcher_send @@ -22,19 +20,19 @@ from .const import ( CONF_DONT_TRACK_CLIENTS, CONF_DONT_TRACK_DEVICES, CONF_DONT_TRACK_WIRED_CLIENTS, + CONF_SITE_ID, + CONF_SSID_FILTER, CONF_TRACK_CLIENTS, CONF_TRACK_DEVICES, CONF_TRACK_WIRED_CLIENTS, - CONF_SITE_ID, - CONF_SSID_FILTER, CONTROLLER_ID, DEFAULT_ALLOW_BANDWIDTH_SENSORS, DEFAULT_BLOCK_CLIENTS, + DEFAULT_DETECTION_TIME, + DEFAULT_SSID_FILTER, DEFAULT_TRACK_CLIENTS, DEFAULT_TRACK_DEVICES, DEFAULT_TRACK_WIRED_CLIENTS, - DEFAULT_DETECTION_TIME, - DEFAULT_SSID_FILTER, DOMAIN, LOGGER, UNIFI_CONFIG, diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index b92211c4eae..0806a2c04e3 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -1,16 +1,15 @@ """Track devices using UniFi controllers.""" import logging -from homeassistant.components.unifi.config_flow import get_controller_from_config_entry from homeassistant.components.device_tracker import DOMAIN as DEVICE_TRACKER_DOMAIN from homeassistant.components.device_tracker.config_entry import ScannerEntity from homeassistant.components.device_tracker.const import SOURCE_TYPE_ROUTER +from homeassistant.components.unifi.config_flow import get_controller_from_config_entry from homeassistant.core import callback from homeassistant.helpers import entity_registry from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_registry import DISABLED_CONFIG_ENTRY - import homeassistant.util.dt as dt_util from .const import ATTR_MANUFACTURER diff --git a/homeassistant/components/unifi/switch.py b/homeassistant/components/unifi/switch.py index 82aa6f0384d..18bfbb4066e 100644 --- a/homeassistant/components/unifi/switch.py +++ b/homeassistant/components/unifi/switch.py @@ -1,8 +1,8 @@ """Support for devices connected to UniFi POE.""" import logging -from homeassistant.components.unifi.config_flow import get_controller_from_config_entry from homeassistant.components.switch import SwitchDevice +from homeassistant.components.unifi.config_flow import get_controller_from_config_entry from homeassistant.core import callback from homeassistant.helpers import entity_registry from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC diff --git a/tests/components/unifi/test_config_flow.py b/tests/components/unifi/test_config_flow.py index aea4d565f3d..1b973aee9a5 100644 --- a/tests/components/unifi/test_config_flow.py +++ b/tests/components/unifi/test_config_flow.py @@ -1,10 +1,10 @@ """Test UniFi config flow.""" +import aiounifi from asynctest import patch from homeassistant.components import unifi from homeassistant.components.unifi import config_flow from homeassistant.components.unifi.const import CONF_CONTROLLER, CONF_SITE_ID - from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -15,8 +15,6 @@ from homeassistant.const import ( from tests.common import MockConfigEntry -import aiounifi - async def test_flow_works(hass, aioclient_mock): """Test config flow.""" diff --git a/tests/components/unifi/test_controller.py b/tests/components/unifi/test_controller.py index 2b64e56cd99..c86e2f11538 100644 --- a/tests/components/unifi/test_controller.py +++ b/tests/components/unifi/test_controller.py @@ -2,12 +2,11 @@ from collections import deque from datetime import timedelta +import aiounifi from asynctest import Mock, patch - import pytest from homeassistant import config_entries -from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.components import unifi from homeassistant.components.unifi.const import ( CONF_CONTROLLER, @@ -22,7 +21,7 @@ from homeassistant.const import ( CONF_USERNAME, CONF_VERIFY_SSL, ) -import aiounifi +from homeassistant.exceptions import ConfigEntryNotReady CONTROLLER_HOST = { "hostname": "controller_host", diff --git a/tests/components/unifi/test_device_tracker.py b/tests/components/unifi/test_device_tracker.py index 29b16553757..634247a5283 100644 --- a/tests/components/unifi/test_device_tracker.py +++ b/tests/components/unifi/test_device_tracker.py @@ -4,6 +4,7 @@ from datetime import timedelta from homeassistant import config_entries from homeassistant.components import unifi +import homeassistant.components.device_tracker as device_tracker from homeassistant.components.unifi.const import ( CONF_SSID_FILTER, CONF_TRACK_DEVICES, @@ -12,8 +13,6 @@ from homeassistant.components.unifi.const import ( from homeassistant.const import STATE_UNAVAILABLE from homeassistant.helpers import entity_registry from homeassistant.setup import async_setup_component - -import homeassistant.components.device_tracker as device_tracker import homeassistant.util.dt as dt_util from .test_controller import ENTRY_CONFIG, SITES, setup_unifi_integration diff --git a/tests/components/unifi/test_init.py b/tests/components/unifi/test_init.py index 6b17b803390..1f5a3852e16 100644 --- a/tests/components/unifi/test_init.py +++ b/tests/components/unifi/test_init.py @@ -2,11 +2,9 @@ from unittest.mock import Mock, patch from homeassistant.components import unifi - from homeassistant.setup import async_setup_component - -from tests.common import mock_coro, MockConfigEntry +from tests.common import MockConfigEntry, mock_coro async def test_setup_with_no_config(hass): diff --git a/tests/components/unifi/test_sensor.py b/tests/components/unifi/test_sensor.py index f591801a966..8819f2f33ae 100644 --- a/tests/components/unifi/test_sensor.py +++ b/tests/components/unifi/test_sensor.py @@ -2,9 +2,8 @@ from copy import deepcopy from homeassistant.components import unifi -from homeassistant.setup import async_setup_component - import homeassistant.components.sensor as sensor +from homeassistant.setup import async_setup_component from .test_controller import ENTRY_CONFIG, SITES, setup_unifi_integration diff --git a/tests/components/unifi/test_switch.py b/tests/components/unifi/test_switch.py index 3d754fb5dff..30f9e990421 100644 --- a/tests/components/unifi/test_switch.py +++ b/tests/components/unifi/test_switch.py @@ -3,11 +3,10 @@ from copy import deepcopy from homeassistant import config_entries from homeassistant.components import unifi +import homeassistant.components.switch as switch from homeassistant.helpers import entity_registry from homeassistant.setup import async_setup_component -import homeassistant.components.switch as switch - from .test_controller import ( CONTROLLER_HOST, ENTRY_CONFIG, From 41cd678f00a0d3331a71903bafb2de8d66f762a2 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 12:25:35 +0100 Subject: [PATCH 185/677] Sort imports according to PEP8 for deconz (#29659) --- tests/components/deconz/test_binary_sensor.py | 5 ++--- tests/components/deconz/test_climate.py | 5 ++--- tests/components/deconz/test_config_flow.py | 6 +++--- tests/components/deconz/test_cover.py | 5 ++--- tests/components/deconz/test_deconz_event.py | 2 +- tests/components/deconz/test_device_trigger.py | 4 ++-- tests/components/deconz/test_gateway.py | 7 ++----- tests/components/deconz/test_init.py | 3 +-- tests/components/deconz/test_light.py | 5 ++--- tests/components/deconz/test_scene.py | 5 ++--- tests/components/deconz/test_sensor.py | 5 ++--- tests/components/deconz/test_services.py | 3 +-- tests/components/deconz/test_switch.py | 5 ++--- 13 files changed, 24 insertions(+), 36 deletions(-) diff --git a/tests/components/deconz/test_binary_sensor.py b/tests/components/deconz/test_binary_sensor.py index c340018b641..297be46dd27 100644 --- a/tests/components/deconz/test_binary_sensor.py +++ b/tests/components/deconz/test_binary_sensor.py @@ -2,11 +2,10 @@ from copy import deepcopy from homeassistant.components import deconz +import homeassistant.components.binary_sensor as binary_sensor from homeassistant.setup import async_setup_component -import homeassistant.components.binary_sensor as binary_sensor - -from .test_gateway import ENTRY_CONFIG, DECONZ_WEB_REQUEST, setup_deconz_integration +from .test_gateway import DECONZ_WEB_REQUEST, ENTRY_CONFIG, setup_deconz_integration SENSORS = { "1": { diff --git a/tests/components/deconz/test_climate.py b/tests/components/deconz/test_climate.py index 9536929ff3c..d905c80b2cd 100644 --- a/tests/components/deconz/test_climate.py +++ b/tests/components/deconz/test_climate.py @@ -4,11 +4,10 @@ from copy import deepcopy from asynctest import patch from homeassistant.components import deconz +import homeassistant.components.climate as climate from homeassistant.setup import async_setup_component -import homeassistant.components.climate as climate - -from .test_gateway import ENTRY_CONFIG, DECONZ_WEB_REQUEST, setup_deconz_integration +from .test_gateway import DECONZ_WEB_REQUEST, ENTRY_CONFIG, setup_deconz_integration SENSORS = { "1": { diff --git a/tests/components/deconz/test_config_flow.py b/tests/components/deconz/test_config_flow.py index 4045201bd18..b95743c95ad 100644 --- a/tests/components/deconz/test_config_flow.py +++ b/tests/components/deconz/test_config_flow.py @@ -1,13 +1,13 @@ """Tests for deCONZ config flow.""" +import asyncio from unittest.mock import Mock, patch -import asyncio +import pydeconz from homeassistant.components.deconz import config_flow from homeassistant.components.ssdp import ATTR_MANUFACTURERURL, ATTR_SERIAL -from tests.common import MockConfigEntry -import pydeconz +from tests.common import MockConfigEntry async def test_flow_works(hass, aioclient_mock): diff --git a/tests/components/deconz/test_cover.py b/tests/components/deconz/test_cover.py index 31819888404..f6f5f3a23ca 100644 --- a/tests/components/deconz/test_cover.py +++ b/tests/components/deconz/test_cover.py @@ -4,11 +4,10 @@ from copy import deepcopy from asynctest import patch from homeassistant.components import deconz +import homeassistant.components.cover as cover from homeassistant.setup import async_setup_component -import homeassistant.components.cover as cover - -from .test_gateway import ENTRY_CONFIG, DECONZ_WEB_REQUEST, setup_deconz_integration +from .test_gateway import DECONZ_WEB_REQUEST, ENTRY_CONFIG, setup_deconz_integration COVERS = { "1": { diff --git a/tests/components/deconz/test_deconz_event.py b/tests/components/deconz/test_deconz_event.py index ade9aa02ad4..d4f46176208 100644 --- a/tests/components/deconz/test_deconz_event.py +++ b/tests/components/deconz/test_deconz_event.py @@ -5,7 +5,7 @@ from asynctest import Mock from homeassistant.components.deconz.deconz_event import CONF_DECONZ_EVENT -from .test_gateway import ENTRY_CONFIG, DECONZ_WEB_REQUEST, setup_deconz_integration +from .test_gateway import DECONZ_WEB_REQUEST, ENTRY_CONFIG, setup_deconz_integration SENSORS = { "1": { diff --git a/tests/components/deconz/test_device_trigger.py b/tests/components/deconz/test_device_trigger.py index 91714e647bd..0d86fc30730 100644 --- a/tests/components/deconz/test_device_trigger.py +++ b/tests/components/deconz/test_device_trigger.py @@ -3,9 +3,9 @@ from copy import deepcopy from homeassistant.components.deconz import device_trigger -from tests.common import assert_lists_same, async_get_device_automations +from .test_gateway import DECONZ_WEB_REQUEST, ENTRY_CONFIG, setup_deconz_integration -from .test_gateway import ENTRY_CONFIG, DECONZ_WEB_REQUEST, setup_deconz_integration +from tests.common import assert_lists_same, async_get_device_automations SENSORS = { "1": { diff --git a/tests/components/deconz/test_gateway.py b/tests/components/deconz/test_gateway.py index f9290cc0e05..109bb325dac 100644 --- a/tests/components/deconz/test_gateway.py +++ b/tests/components/deconz/test_gateway.py @@ -2,17 +2,14 @@ from copy import deepcopy from asynctest import Mock, patch - +import pydeconz import pytest from homeassistant import config_entries -from homeassistant.components import deconz -from homeassistant.components import ssdp +from homeassistant.components import deconz, ssdp from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.dispatcher import async_dispatcher_connect -import pydeconz - BRIDGEID = "0123456789" ENTRY_CONFIG = { diff --git a/tests/components/deconz/test_init.py b/tests/components/deconz/test_init.py index d21859197be..3c6e02ab41c 100644 --- a/tests/components/deconz/test_init.py +++ b/tests/components/deconz/test_init.py @@ -2,11 +2,10 @@ import asyncio from asynctest import Mock, patch - import pytest -from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.components import deconz +from homeassistant.exceptions import ConfigEntryNotReady from tests.common import MockConfigEntry diff --git a/tests/components/deconz/test_light.py b/tests/components/deconz/test_light.py index 438b2209b1d..0ba4463ab81 100644 --- a/tests/components/deconz/test_light.py +++ b/tests/components/deconz/test_light.py @@ -4,11 +4,10 @@ from copy import deepcopy from asynctest import patch from homeassistant.components import deconz +import homeassistant.components.light as light from homeassistant.setup import async_setup_component -import homeassistant.components.light as light - -from .test_gateway import ENTRY_CONFIG, DECONZ_WEB_REQUEST, setup_deconz_integration +from .test_gateway import DECONZ_WEB_REQUEST, ENTRY_CONFIG, setup_deconz_integration GROUPS = { "1": { diff --git a/tests/components/deconz/test_scene.py b/tests/components/deconz/test_scene.py index ca7493a41f1..2b7cbaa55f5 100644 --- a/tests/components/deconz/test_scene.py +++ b/tests/components/deconz/test_scene.py @@ -4,11 +4,10 @@ from copy import deepcopy from asynctest import patch from homeassistant.components import deconz +import homeassistant.components.scene as scene from homeassistant.setup import async_setup_component -import homeassistant.components.scene as scene - -from .test_gateway import ENTRY_CONFIG, DECONZ_WEB_REQUEST, setup_deconz_integration +from .test_gateway import DECONZ_WEB_REQUEST, ENTRY_CONFIG, setup_deconz_integration GROUPS = { "1": { diff --git a/tests/components/deconz/test_sensor.py b/tests/components/deconz/test_sensor.py index b678ee13644..c42cd82b3d2 100644 --- a/tests/components/deconz/test_sensor.py +++ b/tests/components/deconz/test_sensor.py @@ -2,11 +2,10 @@ from copy import deepcopy from homeassistant.components import deconz +import homeassistant.components.sensor as sensor from homeassistant.setup import async_setup_component -import homeassistant.components.sensor as sensor - -from .test_gateway import ENTRY_CONFIG, DECONZ_WEB_REQUEST, setup_deconz_integration +from .test_gateway import DECONZ_WEB_REQUEST, ENTRY_CONFIG, setup_deconz_integration SENSORS = { "1": { diff --git a/tests/components/deconz/test_services.py b/tests/components/deconz/test_services.py index fae0ed41d93..fad5444aa00 100644 --- a/tests/components/deconz/test_services.py +++ b/tests/components/deconz/test_services.py @@ -2,7 +2,6 @@ from copy import deepcopy from asynctest import Mock, patch - import pytest import voluptuous as vol @@ -10,8 +9,8 @@ from homeassistant.components import deconz from .test_gateway import ( BRIDGEID, - ENTRY_CONFIG, DECONZ_WEB_REQUEST, + ENTRY_CONFIG, setup_deconz_integration, ) diff --git a/tests/components/deconz/test_switch.py b/tests/components/deconz/test_switch.py index 0c66ace5640..352ec84c502 100644 --- a/tests/components/deconz/test_switch.py +++ b/tests/components/deconz/test_switch.py @@ -4,11 +4,10 @@ from copy import deepcopy from asynctest import patch from homeassistant.components import deconz +import homeassistant.components.switch as switch from homeassistant.setup import async_setup_component -import homeassistant.components.switch as switch - -from .test_gateway import ENTRY_CONFIG, DECONZ_WEB_REQUEST, setup_deconz_integration +from .test_gateway import DECONZ_WEB_REQUEST, ENTRY_CONFIG, setup_deconz_integration SWITCHES = { "1": { From 16a7408f233538d410f0487b283a659e7ee2b9ea Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 12:29:12 +0100 Subject: [PATCH 186/677] Sort imports according to PEP8 for zwave (#29658) --- homeassistant/components/zwave/__init__.py | 52 +++++++++---------- .../components/zwave/binary_sensor.py | 10 ++-- homeassistant/components/zwave/climate.py | 12 ++--- homeassistant/components/zwave/config_flow.py | 2 +- homeassistant/components/zwave/cover.py | 18 ++++--- homeassistant/components/zwave/fan.py | 9 ++-- homeassistant/components/zwave/light.py | 13 ++--- homeassistant/components/zwave/lock.py | 5 +- homeassistant/components/zwave/node_entity.py | 20 +++---- homeassistant/components/zwave/sensor.py | 8 +-- homeassistant/components/zwave/switch.py | 4 +- tests/components/zwave/conftest.py | 2 +- tests/components/zwave/test_binary_sensor.py | 5 +- tests/components/zwave/test_climate.py | 8 +-- tests/components/zwave/test_cover.py | 8 +-- tests/components/zwave/test_fan.py | 8 +-- tests/components/zwave/test_init.py | 2 +- tests/components/zwave/test_light.py | 12 ++--- tests/components/zwave/test_lock.py | 4 +- tests/components/zwave/test_node_entity.py | 9 ++-- tests/components/zwave/test_sensor.py | 2 +- tests/components/zwave/test_switch.py | 2 +- tests/components/zwave/test_websocket_api.py | 3 +- tests/components/zwave/test_workaround.py | 1 + 24 files changed, 114 insertions(+), 105 deletions(-) diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 293cc45273f..6dd007fb8a8 100644 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -8,62 +8,60 @@ from pprint import pprint import voluptuous as vol from homeassistant import config_entries -from homeassistant.core import callback, CoreState +from homeassistant.const import ( + ATTR_ENTITY_ID, + EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP, +) +from homeassistant.core import CoreState, callback from homeassistant.helpers import discovery +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.device_registry import ( + async_get_registry as async_get_device_registry, +) +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, + async_dispatcher_send, +) from homeassistant.helpers.entity import generate_entity_id from homeassistant.helpers.entity_component import DEFAULT_SCAN_INTERVAL from homeassistant.helpers.entity_platform import EntityPlatform from homeassistant.helpers.entity_registry import ( async_get_registry as async_get_entity_registry, ) -from homeassistant.helpers.device_registry import ( - async_get_registry as async_get_device_registry, -) -from homeassistant.const import ( - ATTR_ENTITY_ID, - EVENT_HOMEASSISTANT_START, - EVENT_HOMEASSISTANT_STOP, -) from homeassistant.helpers.entity_values import EntityValues from homeassistant.helpers.event import async_track_time_change from homeassistant.util import convert import homeassistant.util.dt as dt_util -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.dispatcher import ( - async_dispatcher_connect, - async_dispatcher_send, -) -from . import const from . import config_flow # noqa: F401 pylint: disable=unused-import -from . import websocket_api as wsapi +from . import const, websocket_api as wsapi, workaround from .const import ( CONF_AUTOHEAL, + CONF_CONFIG_PATH, CONF_DEBUG, + CONF_NETWORK_KEY, CONF_POLLING_INTERVAL, CONF_USB_STICK_PATH, - CONF_CONFIG_PATH, - CONF_NETWORK_KEY, + DATA_DEVICES, + DATA_ENTITY_VALUES, + DATA_NETWORK, + DATA_ZWAVE_CONFIG, DEFAULT_CONF_AUTOHEAL, DEFAULT_CONF_USB_STICK_PATH, - DEFAULT_POLLING_INTERVAL, DEFAULT_DEBUG, + DEFAULT_POLLING_INTERVAL, DOMAIN, - DATA_DEVICES, - DATA_NETWORK, - DATA_ENTITY_VALUES, - DATA_ZWAVE_CONFIG, ) -from .node_entity import ZWaveBaseEntity, ZWaveNodeEntity -from . import workaround from .discovery_schemas import DISCOVERY_SCHEMAS +from .node_entity import ZWaveBaseEntity, ZWaveNodeEntity from .util import ( + check_has_unique_id, check_node_schema, check_value_schema, - node_name, - check_has_unique_id, is_node_parsed, node_device_id_and_name, + node_name, ) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/zwave/binary_sensor.py b/homeassistant/components/zwave/binary_sensor.py index a28f53f93d4..68df3313de3 100644 --- a/homeassistant/components/zwave/binary_sensor.py +++ b/homeassistant/components/zwave/binary_sensor.py @@ -1,12 +1,14 @@ """Support for Z-Wave binary sensors.""" -import logging import datetime -import homeassistant.util.dt as dt_util +import logging + +from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDevice from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.event import track_point_in_time -from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDevice -from . import workaround, ZWaveDeviceEntity +import homeassistant.util.dt as dt_util + +from . import ZWaveDeviceEntity, workaround from .const import COMMAND_CLASS_SENSOR_BINARY _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/zwave/climate.py b/homeassistant/components/zwave/climate.py index e5090878328..81b37aa5cb6 100644 --- a/homeassistant/components/zwave/climate.py +++ b/homeassistant/components/zwave/climate.py @@ -1,11 +1,12 @@ """Support for Z-Wave climate devices.""" # Because we do not compile openzwave on CI import logging - from typing import Optional from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( + ATTR_TARGET_TEMP_HIGH, + ATTR_TARGET_TEMP_LOW, CURRENT_HVAC_COOL, CURRENT_HVAC_FAN, CURRENT_HVAC_HEAT, @@ -14,28 +15,25 @@ from homeassistant.components.climate.const import ( DOMAIN, HVAC_MODE_AUTO, HVAC_MODE_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY, + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, PRESET_AWAY, PRESET_BOOST, PRESET_NONE, SUPPORT_AUX_HEAT, SUPPORT_FAN_MODE, + SUPPORT_PRESET_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_RANGE, - SUPPORT_PRESET_MODE, - ATTR_TARGET_TEMP_LOW, - ATTR_TARGET_TEMP_HIGH, ) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect - from . import ZWaveDeviceEntity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/zwave/config_flow.py b/homeassistant/components/zwave/config_flow.py index a264cdea7da..b570e31c128 100644 --- a/homeassistant/components/zwave/config_flow.py +++ b/homeassistant/components/zwave/config_flow.py @@ -7,8 +7,8 @@ import voluptuous as vol from homeassistant import config_entries from .const import ( - CONF_USB_STICK_PATH, CONF_NETWORK_KEY, + CONF_USB_STICK_PATH, DEFAULT_CONF_USB_STICK_PATH, DOMAIN, ) diff --git a/homeassistant/components/zwave/cover.py b/homeassistant/components/zwave/cover.py index 3a389fbf2bb..95cc994e4ff 100644 --- a/homeassistant/components/zwave/cover.py +++ b/homeassistant/components/zwave/cover.py @@ -1,24 +1,26 @@ """Support for Z-Wave covers.""" import logging -from homeassistant.core import callback + from homeassistant.components.cover import ( - DOMAIN, - SUPPORT_OPEN, - SUPPORT_CLOSE, ATTR_POSITION, + DOMAIN, + SUPPORT_CLOSE, + SUPPORT_OPEN, + CoverDevice, ) -from homeassistant.components.cover import CoverDevice +from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect + from . import ( - ZWaveDeviceEntity, CONF_INVERT_OPENCLOSE_BUTTONS, CONF_INVERT_PERCENT, + ZWaveDeviceEntity, workaround, ) from .const import ( - COMMAND_CLASS_SWITCH_MULTILEVEL, - COMMAND_CLASS_SWITCH_BINARY, COMMAND_CLASS_BARRIER_OPERATOR, + COMMAND_CLASS_SWITCH_BINARY, + COMMAND_CLASS_SWITCH_MULTILEVEL, DATA_NETWORK, ) diff --git a/homeassistant/components/zwave/fan.py b/homeassistant/components/zwave/fan.py index a52a7613d72..b77ab8dcf68 100644 --- a/homeassistant/components/zwave/fan.py +++ b/homeassistant/components/zwave/fan.py @@ -2,17 +2,18 @@ import logging import math -from homeassistant.core import callback from homeassistant.components.fan import ( DOMAIN, - FanEntity, - SPEED_OFF, + SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, - SPEED_HIGH, + SPEED_OFF, SUPPORT_SET_SPEED, + FanEntity, ) +from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect + from . import ZWaveDeviceEntity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/zwave/light.py b/homeassistant/components/zwave/light.py index bdb9021c02c..e941b2a97dc 100644 --- a/homeassistant/components/zwave/light.py +++ b/homeassistant/components/zwave/light.py @@ -1,26 +1,27 @@ """Support for Z-Wave lights.""" import logging - from threading import Timer -from homeassistant.core import callback + from homeassistant.components.light import ( - ATTR_WHITE_VALUE, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, ATTR_TRANSITION, + ATTR_WHITE_VALUE, + DOMAIN, SUPPORT_BRIGHTNESS, - SUPPORT_COLOR_TEMP, SUPPORT_COLOR, + SUPPORT_COLOR_TEMP, SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE, - DOMAIN, Light, ) from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect import homeassistant.util.color as color_util -from . import CONF_REFRESH_VALUE, CONF_REFRESH_DELAY, const, ZWaveDeviceEntity + +from . import CONF_REFRESH_DELAY, CONF_REFRESH_VALUE, ZWaveDeviceEntity, const _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/zwave/lock.py b/homeassistant/components/zwave/lock.py index ae5640e812d..9d7b2bffddd 100644 --- a/homeassistant/components/zwave/lock.py +++ b/homeassistant/components/zwave/lock.py @@ -3,10 +3,11 @@ import logging import voluptuous as vol -from homeassistant.core import callback from homeassistant.components.lock import DOMAIN, LockDevice -from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.core import callback import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import async_dispatcher_connect + from . import ZWaveDeviceEntity, const _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/zwave/node_entity.py b/homeassistant/components/zwave/node_entity.py index 44241e91daf..3b94991312a 100644 --- a/homeassistant/components/zwave/node_entity.py +++ b/homeassistant/components/zwave/node_entity.py @@ -1,26 +1,26 @@ """Entity class that represents Z-Wave node.""" -import logging from itertools import count +import logging +from homeassistant.const import ATTR_BATTERY_LEVEL, ATTR_ENTITY_ID, ATTR_WAKEUP from homeassistant.core import callback -from homeassistant.const import ATTR_BATTERY_LEVEL, ATTR_WAKEUP, ATTR_ENTITY_ID -from homeassistant.helpers.entity_registry import async_get_registry from homeassistant.helpers.device_registry import async_get_registry as get_dev_reg from homeassistant.helpers.entity import Entity +from homeassistant.helpers.entity_registry import async_get_registry from .const import ( - ATTR_NODE_ID, - COMMAND_CLASS_WAKE_UP, - ATTR_SCENE_ID, - ATTR_SCENE_DATA, ATTR_BASIC_LEVEL, - EVENT_NODE_EVENT, - EVENT_SCENE_ACTIVATED, + ATTR_NODE_ID, + ATTR_SCENE_DATA, + ATTR_SCENE_ID, COMMAND_CLASS_CENTRAL_SCENE, COMMAND_CLASS_VERSION, + COMMAND_CLASS_WAKE_UP, DOMAIN, + EVENT_NODE_EVENT, + EVENT_SCENE_ACTIVATED, ) -from .util import node_name, is_node_parsed, node_device_id_and_name +from .util import is_node_parsed, node_device_id_and_name, node_name _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/zwave/sensor.py b/homeassistant/components/zwave/sensor.py index 0820feb8d0f..08ee54415ad 100644 --- a/homeassistant/components/zwave/sensor.py +++ b/homeassistant/components/zwave/sensor.py @@ -1,10 +1,12 @@ """Support for Z-Wave sensors.""" import logging -from homeassistant.core import callback -from homeassistant.components.sensor import DOMAIN, DEVICE_CLASS_BATTERY + +from homeassistant.components.sensor import DEVICE_CLASS_BATTERY, DOMAIN from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT +from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from . import const, ZWaveDeviceEntity + +from . import ZWaveDeviceEntity, const _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/zwave/switch.py b/homeassistant/components/zwave/switch.py index 4b13b06d2b9..3592f534074 100644 --- a/homeassistant/components/zwave/switch.py +++ b/homeassistant/components/zwave/switch.py @@ -1,9 +1,11 @@ """Support for Z-Wave switches.""" import logging import time -from homeassistant.core import callback + from homeassistant.components.switch import DOMAIN, SwitchDevice +from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect + from . import ZWaveDeviceEntity, workaround _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/zwave/conftest.py b/tests/components/zwave/conftest.py index cc9dc5c72ba..f80c55f7767 100644 --- a/tests/components/zwave/conftest.py +++ b/tests/components/zwave/conftest.py @@ -1,5 +1,5 @@ """Fixtures for Z-Wave tests.""" -from unittest.mock import patch, MagicMock +from unittest.mock import MagicMock, patch import pytest diff --git a/tests/components/zwave/test_binary_sensor.py b/tests/components/zwave/test_binary_sensor.py index b68f4208f7a..54270cdc3f4 100644 --- a/tests/components/zwave/test_binary_sensor.py +++ b/tests/components/zwave/test_binary_sensor.py @@ -1,11 +1,10 @@ """Test Z-Wave binary sensors.""" import datetime - from unittest.mock import patch -from homeassistant.components.zwave import const, binary_sensor +from homeassistant.components.zwave import binary_sensor, const -from tests.mock.zwave import MockNode, MockValue, MockEntityValues, value_changed +from tests.mock.zwave import MockEntityValues, MockNode, MockValue, value_changed def test_get_device_detects_none(mock_openzwave): diff --git a/tests/components/zwave/test_climate.py b/tests/components/zwave/test_climate.py index c9fe123af82..b820e496226 100644 --- a/tests/components/zwave/test_climate.py +++ b/tests/components/zwave/test_climate.py @@ -2,13 +2,15 @@ import pytest from homeassistant.components.climate.const import ( - CURRENT_HVAC_HEAT, + ATTR_TARGET_TEMP_HIGH, + ATTR_TARGET_TEMP_LOW, CURRENT_HVAC_COOL, - HVAC_MODES, + CURRENT_HVAC_HEAT, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, + HVAC_MODES, PRESET_AWAY, PRESET_BOOST, PRESET_ECO, @@ -18,8 +20,6 @@ from homeassistant.components.climate.const import ( SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_RANGE, - ATTR_TARGET_TEMP_LOW, - ATTR_TARGET_TEMP_HIGH, ) from homeassistant.components.zwave import climate from homeassistant.components.zwave.climate import DEFAULT_HVAC_MODES diff --git a/tests/components/zwave/test_cover.py b/tests/components/zwave/test_cover.py index 848decc2fb5..e8b784feefe 100644 --- a/tests/components/zwave/test_cover.py +++ b/tests/components/zwave/test_cover.py @@ -1,15 +1,15 @@ """Test Z-Wave cover devices.""" from unittest.mock import MagicMock -from homeassistant.components.cover import SUPPORT_OPEN, SUPPORT_CLOSE +from homeassistant.components.cover import SUPPORT_CLOSE, SUPPORT_OPEN from homeassistant.components.zwave import ( - const, - cover, CONF_INVERT_OPENCLOSE_BUTTONS, CONF_INVERT_PERCENT, + const, + cover, ) -from tests.mock.zwave import MockNode, MockValue, MockEntityValues, value_changed +from tests.mock.zwave import MockEntityValues, MockNode, MockValue, value_changed def test_get_device_detects_none(hass, mock_openzwave): diff --git a/tests/components/zwave/test_fan.py b/tests/components/zwave/test_fan.py index 2252a42a064..e5dac881ba2 100644 --- a/tests/components/zwave/test_fan.py +++ b/tests/components/zwave/test_fan.py @@ -1,14 +1,14 @@ """Test Z-Wave fans.""" -from homeassistant.components.zwave import fan from homeassistant.components.fan import ( - SPEED_OFF, + SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, - SPEED_HIGH, + SPEED_OFF, SUPPORT_SET_SPEED, ) +from homeassistant.components.zwave import fan -from tests.mock.zwave import MockNode, MockValue, MockEntityValues, value_changed +from tests.mock.zwave import MockEntityValues, MockNode, MockValue, value_changed def test_get_device_detects_fan(mock_openzwave): diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py index 7038d6b6114..8f717b2903c 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -19,8 +19,8 @@ from homeassistant.components.zwave import ( ) from homeassistant.components.zwave.binary_sensor import get_device from homeassistant.const import ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_START -from homeassistant.helpers.entity_registry import async_get_registry from homeassistant.helpers.device_registry import async_get_registry as get_dev_reg +from homeassistant.helpers.entity_registry import async_get_registry from homeassistant.setup import setup_component from tests.common import ( diff --git a/tests/components/zwave/test_light.py b/tests/components/zwave/test_light.py index 55070b9081d..10efed24bf2 100644 --- a/tests/components/zwave/test_light.py +++ b/tests/components/zwave/test_light.py @@ -1,22 +1,22 @@ """Test Z-Wave lights.""" -from unittest.mock import patch, MagicMock +from unittest.mock import MagicMock, patch from homeassistant.components import zwave -from homeassistant.components.zwave import const, light from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, ATTR_TRANSITION, - SUPPORT_BRIGHTNESS, - SUPPORT_TRANSITION, - SUPPORT_COLOR, ATTR_WHITE_VALUE, + SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, SUPPORT_COLOR_TEMP, + SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE, ) +from homeassistant.components.zwave import const, light -from tests.mock.zwave import MockNode, MockValue, MockEntityValues, value_changed +from tests.mock.zwave import MockEntityValues, MockNode, MockValue, value_changed class MockLightValues(MockEntityValues): diff --git a/tests/components/zwave/test_lock.py b/tests/components/zwave/test_lock.py index 4a32c3fb07c..d5b6d0a0d27 100644 --- a/tests/components/zwave/test_lock.py +++ b/tests/components/zwave/test_lock.py @@ -1,10 +1,10 @@ """Test Z-Wave locks.""" -from unittest.mock import patch, MagicMock +from unittest.mock import MagicMock, patch from homeassistant import config_entries from homeassistant.components.zwave import const, lock -from tests.mock.zwave import MockNode, MockValue, MockEntityValues, value_changed +from tests.mock.zwave import MockEntityValues, MockNode, MockValue, value_changed def test_get_device_detects_lock(mock_openzwave): diff --git a/tests/components/zwave/test_node_entity.py b/tests/components/zwave/test_node_entity.py index dba187d7b96..9136b53ff0b 100644 --- a/tests/components/zwave/test_node_entity.py +++ b/tests/components/zwave/test_node_entity.py @@ -1,11 +1,14 @@ """Test Z-Wave node entity.""" import unittest -from unittest.mock import patch, MagicMock -import tests.mock.zwave as mock_zwave +from unittest.mock import MagicMock, patch + import pytest -from homeassistant.components.zwave import node_entity, const + +from homeassistant.components.zwave import const, node_entity from homeassistant.const import ATTR_ENTITY_ID +import tests.mock.zwave as mock_zwave + async def test_maybe_schedule_update(hass, mock_openzwave): """Test maybe schedule update.""" diff --git a/tests/components/zwave/test_sensor.py b/tests/components/zwave/test_sensor.py index cec93f5af4a..74e1ef2cd03 100644 --- a/tests/components/zwave/test_sensor.py +++ b/tests/components/zwave/test_sensor.py @@ -2,7 +2,7 @@ from homeassistant.components.zwave import const, sensor import homeassistant.const -from tests.mock.zwave import MockNode, MockValue, MockEntityValues, value_changed +from tests.mock.zwave import MockEntityValues, MockNode, MockValue, value_changed def test_get_device_detects_none(mock_openzwave): diff --git a/tests/components/zwave/test_switch.py b/tests/components/zwave/test_switch.py index a275e4b9e01..4293a4a23fd 100644 --- a/tests/components/zwave/test_switch.py +++ b/tests/components/zwave/test_switch.py @@ -3,7 +3,7 @@ from unittest.mock import patch from homeassistant.components.zwave import switch -from tests.mock.zwave import MockNode, MockValue, MockEntityValues, value_changed +from tests.mock.zwave import MockEntityValues, MockNode, MockValue, value_changed def test_get_device_detects_switch(mock_openzwave): diff --git a/tests/components/zwave/test_websocket_api.py b/tests/components/zwave/test_websocket_api.py index b55024eb3f0..978fc09a10d 100644 --- a/tests/components/zwave/test_websocket_api.py +++ b/tests/components/zwave/test_websocket_api.py @@ -1,10 +1,9 @@ """Test Z-Wave Websocket API.""" from homeassistant.bootstrap import async_setup_component - from homeassistant.components.zwave.const import ( - CONF_USB_STICK_PATH, CONF_AUTOHEAL, CONF_POLLING_INTERVAL, + CONF_USB_STICK_PATH, ) from homeassistant.components.zwave.websocket_api import ID, TYPE diff --git a/tests/components/zwave/test_workaround.py b/tests/components/zwave/test_workaround.py index 0825b8f47f9..ec708d38e43 100644 --- a/tests/components/zwave/test_workaround.py +++ b/tests/components/zwave/test_workaround.py @@ -1,5 +1,6 @@ """Test Z-Wave workarounds.""" from homeassistant.components.zwave import const, workaround + from tests.mock.zwave import MockNode, MockValue From 3df40c7a16ef49377a342fece6f983c76c28418c Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 12:30:23 +0100 Subject: [PATCH 187/677] Sort imports according to PEP8 for websocket_api (#29657) --- tests/components/websocket_api/conftest.py | 4 ++-- tests/components/websocket_api/test_auth.py | 11 +++++------ tests/components/websocket_api/test_commands.py | 6 +++--- tests/components/websocket_api/test_init.py | 2 +- tests/components/websocket_api/test_sensor.py | 3 ++- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/components/websocket_api/conftest.py b/tests/components/websocket_api/conftest.py index 382de3142e8..65b9232821f 100644 --- a/tests/components/websocket_api/conftest.py +++ b/tests/components/websocket_api/conftest.py @@ -1,9 +1,9 @@ """Fixtures for websocket tests.""" import pytest -from homeassistant.setup import async_setup_component -from homeassistant.components.websocket_api.http import URL from homeassistant.components.websocket_api.auth import TYPE_AUTH_REQUIRED +from homeassistant.components.websocket_api.http import URL +from homeassistant.setup import async_setup_component @pytest.fixture diff --git a/tests/components/websocket_api/test_auth.py b/tests/components/websocket_api/test_auth.py index 00387506020..ccc033ccc72 100644 --- a/tests/components/websocket_api/test_auth.py +++ b/tests/components/websocket_api/test_auth.py @@ -1,18 +1,17 @@ """Test auth of websocket API.""" from unittest.mock import patch -from homeassistant.components.websocket_api.const import ( - URL, - SIGNAL_WEBSOCKET_CONNECTED, - SIGNAL_WEBSOCKET_DISCONNECTED, -) from homeassistant.components.websocket_api.auth import ( TYPE_AUTH, TYPE_AUTH_INVALID, TYPE_AUTH_OK, TYPE_AUTH_REQUIRED, ) - +from homeassistant.components.websocket_api.const import ( + SIGNAL_WEBSOCKET_CONNECTED, + SIGNAL_WEBSOCKET_DISCONNECTED, + URL, +) from homeassistant.setup import async_setup_component from tests.common import mock_coro diff --git a/tests/components/websocket_api/test_commands.py b/tests/components/websocket_api/test_commands.py index 1de5b8bb2c1..58d904c8f4b 100644 --- a/tests/components/websocket_api/test_commands.py +++ b/tests/components/websocket_api/test_commands.py @@ -1,14 +1,14 @@ """Tests for WebSocket API commands.""" from async_timeout import timeout -from homeassistant.core import callback -from homeassistant.components.websocket_api.const import URL +from homeassistant.components.websocket_api import const from homeassistant.components.websocket_api.auth import ( TYPE_AUTH, TYPE_AUTH_OK, TYPE_AUTH_REQUIRED, ) -from homeassistant.components.websocket_api import const +from homeassistant.components.websocket_api.const import URL +from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError from homeassistant.setup import async_setup_component diff --git a/tests/components/websocket_api/test_init.py b/tests/components/websocket_api/test_init.py index ed3319ed4f4..7cda3200e7b 100644 --- a/tests/components/websocket_api/test_init.py +++ b/tests/components/websocket_api/test_init.py @@ -1,6 +1,6 @@ """Tests for the Home Assistant Websocket API.""" import asyncio -from unittest.mock import patch, Mock +from unittest.mock import Mock, patch from aiohttp import WSMsgType import pytest diff --git a/tests/components/websocket_api/test_sensor.py b/tests/components/websocket_api/test_sensor.py index 84b73060698..2c711737851 100644 --- a/tests/components/websocket_api/test_sensor.py +++ b/tests/components/websocket_api/test_sensor.py @@ -2,9 +2,10 @@ from homeassistant.bootstrap import async_setup_component -from tests.common import assert_setup_component from .test_auth import test_auth_active_with_token +from tests.common import assert_setup_component + async def test_websocket_api( hass, no_auth_websocket_client, hass_access_token, legacy_auth From e9b428781b342b9aeb3083d2e024d7dc8e4eb2c2 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 13:06:25 +0100 Subject: [PATCH 188/677] Sort imports according to PEP8 for pushbullet (#29748) --- homeassistant/components/pushbullet/notify.py | 9 +++------ homeassistant/components/pushbullet/sensor.py | 6 ++---- tests/components/pushbullet/test_notify.py | 3 ++- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/pushbullet/notify.py b/homeassistant/components/pushbullet/notify.py index 76c1e14e5a5..28cb08cc69c 100644 --- a/homeassistant/components/pushbullet/notify.py +++ b/homeassistant/components/pushbullet/notify.py @@ -2,14 +2,9 @@ import logging import mimetypes -from pushbullet import PushBullet -from pushbullet import InvalidKeyError -from pushbullet import PushError +from pushbullet import InvalidKeyError, PushBullet, PushError import voluptuous as vol -from homeassistant.const import CONF_API_KEY -import homeassistant.helpers.config_validation as cv - from homeassistant.components.notify import ( ATTR_DATA, ATTR_TARGET, @@ -18,6 +13,8 @@ from homeassistant.components.notify import ( PLATFORM_SCHEMA, BaseNotificationService, ) +from homeassistant.const import CONF_API_KEY +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/pushbullet/sensor.py b/homeassistant/components/pushbullet/sensor.py index 600b38b6eaf..771ba55586c 100644 --- a/homeassistant/components/pushbullet/sensor.py +++ b/homeassistant/components/pushbullet/sensor.py @@ -2,13 +2,11 @@ import logging import threading -from pushbullet import PushBullet -from pushbullet import InvalidKeyError -from pushbullet import Listener +from pushbullet import InvalidKeyError, Listener, PushBullet import voluptuous as vol -from homeassistant.const import CONF_API_KEY, CONF_MONITORED_CONDITIONS from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import CONF_API_KEY, CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity diff --git a/tests/components/pushbullet/test_notify.py b/tests/components/pushbullet/test_notify.py index d95a942d25b..4c731c1f704 100644 --- a/tests/components/pushbullet/test_notify.py +++ b/tests/components/pushbullet/test_notify.py @@ -6,8 +6,9 @@ from unittest.mock import patch from pushbullet import PushBullet import requests_mock -from homeassistant.setup import setup_component import homeassistant.components.notify as notify +from homeassistant.setup import setup_component + from tests.common import assert_setup_component, get_test_home_assistant, load_fixture From c4a6f265e8570c8f84b5c7db08e4b6cd0dbe04b5 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 13:08:05 +0100 Subject: [PATCH 189/677] Sort imports according to PEP8 for versasense (#29753) --- homeassistant/components/versasense/__init__.py | 12 ++++++------ homeassistant/components/versasense/sensor.py | 10 +++++----- homeassistant/components/versasense/switch.py | 14 +++++++------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/versasense/__init__.py b/homeassistant/components/versasense/__init__.py index 4f378f4ab00..d2081d715d5 100644 --- a/homeassistant/components/versasense/__init__.py +++ b/homeassistant/components/versasense/__init__.py @@ -10,14 +10,14 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.discovery import async_load_platform from .const import ( + KEY_CONSUMER, + KEY_IDENTIFIER, + KEY_MEASUREMENT, + KEY_PARENT_MAC, + KEY_PARENT_NAME, + KEY_UNIT, PERIPHERAL_CLASS_SENSOR, PERIPHERAL_CLASS_SENSOR_ACTUATOR, - KEY_IDENTIFIER, - KEY_PARENT_NAME, - KEY_PARENT_MAC, - KEY_UNIT, - KEY_MEASUREMENT, - KEY_CONSUMER, ) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/versasense/sensor.py b/homeassistant/components/versasense/sensor.py index 4253bfcbba4..e598093cd37 100644 --- a/homeassistant/components/versasense/sensor.py +++ b/homeassistant/components/versasense/sensor.py @@ -5,12 +5,12 @@ from homeassistant.helpers.entity import Entity from . import DOMAIN from .const import ( - KEY_IDENTIFIER, - KEY_PARENT_NAME, - KEY_PARENT_MAC, - KEY_UNIT, - KEY_MEASUREMENT, KEY_CONSUMER, + KEY_IDENTIFIER, + KEY_MEASUREMENT, + KEY_PARENT_MAC, + KEY_PARENT_NAME, + KEY_UNIT, ) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/versasense/switch.py b/homeassistant/components/versasense/switch.py index 4ea118a6c97..4b44cb7aa2a 100644 --- a/homeassistant/components/versasense/switch.py +++ b/homeassistant/components/versasense/switch.py @@ -5,14 +5,14 @@ from homeassistant.components.switch import SwitchDevice from . import DOMAIN from .const import ( - PERIPHERAL_STATE_ON, - PERIPHERAL_STATE_OFF, - KEY_IDENTIFIER, - KEY_PARENT_NAME, - KEY_PARENT_MAC, - KEY_UNIT, - KEY_MEASUREMENT, KEY_CONSUMER, + KEY_IDENTIFIER, + KEY_MEASUREMENT, + KEY_PARENT_MAC, + KEY_PARENT_NAME, + KEY_UNIT, + PERIPHERAL_STATE_OFF, + PERIPHERAL_STATE_ON, ) _LOGGER = logging.getLogger(__name__) From f81e608cc14a728d28c080bf2e1dd5879565f95f Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 13:09:16 +0100 Subject: [PATCH 190/677] Sort imports according to PEP8 for remote (#29749) --- homeassistant/components/remote/__init__.py | 23 ++++++++++----------- tests/components/remote/common.py | 2 +- tests/components/remote/test_init.py | 10 ++++----- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/remote/__init__.py b/homeassistant/components/remote/__init__.py index af653165ee3..b88629ea468 100644 --- a/homeassistant/components/remote/__init__.py +++ b/homeassistant/components/remote/__init__.py @@ -5,23 +5,22 @@ import logging import voluptuous as vol -from homeassistant.loader import bind_hass -from homeassistant.helpers.entity_component import EntityComponent -from homeassistant.helpers.entity import ToggleEntity -import homeassistant.helpers.config_validation as cv -from homeassistant.const import ( - STATE_ON, - SERVICE_TURN_ON, - SERVICE_TURN_OFF, - SERVICE_TOGGLE, -) from homeassistant.components import group +from homeassistant.const import ( + SERVICE_TOGGLE, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_ON, +) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.config_validation import ( # noqa: F401 - make_entity_service_schema, PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE, + make_entity_service_schema, ) - +from homeassistant.helpers.entity import ToggleEntity +from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.loader import bind_hass # mypy: allow-untyped-defs, no-check-untyped-defs diff --git a/tests/components/remote/common.py b/tests/components/remote/common.py index d972640487a..1f4a5268440 100644 --- a/tests/components/remote/common.py +++ b/tests/components/remote/common.py @@ -17,9 +17,9 @@ from homeassistant.components.remote import ( ) from homeassistant.const import ( ATTR_ENTITY_ID, + ENTITY_MATCH_ALL, SERVICE_TURN_OFF, SERVICE_TURN_ON, - ENTITY_MATCH_ALL, ) from homeassistant.loader import bind_hass diff --git a/tests/components/remote/test_init.py b/tests/components/remote/test_init.py index 723f38baced..392f0e6fa61 100644 --- a/tests/components/remote/test_init.py +++ b/tests/components/remote/test_init.py @@ -3,17 +3,17 @@ import unittest +import homeassistant.components.remote as remote from homeassistant.const import ( ATTR_ENTITY_ID, - STATE_ON, - STATE_OFF, CONF_PLATFORM, - SERVICE_TURN_ON, SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, ) -import homeassistant.components.remote as remote -from tests.common import mock_service, get_test_home_assistant +from tests.common import get_test_home_assistant, mock_service from tests.components.remote import common TEST_PLATFORM = {remote.DOMAIN: {CONF_PLATFORM: "test"}} From 3f469eac28ed6ab98e7090ef1a2ccca334709880 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 13:10:24 +0100 Subject: [PATCH 191/677] Sort imports according to PEP8 for yeelight (#29755) --- homeassistant/components/yeelight/__init__.py | 15 +++-- .../components/yeelight/binary_sensor.py | 3 +- homeassistant/components/yeelight/light.py | 67 ++++++++++--------- 3 files changed, 44 insertions(+), 41 deletions(-) diff --git a/homeassistant/components/yeelight/__init__.py b/homeassistant/components/yeelight/__init__.py index c899c811a47..ddd3cf2a053 100644 --- a/homeassistant/components/yeelight/__init__.py +++ b/homeassistant/components/yeelight/__init__.py @@ -1,24 +1,25 @@ """Support for Xiaomi Yeelight WiFi color bulb.""" -import logging from datetime import timedelta +import logging import voluptuous as vol from yeelight import Bulb, BulbException + +from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.components.discovery import SERVICE_YEELIGHT +from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN from homeassistant.const import ( + ATTR_ENTITY_ID, CONF_DEVICES, + CONF_HOST, CONF_NAME, CONF_SCAN_INTERVAL, - CONF_HOST, - ATTR_ENTITY_ID, ) -from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN -from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.helpers import discovery -from homeassistant.helpers.discovery import load_platform import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.dispatcher import dispatcher_send, dispatcher_connect +from homeassistant.helpers.discovery import load_platform +from homeassistant.helpers.dispatcher import dispatcher_connect, dispatcher_send from homeassistant.helpers.event import track_time_interval _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/yeelight/binary_sensor.py b/homeassistant/components/yeelight/binary_sensor.py index da39152e9ca..29e24b510e5 100644 --- a/homeassistant/components/yeelight/binary_sensor.py +++ b/homeassistant/components/yeelight/binary_sensor.py @@ -4,7 +4,8 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from . import DATA_YEELIGHT, DATA_UPDATED + +from . import DATA_UPDATED, DATA_YEELIGHT _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/yeelight/light.py b/homeassistant/components/yeelight/light.py index 772fb00977b..3e98403587f 100644 --- a/homeassistant/components/yeelight/light.py +++ b/homeassistant/components/yeelight/light.py @@ -4,60 +4,61 @@ import logging import voluptuous as vol import yeelight from yeelight import ( + BulbException, + Flow, RGBTransition, SleepTransition, - Flow, - BulbException, transitions as yee_transitions, ) -from yeelight.enums import PowerMode, LightType, BulbType, SceneClass +from yeelight.enums import BulbType, LightType, PowerMode, SceneClass -from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.service import extract_entity_ids -import homeassistant.helpers.config_validation as cv -from homeassistant.util.color import ( - color_temperature_mired_to_kelvin as mired_to_kelvin, - color_temperature_kelvin_to_mired as kelvin_to_mired, -) -from homeassistant.const import CONF_HOST, ATTR_ENTITY_ID, ATTR_MODE, CONF_NAME -from homeassistant.core import callback from homeassistant.components.light import ( ATTR_BRIGHTNESS, - ATTR_HS_COLOR, - ATTR_TRANSITION, ATTR_COLOR_TEMP, - ATTR_FLASH, - FLASH_SHORT, - FLASH_LONG, ATTR_EFFECT, + ATTR_FLASH, + ATTR_HS_COLOR, + ATTR_KELVIN, + ATTR_RGB_COLOR, + ATTR_TRANSITION, + FLASH_LONG, + FLASH_SHORT, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, - SUPPORT_TRANSITION, SUPPORT_COLOR_TEMP, - SUPPORT_FLASH, SUPPORT_EFFECT, + SUPPORT_FLASH, + SUPPORT_TRANSITION, Light, - ATTR_RGB_COLOR, - ATTR_KELVIN, ) +from homeassistant.const import ATTR_ENTITY_ID, ATTR_MODE, CONF_HOST, CONF_NAME +from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.service import extract_entity_ids import homeassistant.util.color as color_util +from homeassistant.util.color import ( + color_temperature_kelvin_to_mired as kelvin_to_mired, + color_temperature_mired_to_kelvin as mired_to_kelvin, +) + from . import ( - CONF_TRANSITION, - DATA_YEELIGHT, - CONF_MODE_MUSIC, - CONF_SAVE_ON_CHANGE, - CONF_CUSTOM_EFFECTS, - DATA_UPDATED, - YEELIGHT_SERVICE_SCHEMA, - DOMAIN, - ATTR_TRANSITIONS, - YEELIGHT_FLOW_TRANSITION_SCHEMA, ACTION_RECOVER, - CONF_FLOW_PARAMS, ATTR_ACTION, ATTR_COUNT, - NIGHTLIGHT_SWITCH_TYPE_LIGHT, + ATTR_TRANSITIONS, + CONF_CUSTOM_EFFECTS, + CONF_FLOW_PARAMS, + CONF_MODE_MUSIC, CONF_NIGHTLIGHT_SWITCH_TYPE, + CONF_SAVE_ON_CHANGE, + CONF_TRANSITION, + DATA_UPDATED, + DATA_YEELIGHT, + DOMAIN, + NIGHTLIGHT_SWITCH_TYPE_LIGHT, + YEELIGHT_FLOW_TRANSITION_SCHEMA, + YEELIGHT_SERVICE_SCHEMA, ) _LOGGER = logging.getLogger(__name__) From 127d84edd1101e1216cd6c5a40735f5095419ff3 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 13:11:37 +0100 Subject: [PATCH 192/677] Sort imports according to PEP8 for solarlog (#29752) --- homeassistant/components/solarlog/const.py | 2 +- homeassistant/components/solarlog/sensor.py | 6 +++--- tests/components/solarlog/test_config_flow.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/solarlog/const.py b/homeassistant/components/solarlog/const.py index 67eb8006cec..933f8014090 100644 --- a/homeassistant/components/solarlog/const.py +++ b/homeassistant/components/solarlog/const.py @@ -1,7 +1,7 @@ """Constants for the Solar-Log integration.""" from datetime import timedelta -from homeassistant.const import POWER_WATT, ENERGY_KILO_WATT_HOUR +from homeassistant.const import ENERGY_KILO_WATT_HOUR, POWER_WATT DOMAIN = "solarlog" diff --git a/homeassistant/components/solarlog/sensor.py b/homeassistant/components/solarlog/sensor.py index 583529ffe87..85ab9eb913e 100644 --- a/homeassistant/components/solarlog/sensor.py +++ b/homeassistant/components/solarlog/sensor.py @@ -6,14 +6,14 @@ from requests.exceptions import HTTPError, Timeout from sunwatcher.solarlog.solarlog import SolarLog import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import CONF_HOST, CONF_NAME +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle -from .const import DOMAIN, DEFAULT_HOST, DEFAULT_NAME, SCAN_INTERVAL, SENSOR_TYPES +from .const import DEFAULT_HOST, DEFAULT_NAME, DOMAIN, SCAN_INTERVAL, SENSOR_TYPES _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/solarlog/test_config_flow.py b/tests/components/solarlog/test_config_flow.py index 86f3b05d975..cd05cf13185 100644 --- a/tests/components/solarlog/test_config_flow.py +++ b/tests/components/solarlog/test_config_flow.py @@ -1,9 +1,9 @@ """Test the solarlog config flow.""" from unittest.mock import patch + import pytest -from homeassistant import data_entry_flow -from homeassistant import config_entries, setup +from homeassistant import config_entries, data_entry_flow, setup from homeassistant.components.solarlog import config_flow from homeassistant.components.solarlog.const import DEFAULT_HOST, DOMAIN from homeassistant.const import CONF_HOST, CONF_NAME From 76debf4c8834e393d0beb031004199f01a84f76f Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 13:12:43 +0100 Subject: [PATCH 193/677] Sort imports according to PEP8 for scene (#29750) --- homeassistant/components/scene/__init__.py | 3 +-- tests/components/scene/common.py | 2 +- tests/components/scene/test_init.py | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/scene/__init__.py b/homeassistant/components/scene/__init__.py index 63a64f34fe9..75ec2bfd875 100644 --- a/homeassistant/components/scene/__init__.py +++ b/homeassistant/components/scene/__init__.py @@ -4,12 +4,11 @@ import logging import voluptuous as vol -from homeassistant.core import DOMAIN as HA_DOMAIN from homeassistant.const import CONF_PLATFORM, SERVICE_TURN_ON +from homeassistant.core import DOMAIN as HA_DOMAIN from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent - # mypy: allow-untyped-defs, no-check-untyped-defs DOMAIN = "scene" diff --git a/tests/components/scene/common.py b/tests/components/scene/common.py index 5da0cc21db0..cdf124add29 100644 --- a/tests/components/scene/common.py +++ b/tests/components/scene/common.py @@ -4,7 +4,7 @@ All containing methods are legacy helpers that should not be used by new components. Instead call the service directly. """ from homeassistant.components.scene import DOMAIN -from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_ON, ENTITY_MATCH_ALL +from homeassistant.const import ATTR_ENTITY_ID, ENTITY_MATCH_ALL, SERVICE_TURN_ON from homeassistant.loader import bind_hass diff --git a/tests/components/scene/test_init.py b/tests/components/scene/test_init.py index 5c8d46cb727..f26189eec6c 100644 --- a/tests/components/scene/test_init.py +++ b/tests/components/scene/test_init.py @@ -2,8 +2,8 @@ import io import unittest -from homeassistant.setup import setup_component from homeassistant.components import light, scene +from homeassistant.setup import setup_component from homeassistant.util.yaml import loader as yaml_loader from tests.common import get_test_home_assistant From f281069c8cb8d256dbde40b0dcbbc77372a06749 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 13:13:33 +0100 Subject: [PATCH 194/677] Sort imports according to PEP8 for vicare (#29754) --- homeassistant/components/vicare/__init__.py | 7 ++--- homeassistant/components/vicare/climate.py | 29 ++++++++++--------- .../components/vicare/water_heater.py | 8 ++--- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/vicare/__init__.py b/homeassistant/components/vicare/__init__.py index e091ff99970..282e234811a 100644 --- a/homeassistant/components/vicare/__init__.py +++ b/homeassistant/components/vicare/__init__.py @@ -2,15 +2,14 @@ import enum import logging -import voluptuous as vol - from PyViCare.PyViCareDevice import Device from PyViCare.PyViCareGazBoiler import GazBoiler from PyViCare.PyViCareHeatPump import HeatPump +import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_NAME +from homeassistant.const import CONF_NAME, CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers import discovery +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/vicare/climate.py b/homeassistant/components/vicare/climate.py index 4f6f0cedcd9..1b101cc7612 100644 --- a/homeassistant/components/vicare/climate.py +++ b/homeassistant/components/vicare/climate.py @@ -1,26 +1,29 @@ """Viessmann ViCare climate device.""" import logging + import requests from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, - PRESET_ECO, - PRESET_COMFORT, - HVAC_MODE_OFF, - HVAC_MODE_HEAT, - HVAC_MODE_AUTO, CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, + HVAC_MODE_AUTO, + HVAC_MODE_HEAT, + HVAC_MODE_OFF, + PRESET_COMFORT, + PRESET_ECO, + SUPPORT_PRESET_MODE, + SUPPORT_TARGET_TEMPERATURE, ) -from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE, PRECISION_WHOLE +from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS -from . import DOMAIN as VICARE_DOMAIN -from . import VICARE_API -from . import VICARE_NAME -from . import VICARE_HEATING_TYPE -from . import HeatingType +from . import ( + DOMAIN as VICARE_DOMAIN, + VICARE_API, + VICARE_HEATING_TYPE, + VICARE_NAME, + HeatingType, +) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/vicare/water_heater.py b/homeassistant/components/vicare/water_heater.py index eefacf99c39..f31e4f65170 100644 --- a/homeassistant/components/vicare/water_heater.py +++ b/homeassistant/components/vicare/water_heater.py @@ -1,17 +1,15 @@ """Viessmann ViCare water_heater device.""" import logging + import requests from homeassistant.components.water_heater import ( SUPPORT_TARGET_TEMPERATURE, WaterHeaterDevice, ) -from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE, PRECISION_WHOLE +from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS -from . import DOMAIN as VICARE_DOMAIN -from . import VICARE_API -from . import VICARE_NAME -from . import VICARE_HEATING_TYPE +from . import DOMAIN as VICARE_DOMAIN, VICARE_API, VICARE_HEATING_TYPE, VICARE_NAME _LOGGER = logging.getLogger(__name__) From d1b38c0c7992ed41c589aff89e17ee1976dec0d4 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 13:14:08 +0100 Subject: [PATCH 195/677] Sort imports according to PEP8 for plaato (#29747) --- homeassistant/components/plaato/__init__.py | 1 + homeassistant/components/plaato/config_flow.py | 1 + homeassistant/components/plaato/sensor.py | 6 ++++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/plaato/__init__.py b/homeassistant/components/plaato/__init__.py index 49b749b8de6..0dd57f75812 100644 --- a/homeassistant/components/plaato/__init__.py +++ b/homeassistant/components/plaato/__init__.py @@ -15,6 +15,7 @@ from homeassistant.const import ( ) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send + from .const import DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/plaato/config_flow.py b/homeassistant/components/plaato/config_flow.py index 59cb270c616..3c616c822fb 100644 --- a/homeassistant/components/plaato/config_flow.py +++ b/homeassistant/components/plaato/config_flow.py @@ -1,5 +1,6 @@ """Config flow for GPSLogger.""" from homeassistant.helpers import config_entry_flow + from .const import DOMAIN config_entry_flow.register_webhook_flow( diff --git a/homeassistant/components/plaato/sensor.py b/homeassistant/components/plaato/sensor.py index f8e6a3e9fa7..e7c8033f2ac 100644 --- a/homeassistant/components/plaato/sensor.py +++ b/homeassistant/components/plaato/sensor.py @@ -2,8 +2,10 @@ import logging -from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, + async_dispatcher_send, +) from homeassistant.helpers.entity import Entity from . import ( From 991834f337cbdc4e8420cfb89b23c9d6d7dd07ba Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 13:14:49 +0100 Subject: [PATCH 196/677] Sort imports according to PEP8 for shopping_list (#29751) --- homeassistant/components/shopping_list/__init__.py | 7 +++---- tests/components/shopping_list/conftest.py | 2 +- tests/components/shopping_list/test_init.py | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/shopping_list/__init__.py b/homeassistant/components/shopping_list/__init__.py index 850b06332f8..856ea0784ba 100644 --- a/homeassistant/components/shopping_list/__init__.py +++ b/homeassistant/components/shopping_list/__init__.py @@ -5,13 +5,12 @@ import uuid import voluptuous as vol -from homeassistant.const import HTTP_NOT_FOUND, HTTP_BAD_REQUEST -from homeassistant.core import callback -from homeassistant.components import http +from homeassistant.components import http, websocket_api from homeassistant.components.http.data_validator import RequestDataValidator +from homeassistant.const import HTTP_BAD_REQUEST, HTTP_NOT_FOUND +from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.util.json import load_json, save_json -from homeassistant.components import websocket_api ATTR_NAME = "name" diff --git a/tests/components/shopping_list/conftest.py b/tests/components/shopping_list/conftest.py index 646b3bee4c0..5026f19302e 100644 --- a/tests/components/shopping_list/conftest.py +++ b/tests/components/shopping_list/conftest.py @@ -3,8 +3,8 @@ from unittest.mock import patch import pytest -from homeassistant.setup import async_setup_component from homeassistant.components.shopping_list import intent as sl_intent +from homeassistant.setup import async_setup_component @pytest.fixture(autouse=True) diff --git a/tests/components/shopping_list/test_init.py b/tests/components/shopping_list/test_init.py index 4394a835f49..74c354848a3 100644 --- a/tests/components/shopping_list/test_init.py +++ b/tests/components/shopping_list/test_init.py @@ -1,8 +1,8 @@ """Test shopping list component.""" import asyncio -from homeassistant.helpers import intent from homeassistant.components.websocket_api.const import TYPE_RESULT +from homeassistant.helpers import intent @asyncio.coroutine From 60e1789557001cf4c713eacfebf359a20c617d6b Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 13:20:51 +0100 Subject: [PATCH 197/677] Sort imports according to PEP8 for emulated_roku (#29756) --- tests/components/emulated_roku/test_binding.py | 10 +++++----- tests/components/emulated_roku/test_config_flow.py | 1 + tests/components/emulated_roku/test_init.py | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/components/emulated_roku/test_binding.py b/tests/components/emulated_roku/test_binding.py index 712c35f5a10..53b6217fcbc 100644 --- a/tests/components/emulated_roku/test_binding.py +++ b/tests/components/emulated_roku/test_binding.py @@ -2,16 +2,16 @@ from unittest.mock import Mock, patch from homeassistant.components.emulated_roku.binding import ( - EmulatedRoku, - EVENT_ROKU_COMMAND, - ATTR_SOURCE_NAME, + ATTR_APP_ID, ATTR_COMMAND_TYPE, ATTR_KEY, - ATTR_APP_ID, - ROKU_COMMAND_KEYPRESS, + ATTR_SOURCE_NAME, + EVENT_ROKU_COMMAND, ROKU_COMMAND_KEYDOWN, + ROKU_COMMAND_KEYPRESS, ROKU_COMMAND_KEYUP, ROKU_COMMAND_LAUNCH, + EmulatedRoku, ) from tests.common import mock_coro_func diff --git a/tests/components/emulated_roku/test_config_flow.py b/tests/components/emulated_roku/test_config_flow.py index 3cb25f5a7fc..879d95d0cfc 100644 --- a/tests/components/emulated_roku/test_config_flow.py +++ b/tests/components/emulated_roku/test_config_flow.py @@ -1,5 +1,6 @@ """Tests for emulated_roku config flow.""" from homeassistant.components.emulated_roku import config_flow + from tests.common import MockConfigEntry diff --git a/tests/components/emulated_roku/test_init.py b/tests/components/emulated_roku/test_init.py index 92524f24d97..efdf330a876 100644 --- a/tests/components/emulated_roku/test_init.py +++ b/tests/components/emulated_roku/test_init.py @@ -1,8 +1,8 @@ """Test emulated_roku component setup process.""" from unittest.mock import Mock, patch -from homeassistant.setup import async_setup_component from homeassistant.components import emulated_roku +from homeassistant.setup import async_setup_component from tests.common import mock_coro_func From 53012a548b6f908e603b70405db499582949fe48 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 13:21:45 +0100 Subject: [PATCH 198/677] Sort imports according to PEP8 for sleepiq (#29759) --- tests/components/sleepiq/test_binary_sensor.py | 4 ++-- tests/components/sleepiq/test_init.py | 2 +- tests/components/sleepiq/test_sensor.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/components/sleepiq/test_binary_sensor.py b/tests/components/sleepiq/test_binary_sensor.py index a3bf31134c5..b8c3a2cd2e8 100644 --- a/tests/components/sleepiq/test_binary_sensor.py +++ b/tests/components/sleepiq/test_binary_sensor.py @@ -4,11 +4,11 @@ from unittest.mock import MagicMock import requests_mock -from homeassistant.setup import setup_component from homeassistant.components.sleepiq import binary_sensor as sleepiq +from homeassistant.setup import setup_component -from tests.components.sleepiq.test_init import mock_responses from tests.common import get_test_home_assistant +from tests.components.sleepiq.test_init import mock_responses class TestSleepIQBinarySensorSetup(unittest.TestCase): diff --git a/tests/components/sleepiq/test_init.py b/tests/components/sleepiq/test_init.py index a418253d409..67fe19da45a 100644 --- a/tests/components/sleepiq/test_init.py +++ b/tests/components/sleepiq/test_init.py @@ -7,7 +7,7 @@ import requests_mock from homeassistant import setup import homeassistant.components.sleepiq as sleepiq -from tests.common import load_fixture, get_test_home_assistant +from tests.common import get_test_home_assistant, load_fixture def mock_responses(mock, single=False): diff --git a/tests/components/sleepiq/test_sensor.py b/tests/components/sleepiq/test_sensor.py index 9dbba3a8b0a..a049dfd2fbf 100644 --- a/tests/components/sleepiq/test_sensor.py +++ b/tests/components/sleepiq/test_sensor.py @@ -4,11 +4,11 @@ from unittest.mock import MagicMock import requests_mock -from homeassistant.setup import setup_component import homeassistant.components.sleepiq.sensor as sleepiq +from homeassistant.setup import setup_component -from tests.components.sleepiq.test_init import mock_responses from tests.common import get_test_home_assistant +from tests.components.sleepiq.test_init import mock_responses class TestSleepIQSensorSetup(unittest.TestCase): From abfcc18004c5ae99a5c9cfb7501b9c8d4d551e03 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 13:22:42 +0100 Subject: [PATCH 199/677] Sort imports according to PEP8 for mobile_app (#29758) --- tests/components/mobile_app/conftest.py | 7 +++---- tests/components/mobile_app/test_notify.py | 3 +-- tests/components/mobile_app/test_webhook.py | 5 +++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/tests/components/mobile_app/conftest.py b/tests/components/mobile_app/conftest.py index 1d653b73ba3..cd819a9891c 100644 --- a/tests/components/mobile_app/conftest.py +++ b/tests/components/mobile_app/conftest.py @@ -2,14 +2,13 @@ # pylint: disable=redefined-outer-name,unused-import import pytest -from tests.common import mock_device_registry - +from homeassistant.components.mobile_app.const import DOMAIN from homeassistant.setup import async_setup_component -from homeassistant.components.mobile_app.const import DOMAIN - from .const import REGISTER, REGISTER_CLEARTEXT +from tests.common import mock_device_registry + @pytest.fixture def registry(hass): diff --git a/tests/components/mobile_app/test_notify.py b/tests/components/mobile_app/test_notify.py index 83a2a5e0766..860f3d9f81f 100644 --- a/tests/components/mobile_app/test_notify.py +++ b/tests/components/mobile_app/test_notify.py @@ -2,9 +2,8 @@ # pylint: disable=redefined-outer-name import pytest -from homeassistant.setup import async_setup_component - from homeassistant.components.mobile_app.const import DOMAIN +from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry diff --git a/tests/components/mobile_app/test_webhook.py b/tests/components/mobile_app/test_webhook.py index 6e8efe15dd0..6a41b5f054d 100644 --- a/tests/components/mobile_app/test_webhook.py +++ b/tests/components/mobile_app/test_webhook.py @@ -1,6 +1,7 @@ """Webhook tests for mobile_app.""" import logging + import pytest from homeassistant.components.mobile_app.const import CONF_SECRET @@ -9,10 +10,10 @@ from homeassistant.const import CONF_WEBHOOK_ID from homeassistant.core import callback from homeassistant.setup import async_setup_component -from tests.common import async_mock_service - from .const import CALL_SERVICE, FIRE_EVENT, REGISTER_CLEARTEXT, RENDER_TEMPLATE, UPDATE +from tests.common import async_mock_service + _LOGGER = logging.getLogger(__name__) From c7cf1b820c83fd00fd88cf393ffba8e51d14b99a Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 13:25:04 +0100 Subject: [PATCH 200/677] Sort imports according to PEP8 for hue (#29757) --- tests/components/hue/test_bridge.py | 2 +- tests/components/hue/test_init.py | 4 ++-- tests/components/hue/test_light.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/components/hue/test_bridge.py b/tests/components/hue/test_bridge.py index 7265b468714..b66733e7c76 100644 --- a/tests/components/hue/test_bridge.py +++ b/tests/components/hue/test_bridge.py @@ -3,8 +3,8 @@ from unittest.mock import Mock, patch import pytest -from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.components.hue import bridge, errors +from homeassistant.exceptions import ConfigEntryNotReady from tests.common import mock_coro diff --git a/tests/components/hue/test_init.py b/tests/components/hue/test_init.py index f6ff112cc37..58f004ec540 100644 --- a/tests/components/hue/test_init.py +++ b/tests/components/hue/test_init.py @@ -1,10 +1,10 @@ """Test Hue setup process.""" from unittest.mock import Mock, patch -from homeassistant.setup import async_setup_component from homeassistant.components import hue +from homeassistant.setup import async_setup_component -from tests.common import mock_coro, MockConfigEntry +from tests.common import MockConfigEntry, mock_coro async def test_setup_with_no_config(hass): diff --git a/tests/components/hue/test_light.py b/tests/components/hue/test_light.py index 5b10ff9446c..c218e729255 100644 --- a/tests/components/hue/test_light.py +++ b/tests/components/hue/test_light.py @@ -5,8 +5,8 @@ import logging from unittest.mock import Mock import aiohue -from aiohue.lights import Lights from aiohue.groups import Groups +from aiohue.lights import Lights import pytest from homeassistant import config_entries From 9b27e5b86cdd6132fe66ec0421a002d04803cbd2 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 13:33:08 +0100 Subject: [PATCH 201/677] Sort imports according to PEP8 for vultr (#29760) --- tests/components/vultr/test_binary_sensor.py | 10 +++++----- tests/components/vultr/test_sensor.py | 6 +++--- tests/components/vultr/test_switch.py | 10 +++++----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/components/vultr/test_binary_sensor.py b/tests/components/vultr/test_binary_sensor.py index e59c7b4e46c..f57926f30c8 100644 --- a/tests/components/vultr/test_binary_sensor.py +++ b/tests/components/vultr/test_binary_sensor.py @@ -3,25 +3,25 @@ import json import unittest from unittest.mock import patch -import requests_mock import pytest +import requests_mock import voluptuous as vol -from homeassistant.components.vultr import binary_sensor as vultr from homeassistant.components import vultr as base_vultr from homeassistant.components.vultr import ( ATTR_ALLOWED_BANDWIDTH, ATTR_AUTO_BACKUPS, - ATTR_IPV4_ADDRESS, ATTR_COST_PER_MONTH, ATTR_CREATED_AT, + ATTR_IPV4_ADDRESS, ATTR_SUBSCRIPTION_ID, CONF_SUBSCRIPTION, + binary_sensor as vultr, ) -from homeassistant.const import CONF_PLATFORM, CONF_NAME +from homeassistant.const import CONF_NAME, CONF_PLATFORM -from tests.components.vultr.test_init import VALID_CONFIG from tests.common import get_test_home_assistant, load_fixture +from tests.components.vultr.test_init import VALID_CONFIG class TestVultrBinarySensorSetup(unittest.TestCase): diff --git a/tests/components/vultr/test_sensor.py b/tests/components/vultr/test_sensor.py index 6e2969dd2e0..4da60783c44 100644 --- a/tests/components/vultr/test_sensor.py +++ b/tests/components/vultr/test_sensor.py @@ -7,13 +7,13 @@ import pytest import requests_mock import voluptuous as vol -import homeassistant.components.vultr.sensor as vultr from homeassistant.components import vultr as base_vultr from homeassistant.components.vultr import CONF_SUBSCRIPTION -from homeassistant.const import CONF_NAME, CONF_MONITORED_CONDITIONS, CONF_PLATFORM +import homeassistant.components.vultr.sensor as vultr +from homeassistant.const import CONF_MONITORED_CONDITIONS, CONF_NAME, CONF_PLATFORM -from tests.components.vultr.test_init import VALID_CONFIG from tests.common import get_test_home_assistant, load_fixture +from tests.components.vultr.test_init import VALID_CONFIG class TestVultrSensorSetup(unittest.TestCase): diff --git a/tests/components/vultr/test_switch.py b/tests/components/vultr/test_switch.py index 0d5055cd6a5..4f61291c4ad 100644 --- a/tests/components/vultr/test_switch.py +++ b/tests/components/vultr/test_switch.py @@ -3,25 +3,25 @@ import json import unittest from unittest.mock import patch -import requests_mock import pytest +import requests_mock import voluptuous as vol -from homeassistant.components.vultr import switch as vultr from homeassistant.components import vultr as base_vultr from homeassistant.components.vultr import ( ATTR_ALLOWED_BANDWIDTH, ATTR_AUTO_BACKUPS, - ATTR_IPV4_ADDRESS, ATTR_COST_PER_MONTH, ATTR_CREATED_AT, + ATTR_IPV4_ADDRESS, ATTR_SUBSCRIPTION_ID, CONF_SUBSCRIPTION, + switch as vultr, ) -from homeassistant.const import CONF_PLATFORM, CONF_NAME +from homeassistant.const import CONF_NAME, CONF_PLATFORM -from tests.components.vultr.test_init import VALID_CONFIG from tests.common import get_test_home_assistant, load_fixture +from tests.components.vultr.test_init import VALID_CONFIG class TestVultrSwitchSetup(unittest.TestCase): From 4bb670cdf7b43e8b0d5c3a782bcce61b22fdb06a Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Mon, 9 Dec 2019 13:33:28 +0100 Subject: [PATCH 202/677] HomeAssistant-pyozw 0.1.7 (#29743) --- homeassistant/components/zwave/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zwave/manifest.json b/homeassistant/components/zwave/manifest.json index 78362b25462..c781a493b55 100644 --- a/homeassistant/components/zwave/manifest.json +++ b/homeassistant/components/zwave/manifest.json @@ -3,7 +3,7 @@ "name": "Z-Wave", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zwave", - "requirements": ["homeassistant-pyozw==0.1.6", "pydispatcher==2.0.5"], + "requirements": ["homeassistant-pyozw==0.1.7", "pydispatcher==2.0.5"], "dependencies": [], "codeowners": ["@home-assistant/z-wave"] } diff --git a/requirements_all.txt b/requirements_all.txt index e5983972243..23449f1a59c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -670,7 +670,7 @@ holidays==0.9.11 home-assistant-frontend==20191204.0 # homeassistant.components.zwave -homeassistant-pyozw==0.1.6 +homeassistant-pyozw==0.1.7 # homeassistant.components.homekit_controller homekit[IP]==0.15.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 405e632c0cc..9f30a3cfcc7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -234,7 +234,7 @@ holidays==0.9.11 home-assistant-frontend==20191204.0 # homeassistant.components.zwave -homeassistant-pyozw==0.1.6 +homeassistant-pyozw==0.1.7 # homeassistant.components.homekit_controller homekit[IP]==0.15.0 From df74272ba6311d527fd07198929c80a45d9fed15 Mon Sep 17 00:00:00 2001 From: tetienne Date: Mon, 9 Dec 2019 13:35:14 +0100 Subject: [PATCH 203/677] Remove Tahoma component #29744 (#29745) --- CODEOWNERS | 1 - homeassistant/components/tahoma/__init__.py | 140 ---------- .../components/tahoma/binary_sensor.py | 95 ------- homeassistant/components/tahoma/cover.py | 249 ------------------ homeassistant/components/tahoma/manifest.json | 12 - homeassistant/components/tahoma/scene.py | 41 --- homeassistant/components/tahoma/sensor.py | 106 -------- homeassistant/components/tahoma/switch.py | 109 -------- requirements_all.txt | 3 - 9 files changed, 756 deletions(-) delete mode 100644 homeassistant/components/tahoma/__init__.py delete mode 100644 homeassistant/components/tahoma/binary_sensor.py delete mode 100644 homeassistant/components/tahoma/cover.py delete mode 100644 homeassistant/components/tahoma/manifest.json delete mode 100644 homeassistant/components/tahoma/scene.py delete mode 100644 homeassistant/components/tahoma/sensor.py delete mode 100644 homeassistant/components/tahoma/switch.py diff --git a/CODEOWNERS b/CODEOWNERS index 6723244c089..afea92c8847 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -317,7 +317,6 @@ homeassistant/components/syncthru/* @nielstron homeassistant/components/synology_srm/* @aerialls homeassistant/components/syslog/* @fabaff homeassistant/components/tado/* @michaelarnauts -homeassistant/components/tahoma/* @philklei homeassistant/components/tautulli/* @ludeeus homeassistant/components/tellduslive/* @fredrike homeassistant/components/template/* @PhracturedBlue diff --git a/homeassistant/components/tahoma/__init__.py b/homeassistant/components/tahoma/__init__.py deleted file mode 100644 index 640cc6418d0..00000000000 --- a/homeassistant/components/tahoma/__init__.py +++ /dev/null @@ -1,140 +0,0 @@ -"""Support for Tahoma devices.""" -from collections import defaultdict -import logging - -from requests.exceptions import RequestException -from tahoma_api import Action, TahomaApi -import voluptuous as vol - -from homeassistant.const import CONF_EXCLUDE, CONF_PASSWORD, CONF_USERNAME -from homeassistant.helpers import config_validation as cv, discovery -from homeassistant.helpers.entity import Entity - -_LOGGER = logging.getLogger(__name__) - -DOMAIN = "tahoma" - -TAHOMA_ID_FORMAT = "{}_{}" - -CONFIG_SCHEMA = vol.Schema( - { - DOMAIN: vol.Schema( - { - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_EXCLUDE, default=[]): vol.All( - cv.ensure_list, [cv.string] - ), - } - ) - }, - extra=vol.ALLOW_EXTRA, -) - -TAHOMA_COMPONENTS = ["scene", "sensor", "cover", "switch", "binary_sensor"] - -TAHOMA_TYPES = { - "io:ExteriorVenetianBlindIOComponent": "cover", - "io:HorizontalAwningIOComponent": "cover", - "io:LightIOSystemSensor": "sensor", - "io:OnOffIOComponent": "switch", - "io:OnOffLightIOComponent": "switch", - "io:RollerShutterGenericIOComponent": "cover", - "io:RollerShutterUnoIOComponent": "cover", - "io:RollerShutterVeluxIOComponent": "cover", - "io:RollerShutterWithLowSpeedManagementIOComponent": "cover", - "io:SomfyBasicContactIOSystemSensor": "sensor", - "io:SomfyContactIOSystemSensor": "sensor", - "io:VerticalExteriorAwningIOComponent": "cover", - "io:VerticalInteriorBlindVeluxIOComponent": "cover", - "io:WindowOpenerVeluxIOComponent": "cover", - "io:GarageOpenerIOComponent": "cover", - "io:DiscreteGarageOpenerIOComponent": "cover", - "rtds:RTDSContactSensor": "sensor", - "rtds:RTDSMotionSensor": "sensor", - "rtds:RTDSSmokeSensor": "smoke", - "rts:BlindRTSComponent": "cover", - "rts:CurtainRTSComponent": "cover", - "rts:DualCurtainRTSComponent": "cover", - "rts:ExteriorVenetianBlindRTSComponent": "cover", - "rts:GarageDoor4TRTSComponent": "switch", - "rts:RollerShutterRTSComponent": "cover", - "rts:VenetianBlindRTSComponent": "cover", -} - - -def setup(hass, config): - """Activate Tahoma component.""" - - conf = config[DOMAIN] - username = conf.get(CONF_USERNAME) - password = conf.get(CONF_PASSWORD) - exclude = conf.get(CONF_EXCLUDE) - try: - api = TahomaApi(username, password) - except RequestException: - _LOGGER.exception("Error when trying to log in to the Tahoma API") - return False - - try: - api.get_setup() - devices = api.get_devices() - scenes = api.get_action_groups() - except RequestException: - _LOGGER.exception("Error when getting devices from the Tahoma API") - return False - - hass.data[DOMAIN] = {"controller": api, "devices": defaultdict(list), "scenes": []} - - for device in devices: - _device = api.get_device(device) - if all(ext not in _device.type for ext in exclude): - device_type = map_tahoma_device(_device) - if device_type is None: - _LOGGER.warning( - "Unsupported type %s for Tahoma device %s", - _device.type, - _device.label, - ) - continue - hass.data[DOMAIN]["devices"][device_type].append(_device) - - for scene in scenes: - hass.data[DOMAIN]["scenes"].append(scene) - - for component in TAHOMA_COMPONENTS: - discovery.load_platform(hass, component, DOMAIN, {}, config) - - return True - - -def map_tahoma_device(tahoma_device): - """Map Tahoma device types to Home Assistant components.""" - return TAHOMA_TYPES.get(tahoma_device.type) - - -class TahomaDevice(Entity): - """Representation of a Tahoma device entity.""" - - def __init__(self, tahoma_device, controller): - """Initialize the device.""" - self.tahoma_device = tahoma_device - self.controller = controller - self._name = self.tahoma_device.label - - @property - def name(self): - """Return the name of the device.""" - return self._name - - @property - def device_state_attributes(self): - """Return the state attributes of the device.""" - return {"tahoma_device_id": self.tahoma_device.url} - - def apply_action(self, cmd_name, *args): - """Apply Action to Device.""" - - action = Action(self.tahoma_device.url) - action.add_command(cmd_name, *args) - self.controller.apply_actions("HomeAssistant", [action]) diff --git a/homeassistant/components/tahoma/binary_sensor.py b/homeassistant/components/tahoma/binary_sensor.py deleted file mode 100644 index 81078ab480b..00000000000 --- a/homeassistant/components/tahoma/binary_sensor.py +++ /dev/null @@ -1,95 +0,0 @@ -"""Support for Tahoma binary sensors.""" -from datetime import timedelta -import logging - -from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.const import ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON - -from . import DOMAIN as TAHOMA_DOMAIN, TahomaDevice - -_LOGGER = logging.getLogger(__name__) - -SCAN_INTERVAL = timedelta(seconds=120) - - -def setup_platform(hass, config, add_entities, 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_entities(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 - self._available = False - - @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 - - @property - def available(self): - """Return True if entity is available.""" - return self._available - - 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: - # 'lowBattery' for low battery warning. 'dead' for not available. - self._battery = self.tahoma_device.active_states["core:SensorDefectState"] - self._available = bool(self._battery != "dead") - else: - self._battery = None - self._available = True - - 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/cover.py b/homeassistant/components/tahoma/cover.py deleted file mode 100644 index e11c2f4cdf5..00000000000 --- a/homeassistant/components/tahoma/cover.py +++ /dev/null @@ -1,249 +0,0 @@ -"""Support for Tahoma cover - shutters etc.""" -from datetime import timedelta -import logging - -from homeassistant.components.cover import ( - ATTR_POSITION, - DEVICE_CLASS_AWNING, - DEVICE_CLASS_BLIND, - DEVICE_CLASS_CURTAIN, - DEVICE_CLASS_GARAGE, - DEVICE_CLASS_SHUTTER, - DEVICE_CLASS_WINDOW, - CoverDevice, -) -from homeassistant.util.dt import utcnow - -from . import DOMAIN as TAHOMA_DOMAIN, TahomaDevice - -_LOGGER = logging.getLogger(__name__) - -ATTR_MEM_POS = "memorized_position" -ATTR_RSSI_LEVEL = "rssi_level" -ATTR_LOCK_START_TS = "lock_start_ts" -ATTR_LOCK_END_TS = "lock_end_ts" -ATTR_LOCK_LEVEL = "lock_level" -ATTR_LOCK_ORIG = "lock_originator" - -HORIZONTAL_AWNING = "io:HorizontalAwningIOComponent" - -TAHOMA_DEVICE_CLASSES = { - "io:ExteriorVenetianBlindIOComponent": DEVICE_CLASS_BLIND, - HORIZONTAL_AWNING: DEVICE_CLASS_AWNING, - "io:RollerShutterGenericIOComponent": DEVICE_CLASS_SHUTTER, - "io:RollerShutterUnoIOComponent": DEVICE_CLASS_SHUTTER, - "io:RollerShutterVeluxIOComponent": DEVICE_CLASS_SHUTTER, - "io:RollerShutterWithLowSpeedManagementIOComponent": DEVICE_CLASS_SHUTTER, - "io:VerticalExteriorAwningIOComponent": DEVICE_CLASS_AWNING, - "io:VerticalInteriorBlindVeluxIOComponent": DEVICE_CLASS_BLIND, - "io:WindowOpenerVeluxIOComponent": DEVICE_CLASS_WINDOW, - "io:GarageOpenerIOComponent": DEVICE_CLASS_GARAGE, - "io:DiscreteGarageOpenerIOComponent": DEVICE_CLASS_GARAGE, - "rts:BlindRTSComponent": DEVICE_CLASS_BLIND, - "rts:CurtainRTSComponent": DEVICE_CLASS_CURTAIN, - "rts:DualCurtainRTSComponent": DEVICE_CLASS_CURTAIN, - "rts:ExteriorVenetianBlindRTSComponent": DEVICE_CLASS_BLIND, - "rts:RollerShutterRTSComponent": DEVICE_CLASS_SHUTTER, - "rts:VenetianBlindRTSComponent": DEVICE_CLASS_BLIND, -} - - -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the Tahoma covers.""" - controller = hass.data[TAHOMA_DOMAIN]["controller"] - devices = [] - for device in hass.data[TAHOMA_DOMAIN]["devices"]["cover"]: - devices.append(TahomaCover(device, controller)) - add_entities(devices, True) - - -class TahomaCover(TahomaDevice, CoverDevice): - """Representation a Tahoma Cover.""" - - def __init__(self, tahoma_device, controller): - """Initialize the device.""" - super().__init__(tahoma_device, controller) - - self._closure = 0 - # 100 equals open - self._position = 100 - self._closed = False - self._rssi_level = None - self._icon = None - # Can be 0 and bigger - self._lock_timer = 0 - self._lock_start_ts = None - self._lock_end_ts = None - # Can be 'comfortLevel1', 'comfortLevel2', 'comfortLevel3', - # 'comfortLevel4', 'environmentProtection', 'humanProtection', - # 'userLevel1', 'userLevel2' - self._lock_level = None - # Can be 'LSC', 'SAAC', 'SFC', 'UPS', 'externalGateway', 'localUser', - # 'myself', 'rain', 'security', 'temperature', 'timer', 'user', 'wind' - self._lock_originator = None - - def update(self): - """Update method.""" - self.controller.get_states([self.tahoma_device]) - - # For vertical covers - self._closure = self.tahoma_device.active_states.get("core:ClosureState") - # For horizontal covers - if self._closure is None: - self._closure = self.tahoma_device.active_states.get("core:DeploymentState") - - # For all, if available - if "core:PriorityLockTimerState" in self.tahoma_device.active_states: - old_lock_timer = self._lock_timer - self._lock_timer = self.tahoma_device.active_states[ - "core:PriorityLockTimerState" - ] - # Derive timestamps from _lock_timer, only if not already set or - # something has changed - if self._lock_timer > 0: - _LOGGER.debug("Update %s, lock_timer: %d", self._name, self._lock_timer) - if self._lock_start_ts is None: - self._lock_start_ts = utcnow() - if self._lock_end_ts is None or old_lock_timer != self._lock_timer: - self._lock_end_ts = utcnow() + timedelta(seconds=self._lock_timer) - else: - self._lock_start_ts = None - self._lock_end_ts = None - else: - self._lock_timer = 0 - self._lock_start_ts = None - self._lock_end_ts = None - - self._lock_level = self.tahoma_device.active_states.get( - "io:PriorityLockLevelState" - ) - - self._lock_originator = self.tahoma_device.active_states.get( - "io:PriorityLockOriginatorState" - ) - - self._rssi_level = self.tahoma_device.active_states.get("core:RSSILevelState") - - # Define which icon to use - if self._lock_timer > 0: - if self._lock_originator == "wind": - self._icon = "mdi:weather-windy" - else: - self._icon = "mdi:lock-alert" - else: - self._icon = None - - # Define current position. - # _position: 0 is closed, 100 is fully open. - # 'core:ClosureState': 100 is closed, 0 is fully open. - if self._closure is not None: - if self.tahoma_device.type == HORIZONTAL_AWNING: - self._position = self._closure - else: - self._position = 100 - self._closure - if self._position <= 5: - self._position = 0 - if self._position >= 95: - self._position = 100 - self._closed = self._position == 0 - else: - self._position = None - if "core:OpenClosedState" in self.tahoma_device.active_states: - self._closed = ( - self.tahoma_device.active_states["core:OpenClosedState"] == "closed" - ) - else: - self._closed = False - - _LOGGER.debug("Update %s, position: %d", self._name, self._position) - - @property - def current_cover_position(self): - """Return current position of cover.""" - return self._position - - def set_cover_position(self, **kwargs): - """Move the cover to a specific position.""" - if self.tahoma_device.type == "io:WindowOpenerVeluxIOComponent": - command = "setClosure" - else: - command = "setPosition" - - if self.tahoma_device.type == HORIZONTAL_AWNING: - self.apply_action(command, kwargs.get(ATTR_POSITION, 0)) - else: - self.apply_action(command, 100 - kwargs.get(ATTR_POSITION, 0)) - - @property - def is_closed(self): - """Return if the cover is closed.""" - return self._closed - - @property - def device_class(self): - """Return the class of the device.""" - return TAHOMA_DEVICE_CLASSES.get(self.tahoma_device.type) - - @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 "core:Memorized1PositionState" in self.tahoma_device.active_states: - attr[ATTR_MEM_POS] = self.tahoma_device.active_states[ - "core:Memorized1PositionState" - ] - if self._rssi_level is not None: - attr[ATTR_RSSI_LEVEL] = self._rssi_level - if self._lock_start_ts is not None: - attr[ATTR_LOCK_START_TS] = self._lock_start_ts.isoformat() - if self._lock_end_ts is not None: - attr[ATTR_LOCK_END_TS] = self._lock_end_ts.isoformat() - if self._lock_level is not None: - attr[ATTR_LOCK_LEVEL] = self._lock_level - if self._lock_originator is not None: - attr[ATTR_LOCK_ORIG] = self._lock_originator - return attr - - @property - def icon(self): - """Return the icon to use in the frontend, if any.""" - return self._icon - - def open_cover(self, **kwargs): - """Open the cover.""" - self.apply_action("open") - - def close_cover(self, **kwargs): - """Close the cover.""" - self.apply_action("close") - - def stop_cover(self, **kwargs): - """Stop the cover.""" - if ( - self.tahoma_device.type - == "io:RollerShutterWithLowSpeedManagementIOComponent" - ): - self.apply_action("setPosition", "secured") - elif self.tahoma_device.type in ( - "rts:BlindRTSComponent", - "io:ExteriorVenetianBlindIOComponent", - "rts:VenetianBlindRTSComponent", - "rts:DualCurtainRTSComponent", - "rts:ExteriorVenetianBlindRTSComponent", - "rts:BlindRTSComponent", - ): - self.apply_action("my") - elif self.tahoma_device.type in ( - HORIZONTAL_AWNING, - "io:RollerShutterGenericIOComponent", - "io:VerticalExteriorAwningIOComponent", - "io:VerticalInteriorBlindVeluxIOComponent", - "io:WindowOpenerVeluxIOComponent", - ): - self.apply_action("stop") - else: - self.apply_action("stopIdentify") diff --git a/homeassistant/components/tahoma/manifest.json b/homeassistant/components/tahoma/manifest.json deleted file mode 100644 index 1e99d4b288d..00000000000 --- a/homeassistant/components/tahoma/manifest.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "domain": "tahoma", - "name": "Tahoma", - "documentation": "https://www.home-assistant.io/integrations/tahoma", - "requirements": [ - "tahoma-api==0.0.14" - ], - "dependencies": [], - "codeowners": [ - "@philklei" - ] -} diff --git a/homeassistant/components/tahoma/scene.py b/homeassistant/components/tahoma/scene.py deleted file mode 100644 index e54ff91a0f6..00000000000 --- a/homeassistant/components/tahoma/scene.py +++ /dev/null @@ -1,41 +0,0 @@ -"""Support for Tahoma scenes.""" -import logging - -from homeassistant.components.scene import Scene - -from . import DOMAIN as TAHOMA_DOMAIN - -_LOGGER = logging.getLogger(__name__) - - -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the Tahoma scenes.""" - controller = hass.data[TAHOMA_DOMAIN]["controller"] - scenes = [] - for scene in hass.data[TAHOMA_DOMAIN]["scenes"]: - scenes.append(TahomaScene(scene, controller)) - add_entities(scenes, True) - - -class TahomaScene(Scene): - """Representation of a Tahoma scene entity.""" - - def __init__(self, tahoma_scene, controller): - """Initialize the scene.""" - self.tahoma_scene = tahoma_scene - self.controller = controller - self._name = self.tahoma_scene.name - - def activate(self): - """Activate the scene.""" - self.controller.launch_action_group(self.tahoma_scene.oid) - - @property - def name(self): - """Return the name of the scene.""" - return self._name - - @property - def device_state_attributes(self): - """Return the state attributes of the scene.""" - return {"tahoma_scene_oid": self.tahoma_scene.oid} diff --git a/homeassistant/components/tahoma/sensor.py b/homeassistant/components/tahoma/sensor.py deleted file mode 100644 index 5279b160d9c..00000000000 --- a/homeassistant/components/tahoma/sensor.py +++ /dev/null @@ -1,106 +0,0 @@ -"""Support for Tahoma sensors.""" -from datetime import timedelta -import logging - -from homeassistant.const import ATTR_BATTERY_LEVEL -from homeassistant.helpers.entity import Entity - -from . import DOMAIN as TAHOMA_DOMAIN, TahomaDevice - -_LOGGER = logging.getLogger(__name__) - -SCAN_INTERVAL = timedelta(seconds=60) - -ATTR_RSSI_LEVEL = "rssi_level" - - -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up Tahoma controller devices.""" - controller = hass.data[TAHOMA_DOMAIN]["controller"] - devices = [] - for device in hass.data[TAHOMA_DOMAIN]["devices"]["sensor"]: - devices.append(TahomaSensor(device, controller)) - add_entities(devices, True) - - -class TahomaSensor(TahomaDevice, Entity): - """Representation of a Tahoma Sensor.""" - - def __init__(self, tahoma_device, controller): - """Initialize the sensor.""" - self.current_value = None - self._available = False - super().__init__(tahoma_device, controller) - - @property - def state(self): - """Return the name of the sensor.""" - return self.current_value - - @property - def unit_of_measurement(self): - """Return the unit of measurement of this entity, if any.""" - if self.tahoma_device.type == "Temperature Sensor": - return None - if self.tahoma_device.type == "io:SomfyContactIOSystemSensor": - return None - if self.tahoma_device.type == "io:SomfyBasicContactIOSystemSensor": - return None - if self.tahoma_device.type == "io:LightIOSystemSensor": - return "lx" - if self.tahoma_device.type == "Humidity Sensor": - return "%" - if self.tahoma_device.type == "rtds:RTDSContactSensor": - return None - if self.tahoma_device.type == "rtds:RTDSMotionSensor": - return None - - def update(self): - """Update the state.""" - self.controller.get_states([self.tahoma_device]) - if self.tahoma_device.type == "io:LightIOSystemSensor": - self.current_value = self.tahoma_device.active_states["core:LuminanceState"] - self._available = bool( - self.tahoma_device.active_states.get("core:StatusState") == "available" - ) - if self.tahoma_device.type == "io:SomfyContactIOSystemSensor": - self.current_value = self.tahoma_device.active_states["core:ContactState"] - self._available = bool( - self.tahoma_device.active_states.get("core:StatusState") == "available" - ) - if self.tahoma_device.type == "io:SomfyBasicContactIOSystemSensor": - self.current_value = self.tahoma_device.active_states["core:ContactState"] - self._available = bool( - self.tahoma_device.active_states.get("core:StatusState") == "available" - ) - if self.tahoma_device.type == "rtds:RTDSContactSensor": - self.current_value = self.tahoma_device.active_states["core:ContactState"] - self._available = True - if self.tahoma_device.type == "rtds:RTDSMotionSensor": - self.current_value = self.tahoma_device.active_states["core:OccupancyState"] - self._available = True - - _LOGGER.debug("Update %s, value: %d", self._name, self.current_value) - - @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 "core:RSSILevelState" in self.tahoma_device.active_states: - attr[ATTR_RSSI_LEVEL] = self.tahoma_device.active_states[ - "core:RSSILevelState" - ] - if "core:SensorDefectState" in self.tahoma_device.active_states: - attr[ATTR_BATTERY_LEVEL] = self.tahoma_device.active_states[ - "core:SensorDefectState" - ] - return attr - - @property - def available(self): - """Return True if entity is available.""" - return self._available diff --git a/homeassistant/components/tahoma/switch.py b/homeassistant/components/tahoma/switch.py deleted file mode 100644 index a0a95ab47ce..00000000000 --- a/homeassistant/components/tahoma/switch.py +++ /dev/null @@ -1,109 +0,0 @@ -"""Support for Tahoma switches.""" -import logging - -from homeassistant.components.switch import SwitchDevice -from homeassistant.const import STATE_OFF, STATE_ON - -from . import DOMAIN as TAHOMA_DOMAIN, TahomaDevice - -_LOGGER = logging.getLogger(__name__) - -ATTR_RSSI_LEVEL = "rssi_level" - - -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up Tahoma switches.""" - controller = hass.data[TAHOMA_DOMAIN]["controller"] - devices = [] - for switch in hass.data[TAHOMA_DOMAIN]["devices"]["switch"]: - devices.append(TahomaSwitch(switch, controller)) - add_entities(devices, True) - - -class TahomaSwitch(TahomaDevice, SwitchDevice): - """Representation a Tahoma Switch.""" - - def __init__(self, tahoma_device, controller): - """Initialize the switch.""" - super().__init__(tahoma_device, controller) - self._state = STATE_OFF - self._skip_update = False - self._available = False - - def update(self): - """Update method.""" - # Postpone the immediate state check for changes that take time. - if self._skip_update: - self._skip_update = False - return - - self.controller.get_states([self.tahoma_device]) - - if self.tahoma_device.type == "io:OnOffLightIOComponent": - if self.tahoma_device.active_states.get("core:OnOffState") == "on": - self._state = STATE_ON - else: - self._state = STATE_OFF - - self._available = bool( - self.tahoma_device.active_states.get("core:StatusState") == "available" - ) - - _LOGGER.debug("Update %s, state: %s", self._name, self._state) - - @property - def device_class(self): - """Return the class of the device.""" - if self.tahoma_device.type == "rts:GarageDoor4TRTSComponent": - return "garage" - return None - - def turn_on(self, **kwargs): - """Send the on command.""" - _LOGGER.debug("Turn on: %s", self._name) - if self.tahoma_device.type == "rts:GarageDoor4TRTSComponent": - self.toggle() - else: - self.apply_action("on") - self._skip_update = True - self._state = STATE_ON - - def turn_off(self, **kwargs): - """Send the off command.""" - _LOGGER.debug("Turn off: %s", self._name) - if self.tahoma_device.type == "rts:GarageDoor4TRTSComponent": - return - - self.apply_action("off") - self._skip_update = True - self._state = STATE_OFF - - def toggle(self, **kwargs): - """Click the switch.""" - self.apply_action("cycle") - - @property - def is_on(self): - """Get whether the switch is in on state.""" - if self.tahoma_device.type == "rts:GarageDoor4TRTSComponent": - return False - return bool(self._state == STATE_ON) - - @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 "core:RSSILevelState" in self.tahoma_device.active_states: - attr[ATTR_RSSI_LEVEL] = self.tahoma_device.active_states[ - "core:RSSILevelState" - ] - return attr - - @property - def available(self): - """Return True if entity is available.""" - return self._available diff --git a/requirements_all.txt b/requirements_all.txt index 23449f1a59c..0e163f77595 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1906,9 +1906,6 @@ swisshydrodata==0.0.3 # homeassistant.components.synology_srm synology-srm==0.0.7 -# homeassistant.components.tahoma -tahoma-api==0.0.14 - # homeassistant.components.tank_utility tank_utility==1.4.0 From 73de69896b7b78e558e024d9d84c7a508c30285c Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 13:52:18 +0100 Subject: [PATCH 204/677] Sort imports according to PEP8 for components starting with "B" (#29762) --- .../components/bbb_gpio/binary_sensor.py | 4 ++-- homeassistant/components/bbb_gpio/switch.py | 6 ++--- .../components/bbox/device_tracker.py | 1 - homeassistant/components/bbox/sensor.py | 11 ++++----- .../components/beewi_smartclim/sensor.py | 8 +++---- homeassistant/components/bh1750/sensor.py | 5 ++-- homeassistant/components/bizkaibus/sensor.py | 7 +++--- .../components/blackbird/media_player.py | 1 + homeassistant/components/blinkt/light.py | 6 ++--- .../components/bloomsky/binary_sensor.py | 2 +- homeassistant/components/bloomsky/sensor.py | 4 ++-- .../components/bluesound/media_player.py | 1 + .../bluetooth_le_tracker/device_tracker.py | 14 +++++------ .../bluetooth_tracker/device_tracker.py | 2 +- homeassistant/components/bme280/sensor.py | 5 ++-- homeassistant/components/bme680/sensor.py | 2 +- homeassistant/components/bom/sensor.py | 10 ++++---- .../components/brottsplatskartan/sensor.py | 1 - homeassistant/components/browser/__init__.py | 1 + .../bt_home_hub_5/device_tracker.py | 3 +-- .../components/bayesian/test_binary_sensor.py | 2 +- .../components/blackbird/test_media_player.py | 23 ++++++++++--------- tests/components/bom/test_sensor.py | 1 + 23 files changed, 59 insertions(+), 61 deletions(-) diff --git a/homeassistant/components/bbb_gpio/binary_sensor.py b/homeassistant/components/bbb_gpio/binary_sensor.py index 105015da720..3ef13c117a2 100644 --- a/homeassistant/components/bbb_gpio/binary_sensor.py +++ b/homeassistant/components/bbb_gpio/binary_sensor.py @@ -4,8 +4,8 @@ import logging import voluptuous as vol from homeassistant.components import bbb_gpio -from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA -from homeassistant.const import DEVICE_DEFAULT_NAME, CONF_NAME +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.const import CONF_NAME, DEVICE_DEFAULT_NAME import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/bbb_gpio/switch.py b/homeassistant/components/bbb_gpio/switch.py index 45f95609758..eb75c6f374c 100644 --- a/homeassistant/components/bbb_gpio/switch.py +++ b/homeassistant/components/bbb_gpio/switch.py @@ -3,11 +3,11 @@ import logging import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA from homeassistant.components import bbb_gpio -from homeassistant.const import DEVICE_DEFAULT_NAME, CONF_NAME -from homeassistant.helpers.entity import ToggleEntity +from homeassistant.components.switch import PLATFORM_SCHEMA +from homeassistant.const import CONF_NAME, DEVICE_DEFAULT_NAME import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import ToggleEntity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/bbox/device_tracker.py b/homeassistant/components/bbox/device_tracker.py index 122016ecf96..8097c11eb89 100644 --- a/homeassistant/components/bbox/device_tracker.py +++ b/homeassistant/components/bbox/device_tracker.py @@ -5,7 +5,6 @@ import logging from typing import List import pybbox - import voluptuous as vol from homeassistant.components.device_tracker import ( diff --git a/homeassistant/components/bbox/sensor.py b/homeassistant/components/bbox/sensor.py index 7b795a8788e..f5e5865f6f0 100644 --- a/homeassistant/components/bbox/sensor.py +++ b/homeassistant/components/bbox/sensor.py @@ -1,20 +1,19 @@ """Support for Bbox Bouygues Modem Router.""" -import logging from datetime import timedelta +import logging -import requests import pybbox - +import requests import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_NAME, - CONF_MONITORED_VARIABLES, ATTR_ATTRIBUTION, + CONF_MONITORED_VARIABLES, + CONF_NAME, DEVICE_CLASS_TIMESTAMP, ) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from homeassistant.util.dt import utcnow diff --git a/homeassistant/components/beewi_smartclim/sensor.py b/homeassistant/components/beewi_smartclim/sensor.py index 7bfa8883013..be1697e4f88 100644 --- a/homeassistant/components/beewi_smartclim/sensor.py +++ b/homeassistant/components/beewi_smartclim/sensor.py @@ -5,15 +5,15 @@ from beewi_smartclim import BeewiSmartClimPoller import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -import homeassistant.helpers.config_validation as cv from homeassistant.const import ( - CONF_NAME, CONF_MAC, - TEMP_CELSIUS, + CONF_NAME, + DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, - DEVICE_CLASS_BATTERY, + TEMP_CELSIUS, ) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/bh1750/sensor.py b/homeassistant/components/bh1750/sensor.py index cc91fa48bae..924bfcd5507 100644 --- a/homeassistant/components/bh1750/sensor.py +++ b/homeassistant/components/bh1750/sensor.py @@ -2,14 +2,13 @@ from functools import partial import logging -import smbus # pylint: disable=import-error from i2csense.bh1750 import BH1750 # pylint: disable=import-error - +import smbus # pylint: disable=import-error import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -import homeassistant.helpers.config_validation as cv from homeassistant.const import CONF_NAME, DEVICE_CLASS_ILLUMINANCE +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/bizkaibus/sensor.py b/homeassistant/components/bizkaibus/sensor.py index c54a61c66b1..931fbbb834d 100644 --- a/homeassistant/components/bizkaibus/sensor.py +++ b/homeassistant/components/bizkaibus/sensor.py @@ -2,15 +2,14 @@ import logging -import voluptuous as vol from bizkaibus.bizkaibus import BizkaibusData -import homeassistant.helpers.config_validation as cv +import voluptuous as vol -from homeassistant.const import CONF_NAME from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import CONF_NAME +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity - _LOGGER = logging.getLogger(__name__) ATTR_DUE_IN = "Due in" diff --git a/homeassistant/components/blackbird/media_player.py b/homeassistant/components/blackbird/media_player.py index 08efc1e6647..a0ea369bb9b 100644 --- a/homeassistant/components/blackbird/media_player.py +++ b/homeassistant/components/blackbird/media_player.py @@ -22,6 +22,7 @@ from homeassistant.const import ( STATE_ON, ) import homeassistant.helpers.config_validation as cv + from .const import DOMAIN, SERVICE_SETALLZONES _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/blinkt/light.py b/homeassistant/components/blinkt/light.py index e626a73d287..0fedc2b794b 100644 --- a/homeassistant/components/blinkt/light.py +++ b/homeassistant/components/blinkt/light.py @@ -4,16 +4,16 @@ import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.light import ( ATTR_BRIGHTNESS, - SUPPORT_BRIGHTNESS, ATTR_HS_COLOR, + PLATFORM_SCHEMA, + SUPPORT_BRIGHTNESS, SUPPORT_COLOR, Light, - PLATFORM_SCHEMA, ) from homeassistant.const import CONF_NAME +import homeassistant.helpers.config_validation as cv import homeassistant.util.color as color_util _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/bloomsky/binary_sensor.py b/homeassistant/components/bloomsky/binary_sensor.py index 99951fcf5c5..cc6562a0bc1 100644 --- a/homeassistant/components/bloomsky/binary_sensor.py +++ b/homeassistant/components/bloomsky/binary_sensor.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice from homeassistant.const import CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/bloomsky/sensor.py b/homeassistant/components/bloomsky/sensor.py index 18f60036397..84871b7b30e 100644 --- a/homeassistant/components/bloomsky/sensor.py +++ b/homeassistant/components/bloomsky/sensor.py @@ -4,9 +4,9 @@ import logging import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import TEMP_FAHRENHEIT, TEMP_CELSIUS, CONF_MONITORED_CONDITIONS -from homeassistant.helpers.entity import Entity +from homeassistant.const import CONF_MONITORED_CONDITIONS, TEMP_CELSIUS, TEMP_FAHRENHEIT import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity from . import BLOOMSKY diff --git a/homeassistant/components/bluesound/media_player.py b/homeassistant/components/bluesound/media_player.py index 5a9f3561dc9..04ba21555d4 100644 --- a/homeassistant/components/bluesound/media_player.py +++ b/homeassistant/components/bluesound/media_player.py @@ -49,6 +49,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_time_interval from homeassistant.util import Throttle import homeassistant.util.dt as dt_util + from .const import ( DOMAIN, SERVICE_CLEAR_TIMER, diff --git a/homeassistant/components/bluetooth_le_tracker/device_tracker.py b/homeassistant/components/bluetooth_le_tracker/device_tracker.py index 18edd750639..40f25f2fc43 100644 --- a/homeassistant/components/bluetooth_le_tracker/device_tracker.py +++ b/homeassistant/components/bluetooth_le_tracker/device_tracker.py @@ -4,18 +4,18 @@ import logging import pygatt # pylint: disable=import-error -from homeassistant.helpers.event import track_point_in_utc_time +from homeassistant.components.device_tracker.const import ( + CONF_SCAN_INTERVAL, + CONF_TRACK_NEW, + SCAN_INTERVAL, + SOURCE_TYPE_BLUETOOTH_LE, +) from homeassistant.components.device_tracker.legacy import ( YAML_DEVICES, async_load_config, ) -from homeassistant.components.device_tracker.const import ( - CONF_TRACK_NEW, - CONF_SCAN_INTERVAL, - SCAN_INTERVAL, - SOURCE_TYPE_BLUETOOTH_LE, -) from homeassistant.const import EVENT_HOMEASSISTANT_STOP +from homeassistant.helpers.event import track_point_in_utc_time import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/bluetooth_tracker/device_tracker.py b/homeassistant/components/bluetooth_tracker/device_tracker.py index 102c8e494aa..d833f60c84f 100644 --- a/homeassistant/components/bluetooth_tracker/device_tracker.py +++ b/homeassistant/components/bluetooth_tracker/device_tracker.py @@ -1,7 +1,7 @@ """Tracking for bluetooth devices.""" import asyncio import logging -from typing import List, Set, Tuple, Optional +from typing import List, Optional, Set, Tuple # pylint: disable=import-error import bluetooth diff --git a/homeassistant/components/bme280/sensor.py b/homeassistant/components/bme280/sensor.py index b9bc18e6abf..e1e33210c9b 100644 --- a/homeassistant/components/bme280/sensor.py +++ b/homeassistant/components/bme280/sensor.py @@ -3,14 +3,13 @@ from datetime import timedelta from functools import partial import logging -import smbus # pylint: disable=import-error from i2csense.bme280 import BME280 # pylint: disable=import-error - +import smbus # pylint: disable=import-error import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import CONF_MONITORED_CONDITIONS, CONF_NAME, TEMP_FAHRENHEIT import homeassistant.helpers.config_validation as cv -from homeassistant.const import TEMP_FAHRENHEIT, CONF_NAME, CONF_MONITORED_CONDITIONS from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from homeassistant.util.temperature import celsius_to_fahrenheit diff --git a/homeassistant/components/bme680/sensor.py b/homeassistant/components/bme680/sensor.py index 5a1e9fd120f..65c87890242 100644 --- a/homeassistant/components/bme680/sensor.py +++ b/homeassistant/components/bme680/sensor.py @@ -3,8 +3,8 @@ import logging import threading from time import sleep, time -from smbus import SMBus # pylint: disable=import-error import bme680 # pylint: disable=import-error +from smbus import SMBus # pylint: disable=import-error import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA diff --git a/homeassistant/components/bom/sensor.py b/homeassistant/components/bom/sensor.py index ed22be003ad..7d951968cb2 100644 --- a/homeassistant/components/bom/sensor.py +++ b/homeassistant/components/bom/sensor.py @@ -12,19 +12,19 @@ import zipfile import requests import voluptuous as vol -import homeassistant.helpers.config_validation as cv -import homeassistant.util.dt as dt_util from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_MONITORED_CONDITIONS, - TEMP_CELSIUS, - CONF_NAME, ATTR_ATTRIBUTION, CONF_LATITUDE, CONF_LONGITUDE, + CONF_MONITORED_CONDITIONS, + CONF_NAME, + TEMP_CELSIUS, ) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle +import homeassistant.util.dt as dt_util _RESOURCE = "http://www.bom.gov.au/fwo/{}/{}.{}.json" _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/brottsplatskartan/sensor.py b/homeassistant/components/brottsplatskartan/sensor.py index d8592f44fff..282433aa7a4 100644 --- a/homeassistant/components/brottsplatskartan/sensor.py +++ b/homeassistant/components/brottsplatskartan/sensor.py @@ -5,7 +5,6 @@ import logging import uuid import brottsplatskartan - import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA diff --git a/homeassistant/components/browser/__init__.py b/homeassistant/components/browser/__init__.py index b7612def701..fc0e9eccb3a 100644 --- a/homeassistant/components/browser/__init__.py +++ b/homeassistant/components/browser/__init__.py @@ -1,5 +1,6 @@ """Support for launching a web browser on the host machine.""" import webbrowser + import voluptuous as vol ATTR_URL = "url" diff --git a/homeassistant/components/bt_home_hub_5/device_tracker.py b/homeassistant/components/bt_home_hub_5/device_tracker.py index 20ad909c44e..32b8e2aa050 100644 --- a/homeassistant/components/bt_home_hub_5/device_tracker.py +++ b/homeassistant/components/bt_home_hub_5/device_tracker.py @@ -2,16 +2,15 @@ import logging import bthomehub5_devicelist - import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.device_tracker import ( DOMAIN, PLATFORM_SCHEMA, DeviceScanner, ) from homeassistant.const import CONF_HOST +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/bayesian/test_binary_sensor.py b/tests/components/bayesian/test_binary_sensor.py index 0ea92b143be..d9341bb3271 100644 --- a/tests/components/bayesian/test_binary_sensor.py +++ b/tests/components/bayesian/test_binary_sensor.py @@ -1,8 +1,8 @@ """The test for the bayesian sensor platform.""" import unittest -from homeassistant.setup import setup_component from homeassistant.components.bayesian import binary_sensor as bayesian +from homeassistant.setup import setup_component from tests.common import get_test_home_assistant diff --git a/tests/components/blackbird/test_media_player.py b/tests/components/blackbird/test_media_player.py index 0b6eda16c15..b090368a4ce 100644 --- a/tests/components/blackbird/test_media_player.py +++ b/tests/components/blackbird/test_media_player.py @@ -1,24 +1,25 @@ """The tests for the Monoprice Blackbird media player platform.""" +from collections import defaultdict import unittest from unittest import mock + +import pytest import voluptuous as vol -from collections import defaultdict -from homeassistant.components.media_player.const import ( - SUPPORT_TURN_ON, - SUPPORT_TURN_OFF, - SUPPORT_SELECT_SOURCE, -) -from homeassistant.const import STATE_ON, STATE_OFF - -import tests.common +from homeassistant.components.blackbird.const import DOMAIN, SERVICE_SETALLZONES from homeassistant.components.blackbird.media_player import ( DATA_BLACKBIRD, PLATFORM_SCHEMA, setup_platform, ) -from homeassistant.components.blackbird.const import DOMAIN, SERVICE_SETALLZONES -import pytest +from homeassistant.components.media_player.const import ( + SUPPORT_SELECT_SOURCE, + SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, +) +from homeassistant.const import STATE_OFF, STATE_ON + +import tests.common class AttrDict(dict): diff --git a/tests/components/bom/test_sensor.py b/tests/components/bom/test_sensor.py index 66d00e50796..6d452f7a6a3 100644 --- a/tests/components/bom/test_sensor.py +++ b/tests/components/bom/test_sensor.py @@ -10,6 +10,7 @@ import requests from homeassistant.components import sensor from homeassistant.components.bom.sensor import BOMCurrentData from homeassistant.setup import setup_component + from tests.common import assert_setup_component, get_test_home_assistant, load_fixture VALID_CONFIG = { From 96961b9bccebe66b2ad18567fceb70e01db59854 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 13:57:24 +0100 Subject: [PATCH 205/677] Sort imports according to PEP8 for components starting with "A" (#29761) --- .../alarmdecoder/alarm_control_panel.py | 2 +- homeassistant/components/alert/__init__.py | 20 +++++++++--------- .../components/alpha_vantage/sensor.py | 4 ++-- homeassistant/components/amazon_polly/tts.py | 2 +- homeassistant/components/amcrest/__init__.py | 4 ++-- .../components/amcrest/binary_sensor.py | 4 ++-- .../components/androidtv/media_player.py | 6 +++--- .../components/anthemav/media_player.py | 3 +-- .../components/apcupsd/binary_sensor.py | 4 ++-- homeassistant/components/api/__init__.py | 6 +++--- homeassistant/components/apns/notify.py | 2 +- homeassistant/components/apple_tv/__init__.py | 2 +- homeassistant/components/apprise/notify.py | 6 ++---- .../components/aprs/device_tracker.py | 6 ++---- .../components/aquostv/media_player.py | 3 +-- .../components/arcam_fmj/__init__.py | 21 ++++++++++--------- .../components/arcam_fmj/media_player.py | 10 ++++----- homeassistant/components/arwn/sensor.py | 2 +- .../components/asterisk_cdr/mailbox.py | 12 ++++++----- homeassistant/components/atome/sensor.py | 13 ++++++------ .../components/aurora/binary_sensor.py | 2 +- .../components/aurora_abb_powerone/sensor.py | 2 +- .../components/automatic/device_tracker.py | 3 +-- homeassistant/components/avea/light.py | 2 +- .../components/azure_event_hub/__init__.py | 2 +- tests/components/alert/test_init.py | 10 ++++----- .../ambient_station/test_config_flow.py | 4 ++-- .../components/androidtv/test_media_player.py | 3 +-- tests/components/apns/test_notify.py | 6 +++--- tests/components/apprise/test_notify.py | 3 +-- tests/components/arlo/test_sensor.py | 11 +++++----- .../components/asuswrt/test_device_tracker.py | 8 +++---- tests/components/aurora/test_binary_sensor.py | 3 ++- .../automatic/test_device_tracker.py | 5 +++-- tests/components/awair/test_sensor.py | 2 +- 35 files changed, 97 insertions(+), 101 deletions(-) diff --git a/homeassistant/components/alarmdecoder/alarm_control_panel.py b/homeassistant/components/alarmdecoder/alarm_control_panel.py index d2e9fd136a8..66960ca3034 100644 --- a/homeassistant/components/alarmdecoder/alarm_control_panel.py +++ b/homeassistant/components/alarmdecoder/alarm_control_panel.py @@ -4,8 +4,8 @@ import logging import voluptuous as vol from homeassistant.components.alarm_control_panel import ( - AlarmControlPanel, FORMAT_NUMBER, + AlarmControlPanel, ) from homeassistant.components.alarm_control_panel.const import ( SUPPORT_ALARM_ARM_AWAY, diff --git a/homeassistant/components/alert/__init__.py b/homeassistant/components/alert/__init__.py index 420d730933c..09e2883c332 100644 --- a/homeassistant/components/alert/__init__.py +++ b/homeassistant/components/alert/__init__.py @@ -1,30 +1,30 @@ """Support for repeating alerts when conditions are met.""" import asyncio -import logging from datetime import timedelta +import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.notify import ( + ATTR_DATA, ATTR_MESSAGE, ATTR_TITLE, - ATTR_DATA, DOMAIN as DOMAIN_NOTIFY, ) from homeassistant.const import ( + ATTR_ENTITY_ID, CONF_ENTITY_ID, - STATE_IDLE, CONF_NAME, CONF_STATE, - STATE_ON, - STATE_OFF, - SERVICE_TURN_ON, - SERVICE_TURN_OFF, SERVICE_TOGGLE, - ATTR_ENTITY_ID, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_IDLE, + STATE_OFF, + STATE_ON, ) -from homeassistant.helpers import service, event +from homeassistant.helpers import event, service +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import ToggleEntity from homeassistant.util.dt import now diff --git a/homeassistant/components/alpha_vantage/sensor.py b/homeassistant/components/alpha_vantage/sensor.py index da29e4e25e1..7d871c286e5 100644 --- a/homeassistant/components/alpha_vantage/sensor.py +++ b/homeassistant/components/alpha_vantage/sensor.py @@ -2,9 +2,9 @@ from datetime import timedelta import logging -import voluptuous as vol -from alpha_vantage.timeseries import TimeSeries from alpha_vantage.foreignexchange import ForeignExchange +from alpha_vantage.timeseries import TimeSeries +import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY, CONF_CURRENCY, CONF_NAME diff --git a/homeassistant/components/amazon_polly/tts.py b/homeassistant/components/amazon_polly/tts.py index 3d05236935f..bcb4a24e95b 100644 --- a/homeassistant/components/amazon_polly/tts.py +++ b/homeassistant/components/amazon_polly/tts.py @@ -1,7 +1,7 @@ """Support for the Amazon Polly text to speech service.""" import logging -import boto3 +import boto3 import voluptuous as vol from homeassistant.components.tts import PLATFORM_SCHEMA, Provider diff --git a/homeassistant/components/amcrest/__init__.py b/homeassistant/components/amcrest/__init__.py index d49104a0b26..3420f42f9c9 100644 --- a/homeassistant/components/amcrest/__init__.py +++ b/homeassistant/components/amcrest/__init__.py @@ -1,6 +1,6 @@ """Support for Amcrest IP cameras.""" -import logging from datetime import timedelta +import logging import threading import aiohttp @@ -36,7 +36,7 @@ from homeassistant.helpers.service import async_extract_entity_ids from .binary_sensor import BINARY_SENSOR_MOTION_DETECTED, BINARY_SENSORS from .camera import CAMERA_SERVICES, STREAM_SOURCE_LIST -from .const import CAMERAS, DOMAIN, DATA_AMCREST, DEVICES, SERVICE_UPDATE +from .const import CAMERAS, DATA_AMCREST, DEVICES, DOMAIN, SERVICE_UPDATE from .helpers import service_signal from .sensor import SENSOR_MOTION_DETECTOR, SENSORS from .switch import SWITCHES diff --git a/homeassistant/components/amcrest/binary_sensor.py b/homeassistant/components/amcrest/binary_sensor.py index f8b50d1114e..ac16f0664aa 100644 --- a/homeassistant/components/amcrest/binary_sensor.py +++ b/homeassistant/components/amcrest/binary_sensor.py @@ -5,11 +5,11 @@ import logging from amcrest import AmcrestError from homeassistant.components.binary_sensor import ( - BinarySensorDevice, DEVICE_CLASS_CONNECTIVITY, DEVICE_CLASS_MOTION, + BinarySensorDevice, ) -from homeassistant.const import CONF_NAME, CONF_BINARY_SENSORS +from homeassistant.const import CONF_BINARY_SENSORS, CONF_NAME from homeassistant.helpers.dispatcher import async_dispatcher_connect from .const import ( diff --git a/homeassistant/components/androidtv/media_player.py b/homeassistant/components/androidtv/media_player.py index b1cb86f7633..8b7f1880264 100644 --- a/homeassistant/components/androidtv/media_player.py +++ b/homeassistant/components/androidtv/media_player.py @@ -2,7 +2,6 @@ import functools import logging import os -import voluptuous as vol from adb_shell.auth.keygen import keygen from adb_shell.exceptions import ( @@ -11,10 +10,11 @@ from adb_shell.exceptions import ( InvalidResponseError, TcpTimeoutException, ) -from androidtv import setup, ha_state_detection_rules_validator +from androidtv import ha_state_detection_rules_validator, setup from androidtv.constants import APPS, KEYS +import voluptuous as vol -from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice from homeassistant.components.media_player.const import ( SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, diff --git a/homeassistant/components/anthemav/media_player.py b/homeassistant/components/anthemav/media_player.py index d472af6104e..f7b385d80a2 100644 --- a/homeassistant/components/anthemav/media_player.py +++ b/homeassistant/components/anthemav/media_player.py @@ -2,10 +2,9 @@ import logging import anthemav - import voluptuous as vol -from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice from homeassistant.components.media_player.const import ( SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, diff --git a/homeassistant/components/apcupsd/binary_sensor.py b/homeassistant/components/apcupsd/binary_sensor.py index 62f0c90a447..29825fd695e 100644 --- a/homeassistant/components/apcupsd/binary_sensor.py +++ b/homeassistant/components/apcupsd/binary_sensor.py @@ -1,10 +1,10 @@ """Support for tracking the online status of a UPS.""" import voluptuous as vol -from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA +from homeassistant.components import apcupsd +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.components import apcupsd DEFAULT_NAME = "UPS Online Status" PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( diff --git a/homeassistant/components/api/__init__.py b/homeassistant/components/api/__init__.py index d4faa55ed8c..fc2f01d418d 100644 --- a/homeassistant/components/api/__init__.py +++ b/homeassistant/components/api/__init__.py @@ -8,6 +8,7 @@ from aiohttp.web_exceptions import HTTPBadRequest import async_timeout import voluptuous as vol +from homeassistant.auth.permissions.const import POLICY_READ from homeassistant.bootstrap import DATA_LOGGING from homeassistant.components.http import HomeAssistantView from homeassistant.const import ( @@ -31,12 +32,11 @@ from homeassistant.const import ( __version__, ) import homeassistant.core as ha -from homeassistant.auth.permissions.const import POLICY_READ -from homeassistant.exceptions import TemplateError, Unauthorized, ServiceNotFound +from homeassistant.exceptions import ServiceNotFound, TemplateError, Unauthorized from homeassistant.helpers import template +from homeassistant.helpers.json import JSONEncoder from homeassistant.helpers.service import async_get_all_descriptions from homeassistant.helpers.state import AsyncTrackStates -from homeassistant.helpers.json import JSONEncoder _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/apns/notify.py b/homeassistant/components/apns/notify.py index ce761b502ac..990598508af 100644 --- a/homeassistant/components/apns/notify.py +++ b/homeassistant/components/apns/notify.py @@ -6,6 +6,7 @@ from apns2.errors import Unregistered from apns2.payload import Payload import voluptuous as vol +from homeassistant.components.device_tracker import DOMAIN as DEVICE_TRACKER_DOMAIN from homeassistant.components.notify import ( ATTR_DATA, ATTR_TARGET, @@ -17,7 +18,6 @@ from homeassistant.const import ATTR_NAME, CONF_NAME, CONF_PLATFORM from homeassistant.helpers import template as template_helper import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import track_state_change -from homeassistant.components.device_tracker import DOMAIN as DEVICE_TRACKER_DOMAIN from .const import DOMAIN diff --git a/homeassistant/components/apple_tv/__init__.py b/homeassistant/components/apple_tv/__init__.py index 38d520f73da..e11b246fd5e 100644 --- a/homeassistant/components/apple_tv/__init__.py +++ b/homeassistant/components/apple_tv/__init__.py @@ -7,11 +7,11 @@ from pyatv import AppleTVDevice, connect_to_apple_tv, scan_for_apple_tvs from pyatv.exceptions import DeviceAuthenticationError import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.discovery import SERVICE_APPLE_TV from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST, CONF_NAME from homeassistant.helpers import discovery from homeassistant.helpers.aiohttp_client import async_get_clientsession +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/apprise/notify.py b/homeassistant/components/apprise/notify.py index 662cc9c1ab6..0c8c5b26eec 100644 --- a/homeassistant/components/apprise/notify.py +++ b/homeassistant/components/apprise/notify.py @@ -1,11 +1,8 @@ """Apprise platform for notify component.""" import logging -import voluptuous as vol - import apprise - -import homeassistant.helpers.config_validation as cv +import voluptuous as vol from homeassistant.components.notify import ( ATTR_TARGET, @@ -14,6 +11,7 @@ from homeassistant.components.notify import ( PLATFORM_SCHEMA, BaseNotificationService, ) +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/aprs/device_tracker.py b/homeassistant/components/aprs/device_tracker.py index 0d23cedb4ee..6258b470ebb 100644 --- a/homeassistant/components/aprs/device_tracker.py +++ b/homeassistant/components/aprs/device_tracker.py @@ -3,11 +3,9 @@ import logging import threading -import geopy.distance import aprslib -from aprslib import ConnectionError as AprsConnectionError -from aprslib import LoginError - +from aprslib import ConnectionError as AprsConnectionError, LoginError +import geopy.distance import voluptuous as vol from homeassistant.components.device_tracker import PLATFORM_SCHEMA diff --git a/homeassistant/components/aquostv/media_player.py b/homeassistant/components/aquostv/media_player.py index d8770592c9f..f71f41dc293 100644 --- a/homeassistant/components/aquostv/media_player.py +++ b/homeassistant/components/aquostv/media_player.py @@ -2,10 +2,9 @@ import logging import sharp_aquos_rc - import voluptuous as vol -from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice from homeassistant.components.media_player.const import ( SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, diff --git a/homeassistant/components/arcam_fmj/__init__.py b/homeassistant/components/arcam_fmj/__init__.py index bdb3bf67bbe..d818414753f 100644 --- a/homeassistant/components/arcam_fmj/__init__.py +++ b/homeassistant/components/arcam_fmj/__init__.py @@ -1,31 +1,32 @@ """Arcam component.""" -import logging import asyncio +import logging -import voluptuous as vol -import async_timeout -from arcam.fmj.client import Client from arcam.fmj import ConnectionFailed +from arcam.fmj.client import Client +import async_timeout +import voluptuous as vol from homeassistant import config_entries -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.typing import HomeAssistantType, ConfigType from homeassistant.const import ( - EVENT_HOMEASSISTANT_STOP, CONF_HOST, CONF_NAME, CONF_PORT, CONF_SCAN_INTERVAL, CONF_ZONE, + EVENT_HOMEASSISTANT_STOP, SERVICE_TURN_ON, ) +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.typing import ConfigType, HomeAssistantType + from .const import ( - DOMAIN, - DOMAIN_DATA_ENTRIES, - DOMAIN_DATA_CONFIG, DEFAULT_NAME, DEFAULT_PORT, DEFAULT_SCAN_INTERVAL, + DOMAIN, + DOMAIN_DATA_CONFIG, + DOMAIN_DATA_ENTRIES, SIGNAL_CLIENT_DATA, SIGNAL_CLIENT_STARTED, SIGNAL_CLIENT_STOPPED, diff --git a/homeassistant/components/arcam_fmj/media_player.py b/homeassistant/components/arcam_fmj/media_player.py index 231e9821dc6..8a54c745695 100644 --- a/homeassistant/components/arcam_fmj/media_player.py +++ b/homeassistant/components/arcam_fmj/media_player.py @@ -6,14 +6,13 @@ from arcam.fmj import DecodeMode2CH, DecodeModeMCH, IncomingAudioFormat, SourceC from arcam.fmj.state import State from homeassistant import config_entries -from homeassistant.core import callback from homeassistant.components.media_player import MediaPlayerDevice from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, SUPPORT_SELECT_SOUND_MODE, SUPPORT_SELECT_SOURCE, - SUPPORT_TURN_ON, SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP, @@ -25,15 +24,16 @@ from homeassistant.const import ( STATE_OFF, STATE_ON, ) -from homeassistant.helpers.typing import HomeAssistantType, ConfigType +from homeassistant.core import callback from homeassistant.helpers.service import async_call_from_config +from homeassistant.helpers.typing import ConfigType, HomeAssistantType from .const import ( + DOMAIN, + DOMAIN_DATA_ENTRIES, SIGNAL_CLIENT_DATA, SIGNAL_CLIENT_STARTED, SIGNAL_CLIENT_STOPPED, - DOMAIN_DATA_ENTRIES, - DOMAIN, ) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/arwn/sensor.py b/homeassistant/components/arwn/sensor.py index 23cd811a3e0..685e5d90f53 100644 --- a/homeassistant/components/arwn/sensor.py +++ b/homeassistant/components/arwn/sensor.py @@ -3,8 +3,8 @@ import json import logging from homeassistant.components import mqtt +from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.core import callback -from homeassistant.const import TEMP_FAHRENHEIT, TEMP_CELSIUS from homeassistant.helpers.entity import Entity from homeassistant.util import slugify diff --git a/homeassistant/components/asterisk_cdr/mailbox.py b/homeassistant/components/asterisk_cdr/mailbox.py index 4146ca9ddf9..0bae6ebf3ad 100644 --- a/homeassistant/components/asterisk_cdr/mailbox.py +++ b/homeassistant/components/asterisk_cdr/mailbox.py @@ -1,12 +1,14 @@ """Support for the Asterisk CDR interface.""" -import logging -import hashlib import datetime +import hashlib +import logging -from homeassistant.core import callback -from homeassistant.components.asterisk_mbox import SIGNAL_CDR_UPDATE -from homeassistant.components.asterisk_mbox import DOMAIN as ASTERISK_DOMAIN +from homeassistant.components.asterisk_mbox import ( + DOMAIN as ASTERISK_DOMAIN, + SIGNAL_CDR_UPDATE, +) from homeassistant.components.mailbox import Mailbox +from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/atome/sensor.py b/homeassistant/components/atome/sensor.py index c98b634bb21..f9dd6b2dd61 100644 --- a/homeassistant/components/atome/sensor.py +++ b/homeassistant/components/atome/sensor.py @@ -1,23 +1,22 @@ """Linky Atome.""" -import logging from datetime import timedelta +import logging +from pyatome.client import AtomeClient, PyAtomeError import voluptuous as vol -from pyatome.client import AtomeClient -from pyatome.client import PyAtomeError +from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( + CONF_NAME, CONF_PASSWORD, CONF_USERNAME, - CONF_NAME, DEVICE_CLASS_POWER, - POWER_WATT, ENERGY_KILO_WATT_HOUR, + POWER_WATT, ) -from homeassistant.components.sensor import PLATFORM_SCHEMA +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle -import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/aurora/binary_sensor.py b/homeassistant/components/aurora/binary_sensor.py index a69433c4186..d76884d2895 100644 --- a/homeassistant/components/aurora/binary_sensor.py +++ b/homeassistant/components/aurora/binary_sensor.py @@ -7,7 +7,7 @@ import requests import voluptuous as vol from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice -from homeassistant.const import CONF_NAME, ATTR_ATTRIBUTION +from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle diff --git a/homeassistant/components/aurora_abb_powerone/sensor.py b/homeassistant/components/aurora_abb_powerone/sensor.py index 05ed5fa99bf..a2645e5d7cb 100644 --- a/homeassistant/components/aurora_abb_powerone/sensor.py +++ b/homeassistant/components/aurora_abb_powerone/sensor.py @@ -2,8 +2,8 @@ import logging +from aurorapy.client import AuroraError, AuroraSerialClient import voluptuous as vol -from aurorapy.client import AuroraSerialClient, AuroraError from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( diff --git a/homeassistant/components/automatic/device_tracker.py b/homeassistant/components/automatic/device_tracker.py index fbb823dd329..bb403687963 100644 --- a/homeassistant/components/automatic/device_tracker.py +++ b/homeassistant/components/automatic/device_tracker.py @@ -5,9 +5,8 @@ import json import logging import os -from aiohttp import web import aioautomatic - +from aiohttp import web import voluptuous as vol from homeassistant.components.device_tracker import ( diff --git a/homeassistant/components/avea/light.py b/homeassistant/components/avea/light.py index e6ceedcf96d..92d66a554da 100644 --- a/homeassistant/components/avea/light.py +++ b/homeassistant/components/avea/light.py @@ -1,5 +1,6 @@ """Support for the Elgato Avea lights.""" import logging + import avea from homeassistant.components.light import ( @@ -12,7 +13,6 @@ from homeassistant.components.light import ( from homeassistant.exceptions import PlatformNotReady import homeassistant.util.color as color_util - _LOGGER = logging.getLogger(__name__) SUPPORT_AVEA = SUPPORT_BRIGHTNESS | SUPPORT_COLOR diff --git a/homeassistant/components/azure_event_hub/__init__.py b/homeassistant/components/azure_event_hub/__init__.py index 371b8d1ea8d..7e141cd8060 100644 --- a/homeassistant/components/azure_event_hub/__init__.py +++ b/homeassistant/components/azure_event_hub/__init__.py @@ -3,8 +3,8 @@ import json import logging from typing import Any, Dict -import voluptuous as vol from azure.eventhub import EventData, EventHubClientAsync +import voluptuous as vol from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP, diff --git a/tests/components/alert/test_init.py b/tests/components/alert/test_init.py index bcdb2058a42..55a3112c32f 100644 --- a/tests/components/alert/test_init.py +++ b/tests/components/alert/test_init.py @@ -1,26 +1,26 @@ """The tests for the Alert component.""" -import unittest - # pylint: disable=protected-access from copy import deepcopy +import unittest import homeassistant.components.alert as alert -import homeassistant.components.notify as notify from homeassistant.components.alert import DOMAIN +import homeassistant.components.notify as notify from homeassistant.const import ( ATTR_ENTITY_ID, CONF_ENTITY_ID, - STATE_IDLE, CONF_NAME, CONF_STATE, SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON, - STATE_ON, + STATE_IDLE, STATE_OFF, + STATE_ON, ) from homeassistant.core import callback from homeassistant.setup import setup_component + from tests.common import get_test_home_assistant NAME = "alert_test" diff --git a/tests/components/ambient_station/test_config_flow.py b/tests/components/ambient_station/test_config_flow.py index c94a51be94e..25e46090009 100644 --- a/tests/components/ambient_station/test_config_flow.py +++ b/tests/components/ambient_station/test_config_flow.py @@ -1,15 +1,15 @@ """Define tests for the Ambient PWS config flow.""" import json +from unittest.mock import patch import aioambient import pytest -from unittest.mock import patch from homeassistant import data_entry_flow from homeassistant.components.ambient_station import CONF_APP_KEY, DOMAIN, config_flow from homeassistant.const import CONF_API_KEY -from tests.common import load_fixture, MockConfigEntry, mock_coro +from tests.common import MockConfigEntry, load_fixture, mock_coro @pytest.fixture diff --git a/tests/components/androidtv/test_media_player.py b/tests/components/androidtv/test_media_player.py index 04b0bebf447..d7a6a8c1ce6 100644 --- a/tests/components/androidtv/test_media_player.py +++ b/tests/components/androidtv/test_media_player.py @@ -1,7 +1,6 @@ """The tests for the androidtv platform.""" import logging -from homeassistant.setup import async_setup_component from homeassistant.components.androidtv.media_player import ( ANDROIDTV_DOMAIN, CONF_ADB_SERVER_IP, @@ -24,10 +23,10 @@ from homeassistant.const import ( STATE_PLAYING, STATE_UNAVAILABLE, ) +from homeassistant.setup import async_setup_component from . import patchers - # Android TV device with Python ADB implementation CONFIG_ANDROIDTV_PYTHON_ADB = { DOMAIN: { diff --git a/tests/components/apns/test_notify.py b/tests/components/apns/test_notify.py index 19d869ea678..61092899e24 100644 --- a/tests/components/apns/test_notify.py +++ b/tests/components/apns/test_notify.py @@ -1,15 +1,15 @@ """The tests for the APNS component.""" import io import unittest -from unittest.mock import Mock, patch, mock_open +from unittest.mock import Mock, mock_open, patch from apns2.errors import Unregistered import yaml -import homeassistant.components.notify as notify -from homeassistant.setup import setup_component import homeassistant.components.apns.notify as apns +import homeassistant.components.notify as notify from homeassistant.core import State +from homeassistant.setup import setup_component from tests.common import assert_setup_component, get_test_home_assistant diff --git a/tests/components/apprise/test_notify.py b/tests/components/apprise/test_notify.py index 237f99de676..a275e57653d 100644 --- a/tests/components/apprise/test_notify.py +++ b/tests/components/apprise/test_notify.py @@ -1,6 +1,5 @@ """The tests for the apprise notification platform.""" -from unittest.mock import patch -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch from homeassistant.setup import async_setup_component diff --git a/tests/components/arlo/test_sensor.py b/tests/components/arlo/test_sensor.py index d06b48ac3a2..ac64ad8f272 100644 --- a/tests/components/arlo/test_sensor.py +++ b/tests/components/arlo/test_sensor.py @@ -1,14 +1,15 @@ """The tests for the Netgear Arlo sensors.""" from collections import namedtuple -from unittest.mock import patch, MagicMock +from unittest.mock import MagicMock, patch + import pytest + +from homeassistant.components.arlo import DATA_ARLO, sensor as arlo from homeassistant.const import ( - DEVICE_CLASS_TEMPERATURE, - DEVICE_CLASS_HUMIDITY, ATTR_ATTRIBUTION, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, ) -from homeassistant.components.arlo import sensor as arlo -from homeassistant.components.arlo import DATA_ARLO def _get_named_tuple(input_dict): diff --git a/tests/components/asuswrt/test_device_tracker.py b/tests/components/asuswrt/test_device_tracker.py index de999362f51..2ecab9c1d37 100644 --- a/tests/components/asuswrt/test_device_tracker.py +++ b/tests/components/asuswrt/test_device_tracker.py @@ -1,15 +1,15 @@ """The tests for the ASUSWRT device tracker platform.""" from unittest.mock import patch -from homeassistant.setup import async_setup_component from homeassistant.components.asuswrt import ( - CONF_PROTOCOL, CONF_MODE, - DOMAIN, CONF_PORT, + CONF_PROTOCOL, DATA_ASUSWRT, + DOMAIN, ) -from homeassistant.const import CONF_PLATFORM, CONF_PASSWORD, CONF_USERNAME, CONF_HOST +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PLATFORM, CONF_USERNAME +from homeassistant.setup import async_setup_component from tests.common import mock_coro_func diff --git a/tests/components/aurora/test_binary_sensor.py b/tests/components/aurora/test_binary_sensor.py index 72cef0b44ca..1683e1951a0 100644 --- a/tests/components/aurora/test_binary_sensor.py +++ b/tests/components/aurora/test_binary_sensor.py @@ -5,7 +5,8 @@ import unittest import requests_mock from homeassistant.components.aurora import binary_sensor as aurora -from tests.common import load_fixture, get_test_home_assistant + +from tests.common import get_test_home_assistant, load_fixture class TestAuroraSensorSetUp(unittest.TestCase): diff --git a/tests/components/automatic/test_device_tracker.py b/tests/components/automatic/test_device_tracker.py index 4186316e7ac..3611a5ae0e3 100644 --- a/tests/components/automatic/test_device_tracker.py +++ b/tests/components/automatic/test_device_tracker.py @@ -2,11 +2,12 @@ import asyncio from datetime import datetime import logging -from unittest.mock import patch, MagicMock +from unittest.mock import MagicMock, patch + import aioautomatic -from homeassistant.setup import async_setup_component from homeassistant.components.automatic.device_tracker import async_setup_scanner +from homeassistant.setup import async_setup_component _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/awair/test_sensor.py b/tests/components/awair/test_sensor.py index ee4e4906826..ded1520718f 100644 --- a/tests/components/awair/test_sensor.py +++ b/tests/components/awair/test_sensor.py @@ -6,7 +6,6 @@ import json import logging from unittest.mock import patch -from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.awair.sensor import ( ATTR_LAST_API_UPDATE, ATTR_TIMESTAMP, @@ -15,6 +14,7 @@ from homeassistant.components.awair.sensor import ( DEVICE_CLASS_SCORE, DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, ) +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.const import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, From f9e06ca2fd819f76965028c56bec99374119e252 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 14:10:04 +0100 Subject: [PATCH 206/677] Sort imports according to PEP8 for components starting with "E" (#29765) --- .../components/eddystone_temperature/sensor.py | 6 +----- homeassistant/components/efergy/sensor.py | 2 +- .../components/elkm1/alarm_control_panel.py | 6 +++--- homeassistant/components/elv/__init__.py | 2 +- homeassistant/components/elv/switch.py | 2 +- homeassistant/components/emoncms/sensor.py | 14 +++++++------- .../components/emoncms_history/__init__.py | 12 ++++++------ homeassistant/components/envirophat/sensor.py | 4 ++-- homeassistant/components/epson/media_player.py | 12 ++++++------ tests/components/efergy/test_sensor.py | 2 +- 10 files changed, 29 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/eddystone_temperature/sensor.py b/homeassistant/components/eddystone_temperature/sensor.py index 2084e307029..22d3533d32f 100644 --- a/homeassistant/components/eddystone_temperature/sensor.py +++ b/homeassistant/components/eddystone_temperature/sensor.py @@ -10,11 +10,7 @@ https://home-assistant.io/components/sensor.eddystone_temperature/ import logging # pylint: disable=import-error -from beacontools import ( - BeaconScanner, - EddystoneFilter, - EddystoneTLMFrame, -) +from beacontools import BeaconScanner, EddystoneFilter, EddystoneTLMFrame import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA diff --git a/homeassistant/components/efergy/sensor.py b/homeassistant/components/efergy/sensor.py index 43c3b67457a..3be962fea2f 100644 --- a/homeassistant/components/efergy/sensor.py +++ b/homeassistant/components/efergy/sensor.py @@ -5,7 +5,7 @@ import requests import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONF_CURRENCY, POWER_WATT, ENERGY_KILO_WATT_HOUR +from homeassistant.const import CONF_CURRENCY, ENERGY_KILO_WATT_HOUR, POWER_WATT import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity diff --git a/homeassistant/components/elkm1/alarm_control_panel.py b/homeassistant/components/elkm1/alarm_control_panel.py index 1e1a8eba9e0..de1cb62234c 100644 --- a/homeassistant/components/elkm1/alarm_control_panel.py +++ b/homeassistant/components/elkm1/alarm_control_panel.py @@ -3,8 +3,8 @@ from elkm1_lib.const import AlarmState, ArmedStatus, ArmLevel, ArmUpState import voluptuous as vol from homeassistant.components.alarm_control_panel import ( - AlarmControlPanel, FORMAT_NUMBER, + AlarmControlPanel, ) from homeassistant.components.alarm_control_panel.const import ( SUPPORT_ALARM_ARM_AWAY, @@ -29,13 +29,13 @@ from homeassistant.helpers.dispatcher import ( ) from . import ( - create_elk_entities, DOMAIN, - ElkEntity, SERVICE_ALARM_ARM_HOME_INSTANT, SERVICE_ALARM_ARM_NIGHT_INSTANT, SERVICE_ALARM_ARM_VACATION, SERVICE_ALARM_DISPLAY_MESSAGE, + ElkEntity, + create_elk_entities, ) SIGNAL_ARM_ENTITY = "elkm1_arm" diff --git a/homeassistant/components/elv/__init__.py b/homeassistant/components/elv/__init__.py index b6097737414..b776c7f5453 100644 --- a/homeassistant/components/elv/__init__.py +++ b/homeassistant/components/elv/__init__.py @@ -4,8 +4,8 @@ import logging import voluptuous as vol -from homeassistant.helpers import discovery from homeassistant.const import CONF_DEVICE +from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/elv/switch.py b/homeassistant/components/elv/switch.py index 362424c7fac..a77d21cf173 100644 --- a/homeassistant/components/elv/switch.py +++ b/homeassistant/components/elv/switch.py @@ -4,7 +4,7 @@ import logging import pypca from serial import SerialException -from homeassistant.components.switch import SwitchDevice, ATTR_CURRENT_POWER_W +from homeassistant.components.switch import ATTR_CURRENT_POWER_W, SwitchDevice from homeassistant.const import EVENT_HOMEASSISTANT_STOP _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/emoncms/sensor.py b/homeassistant/components/emoncms/sensor.py index 5f9d31697b8..34063e4c253 100644 --- a/homeassistant/components/emoncms/sensor.py +++ b/homeassistant/components/emoncms/sensor.py @@ -2,23 +2,23 @@ from datetime import timedelta import logging -import voluptuous as vol import requests +import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( CONF_API_KEY, - CONF_URL, - CONF_VALUE_TEMPLATE, - CONF_UNIT_OF_MEASUREMENT, CONF_ID, CONF_SCAN_INTERVAL, - STATE_UNKNOWN, + CONF_UNIT_OF_MEASUREMENT, + CONF_URL, + CONF_VALUE_TEMPLATE, POWER_WATT, + STATE_UNKNOWN, ) -from homeassistant.helpers.entity import Entity from homeassistant.helpers import template +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/emoncms_history/__init__.py b/homeassistant/components/emoncms_history/__init__.py index 3b30a29960b..fd38da1cac1 100644 --- a/homeassistant/components/emoncms_history/__init__.py +++ b/homeassistant/components/emoncms_history/__init__.py @@ -1,20 +1,20 @@ """Support for sending data to Emoncms.""" -import logging from datetime import timedelta +import logging import requests import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.const import ( CONF_API_KEY, - CONF_WHITELIST, - CONF_URL, - STATE_UNKNOWN, - STATE_UNAVAILABLE, CONF_SCAN_INTERVAL, + CONF_URL, + CONF_WHITELIST, + STATE_UNAVAILABLE, + STATE_UNKNOWN, ) from homeassistant.helpers import state as state_helper +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import track_point_in_time from homeassistant.util import dt as dt_util diff --git a/homeassistant/components/envirophat/sensor.py b/homeassistant/components/envirophat/sensor.py index 2aaeefa48cf..ce1f154f911 100644 --- a/homeassistant/components/envirophat/sensor.py +++ b/homeassistant/components/envirophat/sensor.py @@ -1,12 +1,12 @@ """Support for Enviro pHAT sensors.""" +from datetime import timedelta import importlib import logging -from datetime import timedelta import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import TEMP_CELSIUS, CONF_DISPLAY_OPTIONS, CONF_NAME +from homeassistant.const import CONF_DISPLAY_OPTIONS, CONF_NAME, TEMP_CELSIUS import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle diff --git a/homeassistant/components/epson/media_player.py b/homeassistant/components/epson/media_player.py index f3428602fad..b39722c39f3 100644 --- a/homeassistant/components/epson/media_player.py +++ b/homeassistant/components/epson/media_player.py @@ -1,8 +1,7 @@ """Support for Epson projector.""" import logging -import voluptuous as vol - +import epson_projector as epson from epson_projector.const import ( BACK, BUSY, @@ -19,15 +18,15 @@ from epson_projector.const import ( POWER, SOURCE, SOURCE_LIST, - TURN_ON, TURN_OFF, - VOLUME, + TURN_ON, VOL_DOWN, VOL_UP, + VOLUME, ) -import epson_projector as epson +import voluptuous as vol -from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice from homeassistant.components.media_player.const import ( SUPPORT_NEXT_TRACK, SUPPORT_PREVIOUS_TRACK, @@ -48,6 +47,7 @@ from homeassistant.const import ( ) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv + from .const import ( ATTR_CMODE, DATA_EPSON, diff --git a/tests/components/efergy/test_sensor.py b/tests/components/efergy/test_sensor.py index cbba49d0ed0..18a00005dd5 100644 --- a/tests/components/efergy/test_sensor.py +++ b/tests/components/efergy/test_sensor.py @@ -5,7 +5,7 @@ import requests_mock from homeassistant.setup import setup_component -from tests.common import load_fixture, get_test_home_assistant +from tests.common import get_test_home_assistant, load_fixture token = "9p6QGJ7dpZfO3fqPTBk1fyEmjV1cGoLT" multi_sensor_token = "9r6QGF7dpZfO3fqPTBl1fyRmjV1cGoLT" From ca0fad2cbb0544125d87d21ffe308cf3addcde5a Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 14:14:40 +0100 Subject: [PATCH 207/677] Sort imports according to PEP8 for components starting with "F" (#29766) --- homeassistant/components/facebook/notify.py | 5 ++-- .../components/facebox/image_processing.py | 15 ++++++------ homeassistant/components/fail2ban/sensor.py | 11 ++++----- .../components/feedreader/__init__.py | 8 +++---- .../components/ffmpeg_motion/binary_sensor.py | 12 +++++----- .../components/ffmpeg_noise/binary_sensor.py | 10 ++++---- homeassistant/components/filesize/sensor.py | 4 ++-- homeassistant/components/filter/sensor.py | 24 +++++++++---------- homeassistant/components/fitbit/sensor.py | 12 ++++------ .../components/flic/binary_sensor.py | 8 +++---- homeassistant/components/flock/notify.py | 3 +-- homeassistant/components/flux/switch.py | 12 +++++----- homeassistant/components/flux_led/light.py | 22 ++++++++--------- homeassistant/components/folder/sensor.py | 4 ++-- .../components/fortios/device_tracker.py | 7 +++--- homeassistant/components/foscam/camera.py | 18 +++++++------- .../components/foursquare/__init__.py | 4 ++-- homeassistant/components/freedns/__init__.py | 4 ++-- homeassistant/components/fritzbox/climate.py | 6 ++--- homeassistant/components/fronius/sensor.py | 7 +++--- tests/components/facebook/test_notify.py | 1 + .../facebox/test_image_processing.py | 10 ++++---- tests/components/fail2ban/test_sensor.py | 10 ++++---- tests/components/feedreader/test_init.py | 19 ++++++++------- tests/components/fido/test_sensor.py | 2 +- tests/components/filesize/test_sensor.py | 4 ++-- tests/components/filter/test_sensor.py | 9 +++---- tests/components/flux/test_switch.py | 6 ++--- tests/components/folder/test_sensor.py | 4 ++-- tests/components/folder_watcher/test_init.py | 3 ++- tests/components/foobot/test_sensor.py | 7 +++--- tests/components/freedns/test_init.py | 3 ++- tests/components/frontend/test_init.py | 11 ++++----- tests/components/frontend/test_storage.py | 2 +- 34 files changed, 143 insertions(+), 144 deletions(-) diff --git a/homeassistant/components/facebook/notify.py b/homeassistant/components/facebook/notify.py index 452b81c0f16..b75f2628033 100644 --- a/homeassistant/components/facebook/notify.py +++ b/homeassistant/components/facebook/notify.py @@ -6,15 +6,14 @@ from aiohttp.hdrs import CONTENT_TYPE import requests import voluptuous as vol -from homeassistant.const import CONTENT_TYPE_JSON -import homeassistant.helpers.config_validation as cv - from homeassistant.components.notify import ( ATTR_DATA, ATTR_TARGET, PLATFORM_SCHEMA, BaseNotificationService, ) +from homeassistant.const import CONTENT_TYPE_JSON +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/facebox/image_processing.py b/homeassistant/components/facebox/image_processing.py index ba53ac1ac7d..ee6e4d8a6fa 100644 --- a/homeassistant/components/facebox/image_processing.py +++ b/homeassistant/components/facebox/image_processing.py @@ -5,26 +5,27 @@ import logging import requests import voluptuous as vol -from homeassistant.const import ATTR_ENTITY_ID, ATTR_NAME -from homeassistant.core import split_entity_id -import homeassistant.helpers.config_validation as cv from homeassistant.components.image_processing import ( - PLATFORM_SCHEMA, - ImageProcessingFaceEntity, ATTR_CONFIDENCE, - CONF_SOURCE, CONF_ENTITY_ID, CONF_NAME, + CONF_SOURCE, + PLATFORM_SCHEMA, + ImageProcessingFaceEntity, ) from homeassistant.const import ( + ATTR_ENTITY_ID, + ATTR_NAME, CONF_IP_ADDRESS, - CONF_PORT, CONF_PASSWORD, + CONF_PORT, CONF_USERNAME, HTTP_BAD_REQUEST, HTTP_OK, HTTP_UNAUTHORIZED, ) +from homeassistant.core import split_entity_id +import homeassistant.helpers.config_validation as cv from .const import DOMAIN, SERVICE_TEACH_FACE diff --git a/homeassistant/components/fail2ban/sensor.py b/homeassistant/components/fail2ban/sensor.py index 2dc528b2cff..692b48d9db5 100644 --- a/homeassistant/components/fail2ban/sensor.py +++ b/homeassistant/components/fail2ban/sensor.py @@ -5,17 +5,16 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.fail2ban/ """ -import os -import logging - from datetime import timedelta - +import logging +import os import re + import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONF_NAME, CONF_FILE_PATH +from homeassistant.const import CONF_FILE_PATH, CONF_NAME +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/feedreader/__init__.py b/homeassistant/components/feedreader/__init__.py index 27b164e4edf..bf1e55370bc 100644 --- a/homeassistant/components/feedreader/__init__.py +++ b/homeassistant/components/feedreader/__init__.py @@ -2,15 +2,15 @@ from datetime import datetime, timedelta from logging import getLogger from os.path import exists -from threading import Lock import pickle +from threading import Lock -import voluptuous as vol import feedparser +import voluptuous as vol -from homeassistant.const import EVENT_HOMEASSISTANT_START, CONF_SCAN_INTERVAL -from homeassistant.helpers.event import track_time_interval +from homeassistant.const import CONF_SCAN_INTERVAL, EVENT_HOMEASSISTANT_START import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.event import track_time_interval _LOGGER = getLogger(__name__) diff --git a/homeassistant/components/ffmpeg_motion/binary_sensor.py b/homeassistant/components/ffmpeg_motion/binary_sensor.py index 54f3981f48a..294fcc2518f 100644 --- a/homeassistant/components/ffmpeg_motion/binary_sensor.py +++ b/homeassistant/components/ffmpeg_motion/binary_sensor.py @@ -4,17 +4,17 @@ import logging import haffmpeg.sensor as ffmpeg_sensor import voluptuous as vol -from homeassistant.core import callback -import homeassistant.helpers.config_validation as cv -from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice from homeassistant.components.ffmpeg import ( - FFmpegBase, - DATA_FFMPEG, - CONF_INPUT, CONF_EXTRA_ARGUMENTS, CONF_INITIAL_STATE, + CONF_INPUT, + DATA_FFMPEG, + FFmpegBase, ) from homeassistant.const import CONF_NAME +from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/ffmpeg_noise/binary_sensor.py b/homeassistant/components/ffmpeg_noise/binary_sensor.py index 7c5f8656410..6ada2bb2748 100644 --- a/homeassistant/components/ffmpeg_noise/binary_sensor.py +++ b/homeassistant/components/ffmpeg_noise/binary_sensor.py @@ -4,17 +4,17 @@ import logging import haffmpeg.sensor as ffmpeg_sensor import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.binary_sensor import PLATFORM_SCHEMA -from homeassistant.components.ffmpeg_motion.binary_sensor import FFmpegBinarySensor from homeassistant.components.ffmpeg import ( - DATA_FFMPEG, - CONF_INPUT, - CONF_OUTPUT, CONF_EXTRA_ARGUMENTS, CONF_INITIAL_STATE, + CONF_INPUT, + CONF_OUTPUT, + DATA_FFMPEG, ) +from homeassistant.components.ffmpeg_motion.binary_sensor import FFmpegBinarySensor from homeassistant.const import CONF_NAME +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/filesize/sensor.py b/homeassistant/components/filesize/sensor.py index af9375aad05..8c6cd30b118 100644 --- a/homeassistant/components/filesize/sensor.py +++ b/homeassistant/components/filesize/sensor.py @@ -5,9 +5,9 @@ import os import voluptuous as vol -from homeassistant.helpers.entity import Entity -import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/filter/sensor.py b/homeassistant/components/filter/sensor.py index 81c4623c53f..eeb0d32f51c 100644 --- a/homeassistant/components/filter/sensor.py +++ b/homeassistant/components/filter/sensor.py @@ -1,31 +1,31 @@ """Allows the creation of a sensor that filters state property.""" -import logging -import statistics -from collections import deque, Counter -from numbers import Number -from functools import partial +from collections import Counter, deque from copy import copy from datetime import timedelta +from functools import partial +import logging +from numbers import Number +import statistics from typing import Optional import voluptuous as vol -from homeassistant.core import callback +from homeassistant.components import history from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_NAME, - CONF_ENTITY_ID, - ATTR_UNIT_OF_MEASUREMENT, ATTR_ENTITY_ID, ATTR_ICON, - STATE_UNKNOWN, + ATTR_UNIT_OF_MEASUREMENT, + CONF_ENTITY_ID, + CONF_NAME, STATE_UNAVAILABLE, + STATE_UNKNOWN, ) +from homeassistant.core import callback 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 -from homeassistant.components import history +from homeassistant.util.decorator import Registry import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/fitbit/sensor.py b/homeassistant/components/fitbit/sensor.py index 0d4b8d61e7a..5ddb63ef899 100644 --- a/homeassistant/components/fitbit/sensor.py +++ b/homeassistant/components/fitbit/sensor.py @@ -1,7 +1,7 @@ """Support for the Fitbit API.""" -import os -import logging import datetime +import logging +import os import time from fitbit import Fitbit @@ -9,17 +9,15 @@ from fitbit.api import FitbitOauth2Client from oauthlib.oauth2.rfc6749.errors import MismatchingStateError, MissingTokenError import voluptuous as vol -from homeassistant.core import callback from homeassistant.components.http import HomeAssistantView from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ATTR_ATTRIBUTION -from homeassistant.const import CONF_UNIT_SYSTEM +from homeassistant.const import ATTR_ATTRIBUTION, CONF_UNIT_SYSTEM +from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level -import homeassistant.helpers.config_validation as cv from homeassistant.util.json import load_json, save_json - _CONFIGURING = {} _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/flic/binary_sensor.py b/homeassistant/components/flic/binary_sensor.py index 416d39e5332..4f2f229977f 100644 --- a/homeassistant/components/flic/binary_sensor.py +++ b/homeassistant/components/flic/binary_sensor.py @@ -3,24 +3,24 @@ import logging import threading from pyflic import ( - FlicClient, ButtonConnectionChannel, ClickType, ConnectionStatus, + FlicClient, ScanWizard, ScanWizardResult, ) import voluptuous as vol -import homeassistant.helpers.config_validation as cv +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice from homeassistant.const import ( + CONF_DISCOVERY, CONF_HOST, CONF_PORT, - CONF_DISCOVERY, CONF_TIMEOUT, EVENT_HOMEASSISTANT_STOP, ) -from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/flock/notify.py b/homeassistant/components/flock/notify.py index ba52d3b4beb..a71601ea2c4 100644 --- a/homeassistant/components/flock/notify.py +++ b/homeassistant/components/flock/notify.py @@ -5,12 +5,11 @@ import logging import async_timeout import voluptuous as vol +from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationService from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationService - _LOGGER = logging.getLogger(__name__) _RESOURCE = "https://api.flock.com/hooks/sendMessage/" diff --git a/homeassistant/components/flux/switch.py b/homeassistant/components/flux/switch.py index 404067d4107..a02b1b2504b 100644 --- a/homeassistant/components/flux/switch.py +++ b/homeassistant/components/flux/switch.py @@ -11,9 +11,7 @@ import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.light import ( - is_on, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_RGB_COLOR, @@ -22,29 +20,31 @@ from homeassistant.components.light import ( ATTR_XY_COLOR, DOMAIN as LIGHT_DOMAIN, VALID_TRANSITION, + is_on, ) from homeassistant.components.switch import DOMAIN, SwitchDevice from homeassistant.const import ( ATTR_ENTITY_ID, - CONF_NAME, - CONF_PLATFORM, CONF_LIGHTS, CONF_MODE, + CONF_NAME, + CONF_PLATFORM, SERVICE_TURN_ON, STATE_ON, SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET, ) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.sun import get_astral_event_date from homeassistant.util import slugify from homeassistant.util.color import ( - color_temperature_to_rgb, color_RGB_to_xy_brightness, color_temperature_kelvin_to_mired, + color_temperature_to_rgb, ) -from homeassistant.util.dt import utcnow as dt_utcnow, as_local +from homeassistant.util.dt import as_local, utcnow as dt_utcnow _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/flux_led/light.py b/homeassistant/components/flux_led/light.py index 7973956848a..16db60abbc0 100644 --- a/homeassistant/components/flux_led/light.py +++ b/homeassistant/components/flux_led/light.py @@ -1,28 +1,28 @@ """Support for Flux lights.""" import logging -import socket import random +import socket from flux_led import BulbScanner, WifiLedBulb import voluptuous as vol -from homeassistant.const import CONF_DEVICES, CONF_NAME, CONF_PROTOCOL, ATTR_MODE from homeassistant.components.light import ( ATTR_BRIGHTNESS, - ATTR_HS_COLOR, - ATTR_EFFECT, - ATTR_WHITE_VALUE, ATTR_COLOR_TEMP, + ATTR_EFFECT, + ATTR_HS_COLOR, + ATTR_WHITE_VALUE, EFFECT_COLORLOOP, EFFECT_RANDOM, - SUPPORT_BRIGHTNESS, - SUPPORT_EFFECT, - SUPPORT_COLOR, - SUPPORT_WHITE_VALUE, - SUPPORT_COLOR_TEMP, - Light, PLATFORM_SCHEMA, + SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, + SUPPORT_COLOR_TEMP, + SUPPORT_EFFECT, + SUPPORT_WHITE_VALUE, + Light, ) +from homeassistant.const import ATTR_MODE, CONF_DEVICES, CONF_NAME, CONF_PROTOCOL import homeassistant.helpers.config_validation as cv import homeassistant.util.color as color_util diff --git a/homeassistant/components/folder/sensor.py b/homeassistant/components/folder/sensor.py index e9e4ea680c4..a706ab2a0b5 100644 --- a/homeassistant/components/folder/sensor.py +++ b/homeassistant/components/folder/sensor.py @@ -6,9 +6,9 @@ import os import voluptuous as vol -from homeassistant.helpers.entity import Entity -import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/fortios/device_tracker.py b/homeassistant/components/fortios/device_tracker.py index 51bce5429f6..2b2d14f60e0 100644 --- a/homeassistant/components/fortios/device_tracker.py +++ b/homeassistant/components/fortios/device_tracker.py @@ -5,17 +5,16 @@ This component is part of the device_tracker platform. """ import logging -import voluptuous as vol from fortiosapi import FortiOSAPI +import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.device_tracker import ( DOMAIN, PLATFORM_SCHEMA, DeviceScanner, ) -from homeassistant.const import CONF_HOST, CONF_TOKEN -from homeassistant.const import CONF_VERIFY_SSL +from homeassistant.const import CONF_HOST, CONF_TOKEN, CONF_VERIFY_SSL +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) DEFAULT_VERIFY_SSL = False diff --git a/homeassistant/components/foscam/camera.py b/homeassistant/components/foscam/camera.py index 0e2ca4073bf..f4ec6556894 100644 --- a/homeassistant/components/foscam/camera.py +++ b/homeassistant/components/foscam/camera.py @@ -1,26 +1,26 @@ """This component provides basic support for Foscam IP cameras.""" -import logging import asyncio +import logging from libpyfoscam import FoscamCamera - import voluptuous as vol -from homeassistant.components.camera import Camera, PLATFORM_SCHEMA, SUPPORT_STREAM +from homeassistant.components.camera import PLATFORM_SCHEMA, SUPPORT_STREAM, Camera from homeassistant.const import ( + ATTR_ENTITY_ID, CONF_NAME, - CONF_USERNAME, CONF_PASSWORD, CONF_PORT, - ATTR_ENTITY_ID, + CONF_USERNAME, ) from homeassistant.helpers import config_validation as cv from homeassistant.helpers.service import async_extract_entity_ids -from .const import DOMAIN as FOSCAM_DOMAIN -from .const import DATA as FOSCAM_DATA -from .const import ENTITIES as FOSCAM_ENTITIES - +from .const import ( + DATA as FOSCAM_DATA, + DOMAIN as FOSCAM_DOMAIN, + ENTITIES as FOSCAM_ENTITIES, +) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/foursquare/__init__.py b/homeassistant/components/foursquare/__init__.py index 7efb989e142..3f0578cf5b4 100644 --- a/homeassistant/components/foursquare/__init__.py +++ b/homeassistant/components/foursquare/__init__.py @@ -4,9 +4,9 @@ import logging import requests import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.const import CONF_ACCESS_TOKEN, HTTP_BAD_REQUEST from homeassistant.components.http import HomeAssistantView +from homeassistant.const import CONF_ACCESS_TOKEN, HTTP_BAD_REQUEST +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/freedns/__init__.py b/homeassistant/components/freedns/__init__.py index 30f80280c1f..7aa34c8780e 100644 --- a/homeassistant/components/freedns/__init__.py +++ b/homeassistant/components/freedns/__init__.py @@ -1,14 +1,14 @@ """Integrate with FreeDNS Dynamic DNS service at freedns.afraid.org.""" import asyncio -import logging from datetime import timedelta +import logging import aiohttp import async_timeout import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.const import CONF_ACCESS_TOKEN, CONF_SCAN_INTERVAL, CONF_URL +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/fritzbox/climate.py b/homeassistant/components/fritzbox/climate.py index 40c16930206..115f7f8e644 100644 --- a/homeassistant/components/fritzbox/climate.py +++ b/homeassistant/components/fritzbox/climate.py @@ -7,11 +7,11 @@ from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( ATTR_HVAC_MODE, HVAC_MODE_HEAT, - PRESET_ECO, - PRESET_COMFORT, - SUPPORT_TARGET_TEMPERATURE, HVAC_MODE_OFF, + PRESET_COMFORT, + PRESET_ECO, SUPPORT_PRESET_MODE, + SUPPORT_TARGET_TEMPERATURE, ) from homeassistant.const import ( ATTR_BATTERY_LEVEL, diff --git a/homeassistant/components/fronius/sensor.py b/homeassistant/components/fronius/sensor.py index ff0694afaab..27e2531c9f9 100644 --- a/homeassistant/components/fronius/sensor.py +++ b/homeassistant/components/fronius/sensor.py @@ -2,24 +2,23 @@ import copy from datetime import timedelta import logging -import voluptuous as vol from pyfronius import Fronius +import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_RESOURCE, - CONF_SENSOR_TYPE, CONF_DEVICE, CONF_MONITORED_CONDITIONS, + CONF_RESOURCE, CONF_SCAN_INTERVAL, + CONF_SENSOR_TYPE, ) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_time_interval - _LOGGER = logging.getLogger(__name__) CONF_SCOPE = "scope" diff --git a/tests/components/facebook/test_notify.py b/tests/components/facebook/test_notify.py index 5cd5f89ef1f..e23cc4f0982 100644 --- a/tests/components/facebook/test_notify.py +++ b/tests/components/facebook/test_notify.py @@ -1,5 +1,6 @@ """The test for the Facebook notify module.""" import unittest + import requests_mock # import homeassistant.components.facebook as facebook diff --git a/tests/components/facebox/test_image_processing.py b/tests/components/facebox/test_image_processing.py index 9b4610e84b0..d82f70e7ca1 100644 --- a/tests/components/facebox/test_image_processing.py +++ b/tests/components/facebox/test_image_processing.py @@ -5,23 +5,23 @@ import pytest import requests import requests_mock -from homeassistant.core import callback +import homeassistant.components.facebox.image_processing as fb +import homeassistant.components.image_processing as ip from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_NAME, CONF_FRIENDLY_NAME, - CONF_PASSWORD, - CONF_USERNAME, CONF_IP_ADDRESS, + CONF_PASSWORD, CONF_PORT, + CONF_USERNAME, HTTP_BAD_REQUEST, HTTP_OK, HTTP_UNAUTHORIZED, STATE_UNKNOWN, ) +from homeassistant.core import callback from homeassistant.setup import async_setup_component -import homeassistant.components.image_processing as ip -import homeassistant.components.facebox.image_processing as fb MOCK_IP = "192.168.0.1" MOCK_PORT = "8080" diff --git a/tests/components/fail2ban/test_sensor.py b/tests/components/fail2ban/test_sensor.py index 55195958169..796ddd93d26 100644 --- a/tests/components/fail2ban/test_sensor.py +++ b/tests/components/fail2ban/test_sensor.py @@ -4,15 +4,15 @@ from unittest.mock import Mock, patch from mock_open import MockOpen -from homeassistant.setup import setup_component from homeassistant.components.fail2ban.sensor import ( - BanSensor, - BanLogParser, - STATE_CURRENT_BANS, STATE_ALL_BANS, + STATE_CURRENT_BANS, + BanLogParser, + BanSensor, ) +from homeassistant.setup import setup_component -from tests.common import get_test_home_assistant, assert_setup_component +from tests.common import assert_setup_component, get_test_home_assistant def fake_log(log_key): diff --git a/tests/components/feedreader/test_init.py b/tests/components/feedreader/test_init.py index eff44c44303..d74bbd79d80 100644 --- a/tests/components/feedreader/test_init.py +++ b/tests/components/feedreader/test_init.py @@ -1,27 +1,28 @@ """The tests for the feedreader component.""" -import time from datetime import timedelta - -import unittest -from genericpath import exists from logging import getLogger from os import remove +import time +import unittest from unittest import mock from unittest.mock import patch +from genericpath import exists + from homeassistant.components import feedreader from homeassistant.components.feedreader import ( + CONF_MAX_ENTRIES, CONF_URLS, + DEFAULT_MAX_ENTRIES, + DEFAULT_SCAN_INTERVAL, + EVENT_FEEDREADER, FeedManager, StoredData, - EVENT_FEEDREADER, - DEFAULT_SCAN_INTERVAL, - CONF_MAX_ENTRIES, - DEFAULT_MAX_ENTRIES, ) -from homeassistant.const import EVENT_HOMEASSISTANT_START, CONF_SCAN_INTERVAL +from homeassistant.const import CONF_SCAN_INTERVAL, EVENT_HOMEASSISTANT_START from homeassistant.core import callback from homeassistant.setup import setup_component + from tests.common import get_test_home_assistant, load_fixture _LOGGER = getLogger(__name__) diff --git a/tests/components/fido/test_sensor.py b/tests/components/fido/test_sensor.py index 010896e086b..1f67a1e2e11 100644 --- a/tests/components/fido/test_sensor.py +++ b/tests/components/fido/test_sensor.py @@ -6,8 +6,8 @@ from unittest.mock import MagicMock, patch from homeassistant.bootstrap import async_setup_component from homeassistant.components.fido import sensor as fido -from tests.common import assert_setup_component +from tests.common import assert_setup_component CONTRACT = "123456789" diff --git a/tests/components/filesize/test_sensor.py b/tests/components/filesize/test_sensor.py index 2246a74abe5..29bd6a7fb1f 100644 --- a/tests/components/filesize/test_sensor.py +++ b/tests/components/filesize/test_sensor.py @@ -1,11 +1,11 @@ """The tests for the filesize sensor.""" -import unittest import os +import unittest from homeassistant.components.filesize.sensor import CONF_FILE_PATHS from homeassistant.setup import setup_component -from tests.common import get_test_home_assistant +from tests.common import get_test_home_assistant TEST_DIR = os.path.join(os.path.dirname(__file__)) TEST_FILE = os.path.join(TEST_DIR, "mock_file_test_filesize.txt") diff --git a/tests/components/filter/test_sensor.py b/tests/components/filter/test_sensor.py index e9c8f4c35e2..17e5bd8fd5d 100644 --- a/tests/components/filter/test_sensor.py +++ b/tests/components/filter/test_sensor.py @@ -6,17 +6,18 @@ from unittest.mock import patch from homeassistant.components.filter.sensor import ( LowPassFilter, OutlierFilter, + RangeFilter, ThrottleFilter, TimeSMAFilter, - RangeFilter, TimeThrottleFilter, ) -import homeassistant.util.dt as dt_util -from homeassistant.setup import setup_component import homeassistant.core as ha +from homeassistant.setup import setup_component +import homeassistant.util.dt as dt_util + from tests.common import ( - get_test_home_assistant, assert_setup_component, + get_test_home_assistant, init_recorder_component, ) diff --git a/tests/components/flux/test_switch.py b/tests/components/flux/test_switch.py index 91871666f46..b3d0a008961 100644 --- a/tests/components/flux/test_switch.py +++ b/tests/components/flux/test_switch.py @@ -2,15 +2,15 @@ from asynctest.mock import patch import pytest -from homeassistant.setup import async_setup_component -from homeassistant.components import switch, light +from homeassistant.components import light, switch from homeassistant.const import ( CONF_PLATFORM, - STATE_ON, SERVICE_TURN_ON, + STATE_ON, SUN_EVENT_SUNRISE, ) from homeassistant.core import State +from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from tests.common import ( diff --git a/tests/components/folder/test_sensor.py b/tests/components/folder/test_sensor.py index 97c933fdd23..fc7de1f59c0 100644 --- a/tests/components/folder/test_sensor.py +++ b/tests/components/folder/test_sensor.py @@ -1,11 +1,11 @@ """The tests for the folder sensor.""" -import unittest import os +import unittest from homeassistant.components.folder.sensor import CONF_FOLDER_PATHS from homeassistant.setup import setup_component -from tests.common import get_test_home_assistant +from tests.common import get_test_home_assistant CWD = os.path.join(os.path.dirname(__file__)) TEST_FOLDER = "test_folder" diff --git a/tests/components/folder_watcher/test_init.py b/tests/components/folder_watcher/test_init.py index 69dea7f51ec..dee97afa468 100644 --- a/tests/components/folder_watcher/test_init.py +++ b/tests/components/folder_watcher/test_init.py @@ -1,9 +1,10 @@ """The tests for the folder_watcher component.""" -from unittest.mock import Mock, patch import os +from unittest.mock import Mock, patch from homeassistant.components import folder_watcher from homeassistant.setup import async_setup_component + from tests.common import MockDependency diff --git a/tests/components/foobot/test_sensor.py b/tests/components/foobot/test_sensor.py index 6c601c5ad69..9c6a17264eb 100644 --- a/tests/components/foobot/test_sensor.py +++ b/tests/components/foobot/test_sensor.py @@ -1,16 +1,17 @@ """The tests for the Foobot sensor platform.""" -import re import asyncio +import re from unittest.mock import MagicMock + import pytest - -import homeassistant.components.sensor as sensor from homeassistant.components.foobot import sensor as foobot +import homeassistant.components.sensor as sensor from homeassistant.const import TEMP_CELSIUS from homeassistant.exceptions import PlatformNotReady from homeassistant.setup import async_setup_component + from tests.common import load_fixture VALID_CONFIG = { diff --git a/tests/components/freedns/test_init.py b/tests/components/freedns/test_init.py index 6441a418bfa..b9e59de9ff1 100644 --- a/tests/components/freedns/test_init.py +++ b/tests/components/freedns/test_init.py @@ -1,9 +1,10 @@ """Test the FreeDNS component.""" import asyncio + import pytest -from homeassistant.setup import async_setup_component from homeassistant.components import freedns +from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow from tests.common import async_fire_time_changed diff --git a/tests/components/frontend/test_init.py b/tests/components/frontend/test_init.py index b38c55cd583..f8fd5f1d7e3 100644 --- a/tests/components/frontend/test_init.py +++ b/tests/components/frontend/test_init.py @@ -5,19 +5,18 @@ from unittest.mock import patch import pytest -from homeassistant.setup import async_setup_component from homeassistant.components.frontend import ( - DOMAIN, - CONF_JS_VERSION, - CONF_THEMES, CONF_EXTRA_HTML_URL, CONF_EXTRA_HTML_URL_ES5, + CONF_JS_VERSION, + CONF_THEMES, + DOMAIN, EVENT_PANELS_UPDATED, ) from homeassistant.components.websocket_api.const import TYPE_RESULT +from homeassistant.setup import async_setup_component -from tests.common import mock_coro, async_capture_events - +from tests.common import async_capture_events, mock_coro CONFIG_THEMES = {DOMAIN: {CONF_THEMES: {"happy": {"primary-color": "red"}}}} diff --git a/tests/components/frontend/test_storage.py b/tests/components/frontend/test_storage.py index 76296f743c5..d907f69bbf9 100644 --- a/tests/components/frontend/test_storage.py +++ b/tests/components/frontend/test_storage.py @@ -1,8 +1,8 @@ """The tests for frontend storage.""" import pytest -from homeassistant.setup import async_setup_component from homeassistant.components.frontend import storage +from homeassistant.setup import async_setup_component @pytest.fixture(autouse=True) From d58e6e924a271fa446f6267d598ad18269e7d164 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 14:17:36 +0100 Subject: [PATCH 208/677] Sort imports according to PEP8 for components starting with "G" (#29767) --- homeassistant/components/garadget/cover.py | 14 +++++------ homeassistant/components/geizhals/sensor.py | 8 +++---- homeassistant/components/generic/camera.py | 24 +++++++++---------- .../components/generic_thermostat/climate.py | 2 +- homeassistant/components/geniushub/switch.py | 2 +- .../components/geo_location/__init__.py | 1 - .../components/geo_rss_events/sensor.py | 10 ++++---- homeassistant/components/github/sensor.py | 1 + homeassistant/components/google/__init__.py | 13 +++++----- homeassistant/components/google_cloud/tts.py | 4 ++-- .../components/google_domains/__init__.py | 2 +- .../components/google_wifi/sensor.py | 11 ++++----- homeassistant/components/graphite/__init__.py | 2 +- .../components/growatt_server/sensor.py | 12 +++++----- tests/components/generic/test_camera.py | 1 - .../geo_json_events/test_geo_location.py | 17 ++++++------- .../components/geo_rss_events/test_sensor.py | 11 +++++---- tests/components/google/test_calendar.py | 1 - tests/components/google_domains/test_init.py | 2 +- tests/components/google_translate/test_tts.py | 7 +++--- tests/components/google_wifi/test_sensor.py | 8 +++---- tests/components/graphite/test_init.py | 9 +++---- 22 files changed, 80 insertions(+), 82 deletions(-) diff --git a/homeassistant/components/garadget/cover.py b/homeassistant/components/garadget/cover.py index d487c39db6b..0eeb5f2b8f9 100644 --- a/homeassistant/components/garadget/cover.py +++ b/homeassistant/components/garadget/cover.py @@ -4,19 +4,19 @@ import logging import requests import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.components.cover import CoverDevice, PLATFORM_SCHEMA -from homeassistant.helpers.event import track_utc_time_change +from homeassistant.components.cover import PLATFORM_SCHEMA, CoverDevice from homeassistant.const import ( - CONF_DEVICE, - CONF_USERNAME, - CONF_PASSWORD, CONF_ACCESS_TOKEN, + CONF_COVERS, + CONF_DEVICE, CONF_NAME, + CONF_PASSWORD, + CONF_USERNAME, STATE_CLOSED, STATE_OPEN, - CONF_COVERS, ) +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.event import track_utc_time_change _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/geizhals/sensor.py b/homeassistant/components/geizhals/sensor.py index f04e943964c..9d5605cc404 100644 --- a/homeassistant/components/geizhals/sensor.py +++ b/homeassistant/components/geizhals/sensor.py @@ -1,15 +1,15 @@ """Parse prices of a device from geizhals.""" -import logging from datetime import timedelta +import logging from geizhals import Device, Geizhals import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -import homeassistant.helpers.config_validation as cv -from homeassistant.util import Throttle -from homeassistant.helpers.entity import Entity from homeassistant.const import CONF_NAME +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity +from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/generic/camera.py b/homeassistant/components/generic/camera.py index 01d2fb948ed..3d39d75ff4a 100644 --- a/homeassistant/components/generic/camera.py +++ b/homeassistant/components/generic/camera.py @@ -8,24 +8,24 @@ import requests from requests.auth import HTTPDigestAuth import voluptuous as vol -from homeassistant.const import ( - CONF_NAME, - CONF_USERNAME, - CONF_PASSWORD, - CONF_AUTHENTICATION, - HTTP_BASIC_AUTHENTICATION, - HTTP_DIGEST_AUTHENTICATION, - CONF_VERIFY_SSL, -) -from homeassistant.exceptions import TemplateError from homeassistant.components.camera import ( - PLATFORM_SCHEMA, DEFAULT_CONTENT_TYPE, + PLATFORM_SCHEMA, SUPPORT_STREAM, Camera, ) -from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.const import ( + CONF_AUTHENTICATION, + CONF_NAME, + CONF_PASSWORD, + CONF_USERNAME, + CONF_VERIFY_SSL, + HTTP_BASIC_AUTHENTICATION, + HTTP_DIGEST_AUTHENTICATION, +) +from homeassistant.exceptions import TemplateError from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.aiohttp_client import async_get_clientsession _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/generic_thermostat/climate.py b/homeassistant/components/generic_thermostat/climate.py index 5cb4c21c577..cb5ae275df7 100644 --- a/homeassistant/components/generic_thermostat/climate.py +++ b/homeassistant/components/generic_thermostat/climate.py @@ -15,9 +15,9 @@ from homeassistant.components.climate.const import ( HVAC_MODE_HEAT, HVAC_MODE_OFF, PRESET_AWAY, + PRESET_NONE, SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE, - PRESET_NONE, ) from homeassistant.const import ( ATTR_ENTITY_ID, diff --git a/homeassistant/components/geniushub/switch.py b/homeassistant/components/geniushub/switch.py index 79d14417dd4..b73c9a89041 100644 --- a/homeassistant/components/geniushub/switch.py +++ b/homeassistant/components/geniushub/switch.py @@ -1,5 +1,5 @@ """Support for Genius Hub switch/outlet devices.""" -from homeassistant.components.switch import SwitchDevice, DEVICE_CLASS_OUTLET +from homeassistant.components.switch import DEVICE_CLASS_OUTLET, SwitchDevice from homeassistant.helpers.typing import ConfigType, HomeAssistantType from . import DOMAIN, GeniusZone diff --git a/homeassistant/components/geo_location/__init__.py b/homeassistant/components/geo_location/__init__.py index e5c587f93ed..6142fa22209 100644 --- a/homeassistant/components/geo_location/__init__.py +++ b/homeassistant/components/geo_location/__init__.py @@ -11,7 +11,6 @@ from homeassistant.helpers.config_validation import ( # noqa: F401 from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent - # mypy: allow-untyped-defs, no-check-untyped-defs _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/geo_rss_events/sensor.py b/homeassistant/components/geo_rss_events/sensor.py index 39e6c5c7e82..b8891cdef0d 100644 --- a/homeassistant/components/geo_rss_events/sensor.py +++ b/homeassistant/components/geo_rss_events/sensor.py @@ -8,23 +8,23 @@ and grouped by category. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.geo_rss_events/ """ -import logging from datetime import timedelta +import logging -import voluptuous as vol from georss_client import UPDATE_OK, UPDATE_OK_NO_DATA from georss_client.generic_feed import GenericFeed +import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_UNIT_OF_MEASUREMENT, - CONF_NAME, CONF_LATITUDE, CONF_LONGITUDE, + CONF_NAME, CONF_RADIUS, + CONF_UNIT_OF_MEASUREMENT, CONF_URL, ) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/github/sensor.py b/homeassistant/components/github/sensor.py index 5e8200b41ab..c77cf7930b8 100644 --- a/homeassistant/components/github/sensor.py +++ b/homeassistant/components/github/sensor.py @@ -1,6 +1,7 @@ """Support for GitHub.""" from datetime import timedelta import logging + import github import voluptuous as vol diff --git a/homeassistant/components/google/__init__.py b/homeassistant/components/google/__init__.py index 9cb9be0fa4f..0e7ccd33b33 100644 --- a/homeassistant/components/google/__init__.py +++ b/homeassistant/components/google/__init__.py @@ -1,23 +1,22 @@ """Support for Google - Calendar Event Devices.""" -from datetime import timedelta, datetime +from datetime import datetime, timedelta import logging import os -import yaml +from googleapiclient import discovery as google_discovery import httplib2 from oauth2client.client import ( - OAuth2WebServerFlow, - OAuth2DeviceCodeError, FlowExchangeError, + OAuth2DeviceCodeError, + OAuth2WebServerFlow, ) from oauth2client.file import Storage -from googleapiclient import discovery as google_discovery - import voluptuous as vol from voluptuous.error import Error as VoluptuousError +import yaml -import homeassistant.helpers.config_validation as cv from homeassistant.helpers import discovery +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import generate_entity_id from homeassistant.helpers.event import track_time_change from homeassistant.util import convert, dt diff --git a/homeassistant/components/google_cloud/tts.py b/homeassistant/components/google_cloud/tts.py index 942ee0a4e48..6721520d130 100644 --- a/homeassistant/components/google_cloud/tts.py +++ b/homeassistant/components/google_cloud/tts.py @@ -1,11 +1,11 @@ """Support for the Google Cloud TTS service.""" +import asyncio import logging import os -import asyncio import async_timeout -import voluptuous as vol from google.cloud import texttospeech +import voluptuous as vol from homeassistant.components.tts import CONF_LANG, PLATFORM_SCHEMA, Provider import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/google_domains/__init__.py b/homeassistant/components/google_domains/__init__.py index 8f975db6fd8..d440567d9ad 100644 --- a/homeassistant/components/google_domains/__init__.py +++ b/homeassistant/components/google_domains/__init__.py @@ -7,8 +7,8 @@ import aiohttp import async_timeout import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.const import CONF_DOMAIN, CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/google_wifi/sensor.py b/homeassistant/components/google_wifi/sensor.py index 1d4ed8d84f8..9d6f3ea3d58 100644 --- a/homeassistant/components/google_wifi/sensor.py +++ b/homeassistant/components/google_wifi/sensor.py @@ -1,21 +1,20 @@ """Support for retrieving status info from Google Wifi/OnHub routers.""" -import logging from datetime import timedelta +import logging -import voluptuous as vol import requests +import voluptuous as vol -from homeassistant.util import dt -import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_NAME, CONF_HOST, CONF_MONITORED_CONDITIONS, + CONF_NAME, STATE_UNKNOWN, ) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -from homeassistant.util import Throttle +from homeassistant.util import Throttle, dt _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/graphite/__init__.py b/homeassistant/components/graphite/__init__.py index 3809249bea6..bf34bc3ddea 100644 --- a/homeassistant/components/graphite/__init__.py +++ b/homeassistant/components/graphite/__init__.py @@ -7,7 +7,6 @@ import time import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.const import ( CONF_HOST, CONF_PORT, @@ -17,6 +16,7 @@ from homeassistant.const import ( EVENT_STATE_CHANGED, ) from homeassistant.helpers import state +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/growatt_server/sensor.py b/homeassistant/components/growatt_server/sensor.py index 3b7109222a4..2816b86be84 100644 --- a/homeassistant/components/growatt_server/sensor.py +++ b/homeassistant/components/growatt_server/sensor.py @@ -1,17 +1,17 @@ """Read status of growatt inverters.""" -import re +import datetime import json import logging -import datetime +import re import growattServer import voluptuous as vol -from homeassistant.util import Throttle -from homeassistant.helpers.entity import Entity -import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONF_NAME, CONF_USERNAME, CONF_PASSWORD +from homeassistant.const import CONF_NAME, CONF_PASSWORD, CONF_USERNAME +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity +from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/generic/test_camera.py b/tests/components/generic/test_camera.py index 4708725fd3b..c25b4ce9f3d 100644 --- a/tests/components/generic/test_camera.py +++ b/tests/components/generic/test_camera.py @@ -1,6 +1,5 @@ """The tests for generic camera component.""" import asyncio - from unittest import mock from homeassistant.setup import async_setup_component diff --git a/tests/components/geo_json_events/test_geo_location.py b/tests/components/geo_json_events/test_geo_location.py index 7e356cc03f6..38c7200cce1 100644 --- a/tests/components/geo_json_events/test_geo_location.py +++ b/tests/components/geo_json_events/test_geo_location.py @@ -1,30 +1,31 @@ """The tests for the geojson platform.""" -from asynctest.mock import patch, MagicMock, call +from asynctest.mock import MagicMock, call, patch from homeassistant.components import geo_location -from homeassistant.components.geo_location import ATTR_SOURCE from homeassistant.components.geo_json_events.geo_location import ( - SCAN_INTERVAL, ATTR_EXTERNAL_ID, + SCAN_INTERVAL, SIGNAL_DELETE_ENTITY, SIGNAL_UPDATE_ENTITY, ) +from homeassistant.components.geo_location import ATTR_SOURCE from homeassistant.const import ( - CONF_URL, - EVENT_HOMEASSISTANT_START, - CONF_RADIUS, + ATTR_FRIENDLY_NAME, ATTR_LATITUDE, ATTR_LONGITUDE, - ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, CONF_LATITUDE, CONF_LONGITUDE, + CONF_RADIUS, + CONF_URL, + EVENT_HOMEASSISTANT_START, ) from homeassistant.helpers.dispatcher import DATA_DISPATCHER from homeassistant.setup import async_setup_component -from tests.common import assert_setup_component, async_fire_time_changed import homeassistant.util.dt as dt_util +from tests.common import assert_setup_component, async_fire_time_changed + URL = "http://geo.json.local/geo_json_events.json" CONFIG = { geo_location.DOMAIN: [ diff --git a/tests/components/geo_rss_events/test_sensor.py b/tests/components/geo_rss_events/test_sensor.py index 492290b9519..25243afea78 100644 --- a/tests/components/geo_rss_events/test_sensor.py +++ b/tests/components/geo_rss_events/test_sensor.py @@ -4,20 +4,21 @@ from unittest import mock from unittest.mock import MagicMock, patch from homeassistant.components import sensor +import homeassistant.components.geo_rss_events.sensor as geo_rss_events from homeassistant.const import ( - ATTR_UNIT_OF_MEASUREMENT, ATTR_FRIENDLY_NAME, - EVENT_HOMEASSISTANT_START, ATTR_ICON, + ATTR_UNIT_OF_MEASUREMENT, + EVENT_HOMEASSISTANT_START, ) from homeassistant.setup import setup_component +import homeassistant.util.dt as dt_util + from tests.common import ( - get_test_home_assistant, assert_setup_component, fire_time_changed, + get_test_home_assistant, ) -import homeassistant.components.geo_rss_events.sensor as geo_rss_events -import homeassistant.util.dt as dt_util URL = "http://geo.rss.local/geo_rss_events.xml" VALID_CONFIG_WITH_CATEGORIES = { diff --git a/tests/components/google/test_calendar.py b/tests/components/google/test_calendar.py index 496570ca468..4aace6f5484 100644 --- a/tests/components/google/test_calendar.py +++ b/tests/components/google/test_calendar.py @@ -25,7 +25,6 @@ import homeassistant.util.dt as dt_util from tests.common import async_mock_service - GOOGLE_CONFIG = {CONF_CLIENT_ID: "client_id", CONF_CLIENT_SECRET: "client_secret"} TEST_ENTITY = "calendar.we_are_we_are_a_test_calendar" TEST_ENTITY_NAME = "We are, we are, a... Test Calendar" diff --git a/tests/components/google_domains/test_init.py b/tests/components/google_domains/test_init.py index 1334e46b96f..80844063b00 100644 --- a/tests/components/google_domains/test_init.py +++ b/tests/components/google_domains/test_init.py @@ -4,8 +4,8 @@ from datetime import timedelta import pytest -from homeassistant.setup import async_setup_component from homeassistant.components import google_domains +from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow from tests.common import async_fire_time_changed diff --git a/tests/components/google_translate/test_tts.py b/tests/components/google_translate/test_tts.py index 13f9eb88fce..15e84b384c0 100644 --- a/tests/components/google_translate/test_tts.py +++ b/tests/components/google_translate/test_tts.py @@ -4,16 +4,15 @@ import os import shutil from unittest.mock import patch -import homeassistant.components.tts as tts from homeassistant.components.media_player.const import ( - SERVICE_PLAY_MEDIA, ATTR_MEDIA_CONTENT_ID, DOMAIN as DOMAIN_MP, + SERVICE_PLAY_MEDIA, ) +import homeassistant.components.tts as tts from homeassistant.setup import setup_component -from tests.common import get_test_home_assistant, assert_setup_component, mock_service - +from tests.common import assert_setup_component, get_test_home_assistant, mock_service from tests.components.tts.test_init import mutagen_mock # noqa: F401 diff --git a/tests/components/google_wifi/test_sensor.py b/tests/components/google_wifi/test_sensor.py index a9883c6be66..8a529f93f72 100644 --- a/tests/components/google_wifi/test_sensor.py +++ b/tests/components/google_wifi/test_sensor.py @@ -1,17 +1,17 @@ """The tests for the Google Wifi platform.""" -import unittest -from unittest.mock import patch, Mock from datetime import datetime, timedelta +import unittest +from unittest.mock import Mock, patch import requests_mock from homeassistant import core as ha -from homeassistant.setup import setup_component import homeassistant.components.google_wifi.sensor as google_wifi from homeassistant.const import STATE_UNKNOWN +from homeassistant.setup import setup_component from homeassistant.util import dt as dt_util -from tests.common import get_test_home_assistant, assert_setup_component +from tests.common import assert_setup_component, get_test_home_assistant NAME = "foo" diff --git a/tests/components/graphite/test_init.py b/tests/components/graphite/test_init.py index f6484f08245..dd60f03cd58 100644 --- a/tests/components/graphite/test_init.py +++ b/tests/components/graphite/test_init.py @@ -4,16 +4,17 @@ import unittest from unittest import mock from unittest.mock import patch -from homeassistant.setup import setup_component -import homeassistant.core as ha import homeassistant.components.graphite as graphite from homeassistant.const import ( - EVENT_STATE_CHANGED, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, - STATE_ON, + EVENT_STATE_CHANGED, STATE_OFF, + STATE_ON, ) +import homeassistant.core as ha +from homeassistant.setup import setup_component + from tests.common import get_test_home_assistant From 8b39957c56b449b984e7349e1e6f334a34eaf46d Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 14:19:48 +0100 Subject: [PATCH 209/677] Sort imports according to PEP8 for components starting with "H" (#29768) --- .../components/haveibeenpwned/sensor.py | 2 +- homeassistant/components/hddtemp/sensor.py | 10 +++---- .../components/history_graph/__init__.py | 2 +- .../components/history_stats/sensor.py | 8 ++--- .../components/hitron_coda/device_tracker.py | 6 ++-- homeassistant/components/honeywell/climate.py | 30 +++++++++---------- homeassistant/components/hook/switch.py | 10 +++---- homeassistant/components/html5/notify.py | 22 +++++++------- .../huawei_router/device_tracker.py | 4 +-- tests/components/hddtemp/test_sensor.py | 1 - tests/components/history/test_init.py | 8 ++--- tests/components/history_graph/test_init.py | 3 +- tests/components/history_stats/test_sensor.py | 7 +++-- tests/components/homematic/test_notify.py | 3 +- tests/components/honeywell/test_climate.py | 18 +++++------ tests/components/html5/test_notify.py | 7 +++-- 16 files changed, 70 insertions(+), 71 deletions(-) diff --git a/homeassistant/components/haveibeenpwned/sensor.py b/homeassistant/components/haveibeenpwned/sensor.py index 7fa3f422300..a0f30dd1a8b 100644 --- a/homeassistant/components/haveibeenpwned/sensor.py +++ b/homeassistant/components/haveibeenpwned/sensor.py @@ -7,7 +7,7 @@ import requests import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONF_EMAIL, CONF_API_KEY, ATTR_ATTRIBUTION +from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY, CONF_EMAIL import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import track_point_in_time diff --git a/homeassistant/components/hddtemp/sensor.py b/homeassistant/components/hddtemp/sensor.py index d0dd5018dca..a1052b0440a 100644 --- a/homeassistant/components/hddtemp/sensor.py +++ b/homeassistant/components/hddtemp/sensor.py @@ -1,21 +1,21 @@ """Support for getting the disk temperature of a host.""" -import logging from datetime import timedelta -from telnetlib import Telnet +import logging import socket +from telnetlib import Telnet import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_NAME, + CONF_DISKS, CONF_HOST, + CONF_NAME, CONF_PORT, TEMP_CELSIUS, TEMP_FAHRENHEIT, - CONF_DISKS, ) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/history_graph/__init__.py b/homeassistant/components/history_graph/__init__.py index ad8398c75f5..2b89556818f 100644 --- a/homeassistant/components/history_graph/__init__.py +++ b/homeassistant/components/history_graph/__init__.py @@ -3,8 +3,8 @@ import logging import voluptuous as vol +from homeassistant.const import ATTR_ENTITY_ID, CONF_ENTITIES, CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.const import CONF_ENTITIES, CONF_NAME, ATTR_ENTITY_ID from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent diff --git a/homeassistant/components/history_stats/sensor.py b/homeassistant/components/history_stats/sensor.py index 5c59b5f8e97..0bded03a29c 100644 --- a/homeassistant/components/history_stats/sensor.py +++ b/homeassistant/components/history_stats/sensor.py @@ -5,21 +5,21 @@ import math import voluptuous as vol -from homeassistant.core import callback 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 from homeassistant.const import ( - CONF_NAME, CONF_ENTITY_ID, + CONF_NAME, CONF_STATE, CONF_TYPE, EVENT_HOMEASSISTANT_START, ) +from homeassistant.core import callback from homeassistant.exceptions import TemplateError +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_state_change +import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/hitron_coda/device_tracker.py b/homeassistant/components/hitron_coda/device_tracker.py index 2f3526d45b6..12b03acbcc5 100644 --- a/homeassistant/components/hitron_coda/device_tracker.py +++ b/homeassistant/components/hitron_coda/device_tracker.py @@ -1,17 +1,17 @@ """Support for the Hitron CODA-4582U, provided by Rogers.""" -import logging from collections import namedtuple +import logging import requests import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.device_tracker import ( DOMAIN, PLATFORM_SCHEMA, DeviceScanner, ) -from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_TYPE +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_TYPE, CONF_USERNAME +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/honeywell/climate.py b/homeassistant/components/honeywell/climate.py index 42f4778eb4f..f8537bfe96a 100644 --- a/homeassistant/components/honeywell/climate.py +++ b/homeassistant/components/honeywell/climate.py @@ -1,43 +1,43 @@ """Support for Honeywell (US) Total Connect Comfort climate systems.""" import datetime import logging -from typing import Any, Dict, Optional, List +from typing import Any, Dict, List, Optional import requests -import voluptuous as vol import somecomfort +import voluptuous as vol -from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA +from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice from homeassistant.components.climate.const import ( ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, + CURRENT_HVAC_COOL, + CURRENT_HVAC_FAN, + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, FAN_AUTO, FAN_DIFFUSE, FAN_ON, + HVAC_MODE_COOL, + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_OFF, + PRESET_AWAY, + PRESET_NONE, SUPPORT_AUX_HEAT, SUPPORT_FAN_MODE, SUPPORT_PRESET_MODE, SUPPORT_TARGET_HUMIDITY, SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_RANGE, - CURRENT_HVAC_COOL, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - CURRENT_HVAC_FAN, - HVAC_MODE_OFF, - HVAC_MODE_HEAT, - HVAC_MODE_COOL, - HVAC_MODE_HEAT_COOL, - PRESET_AWAY, - PRESET_NONE, ) from homeassistant.const import ( + ATTR_TEMPERATURE, CONF_PASSWORD, + CONF_REGION, CONF_USERNAME, TEMP_CELSIUS, TEMP_FAHRENHEIT, - ATTR_TEMPERATURE, - CONF_REGION, ) import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/hook/switch.py b/homeassistant/components/hook/switch.py index d26f35e2dfc..14c4d4ba662 100644 --- a/homeassistant/components/hook/switch.py +++ b/homeassistant/components/hook/switch.py @@ -1,13 +1,13 @@ """Support Hook, available at hooksmarthome.com.""" -import logging import asyncio +import logging -import voluptuous as vol -import async_timeout import aiohttp +import async_timeout +import voluptuous as vol -from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA -from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, CONF_TOKEN +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.const import CONF_PASSWORD, CONF_TOKEN, CONF_USERNAME from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/html5/notify.py b/homeassistant/components/html5/notify.py index 481a00e96e1..6d6fcd5c377 100644 --- a/homeassistant/components/html5/notify.py +++ b/homeassistant/components/html5/notify.py @@ -1,23 +1,30 @@ """HTML5 Push Messaging notification service.""" from datetime import datetime, timedelta - from functools import partial -from urllib.parse import urlparse import json import logging import time +from urllib.parse import urlparse import uuid from aiohttp.hdrs import AUTHORIZATION import jwt -from pywebpush import WebPusher from py_vapid import Vapid +from pywebpush import WebPusher import voluptuous as vol from voluptuous.humanize import humanize_error from homeassistant.components import websocket_api from homeassistant.components.frontend import add_manifest_json_key from homeassistant.components.http import HomeAssistantView +from homeassistant.components.notify import ( + ATTR_DATA, + ATTR_TARGET, + ATTR_TITLE, + ATTR_TITLE_DEFAULT, + PLATFORM_SCHEMA, + BaseNotificationService, +) from homeassistant.const import ( HTTP_BAD_REQUEST, HTTP_INTERNAL_SERVER_ERROR, @@ -29,15 +36,6 @@ from homeassistant.helpers import config_validation as cv from homeassistant.util import ensure_unique_string from homeassistant.util.json import load_json, save_json -from homeassistant.components.notify import ( - ATTR_DATA, - ATTR_TARGET, - ATTR_TITLE, - ATTR_TITLE_DEFAULT, - PLATFORM_SCHEMA, - BaseNotificationService, -) - from .const import DOMAIN, SERVICE_DISMISS _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/huawei_router/device_tracker.py b/homeassistant/components/huawei_router/device_tracker.py index b7b5731dfd3..4b52060e425 100644 --- a/homeassistant/components/huawei_router/device_tracker.py +++ b/homeassistant/components/huawei_router/device_tracker.py @@ -1,19 +1,19 @@ """Support for HUAWEI routers.""" import base64 +from collections import namedtuple import logging import re -from collections import namedtuple import requests import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.device_tracker import ( DOMAIN, PLATFORM_SCHEMA, DeviceScanner, ) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/hddtemp/test_sensor.py b/tests/components/hddtemp/test_sensor.py index aeb4ed2ab9b..0b4d2d0d4c6 100644 --- a/tests/components/hddtemp/test_sensor.py +++ b/tests/components/hddtemp/test_sensor.py @@ -1,6 +1,5 @@ """The tests for the hddtemp platform.""" import socket - import unittest from unittest.mock import patch diff --git a/tests/components/history/test_init.py b/tests/components/history/test_init.py index 68bc9c5371f..051024999e4 100644 --- a/tests/components/history/test_init.py +++ b/tests/components/history/test_init.py @@ -4,15 +4,15 @@ from datetime import timedelta import unittest from unittest.mock import patch, sentinel -from homeassistant.setup import setup_component, async_setup_component -import homeassistant.core as ha -import homeassistant.util.dt as dt_util from homeassistant.components import history, recorder +import homeassistant.core as ha +from homeassistant.setup import async_setup_component, setup_component +import homeassistant.util.dt as dt_util from tests.common import ( + get_test_home_assistant, init_recorder_component, mock_state_change_event, - get_test_home_assistant, ) diff --git a/tests/components/history_graph/test_init.py b/tests/components/history_graph/test_init.py index d46bdd02843..ef41f70aaa7 100644 --- a/tests/components/history_graph/test_init.py +++ b/tests/components/history_graph/test_init.py @@ -3,7 +3,8 @@ import unittest from homeassistant.setup import setup_component -from tests.common import init_recorder_component, get_test_home_assistant + +from tests.common import get_test_home_assistant, init_recorder_component class TestGraph(unittest.TestCase): diff --git a/tests/components/history_stats/test_sensor.py b/tests/components/history_stats/test_sensor.py index 178abf2b152..492f928c9f0 100644 --- a/tests/components/history_stats/test_sensor.py +++ b/tests/components/history_stats/test_sensor.py @@ -3,17 +3,18 @@ from datetime import datetime, timedelta import unittest from unittest.mock import patch + import pytest import pytz -from homeassistant.const import STATE_UNKNOWN -from homeassistant.setup import setup_component from homeassistant.components.history_stats.sensor import HistoryStatsSensor +from homeassistant.const import STATE_UNKNOWN import homeassistant.core as ha from homeassistant.helpers.template import Template +from homeassistant.setup import setup_component import homeassistant.util.dt as dt_util -from tests.common import init_recorder_component, get_test_home_assistant +from tests.common import get_test_home_assistant, init_recorder_component class TestHistoryStatsSensor(unittest.TestCase): diff --git a/tests/components/homematic/test_notify.py b/tests/components/homematic/test_notify.py index 967e217f1a7..411be41eb39 100644 --- a/tests/components/homematic/test_notify.py +++ b/tests/components/homematic/test_notify.py @@ -2,8 +2,9 @@ import unittest -from homeassistant.setup import setup_component import homeassistant.components.notify as notify_comp +from homeassistant.setup import setup_component + from tests.common import assert_setup_component, get_test_home_assistant diff --git a/tests/components/honeywell/test_climate.py b/tests/components/honeywell/test_climate.py index 9c93eb9a7c7..feba4f6410e 100644 --- a/tests/components/honeywell/test_climate.py +++ b/tests/components/honeywell/test_climate.py @@ -2,25 +2,23 @@ import unittest from unittest import mock -import voluptuous as vol +import pytest import requests.exceptions import somecomfort -import pytest +import voluptuous as vol -from homeassistant.const import ( - CONF_USERNAME, - CONF_PASSWORD, - TEMP_CELSIUS, - TEMP_FAHRENHEIT, -) from homeassistant.components.climate.const import ( ATTR_FAN_MODE, ATTR_FAN_MODES, ATTR_HVAC_MODES, ) - import homeassistant.components.honeywell.climate as honeywell - +from homeassistant.const import ( + CONF_PASSWORD, + CONF_USERNAME, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, +) pytestmark = pytest.mark.skip("Need to be fixed!") diff --git a/tests/components/html5/test_notify.py b/tests/components/html5/test_notify.py index 481d7a010c9..a9fd998f003 100644 --- a/tests/components/html5/test_notify.py +++ b/tests/components/html5/test_notify.py @@ -1,11 +1,12 @@ """Test HTML5 notify platform.""" import json -from unittest.mock import patch, MagicMock, mock_open +from unittest.mock import MagicMock, mock_open, patch + from aiohttp.hdrs import AUTHORIZATION -from homeassistant.setup import async_setup_component -from homeassistant.exceptions import HomeAssistantError import homeassistant.components.html5.notify as html5 +from homeassistant.exceptions import HomeAssistantError +from homeassistant.setup import async_setup_component CONFIG_FILE = "file.conf" From 710680d60454275b06d1387d98b0f158b0fa418d Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 14:20:40 +0100 Subject: [PATCH 210/677] use isort to sort imports for components starting with 'm' (#29772) --- homeassistant/components/mailbox/__init__.py | 1 - .../components/mcp23017/binary_sensor.py | 6 ++--- homeassistant/components/mcp23017/switch.py | 6 ++--- homeassistant/components/melissa/climate.py | 8 +++--- .../components/meraki/device_tracker.py | 9 ++++--- .../components/microsoft_face/__init__.py | 2 +- homeassistant/components/mikrotik/__init__.py | 25 ++++++++++--------- .../components/mikrotik/device_tracker.py | 17 +++++++------ homeassistant/components/mill/climate.py | 2 +- homeassistant/components/min_max/sensor.py | 10 ++++---- homeassistant/components/mjpeg/camera.py | 20 +++++++-------- .../components/mold_indicator/sensor.py | 7 +++--- .../components/mpchc/media_player.py | 2 +- .../components/mqtt_eventstream/__init__.py | 3 +-- .../components/mqtt_json/device_tracker.py | 10 ++++---- homeassistant/components/mqtt_room/sensor.py | 8 +++--- .../components/mqtt_statestream/__init__.py | 4 +-- homeassistant/components/myq/cover.py | 2 +- .../manual/test_alarm_control_panel.py | 16 ++++++------ .../manual_mqtt/test_alarm_control_panel.py | 14 +++++------ tests/components/marytts/test_tts.py | 11 ++++---- .../components/meraki/test_device_tracker.py | 9 ++++--- tests/components/mfi/test_sensor.py | 6 ++--- tests/components/mfi/test_switch.py | 4 +-- tests/components/mhz19/test_sensor.py | 9 ++++--- tests/components/microsoft_face/test_init.py | 4 +-- .../test_image_processing.py | 10 ++++---- .../test_image_processing.py | 10 ++++---- tests/components/min_max/test_sensor.py | 7 +++--- tests/components/mochad/test_switch.py | 2 +- tests/components/modbus/test_modbus_sensor.py | 18 +++++++------ .../components/mold_indicator/test_sensor.py | 6 ++--- .../components/monoprice/test_media_player.py | 25 ++++++++++--------- tests/components/moon/test_sensor.py | 4 +-- .../components/mqtt_eventstream/test_init.py | 6 ++--- .../mqtt_json/test_device_tracker.py | 9 ++++--- tests/components/mqtt_room/test_sensor.py | 6 ++--- .../components/mqtt_statestream/test_init.py | 2 +- tests/components/mythicbeastsdns/test_init.py | 3 ++- 39 files changed, 167 insertions(+), 156 deletions(-) diff --git a/homeassistant/components/mailbox/__init__.py b/homeassistant/components/mailbox/__init__.py index 1252036e1b2..0381d932328 100644 --- a/homeassistant/components/mailbox/__init__.py +++ b/homeassistant/components/mailbox/__init__.py @@ -16,7 +16,6 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.setup import async_prepare_setup_platform - # mypy: allow-untyped-defs, no-check-untyped-defs _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/mcp23017/binary_sensor.py b/homeassistant/components/mcp23017/binary_sensor.py index 088052c469e..e95b91389cd 100644 --- a/homeassistant/components/mcp23017/binary_sensor.py +++ b/homeassistant/components/mcp23017/binary_sensor.py @@ -1,13 +1,13 @@ """Support for binary sensor using I2C MCP23017 chip.""" import logging -import voluptuous as vol +import adafruit_mcp230xx # pylint: disable=import-error import board # pylint: disable=import-error import busio # pylint: disable=import-error -import adafruit_mcp230xx # pylint: disable=import-error import digitalio # pylint: disable=import-error +import voluptuous as vol -from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice from homeassistant.const import DEVICE_DEFAULT_NAME import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/mcp23017/switch.py b/homeassistant/components/mcp23017/switch.py index 399ed17c44b..8506106b705 100644 --- a/homeassistant/components/mcp23017/switch.py +++ b/homeassistant/components/mcp23017/switch.py @@ -1,16 +1,16 @@ """Support for switch sensor using I2C MCP23017 chip.""" import logging -import voluptuous as vol +import adafruit_mcp230xx # pylint: disable=import-error import board # pylint: disable=import-error import busio # pylint: disable=import-error -import adafruit_mcp230xx # pylint: disable=import-error import digitalio # pylint: disable=import-error +import voluptuous as vol from homeassistant.components.switch import PLATFORM_SCHEMA from homeassistant.const import DEVICE_DEFAULT_NAME -from homeassistant.helpers.entity import ToggleEntity import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import ToggleEntity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/melissa/climate.py b/homeassistant/components/melissa/climate.py index c09203c3e33..4b033811f43 100644 --- a/homeassistant/components/melissa/climate.py +++ b/homeassistant/components/melissa/climate.py @@ -3,6 +3,10 @@ import logging from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( + FAN_AUTO, + FAN_HIGH, + FAN_LOW, + FAN_MEDIUM, HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_DRY, @@ -11,10 +15,6 @@ from homeassistant.components.climate.const import ( HVAC_MODE_OFF, SUPPORT_FAN_MODE, SUPPORT_TARGET_TEMPERATURE, - FAN_AUTO, - FAN_HIGH, - FAN_MEDIUM, - FAN_LOW, ) from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS diff --git a/homeassistant/components/meraki/device_tracker.py b/homeassistant/components/meraki/device_tracker.py index 3f50caa2c8b..1aa1485922e 100644 --- a/homeassistant/components/meraki/device_tracker.py +++ b/homeassistant/components/meraki/device_tracker.py @@ -5,15 +5,16 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.meraki/ """ -import logging import json +import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv + +from homeassistant.components.device_tracker import PLATFORM_SCHEMA, SOURCE_TYPE_ROUTER +from homeassistant.components.http import HomeAssistantView from homeassistant.const import HTTP_BAD_REQUEST, HTTP_UNPROCESSABLE_ENTITY from homeassistant.core import callback -from homeassistant.components.http import HomeAssistantView -from homeassistant.components.device_tracker import PLATFORM_SCHEMA, SOURCE_TYPE_ROUTER +import homeassistant.helpers.config_validation as cv CONF_VALIDATOR = "validator" CONF_SECRET = "secret" diff --git a/homeassistant/components/microsoft_face/__init__.py b/homeassistant/components/microsoft_face/__init__.py index 5d0c50e536a..244c8a0e8ee 100644 --- a/homeassistant/components/microsoft_face/__init__.py +++ b/homeassistant/components/microsoft_face/__init__.py @@ -8,7 +8,7 @@ from aiohttp.hdrs import CONTENT_TYPE import async_timeout import voluptuous as vol -from homeassistant.const import CONF_API_KEY, CONF_TIMEOUT, ATTR_NAME +from homeassistant.const import ATTR_NAME, CONF_API_KEY, CONF_TIMEOUT from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/mikrotik/__init__.py b/homeassistant/components/mikrotik/__init__.py index aacd3c65b3e..9b533288d86 100644 --- a/homeassistant/components/mikrotik/__init__.py +++ b/homeassistant/components/mikrotik/__init__.py @@ -2,34 +2,35 @@ import logging import ssl -import voluptuous as vol import librouteros from librouteros.login import login_plain, login_token +import voluptuous as vol +from homeassistant.components.device_tracker import DOMAIN as DEVICE_TRACKER from homeassistant.const import ( CONF_HOST, + CONF_METHOD, CONF_PASSWORD, - CONF_USERNAME, CONF_PORT, CONF_SSL, - CONF_METHOD, + CONF_USERNAME, ) from homeassistant.helpers import config_validation as cv from homeassistant.helpers.discovery import load_platform -from homeassistant.components.device_tracker import DOMAIN as DEVICE_TRACKER + from .const import ( - NAME, + CONF_ARP_PING, + CONF_ENCODING, + CONF_LOGIN_METHOD, + CONF_TRACK_DEVICES, + DEFAULT_ENCODING, DOMAIN, HOSTS, + IDENTITY, + MIKROTIK_SERVICES, MTK_LOGIN_PLAIN, MTK_LOGIN_TOKEN, - DEFAULT_ENCODING, - IDENTITY, - CONF_TRACK_DEVICES, - CONF_ENCODING, - CONF_ARP_PING, - CONF_LOGIN_METHOD, - MIKROTIK_SERVICES, + NAME, ) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/mikrotik/device_tracker.py b/homeassistant/components/mikrotik/device_tracker.py index 6c3fb559cba..92fcfac4ae4 100644 --- a/homeassistant/components/mikrotik/device_tracker.py +++ b/homeassistant/components/mikrotik/device_tracker.py @@ -5,18 +5,19 @@ from homeassistant.components.device_tracker import ( DOMAIN as DEVICE_TRACKER, DeviceScanner, ) -from homeassistant.util import slugify from homeassistant.const import CONF_METHOD +from homeassistant.util import slugify + from .const import ( - HOSTS, - MIKROTIK, - CONF_ARP_PING, - MIKROTIK_SERVICES, - CAPSMAN, - WIRELESS, - DHCP, ARP, ATTR_DEVICE_TRACKER, + CAPSMAN, + CONF_ARP_PING, + DHCP, + HOSTS, + MIKROTIK, + MIKROTIK_SERVICES, + WIRELESS, ) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/mill/climate.py b/homeassistant/components/mill/climate.py index b08015fe548..875d217247c 100644 --- a/homeassistant/components/mill/climate.py +++ b/homeassistant/components/mill/climate.py @@ -6,11 +6,11 @@ import voluptuous as vol from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice from homeassistant.components.climate.const import ( + FAN_ON, HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_FAN_MODE, SUPPORT_TARGET_TEMPERATURE, - FAN_ON, ) from homeassistant.const import ( ATTR_TEMPERATURE, diff --git a/homeassistant/components/min_max/sensor.py b/homeassistant/components/min_max/sensor.py index 519bba464de..977ee51cd1c 100644 --- a/homeassistant/components/min_max/sensor.py +++ b/homeassistant/components/min_max/sensor.py @@ -3,16 +3,16 @@ import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_NAME, - STATE_UNKNOWN, - STATE_UNAVAILABLE, - CONF_TYPE, ATTR_UNIT_OF_MEASUREMENT, + CONF_NAME, + CONF_TYPE, + STATE_UNAVAILABLE, + STATE_UNKNOWN, ) from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_state_change diff --git a/homeassistant/components/mjpeg/camera.py b/homeassistant/components/mjpeg/camera.py index b7cfa20a163..ab0409694d1 100644 --- a/homeassistant/components/mjpeg/camera.py +++ b/homeassistant/components/mjpeg/camera.py @@ -1,7 +1,7 @@ """Support for IP Cameras.""" import asyncio -import logging from contextlib import closing +import logging import aiohttp import async_timeout @@ -9,21 +9,21 @@ import requests from requests.auth import HTTPBasicAuth, HTTPDigestAuth import voluptuous as vol +from homeassistant.components.camera import PLATFORM_SCHEMA, Camera from homeassistant.const import ( - CONF_NAME, - CONF_USERNAME, - CONF_PASSWORD, CONF_AUTHENTICATION, + CONF_NAME, + CONF_PASSWORD, + CONF_USERNAME, + CONF_VERIFY_SSL, HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION, - CONF_VERIFY_SSL, -) -from homeassistant.components.camera import PLATFORM_SCHEMA, Camera -from homeassistant.helpers.aiohttp_client import ( - async_get_clientsession, - async_aiohttp_proxy_web, ) from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.aiohttp_client import ( + async_aiohttp_proxy_web, + async_get_clientsession, +) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/mold_indicator/sensor.py b/homeassistant/components/mold_indicator/sensor.py index d0e5c7d51dd..15f8b80a5ab 100644 --- a/homeassistant/components/mold_indicator/sensor.py +++ b/homeassistant/components/mold_indicator/sensor.py @@ -6,20 +6,19 @@ import voluptuous as vol from homeassistant import util from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.core import callback from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, + CONF_NAME, EVENT_HOMEASSISTANT_START, STATE_UNKNOWN, TEMP_CELSIUS, TEMP_FAHRENHEIT, - CONF_NAME, ) +from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_state_change -import homeassistant.helpers.config_validation as cv - _LOGGER = logging.getLogger(__name__) ATTR_CRITICAL_TEMP = "estimated_critical_temp" diff --git a/homeassistant/components/mpchc/media_player.py b/homeassistant/components/mpchc/media_player.py index 580156a5653..a3f2c500030 100644 --- a/homeassistant/components/mpchc/media_player.py +++ b/homeassistant/components/mpchc/media_player.py @@ -5,7 +5,7 @@ import re import requests import voluptuous as vol -from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice from homeassistant.components.media_player.const import ( SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, diff --git a/homeassistant/components/mqtt_eventstream/__init__.py b/homeassistant/components/mqtt_eventstream/__init__.py index ce11c7cb933..48448355df8 100644 --- a/homeassistant/components/mqtt_eventstream/__init__.py +++ b/homeassistant/components/mqtt_eventstream/__init__.py @@ -4,7 +4,6 @@ import json import voluptuous as vol -from homeassistant.core import callback from homeassistant.components.mqtt import valid_publish_topic, valid_subscribe_topic from homeassistant.const import ( ATTR_SERVICE_DATA, @@ -13,7 +12,7 @@ from homeassistant.const import ( EVENT_TIME_CHANGED, MATCH_ALL, ) -from homeassistant.core import EventOrigin, State +from homeassistant.core import EventOrigin, State, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.json import JSONEncoder diff --git a/homeassistant/components/mqtt_json/device_tracker.py b/homeassistant/components/mqtt_json/device_tracker.py index a7dda18e7e6..8f64636b817 100644 --- a/homeassistant/components/mqtt_json/device_tracker.py +++ b/homeassistant/components/mqtt_json/device_tracker.py @@ -5,17 +5,17 @@ import logging import voluptuous as vol 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 -import homeassistant.helpers.config_validation as cv +from homeassistant.components.mqtt import CONF_QOS from homeassistant.const import ( - CONF_DEVICES, + ATTR_BATTERY_LEVEL, ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE, - ATTR_BATTERY_LEVEL, + CONF_DEVICES, ) +from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/mqtt_room/sensor.py b/homeassistant/components/mqtt_room/sensor.py index 5323383e892..d8dfa65f799 100644 --- a/homeassistant/components/mqtt_room/sensor.py +++ b/homeassistant/components/mqtt_room/sensor.py @@ -1,16 +1,16 @@ """Support for MQTT room presence detection.""" -import logging -import json from datetime import timedelta +import json +import logging import voluptuous as vol 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 -from homeassistant.const import CONF_NAME, CONF_TIMEOUT, STATE_NOT_HOME, ATTR_ID +from homeassistant.const import ATTR_ID, CONF_NAME, CONF_TIMEOUT, STATE_NOT_HOME from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import dt, slugify diff --git a/homeassistant/components/mqtt_statestream/__init__.py b/homeassistant/components/mqtt_statestream/__init__.py index f99b341df5b..e35f2653283 100644 --- a/homeassistant/components/mqtt_statestream/__init__.py +++ b/homeassistant/components/mqtt_statestream/__init__.py @@ -3,6 +3,7 @@ import json import voluptuous as vol +from homeassistant.components.mqtt import valid_publish_topic from homeassistant.const import ( CONF_DOMAINS, CONF_ENTITIES, @@ -11,11 +12,10 @@ from homeassistant.const import ( MATCH_ALL, ) from homeassistant.core import callback -from homeassistant.components.mqtt import valid_publish_topic +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entityfilter import generate_filter from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers.json import JSONEncoder -import homeassistant.helpers.config_validation as cv CONF_BASE_TOPIC = "base_topic" CONF_PUBLISH_ATTRIBUTES = "publish_attributes" diff --git a/homeassistant/components/myq/cover.py b/homeassistant/components/myq/cover.py index b6da7174f05..8a83f398e64 100644 --- a/homeassistant/components/myq/cover.py +++ b/homeassistant/components/myq/cover.py @@ -6,10 +6,10 @@ from pymyq.errors import MyQError import voluptuous as vol from homeassistant.components.cover import ( - CoverDevice, PLATFORM_SCHEMA, SUPPORT_CLOSE, SUPPORT_OPEN, + CoverDevice, ) from homeassistant.const import ( CONF_PASSWORD, diff --git a/tests/components/manual/test_alarm_control_panel.py b/tests/components/manual/test_alarm_control_panel.py index 666d906ff03..1b06477750b 100644 --- a/tests/components/manual/test_alarm_control_panel.py +++ b/tests/components/manual/test_alarm_control_panel.py @@ -1,22 +1,24 @@ """The tests for the manual Alarm Control Panel component.""" from datetime import timedelta -from unittest.mock import patch, MagicMock +from unittest.mock import MagicMock, patch + +from homeassistant.components import alarm_control_panel from homeassistant.components.demo import alarm_control_panel as demo -from homeassistant.setup import async_setup_component from homeassistant.const import ( - STATE_ALARM_DISARMED, - STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY, - STATE_ALARM_ARMED_NIGHT, STATE_ALARM_ARMED_CUSTOM_BYPASS, + STATE_ALARM_ARMED_HOME, + STATE_ALARM_ARMED_NIGHT, + STATE_ALARM_DISARMED, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, ) -from homeassistant.components import alarm_control_panel +from homeassistant.core import CoreState, State +from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util + from tests.common import async_fire_time_changed, mock_component, mock_restore_cache from tests.components.alarm_control_panel import common -from homeassistant.core import State, CoreState CODE = "HELLO_CODE" diff --git a/tests/components/manual_mqtt/test_alarm_control_panel.py b/tests/components/manual_mqtt/test_alarm_control_panel.py index 80a1aa8495d..91e97685588 100644 --- a/tests/components/manual_mqtt/test_alarm_control_panel.py +++ b/tests/components/manual_mqtt/test_alarm_control_panel.py @@ -1,26 +1,26 @@ """The tests for the manual_mqtt Alarm Control Panel component.""" from datetime import timedelta import unittest -from unittest.mock import patch, Mock +from unittest.mock import Mock, patch -from homeassistant.setup import setup_component +from homeassistant.components import alarm_control_panel from homeassistant.const import ( - STATE_ALARM_DISARMED, - STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, + STATE_ALARM_DISARMED, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, ) -from homeassistant.components import alarm_control_panel +from homeassistant.setup import setup_component import homeassistant.util.dt as dt_util from tests.common import ( + assert_setup_component, + fire_mqtt_message, fire_time_changed, get_test_home_assistant, mock_mqtt_component, - fire_mqtt_message, - assert_setup_component, ) from tests.components.alarm_control_panel import common diff --git a/tests/components/marytts/test_tts.py b/tests/components/marytts/test_tts.py index 5692d72d388..65d4ab7e39c 100644 --- a/tests/components/marytts/test_tts.py +++ b/tests/components/marytts/test_tts.py @@ -3,15 +3,14 @@ import asyncio import os import shutil +from homeassistant.components.media_player.const import ( + DOMAIN as DOMAIN_MP, + SERVICE_PLAY_MEDIA, +) import homeassistant.components.tts as tts from homeassistant.setup import setup_component -from homeassistant.components.media_player.const import ( - SERVICE_PLAY_MEDIA, - DOMAIN as DOMAIN_MP, -) - -from tests.common import get_test_home_assistant, assert_setup_component, mock_service +from tests.common import assert_setup_component, get_test_home_assistant, mock_service from tests.components.tts.test_init import mutagen_mock # noqa: F401 diff --git a/tests/components/meraki/test_device_tracker.py b/tests/components/meraki/test_device_tracker.py index 9efd9037b67..360eb67a1ff 100644 --- a/tests/components/meraki/test_device_tracker.py +++ b/tests/components/meraki/test_device_tracker.py @@ -4,11 +4,14 @@ import json import pytest -from homeassistant.components.meraki.device_tracker import CONF_VALIDATOR, CONF_SECRET -from homeassistant.setup import async_setup_component import homeassistant.components.device_tracker as device_tracker +from homeassistant.components.meraki.device_tracker import ( + CONF_SECRET, + CONF_VALIDATOR, + URL, +) from homeassistant.const import CONF_PLATFORM -from homeassistant.components.meraki.device_tracker import URL +from homeassistant.setup import async_setup_component @pytest.fixture diff --git a/tests/components/mfi/test_sensor.py b/tests/components/mfi/test_sensor.py index 6849578bbb9..05f175fc191 100644 --- a/tests/components/mfi/test_sensor.py +++ b/tests/components/mfi/test_sensor.py @@ -2,13 +2,13 @@ import unittest import unittest.mock as mock -import requests from mficlient.client import FailedToLogin +import requests -from homeassistant.setup import setup_component -import homeassistant.components.sensor as sensor import homeassistant.components.mfi.sensor as mfi +import homeassistant.components.sensor as sensor from homeassistant.const import TEMP_CELSIUS +from homeassistant.setup import setup_component from tests.common import get_test_home_assistant diff --git a/tests/components/mfi/test_switch.py b/tests/components/mfi/test_switch.py index ebddc8c5bc2..42469b1b5ac 100644 --- a/tests/components/mfi/test_switch.py +++ b/tests/components/mfi/test_switch.py @@ -2,9 +2,9 @@ import unittest import unittest.mock as mock -from homeassistant.setup import setup_component -import homeassistant.components.switch as switch import homeassistant.components.mfi.switch as mfi +import homeassistant.components.switch as switch +from homeassistant.setup import setup_component from tests.common import get_test_home_assistant diff --git a/tests/components/mhz19/test_sensor.py b/tests/components/mhz19/test_sensor.py index 06a1f146237..5eab93a30ff 100644 --- a/tests/components/mhz19/test_sensor.py +++ b/tests/components/mhz19/test_sensor.py @@ -1,12 +1,13 @@ """Tests for MH-Z19 sensor.""" import unittest -from unittest.mock import patch, DEFAULT, Mock +from unittest.mock import DEFAULT, Mock, patch -from homeassistant.setup import setup_component -from homeassistant.components.sensor import DOMAIN import homeassistant.components.mhz19.sensor as mhz19 +from homeassistant.components.sensor import DOMAIN from homeassistant.const import TEMP_FAHRENHEIT -from tests.common import get_test_home_assistant, assert_setup_component +from homeassistant.setup import setup_component + +from tests.common import assert_setup_component, get_test_home_assistant class TestMHZ19Sensor(unittest.TestCase): diff --git a/tests/components/microsoft_face/test_init.py b/tests/components/microsoft_face/test_init.py index 26a47778603..24d67f56fb5 100644 --- a/tests/components/microsoft_face/test_init.py +++ b/tests/components/microsoft_face/test_init.py @@ -19,10 +19,10 @@ from homeassistant.const import ATTR_NAME from homeassistant.setup import setup_component from tests.common import ( - get_test_home_assistant, assert_setup_component, - mock_coro, + get_test_home_assistant, load_fixture, + mock_coro, ) diff --git a/tests/components/microsoft_face_detect/test_image_processing.py b/tests/components/microsoft_face_detect/test_image_processing.py index b25b3453825..1b01ee7434c 100644 --- a/tests/components/microsoft_face_detect/test_image_processing.py +++ b/tests/components/microsoft_face_detect/test_image_processing.py @@ -1,15 +1,15 @@ """The tests for the microsoft face detect platform.""" -from unittest.mock import patch, PropertyMock +from unittest.mock import PropertyMock, patch -from homeassistant.core import callback -from homeassistant.const import ATTR_ENTITY_PICTURE -from homeassistant.setup import setup_component import homeassistant.components.image_processing as ip import homeassistant.components.microsoft_face as mf +from homeassistant.const import ATTR_ENTITY_PICTURE +from homeassistant.core import callback +from homeassistant.setup import setup_component from tests.common import ( - get_test_home_assistant, assert_setup_component, + get_test_home_assistant, load_fixture, mock_coro, ) diff --git a/tests/components/microsoft_face_identify/test_image_processing.py b/tests/components/microsoft_face_identify/test_image_processing.py index 95f314dbc21..311d463bc1d 100644 --- a/tests/components/microsoft_face_identify/test_image_processing.py +++ b/tests/components/microsoft_face_identify/test_image_processing.py @@ -1,15 +1,15 @@ """The tests for the microsoft face identify platform.""" -from unittest.mock import patch, PropertyMock +from unittest.mock import PropertyMock, patch -from homeassistant.core import callback -from homeassistant.const import ATTR_ENTITY_PICTURE, STATE_UNKNOWN -from homeassistant.setup import setup_component import homeassistant.components.image_processing as ip import homeassistant.components.microsoft_face as mf +from homeassistant.const import ATTR_ENTITY_PICTURE, STATE_UNKNOWN +from homeassistant.core import callback +from homeassistant.setup import setup_component from tests.common import ( - get_test_home_assistant, assert_setup_component, + get_test_home_assistant, load_fixture, mock_coro, ) diff --git a/tests/components/min_max/test_sensor.py b/tests/components/min_max/test_sensor.py index 7fa0dee5aa3..fcc9a72cf7b 100644 --- a/tests/components/min_max/test_sensor.py +++ b/tests/components/min_max/test_sensor.py @@ -1,14 +1,15 @@ """The test for the min/max sensor platform.""" import unittest -from homeassistant.setup import setup_component from homeassistant.const import ( - STATE_UNKNOWN, - STATE_UNAVAILABLE, ATTR_UNIT_OF_MEASUREMENT, + STATE_UNAVAILABLE, + STATE_UNKNOWN, TEMP_CELSIUS, TEMP_FAHRENHEIT, ) +from homeassistant.setup import setup_component + from tests.common import get_test_home_assistant diff --git a/tests/components/mochad/test_switch.py b/tests/components/mochad/test_switch.py index 8c0dc3554db..aa6ce354a32 100644 --- a/tests/components/mochad/test_switch.py +++ b/tests/components/mochad/test_switch.py @@ -4,9 +4,9 @@ import unittest.mock as mock import pytest -from homeassistant.setup import setup_component from homeassistant.components import switch from homeassistant.components.mochad import switch as mochad +from homeassistant.setup import setup_component from tests.common import get_test_home_assistant diff --git a/tests/components/modbus/test_modbus_sensor.py b/tests/components/modbus/test_modbus_sensor.py index 82d0b4bd5f0..9f13cba8907 100644 --- a/tests/components/modbus/test_modbus_sensor.py +++ b/tests/components/modbus/test_modbus_sensor.py @@ -1,14 +1,9 @@ """The tests for the Modbus sensor component.""" -import pytest from datetime import timedelta from unittest import mock -from homeassistant.const import ( - CONF_NAME, - CONF_OFFSET, - CONF_PLATFORM, - CONF_SCAN_INTERVAL, -) +import pytest + from homeassistant.components.modbus import DEFAULT_HUB, DOMAIN as MODBUS_DOMAIN from homeassistant.components.modbus.sensor import ( CONF_COUNT, @@ -26,9 +21,16 @@ from homeassistant.components.modbus.sensor import ( REGISTER_TYPE_INPUT, ) from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.const import ( + CONF_NAME, + CONF_OFFSET, + CONF_PLATFORM, + CONF_SCAN_INTERVAL, +) from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util -from tests.common import MockModule, mock_integration, async_fire_time_changed + +from tests.common import MockModule, async_fire_time_changed, mock_integration @pytest.fixture() diff --git a/tests/components/mold_indicator/test_sensor.py b/tests/components/mold_indicator/test_sensor.py index d0a08dd25d4..ce0450d3304 100644 --- a/tests/components/mold_indicator/test_sensor.py +++ b/tests/components/mold_indicator/test_sensor.py @@ -1,13 +1,13 @@ """The tests for the MoldIndicator sensor.""" import unittest -from homeassistant.setup import setup_component -import homeassistant.components.sensor as sensor from homeassistant.components.mold_indicator.sensor import ( - ATTR_DEWPOINT, ATTR_CRITICAL_TEMP, + ATTR_DEWPOINT, ) +import homeassistant.components.sensor as sensor from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT, STATE_UNKNOWN, TEMP_CELSIUS +from homeassistant.setup import setup_component from tests.common import get_test_home_assistant diff --git a/tests/components/monoprice/test_media_player.py b/tests/components/monoprice/test_media_player.py index cb064048d7b..e6747dfc4bf 100644 --- a/tests/components/monoprice/test_media_player.py +++ b/tests/components/monoprice/test_media_player.py @@ -1,31 +1,32 @@ """The tests for Monoprice Media player platform.""" +from collections import defaultdict import unittest from unittest import mock + +import pytest import voluptuous as vol -from collections import defaultdict from homeassistant.components.media_player.const import ( - SUPPORT_TURN_ON, + SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP, - SUPPORT_SELECT_SOURCE, -) -from homeassistant.const import STATE_ON, STATE_OFF - -import tests.common -from homeassistant.components.monoprice.media_player import ( - DATA_MONOPRICE, - PLATFORM_SCHEMA, - setup_platform, ) from homeassistant.components.monoprice.const import ( DOMAIN, SERVICE_RESTORE, SERVICE_SNAPSHOT, ) -import pytest +from homeassistant.components.monoprice.media_player import ( + DATA_MONOPRICE, + PLATFORM_SCHEMA, + setup_platform, +) +from homeassistant.const import STATE_OFF, STATE_ON + +import tests.common class AttrDict(dict): diff --git a/tests/components/moon/test_sensor.py b/tests/components/moon/test_sensor.py index 95b1a1c305f..1e19d0a4d83 100644 --- a/tests/components/moon/test_sensor.py +++ b/tests/components/moon/test_sensor.py @@ -1,10 +1,10 @@ """The test for the moon sensor platform.""" -import unittest from datetime import datetime +import unittest from unittest.mock import patch -import homeassistant.util.dt as dt_util from homeassistant.setup import setup_component +import homeassistant.util.dt as dt_util from tests.common import get_test_home_assistant diff --git a/tests/components/mqtt_eventstream/test_init.py b/tests/components/mqtt_eventstream/test_init.py index b5ebe3c1ae1..f4062458d91 100644 --- a/tests/components/mqtt_eventstream/test_init.py +++ b/tests/components/mqtt_eventstream/test_init.py @@ -2,19 +2,19 @@ import json from unittest.mock import ANY, patch -from homeassistant.setup import setup_component import homeassistant.components.mqtt_eventstream as eventstream from homeassistant.const import EVENT_STATE_CHANGED from homeassistant.core import State, callback from homeassistant.helpers.json import JSONEncoder +from homeassistant.setup import setup_component import homeassistant.util.dt as dt_util from tests.common import ( + fire_mqtt_message, + fire_time_changed, get_test_home_assistant, mock_mqtt_component, - fire_mqtt_message, mock_state_change_event, - fire_time_changed, ) diff --git a/tests/components/mqtt_json/test_device_tracker.py b/tests/components/mqtt_json/test_device_tracker.py index 00be001840b..7f3f806da52 100644 --- a/tests/components/mqtt_json/test_device_tracker.py +++ b/tests/components/mqtt_json/test_device_tracker.py @@ -2,18 +2,19 @@ import json import logging import os + from asynctest import patch import pytest -from homeassistant.setup import async_setup_component from homeassistant.components.device_tracker.legacy import ( - YAML_DEVICES, - ENTITY_ID_FORMAT, DOMAIN as DT_DOMAIN, + ENTITY_ID_FORMAT, + YAML_DEVICES, ) from homeassistant.const import CONF_PLATFORM +from homeassistant.setup import async_setup_component -from tests.common import async_mock_mqtt_component, async_fire_mqtt_message +from tests.common import async_fire_mqtt_message, async_mock_mqtt_component _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/mqtt_room/test_sensor.py b/tests/components/mqtt_room/test_sensor.py index 07b8f89ef3f..e8a9e62403f 100644 --- a/tests/components/mqtt_room/test_sensor.py +++ b/tests/components/mqtt_room/test_sensor.py @@ -1,12 +1,12 @@ """The tests for the MQTT room presence sensor.""" -import json import datetime +import json from unittest.mock import patch -from homeassistant.setup import async_setup_component +from homeassistant.components.mqtt import CONF_QOS, CONF_STATE_TOPIC, DEFAULT_QOS import homeassistant.components.sensor as sensor -from homeassistant.components.mqtt import CONF_STATE_TOPIC, CONF_QOS, DEFAULT_QOS from homeassistant.const import CONF_NAME, CONF_PLATFORM +from homeassistant.setup import async_setup_component from homeassistant.util import dt from tests.common import async_fire_mqtt_message, async_mock_mqtt_component diff --git a/tests/components/mqtt_statestream/test_init.py b/tests/components/mqtt_statestream/test_init.py index 280b61a490c..ffab0e0846f 100644 --- a/tests/components/mqtt_statestream/test_init.py +++ b/tests/components/mqtt_statestream/test_init.py @@ -1,9 +1,9 @@ """The tests for the MQTT statestream component.""" from unittest.mock import ANY, call, patch -from homeassistant.setup import setup_component import homeassistant.components.mqtt_statestream as statestream from homeassistant.core import State +from homeassistant.setup import setup_component from tests.common import ( get_test_home_assistant, diff --git a/tests/components/mythicbeastsdns/test_init.py b/tests/components/mythicbeastsdns/test_init.py index c62b7241510..ee037a029ed 100644 --- a/tests/components/mythicbeastsdns/test_init.py +++ b/tests/components/mythicbeastsdns/test_init.py @@ -1,9 +1,10 @@ """Test the Mythic Beasts DNS component.""" import logging + import asynctest -from homeassistant.setup import async_setup_component from homeassistant.components import mythicbeastsdns +from homeassistant.setup import async_setup_component _LOGGER = logging.getLogger(__name__) From 14779ce3d0955c2f7f98ce5fd5c665ac24586fdc Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 14:21:24 +0100 Subject: [PATCH 211/677] Sort imports according to PEP8 for components starting with "I" (#29769) --- .../components/imap_email_content/sensor.py | 12 +++++------ .../input_boolean/reproduce_state.py | 4 ++-- .../input_number/reproduce_state.py | 2 +- .../input_select/reproduce_state.py | 4 ++-- .../components/integration/sensor.py | 9 ++++---- homeassistant/components/intent/__init__.py | 8 +++---- .../components/intent_script/__init__.py | 2 +- homeassistant/components/ios/config_flow.py | 4 ++-- .../components/itunes/media_player.py | 4 ++-- .../ign_sismologia/test_geo_location.py | 21 ++++++++++--------- .../components/image_processing/test_init.py | 12 +++++------ .../imap_email_content/test_sensor.py | 6 +++--- tests/components/influxdb/test_init.py | 2 +- tests/components/input_select/test_init.py | 6 +++--- tests/components/intent/test_init.py | 4 ++-- tests/components/ios/test_init.py | 2 +- tests/components/ipma/test_config_flow.py | 6 +++--- tests/components/ipma/test_weather.py | 4 ++-- .../islamic_prayer_times/test_sensor.py | 4 +++- tests/components/izone/test_config_flow.py | 2 +- 20 files changed, 60 insertions(+), 58 deletions(-) diff --git a/homeassistant/components/imap_email_content/sensor.py b/homeassistant/components/imap_email_content/sensor.py index 62dceae0dad..307d5a22c1e 100644 --- a/homeassistant/components/imap_email_content/sensor.py +++ b/homeassistant/components/imap_email_content/sensor.py @@ -1,24 +1,24 @@ """Email sensor support.""" -import logging +from collections import deque import datetime import email -from collections import deque - import imaplib +import logging + import voluptuous as vol -from homeassistant.helpers.entity import Entity from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( + ATTR_DATE, CONF_NAME, + CONF_PASSWORD, CONF_PORT, CONF_USERNAME, - CONF_PASSWORD, CONF_VALUE_TEMPLATE, CONTENT_TYPE_TEXT_PLAIN, - ATTR_DATE, ) import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/input_boolean/reproduce_state.py b/homeassistant/components/input_boolean/reproduce_state.py index b8bc18edfac..558d57ae862 100644 --- a/homeassistant/components/input_boolean/reproduce_state.py +++ b/homeassistant/components/input_boolean/reproduce_state.py @@ -4,11 +4,11 @@ import logging from typing import Iterable, Optional from homeassistant.const import ( + ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON, - STATE_ON, STATE_OFF, - ATTR_ENTITY_ID, + STATE_ON, ) from homeassistant.core import Context, State from homeassistant.helpers.typing import HomeAssistantType diff --git a/homeassistant/components/input_number/reproduce_state.py b/homeassistant/components/input_number/reproduce_state.py index 97a4837d371..22a91f74000 100644 --- a/homeassistant/components/input_number/reproduce_state.py +++ b/homeassistant/components/input_number/reproduce_state.py @@ -7,7 +7,7 @@ from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import Context, State from homeassistant.helpers.typing import HomeAssistantType -from . import DOMAIN, SERVICE_SET_VALUE, ATTR_VALUE +from . import ATTR_VALUE, DOMAIN, SERVICE_SET_VALUE _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/input_select/reproduce_state.py b/homeassistant/components/input_select/reproduce_state.py index 657f518cd3d..818510bee4a 100644 --- a/homeassistant/components/input_select/reproduce_state.py +++ b/homeassistant/components/input_select/reproduce_state.py @@ -9,11 +9,11 @@ from homeassistant.core import Context, State from homeassistant.helpers.typing import HomeAssistantType from . import ( + ATTR_OPTION, + ATTR_OPTIONS, DOMAIN, SERVICE_SELECT_OPTION, SERVICE_SET_OPTIONS, - ATTR_OPTION, - ATTR_OPTIONS, ) ATTR_GROUP = [ATTR_OPTION, ATTR_OPTIONS] diff --git a/homeassistant/components/integration/sensor.py b/homeassistant/components/integration/sensor.py index 236a996794a..560a7cbd33c 100644 --- a/homeassistant/components/integration/sensor.py +++ b/homeassistant/components/integration/sensor.py @@ -1,22 +1,21 @@ """Numeric integration of data coming from a source sensor over time.""" +from decimal import Decimal, DecimalException import logging -from decimal import Decimal, DecimalException import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_NAME, ATTR_UNIT_OF_MEASUREMENT, - STATE_UNKNOWN, + CONF_NAME, STATE_UNAVAILABLE, + STATE_UNKNOWN, ) from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers.restore_state import RestoreEntity - # mypy: allow-untyped-defs, no-check-untyped-defs _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/intent/__init__.py b/homeassistant/components/intent/__init__.py index 31ab36ecc89..53960851f6c 100644 --- a/homeassistant/components/intent/__init__.py +++ b/homeassistant/components/intent/__init__.py @@ -4,13 +4,13 @@ import logging import voluptuous as vol -from homeassistant.core import HomeAssistant -from homeassistant.const import EVENT_COMPONENT_LOADED -from homeassistant.setup import ATTR_COMPONENT from homeassistant.components import http from homeassistant.components.http.data_validator import RequestDataValidator +from homeassistant.const import EVENT_COMPONENT_LOADED +from homeassistant.core import HomeAssistant from homeassistant.helpers import config_validation as cv, intent -from homeassistant.loader import async_get_integration, IntegrationNotFound +from homeassistant.loader import IntegrationNotFound, async_get_integration +from homeassistant.setup import ATTR_COMPONENT from .const import DOMAIN diff --git a/homeassistant/components/intent_script/__init__.py b/homeassistant/components/intent_script/__init__.py index ce4b8b27a51..38f93ed3506 100644 --- a/homeassistant/components/intent_script/__init__.py +++ b/homeassistant/components/intent_script/__init__.py @@ -4,7 +4,7 @@ import logging import voluptuous as vol -from homeassistant.helpers import intent, template, script, config_validation as cv +from homeassistant.helpers import config_validation as cv, intent, script, template DOMAIN = "intent_script" diff --git a/homeassistant/components/ios/config_flow.py b/homeassistant/components/ios/config_flow.py index 511e350aae3..9eaca389ba1 100644 --- a/homeassistant/components/ios/config_flow.py +++ b/homeassistant/components/ios/config_flow.py @@ -1,8 +1,8 @@ """Config flow for iOS.""" -from homeassistant.helpers import config_entry_flow from homeassistant import config_entries -from .const import DOMAIN +from homeassistant.helpers import config_entry_flow +from .const import DOMAIN config_entry_flow.register_discovery_flow( DOMAIN, "Home Assistant iOS", lambda *_: True, config_entries.CONN_CLASS_CLOUD_PUSH diff --git a/homeassistant/components/itunes/media_player.py b/homeassistant/components/itunes/media_player.py index aebe16ffa26..112a9c609d8 100644 --- a/homeassistant/components/itunes/media_player.py +++ b/homeassistant/components/itunes/media_player.py @@ -4,7 +4,7 @@ import logging import requests import voluptuous as vol -from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, @@ -14,11 +14,11 @@ from homeassistant.components.media_player.const import ( SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, + SUPPORT_SHUFFLE_SET, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - SUPPORT_SHUFFLE_SET, ) from homeassistant.const import ( CONF_HOST, diff --git a/tests/components/ign_sismologia/test_geo_location.py b/tests/components/ign_sismologia/test_geo_location.py index 4babbb6a425..2d869c1a062 100644 --- a/tests/components/ign_sismologia/test_geo_location.py +++ b/tests/components/ign_sismologia/test_geo_location.py @@ -1,34 +1,35 @@ """The tests for the IGN Sismologia (Earthquakes) Feed platform.""" import datetime -from unittest.mock import patch, MagicMock, call +from unittest.mock import MagicMock, call, patch from homeassistant.components import geo_location from homeassistant.components.geo_location import ATTR_SOURCE from homeassistant.components.ign_sismologia.geo_location import ( ATTR_EXTERNAL_ID, - SCAN_INTERVAL, - ATTR_REGION, - ATTR_MAGNITUDE, ATTR_IMAGE_URL, + ATTR_MAGNITUDE, ATTR_PUBLICATION_DATE, + ATTR_REGION, ATTR_TITLE, + SCAN_INTERVAL, ) from homeassistant.const import ( - EVENT_HOMEASSISTANT_START, - CONF_RADIUS, + ATTR_ATTRIBUTION, + ATTR_FRIENDLY_NAME, + ATTR_ICON, ATTR_LATITUDE, ATTR_LONGITUDE, - ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, - ATTR_ATTRIBUTION, CONF_LATITUDE, CONF_LONGITUDE, - ATTR_ICON, + CONF_RADIUS, + EVENT_HOMEASSISTANT_START, ) from homeassistant.setup import async_setup_component -from tests.common import assert_setup_component, async_fire_time_changed import homeassistant.util.dt as dt_util +from tests.common import assert_setup_component, async_fire_time_changed + CONFIG = {geo_location.DOMAIN: [{"platform": "ign_sismologia", CONF_RADIUS: 200}]} CONFIG_WITH_CUSTOM_LOCATION = { diff --git a/tests/components/image_processing/test_init.py b/tests/components/image_processing/test_init.py index 88c870c78fb..3503fcfb9a2 100644 --- a/tests/components/image_processing/test_init.py +++ b/tests/components/image_processing/test_init.py @@ -1,17 +1,17 @@ """The tests for the image_processing component.""" -from unittest.mock import patch, PropertyMock +from unittest.mock import PropertyMock, patch -from homeassistant.core import callback -from homeassistant.const import ATTR_ENTITY_PICTURE -from homeassistant.setup import setup_component -from homeassistant.exceptions import HomeAssistantError import homeassistant.components.http as http import homeassistant.components.image_processing as ip +from homeassistant.const import ATTR_ENTITY_PICTURE +from homeassistant.core import callback +from homeassistant.exceptions import HomeAssistantError +from homeassistant.setup import setup_component from tests.common import ( + assert_setup_component, get_test_home_assistant, get_test_instance_port, - assert_setup_component, ) from tests.components.image_processing import common diff --git a/tests/components/imap_email_content/test_sensor.py b/tests/components/imap_email_content/test_sensor.py index fcb9da6ddf3..ee39bac51ef 100644 --- a/tests/components/imap_email_content/test_sensor.py +++ b/tests/components/imap_email_content/test_sensor.py @@ -1,14 +1,14 @@ """The tests for the IMAP email content sensor platform.""" from collections import deque +import datetime import email from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText -import datetime import unittest -from homeassistant.helpers.template import Template -from homeassistant.helpers.event import track_state_change from homeassistant.components.imap_email_content import sensor as imap_email_content +from homeassistant.helpers.event import track_state_change +from homeassistant.helpers.template import Template from tests.common import get_test_home_assistant diff --git a/tests/components/influxdb/test_init.py b/tests/components/influxdb/test_init.py index 3e0e1dcf706..1dd2681b7f2 100644 --- a/tests/components/influxdb/test_init.py +++ b/tests/components/influxdb/test_init.py @@ -3,9 +3,9 @@ import datetime import unittest from unittest import mock -from homeassistant.setup import setup_component import homeassistant.components.influxdb as influxdb from homeassistant.const import EVENT_STATE_CHANGED, STATE_OFF, STATE_ON, STATE_STANDBY +from homeassistant.setup import setup_component from tests.common import get_test_home_assistant diff --git a/tests/components/input_select/test_init.py b/tests/components/input_select/test_init.py index cbf9bd5f4ee..6c5d8501239 100644 --- a/tests/components/input_select/test_init.py +++ b/tests/components/input_select/test_init.py @@ -2,18 +2,18 @@ # pylint: disable=protected-access import asyncio -from homeassistant.loader import bind_hass from homeassistant.components.input_select import ( ATTR_OPTION, ATTR_OPTIONS, DOMAIN, - SERVICE_SET_OPTIONS, SERVICE_SELECT_NEXT, SERVICE_SELECT_OPTION, SERVICE_SELECT_PREVIOUS, + SERVICE_SET_OPTIONS, ) from homeassistant.const import ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, ATTR_ICON -from homeassistant.core import State, Context +from homeassistant.core import Context, State +from homeassistant.loader import bind_hass from homeassistant.setup import async_setup_component from tests.common import mock_restore_cache diff --git a/tests/components/intent/test_init.py b/tests/components/intent/test_init.py index 76a0399c688..56344b6affe 100644 --- a/tests/components/intent/test_init.py +++ b/tests/components/intent/test_init.py @@ -1,9 +1,9 @@ """Tests for Intent component.""" import pytest -from homeassistant.setup import async_setup_component -from homeassistant.helpers import intent from homeassistant.components.cover import SERVICE_OPEN_COVER +from homeassistant.helpers import intent +from homeassistant.setup import async_setup_component from tests.common import async_mock_service diff --git a/tests/components/ios/test_init.py b/tests/components/ios/test_init.py index 9a169987298..31eb43fc611 100644 --- a/tests/components/ios/test_init.py +++ b/tests/components/ios/test_init.py @@ -4,8 +4,8 @@ from unittest.mock import patch import pytest from homeassistant import config_entries, data_entry_flow -from homeassistant.setup import async_setup_component from homeassistant.components import ios +from homeassistant.setup import async_setup_component from tests.common import mock_component, mock_coro diff --git a/tests/components/ipma/test_config_flow.py b/tests/components/ipma/test_config_flow.py index 0850a15d620..fd44f8b2a58 100644 --- a/tests/components/ipma/test_config_flow.py +++ b/tests/components/ipma/test_config_flow.py @@ -1,10 +1,10 @@ """Tests for IPMA config flow.""" from unittest.mock import Mock, patch -from tests.common import mock_coro - -from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE from homeassistant.components.ipma import config_flow +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE + +from tests.common import mock_coro async def test_show_config_form(): diff --git a/tests/components/ipma/test_weather.py b/tests/components/ipma/test_weather.py index 5e63f6fa5c7..de13d3c94b2 100644 --- a/tests/components/ipma/test_weather.py +++ b/tests/components/ipma/test_weather.py @@ -1,6 +1,6 @@ """The tests for the IPMA weather component.""" -from unittest.mock import patch from collections import namedtuple +from unittest.mock import patch from homeassistant.components import weather from homeassistant.components.weather import ( @@ -11,9 +11,9 @@ from homeassistant.components.weather import ( ATTR_WEATHER_WIND_SPEED, DOMAIN as WEATHER_DOMAIN, ) +from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry, mock_coro -from homeassistant.setup import async_setup_component TEST_CONFIG = {"name": "HomeTown", "latitude": "40.00", "longitude": "-8.00"} diff --git a/tests/components/islamic_prayer_times/test_sensor.py b/tests/components/islamic_prayer_times/test_sensor.py index ad229404a30..389fa43945e 100644 --- a/tests/components/islamic_prayer_times/test_sensor.py +++ b/tests/components/islamic_prayer_times/test_sensor.py @@ -1,9 +1,11 @@ """The tests for the Islamic prayer times sensor platform.""" from datetime import datetime, timedelta from unittest.mock import patch -from homeassistant.setup import async_setup_component + from homeassistant.components.islamic_prayer_times.sensor import IslamicPrayerTimesData +from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util + from tests.common import async_fire_time_changed LATITUDE = 41 diff --git a/tests/components/izone/test_config_flow.py b/tests/components/izone/test_config_flow.py index b5f9aa41c80..5deafeb08a7 100644 --- a/tests/components/izone/test_config_flow.py +++ b/tests/components/izone/test_config_flow.py @@ -5,7 +5,7 @@ from unittest.mock import Mock, patch import pytest from homeassistant import config_entries, data_entry_flow -from homeassistant.components.izone.const import IZONE, DISPATCH_CONTROLLER_DISCOVERED +from homeassistant.components.izone.const import DISPATCH_CONTROLLER_DISCOVERED, IZONE from tests.common import mock_coro From 1dea0c9e340a4f03b4ec71f8112e99e63221ab50 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 14:22:30 +0100 Subject: [PATCH 212/677] Sort imports according to PEP8 for components starting with "L" (#29771) --- homeassistant/components/lannouncer/notify.py | 5 +-- homeassistant/components/lcn/__init__.py | 2 +- homeassistant/components/lcn/services.py | 2 +- homeassistant/components/lifx_cloud/scene.py | 2 +- .../linksys_smart/device_tracker.py | 2 +- .../components/liveboxplaytv/media_player.py | 2 +- .../components/llamalab_automate/notify.py | 3 +- homeassistant/components/local_file/camera.py | 4 +- homeassistant/components/lockitron/lock.py | 4 +- .../components/logentries/__init__.py | 4 +- homeassistant/components/logger/__init__.py | 2 +- tests/components/litejet/test_init.py | 1 + tests/components/logbook/test_init.py | 42 +++++++++---------- tests/components/logentries/test_init.py | 4 +- tests/components/logger/test_init.py | 2 +- .../logi_circle/test_config_flow.py | 2 +- tests/components/london_air/test_sensor.py | 4 +- tests/components/lovelace/test_init.py | 4 +- 18 files changed, 45 insertions(+), 46 deletions(-) diff --git a/homeassistant/components/lannouncer/notify.py b/homeassistant/components/lannouncer/notify.py index 9512a75047b..9421eb16f51 100644 --- a/homeassistant/components/lannouncer/notify.py +++ b/homeassistant/components/lannouncer/notify.py @@ -5,14 +5,13 @@ from urllib.parse import urlencode import voluptuous as vol -from homeassistant.const import CONF_HOST, CONF_PORT -import homeassistant.helpers.config_validation as cv - from homeassistant.components.notify import ( ATTR_DATA, PLATFORM_SCHEMA, BaseNotificationService, ) +from homeassistant.const import CONF_HOST, CONF_PORT +import homeassistant.helpers.config_validation as cv ATTR_METHOD = "method" ATTR_METHOD_DEFAULT = "speak" diff --git a/homeassistant/components/lcn/__init__.py b/homeassistant/components/lcn/__init__.py index f7170340f1b..14f25be70b0 100644 --- a/homeassistant/components/lcn/__init__.py +++ b/homeassistant/components/lcn/__init__.py @@ -4,7 +4,6 @@ import logging import pypck import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.climate import DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP from homeassistant.const import ( CONF_ADDRESS, @@ -22,6 +21,7 @@ from homeassistant.const import ( TEMP_CELSIUS, TEMP_FAHRENHEIT, ) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.entity import Entity diff --git a/homeassistant/components/lcn/services.py b/homeassistant/components/lcn/services.py index aba29e55176..3c775224623 100644 --- a/homeassistant/components/lcn/services.py +++ b/homeassistant/components/lcn/services.py @@ -2,13 +2,13 @@ import pypck import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.const import ( CONF_ADDRESS, CONF_BRIGHTNESS, CONF_STATE, CONF_UNIT_OF_MEASUREMENT, ) +import homeassistant.helpers.config_validation as cv from .const import ( CONF_CONNECTIONS, diff --git a/homeassistant/components/lifx_cloud/scene.py b/homeassistant/components/lifx_cloud/scene.py index ac4e0201fb8..4068ff20fe2 100644 --- a/homeassistant/components/lifx_cloud/scene.py +++ b/homeassistant/components/lifx_cloud/scene.py @@ -8,7 +8,7 @@ import async_timeout import voluptuous as vol from homeassistant.components.scene import Scene -from homeassistant.const import CONF_TOKEN, CONF_TIMEOUT, CONF_PLATFORM +from homeassistant.const import CONF_PLATFORM, CONF_TIMEOUT, CONF_TOKEN from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/linksys_smart/device_tracker.py b/homeassistant/components/linksys_smart/device_tracker.py index 1af84a4c4ab..a2a8e317133 100644 --- a/homeassistant/components/linksys_smart/device_tracker.py +++ b/homeassistant/components/linksys_smart/device_tracker.py @@ -4,13 +4,13 @@ import logging import requests import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.device_tracker import ( DOMAIN, PLATFORM_SCHEMA, DeviceScanner, ) from homeassistant.const import CONF_HOST +import homeassistant.helpers.config_validation as cv DEFAULT_TIMEOUT = 10 diff --git a/homeassistant/components/liveboxplaytv/media_player.py b/homeassistant/components/liveboxplaytv/media_player.py index 996b4f33b50..66fb383d677 100644 --- a/homeassistant/components/liveboxplaytv/media_player.py +++ b/homeassistant/components/liveboxplaytv/media_player.py @@ -7,7 +7,7 @@ import pyteleloisirs import requests import voluptuous as vol -from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice from homeassistant.components.media_player.const import ( MEDIA_TYPE_CHANNEL, SUPPORT_NEXT_TRACK, diff --git a/homeassistant/components/llamalab_automate/notify.py b/homeassistant/components/llamalab_automate/notify.py index ab6a7032208..5a3d4e0df38 100644 --- a/homeassistant/components/llamalab_automate/notify.py +++ b/homeassistant/components/llamalab_automate/notify.py @@ -4,11 +4,10 @@ import logging import requests import voluptuous as vol +from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationService from homeassistant.const import CONF_API_KEY, CONF_DEVICE from homeassistant.helpers import config_validation as cv -from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationService - _LOGGER = logging.getLogger(__name__) _RESOURCE = "https://llamalab.com/automate/cloud/message" diff --git a/homeassistant/components/local_file/camera.py b/homeassistant/components/local_file/camera.py index 9bd476cfb27..1d06efeb708 100644 --- a/homeassistant/components/local_file/camera.py +++ b/homeassistant/components/local_file/camera.py @@ -5,12 +5,12 @@ import os import voluptuous as vol -from homeassistant.const import CONF_NAME, ATTR_ENTITY_ID from homeassistant.components.camera import ( - Camera, CAMERA_SERVICE_SCHEMA, PLATFORM_SCHEMA, + Camera, ) +from homeassistant.const import ATTR_ENTITY_ID, CONF_NAME from homeassistant.helpers import config_validation as cv from .const import ( diff --git a/homeassistant/components/lockitron/lock.py b/homeassistant/components/lockitron/lock.py index b993f644ecd..5840c7f5537 100644 --- a/homeassistant/components/lockitron/lock.py +++ b/homeassistant/components/lockitron/lock.py @@ -4,9 +4,9 @@ import logging import requests import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.components.lock import LockDevice, PLATFORM_SCHEMA +from homeassistant.components.lock import PLATFORM_SCHEMA, LockDevice from homeassistant.const import CONF_ACCESS_TOKEN, CONF_ID +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/logentries/__init__.py b/homeassistant/components/logentries/__init__.py index 3601ee275b8..55d1ab7aae6 100644 --- a/homeassistant/components/logentries/__init__.py +++ b/homeassistant/components/logentries/__init__.py @@ -1,13 +1,13 @@ """Support for sending data to Logentries webhook endpoint.""" import json import logging -import requests +import requests import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.const import CONF_TOKEN, EVENT_STATE_CHANGED from homeassistant.helpers import state as state_helper +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/logger/__init__.py b/homeassistant/components/logger/__init__.py index a2ed19f92b1..8043469d43b 100644 --- a/homeassistant/components/logger/__init__.py +++ b/homeassistant/components/logger/__init__.py @@ -1,6 +1,6 @@ """Support for settting the level of logging for components.""" -import logging from collections import OrderedDict +import logging import voluptuous as vol diff --git a/tests/components/litejet/test_init.py b/tests/components/litejet/test_init.py index 8dd1440a7bc..3861e7a058e 100644 --- a/tests/components/litejet/test_init.py +++ b/tests/components/litejet/test_init.py @@ -3,6 +3,7 @@ import logging import unittest from homeassistant.components import litejet + from tests.common import get_test_home_assistant _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/logbook/test_init.py b/tests/components/logbook/test_init.py index 6ff043dff28..1b48f301529 100644 --- a/tests/components/logbook/test_init.py +++ b/tests/components/logbook/test_init.py @@ -1,30 +1,13 @@ """The tests for the logbook component.""" # pylint: disable=protected-access,invalid-name +from datetime import datetime, timedelta import logging -from datetime import timedelta, datetime import unittest import pytest import voluptuous as vol -from homeassistant.components import sun -import homeassistant.core as ha -from homeassistant.const import ( - ATTR_ENTITY_ID, - ATTR_SERVICE, - ATTR_NAME, - EVENT_STATE_CHANGED, - EVENT_HOMEASSISTANT_START, - EVENT_HOMEASSISTANT_STOP, - EVENT_AUTOMATION_TRIGGERED, - EVENT_SCRIPT_STARTED, - ATTR_HIDDEN, - STATE_NOT_HOME, - STATE_ON, - STATE_OFF, -) -import homeassistant.util.dt as dt_util -from homeassistant.components import logbook, recorder +from homeassistant.components import logbook, recorder, sun from homeassistant.components.alexa.smart_home import EVENT_ALEXA_SMART_HOME from homeassistant.components.homekit.const import ( ATTR_DISPLAY_NAME, @@ -32,10 +15,25 @@ from homeassistant.components.homekit.const import ( DOMAIN as DOMAIN_HOMEKIT, EVENT_HOMEKIT_CHANGED, ) -from homeassistant.setup import setup_component, async_setup_component - -from tests.common import init_recorder_component, get_test_home_assistant +from homeassistant.const import ( + ATTR_ENTITY_ID, + ATTR_HIDDEN, + ATTR_NAME, + ATTR_SERVICE, + EVENT_AUTOMATION_TRIGGERED, + EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP, + EVENT_SCRIPT_STARTED, + EVENT_STATE_CHANGED, + STATE_NOT_HOME, + STATE_OFF, + STATE_ON, +) +import homeassistant.core as ha +from homeassistant.setup import async_setup_component, setup_component +import homeassistant.util.dt as dt_util +from tests.common import get_test_home_assistant, init_recorder_component _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/logentries/test_init.py b/tests/components/logentries/test_init.py index 8c69ec5ff87..7125822e93e 100644 --- a/tests/components/logentries/test_init.py +++ b/tests/components/logentries/test_init.py @@ -3,9 +3,9 @@ import unittest from unittest import mock -from homeassistant.setup import setup_component import homeassistant.components.logentries as logentries -from homeassistant.const import STATE_ON, STATE_OFF, EVENT_STATE_CHANGED +from homeassistant.const import EVENT_STATE_CHANGED, STATE_OFF, STATE_ON +from homeassistant.setup import setup_component from tests.common import get_test_home_assistant diff --git a/tests/components/logger/test_init.py b/tests/components/logger/test_init.py index eac6060a5bc..00fa5aa3558 100644 --- a/tests/components/logger/test_init.py +++ b/tests/components/logger/test_init.py @@ -3,8 +3,8 @@ from collections import namedtuple import logging import unittest -from homeassistant.setup import setup_component from homeassistant.components import logger +from homeassistant.setup import setup_component from tests.common import get_test_home_assistant diff --git a/tests/components/logi_circle/test_config_flow.py b/tests/components/logi_circle/test_config_flow.py index 0e2ef29da94..7ba3816b5e2 100644 --- a/tests/components/logi_circle/test_config_flow.py +++ b/tests/components/logi_circle/test_config_flow.py @@ -8,8 +8,8 @@ from homeassistant import data_entry_flow from homeassistant.components.logi_circle import config_flow from homeassistant.components.logi_circle.config_flow import ( DOMAIN, - LogiCircleAuthCallbackView, AuthorizationFailed, + LogiCircleAuthCallbackView, ) from homeassistant.setup import async_setup_component diff --git a/tests/components/london_air/test_sensor.py b/tests/components/london_air/test_sensor.py index cd1ee32f223..83405095f2e 100644 --- a/tests/components/london_air/test_sensor.py +++ b/tests/components/london_air/test_sensor.py @@ -1,10 +1,12 @@ """The tests for the tube_state platform.""" import unittest + import requests_mock from homeassistant.components.london_air.sensor import CONF_LOCATIONS, URL from homeassistant.setup import setup_component -from tests.common import load_fixture, get_test_home_assistant + +from tests.common import get_test_home_assistant, load_fixture VALID_CONFIG = {"platform": "london_air", CONF_LOCATIONS: ["Merton"]} diff --git a/tests/components/lovelace/test_init.py b/tests/components/lovelace/test_init.py index 16aab911fb0..e8d041e9dfa 100644 --- a/tests/components/lovelace/test_init.py +++ b/tests/components/lovelace/test_init.py @@ -1,10 +1,10 @@ """Test the Lovelace initialization.""" from unittest.mock import patch -from homeassistant.setup import async_setup_component from homeassistant.components import frontend, lovelace +from homeassistant.setup import async_setup_component -from tests.common import get_system_health_info, async_capture_events +from tests.common import async_capture_events, get_system_health_info async def test_lovelace_from_storage(hass, hass_ws_client, hass_storage): From c49e423c78c425ea0906669fde391e7f9673fac7 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 14:25:18 +0100 Subject: [PATCH 213/677] Sort imports according to PEP8 for components starting with "K" (#29770) --- homeassistant/components/kankun/switch.py | 8 ++++---- homeassistant/components/keba/binary_sensor.py | 4 ++-- homeassistant/components/keba/sensor.py | 3 +-- homeassistant/components/keyboard_remote/__init__.py | 7 ++++--- tests/components/kira/test_init.py | 3 +-- 5 files changed, 12 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/kankun/switch.py b/homeassistant/components/kankun/switch.py index 63f289862f6..4f7ba5c8b06 100644 --- a/homeassistant/components/kankun/switch.py +++ b/homeassistant/components/kankun/switch.py @@ -4,15 +4,15 @@ import logging import requests import voluptuous as vol -from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice from homeassistant.const import ( CONF_HOST, CONF_NAME, - CONF_PORT, - CONF_PATH, - CONF_USERNAME, CONF_PASSWORD, + CONF_PATH, + CONF_PORT, CONF_SWITCHES, + CONF_USERNAME, ) import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/keba/binary_sensor.py b/homeassistant/components/keba/binary_sensor.py index 8c0503a2020..ac7326cc92e 100644 --- a/homeassistant/components/keba/binary_sensor.py +++ b/homeassistant/components/keba/binary_sensor.py @@ -1,12 +1,12 @@ """Support for KEBA charging station binary sensors.""" import logging -from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.binary_sensor import ( - DEVICE_CLASS_PLUG, DEVICE_CLASS_CONNECTIVITY, + DEVICE_CLASS_PLUG, DEVICE_CLASS_POWER, DEVICE_CLASS_SAFETY, + BinarySensorDevice, ) from . import DOMAIN diff --git a/homeassistant/components/keba/sensor.py b/homeassistant/components/keba/sensor.py index f46b2f0cf90..dfa04f95c65 100644 --- a/homeassistant/components/keba/sensor.py +++ b/homeassistant/components/keba/sensor.py @@ -1,9 +1,8 @@ """Support for KEBA charging station sensors.""" import logging -from homeassistant.const import ENERGY_KILO_WATT_HOUR +from homeassistant.const import DEVICE_CLASS_POWER, ENERGY_KILO_WATT_HOUR from homeassistant.helpers.entity import Entity -from homeassistant.const import DEVICE_CLASS_POWER from . import DOMAIN diff --git a/homeassistant/components/keyboard_remote/__init__.py b/homeassistant/components/keyboard_remote/__init__.py index d4ed6128cbe..24889a3f820 100644 --- a/homeassistant/components/keyboard_remote/__init__.py +++ b/homeassistant/components/keyboard_remote/__init__.py @@ -1,13 +1,14 @@ """Receive signals from a keyboard and use it as a remote control.""" # pylint: disable=import-error -import logging import asyncio +import logging -from evdev import InputDevice, categorize, ecodes, list_devices import aionotify +from evdev import InputDevice, categorize, ecodes, list_devices import voluptuous as vol -import homeassistant.helpers.config_validation as cv + from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/kira/test_init.py b/tests/components/kira/test_init.py index 9588c2bc1fa..e5056235127 100644 --- a/tests/components/kira/test_init.py +++ b/tests/components/kira/test_init.py @@ -3,9 +3,8 @@ import os import shutil import tempfile - import unittest -from unittest.mock import patch, MagicMock +from unittest.mock import MagicMock, patch import homeassistant.components.kira as kira from homeassistant.setup import setup_component From 5cdaff5405e3dbac64a1d2b52ae54180bdd9801a Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 14:26:53 +0100 Subject: [PATCH 214/677] Sort imports according to PEP8 for components starting with "O" (#29774) --- homeassistant/components/obihai/sensor.py | 9 +++------ homeassistant/components/octoprint/__init__.py | 12 ++++++------ homeassistant/components/onewire/sensor.py | 8 ++++---- homeassistant/components/onkyo/media_player.py | 8 ++++---- .../openalpr_cloud/image_processing.py | 12 ++++++------ .../openalpr_local/image_processing.py | 16 ++++++++-------- .../components/openexchangerates/sensor.py | 8 ++++---- homeassistant/components/opengarage/cover.py | 10 +++++----- homeassistant/components/opensky/sensor.py | 15 +++++++-------- homeassistant/components/oru/sensor.py | 6 ++---- tests/components/onboarding/test_init.py | 8 ++++---- tests/components/onboarding/test_views.py | 6 +++--- .../openalpr_cloud/test_image_processing.py | 8 ++++---- .../openalpr_local/test_image_processing.py | 10 +++++----- .../openhardwaremonitor/test_sensor.py | 5 ++++- tests/components/openuv/test_config_flow.py | 5 +++-- 16 files changed, 72 insertions(+), 74 deletions(-) diff --git a/homeassistant/components/obihai/sensor.py b/homeassistant/components/obihai/sensor.py index 89bfee7d4ee..13d09de0542 100644 --- a/homeassistant/components/obihai/sensor.py +++ b/homeassistant/components/obihai/sensor.py @@ -1,10 +1,9 @@ """Support for Obihai Sensors.""" +from datetime import timedelta import logging -from datetime import timedelta -import voluptuous as vol - from pyobihai import PyObihai +import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( @@ -13,10 +12,8 @@ from homeassistant.const import ( CONF_USERNAME, DEVICE_CLASS_TIMESTAMP, ) - -from homeassistant.helpers.entity import Entity import homeassistant.helpers.config_validation as cv - +from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/octoprint/__init__.py b/homeassistant/components/octoprint/__init__.py index bc71b1a5911..7564330e499 100644 --- a/homeassistant/components/octoprint/__init__.py +++ b/homeassistant/components/octoprint/__init__.py @@ -2,23 +2,23 @@ import logging import time +from aiohttp.hdrs import CONTENT_TYPE import requests import voluptuous as vol -from aiohttp.hdrs import CONTENT_TYPE from homeassistant.components.discovery import SERVICE_OCTOPRINT from homeassistant.const import ( CONF_API_KEY, + CONF_BINARY_SENSORS, CONF_HOST, - CONTENT_TYPE_JSON, + CONF_MONITORED_CONDITIONS, CONF_NAME, CONF_PATH, CONF_PORT, - CONF_SSL, - TEMP_CELSIUS, - CONF_MONITORED_CONDITIONS, CONF_SENSORS, - CONF_BINARY_SENSORS, + CONF_SSL, + CONTENT_TYPE_JSON, + TEMP_CELSIUS, ) from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/onewire/sensor.py b/homeassistant/components/onewire/sensor.py index e0a47a45b25..6e90178c5d3 100644 --- a/homeassistant/components/onewire/sensor.py +++ b/homeassistant/components/onewire/sensor.py @@ -1,15 +1,15 @@ """Support for 1-Wire environment sensors.""" +from glob import glob +import logging import os import time -import logging -from glob import glob import voluptuous as vol +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import TEMP_CELSIUS import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -from homeassistant.const import TEMP_CELSIUS -from homeassistant.components.sensor import PLATFORM_SCHEMA _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/onkyo/media_player.py b/homeassistant/components/onkyo/media_player.py index 86f0f418c3f..93107b2eb48 100644 --- a/homeassistant/components/onkyo/media_player.py +++ b/homeassistant/components/onkyo/media_player.py @@ -2,12 +2,13 @@ import logging from typing import List -import voluptuous as vol import eiscp from eiscp import eISCP +import voluptuous as vol -from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice from homeassistant.components.media_player.const import ( + DOMAIN, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_SELECT_SOURCE, @@ -16,14 +17,13 @@ from homeassistant.components.media_player.const import ( SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP, - DOMAIN, ) from homeassistant.const import ( + ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON, - ATTR_ENTITY_ID, ) import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/openalpr_cloud/image_processing.py b/homeassistant/components/openalpr_cloud/image_processing.py index 66081d9b271..64ba0d83844 100644 --- a/homeassistant/components/openalpr_cloud/image_processing.py +++ b/homeassistant/components/openalpr_cloud/image_processing.py @@ -1,26 +1,26 @@ """Component that will help set the OpenALPR cloud for ALPR processing.""" import asyncio -import logging from base64 import b64encode +import logging import aiohttp import async_timeout import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.core import split_entity_id -from homeassistant.const import CONF_API_KEY from homeassistant.components.image_processing import ( - PLATFORM_SCHEMA, CONF_CONFIDENCE, - CONF_SOURCE, CONF_ENTITY_ID, CONF_NAME, + CONF_SOURCE, + PLATFORM_SCHEMA, ) from homeassistant.components.openalpr_local.image_processing import ( ImageProcessingAlprEntity, ) +from homeassistant.const import CONF_API_KEY +from homeassistant.core import split_entity_id from homeassistant.helpers.aiohttp_client import async_get_clientsession +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/openalpr_local/image_processing.py b/homeassistant/components/openalpr_local/image_processing.py index 32a08b53165..df7b235a224 100644 --- a/homeassistant/components/openalpr_local/image_processing.py +++ b/homeassistant/components/openalpr_local/image_processing.py @@ -6,19 +6,19 @@ import re import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.core import split_entity_id, callback -from homeassistant.const import CONF_REGION from homeassistant.components.image_processing import ( - PLATFORM_SCHEMA, - ImageProcessingEntity, + ATTR_CONFIDENCE, + ATTR_ENTITY_ID, CONF_CONFIDENCE, - CONF_SOURCE, CONF_ENTITY_ID, CONF_NAME, - ATTR_ENTITY_ID, - ATTR_CONFIDENCE, + CONF_SOURCE, + PLATFORM_SCHEMA, + ImageProcessingEntity, ) +from homeassistant.const import CONF_REGION +from homeassistant.core import callback, split_entity_id +import homeassistant.helpers.config_validation as cv from homeassistant.util.async_ import run_callback_threadsafe _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/openexchangerates/sensor.py b/homeassistant/components/openexchangerates/sensor.py index 9b79eb564e0..cc6da709dff 100644 --- a/homeassistant/components/openexchangerates/sensor.py +++ b/homeassistant/components/openexchangerates/sensor.py @@ -7,11 +7,11 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_API_KEY, - CONF_NAME, - CONF_BASE, - CONF_QUOTE, ATTR_ATTRIBUTION, + CONF_API_KEY, + CONF_BASE, + CONF_NAME, + CONF_QUOTE, ) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity diff --git a/homeassistant/components/opengarage/cover.py b/homeassistant/components/opengarage/cover.py index 6b5cbc912e6..26a69fa11af 100644 --- a/homeassistant/components/opengarage/cover.py +++ b/homeassistant/components/opengarage/cover.py @@ -5,22 +5,22 @@ import requests import voluptuous as vol from homeassistant.components.cover import ( - CoverDevice, DEVICE_CLASS_GARAGE, PLATFORM_SCHEMA, - SUPPORT_OPEN, SUPPORT_CLOSE, + SUPPORT_OPEN, + CoverDevice, ) from homeassistant.const import ( - CONF_NAME, - STATE_CLOSED, - STATE_OPEN, CONF_COVERS, CONF_HOST, + CONF_NAME, CONF_PORT, CONF_SSL, CONF_VERIFY_SSL, + STATE_CLOSED, STATE_CLOSING, + STATE_OPEN, STATE_OPENING, ) import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/opensky/sensor.py b/homeassistant/components/opensky/sensor.py index 0c17daa0ab4..cef99902d23 100644 --- a/homeassistant/components/opensky/sensor.py +++ b/homeassistant/components/opensky/sensor.py @@ -1,26 +1,25 @@ """Sensor for the Open Sky Network.""" -import logging from datetime import timedelta +import logging import requests import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_NAME, - CONF_LATITUDE, - CONF_LONGITUDE, - CONF_RADIUS, ATTR_ATTRIBUTION, ATTR_LATITUDE, ATTR_LONGITUDE, + CONF_LATITUDE, + CONF_LONGITUDE, + CONF_NAME, + CONF_RADIUS, LENGTH_KILOMETERS, LENGTH_METERS, ) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -from homeassistant.util import distance as util_distance -from homeassistant.util import location as util_location +from homeassistant.util import distance as util_distance, location as util_location _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/oru/sensor.py b/homeassistant/components/oru/sensor.py index e68d8e1c45a..32eb5b7569b 100644 --- a/homeassistant/components/oru/sensor.py +++ b/homeassistant/components/oru/sensor.py @@ -2,14 +2,12 @@ from datetime import timedelta import logging +from oru import Meter, MeterError import voluptuous as vol -from oru import Meter -from oru import MeterError - from homeassistant.components.sensor import PLATFORM_SCHEMA -import homeassistant.helpers.config_validation as cv from homeassistant.const import ENERGY_KILO_WATT_HOUR +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/onboarding/test_init.py b/tests/components/onboarding/test_init.py index a7fc1ad43f4..e347a07e73f 100644 --- a/tests/components/onboarding/test_init.py +++ b/tests/components/onboarding/test_init.py @@ -1,13 +1,13 @@ """Tests for the init.""" -from unittest.mock import patch, Mock +from unittest.mock import Mock, patch -from homeassistant.setup import async_setup_component from homeassistant.components import onboarding - -from tests.common import mock_coro, MockUser +from homeassistant.setup import async_setup_component from . import mock_storage +from tests.common import MockUser, mock_coro + # Temporarily: if auth not active, always set onboarded=True diff --git a/tests/components/onboarding/test_views.py b/tests/components/onboarding/test_views.py index 7881b75ee99..6d2c6e4c08f 100644 --- a/tests/components/onboarding/test_views.py +++ b/tests/components/onboarding/test_views.py @@ -4,15 +4,15 @@ from unittest.mock import patch import pytest -from homeassistant.setup import async_setup_component from homeassistant.components import onboarding from homeassistant.components.onboarding import const, views +from homeassistant.setup import async_setup_component + +from . import mock_storage from tests.common import CLIENT_ID, register_auth_provider from tests.components.met.conftest import mock_weather # noqa: F401 -from . import mock_storage - @pytest.fixture(autouse=True) def always_mock_weather(mock_weather): # noqa: F811 diff --git a/tests/components/openalpr_cloud/test_image_processing.py b/tests/components/openalpr_cloud/test_image_processing.py index e559b6adc45..4aec9e68709 100644 --- a/tests/components/openalpr_cloud/test_image_processing.py +++ b/tests/components/openalpr_cloud/test_image_processing.py @@ -1,15 +1,15 @@ """The tests for the openalpr cloud platform.""" import asyncio -from unittest.mock import patch, PropertyMock +from unittest.mock import PropertyMock, patch -from homeassistant.core import callback -from homeassistant.setup import setup_component from homeassistant.components import camera, image_processing as ip from homeassistant.components.openalpr_cloud.image_processing import OPENALPR_API_URL +from homeassistant.core import callback +from homeassistant.setup import setup_component from tests.common import ( - get_test_home_assistant, assert_setup_component, + get_test_home_assistant, load_fixture, mock_coro, ) diff --git a/tests/components/openalpr_local/test_image_processing.py b/tests/components/openalpr_local/test_image_processing.py index bc29c227b0c..4c34abca1d4 100644 --- a/tests/components/openalpr_local/test_image_processing.py +++ b/tests/components/openalpr_local/test_image_processing.py @@ -1,13 +1,13 @@ """The tests for the openalpr local platform.""" import asyncio -from unittest.mock import patch, PropertyMock, MagicMock +from unittest.mock import MagicMock, PropertyMock, patch -from homeassistant.core import callback -from homeassistant.const import ATTR_ENTITY_PICTURE -from homeassistant.setup import setup_component import homeassistant.components.image_processing as ip +from homeassistant.const import ATTR_ENTITY_PICTURE +from homeassistant.core import callback +from homeassistant.setup import setup_component -from tests.common import get_test_home_assistant, assert_setup_component, load_fixture +from tests.common import assert_setup_component, get_test_home_assistant, load_fixture from tests.components.image_processing import common diff --git a/tests/components/openhardwaremonitor/test_sensor.py b/tests/components/openhardwaremonitor/test_sensor.py index 909f9ab2732..3fb93cb1375 100644 --- a/tests/components/openhardwaremonitor/test_sensor.py +++ b/tests/components/openhardwaremonitor/test_sensor.py @@ -1,8 +1,11 @@ """The tests for the Open Hardware Monitor platform.""" import unittest + import requests_mock + from homeassistant.setup import setup_component -from tests.common import load_fixture, get_test_home_assistant + +from tests.common import get_test_home_assistant, load_fixture class TestOpenHardwareMonitorSetup(unittest.TestCase): diff --git a/tests/components/openuv/test_config_flow.py b/tests/components/openuv/test_config_flow.py index 43dd5924a72..3aa67abdc4f 100644 --- a/tests/components/openuv/test_config_flow.py +++ b/tests/components/openuv/test_config_flow.py @@ -1,8 +1,9 @@ """Define tests for the OpenUV config flow.""" -import pytest -from pyopenuv.errors import OpenUvError from unittest.mock import patch +from pyopenuv.errors import OpenUvError +import pytest + from homeassistant import data_entry_flow from homeassistant.components.openuv import DOMAIN, config_flow from homeassistant.const import ( From 21816eeed4a3366c7880724e10929965617cc710 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 14:29:39 +0100 Subject: [PATCH 215/677] Sort imports according to PEP8 for components starting with "P" (#29775) --- .../components/pcal9535a/binary_sensor.py | 4 ++-- homeassistant/components/pcal9535a/switch.py | 2 +- .../persistent_notification/__init__.py | 1 - homeassistant/components/person/__init__.py | 12 +++++------ .../components/ping/binary_sensor.py | 12 +++++------ .../components/ping/device_tracker.py | 7 +++---- .../components/pioneer/media_player.py | 2 +- homeassistant/components/plugwise/climate.py | 8 ++++---- homeassistant/components/prowl/notify.py | 7 +++---- .../components/proximity/__init__.py | 1 - .../components/pulseaudio_loopback/switch.py | 6 +++--- homeassistant/components/push/camera.py | 10 +++++----- homeassistant/components/pushover/notify.py | 7 +++---- homeassistant/components/pushsafer/notify.py | 3 +-- homeassistant/components/pvoutput/sensor.py | 14 ++++++------- homeassistant/components/pyload/sensor.py | 6 +++--- .../persistent_notification/test_init.py | 4 ++-- tests/components/plant/test_init.py | 20 +++++++++---------- tests/components/prometheus/test_init.py | 6 +++--- tests/components/proximity/test_init.py | 2 +- tests/components/ps4/test_init.py | 3 ++- tests/components/ps4/test_media_player.py | 4 ++-- tests/components/ptvsd/test_ptvsd.py | 3 ++- tests/components/push/test_camera.py | 3 +-- tests/components/python_script/test_init.py | 4 ++-- 25 files changed, 73 insertions(+), 78 deletions(-) diff --git a/homeassistant/components/pcal9535a/binary_sensor.py b/homeassistant/components/pcal9535a/binary_sensor.py index fd4e92ccf03..236fd47af73 100644 --- a/homeassistant/components/pcal9535a/binary_sensor.py +++ b/homeassistant/components/pcal9535a/binary_sensor.py @@ -1,10 +1,10 @@ """Support for binary sensor using I2C PCAL9535A chip.""" import logging -import voluptuous as vol from pcal9535a import PCAL9535A +import voluptuous as vol -from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice from homeassistant.const import DEVICE_DEFAULT_NAME import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/pcal9535a/switch.py b/homeassistant/components/pcal9535a/switch.py index faebce5d67e..87c8ced1b0d 100644 --- a/homeassistant/components/pcal9535a/switch.py +++ b/homeassistant/components/pcal9535a/switch.py @@ -1,8 +1,8 @@ """Support for switch sensor using I2C PCAL9535A chip.""" import logging -import voluptuous as vol from pcal9535a import PCAL9535A +import voluptuous as vol from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice from homeassistant.const import DEVICE_DEFAULT_NAME diff --git a/homeassistant/components/persistent_notification/__init__.py b/homeassistant/components/persistent_notification/__init__.py index 33f17b18a80..0311bd4d30d 100644 --- a/homeassistant/components/persistent_notification/__init__.py +++ b/homeassistant/components/persistent_notification/__init__.py @@ -14,7 +14,6 @@ from homeassistant.loader import bind_hass from homeassistant.util import slugify import homeassistant.util.dt as dt_util - # mypy: allow-untyped-calls, allow-untyped-defs ATTR_CREATED_AT = "created_at" diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py index 832853c670d..2e347cf4d49 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -7,27 +7,27 @@ import uuid import voluptuous as vol +from homeassistant.auth import EVENT_USER_REMOVED from homeassistant.components import websocket_api from homeassistant.components.device_tracker import ( - DOMAIN as DEVICE_TRACKER_DOMAIN, ATTR_SOURCE_TYPE, + DOMAIN as DEVICE_TRACKER_DOMAIN, SOURCE_TYPE_GPS, ) from homeassistant.const import ( + ATTR_GPS_ACCURACY, ATTR_ID, ATTR_LATITUDE, ATTR_LONGITUDE, - ATTR_GPS_ACCURACY, CONF_ID, CONF_NAME, EVENT_HOMEASSISTANT_START, - STATE_UNKNOWN, - STATE_UNAVAILABLE, STATE_HOME, STATE_NOT_HOME, + STATE_UNAVAILABLE, + STATE_UNKNOWN, ) -from homeassistant.core import callback, Event, State -from homeassistant.auth import EVENT_USER_REMOVED +from homeassistant.core import Event, State, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.event import async_track_state_change diff --git a/homeassistant/components/ping/binary_sensor.py b/homeassistant/components/ping/binary_sensor.py index fe4c12d6738..4d9a99c678e 100644 --- a/homeassistant/components/ping/binary_sensor.py +++ b/homeassistant/components/ping/binary_sensor.py @@ -1,15 +1,15 @@ """Tracks the latency of a host by sending ICMP echo requests (ping).""" -import logging -import subprocess -import re -import sys from datetime import timedelta +import logging +import re +import subprocess +import sys import voluptuous as vol +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.const import CONF_HOST, CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA -from homeassistant.const import CONF_NAME, CONF_HOST _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/ping/device_tracker.py b/homeassistant/components/ping/device_tracker.py index 9bdc38e065b..c4d88f6061c 100644 --- a/homeassistant/components/ping/device_tracker.py +++ b/homeassistant/components/ping/device_tracker.py @@ -1,20 +1,19 @@ """Tracks devices by sending a ICMP echo request (ping).""" +from datetime import timedelta import logging import subprocess import sys -from datetime import timedelta import voluptuous as vol -import homeassistant.helpers.config_validation as cv +from homeassistant import const, util from homeassistant.components.device_tracker import PLATFORM_SCHEMA from homeassistant.components.device_tracker.const import ( CONF_SCAN_INTERVAL, SCAN_INTERVAL, SOURCE_TYPE_ROUTER, ) -from homeassistant import util -from homeassistant import const +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/pioneer/media_player.py b/homeassistant/components/pioneer/media_player.py index 51f55d4e851..3e71b54c9fa 100644 --- a/homeassistant/components/pioneer/media_player.py +++ b/homeassistant/components/pioneer/media_player.py @@ -4,7 +4,7 @@ import telnetlib import voluptuous as vol -from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice from homeassistant.components.media_player.const import ( SUPPORT_PAUSE, SUPPORT_PLAY, diff --git a/homeassistant/components/plugwise/climate.py b/homeassistant/components/plugwise/climate.py index fa1ac86941b..bc303caeca8 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -2,18 +2,17 @@ import logging -import voluptuous as vol import haanna +import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice from homeassistant.components.climate.const import ( - CURRENT_HVAC_HEAT, CURRENT_HVAC_COOL, + CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, + HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, - HVAC_MODE_AUTO, SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE, ) @@ -27,6 +26,7 @@ from homeassistant.const import ( TEMP_CELSIUS, ) from homeassistant.exceptions import PlatformNotReady +import homeassistant.helpers.config_validation as cv SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE diff --git a/homeassistant/components/prowl/notify.py b/homeassistant/components/prowl/notify.py index ab6d1b498a0..9690e748887 100644 --- a/homeassistant/components/prowl/notify.py +++ b/homeassistant/components/prowl/notify.py @@ -5,10 +5,6 @@ import logging import async_timeout import voluptuous as vol -from homeassistant.const import CONF_API_KEY -from homeassistant.helpers.aiohttp_client import async_get_clientsession -import homeassistant.helpers.config_validation as cv - from homeassistant.components.notify import ( ATTR_DATA, ATTR_TITLE, @@ -16,6 +12,9 @@ from homeassistant.components.notify import ( PLATFORM_SCHEMA, BaseNotificationService, ) +from homeassistant.const import CONF_API_KEY +from homeassistant.helpers.aiohttp_client import async_get_clientsession +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) _RESOURCE = "https://api.prowlapp.com/publicapi/" diff --git a/homeassistant/components/proximity/__init__.py b/homeassistant/components/proximity/__init__.py index 1f86958d08e..45a1c19c29e 100644 --- a/homeassistant/components/proximity/__init__.py +++ b/homeassistant/components/proximity/__init__.py @@ -10,7 +10,6 @@ from homeassistant.helpers.event import track_state_change from homeassistant.util.distance import convert from homeassistant.util.location import distance - # mypy: allow-untyped-defs, no-check-untyped-defs _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/pulseaudio_loopback/switch.py b/homeassistant/components/pulseaudio_loopback/switch.py index 618d54ab3ad..a10c5995d63 100644 --- a/homeassistant/components/pulseaudio_loopback/switch.py +++ b/homeassistant/components/pulseaudio_loopback/switch.py @@ -1,14 +1,14 @@ """Switch logic for loading/unloading pulseaudio loopback modules.""" +from datetime import timedelta import logging import re import socket -from datetime import timedelta import voluptuous as vol from homeassistant import util -from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA -from homeassistant.const import CONF_NAME, CONF_HOST, CONF_PORT +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/push/camera.py b/homeassistant/components/push/camera.py index 9d53bd7b033..f78966253b7 100644 --- a/homeassistant/components/push/camera.py +++ b/homeassistant/components/push/camera.py @@ -1,22 +1,22 @@ """Camera platform that receives images through HTTP POST.""" -import logging import asyncio - from collections import deque from datetime import timedelta -import voluptuous as vol +import logging + import aiohttp import async_timeout +import voluptuous as vol from homeassistant.components.camera import ( - Camera, PLATFORM_SCHEMA, STATE_IDLE, STATE_RECORDING, + Camera, ) from homeassistant.components.camera.const import DOMAIN -from homeassistant.core import callback from homeassistant.const import CONF_NAME, CONF_TIMEOUT, CONF_WEBHOOK_ID +from homeassistant.core import callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.event import async_track_point_in_utc_time import homeassistant.util.dt as dt_util diff --git a/homeassistant/components/pushover/notify.py b/homeassistant/components/pushover/notify.py index 3f78897838d..064ad91b6b9 100644 --- a/homeassistant/components/pushover/notify.py +++ b/homeassistant/components/pushover/notify.py @@ -1,12 +1,9 @@ """Pushover platform for notify component.""" import logging +from pushover import Client, InitError, RequestError import requests import voluptuous as vol -from pushover import InitError, Client, RequestError - -from homeassistant.const import CONF_API_KEY -import homeassistant.helpers.config_validation as cv from homeassistant.components.notify import ( ATTR_DATA, @@ -16,6 +13,8 @@ from homeassistant.components.notify import ( PLATFORM_SCHEMA, BaseNotificationService, ) +from homeassistant.const import CONF_API_KEY +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/pushsafer/notify.py b/homeassistant/components/pushsafer/notify.py index 758a3390286..436191ab864 100644 --- a/homeassistant/components/pushsafer/notify.py +++ b/homeassistant/components/pushsafer/notify.py @@ -7,8 +7,6 @@ import requests from requests.auth import HTTPBasicAuth import voluptuous as vol -import homeassistant.helpers.config_validation as cv - from homeassistant.components.notify import ( ATTR_DATA, ATTR_TARGET, @@ -17,6 +15,7 @@ from homeassistant.components.notify import ( PLATFORM_SCHEMA, BaseNotificationService, ) +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) _RESOURCE = "https://www.pushsafer.com/api" diff --git a/homeassistant/components/pvoutput/sensor.py b/homeassistant/components/pvoutput/sensor.py index 90084ab7999..169086af3fc 100644 --- a/homeassistant/components/pvoutput/sensor.py +++ b/homeassistant/components/pvoutput/sensor.py @@ -1,22 +1,22 @@ """Support for getting collected information from PVOutput.""" -import logging from collections import namedtuple from datetime import timedelta +import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity -from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.components.rest.sensor import RestData +from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - ATTR_TEMPERATURE, - CONF_API_KEY, - CONF_NAME, ATTR_DATE, + ATTR_TEMPERATURE, ATTR_TIME, ATTR_VOLTAGE, + CONF_API_KEY, + CONF_NAME, ) +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) _ENDPOINT = "http://pvoutput.org/service/r2/getstatus.jsp" diff --git a/homeassistant/components/pyload/sensor.py b/homeassistant/components/pyload/sensor.py index 8ffe1ece4a2..fd4461e3e1b 100644 --- a/homeassistant/components/pyload/sensor.py +++ b/homeassistant/components/pyload/sensor.py @@ -8,14 +8,14 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_SSL, CONF_HOST, + CONF_MONITORED_VARIABLES, CONF_NAME, - CONF_PORT, CONF_PASSWORD, + CONF_PORT, + CONF_SSL, CONF_USERNAME, CONTENT_TYPE_JSON, - CONF_MONITORED_VARIABLES, ) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity diff --git a/tests/components/persistent_notification/test_init.py b/tests/components/persistent_notification/test_init.py index 286d8a701ed..daf671bd52b 100644 --- a/tests/components/persistent_notification/test_init.py +++ b/tests/components/persistent_notification/test_init.py @@ -1,7 +1,7 @@ """The tests for the persistent notification component.""" -from homeassistant.components.websocket_api.const import TYPE_RESULT -from homeassistant.setup import setup_component, async_setup_component import homeassistant.components.persistent_notification as pn +from homeassistant.components.websocket_api.const import TYPE_RESULT +from homeassistant.setup import async_setup_component, setup_component from tests.common import get_test_home_assistant diff --git a/tests/components/plant/test_init.py b/tests/components/plant/test_init.py index fdbf43618ae..fb919d56607 100644 --- a/tests/components/plant/test_init.py +++ b/tests/components/plant/test_init.py @@ -1,23 +1,23 @@ """Unit tests for platform/plant.py.""" import asyncio -import unittest -import pytest from datetime import datetime, timedelta +import unittest + +import pytest -from homeassistant.const import ( - ATTR_UNIT_OF_MEASUREMENT, - STATE_UNAVAILABLE, - STATE_UNKNOWN, - STATE_PROBLEM, - STATE_OK, -) from homeassistant.components import recorder import homeassistant.components.plant as plant +from homeassistant.const import ( + ATTR_UNIT_OF_MEASUREMENT, + STATE_OK, + STATE_PROBLEM, + STATE_UNAVAILABLE, + STATE_UNKNOWN, +) from homeassistant.setup import setup_component from tests.common import get_test_home_assistant, init_recorder_component - GOOD_DATA = { "moisture": 50, "battery": 90, diff --git a/tests/components/prometheus/test_init.py b/tests/components/prometheus/test_init.py index cf1ff7489f6..dd5b673e844 100644 --- a/tests/components/prometheus/test_init.py +++ b/tests/components/prometheus/test_init.py @@ -1,14 +1,14 @@ """The tests for the Prometheus exporter.""" import asyncio -import pytest -from homeassistant.const import ENERGY_KILO_WATT_HOUR, DEVICE_CLASS_POWER +import pytest from homeassistant import setup from homeassistant.components import climate, sensor from homeassistant.components.demo.sensor import DemoSensor -from homeassistant.setup import async_setup_component import homeassistant.components.prometheus as prometheus +from homeassistant.const import DEVICE_CLASS_POWER, ENERGY_KILO_WATT_HOUR +from homeassistant.setup import async_setup_component @pytest.fixture diff --git a/tests/components/proximity/test_init.py b/tests/components/proximity/test_init.py index 41e9ea98ebd..a01d625d8c4 100644 --- a/tests/components/proximity/test_init.py +++ b/tests/components/proximity/test_init.py @@ -3,8 +3,8 @@ import unittest from homeassistant.components import proximity from homeassistant.components.proximity import DOMAIN - from homeassistant.setup import setup_component + from tests.common import get_test_home_assistant diff --git a/tests/components/ps4/test_init.py b/tests/components/ps4/test_init.py index 5b6d6f87cd5..028c1643ff0 100644 --- a/tests/components/ps4/test_init.py +++ b/tests/components/ps4/test_init.py @@ -26,8 +26,9 @@ from homeassistant.const import ( CONF_TOKEN, ) from homeassistant.exceptions import HomeAssistantError -from homeassistant.util import location from homeassistant.setup import async_setup_component +from homeassistant.util import location + from tests.common import MockConfigEntry, mock_coro, mock_registry MOCK_HOST = "192.168.0.1" diff --git a/tests/components/ps4/test_media_player.py b/tests/components/ps4/test_media_player.py index e2b9e382dc4..56a659aa152 100644 --- a/tests/components/ps4/test_media_player.py +++ b/tests/components/ps4/test_media_player.py @@ -29,13 +29,13 @@ from homeassistant.const import ( CONF_REGION, CONF_TOKEN, STATE_IDLE, - STATE_STANDBY, STATE_PLAYING, + STATE_STANDBY, STATE_UNKNOWN, ) from homeassistant.setup import async_setup_component -from tests.common import MockConfigEntry, mock_device_registry, mock_registry, mock_coro +from tests.common import MockConfigEntry, mock_coro, mock_device_registry, mock_registry MOCK_CREDS = "123412341234abcd12341234abcd12341234abcd12341234abcd12341234abcd" MOCK_NAME = "ha_ps4_name" diff --git a/tests/components/ptvsd/test_ptvsd.py b/tests/components/ptvsd/test_ptvsd.py index e7d9c97be22..d4a2aa1ab94 100644 --- a/tests/components/ptvsd/test_ptvsd.py +++ b/tests/components/ptvsd/test_ptvsd.py @@ -1,12 +1,13 @@ """Tests for PTVSD Debugger.""" from unittest.mock import patch + from asynctest import CoroutineMock from pytest import mark +from homeassistant.bootstrap import _async_set_up_integrations import homeassistant.components.ptvsd as ptvsd_component from homeassistant.setup import async_setup_component -from homeassistant.bootstrap import _async_set_up_integrations @mark.skip("causes code cover to fail") diff --git a/tests/components/push/test_camera.py b/tests/components/push/test_camera.py index c48f0c4322a..b5803b96889 100644 --- a/tests/components/push/test_camera.py +++ b/tests/components/push/test_camera.py @@ -1,7 +1,6 @@ """The tests for generic camera component.""" -import io - from datetime import timedelta +import io from homeassistant import core as ha from homeassistant.setup import async_setup_component diff --git a/tests/components/python_script/test_init.py b/tests/components/python_script/test_init.py index d7732c00f94..75616c7400a 100644 --- a/tests/components/python_script/test_init.py +++ b/tests/components/python_script/test_init.py @@ -1,11 +1,11 @@ """Test the python_script component.""" import asyncio import logging -from unittest.mock import patch, mock_open +from unittest.mock import mock_open, patch +from homeassistant.components.python_script import DOMAIN, FOLDER, execute from homeassistant.helpers.service import async_get_all_descriptions from homeassistant.setup import async_setup_component -from homeassistant.components.python_script import DOMAIN, execute, FOLDER from tests.common import patch_yaml_files From 23b92b2a562d528dc8333f5cda59496f73016039 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 14:38:01 +0100 Subject: [PATCH 216/677] Sort imports according to PEP8 for components starting with "S" (#29777) --- homeassistant/components/saj/sensor.py | 2 +- .../components/samsungtv/media_player.py | 6 +++--- homeassistant/components/scrape/sensor.py | 14 ++++++------- homeassistant/components/script/__init__.py | 21 +++++++++---------- homeassistant/components/sendgrid/notify.py | 18 +++++++--------- homeassistant/components/sensibo/climate.py | 4 ++-- .../components/shell_command/__init__.py | 2 +- homeassistant/components/sigfox/sensor.py | 4 ++-- homeassistant/components/simulated/sensor.py | 2 +- homeassistant/components/sinch/notify.py | 10 ++++----- .../components/sky_hub/device_tracker.py | 2 +- homeassistant/components/slide/__init__.py | 15 ++++++------- homeassistant/components/slide/cover.py | 9 ++++---- homeassistant/components/smtp/notify.py | 15 +++++++------ homeassistant/components/snips/__init__.py | 6 +++--- homeassistant/components/snmp/switch.py | 1 - .../components/solaredge_local/sensor.py | 10 ++++----- homeassistant/components/solax/sensor.py | 7 +++---- .../components/songpal/media_player.py | 14 ++++++------- homeassistant/components/splunk/__init__.py | 4 ++-- .../components/squeezebox/media_player.py | 2 +- homeassistant/components/ssdp/__init__.py | 2 +- homeassistant/components/suez_water/sensor.py | 5 ++--- homeassistant/components/sun/__init__.py | 5 ++--- .../components/supervisord/sensor.py | 2 +- .../components/synology_chat/notify.py | 5 ++--- .../components/samsungtv/test_media_player.py | 10 ++++----- tests/components/script/test_init.py | 9 ++++---- tests/components/season/test_sensor.py | 5 ++--- .../components/seventeentrack/test_sensor.py | 5 +++-- tests/components/shell_command/test_init.py | 4 ++-- tests/components/sigfox/test_sensor.py | 4 +++- .../components/simplisafe/test_config_flow.py | 4 ++-- tests/components/simulated/test_sensor.py | 8 +++---- .../smartthings/test_config_flow.py | 2 +- tests/components/smartthings/test_init.py | 2 +- tests/components/smhi/test_config_flow.py | 6 +++--- tests/components/smhi/test_weather.py | 17 +++++++-------- tests/components/smtp/test_notify.py | 2 +- tests/components/snips/test_init.py | 3 ++- .../components/solaredge/test_config_flow.py | 7 ++++--- tests/components/sonarr/test_sensor.py | 4 ++-- .../soundtouch/test_media_player.py | 4 +++- tests/components/spaceapi/test_init.py | 3 ++- tests/components/spc/test_init.py | 2 +- tests/components/splunk/test_init.py | 8 +++---- tests/components/ssdp/test_init.py | 4 ++-- tests/components/startca/test_sensor.py | 1 + tests/components/statistics/test_sensor.py | 18 ++++++++-------- tests/components/stt/test_init.py | 2 +- tests/components/sun/test_init.py | 4 ++-- tests/components/switcher_kis/test_init.py | 14 ++++++------- tests/components/system_log/test_init.py | 2 +- 53 files changed, 170 insertions(+), 171 deletions(-) diff --git a/homeassistant/components/saj/sensor.py b/homeassistant/components/saj/sensor.py index 52ae3640a7f..704e9996d2d 100644 --- a/homeassistant/components/saj/sensor.py +++ b/homeassistant/components/saj/sensor.py @@ -9,8 +9,8 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( CONF_HOST, - CONF_PASSWORD, CONF_NAME, + CONF_PASSWORD, CONF_TYPE, CONF_USERNAME, DEVICE_CLASS_POWER, diff --git a/homeassistant/components/samsungtv/media_player.py b/homeassistant/components/samsungtv/media_player.py index 2488d5ab913..56b947ba9ad 100644 --- a/homeassistant/components/samsungtv/media_player.py +++ b/homeassistant/components/samsungtv/media_player.py @@ -3,15 +3,15 @@ import asyncio from datetime import timedelta import socket -from samsungctl import exceptions as samsung_exceptions, Remote as SamsungRemote +from samsungctl import Remote as SamsungRemote, exceptions as samsung_exceptions import voluptuous as vol import wakeonlan from websocket import WebSocketException from homeassistant.components.media_player import ( - MediaPlayerDevice, - PLATFORM_SCHEMA, DEVICE_CLASS_TV, + PLATFORM_SCHEMA, + MediaPlayerDevice, ) from homeassistant.components.media_player.const import ( MEDIA_TYPE_CHANNEL, diff --git a/homeassistant/components/scrape/sensor.py b/homeassistant/components/scrape/sensor.py index 0bfb7351c88..13d99a0cb8f 100644 --- a/homeassistant/components/scrape/sensor.py +++ b/homeassistant/components/scrape/sensor.py @@ -2,27 +2,27 @@ import logging from bs4 import BeautifulSoup -import voluptuous as vol from requests.auth import HTTPBasicAuth, HTTPDigestAuth +import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.components.rest.sensor import RestData +from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( + CONF_AUTHENTICATION, + CONF_HEADERS, CONF_NAME, + CONF_PASSWORD, CONF_RESOURCE, CONF_UNIT_OF_MEASUREMENT, + CONF_USERNAME, CONF_VALUE_TEMPLATE, CONF_VERIFY_SSL, - CONF_USERNAME, - CONF_HEADERS, - CONF_PASSWORD, - CONF_AUTHENTICATION, HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION, ) -from homeassistant.helpers.entity import Entity from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/script/__init__.py b/homeassistant/components/script/__init__.py index cb9cb5194ba..a8d78beae95 100644 --- a/homeassistant/components/script/__init__.py +++ b/homeassistant/components/script/__init__.py @@ -6,23 +6,22 @@ import voluptuous as vol from homeassistant.const import ( ATTR_ENTITY_ID, - SERVICE_TURN_OFF, - SERVICE_TURN_ON, - SERVICE_TOGGLE, - SERVICE_RELOAD, - STATE_ON, + ATTR_NAME, CONF_ALIAS, EVENT_SCRIPT_STARTED, - ATTR_NAME, + SERVICE_RELOAD, + SERVICE_TOGGLE, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_ON, ) -from homeassistant.loader import bind_hass -from homeassistant.helpers.entity import ToggleEntity -from homeassistant.helpers.entity_component import EntityComponent import homeassistant.helpers.config_validation as cv from homeassistant.helpers.config_validation import make_entity_service_schema -from homeassistant.helpers.service import async_set_service_schema - +from homeassistant.helpers.entity import ToggleEntity +from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.script import Script +from homeassistant.helpers.service import async_set_service_schema +from homeassistant.loader import bind_hass _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sendgrid/notify.py b/homeassistant/components/sendgrid/notify.py index f16758a5355..6dbf4d5c2b7 100644 --- a/homeassistant/components/sendgrid/notify.py +++ b/homeassistant/components/sendgrid/notify.py @@ -1,17 +1,8 @@ """SendGrid notification service.""" import logging -import voluptuous as vol - from sendgrid import SendGridAPIClient - -from homeassistant.const import ( - CONF_API_KEY, - CONF_RECIPIENT, - CONF_SENDER, - CONTENT_TYPE_TEXT_PLAIN, -) -import homeassistant.helpers.config_validation as cv +import voluptuous as vol from homeassistant.components.notify import ( ATTR_TITLE, @@ -19,6 +10,13 @@ from homeassistant.components.notify import ( PLATFORM_SCHEMA, BaseNotificationService, ) +from homeassistant.const import ( + CONF_API_KEY, + CONF_RECIPIENT, + CONF_SENDER, + CONTENT_TYPE_TEXT_PLAIN, +) +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensibo/climate.py b/homeassistant/components/sensibo/climate.py index a14bdb49133..2431b223f09 100644 --- a/homeassistant/components/sensibo/climate.py +++ b/homeassistant/components/sensibo/climate.py @@ -5,16 +5,16 @@ import logging import aiohttp import async_timeout -import voluptuous as vol import pysensibo +import voluptuous as vol from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice from homeassistant.components.climate.const import ( - HVAC_MODE_HEAT_COOL, HVAC_MODE_COOL, HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, SUPPORT_FAN_MODE, SUPPORT_SWING_MODE, diff --git a/homeassistant/components/shell_command/__init__.py b/homeassistant/components/shell_command/__init__.py index 42057407814..89a1a20e8e4 100644 --- a/homeassistant/components/shell_command/__init__.py +++ b/homeassistant/components/shell_command/__init__.py @@ -5,8 +5,8 @@ import shlex import voluptuous as vol -from homeassistant.exceptions import TemplateError from homeassistant.core import ServiceCall +from homeassistant.exceptions import TemplateError from homeassistant.helpers import config_validation as cv, template from homeassistant.helpers.typing import ConfigType, HomeAssistantType diff --git a/homeassistant/components/sigfox/sensor.py b/homeassistant/components/sigfox/sensor.py index b890880389c..27e2fe9b563 100644 --- a/homeassistant/components/sigfox/sensor.py +++ b/homeassistant/components/sigfox/sensor.py @@ -1,15 +1,15 @@ """Sensor for SigFox devices.""" -import logging import datetime import json +import logging from urllib.parse import urljoin import requests import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_NAME +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/simulated/sensor.py b/homeassistant/components/simulated/sensor.py index f6ed54e5191..d05448a82c7 100644 --- a/homeassistant/components/simulated/sensor.py +++ b/homeassistant/components/simulated/sensor.py @@ -1,8 +1,8 @@ """Adds a simulated sensor.""" +from datetime import datetime import logging import math from random import Random -from datetime import datetime import voluptuous as vol diff --git a/homeassistant/components/sinch/notify.py b/homeassistant/components/sinch/notify.py index d7d1f242c67..c0092f013c4 100644 --- a/homeassistant/components/sinch/notify.py +++ b/homeassistant/components/sinch/notify.py @@ -1,25 +1,25 @@ """Support for Sinch notifications.""" import logging -import voluptuous as vol from clx.xms.api import MtBatchTextSmsResult from clx.xms.client import Client from clx.xms.exceptions import ( ErrorResponseException, - UnexpectedResponseException, - UnauthorizedException, NotFoundException, + UnauthorizedException, + UnexpectedResponseException, ) +import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.notify import ( - ATTR_MESSAGE, ATTR_DATA, + ATTR_MESSAGE, ATTR_TARGET, PLATFORM_SCHEMA, BaseNotificationService, ) from homeassistant.const import CONF_API_KEY, CONF_SENDER +import homeassistant.helpers.config_validation as cv DOMAIN = "sinch" diff --git a/homeassistant/components/sky_hub/device_tracker.py b/homeassistant/components/sky_hub/device_tracker.py index 109c410c16d..f7760a59eed 100644 --- a/homeassistant/components/sky_hub/device_tracker.py +++ b/homeassistant/components/sky_hub/device_tracker.py @@ -5,13 +5,13 @@ import re import requests import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.device_tracker import ( DOMAIN, PLATFORM_SCHEMA, DeviceScanner, ) from homeassistant.const import CONF_HOST +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) _MAC_REGEX = re.compile(r"(([0-9A-Fa-f]{1,2}\:){5}[0-9A-Fa-f]{1,2})") diff --git a/homeassistant/components/slide/__init__.py b/homeassistant/components/slide/__init__.py index 54154ae863e..49e50e601dd 100644 --- a/homeassistant/components/slide/__init__.py +++ b/homeassistant/components/slide/__init__.py @@ -1,24 +1,25 @@ """Component for the Go Slide API.""" -import logging from datetime import timedelta +import logging -import voluptuous as vol from goslideapi import GoSlideCloud, goslideapi +import voluptuous as vol from homeassistant.const import ( - CONF_USERNAME, CONF_PASSWORD, CONF_SCAN_INTERVAL, - STATE_OPEN, + CONF_USERNAME, STATE_CLOSED, - STATE_OPENING, STATE_CLOSING, + STATE_OPEN, + STATE_OPENING, ) from homeassistant.helpers import config_validation as cv from homeassistant.helpers.discovery import async_load_platform -from homeassistant.helpers.event import async_track_time_interval, async_call_later -from .const import DOMAIN, SLIDES, API, COMPONENT, DEFAULT_RETRY +from homeassistant.helpers.event import async_call_later, async_track_time_interval + +from .const import API, COMPONENT, DEFAULT_RETRY, DOMAIN, SLIDES _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/slide/cover.py b/homeassistant/components/slide/cover.py index 1c4e6da5aac..a567a9bf61b 100644 --- a/homeassistant/components/slide/cover.py +++ b/homeassistant/components/slide/cover.py @@ -2,15 +2,16 @@ import logging -from homeassistant.const import ATTR_ID from homeassistant.components.cover import ( ATTR_POSITION, - STATE_CLOSED, - STATE_OPENING, - STATE_CLOSING, DEVICE_CLASS_CURTAIN, + STATE_CLOSED, + STATE_CLOSING, + STATE_OPENING, CoverDevice, ) +from homeassistant.const import ATTR_ID + from .const import API, DOMAIN, SLIDES _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/smtp/notify.py b/homeassistant/components/smtp/notify.py index d592f25a61d..82b0f96f785 100644 --- a/homeassistant/components/smtp/notify.py +++ b/homeassistant/components/smtp/notify.py @@ -10,6 +10,13 @@ import smtplib import voluptuous as vol +from homeassistant.components.notify import ( + ATTR_DATA, + ATTR_TITLE, + ATTR_TITLE_DEFAULT, + PLATFORM_SCHEMA, + BaseNotificationService, +) from homeassistant.const import ( CONF_PASSWORD, CONF_PORT, @@ -21,14 +28,6 @@ from homeassistant.const import ( import homeassistant.helpers.config_validation as cv import homeassistant.util.dt as dt_util -from homeassistant.components.notify import ( - ATTR_DATA, - ATTR_TITLE, - ATTR_TITLE_DEFAULT, - PLATFORM_SCHEMA, - BaseNotificationService, -) - _LOGGER = logging.getLogger(__name__) ATTR_IMAGES = "images" # optional embedded image file attachments diff --git a/homeassistant/components/snips/__init__.py b/homeassistant/components/snips/__init__.py index 93e445e8ced..65015bd723c 100644 --- a/homeassistant/components/snips/__init__.py +++ b/homeassistant/components/snips/__init__.py @@ -1,13 +1,13 @@ """Support for Snips on-device ASR and NLU.""" +from datetime import timedelta import json import logging -from datetime import timedelta import voluptuous as vol -from homeassistant.core import callback -from homeassistant.helpers import intent, config_validation as cv from homeassistant.components import mqtt +from homeassistant.core import callback +from homeassistant.helpers import config_validation as cv, intent DOMAIN = "snips" CONF_INTENTS = "intents" diff --git a/homeassistant/components/snmp/switch.py b/homeassistant/components/snmp/switch.py index 8d5be1221c4..578b97c801e 100644 --- a/homeassistant/components/snmp/switch.py +++ b/homeassistant/components/snmp/switch.py @@ -2,7 +2,6 @@ import logging from pyasn1.type.univ import Integer - import pysnmp.hlapi.asyncio as hlapi from pysnmp.hlapi.asyncio import ( CommunityData, diff --git a/homeassistant/components/solaredge_local/sensor.py b/homeassistant/components/solaredge_local/sensor.py index 917fb86ddcb..ecf9dfde8b1 100644 --- a/homeassistant/components/solaredge_local/sensor.py +++ b/homeassistant/components/solaredge_local/sensor.py @@ -1,10 +1,10 @@ """Support for SolarEdge-local Monitoring API.""" -import logging -from datetime import timedelta -import statistics from copy import deepcopy +from datetime import timedelta +import logging +import statistics -from requests.exceptions import HTTPError, ConnectTimeout +from requests.exceptions import ConnectTimeout, HTTPError from solaredge_local import SolarEdge import voluptuous as vol @@ -12,8 +12,8 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( CONF_IP_ADDRESS, CONF_NAME, - POWER_WATT, ENERGY_WATT_HOUR, + POWER_WATT, TEMP_CELSIUS, TEMP_FAHRENHEIT, ) diff --git a/homeassistant/components/solax/sensor.py b/homeassistant/components/solax/sensor.py index a5b4547b344..8eb61560e63 100644 --- a/homeassistant/components/solax/sensor.py +++ b/homeassistant/components/solax/sensor.py @@ -1,6 +1,5 @@ """Support for Solax inverter via local API.""" import asyncio - from datetime import timedelta import logging @@ -8,11 +7,11 @@ from solax import real_time_api from solax.inverter import InverterError import voluptuous as vol -from homeassistant.const import TEMP_CELSIUS, CONF_IP_ADDRESS, CONF_PORT -from homeassistant.helpers.entity import Entity -import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT, TEMP_CELSIUS from homeassistant.exceptions import PlatformNotReady +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_time_interval _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/songpal/media_player.py b/homeassistant/components/songpal/media_player.py index 681c97a7710..27a81b2a667 100644 --- a/homeassistant/components/songpal/media_player.py +++ b/homeassistant/components/songpal/media_player.py @@ -1,19 +1,19 @@ """Support for Songpal-enabled (Sony) media devices.""" import asyncio -import logging from collections import OrderedDict +import logging -import voluptuous as vol from songpal import ( + ConnectChange, + ContentChange, Device, + PowerChange, SongpalException, VolumeChange, - ContentChange, - PowerChange, - ConnectChange, ) +import voluptuous as vol -from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice from homeassistant.components.media_player.const import ( SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, @@ -25,9 +25,9 @@ from homeassistant.components.media_player.const import ( from homeassistant.const import ( ATTR_ENTITY_ID, CONF_NAME, + EVENT_HOMEASSISTANT_STOP, STATE_OFF, STATE_ON, - EVENT_HOMEASSISTANT_STOP, ) from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/splunk/__init__.py b/homeassistant/components/splunk/__init__.py index c483d7fae87..1d5d39416a3 100644 --- a/homeassistant/components/splunk/__init__.py +++ b/homeassistant/components/splunk/__init__.py @@ -7,12 +7,12 @@ import requests import voluptuous as vol from homeassistant.const import ( - CONF_SSL, - CONF_VERIFY_SSL, CONF_HOST, CONF_NAME, CONF_PORT, + CONF_SSL, CONF_TOKEN, + CONF_VERIFY_SSL, EVENT_STATE_CHANGED, ) from homeassistant.helpers import state as state_helper diff --git a/homeassistant/components/squeezebox/media_player.py b/homeassistant/components/squeezebox/media_player.py index 72a5772a14d..94c497e4db6 100644 --- a/homeassistant/components/squeezebox/media_player.py +++ b/homeassistant/components/squeezebox/media_player.py @@ -38,9 +38,9 @@ from homeassistant.const import ( STATE_PAUSED, STATE_PLAYING, ) +from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.exceptions import PlatformNotReady from homeassistant.util.dt import utcnow from .const import DOMAIN, SERVICE_CALL_METHOD diff --git a/homeassistant/components/ssdp/__init__.py b/homeassistant/components/ssdp/__init__.py index b9a9d4b46c9..1a97b1721fc 100644 --- a/homeassistant/components/ssdp/__init__.py +++ b/homeassistant/components/ssdp/__init__.py @@ -8,8 +8,8 @@ import aiohttp from defusedxml import ElementTree from netdisco import ssdp, util -from homeassistant.helpers.event import async_track_time_interval from homeassistant.generated.ssdp import SSDP +from homeassistant.helpers.event import async_track_time_interval DOMAIN = "ssdp" SCAN_INTERVAL = timedelta(seconds=60) diff --git a/homeassistant/components/suez_water/sensor.py b/homeassistant/components/suez_water/sensor.py index 05f82183e46..bfa529adb34 100644 --- a/homeassistant/components/suez_water/sensor.py +++ b/homeassistant/components/suez_water/sensor.py @@ -2,10 +2,9 @@ from datetime import timedelta import logging -import voluptuous as vol - -from pysuez.client import PySuezError from pysuez import SuezClient +from pysuez.client import PySuezError +import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, VOLUME_LITERS diff --git a/homeassistant/components/sun/__init__.py b/homeassistant/components/sun/__init__.py index e848449e61e..704f9432a0f 100644 --- a/homeassistant/components/sun/__init__.py +++ b/homeassistant/components/sun/__init__.py @@ -1,12 +1,12 @@ """Support for functionality to keep track of the sun.""" -import logging from datetime import timedelta +import logging from homeassistant.const import ( CONF_ELEVATION, + EVENT_CORE_CONFIG_UPDATE, SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET, - EVENT_CORE_CONFIG_UPDATE, ) from homeassistant.core import callback from homeassistant.helpers.entity import Entity @@ -17,7 +17,6 @@ from homeassistant.helpers.sun import ( ) from homeassistant.util import dt as dt_util - # mypy: allow-untyped-calls, allow-untyped-defs, no-check-untyped-defs _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/supervisord/sensor.py b/homeassistant/components/supervisord/sensor.py index e1a816f91ae..c79c09248b4 100644 --- a/homeassistant/components/supervisord/sensor.py +++ b/homeassistant/components/supervisord/sensor.py @@ -6,8 +6,8 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_URL -from homeassistant.helpers.entity import Entity import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/synology_chat/notify.py b/homeassistant/components/synology_chat/notify.py index c67ef79f5d5..3e1aeb4ce13 100644 --- a/homeassistant/components/synology_chat/notify.py +++ b/homeassistant/components/synology_chat/notify.py @@ -5,14 +5,13 @@ import logging import requests import voluptuous as vol -from homeassistant.const import CONF_RESOURCE, CONF_VERIFY_SSL -import homeassistant.helpers.config_validation as cv - from homeassistant.components.notify import ( ATTR_DATA, PLATFORM_SCHEMA, BaseNotificationService, ) +from homeassistant.const import CONF_RESOURCE, CONF_VERIFY_SSL +import homeassistant.helpers.config_validation as cv ATTR_FILE_URL = "file_url" diff --git a/tests/components/samsungtv/test_media_player.py b/tests/components/samsungtv/test_media_player.py index 918e30ef4e7..bb40dc28445 100644 --- a/tests/components/samsungtv/test_media_player.py +++ b/tests/components/samsungtv/test_media_player.py @@ -1,13 +1,12 @@ """Tests for samsungtv component.""" import asyncio -from unittest.mock import call, patch from datetime import timedelta - import logging +from unittest.mock import call, patch + from asynctest import mock import pytest from samsungctl import exceptions -from tests.common import async_fire_time_changed from websocket import WebSocketException from homeassistant.components.media_player import DEVICE_CLASS_TV @@ -17,11 +16,11 @@ from homeassistant.components.media_player.const import ( ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_VOLUME_MUTED, DOMAIN, + MEDIA_TYPE_CHANNEL, + MEDIA_TYPE_URL, SERVICE_PLAY_MEDIA, SERVICE_SELECT_SOURCE, SUPPORT_TURN_ON, - MEDIA_TYPE_CHANNEL, - MEDIA_TYPE_URL, ) from homeassistant.components.samsungtv.const import DOMAIN as SAMSUNGTV_DOMAIN from homeassistant.components.samsungtv.media_player import ( @@ -56,6 +55,7 @@ from homeassistant.helpers.discovery import async_load_platform from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from tests.common import async_fire_time_changed ENTITY_ID = f"{DOMAIN}.fake" MOCK_CONFIG = { diff --git a/tests/components/script/test_init.py b/tests/components/script/test_init.py index d675034e744..697154c46b2 100644 --- a/tests/components/script/test_init.py +++ b/tests/components/script/test_init.py @@ -1,7 +1,7 @@ """The tests for the Script component.""" # pylint: disable=protected-access import unittest -from unittest.mock import patch, Mock +from unittest.mock import Mock, patch import pytest @@ -10,21 +10,20 @@ from homeassistant.components.script import DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_NAME, + EVENT_SCRIPT_STARTED, SERVICE_RELOAD, SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON, - EVENT_SCRIPT_STARTED, ) from homeassistant.core import Context, callback, split_entity_id +from homeassistant.exceptions import ServiceNotFound from homeassistant.helpers.service import async_get_all_descriptions from homeassistant.loader import bind_hass -from homeassistant.setup import setup_component, async_setup_component -from homeassistant.exceptions import ServiceNotFound +from homeassistant.setup import async_setup_component, setup_component from tests.common import get_test_home_assistant - ENTITY_ID = "script.test" diff --git a/tests/components/season/test_sensor.py b/tests/components/season/test_sensor.py index 9d891fe0155..2acc5f6573f 100644 --- a/tests/components/season/test_sensor.py +++ b/tests/components/season/test_sensor.py @@ -1,14 +1,13 @@ """The tests for the Season sensor platform.""" # pylint: disable=protected-access -import unittest from datetime import datetime +import unittest -from homeassistant.setup import setup_component import homeassistant.components.season.sensor as season +from homeassistant.setup import setup_component from tests.common import get_test_home_assistant - HEMISPHERE_NORTHERN = { "homeassistant": {"latitude": "48.864716", "longitude": "2.349014"}, "sensor": {"platform": "season", "type": "astronomical"}, diff --git a/tests/components/seventeentrack/test_sensor.py b/tests/components/seventeentrack/test_sensor.py index 45ab8a62225..10ec22f8b67 100644 --- a/tests/components/seventeentrack/test_sensor.py +++ b/tests/components/seventeentrack/test_sensor.py @@ -2,17 +2,18 @@ import datetime from typing import Union -import pytest import mock from py17track.package import Package +import pytest from homeassistant.components.seventeentrack.sensor import ( CONF_SHOW_ARCHIVED, CONF_SHOW_DELIVERED, ) -from homeassistant.const import CONF_USERNAME, CONF_PASSWORD +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.setup import async_setup_component from homeassistant.util import utcnow + from tests.common import MockDependency, async_fire_time_changed VALID_CONFIG_MINIMAL = { diff --git a/tests/components/shell_command/test_init.py b/tests/components/shell_command/test_init.py index 13899da9a3e..a54bd9f7787 100644 --- a/tests/components/shell_command/test_init.py +++ b/tests/components/shell_command/test_init.py @@ -2,12 +2,12 @@ import asyncio import os import tempfile -import unittest from typing import Tuple +import unittest from unittest.mock import Mock, patch -from homeassistant.setup import setup_component from homeassistant.components import shell_command +from homeassistant.setup import setup_component from tests.common import get_test_home_assistant diff --git a/tests/components/sigfox/test_sensor.py b/tests/components/sigfox/test_sensor.py index eac1e6c2582..35534a3a126 100644 --- a/tests/components/sigfox/test_sensor.py +++ b/tests/components/sigfox/test_sensor.py @@ -1,14 +1,16 @@ """Tests for the sigfox sensor.""" import re -import requests_mock import unittest +import requests_mock + from homeassistant.components.sigfox.sensor import ( API_URL, CONF_API_LOGIN, CONF_API_PASSWORD, ) from homeassistant.setup import setup_component + from tests.common import get_test_home_assistant TEST_API_LOGIN = "foo" diff --git a/tests/components/simplisafe/test_config_flow.py b/tests/components/simplisafe/test_config_flow.py index c0920a738ee..a7a21c577d6 100644 --- a/tests/components/simplisafe/test_config_flow.py +++ b/tests/components/simplisafe/test_config_flow.py @@ -1,7 +1,7 @@ """Define tests for the SimpliSafe config flow.""" -import json from datetime import timedelta -from unittest.mock import mock_open, patch, MagicMock, PropertyMock +import json +from unittest.mock import MagicMock, PropertyMock, mock_open, patch from homeassistant import data_entry_flow from homeassistant.components.simplisafe import DOMAIN, config_flow diff --git a/tests/components/simulated/test_sensor.py b/tests/components/simulated/test_sensor.py index 14d839ee656..09e77f7b283 100644 --- a/tests/components/simulated/test_sensor.py +++ b/tests/components/simulated/test_sensor.py @@ -1,28 +1,28 @@ """The tests for the simulated sensor.""" import unittest -from tests.common import get_test_home_assistant - from homeassistant.components.simulated.sensor import ( CONF_AMP, CONF_FWHM, CONF_MEAN, CONF_PERIOD, CONF_PHASE, + CONF_RELATIVE_TO_EPOCH, CONF_SEED, CONF_UNIT, - CONF_RELATIVE_TO_EPOCH, DEFAULT_AMP, DEFAULT_FWHM, DEFAULT_MEAN, DEFAULT_NAME, DEFAULT_PHASE, - DEFAULT_SEED, DEFAULT_RELATIVE_TO_EPOCH, + DEFAULT_SEED, ) from homeassistant.const import CONF_FRIENDLY_NAME from homeassistant.setup import setup_component +from tests.common import get_test_home_assistant + class TestSimulatedSensor(unittest.TestCase): """Test the simulated sensor.""" diff --git a/tests/components/smartthings/test_config_flow.py b/tests/components/smartthings/test_config_flow.py index 82a24f38287..f299727b948 100644 --- a/tests/components/smartthings/test_config_flow.py +++ b/tests/components/smartthings/test_config_flow.py @@ -6,7 +6,6 @@ from asynctest import Mock, patch from pysmartthings import APIResponseError from homeassistant import data_entry_flow -from homeassistant.setup import async_setup_component from homeassistant.components.smartthings import smartapp from homeassistant.components.smartthings.config_flow import SmartThingsFlowHandler from homeassistant.components.smartthings.const import ( @@ -16,6 +15,7 @@ from homeassistant.components.smartthings.const import ( CONF_REFRESH_TOKEN, DOMAIN, ) +from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry, mock_coro diff --git a/tests/components/smartthings/test_init.py b/tests/components/smartthings/test_init.py index b8cd65f5a0b..0c9d889d558 100644 --- a/tests/components/smartthings/test_init.py +++ b/tests/components/smartthings/test_init.py @@ -6,7 +6,6 @@ from asynctest import Mock, patch from pysmartthings import InstalledAppStatus, OAuthToken import pytest -from homeassistant.setup import async_setup_component from homeassistant.components import cloud, smartthings from homeassistant.components.smartthings.const import ( CONF_CLOUDHOOK_URL, @@ -20,6 +19,7 @@ from homeassistant.components.smartthings.const import ( ) from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry diff --git a/tests/components/smhi/test_config_flow.py b/tests/components/smhi/test_config_flow.py index e5e1d392419..ceccd75e08d 100644 --- a/tests/components/smhi/test_config_flow.py +++ b/tests/components/smhi/test_config_flow.py @@ -3,10 +3,10 @@ from unittest.mock import Mock, patch from smhi.smhi_lib import Smhi as SmhiApi, SmhiForecastException -from tests.common import mock_coro - -from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE from homeassistant.components.smhi import config_flow +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE + +from tests.common import mock_coro # pylint: disable=protected-access diff --git a/tests/components/smhi/test_weather.py b/tests/components/smhi/test_weather.py index 6cb7d690c1c..92557f9d543 100644 --- a/tests/components/smhi/test_weather.py +++ b/tests/components/smhi/test_weather.py @@ -1,31 +1,30 @@ """Test for the smhi weather entity.""" import asyncio -import logging from datetime import datetime +import logging from unittest.mock import Mock, patch +from homeassistant.components.smhi import weather as weather_smhi +from homeassistant.components.smhi.const import ATTR_SMHI_CLOUDINESS from homeassistant.components.weather import ( ATTR_FORECAST_CONDITION, + ATTR_FORECAST_PRECIPITATION, ATTR_FORECAST_TEMP, + ATTR_FORECAST_TEMP_LOW, ATTR_FORECAST_TIME, - ATTR_WEATHER_TEMPERATURE, + ATTR_WEATHER_ATTRIBUTION, ATTR_WEATHER_HUMIDITY, ATTR_WEATHER_PRESSURE, - ATTR_FORECAST_TEMP_LOW, + ATTR_WEATHER_TEMPERATURE, ATTR_WEATHER_VISIBILITY, - ATTR_WEATHER_ATTRIBUTION, ATTR_WEATHER_WIND_BEARING, ATTR_WEATHER_WIND_SPEED, - ATTR_FORECAST_PRECIPITATION, DOMAIN as WEATHER_DOMAIN, ) -from homeassistant.components.smhi import weather as weather_smhi from homeassistant.const import TEMP_CELSIUS from homeassistant.core import HomeAssistant -from tests.common import load_fixture, MockConfigEntry - -from homeassistant.components.smhi.const import ATTR_SMHI_CLOUDINESS +from tests.common import MockConfigEntry, load_fixture _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/smtp/test_notify.py b/tests/components/smtp/test_notify.py index daef7ef130e..c79633dd02d 100644 --- a/tests/components/smtp/test_notify.py +++ b/tests/components/smtp/test_notify.py @@ -1,11 +1,11 @@ """The tests for the notify smtp platform.""" +import re import unittest from unittest.mock import patch from homeassistant.components.smtp.notify import MailNotificationService from tests.common import get_test_home_assistant -import re class MockSMTP(MailNotificationService): diff --git a/tests/components/snips/test_init.py b/tests/components/snips/test_init.py index fa6fbe0b254..40fb30ddd19 100644 --- a/tests/components/snips/test_init.py +++ b/tests/components/snips/test_init.py @@ -9,11 +9,12 @@ from homeassistant.bootstrap import async_setup_component from homeassistant.components.mqtt import MQTT_PUBLISH_SCHEMA import homeassistant.components.snips as snips from homeassistant.helpers.intent import ServiceIntentHandler, async_register + from tests.common import ( async_fire_mqtt_message, async_mock_intent, - async_mock_service, async_mock_mqtt_component, + async_mock_service, ) diff --git a/tests/components/solaredge/test_config_flow.py b/tests/components/solaredge/test_config_flow.py index c1183147bac..46f40dd80ef 100644 --- a/tests/components/solaredge/test_config_flow.py +++ b/tests/components/solaredge/test_config_flow.py @@ -1,12 +1,13 @@ """Tests for the SolarEdge config flow.""" +from unittest.mock import Mock, patch + import pytest -from requests.exceptions import HTTPError, ConnectTimeout -from unittest.mock import patch, Mock +from requests.exceptions import ConnectTimeout, HTTPError from homeassistant import data_entry_flow from homeassistant.components.solaredge import config_flow from homeassistant.components.solaredge.const import CONF_SITE_ID, DEFAULT_NAME -from homeassistant.const import CONF_NAME, CONF_API_KEY +from homeassistant.const import CONF_API_KEY, CONF_NAME from tests.common import MockConfigEntry diff --git a/tests/components/sonarr/test_sensor.py b/tests/components/sonarr/test_sensor.py index 43a53eeadcc..38382dc70ab 100644 --- a/tests/components/sonarr/test_sensor.py +++ b/tests/components/sonarr/test_sensor.py @@ -1,7 +1,7 @@ """The tests for the Sonarr platform.""" -import unittest -import time from datetime import datetime +import time +import unittest import pytest diff --git a/tests/components/soundtouch/test_media_player.py b/tests/components/soundtouch/test_media_player.py index f9921b5bcb2..8789db1ca1f 100644 --- a/tests/components/soundtouch/test_media_player.py +++ b/tests/components/soundtouch/test_media_player.py @@ -2,10 +2,12 @@ import logging import unittest from unittest import mock -from libsoundtouch.device import SoundTouchDevice as STD, Status, Volume, Preset, Config + +from libsoundtouch.device import Config, Preset, SoundTouchDevice as STD, Status, Volume from homeassistant.components.soundtouch import media_player as soundtouch from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING + from tests.common import get_test_home_assistant diff --git a/tests/components/spaceapi/test_init.py b/tests/components/spaceapi/test_init.py index 58c417831a9..840931d073b 100644 --- a/tests/components/spaceapi/test_init.py +++ b/tests/components/spaceapi/test_init.py @@ -3,11 +3,12 @@ from unittest.mock import patch import pytest -from tests.common import mock_coro from homeassistant.components.spaceapi import DOMAIN, SPACEAPI_VERSION, URL_API_SPACEAPI from homeassistant.setup import async_setup_component +from tests.common import mock_coro + CONFIG = { DOMAIN: { "space": "Home", diff --git a/tests/components/spc/test_init.py b/tests/components/spc/test_init.py index f726a064dd1..f08abac261f 100644 --- a/tests/components/spc/test_init.py +++ b/tests/components/spc/test_init.py @@ -1,5 +1,5 @@ """Tests for Vanderbilt SPC component.""" -from unittest.mock import patch, PropertyMock, Mock +from unittest.mock import Mock, PropertyMock, patch from homeassistant.bootstrap import async_setup_component from homeassistant.components.spc import DATA_API diff --git a/tests/components/splunk/test_init.py b/tests/components/splunk/test_init.py index 1fe4c6061cd..256c78af502 100644 --- a/tests/components/splunk/test_init.py +++ b/tests/components/splunk/test_init.py @@ -3,12 +3,12 @@ import json import unittest from unittest import mock -from homeassistant.setup import setup_component import homeassistant.components.splunk as splunk -from homeassistant.const import STATE_ON, STATE_OFF, EVENT_STATE_CHANGED -from homeassistant.helpers import state as state_helper -import homeassistant.util.dt as dt_util +from homeassistant.const import EVENT_STATE_CHANGED, STATE_OFF, STATE_ON from homeassistant.core import State +from homeassistant.helpers import state as state_helper +from homeassistant.setup import setup_component +import homeassistant.util.dt as dt_util from tests.common import get_test_home_assistant, mock_state_change_event diff --git a/tests/components/ssdp/test_init.py b/tests/components/ssdp/test_init.py index 56b937cf9d9..e224842b5ab 100644 --- a/tests/components/ssdp/test_init.py +++ b/tests/components/ssdp/test_init.py @@ -1,12 +1,12 @@ """Test the SSDP integration.""" import asyncio -from unittest.mock import patch, Mock +from unittest.mock import Mock, patch import aiohttp import pytest -from homeassistant.generated import ssdp as gn_ssdp from homeassistant.components import ssdp +from homeassistant.generated import ssdp as gn_ssdp from tests.common import mock_coro diff --git a/tests/components/startca/test_sensor.py b/tests/components/startca/test_sensor.py index 9ecfed3ce81..ab043c44d11 100644 --- a/tests/components/startca/test_sensor.py +++ b/tests/components/startca/test_sensor.py @@ -1,5 +1,6 @@ """Tests for the Start.ca sensor platform.""" import asyncio + from homeassistant.bootstrap import async_setup_component from homeassistant.components.startca.sensor import StartcaData from homeassistant.helpers.aiohttp_client import async_get_clientsession diff --git a/tests/components/statistics/test_sensor.py b/tests/components/statistics/test_sensor.py index 2a28876f552..6a38ea6c391 100644 --- a/tests/components/statistics/test_sensor.py +++ b/tests/components/statistics/test_sensor.py @@ -1,18 +1,18 @@ """The test for the statistics sensor platform.""" -import unittest +from datetime import datetime, timedelta import statistics +import unittest +from unittest.mock import patch import pytest -from homeassistant.setup import setup_component -from homeassistant.components.statistics.sensor import StatisticsSensor -from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS, STATE_UNKNOWN -from homeassistant.util import dt as dt_util -from tests.common import get_test_home_assistant -from unittest.mock import patch -from datetime import datetime, timedelta -from tests.common import init_recorder_component from homeassistant.components import recorder +from homeassistant.components.statistics.sensor import StatisticsSensor +from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT, STATE_UNKNOWN, TEMP_CELSIUS +from homeassistant.setup import setup_component +from homeassistant.util import dt as dt_util + +from tests.common import get_test_home_assistant, init_recorder_component class TestStatisticsSensor(unittest.TestCase): diff --git a/tests/components/stt/test_init.py b/tests/components/stt/test_init.py index 5627d7d3e53..1e69fa2494a 100644 --- a/tests/components/stt/test_init.py +++ b/tests/components/stt/test_init.py @@ -1,7 +1,7 @@ """Test STT component setup.""" -from homeassistant.setup import async_setup_component from homeassistant.components import stt +from homeassistant.setup import async_setup_component async def test_setup_comp(hass): diff --git a/tests/components/sun/test_init.py b/tests/components/sun/test_init.py index 5346f97308f..e04de7e2578 100644 --- a/tests/components/sun/test_init.py +++ b/tests/components/sun/test_init.py @@ -5,10 +5,10 @@ from unittest.mock import patch from pytest import mark import homeassistant.components.sun as sun -import homeassistant.core as ha -import homeassistant.util.dt as dt_util from homeassistant.const import EVENT_STATE_CHANGED +import homeassistant.core as ha from homeassistant.setup import async_setup_component +import homeassistant.util.dt as dt_util async def test_setting_rising(hass): diff --git a/tests/components/switcher_kis/test_init.py b/tests/components/switcher_kis/test_init.py index ad6bafd0643..9e262fafa7e 100644 --- a/tests/components/switcher_kis/test_init.py +++ b/tests/components/switcher_kis/test_init.py @@ -1,28 +1,26 @@ """Test cases for the switcher_kis component.""" from datetime import timedelta -from typing import Any, Generator, TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Generator from pytest import raises -from homeassistant.const import CONF_ENTITY_ID from homeassistant.components.switcher_kis import ( CONF_AUTO_OFF, - DOMAIN, DATA_DEVICE, + DOMAIN, SERVICE_SET_AUTO_OFF_NAME, SERVICE_SET_AUTO_OFF_SCHEMA, SIGNAL_SWITCHER_DEVICE_UPDATE, ) -from homeassistant.core import callback, Context +from homeassistant.const import CONF_ENTITY_ID +from homeassistant.core import Context, callback +from homeassistant.exceptions import Unauthorized, UnknownUser from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.typing import HomeAssistantType -from homeassistant.exceptions import Unauthorized, UnknownUser from homeassistant.setup import async_setup_component from homeassistant.util import dt -from tests.common import async_mock_service, async_fire_time_changed - from .consts import ( DUMMY_AUTO_OFF_SET, DUMMY_DEVICE_ID, @@ -38,6 +36,8 @@ from .consts import ( SWITCH_ENTITY_ID, ) +from tests.common import async_fire_time_changed, async_mock_service + if TYPE_CHECKING: from tests.common import MockUser from aioswitcher.devices import SwitcherV2Device diff --git a/tests/components/system_log/test_init.py b/tests/components/system_log/test_init.py index 55d69db8fb4..c21d4842b45 100644 --- a/tests/components/system_log/test_init.py +++ b/tests/components/system_log/test_init.py @@ -2,9 +2,9 @@ import logging from unittest.mock import MagicMock, patch -from homeassistant.core import callback from homeassistant.bootstrap import async_setup_component from homeassistant.components import system_log +from homeassistant.core import callback _LOGGER = logging.getLogger("test_logger") BASIC_CONFIG = {"system_log": {"max_entries": 2}} From 485761bbaf8d359cf210156c45b3a86395da2675 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 14:39:51 +0100 Subject: [PATCH 217/677] Sort imports according to PEP8 for components starting with "R" (#29776) --- .../components/radiotherm/climate.py | 20 ++++++++-------- homeassistant/components/reddit/sensor.py | 2 +- .../components/rmvtransport/sensor.py | 4 ++-- homeassistant/components/rpi_camera/camera.py | 8 +++---- homeassistant/components/rpi_gpio/switch.py | 4 ++-- .../components/rss_feed_template/__init__.py | 2 +- homeassistant/components/rtorrent/sensor.py | 8 +++---- .../components/russound_rio/media_player.py | 2 +- .../rainmachine/test_config_flow.py | 2 +- tests/components/reddit/test_sensor.py | 23 +++++++++---------- .../components/remember_the_milk/test_init.py | 4 ++-- tests/components/rest_command/test_init.py | 2 +- tests/components/rmvtransport/test_sensor.py | 1 - 13 files changed, 40 insertions(+), 42 deletions(-) diff --git a/homeassistant/components/radiotherm/climate.py b/homeassistant/components/radiotherm/climate.py index a007dd673ac..5ac64fd64e9 100644 --- a/homeassistant/components/radiotherm/climate.py +++ b/homeassistant/components/radiotherm/climate.py @@ -1,32 +1,32 @@ """Support for Radio Thermostat wifi-enabled home thermostats.""" import logging -import voluptuous as vol import radiotherm +import voluptuous as vol -from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA +from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice from homeassistant.components.climate.const import ( + CURRENT_HVAC_COOL, + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, + FAN_OFF, + FAN_ON, HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF, - FAN_ON, - FAN_OFF, - CURRENT_HVAC_IDLE, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_COOL, - SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE, + SUPPORT_TARGET_TEMPERATURE, ) from homeassistant.const import ( ATTR_TEMPERATURE, CONF_HOST, PRECISION_HALVES, - TEMP_FAHRENHEIT, STATE_ON, + TEMP_FAHRENHEIT, ) -from homeassistant.util import dt as dt_util import homeassistant.helpers.config_validation as cv +from homeassistant.util import dt as dt_util _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/reddit/sensor.py b/homeassistant/components/reddit/sensor.py index 82f622b968e..ed24dfe47df 100644 --- a/homeassistant/components/reddit/sensor.py +++ b/homeassistant/components/reddit/sensor.py @@ -6,7 +6,7 @@ import praw import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_MAXIMUM +from homeassistant.const import CONF_MAXIMUM, CONF_PASSWORD, CONF_USERNAME import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity diff --git a/homeassistant/components/rmvtransport/sensor.py b/homeassistant/components/rmvtransport/sensor.py index 190274518cd..8df1191a420 100644 --- a/homeassistant/components/rmvtransport/sensor.py +++ b/homeassistant/components/rmvtransport/sensor.py @@ -1,14 +1,14 @@ """Support for departure information for Rhein-Main public transport.""" import asyncio -import logging from datetime import timedelta +import logging from RMVtransport import RMVtransport from RMVtransport.rmvtransport import RMVtransportApiConnectionError import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONF_NAME, ATTR_ATTRIBUTION +from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/rpi_camera/camera.py b/homeassistant/components/rpi_camera/camera.py index d486f406e41..bf04e0ef492 100644 --- a/homeassistant/components/rpi_camera/camera.py +++ b/homeassistant/components/rpi_camera/camera.py @@ -1,14 +1,14 @@ """Camera platform that has a Raspberry Pi camera.""" -import os -import subprocess import logging +import os import shutil +import subprocess from tempfile import NamedTemporaryFile import voluptuous as vol -from homeassistant.components.camera import Camera, PLATFORM_SCHEMA -from homeassistant.const import CONF_NAME, CONF_FILE_PATH, EVENT_HOMEASSISTANT_STOP +from homeassistant.components.camera import PLATFORM_SCHEMA, Camera +from homeassistant.const import CONF_FILE_PATH, CONF_NAME, EVENT_HOMEASSISTANT_STOP from homeassistant.helpers import config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/rpi_gpio/switch.py b/homeassistant/components/rpi_gpio/switch.py index e9a46978eaf..03cb9f083ce 100644 --- a/homeassistant/components/rpi_gpio/switch.py +++ b/homeassistant/components/rpi_gpio/switch.py @@ -3,11 +3,11 @@ import logging import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA from homeassistant.components import rpi_gpio +from homeassistant.components.switch import PLATFORM_SCHEMA from homeassistant.const import DEVICE_DEFAULT_NAME -from homeassistant.helpers.entity import ToggleEntity import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import ToggleEntity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/rss_feed_template/__init__.py b/homeassistant/components/rss_feed_template/__init__.py index 440482ac31a..69bc9474267 100644 --- a/homeassistant/components/rss_feed_template/__init__.py +++ b/homeassistant/components/rss_feed_template/__init__.py @@ -1,7 +1,7 @@ """Support to export sensor values via RSS feed.""" from html import escape -from aiohttp import web +from aiohttp import web import voluptuous as vol from homeassistant.components.http import HomeAssistantView diff --git a/homeassistant/components/rtorrent/sensor.py b/homeassistant/components/rtorrent/sensor.py index ed16331e912..4ae272ca9bd 100644 --- a/homeassistant/components/rtorrent/sensor.py +++ b/homeassistant/components/rtorrent/sensor.py @@ -6,14 +6,14 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_URL, - CONF_NAME, CONF_MONITORED_VARIABLES, + CONF_NAME, + CONF_URL, STATE_IDLE, ) -from homeassistant.helpers.entity import Entity -import homeassistant.helpers.config_validation as cv from homeassistant.exceptions import PlatformNotReady +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/russound_rio/media_player.py b/homeassistant/components/russound_rio/media_player.py index fdcd308618a..c954553160e 100644 --- a/homeassistant/components/russound_rio/media_player.py +++ b/homeassistant/components/russound_rio/media_player.py @@ -4,7 +4,7 @@ import logging from russound_rio import Russound import voluptuous as vol -from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, SUPPORT_SELECT_SOURCE, diff --git a/tests/components/rainmachine/test_config_flow.py b/tests/components/rainmachine/test_config_flow.py index cfde5140822..9e43f647301 100644 --- a/tests/components/rainmachine/test_config_flow.py +++ b/tests/components/rainmachine/test_config_flow.py @@ -9,8 +9,8 @@ from homeassistant.const import ( CONF_IP_ADDRESS, CONF_PASSWORD, CONF_PORT, - CONF_SSL, CONF_SCAN_INTERVAL, + CONF_SSL, ) from tests.common import MockConfigEntry, mock_coro diff --git a/tests/components/reddit/test_sensor.py b/tests/components/reddit/test_sensor.py index a421f6f417c..c44c62fe080 100644 --- a/tests/components/reddit/test_sensor.py +++ b/tests/components/reddit/test_sensor.py @@ -5,23 +5,22 @@ from unittest.mock import patch from homeassistant.components.reddit import sensor as reddit_sensor from homeassistant.components.reddit.sensor import ( - DOMAIN, - ATTR_SUBREDDIT, - ATTR_POSTS, - CONF_SORT_BY, - ATTR_ID, - ATTR_URL, - ATTR_TITLE, - ATTR_SCORE, + ATTR_BODY, ATTR_COMMENTS_NUMBER, ATTR_CREATED, - ATTR_BODY, + ATTR_ID, + ATTR_POSTS, + ATTR_SCORE, + ATTR_SUBREDDIT, + ATTR_TITLE, + ATTR_URL, + CONF_SORT_BY, + DOMAIN, ) -from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_MAXIMUM +from homeassistant.const import CONF_MAXIMUM, CONF_PASSWORD, CONF_USERNAME from homeassistant.setup import setup_component -from tests.common import get_test_home_assistant, MockDependency - +from tests.common import MockDependency, get_test_home_assistant VALID_CONFIG = { "sensor": { diff --git a/tests/components/remember_the_milk/test_init.py b/tests/components/remember_the_milk/test_init.py index f27e41451a3..e09e1e01dab 100644 --- a/tests/components/remember_the_milk/test_init.py +++ b/tests/components/remember_the_milk/test_init.py @@ -1,9 +1,9 @@ """Tests for the Remember The Milk component.""" -import logging import json +import logging import unittest -from unittest.mock import patch, mock_open, Mock +from unittest.mock import Mock, mock_open, patch import homeassistant.components.remember_the_milk as rtm diff --git a/tests/components/rest_command/test_init.py b/tests/components/rest_command/test_init.py index ba63091041d..0aee8ccfbcc 100644 --- a/tests/components/rest_command/test_init.py +++ b/tests/components/rest_command/test_init.py @@ -6,7 +6,7 @@ import aiohttp import homeassistant.components.rest_command as rc from homeassistant.setup import setup_component -from tests.common import get_test_home_assistant, assert_setup_component +from tests.common import assert_setup_component, get_test_home_assistant class TestRestCommandSetup: diff --git a/tests/components/rmvtransport/test_sensor.py b/tests/components/rmvtransport/test_sensor.py index 7cb94b281d1..b34ba3d1229 100644 --- a/tests/components/rmvtransport/test_sensor.py +++ b/tests/components/rmvtransport/test_sensor.py @@ -6,7 +6,6 @@ from homeassistant.setup import async_setup_component from tests.common import mock_coro - VALID_CONFIG_MINIMAL = { "sensor": {"platform": "rmvtransport", "next_departure": [{"station": "3000010"}]} } From de915e1bf0a0378dfb6149beaba437e61216ac49 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 14:41:48 +0100 Subject: [PATCH 218/677] Sort imports according to PEP8 for components starting with "T" (#29778) --- homeassistant/components/tcp/sensor.py | 8 ++--- homeassistant/components/teksavvy/sensor.py | 2 +- homeassistant/components/telegram/notify.py | 3 +- .../components/telegram_bot/__init__.py | 8 ++--- .../components/telegram_bot/polling.py | 4 +-- homeassistant/components/telnet/switch.py | 2 +- .../components/thomson/device_tracker.py | 2 +- .../components/tile/device_tracker.py | 4 +-- homeassistant/components/time_date/sensor.py | 6 ++-- homeassistant/components/todoist/calendar.py | 2 +- .../components/tomato/device_tracker.py | 6 ++-- homeassistant/components/torque/sensor.py | 4 +-- .../components/totalconnect/__init__.py | 7 ++-- homeassistant/components/tplink/__init__.py | 2 +- .../components/trafikverket_train/sensor.py | 2 +- .../components/transmission/__init__.py | 2 +- .../components/transmission/sensor.py | 1 - .../components/transport_nsw/sensor.py | 6 ++-- homeassistant/components/twitter/notify.py | 7 ++-- tests/components/teksavvy/test_sensor.py | 1 + .../threshold/test_binary_sensor.py | 2 +- tests/components/timer/test_init.py | 32 +++++++++---------- .../components/timer/test_reproduce_state.py | 1 + tests/components/tod/test_binary_sensor.py | 12 ++++--- .../components/tomato/test_device_tracker.py | 5 +-- tests/components/tradfri/test_config_flow.py | 2 +- tests/components/tradfri/test_light.py | 3 +- tests/components/trend/test_binary_sensor.py | 2 +- tests/components/tts/test_init.py | 14 ++++---- tests/components/twilio/test_init.py | 1 + 30 files changed, 77 insertions(+), 76 deletions(-) diff --git a/homeassistant/components/tcp/sensor.py b/homeassistant/components/tcp/sensor.py index a387b3fc0bb..2732f2d6bd1 100644 --- a/homeassistant/components/tcp/sensor.py +++ b/homeassistant/components/tcp/sensor.py @@ -1,23 +1,23 @@ """Support for TCP socket based sensors.""" import logging -import socket import select +import socket import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_NAME, CONF_HOST, - CONF_PORT, + CONF_NAME, CONF_PAYLOAD, + CONF_PORT, CONF_TIMEOUT, CONF_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE, ) from homeassistant.exceptions import TemplateError -from homeassistant.helpers.entity import Entity import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/teksavvy/sensor.py b/homeassistant/components/teksavvy/sensor.py index dc8b16b8ce1..fe183129eaa 100644 --- a/homeassistant/components/teksavvy/sensor.py +++ b/homeassistant/components/teksavvy/sensor.py @@ -1,8 +1,8 @@ """Support for TekSavvy Bandwidth Monitor.""" from datetime import timedelta import logging -import async_timeout +import async_timeout import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA diff --git a/homeassistant/components/telegram/notify.py b/homeassistant/components/telegram/notify.py index 23c36e3bafa..ceb660d9e1d 100644 --- a/homeassistant/components/telegram/notify.py +++ b/homeassistant/components/telegram/notify.py @@ -3,8 +3,6 @@ import logging import voluptuous as vol -from homeassistant.const import ATTR_LOCATION - from homeassistant.components.notify import ( ATTR_DATA, ATTR_MESSAGE, @@ -13,6 +11,7 @@ from homeassistant.components.notify import ( PLATFORM_SCHEMA, BaseNotificationService, ) +from homeassistant.const import ATTR_LOCATION _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/telegram_bot/__init__.py b/homeassistant/components/telegram_bot/__init__.py index 12bde6c72d8..b91c37b35de 100644 --- a/homeassistant/components/telegram_bot/__init__.py +++ b/homeassistant/components/telegram_bot/__init__.py @@ -1,8 +1,8 @@ """Support to send and receive Telegram messages.""" -import io -from ipaddress import ip_network from functools import partial import importlib +import io +from ipaddress import ip_network import logging import requests @@ -26,11 +26,11 @@ from homeassistant.const import ( CONF_API_KEY, CONF_PLATFORM, CONF_TIMEOUT, - HTTP_DIGEST_AUTHENTICATION, CONF_URL, + HTTP_DIGEST_AUTHENTICATION, ) -import homeassistant.helpers.config_validation as cv from homeassistant.exceptions import TemplateError +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/telegram_bot/polling.py b/homeassistant/components/telegram_bot/polling.py index cf3d13d5edc..8bdeef25118 100644 --- a/homeassistant/components/telegram_bot/polling.py +++ b/homeassistant/components/telegram_bot/polling.py @@ -2,8 +2,8 @@ import logging from telegram import Update -from telegram.error import TelegramError, TimedOut, NetworkError, RetryAfter -from telegram.ext import Updater, Handler +from telegram.error import NetworkError, RetryAfter, TelegramError, TimedOut +from telegram.ext import Handler, Updater from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP from homeassistant.core import callback diff --git a/homeassistant/components/telnet/switch.py b/homeassistant/components/telnet/switch.py index 87fb70bb888..a99fe044c46 100644 --- a/homeassistant/components/telnet/switch.py +++ b/homeassistant/components/telnet/switch.py @@ -16,9 +16,9 @@ from homeassistant.const import ( CONF_COMMAND_STATE, CONF_NAME, CONF_PORT, - CONF_TIMEOUT, CONF_RESOURCE, CONF_SWITCHES, + CONF_TIMEOUT, CONF_VALUE_TEMPLATE, ) import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/thomson/device_tracker.py b/homeassistant/components/thomson/device_tracker.py index 214c1b8cfb4..1f3fda6cc72 100644 --- a/homeassistant/components/thomson/device_tracker.py +++ b/homeassistant/components/thomson/device_tracker.py @@ -5,13 +5,13 @@ import telnetlib import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.device_tracker import ( DOMAIN, PLATFORM_SCHEMA, DeviceScanner, ) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/tile/device_tracker.py b/homeassistant/components/tile/device_tracker.py index 1cb88f67c2f..8bc4fb11cdf 100644 --- a/homeassistant/components/tile/device_tracker.py +++ b/homeassistant/components/tile/device_tracker.py @@ -1,13 +1,13 @@ """Support for Tile® Bluetooth trackers.""" -import logging from datetime import timedelta +import logging from pytile import async_login from pytile.errors import SessionExpiredError, TileError import voluptuous as vol from homeassistant.components.device_tracker import PLATFORM_SCHEMA -from homeassistant.const import CONF_USERNAME, CONF_MONITORED_VARIABLES, CONF_PASSWORD +from homeassistant.const import CONF_MONITORED_VARIABLES, CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers import aiohttp_client, config_validation as cv from homeassistant.helpers.event import async_track_time_interval from homeassistant.util import slugify diff --git a/homeassistant/components/time_date/sensor.py b/homeassistant/components/time_date/sensor.py index cbe4c85ace3..9edbdd3a9a1 100644 --- a/homeassistant/components/time_date/sensor.py +++ b/homeassistant/components/time_date/sensor.py @@ -4,13 +4,13 @@ import logging import voluptuous as vol -from homeassistant.core import callback from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_DISPLAY_OPTIONS -from homeassistant.helpers.entity import Entity +from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -import homeassistant.util.dt as dt_util +from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_point_in_utc_time +import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/todoist/calendar.py b/homeassistant/components/todoist/calendar.py index eabec37a053..ed6476af229 100644 --- a/homeassistant/components/todoist/calendar.py +++ b/homeassistant/components/todoist/calendar.py @@ -16,8 +16,8 @@ from .const import ( ALL_TASKS, CHECKED, COMPLETED, - CONF_PROJECT_DUE_DATE, CONF_EXTRA_PROJECTS, + CONF_PROJECT_DUE_DATE, CONF_PROJECT_LABEL_WHITELIST, CONF_PROJECT_WHITELIST, CONTENT, diff --git a/homeassistant/components/tomato/device_tracker.py b/homeassistant/components/tomato/device_tracker.py index 57348c9710a..d53b5ab6cf0 100644 --- a/homeassistant/components/tomato/device_tracker.py +++ b/homeassistant/components/tomato/device_tracker.py @@ -6,7 +6,6 @@ import re import requests import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.device_tracker import ( DOMAIN, PLATFORM_SCHEMA, @@ -14,12 +13,13 @@ from homeassistant.components.device_tracker import ( ) from homeassistant.const import ( CONF_HOST, + CONF_PASSWORD, CONF_PORT, CONF_SSL, - CONF_VERIFY_SSL, - CONF_PASSWORD, CONF_USERNAME, + CONF_VERIFY_SSL, ) +import homeassistant.helpers.config_validation as cv CONF_HTTP_ID = "http_id" diff --git a/homeassistant/components/torque/sensor.py b/homeassistant/components/torque/sensor.py index 10161856a47..f084c135e47 100644 --- a/homeassistant/components/torque/sensor.py +++ b/homeassistant/components/torque/sensor.py @@ -4,12 +4,12 @@ import re import voluptuous as vol -from homeassistant.core import callback from homeassistant.components.http import HomeAssistantView from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_EMAIL, CONF_NAME -from homeassistant.helpers.entity import Entity +from homeassistant.core import callback import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/totalconnect/__init__.py b/homeassistant/components/totalconnect/__init__.py index c14ac36057e..020f2d9c07f 100644 --- a/homeassistant/components/totalconnect/__init__.py +++ b/homeassistant/components/totalconnect/__init__.py @@ -1,13 +1,12 @@ """The totalconnect component.""" import logging -import voluptuous as vol from total_connect_client import TotalConnectClient +import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers import discovery from homeassistant.const import CONF_PASSWORD, CONF_USERNAME - +from homeassistant.helpers import discovery +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/tplink/__init__.py b/homeassistant/components/tplink/__init__.py index 7aa261564f3..764060135a2 100644 --- a/homeassistant/components/tplink/__init__.py +++ b/homeassistant/components/tplink/__init__.py @@ -13,8 +13,8 @@ from .common import ( CONF_DIMMER, CONF_DISCOVERY, CONF_LIGHT, - CONF_SWITCH, CONF_STRIP, + CONF_SWITCH, SmartDevices, async_discover_devices, get_static_devices, diff --git a/homeassistant/components/trafikverket_train/sensor.py b/homeassistant/components/trafikverket_train/sensor.py index e6789ca5aee..12f3cf73e50 100644 --- a/homeassistant/components/trafikverket_train/sensor.py +++ b/homeassistant/components/trafikverket_train/sensor.py @@ -11,8 +11,8 @@ from homeassistant.const import ( CONF_API_KEY, CONF_NAME, CONF_WEEKDAY, - WEEKDAYS, DEVICE_CLASS_TIMESTAMP, + WEEKDAYS, ) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/transmission/__init__.py b/homeassistant/components/transmission/__init__.py index 7bbc61a192f..3e6f2407d17 100644 --- a/homeassistant/components/transmission/__init__.py +++ b/homeassistant/components/transmission/__init__.py @@ -22,12 +22,12 @@ from homeassistant.helpers.event import async_track_time_interval from .const import ( ATTR_TORRENT, + DATA_UPDATED, DEFAULT_NAME, DEFAULT_PORT, DEFAULT_SCAN_INTERVAL, DOMAIN, SERVICE_ADD_TORRENT, - DATA_UPDATED, ) from .errors import AuthenticationError, CannotConnect, UnknownError diff --git a/homeassistant/components/transmission/sensor.py b/homeassistant/components/transmission/sensor.py index c51d48eb532..6bedc793ed9 100644 --- a/homeassistant/components/transmission/sensor.py +++ b/homeassistant/components/transmission/sensor.py @@ -8,7 +8,6 @@ from homeassistant.helpers.entity import Entity from .const import DOMAIN, SENSOR_TYPES, STATE_ATTR_TORRENT_INFO - _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/transport_nsw/sensor.py b/homeassistant/components/transport_nsw/sensor.py index 79df41ac489..7c6990de085 100644 --- a/homeassistant/components/transport_nsw/sensor.py +++ b/homeassistant/components/transport_nsw/sensor.py @@ -2,13 +2,13 @@ from datetime import timedelta import logging -import voluptuous as vol from TransportNSW import TransportNSW +import voluptuous as vol +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import ATTR_ATTRIBUTION, ATTR_MODE, CONF_API_KEY, CONF_NAME import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ATTR_MODE, CONF_NAME, CONF_API_KEY, ATTR_ATTRIBUTION _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/twitter/notify.py b/homeassistant/components/twitter/notify.py index 39faf987ae0..768e1ee7316 100644 --- a/homeassistant/components/twitter/notify.py +++ b/homeassistant/components/twitter/notify.py @@ -9,15 +9,14 @@ import os from TwitterAPI import TwitterAPI import voluptuous as vol -from homeassistant.const import CONF_ACCESS_TOKEN, CONF_USERNAME -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.event import async_track_point_in_time - from homeassistant.components.notify import ( ATTR_DATA, PLATFORM_SCHEMA, BaseNotificationService, ) +from homeassistant.const import CONF_ACCESS_TOKEN, CONF_USERNAME +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.event import async_track_point_in_time _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/teksavvy/test_sensor.py b/tests/components/teksavvy/test_sensor.py index 9e2714f0388..723cf07f173 100644 --- a/tests/components/teksavvy/test_sensor.py +++ b/tests/components/teksavvy/test_sensor.py @@ -1,5 +1,6 @@ """Tests for the TekSavvy sensor platform.""" import asyncio + from homeassistant.bootstrap import async_setup_component from homeassistant.components.teksavvy.sensor import TekSavvyData from homeassistant.helpers.aiohttp_client import async_get_clientsession diff --git a/tests/components/threshold/test_binary_sensor.py b/tests/components/threshold/test_binary_sensor.py index 147e2e37cb4..3eb6299b3be 100644 --- a/tests/components/threshold/test_binary_sensor.py +++ b/tests/components/threshold/test_binary_sensor.py @@ -1,8 +1,8 @@ """The test for the threshold sensor platform.""" import unittest -from homeassistant.setup import setup_component from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT, STATE_UNKNOWN, TEMP_CELSIUS +from homeassistant.setup import setup_component from tests.common import get_test_home_assistant diff --git a/tests/components/timer/test_init.py b/tests/components/timer/test_init.py index 5d57cd2f2d8..39648b68fd7 100644 --- a/tests/components/timer/test_init.py +++ b/tests/components/timer/test_init.py @@ -1,31 +1,31 @@ """The tests for the timer component.""" # pylint: disable=protected-access import asyncio -import logging from datetime import timedelta +import logging -from homeassistant.core import CoreState -from homeassistant.setup import async_setup_component from homeassistant.components.timer import ( - DOMAIN, + ATTR_DURATION, CONF_DURATION, + CONF_ICON, CONF_NAME, + DOMAIN, + EVENT_TIMER_CANCELLED, + EVENT_TIMER_FINISHED, + EVENT_TIMER_PAUSED, + EVENT_TIMER_RESTARTED, + EVENT_TIMER_STARTED, + SERVICE_CANCEL, + SERVICE_FINISH, + SERVICE_PAUSE, + SERVICE_START, STATUS_ACTIVE, STATUS_IDLE, STATUS_PAUSED, - CONF_ICON, - ATTR_DURATION, - EVENT_TIMER_FINISHED, - EVENT_TIMER_CANCELLED, - EVENT_TIMER_STARTED, - EVENT_TIMER_RESTARTED, - EVENT_TIMER_PAUSED, - SERVICE_START, - SERVICE_PAUSE, - SERVICE_CANCEL, - SERVICE_FINISH, ) -from homeassistant.const import ATTR_ICON, ATTR_FRIENDLY_NAME, CONF_ENTITY_ID +from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_ICON, CONF_ENTITY_ID +from homeassistant.core import CoreState +from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow from tests.common import async_fire_time_changed diff --git a/tests/components/timer/test_reproduce_state.py b/tests/components/timer/test_reproduce_state.py index 5539d8610c3..80205a40f5d 100644 --- a/tests/components/timer/test_reproduce_state.py +++ b/tests/components/timer/test_reproduce_state.py @@ -9,6 +9,7 @@ from homeassistant.components.timer import ( STATUS_PAUSED, ) from homeassistant.core import State + from tests.common import async_mock_service diff --git a/tests/components/tod/test_binary_sensor.py b/tests/components/tod/test_binary_sensor.py index 2ef361e1dac..03581d16c09 100644 --- a/tests/components/tod/test_binary_sensor.py +++ b/tests/components/tod/test_binary_sensor.py @@ -1,16 +1,18 @@ """Test Times of the Day Binary Sensor.""" +from datetime import datetime, timedelta import unittest from unittest.mock import patch -from datetime import timedelta, datetime + import pytz from homeassistant import setup -import homeassistant.core as ha from homeassistant.const import STATE_OFF, STATE_ON -import homeassistant.util.dt as dt_util -from homeassistant.setup import setup_component -from tests.common import get_test_home_assistant, assert_setup_component +import homeassistant.core as ha from homeassistant.helpers.sun import get_astral_event_date, get_astral_event_next +from homeassistant.setup import setup_component +import homeassistant.util.dt as dt_util + +from tests.common import assert_setup_component, get_test_home_assistant class TestBinarySensorTod(unittest.TestCase): diff --git a/tests/components/tomato/test_device_tracker.py b/tests/components/tomato/test_device_tracker.py index 89ca2a6e1aa..96957805466 100644 --- a/tests/components/tomato/test_device_tracker.py +++ b/tests/components/tomato/test_device_tracker.py @@ -1,5 +1,6 @@ """The tests for the Tomato device tracker platform.""" from unittest import mock + import pytest import requests import requests_mock @@ -9,11 +10,11 @@ from homeassistant.components.device_tracker import DOMAIN import homeassistant.components.tomato.device_tracker as tomato from homeassistant.const import ( CONF_HOST, - CONF_USERNAME, CONF_PASSWORD, + CONF_PLATFORM, CONF_PORT, CONF_SSL, - CONF_PLATFORM, + CONF_USERNAME, CONF_VERIFY_SSL, ) diff --git a/tests/components/tradfri/test_config_flow.py b/tests/components/tradfri/test_config_flow.py index cc5c6be4c72..151607b1ed8 100644 --- a/tests/components/tradfri/test_config_flow.py +++ b/tests/components/tradfri/test_config_flow.py @@ -6,7 +6,7 @@ import pytest from homeassistant import data_entry_flow from homeassistant.components.tradfri import config_flow -from tests.common import mock_coro, MockConfigEntry +from tests.common import MockConfigEntry, mock_coro @pytest.fixture diff --git a/tests/components/tradfri/test_light.py b/tests/components/tradfri/test_light.py index 4c691f66af8..90449120aaf 100644 --- a/tests/components/tradfri/test_light.py +++ b/tests/components/tradfri/test_light.py @@ -1,7 +1,7 @@ """Tradfri lights platform tests.""" from copy import deepcopy -from unittest.mock import Mock, MagicMock, patch, PropertyMock +from unittest.mock import MagicMock, Mock, PropertyMock, patch import pytest from pytradfri.device import Device @@ -12,7 +12,6 @@ from homeassistant.components import tradfri from tests.common import MockConfigEntry - DEFAULT_TEST_FEATURES = { "can_set_dimmer": False, "can_set_color": False, diff --git a/tests/components/trend/test_binary_sensor.py b/tests/components/trend/test_binary_sensor.py index e4b5cf5df6c..d78cf793d2f 100644 --- a/tests/components/trend/test_binary_sensor.py +++ b/tests/components/trend/test_binary_sensor.py @@ -5,7 +5,7 @@ from unittest.mock import patch from homeassistant import setup import homeassistant.util.dt as dt_util -from tests.common import get_test_home_assistant, assert_setup_component +from tests.common import assert_setup_component, get_test_home_assistant class TestTrendBinarySensor: diff --git a/tests/components/tts/test_init.py b/tests/components/tts/test_init.py index 1107aace133..389f9478ad3 100644 --- a/tests/components/tts/test_init.py +++ b/tests/components/tts/test_init.py @@ -2,27 +2,27 @@ import ctypes import os import shutil -from unittest.mock import patch, PropertyMock +from unittest.mock import PropertyMock, patch import pytest import requests -import homeassistant.components.http as http -import homeassistant.components.tts as tts from homeassistant.components.demo.tts import DemoProvider +import homeassistant.components.http as http from homeassistant.components.media_player.const import ( - SERVICE_PLAY_MEDIA, - MEDIA_TYPE_MUSIC, ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, DOMAIN as DOMAIN_MP, + MEDIA_TYPE_MUSIC, + SERVICE_PLAY_MEDIA, ) -from homeassistant.setup import setup_component, async_setup_component +import homeassistant.components.tts as tts +from homeassistant.setup import async_setup_component, setup_component from tests.common import ( + assert_setup_component, get_test_home_assistant, get_test_instance_port, - assert_setup_component, mock_service, ) diff --git a/tests/components/twilio/test_init.py b/tests/components/twilio/test_init.py index 5795967e492..196d0e99991 100644 --- a/tests/components/twilio/test_init.py +++ b/tests/components/twilio/test_init.py @@ -4,6 +4,7 @@ from unittest.mock import patch from homeassistant import data_entry_flow from homeassistant.components import twilio from homeassistant.core import callback + from tests.common import MockDependency From ee1cc3b3dda3268bfe9421b5d623e02b0af078c4 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 14:42:53 +0100 Subject: [PATCH 219/677] Sort imports according to PEP8 for components starting with "V" (#29780) --- homeassistant/components/vasttrafik/sensor.py | 4 ++-- homeassistant/components/venstar/climate.py | 22 +++++++++---------- homeassistant/components/vera/__init__.py | 15 ++++++------- homeassistant/components/version/sensor.py | 12 +++++----- homeassistant/components/vivotek/camera.py | 4 ++-- homeassistant/components/vlc/media_player.py | 5 ++--- .../components/vlc_telnet/media_player.py | 22 +++++++++---------- .../components/volkszaehler/sensor.py | 4 ++-- .../components/volumio/media_player.py | 4 ++-- tests/components/voicerss/test_tts.py | 7 +++--- 10 files changed, 48 insertions(+), 51 deletions(-) diff --git a/homeassistant/components/vasttrafik/sensor.py b/homeassistant/components/vasttrafik/sensor.py index d13383a0832..54fd0a5503e 100644 --- a/homeassistant/components/vasttrafik/sensor.py +++ b/homeassistant/components/vasttrafik/sensor.py @@ -5,9 +5,9 @@ import logging import vasttrafik import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONF_NAME, ATTR_ATTRIBUTION +from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from homeassistant.util.dt import now diff --git a/homeassistant/components/venstar/climate.py b/homeassistant/components/venstar/climate.py index de26d236649..df7dfcacb20 100644 --- a/homeassistant/components/venstar/climate.py +++ b/homeassistant/components/venstar/climate.py @@ -4,27 +4,27 @@ import logging from venstarcolortouch import VenstarColorTouch import voluptuous as vol -from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA +from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice from homeassistant.components.climate.const import ( ATTR_HVAC_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, + CURRENT_HVAC_COOL, + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, + CURRENT_HVAC_OFF, + FAN_AUTO, + FAN_ON, HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_COOL, - CURRENT_HVAC_IDLE, - CURRENT_HVAC_OFF, - SUPPORT_FAN_MODE, - FAN_ON, - FAN_AUTO, - SUPPORT_TARGET_HUMIDITY, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, PRESET_AWAY, PRESET_NONE, + SUPPORT_FAN_MODE, + SUPPORT_PRESET_MODE, + SUPPORT_TARGET_HUMIDITY, + SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_RANGE, ) from homeassistant.const import ( diff --git a/homeassistant/components/vera/__init__.py b/homeassistant/components/vera/__init__.py index 8fcc8a4a2fe..1c9d412d974 100644 --- a/homeassistant/components/vera/__init__.py +++ b/homeassistant/components/vera/__init__.py @@ -1,25 +1,24 @@ """Support for Vera devices.""" -import logging from collections import defaultdict +import logging import pyvera as veraApi -import voluptuous as vol from requests.exceptions import RequestException +import voluptuous as vol -from homeassistant.util.dt import utc_from_timestamp -from homeassistant.util import convert, slugify -from homeassistant.helpers import discovery -from homeassistant.helpers import config_validation as cv from homeassistant.const import ( ATTR_ARMED, ATTR_BATTERY_LEVEL, ATTR_LAST_TRIP_TIME, ATTR_TRIPPED, - EVENT_HOMEASSISTANT_STOP, - CONF_LIGHTS, CONF_EXCLUDE, + CONF_LIGHTS, + EVENT_HOMEASSISTANT_STOP, ) +from homeassistant.helpers import config_validation as cv, discovery from homeassistant.helpers.entity import Entity +from homeassistant.util import convert, slugify +from homeassistant.util.dt import utc_from_timestamp _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/version/sensor.py b/homeassistant/components/version/sensor.py index a6e43251b54..636e564b816 100644 --- a/homeassistant/components/version/sensor.py +++ b/homeassistant/components/version/sensor.py @@ -1,20 +1,20 @@ """Sensor that can display the current Home Assistant versions.""" -import logging from datetime import timedelta +import logging from pyhaversion import ( - LocalVersion, DockerVersion, - HassioVersion, - PyPiVersion, HaIoVersion, + HassioVersion, + LocalVersion, + PyPiVersion, ) import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_NAME, CONF_SOURCE +from homeassistant.helpers.aiohttp_client import async_get_clientsession +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle diff --git a/homeassistant/components/vivotek/camera.py b/homeassistant/components/vivotek/camera.py index 2e604199dd8..665db373440 100644 --- a/homeassistant/components/vivotek/camera.py +++ b/homeassistant/components/vivotek/camera.py @@ -2,9 +2,10 @@ import logging -import voluptuous as vol from libpyvivotek import VivotekCamera +import voluptuous as vol +from homeassistant.components.camera import PLATFORM_SCHEMA, SUPPORT_STREAM, Camera from homeassistant.const import ( CONF_IP_ADDRESS, CONF_NAME, @@ -13,7 +14,6 @@ from homeassistant.const import ( CONF_USERNAME, CONF_VERIFY_SSL, ) -from homeassistant.components.camera import PLATFORM_SCHEMA, SUPPORT_STREAM, Camera from homeassistant.helpers import config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/vlc/media_player.py b/homeassistant/components/vlc/media_player.py index 30b316cb4e8..c7a3d49fabc 100644 --- a/homeassistant/components/vlc/media_player.py +++ b/homeassistant/components/vlc/media_player.py @@ -1,10 +1,10 @@ """Provide functionality to interact with vlc devices on the network.""" import logging -import voluptuous as vol import vlc +import voluptuous as vol -from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, SUPPORT_PAUSE, @@ -18,7 +18,6 @@ from homeassistant.const import CONF_NAME, STATE_IDLE, STATE_PAUSED, STATE_PLAYI import homeassistant.helpers.config_validation as cv import homeassistant.util.dt as dt_util - _LOGGER = logging.getLogger(__name__) CONF_ARGUMENTS = "arguments" diff --git a/homeassistant/components/vlc_telnet/media_player.py b/homeassistant/components/vlc_telnet/media_player.py index 1adb4eaebb3..45b0971ad9f 100644 --- a/homeassistant/components/vlc_telnet/media_player.py +++ b/homeassistant/components/vlc_telnet/media_player.py @@ -1,33 +1,33 @@ """Provide functionality to interact with the vlc telnet interface.""" import logging + +from python_telnet_vlc import ConnectionError as ConnErr, VLCTelnet import voluptuous as vol -from python_telnet_vlc import VLCTelnet, ConnectionError as ConnErr - -from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, + SUPPORT_CLEAR_PLAYLIST, + SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_SEEK, + SUPPORT_SHUFFLE_SET, SUPPORT_STOP, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SEEK, - SUPPORT_NEXT_TRACK, - SUPPORT_CLEAR_PLAYLIST, - SUPPORT_SHUFFLE_SET, ) from homeassistant.const import ( + CONF_HOST, CONF_NAME, + CONF_PASSWORD, + CONF_PORT, STATE_IDLE, STATE_PAUSED, STATE_PLAYING, STATE_UNAVAILABLE, - CONF_HOST, - CONF_PORT, - CONF_PASSWORD, ) import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/volkszaehler/sensor.py b/homeassistant/components/volkszaehler/sensor.py index a2da620c1a5..a418780c165 100644 --- a/homeassistant/components/volkszaehler/sensor.py +++ b/homeassistant/components/volkszaehler/sensor.py @@ -9,11 +9,11 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( CONF_HOST, + CONF_MONITORED_CONDITIONS, CONF_NAME, CONF_PORT, - CONF_MONITORED_CONDITIONS, - POWER_WATT, ENERGY_WATT_HOUR, + POWER_WATT, ) from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession diff --git a/homeassistant/components/volumio/media_player.py b/homeassistant/components/volumio/media_player.py index 7c13488c3f5..b25520c3f38 100644 --- a/homeassistant/components/volumio/media_player.py +++ b/homeassistant/components/volumio/media_player.py @@ -14,7 +14,7 @@ import socket import aiohttp import voluptuous as vol -from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, SUPPORT_CLEAR_PLAYLIST, @@ -25,11 +25,11 @@ from homeassistant.components.media_player.const import ( SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_SELECT_SOURCE, + SUPPORT_SHUFFLE_SET, SUPPORT_STOP, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP, - SUPPORT_SHUFFLE_SET, ) from homeassistant.const import ( CONF_HOST, diff --git a/tests/components/voicerss/test_tts.py b/tests/components/voicerss/test_tts.py index 0f2a0618096..d2a7197fe1a 100644 --- a/tests/components/voicerss/test_tts.py +++ b/tests/components/voicerss/test_tts.py @@ -3,16 +3,15 @@ import asyncio import os import shutil -import homeassistant.components.tts as tts from homeassistant.components.media_player.const import ( - SERVICE_PLAY_MEDIA, ATTR_MEDIA_CONTENT_ID, DOMAIN as DOMAIN_MP, + SERVICE_PLAY_MEDIA, ) +import homeassistant.components.tts as tts from homeassistant.setup import setup_component -from tests.common import get_test_home_assistant, assert_setup_component, mock_service - +from tests.common import assert_setup_component, get_test_home_assistant, mock_service from tests.components.tts.test_init import mutagen_mock # noqa: F401 From 1ab18083074e7388bddec59d4baad4202685b19f Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 14:46:24 +0100 Subject: [PATCH 220/677] Sort imports according to PEP8 for components starting with "N" (#29773) --- .../components/ness_alarm/__init__.py | 6 ++--- .../components/netgear/device_tracker.py | 12 ++++----- .../components/nfandroidtv/notify.py | 5 ++-- .../components/nissan_leaf/__init__.py | 3 ++- homeassistant/components/no_ip/__init__.py | 4 +-- homeassistant/components/notify/__init__.py | 9 +++---- homeassistant/components/nws/weather.py | 12 ++++----- tests/components/namecheapdns/test_init.py | 2 +- tests/components/ness_alarm/test_init.py | 25 ++++++++--------- tests/components/nextbus/test_sensor.py | 7 +++-- tests/components/no_ip/test_init.py | 2 +- tests/components/notion/test_config_flow.py | 3 ++- .../nsw_fuel_station/test_sensor.py | 3 ++- .../test_geo_location.py | 17 ++++++------ tests/components/nws/test_weather.py | 27 +++++++++---------- tests/components/nx584/test_binary_sensor.py | 4 +-- 16 files changed, 70 insertions(+), 71 deletions(-) diff --git a/homeassistant/components/ness_alarm/__init__.py b/homeassistant/components/ness_alarm/__init__.py index 328d3506a97..cc6fad1346d 100644 --- a/homeassistant/components/ness_alarm/__init__.py +++ b/homeassistant/components/ness_alarm/__init__.py @@ -1,7 +1,7 @@ """Support for Ness D8X/D16X devices.""" +from collections import namedtuple import datetime import logging -from collections import namedtuple import voluptuous as vol @@ -9,9 +9,9 @@ from homeassistant.components.binary_sensor import DEVICE_CLASSES from homeassistant.const import ( ATTR_CODE, ATTR_STATE, - EVENT_HOMEASSISTANT_STOP, - CONF_SCAN_INTERVAL, CONF_HOST, + CONF_SCAN_INTERVAL, + EVENT_HOMEASSISTANT_STOP, ) from homeassistant.helpers import config_validation as cv from homeassistant.helpers.discovery import async_load_platform diff --git a/homeassistant/components/netgear/device_tracker.py b/homeassistant/components/netgear/device_tracker.py index 2e20f6423a5..d556e83ca13 100644 --- a/homeassistant/components/netgear/device_tracker.py +++ b/homeassistant/components/netgear/device_tracker.py @@ -4,21 +4,21 @@ import logging from pynetgear import Netgear import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.device_tracker import ( DOMAIN, PLATFORM_SCHEMA, DeviceScanner, ) from homeassistant.const import ( - CONF_HOST, - CONF_PASSWORD, - CONF_USERNAME, - CONF_PORT, - CONF_SSL, CONF_DEVICES, CONF_EXCLUDE, + CONF_HOST, + CONF_PASSWORD, + CONF_PORT, + CONF_SSL, + CONF_USERNAME, ) +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/nfandroidtv/notify.py b/homeassistant/components/nfandroidtv/notify.py index 36eed0a11db..52f0af607bc 100644 --- a/homeassistant/components/nfandroidtv/notify.py +++ b/homeassistant/components/nfandroidtv/notify.py @@ -7,9 +7,6 @@ import requests from requests.auth import HTTPBasicAuth, HTTPDigestAuth import voluptuous as vol -from homeassistant.const import CONF_TIMEOUT, CONF_HOST -import homeassistant.helpers.config_validation as cv - from homeassistant.components.notify import ( ATTR_DATA, ATTR_TITLE, @@ -17,6 +14,8 @@ from homeassistant.components.notify import ( PLATFORM_SCHEMA, BaseNotificationService, ) +from homeassistant.const import CONF_HOST, CONF_TIMEOUT +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/nissan_leaf/__init__.py b/homeassistant/components/nissan_leaf/__init__.py index 0c72f4f43ea..e5b4f34812a 100644 --- a/homeassistant/components/nissan_leaf/__init__.py +++ b/homeassistant/components/nissan_leaf/__init__.py @@ -1,8 +1,9 @@ """Support for the Nissan Leaf Carwings/Nissan Connect API.""" -from datetime import datetime, timedelta import asyncio +from datetime import datetime, timedelta import logging import sys + from pycarwings2 import CarwingsError, Session import voluptuous as vol diff --git a/homeassistant/components/no_ip/__init__.py b/homeassistant/components/no_ip/__init__.py index 70ac7099d30..12d0fb08ad3 100644 --- a/homeassistant/components/no_ip/__init__.py +++ b/homeassistant/components/no_ip/__init__.py @@ -5,11 +5,11 @@ from datetime import timedelta import logging import aiohttp -from aiohttp.hdrs import USER_AGENT, AUTHORIZATION +from aiohttp.hdrs import AUTHORIZATION, USER_AGENT import async_timeout import voluptuous as vol -from homeassistant.const import CONF_DOMAIN, CONF_TIMEOUT, CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import CONF_DOMAIN, CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME from homeassistant.helpers.aiohttp_client import SERVER_SOFTWARE import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/notify/__init__.py b/homeassistant/components/notify/__init__.py index 6ede7f18da7..8211fdc0828 100644 --- a/homeassistant/components/notify/__init__.py +++ b/homeassistant/components/notify/__init__.py @@ -1,20 +1,19 @@ """Provides functionality to notify people.""" import asyncio -import logging from functools import partial +import logging from typing import Optional import voluptuous as vol -from homeassistant.setup import async_prepare_setup_platform -from homeassistant.exceptions import HomeAssistantError -import homeassistant.helpers.config_validation as cv from homeassistant.const import CONF_NAME, CONF_PLATFORM +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_per_platform, discovery +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.setup import async_prepare_setup_platform from homeassistant.util import slugify - # mypy: allow-untyped-defs, no-check-untyped-defs _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/nws/weather.py b/homeassistant/components/nws/weather.py index 23cf84411a3..c22700f1cf8 100644 --- a/homeassistant/components/nws/weather.py +++ b/homeassistant/components/nws/weather.py @@ -9,32 +9,32 @@ from pynws import SimpleNWS import voluptuous as vol from homeassistant.components.weather import ( - WeatherEntity, - PLATFORM_SCHEMA, ATTR_FORECAST_CONDITION, ATTR_FORECAST_TEMP, ATTR_FORECAST_TIME, - ATTR_FORECAST_WIND_SPEED, ATTR_FORECAST_WIND_BEARING, + ATTR_FORECAST_WIND_SPEED, + PLATFORM_SCHEMA, + WeatherEntity, ) from homeassistant.const import ( CONF_API_KEY, - CONF_NAME, CONF_LATITUDE, CONF_LONGITUDE, CONF_MODE, + CONF_NAME, LENGTH_KILOMETERS, LENGTH_METERS, LENGTH_MILES, PRESSURE_HPA, - PRESSURE_PA, PRESSURE_INHG, + PRESSURE_PA, TEMP_CELSIUS, TEMP_FAHRENHEIT, ) from homeassistant.exceptions import PlatformNotReady -from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.util import Throttle from homeassistant.util.distance import convert as convert_distance from homeassistant.util.pressure import convert as convert_pressure diff --git a/tests/components/namecheapdns/test_init.py b/tests/components/namecheapdns/test_init.py index e3ccb48b6da..1e771b11ea8 100644 --- a/tests/components/namecheapdns/test_init.py +++ b/tests/components/namecheapdns/test_init.py @@ -4,8 +4,8 @@ from datetime import timedelta import pytest -from homeassistant.setup import async_setup_component from homeassistant.components import namecheapdns +from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow from tests.common import async_fire_time_changed diff --git a/tests/components/ness_alarm/test_init.py b/tests/components/ness_alarm/test_init.py index 3e5ccdd5b66..31b173f9be6 100644 --- a/tests/components/ness_alarm/test_init.py +++ b/tests/components/ness_alarm/test_init.py @@ -1,36 +1,37 @@ """Tests for the ness_alarm component.""" from enum import Enum +from asynctest import MagicMock, patch import pytest -from asynctest import patch, MagicMock from homeassistant.components import alarm_control_panel from homeassistant.components.ness_alarm import ( - DOMAIN, - CONF_DEVICE_PORT, - CONF_ZONE_NAME, - CONF_ZONES, - CONF_ZONE_ID, - SERVICE_AUX, - SERVICE_PANIC, ATTR_CODE, ATTR_OUTPUT_ID, + CONF_DEVICE_PORT, + CONF_ZONE_ID, + CONF_ZONE_NAME, + CONF_ZONES, + DOMAIN, + SERVICE_AUX, + SERVICE_PANIC, ) from homeassistant.const import ( - STATE_ALARM_ARMING, - SERVICE_ALARM_DISARM, ATTR_ENTITY_ID, + CONF_HOST, SERVICE_ALARM_ARM_AWAY, SERVICE_ALARM_ARM_HOME, + SERVICE_ALARM_DISARM, SERVICE_ALARM_TRIGGER, - STATE_ALARM_DISARMED, STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMING, + STATE_ALARM_DISARMED, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, STATE_UNKNOWN, - CONF_HOST, ) from homeassistant.setup import async_setup_component + from tests.common import MockDependency VALID_CONFIG = { diff --git a/tests/components/nextbus/test_sensor.py b/tests/components/nextbus/test_sensor.py index 4bac317102a..bc74ebcbe1e 100644 --- a/tests/components/nextbus/test_sensor.py +++ b/tests/components/nextbus/test_sensor.py @@ -1,15 +1,14 @@ """The tests for the nexbus sensor component.""" from copy import deepcopy - -import pytest from unittest.mock import patch -import homeassistant.components.sensor as sensor +import pytest + import homeassistant.components.nextbus.sensor as nextbus +import homeassistant.components.sensor as sensor from tests.common import assert_setup_component, async_setup_component - VALID_AGENCY = "sf-muni" VALID_ROUTE = "F" VALID_STOP = "5650" diff --git a/tests/components/no_ip/test_init.py b/tests/components/no_ip/test_init.py index 07b3eabae10..50063ab3fff 100644 --- a/tests/components/no_ip/test_init.py +++ b/tests/components/no_ip/test_init.py @@ -4,8 +4,8 @@ from datetime import timedelta import pytest -from homeassistant.setup import async_setup_component from homeassistant.components import no_ip +from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow from tests.common import async_fire_time_changed diff --git a/tests/components/notion/test_config_flow.py b/tests/components/notion/test_config_flow.py index aa942a8905d..f7651a570cf 100644 --- a/tests/components/notion/test_config_flow.py +++ b/tests/components/notion/test_config_flow.py @@ -1,6 +1,7 @@ """Define tests for the Notion config flow.""" -import aionotion from unittest.mock import patch + +import aionotion import pytest from homeassistant import data_entry_flow diff --git a/tests/components/nsw_fuel_station/test_sensor.py b/tests/components/nsw_fuel_station/test_sensor.py index 4f1753c0518..babdd0cf1c3 100644 --- a/tests/components/nsw_fuel_station/test_sensor.py +++ b/tests/components/nsw_fuel_station/test_sensor.py @@ -4,7 +4,8 @@ from unittest.mock import patch from homeassistant.components import sensor from homeassistant.setup import setup_component -from tests.common import get_test_home_assistant, assert_setup_component + +from tests.common import assert_setup_component, get_test_home_assistant VALID_CONFIG = { "platform": "nsw_fuel_station", diff --git a/tests/components/nsw_rural_fire_service_feed/test_geo_location.py b/tests/components/nsw_rural_fire_service_feed/test_geo_location.py index 274ef3d3743..1915706ca1a 100644 --- a/tests/components/nsw_rural_fire_service_feed/test_geo_location.py +++ b/tests/components/nsw_rural_fire_service_feed/test_geo_location.py @@ -3,22 +3,22 @@ import datetime from unittest.mock import ANY from aio_geojson_nsw_rfs_incidents import NswRuralFireServiceIncidentsFeed -from asynctest.mock import patch, MagicMock, call +from asynctest.mock import MagicMock, call, patch from homeassistant.components import geo_location from homeassistant.components.geo_location import ATTR_SOURCE from homeassistant.components.nsw_rural_fire_service_feed.geo_location import ( - ATTR_EXTERNAL_ID, - SCAN_INTERVAL, ATTR_CATEGORY, + ATTR_COUNCIL_AREA, + ATTR_EXTERNAL_ID, ATTR_FIRE, ATTR_LOCATION, - ATTR_COUNCIL_AREA, + ATTR_PUBLICATION_DATE, + ATTR_RESPONSIBLE_AGENCY, + ATTR_SIZE, ATTR_STATUS, ATTR_TYPE, - ATTR_SIZE, - ATTR_RESPONSIBLE_AGENCY, - ATTR_PUBLICATION_DATE, + SCAN_INTERVAL, ) from homeassistant.const import ( ATTR_ATTRIBUTION, @@ -34,9 +34,10 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP, ) from homeassistant.setup import async_setup_component -from tests.common import assert_setup_component, async_fire_time_changed import homeassistant.util.dt as dt_util +from tests.common import assert_setup_component, async_fire_time_changed + CONFIG = { geo_location.DOMAIN: [{"platform": "nsw_rural_fire_service_feed", CONF_RADIUS: 200}] } diff --git a/tests/components/nws/test_weather.py b/tests/components/nws/test_weather.py index 0e450f06238..adda88a789d 100644 --- a/tests/components/nws/test_weather.py +++ b/tests/components/nws/test_weather.py @@ -1,13 +1,5 @@ """Tests for the NWS weather component.""" from homeassistant.components.nws.weather import ATTR_FORECAST_PRECIP_PROB -from homeassistant.components.weather import ( - ATTR_WEATHER_HUMIDITY, - ATTR_WEATHER_PRESSURE, - ATTR_WEATHER_TEMPERATURE, - ATTR_WEATHER_VISIBILITY, - ATTR_WEATHER_WIND_BEARING, - ATTR_WEATHER_WIND_SPEED, -) from homeassistant.components.weather import ( ATTR_FORECAST, ATTR_FORECAST_CONDITION, @@ -15,25 +7,30 @@ from homeassistant.components.weather import ( ATTR_FORECAST_TIME, ATTR_FORECAST_WIND_BEARING, ATTR_FORECAST_WIND_SPEED, + ATTR_WEATHER_HUMIDITY, + ATTR_WEATHER_PRESSURE, + ATTR_WEATHER_TEMPERATURE, + ATTR_WEATHER_VISIBILITY, + ATTR_WEATHER_WIND_BEARING, + ATTR_WEATHER_WIND_SPEED, ) - from homeassistant.const import ( LENGTH_KILOMETERS, LENGTH_METERS, LENGTH_MILES, + PRESSURE_HPA, PRESSURE_INHG, PRESSURE_PA, - PRESSURE_HPA, TEMP_CELSIUS, TEMP_FAHRENHEIT, ) -from homeassistant.util.pressure import convert as convert_pressure -from homeassistant.util.distance import convert as convert_distance -from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM -from homeassistant.util.temperature import convert as convert_temperature from homeassistant.setup import async_setup_component +from homeassistant.util.distance import convert as convert_distance +from homeassistant.util.pressure import convert as convert_pressure +from homeassistant.util.temperature import convert as convert_temperature +from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM -from tests.common import load_fixture, assert_setup_component +from tests.common import assert_setup_component, load_fixture EXP_OBS_IMP = { ATTR_WEATHER_TEMPERATURE: round( diff --git a/tests/components/nx584/test_binary_sensor.py b/tests/components/nx584/test_binary_sensor.py index 3736d97bbcd..24823e9046a 100644 --- a/tests/components/nx584/test_binary_sensor.py +++ b/tests/components/nx584/test_binary_sensor.py @@ -1,15 +1,15 @@ """The tests for the nx584 sensor platform.""" -import requests import unittest from unittest import mock from nx584 import client as nx584_client +import pytest +import requests from homeassistant.components.nx584 import binary_sensor as nx584 from homeassistant.setup import setup_component from tests.common import get_test_home_assistant -import pytest class StopMe(Exception): From fbf1836997b64c574af24bb78ab2b8abaeaf5c59 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 14:47:53 +0100 Subject: [PATCH 221/677] Sort imports according to PEP8 for components starting with "W" (#29781) --- homeassistant/components/w800rf32/__init__.py | 3 +- .../components/w800rf32/binary_sensor.py | 2 +- homeassistant/components/waqi/sensor.py | 8 ++-- .../components/water_heater/__init__.py | 37 +++++++++---------- .../components/waterfurnace/__init__.py | 7 ++-- homeassistant/components/watson_tts/tts.py | 2 +- homeassistant/components/weather/__init__.py | 1 - homeassistant/components/webhook/__init__.py | 6 +-- homeassistant/components/weblink/__init__.py | 4 +- homeassistant/components/wink/__init__.py | 2 +- homeassistant/components/wink/climate.py | 4 +- .../components/workday/binary_sensor.py | 4 +- homeassistant/components/worldclock/sensor.py | 4 +- .../components/worxlandroid/sensor.py | 8 ++-- homeassistant/components/wsdot/sensor.py | 10 ++--- .../components/wunderground/sensor.py | 16 ++++---- .../components/wunderlist/__init__.py | 2 +- homeassistant/components/wwlln/config_flow.py | 2 +- tests/components/wake_on_lan/test_switch.py | 5 +-- tests/components/water_heater/common.py | 2 +- tests/components/weather/test_weather.py | 12 +++--- tests/components/weblink/test_init.py | 2 +- .../components/workday/test_binary_sensor.py | 3 +- tests/components/worldclock/test_sensor.py | 3 +- tests/components/wsdot/test_sensor.py | 3 +- tests/components/wunderground/test_sensor.py | 7 ++-- 26 files changed, 77 insertions(+), 82 deletions(-) diff --git a/homeassistant/components/w800rf32/__init__.py b/homeassistant/components/w800rf32/__init__.py index 805cca47023..bbd3fdac23d 100644 --- a/homeassistant/components/w800rf32/__init__.py +++ b/homeassistant/components/w800rf32/__init__.py @@ -1,15 +1,14 @@ """Support for w800rf32 devices.""" import logging -import voluptuous as vol import W800rf32 as w800 +import voluptuous as vol from homeassistant.const import ( CONF_DEVICE, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, ) - import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import dispatcher_send diff --git a/homeassistant/components/w800rf32/binary_sensor.py b/homeassistant/components/w800rf32/binary_sensor.py index e08111da8ba..9c83dbce804 100644 --- a/homeassistant/components/w800rf32/binary_sensor.py +++ b/homeassistant/components/w800rf32/binary_sensor.py @@ -1,8 +1,8 @@ """Support for w800rf32 binary sensors.""" import logging -import voluptuous as vol import W800rf32 as w800 +import voluptuous as vol from homeassistant.components.binary_sensor import ( DEVICE_CLASSES_SCHEMA, diff --git a/homeassistant/components/waqi/sensor.py b/homeassistant/components/waqi/sensor.py index b53723a29b6..8f16c216b37 100644 --- a/homeassistant/components/waqi/sensor.py +++ b/homeassistant/components/waqi/sensor.py @@ -1,21 +1,21 @@ """Support for the World Air Quality Index service.""" import asyncio -import logging from datetime import timedelta +import logging import aiohttp import voluptuous as vol from waqiasync import WaqiClient -from homeassistant.exceptions import PlatformNotReady -import homeassistant.helpers.config_validation as cv from homeassistant.const import ( ATTR_ATTRIBUTION, - ATTR_TIME, ATTR_TEMPERATURE, + ATTR_TIME, CONF_TOKEN, ) +from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.config_validation import PLATFORM_SCHEMA from homeassistant.helpers.entity import Entity diff --git a/homeassistant/components/water_heater/__init__.py b/homeassistant/components/water_heater/__init__.py index 6e7b918c289..c5ba009717c 100644 --- a/homeassistant/components/water_heater/__init__.py +++ b/homeassistant/components/water_heater/__init__.py @@ -1,32 +1,31 @@ """Support for water heater devices.""" from datetime import timedelta -import logging import functools as ft +import logging import voluptuous as vol -from homeassistant.helpers.temperature import display_temp as show_temp -from homeassistant.util.temperature import convert as convert_temperature -from homeassistant.helpers.entity_component import EntityComponent -from homeassistant.helpers.entity import Entity +from homeassistant.const import ( + ATTR_ENTITY_ID, + ATTR_TEMPERATURE, + PRECISION_TENTHS, + PRECISION_WHOLE, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, +) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.config_validation import ( # noqa: F401 PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE, ) -import homeassistant.helpers.config_validation as cv -from homeassistant.const import ( - ATTR_ENTITY_ID, - ATTR_TEMPERATURE, - SERVICE_TURN_ON, - SERVICE_TURN_OFF, - STATE_ON, - STATE_OFF, - TEMP_CELSIUS, - PRECISION_WHOLE, - PRECISION_TENTHS, - TEMP_FAHRENHEIT, -) - +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.helpers.temperature import display_temp as show_temp +from homeassistant.util.temperature import convert as convert_temperature # mypy: allow-untyped-defs, no-check-untyped-defs diff --git a/homeassistant/components/waterfurnace/__init__.py b/homeassistant/components/waterfurnace/__init__.py index b6eb22c89ae..942ab8a14ac 100644 --- a/homeassistant/components/waterfurnace/__init__.py +++ b/homeassistant/components/waterfurnace/__init__.py @@ -1,16 +1,15 @@ """Support for Waterfurnaces.""" from datetime import timedelta import logging -import time import threading +import time import voluptuous as vol from waterfurnace.waterfurnace import WaterFurnace, WFCredentialError, WFException -from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, EVENT_HOMEASSISTANT_STOP from homeassistant.core import callback -from homeassistant.helpers import config_validation as cv -from homeassistant.helpers import discovery +from homeassistant.helpers import config_validation as cv, discovery _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/watson_tts/tts.py b/homeassistant/components/watson_tts/tts.py index 520151cf20e..74803464484 100644 --- a/homeassistant/components/watson_tts/tts.py +++ b/homeassistant/components/watson_tts/tts.py @@ -1,8 +1,8 @@ """Support for IBM Watson TTS integration.""" import logging -from ibm_watson import TextToSpeechV1 from ibm_cloud_sdk_core.authenticators import IAMAuthenticator +from ibm_watson import TextToSpeechV1 import voluptuous as vol from homeassistant.components.tts import PLATFORM_SCHEMA, Provider diff --git a/homeassistant/components/weather/__init__.py b/homeassistant/components/weather/__init__.py index bdeedd4cd6b..01ca8ed6790 100644 --- a/homeassistant/components/weather/__init__.py +++ b/homeassistant/components/weather/__init__.py @@ -11,7 +11,6 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.temperature import display_temp as show_temp - # mypy: allow-untyped-defs, no-check-untyped-defs _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/webhook/__init__.py b/homeassistant/components/webhook/__init__.py index 5a41bfa9851..d51771d0b16 100644 --- a/homeassistant/components/webhook/__init__.py +++ b/homeassistant/components/webhook/__init__.py @@ -1,14 +1,14 @@ """Webhooks for Home Assistant.""" import logging -from aiohttp.web import Response, Request +from aiohttp.web import Request, Response import voluptuous as vol -from homeassistant.core import callback -from homeassistant.loader import bind_hass from homeassistant.auth.util import generate_secret from homeassistant.components import websocket_api from homeassistant.components.http.view import HomeAssistantView +from homeassistant.core import callback +from homeassistant.loader import bind_hass _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/weblink/__init__.py b/homeassistant/components/weblink/__init__.py index a6ee72fa147..be6814da30c 100644 --- a/homeassistant/components/weblink/__init__.py +++ b/homeassistant/components/weblink/__init__.py @@ -3,10 +3,10 @@ import logging import voluptuous as vol -from homeassistant.const import CONF_NAME, CONF_ICON, CONF_URL +from homeassistant.const import CONF_ICON, CONF_NAME, CONF_URL +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import slugify -import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/wink/__init__.py b/homeassistant/components/wink/__init__.py index 3bda8b314f2..b3ae01c8f67 100644 --- a/homeassistant/components/wink/__init__.py +++ b/homeassistant/components/wink/__init__.py @@ -6,8 +6,8 @@ import os import time from aiohttp.web import Response -import pywink from pubnubsubhandler import PubNubSubscriptionHandler +import pywink import voluptuous as vol from homeassistant.components.http import HomeAssistantView diff --git a/homeassistant/components/wink/climate.py b/homeassistant/components/wink/climate.py index 6323fa7bbfe..85d477313f1 100644 --- a/homeassistant/components/wink/climate.py +++ b/homeassistant/components/wink/climate.py @@ -23,12 +23,12 @@ from homeassistant.components.climate.const import ( HVAC_MODE_OFF, PRESET_AWAY, PRESET_ECO, + PRESET_NONE, SUPPORT_AUX_HEAT, SUPPORT_FAN_MODE, + SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_RANGE, - SUPPORT_PRESET_MODE, - PRESET_NONE, ) from homeassistant.const import ATTR_TEMPERATURE, PRECISION_TENTHS, TEMP_CELSIUS from homeassistant.helpers.temperature import display_temp as show_temp diff --git a/homeassistant/components/workday/binary_sensor.py b/homeassistant/components/workday/binary_sensor.py index efa8b6ad77b..866d87d6240 100644 --- a/homeassistant/components/workday/binary_sensor.py +++ b/homeassistant/components/workday/binary_sensor.py @@ -1,12 +1,12 @@ """Sensor to indicate whether the current day is a workday.""" -import logging from datetime import datetime, timedelta +import logging import holidays import voluptuous as vol +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice from homeassistant.const import CONF_NAME, WEEKDAYS -from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/worldclock/sensor.py b/homeassistant/components/worldclock/sensor.py index 103c38819bb..1709ac7d23e 100644 --- a/homeassistant/components/worldclock/sensor.py +++ b/homeassistant/components/worldclock/sensor.py @@ -5,9 +5,9 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_NAME, CONF_TIME_ZONE -import homeassistant.util.dt as dt_util -from homeassistant.helpers.entity import Entity import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity +import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/worxlandroid/sensor.py b/homeassistant/components/worxlandroid/sensor.py index dd74c5b7d17..fa2cae53f52 100644 --- a/homeassistant/components/worxlandroid/sensor.py +++ b/homeassistant/components/worxlandroid/sensor.py @@ -1,18 +1,16 @@ """Support for Worx Landroid mower.""" -import logging import asyncio +import logging import aiohttp import async_timeout - import voluptuous as vol -import homeassistant.helpers.config_validation as cv - -from homeassistant.helpers.entity import Entity from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_HOST, CONF_PIN, CONF_TIMEOUT from homeassistant.helpers.aiohttp_client import async_get_clientsession +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/wsdot/sensor.py b/homeassistant/components/wsdot/sensor.py index 2b6c0b73563..5afa3a3efcf 100644 --- a/homeassistant/components/wsdot/sensor.py +++ b/homeassistant/components/wsdot/sensor.py @@ -1,21 +1,21 @@ """Support for Washington State Department of Transportation (WSDOT) data.""" +from datetime import datetime, timedelta, timezone import logging import re -from datetime import datetime, timezone, timedelta import requests import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_API_KEY, - CONF_NAME, ATTR_ATTRIBUTION, - CONF_ID, ATTR_NAME, + CONF_API_KEY, + CONF_ID, + CONF_NAME, ) -from homeassistant.helpers.entity import Entity import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/wunderground/sensor.py b/homeassistant/components/wunderground/sensor.py index 5272b33ccb5..84c870af54d 100644 --- a/homeassistant/components/wunderground/sensor.py +++ b/homeassistant/components/wunderground/sensor.py @@ -9,27 +9,27 @@ import aiohttp import async_timeout import voluptuous as vol -from homeassistant.helpers.typing import HomeAssistantType, ConfigType from homeassistant.components import sensor from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_MONITORED_CONDITIONS, + ATTR_ATTRIBUTION, CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, - TEMP_FAHRENHEIT, - TEMP_CELSIUS, + CONF_MONITORED_CONDITIONS, + LENGTH_FEET, LENGTH_INCHES, LENGTH_KILOMETERS, LENGTH_MILES, - LENGTH_FEET, - ATTR_ATTRIBUTION, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, ) from homeassistant.exceptions import PlatformNotReady -from homeassistant.helpers.entity import Entity from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.typing import ConfigType, HomeAssistantType +from homeassistant.util import Throttle _RESOURCE = "http://api.wunderground.com/api/{}/{}/{}/q/" _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/wunderlist/__init__.py b/homeassistant/components/wunderlist/__init__.py index 122d09feaa4..4d9ff6e2235 100644 --- a/homeassistant/components/wunderlist/__init__.py +++ b/homeassistant/components/wunderlist/__init__.py @@ -4,8 +4,8 @@ import logging import voluptuous as vol from wunderpy2 import WunderApi +from homeassistant.const import CONF_ACCESS_TOKEN, CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.const import CONF_NAME, CONF_ACCESS_TOKEN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/wwlln/config_flow.py b/homeassistant/components/wwlln/config_flow.py index 3e43ba93278..f9cd022f255 100644 --- a/homeassistant/components/wwlln/config_flow.py +++ b/homeassistant/components/wwlln/config_flow.py @@ -2,7 +2,6 @@ import voluptuous as vol from homeassistant import config_entries -from homeassistant.helpers import config_validation as cv from homeassistant.const import ( CONF_LATITUDE, CONF_LONGITUDE, @@ -12,6 +11,7 @@ from homeassistant.const import ( CONF_UNIT_SYSTEM_METRIC, ) from homeassistant.core import callback +from homeassistant.helpers import config_validation as cv from .const import CONF_WINDOW, DEFAULT_RADIUS, DEFAULT_WINDOW, DOMAIN diff --git a/tests/components/wake_on_lan/test_switch.py b/tests/components/wake_on_lan/test_switch.py index 1fbcc9cc273..e0f12f9c7f8 100644 --- a/tests/components/wake_on_lan/test_switch.py +++ b/tests/components/wake_on_lan/test_switch.py @@ -2,14 +2,13 @@ import unittest from unittest.mock import patch -from homeassistant.setup import setup_component -from homeassistant.const import STATE_ON, STATE_OFF import homeassistant.components.switch as switch +from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.setup import setup_component from tests.common import get_test_home_assistant, mock_service from tests.components.switch import common - TEST_STATE = None diff --git a/tests/components/water_heater/common.py b/tests/components/water_heater/common.py index 0808e3e3dac..04fd345577e 100644 --- a/tests/components/water_heater/common.py +++ b/tests/components/water_heater/common.py @@ -9,8 +9,8 @@ from homeassistant.components.water_heater import ( ATTR_OPERATION_MODE, DOMAIN, SERVICE_SET_AWAY_MODE, - SERVICE_SET_TEMPERATURE, SERVICE_SET_OPERATION_MODE, + SERVICE_SET_TEMPERATURE, ) from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, ENTITY_MATCH_ALL from homeassistant.loader import bind_hass diff --git a/tests/components/weather/test_weather.py b/tests/components/weather/test_weather.py index bbfd07fb551..fd960b594a0 100644 --- a/tests/components/weather/test_weather.py +++ b/tests/components/weather/test_weather.py @@ -3,6 +3,11 @@ import unittest from homeassistant.components import weather from homeassistant.components.weather import ( + ATTR_FORECAST, + ATTR_FORECAST_CONDITION, + ATTR_FORECAST_PRECIPITATION, + ATTR_FORECAST_TEMP, + ATTR_FORECAST_TEMP_LOW, ATTR_WEATHER_ATTRIBUTION, ATTR_WEATHER_HUMIDITY, ATTR_WEATHER_OZONE, @@ -10,14 +15,9 @@ from homeassistant.components.weather import ( ATTR_WEATHER_TEMPERATURE, ATTR_WEATHER_WIND_BEARING, ATTR_WEATHER_WIND_SPEED, - ATTR_FORECAST, - ATTR_FORECAST_CONDITION, - ATTR_FORECAST_PRECIPITATION, - ATTR_FORECAST_TEMP, - ATTR_FORECAST_TEMP_LOW, ) -from homeassistant.util.unit_system import METRIC_SYSTEM from homeassistant.setup import setup_component +from homeassistant.util.unit_system import METRIC_SYSTEM from tests.common import get_test_home_assistant diff --git a/tests/components/weblink/test_init.py b/tests/components/weblink/test_init.py index ae519b95ada..5f803107c46 100644 --- a/tests/components/weblink/test_init.py +++ b/tests/components/weblink/test_init.py @@ -1,8 +1,8 @@ """The tests for the weblink component.""" import unittest -from homeassistant.setup import setup_component from homeassistant.components import weblink +from homeassistant.setup import setup_component from tests.common import get_test_home_assistant diff --git a/tests/components/workday/test_binary_sensor.py b/tests/components/workday/test_binary_sensor.py index eeb0707ea50..19da0cbfd87 100644 --- a/tests/components/workday/test_binary_sensor.py +++ b/tests/components/workday/test_binary_sensor.py @@ -5,8 +5,7 @@ from unittest.mock import patch from homeassistant.components.workday.binary_sensor import day_to_string from homeassistant.setup import setup_component -from tests.common import get_test_home_assistant, assert_setup_component - +from tests.common import assert_setup_component, get_test_home_assistant FUNCTION_PATH = "homeassistant.components.workday.binary_sensor.get_date" diff --git a/tests/components/worldclock/test_sensor.py b/tests/components/worldclock/test_sensor.py index 27cf574f7f8..b0e8119035d 100644 --- a/tests/components/worldclock/test_sensor.py +++ b/tests/components/worldclock/test_sensor.py @@ -2,9 +2,10 @@ import unittest from homeassistant.setup import setup_component -from tests.common import get_test_home_assistant import homeassistant.util.dt as dt_util +from tests.common import get_test_home_assistant + class TestWorldClockSensor(unittest.TestCase): """Test the World clock sensor.""" diff --git a/tests/components/wsdot/test_sensor.py b/tests/components/wsdot/test_sensor.py index 96e2c66a163..b548c099f40 100644 --- a/tests/components/wsdot/test_sensor.py +++ b/tests/components/wsdot/test_sensor.py @@ -4,7 +4,6 @@ import re import unittest import requests_mock -from tests.common import get_test_home_assistant, load_fixture import homeassistant.components.wsdot.sensor as wsdot from homeassistant.components.wsdot.sensor import ( @@ -19,6 +18,8 @@ from homeassistant.components.wsdot.sensor import ( ) from homeassistant.setup import setup_component +from tests.common import get_test_home_assistant, load_fixture + class TestWSDOT(unittest.TestCase): """Test the WSDOT platform.""" diff --git a/tests/components/wunderground/test_sensor.py b/tests/components/wunderground/test_sensor.py index 54072e04ab5..70264249157 100644 --- a/tests/components/wunderground/test_sensor.py +++ b/tests/components/wunderground/test_sensor.py @@ -1,14 +1,15 @@ """The tests for the WUnderground platform.""" import asyncio -import aiohttp +import aiohttp from pytest import raises import homeassistant.components.wunderground.sensor as wunderground -from homeassistant.const import TEMP_CELSIUS, LENGTH_INCHES, STATE_UNKNOWN +from homeassistant.const import LENGTH_INCHES, STATE_UNKNOWN, TEMP_CELSIUS from homeassistant.exceptions import PlatformNotReady from homeassistant.setup import async_setup_component -from tests.common import load_fixture, assert_setup_component + +from tests.common import assert_setup_component, load_fixture VALID_CONFIG_PWS = { "platform": "wunderground", From 4035fda659160af9a0ad0675f3a47714357f810c Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 14:56:20 +0100 Subject: [PATCH 222/677] Sort imports according to PEP8 for components starting with "Q" (#29785) --- tests/components/qld_bushfire/test_geo_location.py | 5 +++-- tests/components/qwikswitch/test_init.py | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/components/qld_bushfire/test_geo_location.py b/tests/components/qld_bushfire/test_geo_location.py index 59de4643cb8..86ab16c5f1b 100644 --- a/tests/components/qld_bushfire/test_geo_location.py +++ b/tests/components/qld_bushfire/test_geo_location.py @@ -1,6 +1,6 @@ """The tests for the Queensland Bushfire Alert Feed platform.""" import datetime -from unittest.mock import patch, MagicMock, call +from unittest.mock import MagicMock, call, patch from homeassistant.components import geo_location from homeassistant.components.geo_location import ATTR_SOURCE @@ -25,9 +25,10 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_START, ) from homeassistant.setup import async_setup_component -from tests.common import assert_setup_component, async_fire_time_changed import homeassistant.util.dt as dt_util +from tests.common import assert_setup_component, async_fire_time_changed + CONFIG = {geo_location.DOMAIN: [{"platform": "qld_bushfire", CONF_RADIUS: 200}]} CONFIG_WITH_CUSTOM_LOCATION = { diff --git a/tests/components/qwikswitch/test_init.py b/tests/components/qwikswitch/test_init.py index e573e8cc293..d9c2a8d0ba6 100644 --- a/tests/components/qwikswitch/test_init.py +++ b/tests/components/qwikswitch/test_init.py @@ -1,14 +1,14 @@ """Test qwikswitch sensors.""" import logging +from aiohttp.client_exceptions import ClientError import pytest -from homeassistant.const import EVENT_HOMEASSISTANT_START -from homeassistant.components.qwikswitch import DOMAIN as QWIKSWITCH from homeassistant.bootstrap import async_setup_component -from tests.test_util.aiohttp import mock_aiohttp_client -from aiohttp.client_exceptions import ClientError +from homeassistant.components.qwikswitch import DOMAIN as QWIKSWITCH +from homeassistant.const import EVENT_HOMEASSISTANT_START +from tests.test_util.aiohttp import mock_aiohttp_client _LOGGER = logging.getLogger(__name__) From 9bcd4653e0f51d666081c9762446ffe7eef6f134 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 14:57:42 +0100 Subject: [PATCH 223/677] Sort imports according to PEP8 for components starting with "Y" (#29783) --- .../yale_smart_alarm/alarm_control_panel.py | 8 ++++---- homeassistant/components/yamaha/media_player.py | 5 ++--- .../components/yamaha_musiccast/media_player.py | 4 ++-- homeassistant/components/yandex_transport/sensor.py | 8 ++++---- homeassistant/components/yeelightsunflower/light.py | 12 ++++++------ homeassistant/components/yessssms/notify.py | 7 ++----- homeassistant/components/yr/sensor.py | 11 +++++------ tests/components/yamaha/test_media_player.py | 5 +++-- .../yandex_transport/test_yandex_transport_sensor.py | 6 ++++-- tests/components/yandextts/test_tts.py | 10 +++++----- tests/components/yessssms/test_notify.py | 7 +++---- tests/components/yr/test_sensor.py | 2 +- 12 files changed, 41 insertions(+), 44 deletions(-) diff --git a/homeassistant/components/yale_smart_alarm/alarm_control_panel.py b/homeassistant/components/yale_smart_alarm/alarm_control_panel.py index 7f2cbc2a33d..05823a511dd 100644 --- a/homeassistant/components/yale_smart_alarm/alarm_control_panel.py +++ b/homeassistant/components/yale_smart_alarm/alarm_control_panel.py @@ -3,11 +3,11 @@ import logging import voluptuous as vol from yalesmartalarmclient.client import ( - YaleSmartAlarmClient, - AuthenticationError, - YALE_STATE_DISARM, - YALE_STATE_ARM_PARTIAL, YALE_STATE_ARM_FULL, + YALE_STATE_ARM_PARTIAL, + YALE_STATE_DISARM, + AuthenticationError, + YaleSmartAlarmClient, ) from homeassistant.components.alarm_control_panel import ( diff --git a/homeassistant/components/yamaha/media_player.py b/homeassistant/components/yamaha/media_player.py index fa2c68dce88..7ab7d5b3a47 100644 --- a/homeassistant/components/yamaha/media_player.py +++ b/homeassistant/components/yamaha/media_player.py @@ -5,7 +5,7 @@ import requests import rxv import voluptuous as vol -from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, @@ -13,15 +13,14 @@ from homeassistant.components.media_player.const import ( SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, + SUPPORT_SELECT_SOUND_MODE, SUPPORT_SELECT_SOURCE, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - SUPPORT_SELECT_SOUND_MODE, ) - from homeassistant.const import ( ATTR_ENTITY_ID, CONF_HOST, diff --git a/homeassistant/components/yamaha_musiccast/media_player.py b/homeassistant/components/yamaha_musiccast/media_player.py index 18b80cc4085..ae5b78b9116 100644 --- a/homeassistant/components/yamaha_musiccast/media_player.py +++ b/homeassistant/components/yamaha_musiccast/media_player.py @@ -1,11 +1,11 @@ """Support for Yamaha MusicCast Receivers.""" import logging - import socket + import pymusiccast import voluptuous as vol -from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, diff --git a/homeassistant/components/yandex_transport/sensor.py b/homeassistant/components/yandex_transport/sensor.py index 4bf634a61f4..54db4882e74 100644 --- a/homeassistant/components/yandex_transport/sensor.py +++ b/homeassistant/components/yandex_transport/sensor.py @@ -1,16 +1,16 @@ """Service for obtaining information about closer bus from Transport Yandex Service.""" -import logging from datetime import timedelta +import logging import voluptuous as vol from ya_ma import YandexMapsRequester -import homeassistant.helpers.config_validation as cv -import homeassistant.util.dt as dt_util from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONF_NAME, ATTR_ATTRIBUTION, DEVICE_CLASS_TIMESTAMP +from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME, DEVICE_CLASS_TIMESTAMP +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity +import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/yeelightsunflower/light.py b/homeassistant/components/yeelightsunflower/light.py index 3424014e8f4..c49c874dc21 100644 --- a/homeassistant/components/yeelightsunflower/light.py +++ b/homeassistant/components/yeelightsunflower/light.py @@ -1,19 +1,19 @@ """Support for Yeelight Sunflower color bulbs (not Yeelight Blue or WiFi).""" import logging -import yeelightsunflower import voluptuous as vol +import yeelightsunflower -import homeassistant.helpers.config_validation as cv from homeassistant.components.light import ( - Light, - ATTR_HS_COLOR, - SUPPORT_COLOR, ATTR_BRIGHTNESS, - SUPPORT_BRIGHTNESS, + ATTR_HS_COLOR, PLATFORM_SCHEMA, + SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, + Light, ) from homeassistant.const import CONF_HOST +import homeassistant.helpers.config_validation as cv import homeassistant.util.color as color_util _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/yessssms/notify.py b/homeassistant/components/yessssms/notify.py index 1c1eed0e89d..70c85f7bdb3 100644 --- a/homeassistant/components/yessssms/notify.py +++ b/homeassistant/components/yessssms/notify.py @@ -1,16 +1,13 @@ """Support for the YesssSMS platform.""" import logging +from YesssSMS import YesssSMS import voluptuous as vol -from YesssSMS import YesssSMS - +from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationService from homeassistant.const import CONF_PASSWORD, CONF_RECIPIENT, CONF_USERNAME import homeassistant.helpers.config_validation as cv -from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationService - - from .const import CONF_PROVIDER _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/yr/sensor.py b/homeassistant/components/yr/sensor.py index fc754c9a257..f8fbc97962f 100644 --- a/homeassistant/components/yr/sensor.py +++ b/homeassistant/components/yr/sensor.py @@ -1,23 +1,21 @@ """Support for Yr.no weather service.""" import asyncio import logging - from random import randrange from xml.parsers.expat import ExpatError import aiohttp import async_timeout -import xmltodict import voluptuous as vol +import xmltodict -import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( + ATTR_ATTRIBUTION, + CONF_ELEVATION, CONF_LATITUDE, CONF_LONGITUDE, - CONF_ELEVATION, CONF_MONITORED_CONDITIONS, - ATTR_ATTRIBUTION, CONF_NAME, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_PRESSURE, @@ -26,8 +24,9 @@ from homeassistant.const import ( TEMP_CELSIUS, ) from homeassistant.helpers.aiohttp_client import async_get_clientsession +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -from homeassistant.helpers.event import async_track_utc_time_change, async_call_later +from homeassistant.helpers.event import async_call_later, async_track_utc_time_change from homeassistant.util import dt as dt_util _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/yamaha/test_media_player.py b/tests/components/yamaha/test_media_player.py index 7e5b04f0269..6b101167c85 100644 --- a/tests/components/yamaha/test_media_player.py +++ b/tests/components/yamaha/test_media_player.py @@ -1,10 +1,11 @@ """The tests for the Yamaha Media player platform.""" import unittest -from unittest.mock import patch, MagicMock +from unittest.mock import MagicMock, patch -from homeassistant.setup import setup_component import homeassistant.components.media_player as mp from homeassistant.components.yamaha import media_player as yamaha +from homeassistant.setup import setup_component + from tests.common import get_test_home_assistant diff --git a/tests/components/yandex_transport/test_yandex_transport_sensor.py b/tests/components/yandex_transport/test_yandex_transport_sensor.py index 7997d01bd13..a67108dc93b 100644 --- a/tests/components/yandex_transport/test_yandex_transport_sensor.py +++ b/tests/components/yandex_transport/test_yandex_transport_sensor.py @@ -1,15 +1,17 @@ """Tests for the yandex transport platform.""" import json + import pytest import homeassistant.components.sensor as sensor -import homeassistant.util.dt as dt_util from homeassistant.const import CONF_NAME +import homeassistant.util.dt as dt_util + from tests.common import ( + MockDependency, assert_setup_component, async_setup_component, - MockDependency, load_fixture, ) diff --git a/tests/components/yandextts/test_tts.py b/tests/components/yandextts/test_tts.py index c532732ccc5..edd5c058f12 100644 --- a/tests/components/yandextts/test_tts.py +++ b/tests/components/yandextts/test_tts.py @@ -3,14 +3,14 @@ import asyncio import os import shutil +from homeassistant.components.media_player.const import ( + DOMAIN as DOMAIN_MP, + SERVICE_PLAY_MEDIA, +) import homeassistant.components.tts as tts from homeassistant.setup import setup_component -from homeassistant.components.media_player.const import ( - SERVICE_PLAY_MEDIA, - DOMAIN as DOMAIN_MP, -) -from tests.common import get_test_home_assistant, assert_setup_component, mock_service +from tests.common import assert_setup_component, get_test_home_assistant, mock_service from tests.components.tts.test_init import mutagen_mock # noqa: F401 diff --git a/tests/components/yessssms/test_notify.py b/tests/components/yessssms/test_notify.py index dbc33b5a388..e5ef24ac150 100644 --- a/tests/components/yessssms/test_notify.py +++ b/tests/components/yessssms/test_notify.py @@ -1,16 +1,15 @@ """The tests for the notify yessssms platform.""" +import logging import unittest from unittest.mock import patch -import logging import pytest import requests_mock -from homeassistant.setup import async_setup_component -import homeassistant.components.yessssms.notify as yessssms from homeassistant.components.yessssms.const import CONF_PROVIDER - +import homeassistant.components.yessssms.notify as yessssms from homeassistant.const import CONF_PASSWORD, CONF_RECIPIENT, CONF_USERNAME +from homeassistant.setup import async_setup_component @pytest.fixture(name="config") diff --git a/tests/components/yr/test_sensor.py b/tests/components/yr/test_sensor.py index dce387b2c8c..1e8282a58ff 100644 --- a/tests/components/yr/test_sensor.py +++ b/tests/components/yr/test_sensor.py @@ -5,8 +5,8 @@ from unittest.mock import patch from homeassistant.bootstrap import async_setup_component import homeassistant.util.dt as dt_util -from tests.common import assert_setup_component, load_fixture +from tests.common import assert_setup_component, load_fixture NOW = datetime(2016, 6, 9, 1, tzinfo=dt_util.UTC) From 96a6a44411d822249df90343f183e2cbd9732274 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 14:58:51 +0100 Subject: [PATCH 224/677] Sort imports according to PEP8 for components starting with "X" (#29782) --- homeassistant/components/x10/light.py | 6 +++--- homeassistant/components/xbox_live/sensor.py | 2 +- homeassistant/components/xeoma/camera.py | 2 +- .../components/xfinity/device_tracker.py | 2 +- .../components/xiaomi_tv/media_player.py | 4 ++-- homeassistant/components/xmpp/notify.py | 15 +++++++-------- homeassistant/components/xs1/climate.py | 2 +- tests/components/xiaomi/test_device_tracker.py | 4 ++-- 8 files changed, 18 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/x10/light.py b/homeassistant/components/x10/light.py index bb2d2d89456..1f74326d544 100644 --- a/homeassistant/components/x10/light.py +++ b/homeassistant/components/x10/light.py @@ -1,16 +1,16 @@ """Support for X10 lights.""" import logging -from subprocess import check_output, CalledProcessError, STDOUT +from subprocess import STDOUT, CalledProcessError, check_output import voluptuous as vol -from homeassistant.const import CONF_NAME, CONF_ID, CONF_DEVICES from homeassistant.components.light import ( ATTR_BRIGHTNESS, + PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, Light, - PLATFORM_SCHEMA, ) +from homeassistant.const import CONF_DEVICES, CONF_ID, CONF_NAME import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/xbox_live/sensor.py b/homeassistant/components/xbox_live/sensor.py index e837cc4bbbc..41ebf69126a 100644 --- a/homeassistant/components/xbox_live/sensor.py +++ b/homeassistant/components/xbox_live/sensor.py @@ -1,6 +1,6 @@ """Sensor for Xbox Live account status.""" -import logging from datetime import timedelta +import logging import voluptuous as vol from xboxapi import xbox_api diff --git a/homeassistant/components/xeoma/camera.py b/homeassistant/components/xeoma/camera.py index bb5febe6bd7..d6f313c0382 100644 --- a/homeassistant/components/xeoma/camera.py +++ b/homeassistant/components/xeoma/camera.py @@ -1,8 +1,8 @@ """Support for Xeoma Cameras.""" import logging -import voluptuous as vol from pyxeoma.xeoma import Xeoma, XeomaError +import voluptuous as vol from homeassistant.components.camera import PLATFORM_SCHEMA, Camera from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME diff --git a/homeassistant/components/xfinity/device_tracker.py b/homeassistant/components/xfinity/device_tracker.py index 712d31d46db..524929ae42d 100644 --- a/homeassistant/components/xfinity/device_tracker.py +++ b/homeassistant/components/xfinity/device_tracker.py @@ -5,13 +5,13 @@ from requests.exceptions import RequestException import voluptuous as vol from xfinity_gateway import XfinityGateway -import homeassistant.helpers.config_validation as cv from homeassistant.components.device_tracker import ( DOMAIN, PLATFORM_SCHEMA, DeviceScanner, ) from homeassistant.const import CONF_HOST +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/xiaomi_tv/media_player.py b/homeassistant/components/xiaomi_tv/media_player.py index c34448ba63b..c82708852c2 100644 --- a/homeassistant/components/xiaomi_tv/media_player.py +++ b/homeassistant/components/xiaomi_tv/media_player.py @@ -1,10 +1,10 @@ """Add support for the Xiaomi TVs.""" import logging -import voluptuous as vol import pymitv +import voluptuous as vol -from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice from homeassistant.components.media_player.const import ( SUPPORT_TURN_OFF, SUPPORT_TURN_ON, diff --git a/homeassistant/components/xmpp/notify.py b/homeassistant/components/xmpp/notify.py index 338b0b85c03..d26b1eed151 100644 --- a/homeassistant/components/xmpp/notify.py +++ b/homeassistant/components/xmpp/notify.py @@ -9,14 +9,20 @@ import string import requests import slixmpp from slixmpp.exceptions import IqError, IqTimeout, XMPPError -from slixmpp.xmlstream.xmlstream import NotConnectedError from slixmpp.plugins.xep_0363.http_upload import ( FileTooBig, FileUploadError, UploadServiceNotFound, ) +from slixmpp.xmlstream.xmlstream import NotConnectedError import voluptuous as vol +from homeassistant.components.notify import ( + ATTR_TITLE, + ATTR_TITLE_DEFAULT, + PLATFORM_SCHEMA, + BaseNotificationService, +) from homeassistant.const import ( CONF_PASSWORD, CONF_RECIPIENT, @@ -27,13 +33,6 @@ from homeassistant.const import ( import homeassistant.helpers.config_validation as cv import homeassistant.helpers.template as template_helper -from homeassistant.components.notify import ( - ATTR_TITLE, - ATTR_TITLE_DEFAULT, - PLATFORM_SCHEMA, - BaseNotificationService, -) - _LOGGER = logging.getLogger(__name__) ATTR_DATA = "data" diff --git a/homeassistant/components/xs1/climate.py b/homeassistant/components/xs1/climate.py index 95ad10539bd..33c778c0d3d 100644 --- a/homeassistant/components/xs1/climate.py +++ b/homeassistant/components/xs1/climate.py @@ -5,8 +5,8 @@ from xs1_api_client.api_constants import ActuatorType from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( - SUPPORT_TARGET_TEMPERATURE, HVAC_MODE_HEAT, + SUPPORT_TARGET_TEMPERATURE, ) from homeassistant.const import ATTR_TEMPERATURE diff --git a/tests/components/xiaomi/test_device_tracker.py b/tests/components/xiaomi/test_device_tracker.py index 60e13ab3493..76788d0c3e8 100644 --- a/tests/components/xiaomi/test_device_tracker.py +++ b/tests/components/xiaomi/test_device_tracker.py @@ -1,13 +1,13 @@ """The tests for the Xiaomi router device tracker platform.""" import logging -from asynctest import mock, patch +from asynctest import mock, patch import requests from homeassistant.components.device_tracker import DOMAIN import homeassistant.components.xiaomi.device_tracker as xiaomi from homeassistant.components.xiaomi.device_tracker import get_scanner -from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_PLATFORM +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PLATFORM, CONF_USERNAME _LOGGER = logging.getLogger(__name__) From bb3fa6990a958d74e4f653c2137554800301b2a1 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 9 Dec 2019 15:10:03 +0100 Subject: [PATCH 225/677] Updated frontend to 20191204.1 (#29787) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 03d2d7899b0..75d02baaeeb 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", "requirements": [ - "home-assistant-frontend==20191204.0" + "home-assistant-frontend==20191204.1" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 07b9b4c80e8..afc92f92ef7 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -12,7 +12,7 @@ cryptography==2.8 defusedxml==0.6.0 distro==1.4.0 hass-nabucasa==0.30 -home-assistant-frontend==20191204.0 +home-assistant-frontend==20191204.1 importlib-metadata==0.23 jinja2>=2.10.3 netdisco==2.6.0 diff --git a/requirements_all.txt b/requirements_all.txt index 0e163f77595..e5af926a51e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -667,7 +667,7 @@ hole==0.5.0 holidays==0.9.11 # homeassistant.components.frontend -home-assistant-frontend==20191204.0 +home-assistant-frontend==20191204.1 # homeassistant.components.zwave homeassistant-pyozw==0.1.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9f30a3cfcc7..4cb7ca26a87 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -231,7 +231,7 @@ hole==0.5.0 holidays==0.9.11 # homeassistant.components.frontend -home-assistant-frontend==20191204.0 +home-assistant-frontend==20191204.1 # homeassistant.components.zwave homeassistant-pyozw==0.1.7 From 72f336a2ddfaf7dc6af65b6604af892c20bb284c Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Mon, 9 Dec 2019 16:10:02 +0100 Subject: [PATCH 226/677] Move imports to top for homekit (#29560) * Move imports to top for homekit * Moved back a couple imports, added annotation to disable import-outside-toplevel * Fix all tests in test_homekit.py --- homeassistant/components/homekit/__init__.py | 4 ++-- tests/components/homekit/test_homekit.py | 12 ++++-------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index cfd59ad61a4..9a2b2d5a851 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -29,6 +29,7 @@ from homeassistant.helpers.entityfilter import FILTER_SCHEMA from homeassistant.util import get_local_ip from homeassistant.util.decorator import Registry +from .accessories import HomeBridge, HomeDriver from .const import ( BRIDGE_NAME, CONF_ADVERTISE_IP, @@ -302,7 +303,6 @@ class HomeKit: def setup(self): """Set up bridge and accessory driver.""" - from .accessories import HomeBridge, HomeDriver self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.stop) @@ -361,7 +361,7 @@ class HomeKit: return self.status = STATUS_WAIT - from . import ( # noqa: F401 pylint: disable=unused-import + from . import ( # noqa: F401 pylint: disable=unused-import, import-outside-toplevel type_covers, type_fans, type_lights, diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index de6aaf0f11e..c845370e278 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -126,7 +126,7 @@ async def test_homekit_setup(hass, hk_driver): homekit = HomeKit(hass, BRIDGE_NAME, DEFAULT_PORT, None, {}, {}, DEFAULT_SAFE_MODE) with patch( - PATH_HOMEKIT + ".accessories.HomeDriver", return_value=hk_driver + PATH_HOMEKIT + ".HomeDriver", return_value=hk_driver ) as mock_driver, patch("homeassistant.util.get_local_ip") as mock_ip: mock_ip.return_value = IP_ADDRESS await hass.async_add_job(homekit.setup) @@ -150,9 +150,7 @@ async def test_homekit_setup_ip_address(hass, hk_driver): """Test setup with given IP address.""" homekit = HomeKit(hass, BRIDGE_NAME, DEFAULT_PORT, "172.0.0.0", {}, {}, None) - with patch( - PATH_HOMEKIT + ".accessories.HomeDriver", return_value=hk_driver - ) as mock_driver: + with patch(PATH_HOMEKIT + ".HomeDriver", return_value=hk_driver) as mock_driver: await hass.async_add_job(homekit.setup) mock_driver.assert_called_with( hass, @@ -169,9 +167,7 @@ async def test_homekit_setup_advertise_ip(hass, hk_driver): hass, BRIDGE_NAME, DEFAULT_PORT, "0.0.0.0", {}, {}, None, "192.168.1.100" ) - with patch( - PATH_HOMEKIT + ".accessories.HomeDriver", return_value=hk_driver - ) as mock_driver: + with patch(PATH_HOMEKIT + ".HomeDriver", return_value=hk_driver) as mock_driver: await hass.async_add_job(homekit.setup) mock_driver.assert_called_with( hass, @@ -186,7 +182,7 @@ async def test_homekit_setup_safe_mode(hass, hk_driver): """Test if safe_mode flag is set.""" homekit = HomeKit(hass, BRIDGE_NAME, DEFAULT_PORT, None, {}, {}, True) - with patch(PATH_HOMEKIT + ".accessories.HomeDriver", return_value=hk_driver): + with patch(PATH_HOMEKIT + ".HomeDriver", return_value=hk_driver): await hass.async_add_job(homekit.setup) assert homekit.driver.safe_mode is True From 3a28361bebbe11d124793a0ec269de74ef180665 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Mon, 9 Dec 2019 16:21:12 +0100 Subject: [PATCH 227/677] Cleanup removed component (#29788) --- .coveragerc | 1 - 1 file changed, 1 deletion(-) diff --git a/.coveragerc b/.coveragerc index b974356e0fe..8f872a93d8d 100644 --- a/.coveragerc +++ b/.coveragerc @@ -675,7 +675,6 @@ omit = homeassistant/components/systemmonitor/sensor.py homeassistant/components/tado/* homeassistant/components/tado/device_tracker.py - homeassistant/components/tahoma/* homeassistant/components/tank_utility/sensor.py homeassistant/components/tapsaff/binary_sensor.py homeassistant/components/tautulli/sensor.py From 5cb6d1b21fcfd46e517f89c801cd925f2f4b9498 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 16:24:03 +0100 Subject: [PATCH 228/677] Sort imports according to PEP8 for 'script' folder (#29790) --- script/gen_requirements_all.py | 4 ++-- script/hassfest/__main__.py | 2 +- script/hassfest/codeowners.py | 2 +- script/hassfest/config_flow.py | 2 +- script/hassfest/dependencies.py | 2 +- script/hassfest/manifest.py | 1 - script/hassfest/manifest_helper.py | 1 - script/hassfest/model.py | 6 +++--- script/hassfest/services.py | 2 +- script/hassfest/ssdp.py | 2 +- script/hassfest/zeroconf.py | 2 +- script/inspect_schemas.py | 2 +- script/lazytox.py | 6 +++--- script/scaffold/__main__.py | 3 +-- script/scaffold/docs.py | 1 - script/scaffold/gather_info.py | 3 +-- .../config_flow/integration/__init__.py | 2 +- .../config_flow/integration/config_flow.py | 2 +- .../config_flow/tests/test_config_flow.py | 2 +- .../integration/__init__.py | 2 +- .../integration/config_flow.py | 3 ++- .../config_flow_oauth2/integration/__init__.py | 16 ++++++++-------- .../config_flow_oauth2/integration/api.py | 2 +- .../integration/config_flow.py | 1 + .../config_flow_oauth2/tests/test_config_flow.py | 2 +- .../device_action/integration/device_action.py | 12 +++++++----- .../device_action/tests/test_device_action.py | 4 ++-- .../integration/device_condition.py | 8 +++++--- .../tests/test_device_condition.py | 6 +++--- .../device_trigger/integration/device_trigger.py | 16 +++++++++------- .../device_trigger/tests/test_device_trigger.py | 6 +++--- .../integration/integration/__init__.py | 1 - .../integration/reproduce_state.py | 4 ++-- script/translations_download_split.py | 2 +- script/translations_upload_merge.py | 2 +- script/version_bump.py | 2 +- 36 files changed, 69 insertions(+), 67 deletions(-) diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 9bbe7d379ec..c18b51ba5d1 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -8,10 +8,10 @@ import pkgutil import re import sys -from homeassistant.util.yaml.loader import load_yaml - from script.hassfest.model import Integration +from homeassistant.util.yaml.loader import load_yaml + COMMENT_REQUIREMENTS = ( "Adafruit_BBIO", "Adafruit-DHT", diff --git a/script/hassfest/__main__.py b/script/hassfest/__main__.py index a1168b15f7d..78b46b8db57 100644 --- a/script/hassfest/__main__.py +++ b/script/hassfest/__main__.py @@ -2,8 +2,8 @@ import pathlib import sys -from .model import Integration, Config from . import codeowners, config_flow, dependencies, manifest, services, ssdp, zeroconf +from .model import Config, Integration PLUGINS = [codeowners, config_flow, dependencies, manifest, services, ssdp, zeroconf] diff --git a/script/hassfest/codeowners.py b/script/hassfest/codeowners.py index 6f63fab3fdb..f6970b50a3c 100644 --- a/script/hassfest/codeowners.py +++ b/script/hassfest/codeowners.py @@ -1,7 +1,7 @@ """Generate CODEOWNERS.""" from typing import Dict -from .model import Integration, Config +from .model import Config, Integration BASE = """ # This file is generated by script/hassfest/codeowners.py diff --git a/script/hassfest/config_flow.py b/script/hassfest/config_flow.py index 4384399f4db..83d495e1bf2 100644 --- a/script/hassfest/config_flow.py +++ b/script/hassfest/config_flow.py @@ -2,7 +2,7 @@ import json from typing import Dict -from .model import Integration, Config +from .model import Config, Integration BASE = """ \"\"\"Automatically generated by hassfest. diff --git a/script/hassfest/dependencies.py b/script/hassfest/dependencies.py index 42a31f20610..71936411b75 100644 --- a/script/hassfest/dependencies.py +++ b/script/hassfest/dependencies.py @@ -1,7 +1,7 @@ """Validate dependencies.""" import pathlib import re -from typing import Set, Dict +from typing import Dict, Set from .model import Integration diff --git a/script/hassfest/manifest.py b/script/hassfest/manifest.py index 16f8b77b5d3..acc5e9af832 100644 --- a/script/hassfest/manifest.py +++ b/script/hassfest/manifest.py @@ -6,7 +6,6 @@ from voluptuous.humanize import humanize_error from .model import Integration - MANIFEST_SCHEMA = vol.Schema( { vol.Required("domain"): str, diff --git a/script/hassfest/manifest_helper.py b/script/hassfest/manifest_helper.py index 251a2939807..0c2a1456ec6 100644 --- a/script/hassfest/manifest_helper.py +++ b/script/hassfest/manifest_helper.py @@ -2,7 +2,6 @@ import json import pathlib - component_dir = pathlib.Path("homeassistant/components") diff --git a/script/hassfest/model.py b/script/hassfest/model.py index 77683d65961..faa1c26262c 100644 --- a/script/hassfest/model.py +++ b/script/hassfest/model.py @@ -1,8 +1,8 @@ """Models for manifest validator.""" -import json -from typing import List, Dict, Any -import pathlib import importlib +import json +import pathlib +from typing import Any, Dict, List import attr diff --git a/script/hassfest/services.py b/script/hassfest/services.py index 801ee10e43a..08cde60f5b5 100644 --- a/script/hassfest/services.py +++ b/script/hassfest/services.py @@ -1,8 +1,8 @@ """Validate dependencies.""" import pathlib +import re from typing import Dict -import re import voluptuous as vol from voluptuous.humanize import humanize_error diff --git a/script/hassfest/ssdp.py b/script/hassfest/ssdp.py index d2dd724605e..7578d52ed4f 100644 --- a/script/hassfest/ssdp.py +++ b/script/hassfest/ssdp.py @@ -3,7 +3,7 @@ from collections import OrderedDict, defaultdict import json from typing import Dict -from .model import Integration, Config +from .model import Config, Integration BASE = """ \"\"\"Automatically generated by hassfest. diff --git a/script/hassfest/zeroconf.py b/script/hassfest/zeroconf.py index 3d93d363086..f864d3e0327 100644 --- a/script/hassfest/zeroconf.py +++ b/script/hassfest/zeroconf.py @@ -3,7 +3,7 @@ from collections import OrderedDict, defaultdict import json from typing import Dict -from .model import Integration, Config +from .model import Config, Integration BASE = """ \"\"\"Automatically generated by hassfest. diff --git a/script/inspect_schemas.py b/script/inspect_schemas.py index e46165bfaa2..4f305a4fb9c 100755 --- a/script/inspect_schemas.py +++ b/script/inspect_schemas.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """Inspect all component SCHEMAS.""" -import os import importlib +import os import pkgutil from homeassistant.config import _identify_config_schema diff --git a/script/lazytox.py b/script/lazytox.py index ca8a160e7dc..d1b09618998 100755 --- a/script/lazytox.py +++ b/script/lazytox.py @@ -4,12 +4,12 @@ Lazy 'tox' to quickly check if branch is up to PR standards. This is NOT a tox replacement, only a quick check during development. """ -import os import asyncio -import sys +from collections import namedtuple +import os import re import shlex -from collections import namedtuple +import sys try: from colorlog.escape_codes import escape_codes diff --git a/script/scaffold/__main__.py b/script/scaffold/__main__.py index 78490b84ba3..94ac009fd9c 100644 --- a/script/scaffold/__main__.py +++ b/script/scaffold/__main__.py @@ -4,10 +4,9 @@ from pathlib import Path import subprocess import sys -from . import gather_info, generate, error, docs +from . import docs, error, gather_info, generate from .const import COMPONENT_DIR - TEMPLATES = [ p.name for p in (Path(__file__).parent / "templates").glob("*") if p.is_dir() ] diff --git a/script/scaffold/docs.py b/script/scaffold/docs.py index 5df663fec0b..8186b857e80 100644 --- a/script/scaffold/docs.py +++ b/script/scaffold/docs.py @@ -1,7 +1,6 @@ """Print links to relevant docs.""" from .model import Info - DATA = { "config_flow": { "title": "Config Flow", diff --git a/script/scaffold/gather_info.py b/script/scaffold/gather_info.py index 6a69040a6d7..48d0a20ea73 100644 --- a/script/scaffold/gather_info.py +++ b/script/scaffold/gather_info.py @@ -4,9 +4,8 @@ import json from homeassistant.util import slugify from .const import COMPONENT_DIR -from .model import Info from .error import ExitApp - +from .model import Info CHECK_EMPTY = ["Cannot be empty", lambda value: value] diff --git a/script/scaffold/templates/config_flow/integration/__init__.py b/script/scaffold/templates/config_flow/integration/__init__.py index 04b908952d1..4a206981c3c 100644 --- a/script/scaffold/templates/config_flow/integration/__init__.py +++ b/script/scaffold/templates/config_flow/integration/__init__.py @@ -3,8 +3,8 @@ import asyncio import voluptuous as vol -from homeassistant.core import HomeAssistant from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant from .const import DOMAIN diff --git a/script/scaffold/templates/config_flow/integration/config_flow.py b/script/scaffold/templates/config_flow/integration/config_flow.py index e08851f47a0..e2452b5324d 100644 --- a/script/scaffold/templates/config_flow/integration/config_flow.py +++ b/script/scaffold/templates/config_flow/integration/config_flow.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant import core, config_entries, exceptions +from homeassistant import config_entries, core, exceptions from .const import DOMAIN # pylint:disable=unused-import diff --git a/script/scaffold/templates/config_flow/tests/test_config_flow.py b/script/scaffold/templates/config_flow/tests/test_config_flow.py index 35d8a96ab2b..b68adc897bb 100644 --- a/script/scaffold/templates/config_flow/tests/test_config_flow.py +++ b/script/scaffold/templates/config_flow/tests/test_config_flow.py @@ -2,8 +2,8 @@ from unittest.mock import patch from homeassistant import config_entries, setup -from homeassistant.components.NEW_DOMAIN.const import DOMAIN from homeassistant.components.NEW_DOMAIN.config_flow import CannotConnect, InvalidAuth +from homeassistant.components.NEW_DOMAIN.const import DOMAIN from tests.common import mock_coro diff --git a/script/scaffold/templates/config_flow_discovery/integration/__init__.py b/script/scaffold/templates/config_flow_discovery/integration/__init__.py index 04b908952d1..4a206981c3c 100644 --- a/script/scaffold/templates/config_flow_discovery/integration/__init__.py +++ b/script/scaffold/templates/config_flow_discovery/integration/__init__.py @@ -3,8 +3,8 @@ import asyncio import voluptuous as vol -from homeassistant.core import HomeAssistant from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant from .const import DOMAIN diff --git a/script/scaffold/templates/config_flow_discovery/integration/config_flow.py b/script/scaffold/templates/config_flow_discovery/integration/config_flow.py index 16d13aaa99f..db5f719ce3d 100644 --- a/script/scaffold/templates/config_flow_discovery/integration/config_flow.py +++ b/script/scaffold/templates/config_flow_discovery/integration/config_flow.py @@ -1,8 +1,9 @@ """Config flow for NEW_NAME.""" import my_pypi_dependency -from homeassistant.helpers import config_entry_flow from homeassistant import config_entries +from homeassistant.helpers import config_entry_flow + from .const import DOMAIN diff --git a/script/scaffold/templates/config_flow_oauth2/integration/__init__.py b/script/scaffold/templates/config_flow_oauth2/integration/__init__.py index 30e7ad97810..d561f284caf 100644 --- a/script/scaffold/templates/config_flow_oauth2/integration/__init__.py +++ b/script/scaffold/templates/config_flow_oauth2/integration/__init__.py @@ -3,17 +3,17 @@ import asyncio import voluptuous as vol -from homeassistant.core import HomeAssistant -from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET -from homeassistant.helpers import ( - config_validation as cv, - config_entry_oauth2_flow, - aiohttp_client, -) from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET +from homeassistant.core import HomeAssistant +from homeassistant.helpers import ( + aiohttp_client, + config_entry_oauth2_flow, + config_validation as cv, +) -from .const import DOMAIN, OAUTH2_AUTHORIZE, OAUTH2_TOKEN from . import api, config_flow +from .const import DOMAIN, OAUTH2_AUTHORIZE, OAUTH2_TOKEN CONFIG_SCHEMA = vol.Schema( { diff --git a/script/scaffold/templates/config_flow_oauth2/integration/api.py b/script/scaffold/templates/config_flow_oauth2/integration/api.py index c5aa4a81ebe..f1a1f6a7ec4 100644 --- a/script/scaffold/templates/config_flow_oauth2/integration/api.py +++ b/script/scaffold/templates/config_flow_oauth2/integration/api.py @@ -4,7 +4,7 @@ from asyncio import run_coroutine_threadsafe from aiohttp import ClientSession import my_pypi_package -from homeassistant import core, config_entries +from homeassistant import config_entries, core from homeassistant.helpers import config_entry_oauth2_flow # TODO the following two API examples are based on our suggested best practices diff --git a/script/scaffold/templates/config_flow_oauth2/integration/config_flow.py b/script/scaffold/templates/config_flow_oauth2/integration/config_flow.py index 1112a404e60..2343e1d79f8 100644 --- a/script/scaffold/templates/config_flow_oauth2/integration/config_flow.py +++ b/script/scaffold/templates/config_flow_oauth2/integration/config_flow.py @@ -3,6 +3,7 @@ import logging from homeassistant import config_entries from homeassistant.helpers import config_entry_oauth2_flow + from .const import DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/script/scaffold/templates/config_flow_oauth2/tests/test_config_flow.py b/script/scaffold/templates/config_flow_oauth2/tests/test_config_flow.py index 7e61bcbfb1b..50540594a34 100644 --- a/script/scaffold/templates/config_flow_oauth2/tests/test_config_flow.py +++ b/script/scaffold/templates/config_flow_oauth2/tests/test_config_flow.py @@ -1,5 +1,5 @@ """Test the NEW_NAME config flow.""" -from homeassistant import config_entries, setup, data_entry_flow +from homeassistant import config_entries, data_entry_flow, setup from homeassistant.components.NEW_DOMAIN.const import ( DOMAIN, OAUTH2_AUTHORIZE, diff --git a/script/scaffold/templates/device_action/integration/device_action.py b/script/scaffold/templates/device_action/integration/device_action.py index d5674f01b2d..3861ee8ebe9 100644 --- a/script/scaffold/templates/device_action/integration/device_action.py +++ b/script/scaffold/templates/device_action/integration/device_action.py @@ -1,19 +1,21 @@ """Provides device automations for NEW_NAME.""" -from typing import Optional, List +from typing import List, Optional + import voluptuous as vol from homeassistant.const import ( ATTR_ENTITY_ID, - CONF_DOMAIN, - CONF_TYPE, CONF_DEVICE_ID, + CONF_DOMAIN, CONF_ENTITY_ID, - SERVICE_TURN_ON, + CONF_TYPE, SERVICE_TURN_OFF, + SERVICE_TURN_ON, ) -from homeassistant.core import HomeAssistant, Context +from homeassistant.core import Context, HomeAssistant from homeassistant.helpers import entity_registry import homeassistant.helpers.config_validation as cv + from . import DOMAIN # TODO specify your supported action types. diff --git a/script/scaffold/templates/device_action/tests/test_device_action.py b/script/scaffold/templates/device_action/tests/test_device_action.py index b65c8257531..3c7c7bb71a4 100644 --- a/script/scaffold/templates/device_action/tests/test_device_action.py +++ b/script/scaffold/templates/device_action/tests/test_device_action.py @@ -2,17 +2,17 @@ import pytest from homeassistant.components.NEW_DOMAIN import DOMAIN -from homeassistant.setup import async_setup_component import homeassistant.components.automation as automation from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component from tests.common import ( MockConfigEntry, assert_lists_same, + async_get_device_automations, async_mock_service, mock_device_registry, mock_registry, - async_get_device_automations, ) diff --git a/script/scaffold/templates/device_condition/integration/device_condition.py b/script/scaffold/templates/device_condition/integration/device_condition.py index 4b7baf68a37..1414636474d 100644 --- a/script/scaffold/templates/device_condition/integration/device_condition.py +++ b/script/scaffold/templates/device_condition/integration/device_condition.py @@ -1,21 +1,23 @@ """Provide the device automations for NEW_NAME.""" from typing import Dict, List + import voluptuous as vol from homeassistant.const import ( ATTR_ENTITY_ID, CONF_CONDITION, - CONF_DOMAIN, - CONF_TYPE, CONF_DEVICE_ID, + CONF_DOMAIN, CONF_ENTITY_ID, + CONF_TYPE, STATE_OFF, STATE_ON, ) from homeassistant.core import HomeAssistant from homeassistant.helpers import condition, config_validation as cv, entity_registry -from homeassistant.helpers.typing import ConfigType, TemplateVarsType from homeassistant.helpers.config_validation import DEVICE_CONDITION_BASE_SCHEMA +from homeassistant.helpers.typing import ConfigType, TemplateVarsType + from . import DOMAIN # TODO specify your supported condition types. diff --git a/script/scaffold/templates/device_condition/tests/test_device_condition.py b/script/scaffold/templates/device_condition/tests/test_device_condition.py index 1ae4df5f1b7..d58957030dc 100644 --- a/script/scaffold/templates/device_condition/tests/test_device_condition.py +++ b/script/scaffold/templates/device_condition/tests/test_device_condition.py @@ -2,18 +2,18 @@ import pytest from homeassistant.components.NEW_DOMAIN import DOMAIN -from homeassistant.const import STATE_ON, STATE_OFF -from homeassistant.setup import async_setup_component import homeassistant.components.automation as automation +from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component from tests.common import ( MockConfigEntry, assert_lists_same, + async_get_device_automations, async_mock_service, mock_device_registry, mock_registry, - async_get_device_automations, ) diff --git a/script/scaffold/templates/device_trigger/integration/device_trigger.py b/script/scaffold/templates/device_trigger/integration/device_trigger.py index e0741734d5f..a4f918684dc 100644 --- a/script/scaffold/templates/device_trigger/integration/device_trigger.py +++ b/script/scaffold/templates/device_trigger/integration/device_trigger.py @@ -1,21 +1,23 @@ """Provides device automations for NEW_NAME.""" from typing import List + import voluptuous as vol +from homeassistant.components.automation import AutomationActionType, state +from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA from homeassistant.const import ( - CONF_DOMAIN, - CONF_TYPE, - CONF_PLATFORM, CONF_DEVICE_ID, + CONF_DOMAIN, CONF_ENTITY_ID, - STATE_ON, + CONF_PLATFORM, + CONF_TYPE, STATE_OFF, + STATE_ON, ) -from homeassistant.core import HomeAssistant, CALLBACK_TYPE +from homeassistant.core import CALLBACK_TYPE, HomeAssistant from homeassistant.helpers import config_validation as cv, entity_registry from homeassistant.helpers.typing import ConfigType -from homeassistant.components.automation import state, AutomationActionType -from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA + from . import DOMAIN # TODO specify your supported trigger types. diff --git a/script/scaffold/templates/device_trigger/tests/test_device_trigger.py b/script/scaffold/templates/device_trigger/tests/test_device_trigger.py index 99e1f8937af..0ea584f474d 100644 --- a/script/scaffold/templates/device_trigger/tests/test_device_trigger.py +++ b/script/scaffold/templates/device_trigger/tests/test_device_trigger.py @@ -2,18 +2,18 @@ import pytest from homeassistant.components.NEW_DOMAIN import DOMAIN -from homeassistant.const import STATE_ON, STATE_OFF -from homeassistant.setup import async_setup_component import homeassistant.components.automation as automation +from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component from tests.common import ( MockConfigEntry, assert_lists_same, + async_get_device_automations, async_mock_service, mock_device_registry, mock_registry, - async_get_device_automations, ) diff --git a/script/scaffold/templates/integration/integration/__init__.py b/script/scaffold/templates/integration/integration/__init__.py index c2ae59aaad4..0ab65cb7da8 100644 --- a/script/scaffold/templates/integration/integration/__init__.py +++ b/script/scaffold/templates/integration/integration/__init__.py @@ -5,7 +5,6 @@ from homeassistant.core import HomeAssistant from .const import DOMAIN - CONFIG_SCHEMA = vol.Schema({vol.Optional(DOMAIN): {}}, extra=vol.ALLOW_EXTRA) diff --git a/script/scaffold/templates/reproduce_state/integration/reproduce_state.py b/script/scaffold/templates/reproduce_state/integration/reproduce_state.py index 3449009818b..871142a5b00 100644 --- a/script/scaffold/templates/reproduce_state/integration/reproduce_state.py +++ b/script/scaffold/templates/reproduce_state/integration/reproduce_state.py @@ -5,10 +5,10 @@ from typing import Iterable, Optional from homeassistant.const import ( ATTR_ENTITY_ID, - STATE_ON, - STATE_OFF, SERVICE_TURN_OFF, SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, ) from homeassistant.core import Context, State from homeassistant.helpers.typing import HomeAssistantType diff --git a/script/translations_download_split.py b/script/translations_download_split.py index 375a9490e4e..86944ec9f90 100755 --- a/script/translations_download_split.py +++ b/script/translations_download_split.py @@ -4,7 +4,7 @@ import glob import json import os import re -from typing import Union, List, Dict +from typing import Dict, List, Union FILENAME_FORMAT = re.compile(r"strings\.(?P\w+)\.json") diff --git a/script/translations_upload_merge.py b/script/translations_upload_merge.py index c44727f690f..86de1f1842b 100755 --- a/script/translations_upload_merge.py +++ b/script/translations_upload_merge.py @@ -5,7 +5,7 @@ import itertools import json import os import re -from typing import Union, List, Dict +from typing import Dict, List, Union FILENAME_FORMAT = re.compile(r"strings\.(?P\w+)\.json") diff --git a/script/version_bump.py b/script/version_bump.py index de6638df30b..13dfe499f5e 100755 --- a/script/version_bump.py +++ b/script/version_bump.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 """Helper script to bump the current version.""" import argparse +from datetime import datetime import re import subprocess -from datetime import datetime from packaging.version import Version From 29ec17d50df10aab61c67c0a52260321910cb3b1 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 16:30:16 +0100 Subject: [PATCH 229/677] use isort to sort imports for "setup.py" (#29792) --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 73cc893cbe2..f9cb24d1e91 100755 --- a/setup.py +++ b/setup.py @@ -1,7 +1,8 @@ #!/usr/bin/env python3 """Home Assistant setup script.""" from datetime import datetime as dt -from setuptools import setup, find_packages + +from setuptools import find_packages, setup import homeassistant.const as hass_const From 67c56c860da42c531e93fa420a558023c22e8dce Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 16:42:10 +0100 Subject: [PATCH 230/677] Sort imports according to PEP8 for 'homeassistant' folder (#29789) Components are already done --- homeassistant/__main__.py | 4 +- homeassistant/auth/__init__.py | 8 ++-- homeassistant/auth/auth_store.py | 2 +- homeassistant/auth/mfa_modules/__init__.py | 2 +- .../auth/mfa_modules/insecure_example.py | 4 +- homeassistant/auth/mfa_modules/notify.py | 8 ++-- homeassistant/auth/mfa_modules/totp.py | 6 +-- homeassistant/auth/permissions/__init__.py | 5 +-- homeassistant/auth/permissions/entities.py | 5 +-- homeassistant/auth/permissions/merge.py | 4 +- .../auth/permissions/system_policies.py | 2 +- homeassistant/auth/permissions/util.py | 1 - homeassistant/auth/providers/__init__.py | 2 +- homeassistant/auth/providers/command_line.py | 6 +-- homeassistant/auth/providers/homeassistant.py | 7 +-- .../auth/providers/insecure_example.py | 5 +-- .../auth/providers/legacy_api_password.py | 4 +- .../auth/providers/trusted_networks.py | 7 +-- homeassistant/bootstrap.py | 8 ++-- homeassistant/config.py | 44 +++++++++---------- homeassistant/config_entries.py | 13 +++--- homeassistant/core.py | 40 +++++++---------- homeassistant/data_entry_flow.py | 6 ++- homeassistant/exceptions.py | 3 +- homeassistant/helpers/__init__.py | 2 +- homeassistant/helpers/aiohttp_client.py | 11 +++-- homeassistant/helpers/area_registry.py | 7 ++- homeassistant/helpers/check_config.py | 22 +++++----- homeassistant/helpers/condition.py | 10 ++--- homeassistant/helpers/config_entry_flow.py | 4 +- .../helpers/config_entry_oauth2_flow.py | 11 +++-- homeassistant/helpers/config_validation.py | 27 ++++++------ homeassistant/helpers/data_entry_flow.py | 3 +- homeassistant/helpers/device_registry.py | 5 +-- homeassistant/helpers/discovery.py | 6 +-- homeassistant/helpers/dispatcher.py | 2 +- homeassistant/helpers/entity.py | 18 ++++---- homeassistant/helpers/entity_component.py | 8 ++-- homeassistant/helpers/entity_platform.py | 5 +-- homeassistant/helpers/entity_registry.py | 1 - homeassistant/helpers/event.py | 11 +++-- homeassistant/helpers/intent.py | 5 +-- homeassistant/helpers/logging.py | 1 - homeassistant/helpers/network.py | 2 +- homeassistant/helpers/restore_state.py | 17 ++++--- homeassistant/helpers/script.py | 15 +++---- homeassistant/helpers/service.py | 7 ++- homeassistant/helpers/signal.py | 2 +- homeassistant/helpers/state.py | 7 +-- homeassistant/helpers/storage.py | 7 ++- homeassistant/helpers/sun.py | 5 ++- homeassistant/helpers/system_info.py | 1 + homeassistant/helpers/temperature.py | 2 +- homeassistant/helpers/template.py | 5 +-- homeassistant/helpers/translation.py | 3 +- homeassistant/helpers/typing.py | 2 +- homeassistant/loader.py | 10 ++--- homeassistant/requirements.py | 8 ++-- homeassistant/scripts/__init__.py | 3 +- homeassistant/scripts/auth.py | 3 +- homeassistant/scripts/benchmark/__init__.py | 1 - homeassistant/scripts/check_config.py | 9 ++-- homeassistant/scripts/credstash.py | 1 - homeassistant/scripts/ensure_config.py | 3 +- homeassistant/scripts/macos/__init__.py | 1 - homeassistant/setup.py | 6 +-- homeassistant/util/__init__.py | 18 ++++---- homeassistant/util/aiohttp.py | 2 +- homeassistant/util/async_.py | 14 +++--- homeassistant/util/color.py | 4 +- homeassistant/util/distance.py | 10 ++--- homeassistant/util/dt.py | 2 +- homeassistant/util/json.py | 5 +-- homeassistant/util/location.py | 2 +- homeassistant/util/package.py | 5 +-- homeassistant/util/pressure.py | 6 +-- homeassistant/util/ruamel_yaml.py | 8 ++-- homeassistant/util/temperature.py | 2 +- homeassistant/util/unit_system.py | 36 ++++++++------- homeassistant/util/volume.py | 9 ++-- homeassistant/util/yaml/__init__.py | 3 +- homeassistant/util/yaml/dumper.py | 2 +- homeassistant/util/yaml/loader.py | 16 +++---- 83 files changed, 290 insertions(+), 329 deletions(-) diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index d2903370f49..2cecd1217f9 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -5,10 +5,10 @@ import platform import subprocess import sys import threading -from typing import List, Dict, Any, TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Dict, List from homeassistant import monkey_patch -from homeassistant.const import __version__, REQUIRED_PYTHON_VER, RESTART_EXIT_CODE +from homeassistant.const import REQUIRED_PYTHON_VER, RESTART_EXIT_CODE, __version__ if TYPE_CHECKING: from homeassistant import core diff --git a/homeassistant/auth/__init__.py b/homeassistant/auth/__init__.py index 3f7dd570400..e4437bea840 100644 --- a/homeassistant/auth/__init__.py +++ b/homeassistant/auth/__init__.py @@ -1,21 +1,21 @@ """Provide an authentication layer for Home Assistant.""" import asyncio -import logging from collections import OrderedDict from datetime import timedelta +import logging from typing import Any, Dict, List, Optional, Tuple, cast import jwt from homeassistant import data_entry_flow from homeassistant.auth.const import ACCESS_TOKEN_EXPIRATION -from homeassistant.core import callback, HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.util import dt as dt_util from . import auth_store, models from .const import GROUP_ID_ADMIN -from .mfa_modules import auth_mfa_module_from_config, MultiFactorAuthModule -from .providers import auth_provider_from_config, AuthProvider, LoginFlow +from .mfa_modules import MultiFactorAuthModule, auth_mfa_module_from_config +from .providers import AuthProvider, LoginFlow, auth_provider_from_config EVENT_USER_ADDED = "user_added" EVENT_USER_REMOVED = "user_removed" diff --git a/homeassistant/auth/auth_store.py b/homeassistant/auth/auth_store.py index 4c64730edda..57ec9ee63dc 100644 --- a/homeassistant/auth/auth_store.py +++ b/homeassistant/auth/auth_store.py @@ -11,7 +11,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.util import dt as dt_util from . import models -from .const import GROUP_ID_ADMIN, GROUP_ID_USER, GROUP_ID_READ_ONLY +from .const import GROUP_ID_ADMIN, GROUP_ID_READ_ONLY, GROUP_ID_USER from .permissions import PermissionLookup, system_policies from .permissions.types import PolicyType diff --git a/homeassistant/auth/mfa_modules/__init__.py b/homeassistant/auth/mfa_modules/__init__.py index 9020b0b321e..fd9e61b9d17 100644 --- a/homeassistant/auth/mfa_modules/__init__.py +++ b/homeassistant/auth/mfa_modules/__init__.py @@ -7,7 +7,7 @@ from typing import Any, Dict, Optional import voluptuous as vol from voluptuous.humanize import humanize_error -from homeassistant import requirements, data_entry_flow +from homeassistant import data_entry_flow, requirements from homeassistant.const import CONF_ID, CONF_NAME, CONF_TYPE from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError diff --git a/homeassistant/auth/mfa_modules/insecure_example.py b/homeassistant/auth/mfa_modules/insecure_example.py index a3f0d58c6b3..45cc07ae581 100644 --- a/homeassistant/auth/mfa_modules/insecure_example.py +++ b/homeassistant/auth/mfa_modules/insecure_example.py @@ -7,9 +7,9 @@ import voluptuous as vol from homeassistant.core import HomeAssistant from . import ( - MultiFactorAuthModule, - MULTI_FACTOR_AUTH_MODULES, MULTI_FACTOR_AUTH_MODULE_SCHEMA, + MULTI_FACTOR_AUTH_MODULES, + MultiFactorAuthModule, SetupFlow, ) diff --git a/homeassistant/auth/mfa_modules/notify.py b/homeassistant/auth/mfa_modules/notify.py index b14f5fedc22..46cc634bcae 100644 --- a/homeassistant/auth/mfa_modules/notify.py +++ b/homeassistant/auth/mfa_modules/notify.py @@ -3,9 +3,9 @@ Sending HOTP through notify service """ import asyncio -import logging from collections import OrderedDict -from typing import Any, Dict, Optional, List +import logging +from typing import Any, Dict, List, Optional import attr import voluptuous as vol @@ -16,9 +16,9 @@ from homeassistant.exceptions import ServiceNotFound from homeassistant.helpers import config_validation as cv from . import ( - MultiFactorAuthModule, - MULTI_FACTOR_AUTH_MODULES, MULTI_FACTOR_AUTH_MODULE_SCHEMA, + MULTI_FACTOR_AUTH_MODULES, + MultiFactorAuthModule, SetupFlow, ) diff --git a/homeassistant/auth/mfa_modules/totp.py b/homeassistant/auth/mfa_modules/totp.py index 9b0f3910e92..6abddd2123f 100644 --- a/homeassistant/auth/mfa_modules/totp.py +++ b/homeassistant/auth/mfa_modules/totp.py @@ -1,7 +1,7 @@ """Time-based One Time Password auth module.""" import asyncio -import logging from io import BytesIO +import logging from typing import Any, Dict, Optional, Tuple import voluptuous as vol @@ -10,9 +10,9 @@ from homeassistant.auth.models import User from homeassistant.core import HomeAssistant from . import ( - MultiFactorAuthModule, - MULTI_FACTOR_AUTH_MODULES, MULTI_FACTOR_AUTH_MODULE_SCHEMA, + MULTI_FACTOR_AUTH_MODULES, + MultiFactorAuthModule, SetupFlow, ) diff --git a/homeassistant/auth/permissions/__init__.py b/homeassistant/auth/permissions/__init__.py index 36cf9c4f420..92d02c75b91 100644 --- a/homeassistant/auth/permissions/__init__.py +++ b/homeassistant/auth/permissions/__init__.py @@ -5,13 +5,12 @@ from typing import Any, Callable, Optional import voluptuous as vol from .const import CAT_ENTITIES -from .models import PermissionLookup -from .types import PolicyType from .entities import ENTITY_POLICY_SCHEMA, compile_entities from .merge import merge_policies # noqa: F401 +from .models import PermissionLookup +from .types import PolicyType from .util import test_all - POLICY_SCHEMA = vol.Schema({vol.Optional(CAT_ENTITIES): ENTITY_POLICY_SCHEMA}) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/auth/permissions/entities.py b/homeassistant/auth/permissions/entities.py index add9913abf3..be30c7bf69a 100644 --- a/homeassistant/auth/permissions/entities.py +++ b/homeassistant/auth/permissions/entities.py @@ -4,11 +4,10 @@ from typing import Callable, Optional import voluptuous as vol -from .const import SUBCAT_ALL, POLICY_READ, POLICY_CONTROL, POLICY_EDIT +from .const import POLICY_CONTROL, POLICY_EDIT, POLICY_READ, SUBCAT_ALL from .models import PermissionLookup from .types import CategoryType, SubCategoryDict, ValueType - -from .util import SubCatLookupType, lookup_all, compile_policy +from .util import SubCatLookupType, compile_policy, lookup_all SINGLE_ENTITY_SCHEMA = vol.Any( True, diff --git a/homeassistant/auth/permissions/merge.py b/homeassistant/auth/permissions/merge.py index 3cf02e05771..fad98b3f22a 100644 --- a/homeassistant/auth/permissions/merge.py +++ b/homeassistant/auth/permissions/merge.py @@ -1,7 +1,7 @@ """Merging of policies.""" -from typing import cast, Dict, List, Set +from typing import Dict, List, Set, cast -from .types import PolicyType, CategoryType +from .types import CategoryType, PolicyType def merge_policies(policies: List[PolicyType]) -> PolicyType: diff --git a/homeassistant/auth/permissions/system_policies.py b/homeassistant/auth/permissions/system_policies.py index b40400304cc..b45984653fb 100644 --- a/homeassistant/auth/permissions/system_policies.py +++ b/homeassistant/auth/permissions/system_policies.py @@ -1,5 +1,5 @@ """System policies.""" -from .const import CAT_ENTITIES, SUBCAT_ALL, POLICY_READ +from .const import CAT_ENTITIES, POLICY_READ, SUBCAT_ALL ADMIN_POLICY = {CAT_ENTITIES: True} diff --git a/homeassistant/auth/permissions/util.py b/homeassistant/auth/permissions/util.py index 4d38e0a639c..11bbd878eb2 100644 --- a/homeassistant/auth/permissions/util.py +++ b/homeassistant/auth/permissions/util.py @@ -1,6 +1,5 @@ """Helpers to deal with permissions.""" from functools import wraps - from typing import Callable, Dict, List, Optional, cast from .const import SUBCAT_ALL diff --git a/homeassistant/auth/providers/__init__.py b/homeassistant/auth/providers/__init__.py index cbce3152902..bb0fc55b5c4 100644 --- a/homeassistant/auth/providers/__init__.py +++ b/homeassistant/auth/providers/__init__.py @@ -8,8 +8,8 @@ import voluptuous as vol from voluptuous.humanize import humanize_error from homeassistant import data_entry_flow, requirements -from homeassistant.core import callback, HomeAssistant from homeassistant.const import CONF_ID, CONF_NAME, CONF_TYPE +from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.util import dt as dt_util from homeassistant.util.decorator import Registry diff --git a/homeassistant/auth/providers/command_line.py b/homeassistant/auth/providers/command_line.py index 58a2cac1fc5..203bc191193 100644 --- a/homeassistant/auth/providers/command_line.py +++ b/homeassistant/auth/providers/command_line.py @@ -1,20 +1,18 @@ """Auth provider that validates credentials via an external command.""" -from typing import Any, Dict, Optional, cast - import asyncio.subprocess import collections import logging import os +from typing import Any, Dict, Optional, cast import voluptuous as vol from homeassistant.exceptions import HomeAssistantError -from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, LoginFlow +from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow from ..models import Credentials, UserMeta - CONF_COMMAND = "command" CONF_ARGS = "args" CONF_META = "meta" diff --git a/homeassistant/auth/providers/homeassistant.py b/homeassistant/auth/providers/homeassistant.py index 265a24a4b28..9ddbf4189f7 100644 --- a/homeassistant/auth/providers/homeassistant.py +++ b/homeassistant/auth/providers/homeassistant.py @@ -3,21 +3,18 @@ import asyncio import base64 from collections import OrderedDict import logging - from typing import Any, Dict, List, Optional, Set, cast import bcrypt import voluptuous as vol from homeassistant.const import CONF_ID -from homeassistant.core import callback, HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError -from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, LoginFlow - +from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow from ..models import Credentials, UserMeta - STORAGE_VERSION = 1 STORAGE_KEY = "auth_provider.homeassistant" diff --git a/homeassistant/auth/providers/insecure_example.py b/homeassistant/auth/providers/insecure_example.py index 37859f5ed0e..70014a236cd 100644 --- a/homeassistant/auth/providers/insecure_example.py +++ b/homeassistant/auth/providers/insecure_example.py @@ -5,13 +5,12 @@ from typing import Any, Dict, Optional, cast import voluptuous as vol -from homeassistant.exceptions import HomeAssistantError from homeassistant.core import callback +from homeassistant.exceptions import HomeAssistantError -from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, LoginFlow +from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow from ..models import Credentials, UserMeta - USER_SCHEMA = vol.Schema( { vol.Required("username"): str, diff --git a/homeassistant/auth/providers/legacy_api_password.py b/homeassistant/auth/providers/legacy_api_password.py index 018886388df..15ba1dfc14c 100644 --- a/homeassistant/auth/providers/legacy_api_password.py +++ b/homeassistant/auth/providers/legacy_api_password.py @@ -12,9 +12,9 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv -from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, LoginFlow +from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow from .. import AuthManager -from ..models import Credentials, UserMeta, User +from ..models import Credentials, User, UserMeta AUTH_PROVIDER_TYPE = "legacy_api_password" CONF_API_PASSWORD = "api_password" diff --git a/homeassistant/auth/providers/trusted_networks.py b/homeassistant/auth/providers/trusted_networks.py index f71be436acf..bc995368fec 100644 --- a/homeassistant/auth/providers/trusted_networks.py +++ b/homeassistant/auth/providers/trusted_networks.py @@ -3,15 +3,16 @@ It shows list of users if access from trusted network. Abort login flow if not access from trusted network. """ -from ipaddress import ip_network, IPv4Address, IPv6Address, IPv4Network, IPv6Network +from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network, ip_network from typing import Any, Dict, List, Optional, Union, cast import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError -from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, LoginFlow +import homeassistant.helpers.config_validation as cv + +from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow from ..models import Credentials, UserMeta IPAddress = Union[IPv4Address, IPv6Address] diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 312c739cd72..79de0b25453 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -1,22 +1,22 @@ """Provide methods to bootstrap a Home Assistant instance.""" import asyncio +from collections import OrderedDict import logging import logging.handlers import os import sys from time import time -from collections import OrderedDict -from typing import Any, Optional, Dict, Set +from typing import Any, Dict, Optional, Set import voluptuous as vol -from homeassistant import core, config as conf_util, config_entries, loader +from homeassistant import config as conf_util, config_entries, core, loader from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE +from homeassistant.exceptions import HomeAssistantError from homeassistant.setup import async_setup_component from homeassistant.util.logging import AsyncHandler from homeassistant.util.package import async_get_user_site, is_virtual_env from homeassistant.util.yaml import clear_secret_cache -from homeassistant.exceptions import HomeAssistantError _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/config.py b/homeassistant/config.py index 71628be8006..353a717778b 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -1,59 +1,59 @@ """Module to help with parsing and generating configuration files.""" -from collections import OrderedDict - # pylint: disable=no-name-in-module +from collections import OrderedDict from distutils.version import LooseVersion # pylint: disable=import-error import logging import os import re import shutil -from typing import Any, Tuple, Optional, Dict, Union, Callable, Sequence, Set from types import ModuleType +from typing import Any, Callable, Dict, Optional, Sequence, Set, Tuple, Union + import voluptuous as vol from voluptuous.humanize import humanize_error from homeassistant import auth from homeassistant.auth import ( - providers as auth_providers, mfa_modules as auth_mfa_modules, + providers as auth_providers, ) from homeassistant.const import ( + ATTR_ASSUMED_STATE, ATTR_FRIENDLY_NAME, ATTR_HIDDEN, - ATTR_ASSUMED_STATE, + CONF_AUTH_MFA_MODULES, + CONF_AUTH_PROVIDERS, + CONF_CUSTOMIZE, + CONF_CUSTOMIZE_DOMAIN, + CONF_CUSTOMIZE_GLOB, + CONF_ELEVATION, + CONF_ID, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, CONF_PACKAGES, - CONF_UNIT_SYSTEM, - CONF_TIME_ZONE, - CONF_ELEVATION, - CONF_UNIT_SYSTEM_IMPERIAL, CONF_TEMPERATURE_UNIT, + CONF_TIME_ZONE, + CONF_TYPE, + CONF_UNIT_SYSTEM, + CONF_UNIT_SYSTEM_IMPERIAL, + CONF_WHITELIST_EXTERNAL_DIRS, TEMP_CELSIUS, __version__, - CONF_CUSTOMIZE, - CONF_CUSTOMIZE_DOMAIN, - CONF_CUSTOMIZE_GLOB, - CONF_WHITELIST_EXTERNAL_DIRS, - CONF_AUTH_PROVIDERS, - CONF_AUTH_MFA_MODULES, - CONF_TYPE, - CONF_ID, ) from homeassistant.core import DOMAIN as CONF_CORE, SOURCE_YAML, HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers import config_per_platform, extract_domain_configs +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity_values import EntityValues from homeassistant.loader import Integration, IntegrationNotFound from homeassistant.requirements import ( - async_get_integration_with_requirements, RequirementsNotFound, + async_get_integration_with_requirements, ) -from homeassistant.util.yaml import load_yaml, SECRET_YAML from homeassistant.util.package import is_docker_env -import homeassistant.helpers.config_validation as cv from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM -from homeassistant.helpers.entity_values import EntityValues -from homeassistant.helpers import config_per_platform, extract_domain_configs +from homeassistant.util.yaml import SECRET_YAML, load_yaml _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index ae3aeebb1ee..07a287c387c 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -1,21 +1,20 @@ """Manage config entries in Home Assistant.""" import asyncio -import logging import functools -import uuid +import logging from typing import Any, Callable, Dict, List, Optional, Set, cast +import uuid import weakref import attr from homeassistant import data_entry_flow, loader -from homeassistant.core import callback, HomeAssistant -from homeassistant.exceptions import HomeAssistantError, ConfigEntryNotReady -from homeassistant.setup import async_setup_component, async_process_deps_reqs -from homeassistant.util.decorator import Registry +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError from homeassistant.helpers import entity_registry from homeassistant.helpers.event import Event - +from homeassistant.setup import async_process_deps_reqs, async_setup_component +from homeassistant.util.decorator import Registry _LOGGER = logging.getLogger(__name__) _UNDEF: dict = {} diff --git a/homeassistant/core.py b/homeassistant/core.py index 2859e0fe157..0002019fdfa 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -14,65 +14,59 @@ import os import pathlib import threading from time import monotonic -import uuid - from types import MappingProxyType from typing import ( - Optional, - Any, - Callable, - List, - TypeVar, - Dict, - Coroutine, - Set, TYPE_CHECKING, + Any, Awaitable, + Callable, + Coroutine, + Dict, + List, Mapping, + Optional, + Set, + TypeVar, ) +import uuid from async_timeout import timeout import attr import voluptuous as vol +from homeassistant import loader, util from homeassistant.const import ( ATTR_DOMAIN, ATTR_FRIENDLY_NAME, ATTR_NOW, + ATTR_SECONDS, ATTR_SERVICE, ATTR_SERVICE_DATA, - ATTR_SECONDS, CONF_UNIT_SYSTEM_IMPERIAL, EVENT_CALL_SERVICE, EVENT_CORE_CONFIG_UPDATE, + EVENT_HOMEASSISTANT_CLOSE, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, - EVENT_HOMEASSISTANT_CLOSE, - EVENT_SERVICE_REMOVED, EVENT_SERVICE_REGISTERED, + EVENT_SERVICE_REMOVED, EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, EVENT_TIMER_OUT_OF_SYNC, MATCH_ALL, __version__, ) -from homeassistant import loader from homeassistant.exceptions import ( HomeAssistantError, InvalidEntityFormatError, InvalidStateError, - Unauthorized, ServiceNotFound, + Unauthorized, ) -from homeassistant.util.async_ import run_callback_threadsafe, fire_coroutine_threadsafe -from homeassistant import util -import homeassistant.util.dt as dt_util from homeassistant.util import location, slugify -from homeassistant.util.unit_system import ( - UnitSystem, - IMPERIAL_SYSTEM, - METRIC_SYSTEM, -) +from homeassistant.util.async_ import fire_coroutine_threadsafe, run_callback_threadsafe +import homeassistant.util.dt as dt_util +from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM, UnitSystem # Typing imports that create a circular dependency if TYPE_CHECKING: diff --git a/homeassistant/data_entry_flow.py b/homeassistant/data_entry_flow.py index 58d8e4ea131..e7432cd52f7 100644 --- a/homeassistant/data_entry_flow.py +++ b/homeassistant/data_entry_flow.py @@ -1,9 +1,11 @@ """Classes to help gather user submissions.""" import logging -from typing import Dict, Any, Callable, List, Optional +from typing import Any, Callable, Dict, List, Optional import uuid + import voluptuous as vol -from .core import callback, HomeAssistant + +from .core import HomeAssistant, callback from .exceptions import HomeAssistantError _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/exceptions.py b/homeassistant/exceptions.py index 6147e26c809..745d80d386b 100644 --- a/homeassistant/exceptions.py +++ b/homeassistant/exceptions.py @@ -1,5 +1,6 @@ """The exceptions used by Home Assistant.""" -from typing import Optional, Tuple, TYPE_CHECKING +from typing import TYPE_CHECKING, Optional, Tuple + import jinja2 if TYPE_CHECKING: diff --git a/homeassistant/helpers/__init__.py b/homeassistant/helpers/__init__.py index fe60ffc4b33..125d90e1162 100644 --- a/homeassistant/helpers/__init__.py +++ b/homeassistant/helpers/__init__.py @@ -1,6 +1,6 @@ """Helper methods for components within Home Assistant.""" import re -from typing import Any, Iterable, Tuple, Sequence, Dict +from typing import Any, Dict, Iterable, Sequence, Tuple from homeassistant.const import CONF_PLATFORM diff --git a/homeassistant/helpers/aiohttp_client.py b/homeassistant/helpers/aiohttp_client.py index 7f1579cd2c6..eee891b7f88 100644 --- a/homeassistant/helpers/aiohttp_client.py +++ b/homeassistant/helpers/aiohttp_client.py @@ -1,18 +1,17 @@ """Helper for aiohttp webclient stuff.""" import asyncio -import sys from ssl import SSLContext -from typing import Any, Awaitable, Optional, cast -from typing import Union +import sys +from typing import Any, Awaitable, Optional, Union, cast import aiohttp -from aiohttp.hdrs import USER_AGENT, CONTENT_TYPE from aiohttp import web -from aiohttp.web_exceptions import HTTPGatewayTimeout, HTTPBadGateway +from aiohttp.hdrs import CONTENT_TYPE, USER_AGENT +from aiohttp.web_exceptions import HTTPBadGateway, HTTPGatewayTimeout import async_timeout -from homeassistant.core import callback, Event from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE, __version__ +from homeassistant.core import Event, callback from homeassistant.helpers.typing import HomeAssistantType from homeassistant.loader import bind_hass from homeassistant.util import ssl as ssl_util diff --git a/homeassistant/helpers/area_registry.py b/homeassistant/helpers/area_registry.py index e75b195d386..58abecffb8b 100644 --- a/homeassistant/helpers/area_registry.py +++ b/homeassistant/helpers/area_registry.py @@ -1,10 +1,9 @@ """Provide a way to connect devices to one physical location.""" -import logging -import uuid from asyncio import Event from collections import OrderedDict -from typing import MutableMapping -from typing import Iterable, Optional, cast +import logging +from typing import Iterable, MutableMapping, Optional, cast +import uuid import attr diff --git a/homeassistant/helpers/check_config.py b/homeassistant/helpers/check_config.py index 4052a94b9de..81e654247b7 100644 --- a/homeassistant/helpers/check_config.py +++ b/homeassistant/helpers/check_config.py @@ -6,26 +6,24 @@ import attr import voluptuous as vol from homeassistant import loader -from homeassistant.core import HomeAssistant from homeassistant.config import ( CONF_CORE, - CORE_CONFIG_SCHEMA, CONF_PACKAGES, - merge_packages_config, + CORE_CONFIG_SCHEMA, _format_config_error, + config_per_platform, + extract_domain_configs, find_config_file, load_yaml_config_file, - extract_domain_configs, - config_per_platform, + merge_packages_config, ) -from homeassistant.requirements import ( - async_get_integration_with_requirements, - RequirementsNotFound, -) - -import homeassistant.util.yaml.loader as yaml_loader +from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError - +from homeassistant.requirements import ( + RequirementsNotFound, + async_get_integration_with_requirements, +) +import homeassistant.util.yaml.loader as yaml_loader # mypy: allow-untyped-calls, allow-untyped-defs, no-warn-return-any diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index df82ba6076f..c02c49ce311 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -6,9 +6,6 @@ import logging import sys from typing import Callable, Container, Optional, Union, cast -from homeassistant.helpers.template import Template -from homeassistant.helpers.typing import ConfigType, TemplateVarsType -from homeassistant.core import HomeAssistant, State from homeassistant.components import zone as zone_cmp from homeassistant.components.device_automation import ( async_get_device_automation_platform, @@ -34,11 +31,14 @@ from homeassistant.const import ( SUN_EVENT_SUNSET, WEEKDAYS, ) -from homeassistant.exceptions import TemplateError, HomeAssistantError +from homeassistant.core import HomeAssistant, State +from homeassistant.exceptions import HomeAssistantError, TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.sun import get_astral_event_date -import homeassistant.util.dt as dt_util +from homeassistant.helpers.template import Template +from homeassistant.helpers.typing import ConfigType, TemplateVarsType from homeassistant.util.async_ import run_callback_threadsafe +import homeassistant.util.dt as dt_util FROM_CONFIG_FORMAT = "{}_from_config" ASYNC_FROM_CONFIG_FORMAT = "async_{}_from_config" diff --git a/homeassistant/helpers/config_entry_flow.py b/homeassistant/helpers/config_entry_flow.py index 41f90effb89..323c6907411 100644 --- a/homeassistant/helpers/config_entry_flow.py +++ b/homeassistant/helpers/config_entry_flow.py @@ -1,6 +1,8 @@ """Helpers for data entry flows for config entries.""" -from typing import Callable, Awaitable, Union +from typing import Awaitable, Callable, Union + from homeassistant import config_entries + from .typing import HomeAssistantType # mypy: allow-untyped-defs, no-check-untyped-defs diff --git a/homeassistant/helpers/config_entry_oauth2_flow.py b/homeassistant/helpers/config_entry_oauth2_flow.py index dc3d3c91f27..4ea82f5f047 100644 --- a/homeassistant/helpers/config_entry_oauth2_flow.py +++ b/homeassistant/helpers/config_entry_oauth2_flow.py @@ -5,26 +5,25 @@ This module exists of the following parts: - OAuth2 implementation that works with local provided client ID/secret """ +from abc import ABC, ABCMeta, abstractmethod import asyncio -from abc import ABCMeta, ABC, abstractmethod import logging -from typing import Optional, Any, Dict, cast, Awaitable, Callable import time +from typing import Any, Awaitable, Callable, Dict, Optional, cast +from aiohttp import client, web import async_timeout -from aiohttp import web, client import jwt import voluptuous as vol from yarl import URL -from homeassistant.auth.util import generate_secret -from homeassistant.core import HomeAssistant, callback from homeassistant import config_entries +from homeassistant.auth.util import generate_secret from homeassistant.components.http import HomeAssistantView +from homeassistant.core import HomeAssistant, callback from .aiohttp_client import async_get_clientsession - DATA_JWT_SECRET = "oauth2_jwt_secret" DATA_VIEW_REGISTERED = "oauth2_view_reg" DATA_IMPLEMENTATIONS = "oauth2_impl" diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 948fb017d9d..0f5b4e13dc2 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -1,17 +1,17 @@ """Helpers for config validation using voluptuous.""" -import inspect -import logging -import os -import re from datetime import ( - timedelta, + date as date_sys, datetime as datetime_sys, time as time_sys, - date as date_sys, + timedelta, ) -from socket import _GLOBAL_DEFAULT_TIMEOUT +import inspect +import logging from numbers import Number -from typing import Any, Union, TypeVar, Callable, List, Dict, Optional +import os +import re +from socket import _GLOBAL_DEFAULT_TIMEOUT +from typing import Any, Callable, Dict, List, Optional, TypeVar, Union from urllib.parse import urlparse from uuid import UUID @@ -19,8 +19,9 @@ from pkg_resources import parse_version import voluptuous as vol import voluptuous_serialize -import homeassistant.util.dt as dt_util from homeassistant.const import ( + ATTR_AREA_ID, + ATTR_ENTITY_ID, CONF_ABOVE, CONF_ALIAS, CONF_BELOW, @@ -33,10 +34,10 @@ from homeassistant.const import ( CONF_PLATFORM, CONF_SCAN_INTERVAL, CONF_STATE, + CONF_TIMEOUT, CONF_UNIT_SYSTEM_IMPERIAL, CONF_UNIT_SYSTEM_METRIC, CONF_VALUE_TEMPLATE, - CONF_TIMEOUT, ENTITY_MATCH_ALL, SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET, @@ -44,14 +45,12 @@ from homeassistant.const import ( TEMP_FAHRENHEIT, WEEKDAYS, __version__, - ATTR_AREA_ID, - ATTR_ENTITY_ID, ) -from homeassistant.core import valid_entity_id, split_entity_id +from homeassistant.core import split_entity_id, valid_entity_id from homeassistant.exceptions import TemplateError from homeassistant.helpers.logging import KeywordStyleAdapter from homeassistant.util import slugify as util_slugify - +import homeassistant.util.dt as dt_util # mypy: allow-untyped-calls, allow-untyped-defs # mypy: no-check-untyped-defs, no-warn-return-any diff --git a/homeassistant/helpers/data_entry_flow.py b/homeassistant/helpers/data_entry_flow.py index 1d471052474..ac5fb608675 100644 --- a/homeassistant/helpers/data_entry_flow.py +++ b/homeassistant/helpers/data_entry_flow.py @@ -2,11 +2,10 @@ import voluptuous as vol -from homeassistant import data_entry_flow, config_entries +from homeassistant import config_entries, data_entry_flow from homeassistant.components.http import HomeAssistantView from homeassistant.components.http.data_validator import RequestDataValidator - # mypy: allow-untyped-calls, allow-untyped-defs diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index 456678edac7..2ff444da89f 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -1,9 +1,9 @@ """Provide a way to connect entities belonging to one device.""" -import logging -import uuid from asyncio import Event from collections import OrderedDict +import logging from typing import List, Optional, cast +import uuid import attr @@ -12,7 +12,6 @@ from homeassistant.loader import bind_hass from .typing import HomeAssistantType - # mypy: allow-untyped-calls, allow-untyped-defs # mypy: no-check-untyped-defs, no-warn-return-any diff --git a/homeassistant/helpers/discovery.py b/homeassistant/helpers/discovery.py index bc1094613bb..8e4def77440 100644 --- a/homeassistant/helpers/discovery.py +++ b/homeassistant/helpers/discovery.py @@ -5,14 +5,12 @@ There are two different types of discoveries that can be fired/listened for. - listen_platform/discover_platform is for platforms. These are used by components to allow discovery of their platforms. """ -from homeassistant import setup, core -from homeassistant.loader import bind_hass +from homeassistant import core, setup from homeassistant.const import ATTR_DISCOVERED, ATTR_SERVICE, EVENT_PLATFORM_DISCOVERED from homeassistant.exceptions import HomeAssistantError -from homeassistant.loader import DEPENDENCY_BLACKLIST +from homeassistant.loader import DEPENDENCY_BLACKLIST, bind_hass from homeassistant.util.async_ import run_callback_threadsafe - # mypy: allow-untyped-defs, no-check-untyped-defs EVENT_LOAD_PLATFORM = "load_platform.{}" diff --git a/homeassistant/helpers/dispatcher.py b/homeassistant/helpers/dispatcher.py index 81582f4fa54..a4e624f119f 100644 --- a/homeassistant/helpers/dispatcher.py +++ b/homeassistant/helpers/dispatcher.py @@ -6,8 +6,8 @@ from homeassistant.core import callback from homeassistant.loader import bind_hass from homeassistant.util.async_ import run_callback_threadsafe from homeassistant.util.logging import catch_log_exception -from .typing import HomeAssistantType +from .typing import HomeAssistantType _LOGGER = logging.getLogger(__name__) DATA_DISPATCHER = "dispatcher" diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index ed656061401..531444b9d1e 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -2,16 +2,20 @@ from abc import ABC import asyncio from datetime import datetime, timedelta -import logging import functools as ft +import logging from timeit import default_timer as timer from typing import Any, Dict, Iterable, List, Optional, Union +from homeassistant.config import DATA_CUSTOMIZE from homeassistant.const import ( ATTR_ASSUMED_STATE, + ATTR_DEVICE_CLASS, + ATTR_ENTITY_PICTURE, ATTR_FRIENDLY_NAME, ATTR_HIDDEN, ATTR_ICON, + ATTR_SUPPORTED_FEATURES, ATTR_UNIT_OF_MEASUREMENT, DEVICE_DEFAULT_NAME, STATE_OFF, @@ -20,22 +24,16 @@ from homeassistant.const import ( STATE_UNKNOWN, TEMP_CELSIUS, TEMP_FAHRENHEIT, - ATTR_ENTITY_PICTURE, - ATTR_SUPPORTED_FEATURES, - ATTR_DEVICE_CLASS, ) +from homeassistant.core import CALLBACK_TYPE, Context, HomeAssistant, callback +from homeassistant.exceptions import NoEntitySpecifiedError from homeassistant.helpers.entity_platform import EntityPlatform from homeassistant.helpers.entity_registry import ( EVENT_ENTITY_REGISTRY_UPDATED, RegistryEntry, ) -from homeassistant.core import HomeAssistant, callback, CALLBACK_TYPE, Context -from homeassistant.config import DATA_CUSTOMIZE -from homeassistant.exceptions import NoEntitySpecifiedError -from homeassistant.util import ensure_unique_string, slugify +from homeassistant.util import dt as dt_util, ensure_unique_string, slugify from homeassistant.util.async_ import run_callback_threadsafe -from homeassistant.util import dt as dt_util - # mypy: allow-untyped-defs, no-check-untyped-defs, no-warn-return-any diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index 63d1b21fc9a..becd96bf5f3 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -5,11 +5,10 @@ from itertools import chain import logging from homeassistant import config as conf_util -from homeassistant.setup import async_prepare_setup_platform from homeassistant.const import ( ATTR_ENTITY_ID, - CONF_SCAN_INTERVAL, CONF_ENTITY_NAMESPACE, + CONF_SCAN_INTERVAL, ENTITY_MATCH_ALL, ) from homeassistant.core import callback @@ -17,10 +16,11 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_per_platform, discovery from homeassistant.helpers.config_validation import make_entity_service_schema from homeassistant.helpers.service import async_extract_entity_ids -from homeassistant.loader import bind_hass, async_get_integration +from homeassistant.loader import async_get_integration, bind_hass +from homeassistant.setup import async_prepare_setup_platform from homeassistant.util import slugify -from .entity_platform import EntityPlatform +from .entity_platform import EntityPlatform # mypy: allow-untyped-defs, no-check-untyped-defs diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 376a6e23e9a..133d1a5841f 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -4,13 +4,12 @@ from contextvars import ContextVar from typing import Optional from homeassistant.const import DEVICE_DEFAULT_NAME -from homeassistant.core import callback, valid_entity_id, split_entity_id +from homeassistant.core import callback, split_entity_id, valid_entity_id from homeassistant.exceptions import HomeAssistantError, PlatformNotReady from homeassistant.util.async_ import run_callback_threadsafe from .entity_registry import DISABLED_INTEGRATION -from .event import async_track_time_interval, async_call_later - +from .event import async_call_later, async_track_time_interval # mypy: allow-untyped-defs, no-check-untyped-defs diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 08f29a9fb3e..a5bd62d973c 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -23,7 +23,6 @@ from homeassistant.util.yaml import load_yaml from .typing import HomeAssistantType - # mypy: allow-untyped-defs, no-check-untyped-defs PATH_REGISTRY = "entity_registry.yaml" diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index 715344a3969..b3c8af6f50c 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -5,23 +5,22 @@ from typing import Any, Callable, Dict, Iterable, Optional, Union, cast import attr -from homeassistant.loader import bind_hass -from homeassistant.helpers.sun import get_astral_event_next -from homeassistant.helpers.template import Template -from homeassistant.core import HomeAssistant, callback, CALLBACK_TYPE, Event, State from homeassistant.const import ( ATTR_NOW, + EVENT_CORE_CONFIG_UPDATE, EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL, SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET, - EVENT_CORE_CONFIG_UPDATE, ) +from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, State, callback +from homeassistant.helpers.sun import get_astral_event_next +from homeassistant.helpers.template import Template +from homeassistant.loader import bind_hass from homeassistant.util import dt as dt_util from homeassistant.util.async_ import run_callback_threadsafe - # PyLint does not like the use of threaded_listener_factory # pylint: disable=invalid-name diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index 12b346603f0..181d1baebc0 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -5,13 +5,12 @@ from typing import Any, Callable, Dict, Iterable, Optional import voluptuous as vol -from homeassistant.const import ATTR_SUPPORTED_FEATURES -from homeassistant.core import callback, State, T, Context +from homeassistant.const import ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES +from homeassistant.core import Context, State, T, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv from homeassistant.helpers.typing import HomeAssistantType from homeassistant.loader import bind_hass -from homeassistant.const import ATTR_ENTITY_ID _LOGGER = logging.getLogger(__name__) _SlotsType = Dict[str, Any] diff --git a/homeassistant/helpers/logging.py b/homeassistant/helpers/logging.py index dd9e3833801..7b2507d9e05 100644 --- a/homeassistant/helpers/logging.py +++ b/homeassistant/helpers/logging.py @@ -2,7 +2,6 @@ import inspect import logging - # mypy: allow-untyped-defs, no-check-untyped-defs diff --git a/homeassistant/helpers/network.py b/homeassistant/helpers/network.py index 671e7f1fa56..a446b575077 100644 --- a/homeassistant/helpers/network.py +++ b/homeassistant/helpers/network.py @@ -1,6 +1,6 @@ """Network helpers.""" -from typing import Optional, cast from ipaddress import ip_address +from typing import Optional, cast import yarl diff --git a/homeassistant/helpers/restore_state.py b/homeassistant/helpers/restore_state.py index 5d47f34b002..4a67193734e 100644 --- a/homeassistant/helpers/restore_state.py +++ b/homeassistant/helpers/restore_state.py @@ -1,24 +1,23 @@ """Support for restoring entity states on startup.""" import asyncio +from datetime import datetime, timedelta import logging -from datetime import timedelta, datetime -from typing import Any, Dict, List, Set, Optional +from typing import Any, Dict, List, Optional, Set +from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP from homeassistant.core import ( - HomeAssistant, - callback, - State, CoreState, + HomeAssistant, + State, + callback, valid_entity_id, ) -from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP -import homeassistant.util.dt as dt_util +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_time_interval -from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.json import JSONEncoder from homeassistant.helpers.storage import Store - +import homeassistant.util.dt as dt_util # mypy: allow-untyped-calls, allow-untyped-defs, no-check-untyped-defs # mypy: no-warn-return-any diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 21dd0b71487..8e0faa2ce4d 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -1,16 +1,16 @@ """Helpers to execute scripts.""" import asyncio -import logging from contextlib import suppress from datetime import datetime from itertools import islice -from typing import Optional, Sequence, Callable, Dict, List, Set, Tuple, Any +import logging +from typing import Any, Callable, Dict, List, Optional, Sequence, Set, Tuple import voluptuous as vol +from homeassistant import exceptions import homeassistant.components.device_automation as device_automation import homeassistant.components.scene as scene -from homeassistant.core import HomeAssistant, Context, callback, CALLBACK_TYPE from homeassistant.const import ( ATTR_ENTITY_ID, CONF_CONDITION, @@ -19,21 +19,20 @@ from homeassistant.const import ( CONF_TIMEOUT, SERVICE_TURN_ON, ) -from homeassistant import exceptions +from homeassistant.core import CALLBACK_TYPE, Context, HomeAssistant, callback from homeassistant.helpers import ( - service, condition, - template as template, config_validation as cv, + service, + template as template, ) from homeassistant.helpers.event import ( async_track_point_in_utc_time, async_track_template, ) from homeassistant.helpers.typing import ConfigType -import homeassistant.util.dt as date_util from homeassistant.util.async_ import run_callback_threadsafe - +import homeassistant.util.dt as date_util # mypy: allow-untyped-calls, allow-untyped-defs, no-check-untyped-defs diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index 45393dc0486..5381f765993 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -7,7 +7,7 @@ from typing import Callable import voluptuous as vol from homeassistant.auth.permissions.const import CAT_ENTITIES, POLICY_CONTROL -from homeassistant.const import ATTR_ENTITY_ID, ENTITY_MATCH_ALL, ATTR_AREA_ID +from homeassistant.const import ATTR_AREA_ID, ATTR_ENTITY_ID, ENTITY_MATCH_ALL import homeassistant.core as ha from homeassistant.exceptions import ( HomeAssistantError, @@ -16,12 +16,11 @@ from homeassistant.exceptions import ( UnknownUser, ) from homeassistant.helpers import template, typing +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.typing import HomeAssistantType from homeassistant.loader import async_get_integration, bind_hass from homeassistant.util.yaml import load_yaml from homeassistant.util.yaml.loader import JSON_TYPE -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.typing import HomeAssistantType - # mypy: allow-untyped-defs, no-check-untyped-defs diff --git a/homeassistant/helpers/signal.py b/homeassistant/helpers/signal.py index 12792918742..53802a2a119 100644 --- a/homeassistant/helpers/signal.py +++ b/homeassistant/helpers/signal.py @@ -4,8 +4,8 @@ import signal import sys from types import FrameType -from homeassistant.core import callback, HomeAssistant from homeassistant.const import RESTART_EXIT_CODE +from homeassistant.core import HomeAssistant, callback from homeassistant.loader import bind_hass _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/helpers/state.py b/homeassistant/helpers/state.py index abc97bf1f8a..60e6acc8797 100644 --- a/homeassistant/helpers/state.py +++ b/homeassistant/helpers/state.py @@ -1,13 +1,11 @@ """Helpers that help with state related things.""" import asyncio +from collections import defaultdict import datetime as dt import logging -from collections import defaultdict from types import ModuleType, TracebackType from typing import Dict, Iterable, List, Optional, Type, Union -from homeassistant.loader import bind_hass, async_get_integration, IntegrationNotFound -import homeassistant.util.dt as dt_util from homeassistant.components.sun import STATE_ABOVE_HORIZON, STATE_BELOW_HORIZON from homeassistant.const import ( STATE_CLOSED, @@ -21,6 +19,9 @@ from homeassistant.const import ( STATE_UNLOCKED, ) from homeassistant.core import Context, State +from homeassistant.loader import IntegrationNotFound, async_get_integration, bind_hass +import homeassistant.util.dt as dt_util + from .typing import HomeAssistantType _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/helpers/storage.py b/homeassistant/helpers/storage.py index bd18eebfb25..aed6da37518 100644 --- a/homeassistant/helpers/storage.py +++ b/homeassistant/helpers/storage.py @@ -3,14 +3,13 @@ import asyncio from json import JSONEncoder import logging import os -from typing import Dict, List, Optional, Callable, Union, Any, Type +from typing import Any, Callable, Dict, List, Optional, Type, Union from homeassistant.const import EVENT_HOMEASSISTANT_STOP -from homeassistant.core import HomeAssistant, callback, CALLBACK_TYPE +from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback +from homeassistant.helpers.event import async_call_later from homeassistant.loader import bind_hass from homeassistant.util import json as json_util -from homeassistant.helpers.event import async_call_later - # mypy: allow-untyped-calls, allow-untyped-defs, no-warn-return-any # mypy: no-check-untyped-defs diff --git a/homeassistant/helpers/sun.py b/homeassistant/helpers/sun.py index 9fa6e074bdd..45ff06f16de 100644 --- a/homeassistant/helpers/sun.py +++ b/homeassistant/helpers/sun.py @@ -1,11 +1,12 @@ """Helpers for sun events.""" import datetime -from typing import Optional, Union, TYPE_CHECKING +from typing import TYPE_CHECKING, Optional, Union from homeassistant.const import SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET from homeassistant.core import callback -from homeassistant.util import dt as dt_util from homeassistant.loader import bind_hass +from homeassistant.util import dt as dt_util + from .typing import HomeAssistantType if TYPE_CHECKING: diff --git a/homeassistant/helpers/system_info.py b/homeassistant/helpers/system_info.py index b552a634d4b..7d1d6f2b3e7 100644 --- a/homeassistant/helpers/system_info.py +++ b/homeassistant/helpers/system_info.py @@ -6,6 +6,7 @@ from typing import Dict from homeassistant.const import __version__ as current_version from homeassistant.loader import bind_hass from homeassistant.util.package import is_virtual_env + from .typing import HomeAssistantType diff --git a/homeassistant/helpers/temperature.py b/homeassistant/helpers/temperature.py index 30b428a9e17..e0846d6f893 100644 --- a/homeassistant/helpers/temperature.py +++ b/homeassistant/helpers/temperature.py @@ -2,9 +2,9 @@ from numbers import Number from typing import Optional +from homeassistant.const import PRECISION_HALVES, PRECISION_TENTHS from homeassistant.core import HomeAssistant from homeassistant.util.temperature import convert as convert_temperature -from homeassistant.const import PRECISION_HALVES, PRECISION_TENTHS def display_temp( diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 7dcf08ebf92..4bdee750034 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -1,12 +1,12 @@ """Template helper methods for rendering strings with Home Assistant data.""" import base64 +from datetime import datetime +from functools import wraps import json import logging import math import random import re -from datetime import datetime -from functools import wraps from typing import Any, Dict, Iterable, List, Optional, Union import jinja2 @@ -30,7 +30,6 @@ from homeassistant.loader import bind_hass from homeassistant.util import convert, dt as dt_util, location as loc_util from homeassistant.util.async_ import run_callback_threadsafe - # mypy: allow-untyped-calls, allow-untyped-defs # mypy: no-check-untyped-defs, no-warn-return-any diff --git a/homeassistant/helpers/translation.py b/homeassistant/helpers/translation.py index b9fd24c95e0..fe254ab8907 100644 --- a/homeassistant/helpers/translation.py +++ b/homeassistant/helpers/translation.py @@ -3,11 +3,12 @@ import logging from typing import Any, Dict, Iterable, Optional from homeassistant.loader import ( + async_get_config_flows, async_get_integration, bind_hass, - async_get_config_flows, ) from homeassistant.util.json import load_json + from .typing import HomeAssistantType _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/helpers/typing.py b/homeassistant/helpers/typing.py index f084c5fddbe..6e31301066c 100644 --- a/homeassistant/helpers/typing.py +++ b/homeassistant/helpers/typing.py @@ -1,5 +1,5 @@ """Typing Helpers for Home Assistant.""" -from typing import Dict, Any, Tuple, Optional +from typing import Any, Dict, Optional, Tuple import homeassistant.core diff --git a/homeassistant/loader.py b/homeassistant/loader.py index af76b073cd3..0e1ee8ae756 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -13,14 +13,14 @@ import pathlib import sys from types import ModuleType from typing import ( + TYPE_CHECKING, + Any, + Callable, + Dict, + List, Optional, Set, - TYPE_CHECKING, - Callable, - Any, TypeVar, - List, - Dict, Union, cast, ) diff --git a/homeassistant/requirements.py b/homeassistant/requirements.py index eb8aeeecfae..bd52253cdb1 100644 --- a/homeassistant/requirements.py +++ b/homeassistant/requirements.py @@ -1,14 +1,14 @@ """Module to handle installing requirements.""" import asyncio -from pathlib import Path import logging import os +from pathlib import Path from typing import Any, Dict, List, Optional, Set -from homeassistant.exceptions import HomeAssistantError -import homeassistant.util.package as pkg_util from homeassistant.core import HomeAssistant -from homeassistant.loader import async_get_integration, Integration +from homeassistant.exceptions import HomeAssistantError +from homeassistant.loader import Integration, async_get_integration +import homeassistant.util.package as pkg_util DATA_PIP_LOCK = "pip_lock" DATA_PKG_CACHE = "pkg_cache" diff --git a/homeassistant/scripts/__init__.py b/homeassistant/scripts/__init__.py index ecac61895c5..b47450ab9dd 100644 --- a/homeassistant/scripts/__init__.py +++ b/homeassistant/scripts/__init__.py @@ -10,8 +10,7 @@ from typing import List, Optional, Sequence, Text from homeassistant.bootstrap import async_mount_local_lib_path from homeassistant.config import get_default_config_dir from homeassistant.requirements import pip_kwargs -from homeassistant.util.package import install_package, is_virtual_env, is_installed - +from homeassistant.util.package import install_package, is_installed, is_virtual_env # mypy: allow-untyped-defs, no-warn-return-any diff --git a/homeassistant/scripts/auth.py b/homeassistant/scripts/auth.py index e0a8b5cf117..66baf555306 100644 --- a/homeassistant/scripts/auth.py +++ b/homeassistant/scripts/auth.py @@ -6,9 +6,8 @@ import os from homeassistant.auth import auth_manager_from_config from homeassistant.auth.providers import homeassistant as hass_auth -from homeassistant.core import HomeAssistant from homeassistant.config import get_default_config_dir - +from homeassistant.core import HomeAssistant # mypy: allow-untyped-calls, allow-untyped-defs diff --git a/homeassistant/scripts/benchmark/__init__.py b/homeassistant/scripts/benchmark/__init__.py index 480f6fc9fde..58125bc4829 100644 --- a/homeassistant/scripts/benchmark/__init__.py +++ b/homeassistant/scripts/benchmark/__init__.py @@ -11,7 +11,6 @@ from homeassistant import core from homeassistant.const import ATTR_NOW, EVENT_STATE_CHANGED, EVENT_TIME_CHANGED from homeassistant.util import dt as dt_util - # mypy: allow-untyped-calls, allow-untyped-defs, no-check-untyped-defs # mypy: no-warn-return-any diff --git a/homeassistant/scripts/check_config.py b/homeassistant/scripts/check_config.py index 3ac023115a1..46724fc7c10 100644 --- a/homeassistant/scripts/check_config.py +++ b/homeassistant/scripts/check_config.py @@ -1,19 +1,18 @@ """Script to check the configuration file.""" import argparse -import logging -import os from collections import OrderedDict from glob import glob -from typing import Dict, List, Sequence, Any, Tuple, Callable +import logging +import os +from typing import Any, Callable, Dict, List, Sequence, Tuple from unittest.mock import patch from homeassistant import bootstrap, core from homeassistant.config import get_default_config_dir +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.check_config import async_check_ha_config_file import homeassistant.util.yaml.loader as yaml_loader -from homeassistant.exceptions import HomeAssistantError - # mypy: allow-untyped-calls, allow-untyped-defs diff --git a/homeassistant/scripts/credstash.py b/homeassistant/scripts/credstash.py index e77ae326cd7..f90ab5f793e 100644 --- a/homeassistant/scripts/credstash.py +++ b/homeassistant/scripts/credstash.py @@ -4,7 +4,6 @@ import getpass from homeassistant.util.yaml import _SECRET_NAMESPACE - # mypy: allow-untyped-defs REQUIREMENTS = ["credstash==1.15.0"] diff --git a/homeassistant/scripts/ensure_config.py b/homeassistant/scripts/ensure_config.py index c5cf69283e6..cb2c4088049 100644 --- a/homeassistant/scripts/ensure_config.py +++ b/homeassistant/scripts/ensure_config.py @@ -2,9 +2,8 @@ import argparse import os -from homeassistant.core import HomeAssistant import homeassistant.config as config_util - +from homeassistant.core import HomeAssistant # mypy: allow-untyped-calls, allow-untyped-defs diff --git a/homeassistant/scripts/macos/__init__.py b/homeassistant/scripts/macos/__init__.py index ceb3609dbdb..9d9c5cd8248 100644 --- a/homeassistant/scripts/macos/__init__.py +++ b/homeassistant/scripts/macos/__init__.py @@ -2,7 +2,6 @@ import os import time - # mypy: allow-untyped-calls, allow-untyped-defs diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 42296a4935d..01b13629e7b 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -2,16 +2,14 @@ import asyncio import logging.handlers from timeit import default_timer as timer - from types import ModuleType -from typing import Awaitable, Callable, Optional, Dict, List +from typing import Awaitable, Callable, Dict, List, Optional -from homeassistant import requirements, core, loader, config as conf_util +from homeassistant import config as conf_util, core, loader, requirements from homeassistant.config import async_notify_setup_error from homeassistant.const import EVENT_COMPONENT_LOADED, PLATFORM_FORMAT from homeassistant.exceptions import HomeAssistantError - _LOGGER = logging.getLogger(__name__) ATTR_COMPONENT = "component" diff --git a/homeassistant/util/__init__.py b/homeassistant/util/__init__.py index 408d1e370d4..f39fa5f1e55 100644 --- a/homeassistant/util/__init__.py +++ b/homeassistant/util/__init__.py @@ -1,23 +1,23 @@ """Helper methods for various modules.""" import asyncio from datetime import datetime, timedelta -import threading -import re import enum -import socket -import random -import string from functools import wraps +import random +import re +import socket +import string +import threading from types import MappingProxyType from typing import ( Any, + Callable, + Coroutine, + Iterable, + KeysView, Optional, TypeVar, - Callable, - KeysView, Union, - Iterable, - Coroutine, ) import slugify as unicode_slug diff --git a/homeassistant/util/aiohttp.py b/homeassistant/util/aiohttp.py index 1e36d2d4875..69911986f57 100644 --- a/homeassistant/util/aiohttp.py +++ b/homeassistant/util/aiohttp.py @@ -1,7 +1,7 @@ """Utilities to help with aiohttp.""" import json -from urllib.parse import parse_qsl from typing import Any, Dict, Optional +from urllib.parse import parse_qsl from multidict import CIMultiDict, MultiDict diff --git a/homeassistant/util/async_.py b/homeassistant/util/async_.py index 64bedfe2501..b1d3a7dd8e7 100644 --- a/homeassistant/util/async_.py +++ b/homeassistant/util/async_.py @@ -1,13 +1,11 @@ """Asyncio backports for Python 3.6 compatibility.""" -import concurrent.futures -import threading -import logging -from asyncio import coroutines -from asyncio.events import AbstractEventLoop - import asyncio -from asyncio import ensure_future -from typing import Any, Coroutine, Callable, TypeVar, Awaitable +from asyncio import coroutines, ensure_future +from asyncio.events import AbstractEventLoop +import concurrent.futures +import logging +import threading +from typing import Any, Awaitable, Callable, Coroutine, TypeVar _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/util/color.py b/homeassistant/util/color.py index 7361b711dd2..b56ecbbaa89 100644 --- a/homeassistant/util/color.py +++ b/homeassistant/util/color.py @@ -1,8 +1,8 @@ """Color util methods.""" -import math import colorsys +import math +from typing import List, Optional, Tuple -from typing import Tuple, List, Optional import attr # Official CSS3 colors from w3.org: diff --git a/homeassistant/util/distance.py b/homeassistant/util/distance.py index b9cce45cb5b..4fdc40bde2f 100644 --- a/homeassistant/util/distance.py +++ b/homeassistant/util/distance.py @@ -4,12 +4,12 @@ import logging from numbers import Number from homeassistant.const import ( - LENGTH_KILOMETERS, - LENGTH_MILES, - LENGTH_FEET, - LENGTH_METERS, - UNIT_NOT_RECOGNIZED_TEMPLATE, LENGTH, + LENGTH_FEET, + LENGTH_KILOMETERS, + LENGTH_METERS, + LENGTH_MILES, + UNIT_NOT_RECOGNIZED_TEMPLATE, ) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/util/dt.py b/homeassistant/util/dt.py index 1abb4294398..791b36a4236 100644 --- a/homeassistant/util/dt.py +++ b/homeassistant/util/dt.py @@ -1,7 +1,7 @@ """Helper methods to handle the time in Home Assistant.""" import datetime as dt import re -from typing import Any, Union, Optional, Tuple, List, cast, Dict +from typing import Any, Dict, List, Optional, Tuple, Union, cast import pytz import pytz.exceptions as pytzexceptions diff --git a/homeassistant/util/json.py b/homeassistant/util/json.py index 5b2ee316376..e975c878672 100644 --- a/homeassistant/util/json.py +++ b/homeassistant/util/json.py @@ -1,10 +1,9 @@ """JSON utility functions.""" -import logging -from typing import Union, List, Dict, Optional, Type - import json +import logging import os import tempfile +from typing import Dict, List, Optional, Type, Union from homeassistant.exceptions import HomeAssistantError diff --git a/homeassistant/util/location.py b/homeassistant/util/location.py index b572b3025a0..a617eba50f9 100644 --- a/homeassistant/util/location.py +++ b/homeassistant/util/location.py @@ -6,7 +6,7 @@ detect_location_info and elevation are mocked by default during tests. import asyncio import collections import math -from typing import Any, Optional, Tuple, Dict +from typing import Any, Dict, Optional, Tuple import aiohttp diff --git a/homeassistant/util/package.py b/homeassistant/util/package.py index 58a3db31bd3..24cf8309228 100644 --- a/homeassistant/util/package.py +++ b/homeassistant/util/package.py @@ -2,15 +2,14 @@ import asyncio import logging import os +from pathlib import Path from subprocess import PIPE, Popen import sys from typing import Optional from urllib.parse import urlparse -from pathlib import Path +from importlib_metadata import PackageNotFoundError, version import pkg_resources -from importlib_metadata import version, PackageNotFoundError - _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/util/pressure.py b/homeassistant/util/pressure.py index e394076800c..df791fd0235 100644 --- a/homeassistant/util/pressure.py +++ b/homeassistant/util/pressure.py @@ -4,13 +4,13 @@ import logging from numbers import Number from homeassistant.const import ( - PRESSURE_PA, + PRESSURE, PRESSURE_HPA, - PRESSURE_MBAR, PRESSURE_INHG, + PRESSURE_MBAR, + PRESSURE_PA, PRESSURE_PSI, UNIT_NOT_RECOGNIZED_TEMPLATE, - PRESSURE, ) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/util/ruamel_yaml.py b/homeassistant/util/ruamel_yaml.py index b7e8927888c..e5ffbb94afe 100644 --- a/homeassistant/util/ruamel_yaml.py +++ b/homeassistant/util/ruamel_yaml.py @@ -1,18 +1,18 @@ """ruamel.yaml utility functions.""" +from collections import OrderedDict import logging import os from os import O_CREAT, O_TRUNC, O_WRONLY, stat_result -from collections import OrderedDict -from typing import Union, List, Dict, Optional +from typing import Dict, List, Optional, Union import ruamel.yaml from ruamel.yaml import YAML +from ruamel.yaml.compat import StringIO from ruamel.yaml.constructor import SafeConstructor from ruamel.yaml.error import YAMLError -from ruamel.yaml.compat import StringIO -from homeassistant.util.yaml import secret_yaml from homeassistant.exceptions import HomeAssistantError +from homeassistant.util.yaml import secret_yaml _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/util/temperature.py b/homeassistant/util/temperature.py index 909cdac60d9..0b3edc6ef57 100644 --- a/homeassistant/util/temperature.py +++ b/homeassistant/util/temperature.py @@ -2,8 +2,8 @@ from homeassistant.const import ( TEMP_CELSIUS, TEMP_FAHRENHEIT, - UNIT_NOT_RECOGNIZED_TEMPLATE, TEMPERATURE, + UNIT_NOT_RECOGNIZED_TEMPLATE, ) diff --git a/homeassistant/util/unit_system.py b/homeassistant/util/unit_system.py index 23ac8f05025..a79c022be45 100644 --- a/homeassistant/util/unit_system.py +++ b/homeassistant/util/unit_system.py @@ -1,35 +1,37 @@ """Unit system helper class and methods.""" import logging -from typing import Optional from numbers import Number +from typing import Optional from homeassistant.const import ( - TEMP_CELSIUS, - TEMP_FAHRENHEIT, - LENGTH_MILES, + CONF_UNIT_SYSTEM_IMPERIAL, + CONF_UNIT_SYSTEM_METRIC, + LENGTH, LENGTH_KILOMETERS, - PRESSURE_PA, - PRESSURE_PSI, - VOLUME_LITERS, - VOLUME_GALLONS, + LENGTH_MILES, + MASS, MASS_GRAMS, MASS_KILOGRAMS, MASS_OUNCES, MASS_POUNDS, - CONF_UNIT_SYSTEM_METRIC, - CONF_UNIT_SYSTEM_IMPERIAL, - LENGTH, - MASS, PRESSURE, - VOLUME, + PRESSURE_PA, + PRESSURE_PSI, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, TEMPERATURE, UNIT_NOT_RECOGNIZED_TEMPLATE, + VOLUME, + VOLUME_GALLONS, + VOLUME_LITERS, +) +from homeassistant.util import ( + distance as distance_util, + pressure as pressure_util, + temperature as temperature_util, + volume as volume_util, ) -from homeassistant.util import temperature as temperature_util -from homeassistant.util import distance as distance_util -from homeassistant.util import pressure as pressure_util -from homeassistant.util import volume as volume_util _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/util/volume.py b/homeassistant/util/volume.py index 5a05b663522..2e033beb35c 100644 --- a/homeassistant/util/volume.py +++ b/homeassistant/util/volume.py @@ -2,13 +2,14 @@ import logging from numbers import Number + from homeassistant.const import ( + UNIT_NOT_RECOGNIZED_TEMPLATE, + VOLUME, + VOLUME_FLUID_OUNCE, + VOLUME_GALLONS, VOLUME_LITERS, VOLUME_MILLILITERS, - VOLUME_GALLONS, - VOLUME_FLUID_OUNCE, - VOLUME, - UNIT_NOT_RECOGNIZED_TEMPLATE, ) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/util/yaml/__init__.py b/homeassistant/util/yaml/__init__.py index 9cf33a401d3..106bdbf8ef5 100644 --- a/homeassistant/util/yaml/__init__.py +++ b/homeassistant/util/yaml/__init__.py @@ -1,9 +1,8 @@ """YAML utility functions.""" -from .const import SECRET_YAML, _SECRET_NAMESPACE +from .const import _SECRET_NAMESPACE, SECRET_YAML from .dumper import dump, save_yaml from .loader import clear_secret_cache, load_yaml, secret_yaml - __all__ = [ "SECRET_YAML", "_SECRET_NAMESPACE", diff --git a/homeassistant/util/yaml/dumper.py b/homeassistant/util/yaml/dumper.py index a53dc0cdd02..ffcd4917363 100644 --- a/homeassistant/util/yaml/dumper.py +++ b/homeassistant/util/yaml/dumper.py @@ -1,10 +1,10 @@ """Custom dumper and representers.""" from collections import OrderedDict + import yaml from .objects import NodeListClass - # mypy: allow-untyped-calls, no-warn-return-any diff --git a/homeassistant/util/yaml/loader.py b/homeassistant/util/yaml/loader.py index fec09a1d690..65422f231ba 100644 --- a/homeassistant/util/yaml/loader.py +++ b/homeassistant/util/yaml/loader.py @@ -1,13 +1,18 @@ """Custom loader.""" +from collections import OrderedDict +import fnmatch import logging import os import sys -import fnmatch -from collections import OrderedDict -from typing import Union, List, Dict, Iterator, overload, TypeVar +from typing import Dict, Iterator, List, TypeVar, Union, overload import yaml +from homeassistant.exceptions import HomeAssistantError + +from .const import _SECRET_NAMESPACE, SECRET_YAML +from .objects import NodeListClass, NodeStrClass + try: import keyring except ImportError: @@ -18,11 +23,6 @@ try: except ImportError: credstash = None -from homeassistant.exceptions import HomeAssistantError - -from .const import _SECRET_NAMESPACE, SECRET_YAML -from .objects import NodeListClass, NodeStrClass - # mypy: allow-untyped-calls, no-warn-return-any From f60125b5c9dc08e58a8346da7825833e65c8f958 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 16:52:24 +0100 Subject: [PATCH 231/677] Sort imports according to PEP8 for 'tests' (#29791) --- .../auth/mfa_modules/test_insecure_example.py | 1 + tests/auth/mfa_modules/test_notify.py | 3 +- tests/auth/mfa_modules/test_totp.py | 3 +- tests/auth/permissions/test_entities.py | 6 +-- .../auth/permissions/test_system_policies.py | 2 +- tests/auth/providers/test_command_line.py | 4 +- tests/auth/providers/test_insecure_example.py | 2 +- tests/auth/test_init.py | 5 ++- tests/common.py | 36 +++++++-------- tests/conftest.py | 17 +++---- tests/helpers/test_aiohttp_client.py | 2 +- tests/helpers/test_area_registry.py | 3 +- tests/helpers/test_check_config.py | 9 ++-- tests/helpers/test_config_entry_flow.py | 5 ++- .../helpers/test_config_entry_oauth2_flow.py | 6 +-- tests/helpers/test_deprecation.py | 4 +- tests/helpers/test_device_registry.py | 3 +- tests/helpers/test_discovery.py | 4 +- tests/helpers/test_dispatcher.py | 2 +- tests/helpers/test_entity.py | 10 ++--- tests/helpers/test_entity_component.py | 23 +++++----- tests/helpers/test_entity_platform.py | 17 ++++--- tests/helpers/test_entity_registry.py | 5 +-- tests/helpers/test_entity_values.py | 1 + tests/helpers/test_entityfilter.py | 2 +- tests/helpers/test_event.py | 8 ++-- tests/helpers/test_intent.py | 5 +-- tests/helpers/test_network.py | 2 +- tests/helpers/test_restore_state.py | 7 ++- tests/helpers/test_script.py | 14 +++--- tests/helpers/test_service.py | 19 ++++---- tests/helpers/test_state.py | 29 ++++++------ tests/helpers/test_storage.py | 3 +- tests/helpers/test_sun.py | 4 +- tests/helpers/test_temperature.py | 6 +-- tests/helpers/test_template.py | 4 +- tests/helpers/test_translation.py | 3 +- tests/scripts/test_auth.py | 2 +- tests/scripts/test_check_config.py | 3 +- tests/test_bootstrap.py | 8 ++-- tests/test_config.py | 28 ++++++------ tests/test_config_entries.py | 10 ++--- tests/test_core.py | 44 +++++++++---------- tests/test_loader.py | 2 +- tests/test_main.py | 2 +- tests/test_requirements.py | 11 ++--- tests/test_setup.py | 20 ++++----- tests/test_util/aiohttp.py | 3 +- tests/test_util/test_aiohttp.py | 4 +- .../test/alarm_control_panel.py | 13 +++--- .../custom_components/test/binary_sensor.py | 4 +- .../custom_components/test/cover.py | 2 +- .../custom_components/test/light.py | 4 +- .../custom_components/test/lock.py | 3 +- .../custom_components/test/sensor.py | 2 +- .../custom_components/test/switch.py | 4 +- .../test_package/__init__.py | 1 - tests/util/test_async.py | 2 +- tests/util/test_distance.py | 4 +- tests/util/test_init.py | 2 +- tests/util/test_json.py | 7 ++- tests/util/test_location.py | 2 +- tests/util/test_package.py | 3 +- tests/util/test_pressure.py | 4 +- tests/util/test_ruamel_yaml.py | 3 +- tests/util/test_unit_system.py | 14 +++--- tests/util/test_volume.py | 6 +-- tests/util/test_yaml.py | 9 ++-- 68 files changed, 254 insertions(+), 251 deletions(-) diff --git a/tests/auth/mfa_modules/test_insecure_example.py b/tests/auth/mfa_modules/test_insecure_example.py index dc233cb53ff..5384ebee4bd 100644 --- a/tests/auth/mfa_modules/test_insecure_example.py +++ b/tests/auth/mfa_modules/test_insecure_example.py @@ -2,6 +2,7 @@ from homeassistant import auth, data_entry_flow from homeassistant.auth.mfa_modules import auth_mfa_module_from_config from homeassistant.auth.models import Credentials + from tests.common import MockUser diff --git a/tests/auth/mfa_modules/test_notify.py b/tests/auth/mfa_modules/test_notify.py index 20ed104b375..bc4ecaab712 100644 --- a/tests/auth/mfa_modules/test_notify.py +++ b/tests/auth/mfa_modules/test_notify.py @@ -3,9 +3,10 @@ import asyncio from unittest.mock import patch from homeassistant import data_entry_flow -from homeassistant.auth import models as auth_models, auth_manager_from_config +from homeassistant.auth import auth_manager_from_config, models as auth_models from homeassistant.auth.mfa_modules import auth_mfa_module_from_config from homeassistant.components.notify import NOTIFY_SERVICE_SCHEMA + from tests.common import MockUser, async_mock_service MOCK_CODE = "123456" diff --git a/tests/auth/mfa_modules/test_totp.py b/tests/auth/mfa_modules/test_totp.py index e53e3030cd2..d0a4f3cf3ac 100644 --- a/tests/auth/mfa_modules/test_totp.py +++ b/tests/auth/mfa_modules/test_totp.py @@ -3,8 +3,9 @@ import asyncio from unittest.mock import patch from homeassistant import data_entry_flow -from homeassistant.auth import models as auth_models, auth_manager_from_config +from homeassistant.auth import auth_manager_from_config, models as auth_models from homeassistant.auth.mfa_modules import auth_mfa_module_from_config + from tests.common import MockUser MOCK_CODE = "123456" diff --git a/tests/auth/permissions/test_entities.py b/tests/auth/permissions/test_entities.py index c2305b5c203..a929984d152 100644 --- a/tests/auth/permissions/test_entities.py +++ b/tests/auth/permissions/test_entities.py @@ -3,14 +3,14 @@ import pytest import voluptuous as vol from homeassistant.auth.permissions.entities import ( - compile_entities, ENTITY_POLICY_SCHEMA, + compile_entities, ) from homeassistant.auth.permissions.models import PermissionLookup -from homeassistant.helpers.entity_registry import RegistryEntry from homeassistant.helpers.device_registry import DeviceEntry +from homeassistant.helpers.entity_registry import RegistryEntry -from tests.common import mock_registry, mock_device_registry +from tests.common import mock_device_registry, mock_registry def test_entities_none(): diff --git a/tests/auth/permissions/test_system_policies.py b/tests/auth/permissions/test_system_policies.py index 2710fdd68c3..58515e2ad2a 100644 --- a/tests/auth/permissions/test_system_policies.py +++ b/tests/auth/permissions/test_system_policies.py @@ -1,8 +1,8 @@ """Test system policies.""" from homeassistant.auth.permissions import ( + POLICY_SCHEMA, PolicyPermissions, system_policies, - POLICY_SCHEMA, ) diff --git a/tests/auth/providers/test_command_line.py b/tests/auth/providers/test_command_line.py index 33b1735457f..abcf124b9c4 100644 --- a/tests/auth/providers/test_command_line.py +++ b/tests/auth/providers/test_command_line.py @@ -1,13 +1,13 @@ """Tests for the command_line auth provider.""" -from unittest.mock import Mock import os +from unittest.mock import Mock import uuid import pytest from homeassistant import data_entry_flow -from homeassistant.auth import auth_store, models as auth_models, AuthManager +from homeassistant.auth import AuthManager, auth_store, models as auth_models from homeassistant.auth.providers import command_line from homeassistant.const import CONF_TYPE diff --git a/tests/auth/providers/test_insecure_example.py b/tests/auth/providers/test_insecure_example.py index 43f5aff290e..c5b3a8db038 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, AuthManager +from homeassistant.auth import AuthManager, auth_store, models as auth_models from homeassistant.auth.providers import insecure_example from tests.common import mock_coro diff --git a/tests/auth/test_init.py b/tests/auth/test_init.py index 73b383fb0fe..aa047a12d54 100644 --- a/tests/auth/test_init.py +++ b/tests/auth/test_init.py @@ -7,11 +7,12 @@ import pytest import voluptuous as vol from homeassistant import auth, data_entry_flow -from homeassistant.auth import models as auth_models, auth_store, const as auth_const +from homeassistant.auth import auth_store, const as auth_const, models as auth_models from homeassistant.auth.const import MFA_SESSION_EXPIRATION from homeassistant.core import callback from homeassistant.util import dt as dt_util -from tests.common import MockUser, ensure_auth_manager_loaded, flush_store, CLIENT_ID + +from tests.common import CLIENT_ID, MockUser, ensure_auth_manager_loaded, flush_store @pytest.fixture diff --git a/tests/common.py b/tests/common.py index e652e10cc54..a54b3899698 100644 --- a/tests/common.py +++ b/tests/common.py @@ -1,32 +1,32 @@ """Test the helper method for writing tests.""" import asyncio import collections -import functools as ft -import json -import logging -import os -import uuid -import sys -import threading - from collections import OrderedDict from contextlib import contextmanager from datetime import timedelta +import functools as ft from io import StringIO +import json +import logging +import os +import sys +import threading from unittest.mock import MagicMock, Mock, patch - -import homeassistant.util.dt as date_util -import homeassistant.util.yaml.loader as yaml_loader +import uuid from homeassistant import auth, config_entries, core as ha, loader from homeassistant.auth import ( - models as auth_models, auth_store, - providers as auth_providers, + models as auth_models, permissions as auth_permissions, + providers as auth_providers, ) from homeassistant.auth.permissions import system_policies from homeassistant.components import mqtt, recorder +from homeassistant.components.device_automation import ( # noqa: F401 + _async_get_device_automation_capabilities as async_get_device_automation_capabilities, + _async_get_device_automations as async_get_device_automations, +) from homeassistant.components.mqtt.models import Message from homeassistant.config import async_process_component_config from homeassistant.const import ( @@ -38,8 +38,8 @@ from homeassistant.const import ( EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, SERVER_PORT, - STATE_ON, STATE_OFF, + STATE_ON, ) from homeassistant.core import State from homeassistant.helpers import ( @@ -54,12 +54,10 @@ from homeassistant.helpers import ( ) from homeassistant.helpers.json import JSONEncoder from homeassistant.setup import async_setup_component, setup_component -from homeassistant.util.unit_system import METRIC_SYSTEM from homeassistant.util.async_ import run_callback_threadsafe -from homeassistant.components.device_automation import ( # noqa: F401 - _async_get_device_automations as async_get_device_automations, - _async_get_device_automation_capabilities as async_get_device_automation_capabilities, -) +import homeassistant.util.dt as date_util +from homeassistant.util.unit_system import METRIC_SYSTEM +import homeassistant.util.yaml.loader as yaml_loader _TEST_INSTANCE_PORT = SERVER_PORT _LOGGER = logging.getLogger(__name__) diff --git a/tests/conftest.py b/tests/conftest.py index 5e1bbc76fb5..2b6a1a16739 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,22 +9,23 @@ import pytest import requests_mock as _requests_mock from homeassistant import util -from homeassistant.util import location from homeassistant.auth.const import GROUP_ID_ADMIN, GROUP_ID_READ_ONLY -from homeassistant.auth.providers import legacy_api_password, homeassistant +from homeassistant.auth.providers import homeassistant, legacy_api_password +from homeassistant.util import location -pytest.register_assert_rewrite("tests.common") from tests.common import ( # noqa: E402 module level import not at top of file - async_test_home_assistant, + CLIENT_ID, INSTANCES, + MockUser, + async_test_home_assistant, mock_coro, mock_storage as mock_storage, - MockUser, - CLIENT_ID, ) -from tests.test_util.aiohttp import ( +from tests.test_util.aiohttp import ( # noqa: E402 module level import not at top of file mock_aiohttp_client, -) # noqa: E402 module level import not at top of file +) + +pytest.register_assert_rewrite("tests.common") if os.environ.get("UVLOOP") == "1": import uvloop diff --git a/tests/helpers/test_aiohttp_client.py b/tests/helpers/test_aiohttp_client.py index 5494bc40a75..de7057ae9c7 100644 --- a/tests/helpers/test_aiohttp_client.py +++ b/tests/helpers/test_aiohttp_client.py @@ -6,8 +6,8 @@ import aiohttp import pytest from homeassistant.core import EVENT_HOMEASSISTANT_CLOSE -from homeassistant.setup import async_setup_component import homeassistant.helpers.aiohttp_client as client +from homeassistant.setup import async_setup_component from homeassistant.util.async_ import run_callback_threadsafe from tests.common import get_test_home_assistant diff --git a/tests/helpers/test_area_registry.py b/tests/helpers/test_area_registry.py index 48d32861a0a..87e4b4c4d03 100644 --- a/tests/helpers/test_area_registry.py +++ b/tests/helpers/test_area_registry.py @@ -6,7 +6,8 @@ import pytest from homeassistant.core import callback from homeassistant.helpers import area_registry -from tests.common import mock_area_registry, flush_store + +from tests.common import flush_store, mock_area_registry @pytest.fixture diff --git a/tests/helpers/test_check_config.py b/tests/helpers/test_check_config.py index 0b34b263caf..54c835895ca 100644 --- a/tests/helpers/test_check_config.py +++ b/tests/helpers/test_check_config.py @@ -2,11 +2,12 @@ import logging from unittest.mock import patch -from homeassistant.helpers.check_config import ( - async_check_ha_config_file, - CheckConfigError, -) from homeassistant.config import YAML_CONFIG_FILE +from homeassistant.helpers.check_config import ( + CheckConfigError, + async_check_ha_config_file, +) + from tests.common import patch_yaml_files _LOGGER = logging.getLogger(__name__) diff --git a/tests/helpers/test_config_entry_flow.py b/tests/helpers/test_config_entry_flow.py index 3c3d1224e12..1c292e5ed48 100644 --- a/tests/helpers/test_config_entry_flow.py +++ b/tests/helpers/test_config_entry_flow.py @@ -1,16 +1,17 @@ """Tests for the Config Entry Flow helper.""" -from unittest.mock import patch, Mock +from unittest.mock import Mock, patch import pytest from homeassistant import config_entries, data_entry_flow, setup from homeassistant.helpers import config_entry_flow + from tests.common import ( MockConfigEntry, MockModule, mock_coro, - mock_integration, mock_entity_platform, + mock_integration, ) diff --git a/tests/helpers/test_config_entry_oauth2_flow.py b/tests/helpers/test_config_entry_oauth2_flow.py index 773dfa09375..366c295874d 100644 --- a/tests/helpers/test_config_entry_oauth2_flow.py +++ b/tests/helpers/test_config_entry_oauth2_flow.py @@ -1,15 +1,15 @@ """Tests for the Somfy config flow.""" import asyncio import logging -from unittest.mock import patch import time +from unittest.mock import patch import pytest -from homeassistant import data_entry_flow, setup, config_entries +from homeassistant import config_entries, data_entry_flow, setup from homeassistant.helpers import config_entry_oauth2_flow -from tests.common import mock_platform, MockConfigEntry +from tests.common import MockConfigEntry, mock_platform TEST_DOMAIN = "oauth2_test" CLIENT_SECRET = "5678" diff --git a/tests/helpers/test_deprecation.py b/tests/helpers/test_deprecation.py index d0cb0eca55a..38410c3bf0f 100644 --- a/tests/helpers/test_deprecation.py +++ b/tests/helpers/test_deprecation.py @@ -1,7 +1,7 @@ """Test deprecation helpers.""" -from homeassistant.helpers.deprecation import deprecated_substitute, get_deprecated +from unittest.mock import MagicMock, patch -from unittest.mock import patch, MagicMock +from homeassistant.helpers.deprecation import deprecated_substitute, get_deprecated class MockBaseClass: diff --git a/tests/helpers/test_device_registry.py b/tests/helpers/test_device_registry.py index 1b146e9cb12..3846230e6d3 100644 --- a/tests/helpers/test_device_registry.py +++ b/tests/helpers/test_device_registry.py @@ -7,7 +7,8 @@ import pytest from homeassistant.core import callback from homeassistant.helpers import device_registry -from tests.common import mock_device_registry, flush_store + +from tests.common import flush_store, mock_device_registry @pytest.fixture diff --git a/tests/helpers/test_discovery.py b/tests/helpers/test_discovery.py index dbd837eb5c7..3b0996d676a 100644 --- a/tests/helpers/test_discovery.py +++ b/tests/helpers/test_discovery.py @@ -9,12 +9,12 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import discovery from tests.common import ( - get_test_home_assistant, MockModule, MockPlatform, + get_test_home_assistant, mock_coro, - mock_integration, mock_entity_platform, + mock_integration, ) diff --git a/tests/helpers/test_dispatcher.py b/tests/helpers/test_dispatcher.py index 789cb6c1dc8..4cf266e88a2 100644 --- a/tests/helpers/test_dispatcher.py +++ b/tests/helpers/test_dispatcher.py @@ -4,8 +4,8 @@ import asyncio from homeassistant.core import callback from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, - dispatcher_send, dispatcher_connect, + dispatcher_send, ) from tests.common import get_test_home_assistant diff --git a/tests/helpers/test_entity.py b/tests/helpers/test_entity.py index cd852f5bfc0..749c11ff1a5 100644 --- a/tests/helpers/test_entity.py +++ b/tests/helpers/test_entity.py @@ -1,16 +1,16 @@ """Test the entity helper.""" # pylint: disable=protected-access import asyncio -import threading from datetime import timedelta -from unittest.mock import MagicMock, patch, PropertyMock +import threading +from unittest.mock import MagicMock, PropertyMock, patch import pytest -from homeassistant.helpers import entity, entity_registry -from homeassistant.core import Context -from homeassistant.const import ATTR_HIDDEN, ATTR_DEVICE_CLASS, STATE_UNAVAILABLE from homeassistant.config import DATA_CUSTOMIZE +from homeassistant.const import ATTR_DEVICE_CLASS, ATTR_HIDDEN, STATE_UNAVAILABLE +from homeassistant.core import Context +from homeassistant.helpers import entity, entity_registry from homeassistant.helpers.entity_values import EntityValues from tests.common import get_test_home_assistant, mock_registry diff --git a/tests/helpers/test_entity_component.py b/tests/helpers/test_entity_component.py index 350aa88b6af..81fbe2d6520 100644 --- a/tests/helpers/test_entity_component.py +++ b/tests/helpers/test_entity_component.py @@ -1,30 +1,29 @@ """The tests for the Entity component helper.""" # pylint: disable=protected-access from collections import OrderedDict -import logging -from unittest.mock import patch, Mock from datetime import timedelta +import logging +from unittest.mock import Mock, patch import asynctest import pytest -import homeassistant.core as ha -from homeassistant.const import ENTITY_MATCH_ALL -from homeassistant.exceptions import PlatformNotReady from homeassistant.components import group +from homeassistant.const import ENTITY_MATCH_ALL +import homeassistant.core as ha +from homeassistant.exceptions import PlatformNotReady +from homeassistant.helpers import discovery from homeassistant.helpers.entity_component import EntityComponent from homeassistant.setup import async_setup_component - -from homeassistant.helpers import discovery import homeassistant.util.dt as dt_util from tests.common import ( - MockPlatform, - MockModule, - mock_coro, - async_fire_time_changed, - MockEntity, MockConfigEntry, + MockEntity, + MockModule, + MockPlatform, + async_fire_time_changed, + mock_coro, mock_entity_platform, mock_integration, ) diff --git a/tests/helpers/test_entity_platform.py b/tests/helpers/test_entity_platform.py index caf8bb702af..5909dfaf3aa 100644 --- a/tests/helpers/test_entity_platform.py +++ b/tests/helpers/test_entity_platform.py @@ -1,30 +1,29 @@ """Tests for the EntityPlatform helper.""" import asyncio -import logging -from unittest.mock import patch, Mock, MagicMock from datetime import timedelta +import logging +from unittest.mock import MagicMock, Mock, patch import asynctest import pytest from homeassistant.exceptions import PlatformNotReady +from homeassistant.helpers import entity_platform, entity_registry from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.entity_component import ( - EntityComponent, DEFAULT_SCAN_INTERVAL, + EntityComponent, ) -from homeassistant.helpers import entity_platform, entity_registry - import homeassistant.util.dt as dt_util from tests.common import ( - MockPlatform, - async_fire_time_changed, - mock_registry, + MockConfigEntry, MockEntity, MockEntityPlatform, - MockConfigEntry, + MockPlatform, + async_fire_time_changed, mock_entity_platform, + mock_registry, ) _LOGGER = logging.getLogger(__name__) diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py index 9debbdbcba7..b07c5237116 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -5,11 +5,10 @@ from unittest.mock import patch import asynctest import pytest -from homeassistant.core import valid_entity_id, callback +from homeassistant.core import callback, valid_entity_id from homeassistant.helpers import entity_registry -from tests.common import MockConfigEntry, mock_registry, flush_store - +from tests.common import MockConfigEntry, flush_store, mock_registry YAML__OPEN_PATH = "homeassistant.util.yaml.loader.open" diff --git a/tests/helpers/test_entity_values.py b/tests/helpers/test_entity_values.py index d9be6c52b4e..f5ad68ee231 100644 --- a/tests/helpers/test_entity_values.py +++ b/tests/helpers/test_entity_values.py @@ -1,5 +1,6 @@ """Test the entity values helper.""" from collections import OrderedDict + from homeassistant.helpers.entity_values import EntityValues as EV ent = "test.test" diff --git a/tests/helpers/test_entityfilter.py b/tests/helpers/test_entityfilter.py index 8deea67ac16..726e6bd92d0 100644 --- a/tests/helpers/test_entityfilter.py +++ b/tests/helpers/test_entityfilter.py @@ -1,5 +1,5 @@ """The tests for the EntityFilter component.""" -from homeassistant.helpers.entityfilter import generate_filter, FILTER_SCHEMA +from homeassistant.helpers.entityfilter import FILTER_SCHEMA, generate_filter def test_no_filters_case_1(): diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py index 3d4804e5079..d331da5d92d 100644 --- a/tests/helpers/test_event.py +++ b/tests/helpers/test_event.py @@ -7,10 +7,10 @@ from unittest.mock import patch from astral import Astral import pytest -from homeassistant.core import callback -from homeassistant.setup import async_setup_component -import homeassistant.core as ha +from homeassistant.components import sun from homeassistant.const import MATCH_ALL +import homeassistant.core as ha +from homeassistant.core import callback from homeassistant.helpers.event import ( async_call_later, async_track_point_in_time, @@ -25,7 +25,7 @@ from homeassistant.helpers.event import ( async_track_utc_time_change, ) from homeassistant.helpers.template import Template -from homeassistant.components import sun +from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from tests.common import async_fire_time_changed diff --git a/tests/helpers/test_intent.py b/tests/helpers/test_intent.py index 3988c86f516..bbb6e394ee0 100644 --- a/tests/helpers/test_intent.py +++ b/tests/helpers/test_intent.py @@ -1,11 +1,10 @@ """Tests for the intent helpers.""" +import pytest import voluptuous as vol -import pytest - from homeassistant.core import State -from homeassistant.helpers import intent, config_validation as cv +from homeassistant.helpers import config_validation as cv, intent class MockIntentHandler(intent.IntentHandler): diff --git a/tests/helpers/test_network.py b/tests/helpers/test_network.py index afb9e88c5a4..d4c5366b879 100644 --- a/tests/helpers/test_network.py +++ b/tests/helpers/test_network.py @@ -1,8 +1,8 @@ """Test network helper.""" from unittest.mock import Mock, patch -from homeassistant.helpers import network from homeassistant.components import cloud +from homeassistant.helpers import network async def test_get_external_url(hass): diff --git a/tests/helpers/test_restore_state.py b/tests/helpers/test_restore_state.py index 94a17697eb4..97004362d20 100644 --- a/tests/helpers/test_restore_state.py +++ b/tests/helpers/test_restore_state.py @@ -8,15 +8,14 @@ from homeassistant.core import CoreState, State from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity import Entity from homeassistant.helpers.restore_state import ( - RestoreStateData, - RestoreEntity, - StoredState, DATA_RESTORE_STATE_TASK, STORAGE_KEY, + RestoreEntity, + RestoreStateData, + StoredState, ) from homeassistant.util import dt as dt_util - from tests.common import mock_coro diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 4b8be715f37..a7fe2c25236 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -6,21 +6,19 @@ from unittest import mock import asynctest import jinja2 -import voluptuous as vol import pytest - -import homeassistant.components.scene as scene -from homeassistant import exceptions -from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_ON -from homeassistant.core import Context, callback +import voluptuous as vol # Otherwise can't test just this file (import order issue) +from homeassistant import exceptions +import homeassistant.components.scene as scene +from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_ON +from homeassistant.core import Context, callback +from homeassistant.helpers import config_validation as cv, script import homeassistant.util.dt as dt_util -from homeassistant.helpers import script, config_validation as cv from tests.common import async_fire_time_changed - ENTITY_ID = "script.test" diff --git a/tests/helpers/test_service.py b/tests/helpers/test_service.py index 20be7db42f5..2697c59b787 100644 --- a/tests/helpers/test_service.py +++ b/tests/helpers/test_service.py @@ -5,28 +5,29 @@ from copy import deepcopy import unittest from unittest.mock import Mock, patch -import voluptuous as vol import pytest +import voluptuous as vol # To prevent circular import when running just this file -import homeassistant.components # noqa: F401 from homeassistant import core as ha, exceptions -from homeassistant.const import STATE_ON, STATE_OFF, ATTR_ENTITY_ID, ENTITY_MATCH_ALL -from homeassistant.setup import async_setup_component -import homeassistant.helpers.config_validation as cv from homeassistant.auth.permissions import PolicyPermissions +import homeassistant.components # noqa: F401 +from homeassistant.const import ATTR_ENTITY_ID, ENTITY_MATCH_ALL, STATE_OFF, STATE_ON from homeassistant.helpers import ( - service, - template, device_registry as dev_reg, entity_registry as ent_reg, + service, + template, ) +import homeassistant.helpers.config_validation as cv +from homeassistant.setup import async_setup_component + from tests.common import ( get_test_home_assistant, - mock_service, mock_coro, - mock_registry, mock_device_registry, + mock_registry, + mock_service, ) diff --git a/tests/helpers/test_state.py b/tests/helpers/test_state.py index 14bcbde5094..567bac65f5b 100644 --- a/tests/helpers/test_state.py +++ b/tests/helpers/test_state.py @@ -5,21 +5,22 @@ from unittest.mock import patch import pytest -import homeassistant.core as ha -from homeassistant.const import SERVICE_TURN_ON, SERVICE_TURN_OFF -from homeassistant.util import dt as dt_util -from homeassistant.helpers import state -from homeassistant.const import ( - STATE_OPEN, - STATE_CLOSED, - STATE_LOCKED, - STATE_UNLOCKED, - STATE_ON, - STATE_OFF, - STATE_HOME, - STATE_NOT_HOME, -) from homeassistant.components.sun import STATE_ABOVE_HORIZON, STATE_BELOW_HORIZON +from homeassistant.const import ( + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_CLOSED, + STATE_HOME, + STATE_LOCKED, + STATE_NOT_HOME, + STATE_OFF, + STATE_ON, + STATE_OPEN, + STATE_UNLOCKED, +) +import homeassistant.core as ha +from homeassistant.helpers import state +from homeassistant.util import dt as dt_util from tests.common import async_mock_service diff --git a/tests/helpers/test_storage.py b/tests/helpers/test_storage.py index 87a22fcb845..2fef58ad115 100644 --- a/tests/helpers/test_storage.py +++ b/tests/helpers/test_storage.py @@ -2,7 +2,7 @@ import asyncio from datetime import timedelta import json -from unittest.mock import patch, Mock +from unittest.mock import Mock, patch import pytest @@ -12,7 +12,6 @@ from homeassistant.util import dt from tests.common import async_fire_time_changed, mock_coro - MOCK_VERSION = 1 MOCK_KEY = "storage-test" MOCK_DATA = {"hello": "world"} diff --git a/tests/helpers/test_sun.py b/tests/helpers/test_sun.py index 1746c7e6fc0..b8ecd1ed86a 100644 --- a/tests/helpers/test_sun.py +++ b/tests/helpers/test_sun.py @@ -1,11 +1,11 @@ """The tests for the Sun helpers.""" # pylint: disable=protected-access +from datetime import datetime, timedelta from unittest.mock import patch -from datetime import timedelta, datetime from homeassistant.const import SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET -import homeassistant.util.dt as dt_util import homeassistant.helpers.sun as sun +import homeassistant.util.dt as dt_util def test_next_events(hass): diff --git a/tests/helpers/test_temperature.py b/tests/helpers/test_temperature.py index 840e6fd5d9d..5808b661150 100644 --- a/tests/helpers/test_temperature.py +++ b/tests/helpers/test_temperature.py @@ -2,11 +2,11 @@ import pytest from homeassistant.const import ( - TEMP_CELSIUS, - PRECISION_WHOLE, - TEMP_FAHRENHEIT, PRECISION_HALVES, PRECISION_TENTHS, + PRECISION_WHOLE, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, ) from homeassistant.helpers.temperature import display_temp diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index f463149bc28..cbd530d0b4c 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -1,13 +1,12 @@ """Test Home Assistant template helper methods.""" +from datetime import datetime import math import random -from datetime import datetime from unittest.mock import patch import pytest import pytz -import homeassistant.util.dt as dt_util from homeassistant.components import group from homeassistant.const import ( LENGTH_METERS, @@ -19,6 +18,7 @@ from homeassistant.const import ( ) from homeassistant.exceptions import TemplateError from homeassistant.helpers import template +import homeassistant.util.dt as dt_util from homeassistant.util.unit_system import UnitSystem diff --git a/tests/helpers/test_translation.py b/tests/helpers/test_translation.py index 1c3748250a5..6b846703914 100644 --- a/tests/helpers/test_translation.py +++ b/tests/helpers/test_translation.py @@ -5,9 +5,10 @@ from unittest.mock import patch import pytest +from homeassistant.generated import config_flows import homeassistant.helpers.translation as translation from homeassistant.setup import async_setup_component -from homeassistant.generated import config_flows + from tests.common import mock_coro diff --git a/tests/scripts/test_auth.py b/tests/scripts/test_auth.py index f762630a42a..3ab19450879 100644 --- a/tests/scripts/test_auth.py +++ b/tests/scripts/test_auth.py @@ -3,8 +3,8 @@ from unittest.mock import Mock, patch import pytest -from homeassistant.scripts import auth as script_auth from homeassistant.auth.providers import homeassistant as hass_auth +from homeassistant.scripts import auth as script_auth from tests.common import register_auth_provider diff --git a/tests/scripts/test_check_config.py b/tests/scripts/test_check_config.py index 8e1ffe63e84..481efc6fb30 100644 --- a/tests/scripts/test_check_config.py +++ b/tests/scripts/test_check_config.py @@ -2,8 +2,9 @@ import logging from unittest.mock import patch -import homeassistant.scripts.check_config as check_config from homeassistant.config import YAML_CONFIG_FILE +import homeassistant.scripts.check_config as check_config + from tests.common import get_test_config_dir, patch_yaml_files _LOGGER = logging.getLogger(__name__) diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index eaabd61af4b..f71be8fe9b1 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -1,20 +1,20 @@ """Test the bootstrapping.""" # pylint: disable=protected-access import asyncio +import logging import os from unittest.mock import Mock, patch -import logging -import homeassistant.config as config_util from homeassistant import bootstrap +import homeassistant.config as config_util import homeassistant.util.dt as dt_util from tests.common import ( - patch_yaml_files, + MockModule, get_test_config_dir, mock_coro, mock_integration, - MockModule, + patch_yaml_files, ) ORIG_TIMEZONE = dt_util.DEFAULT_TIME_ZONE diff --git a/tests/test_config.py b/tests/test_config.py index 1c872369096..1abb6ec7ff1 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,39 +1,39 @@ """Test config utils.""" # pylint: disable=protected-access import asyncio +from collections import OrderedDict import copy import os import unittest.mock as mock -from collections import OrderedDict import asynctest import pytest -from voluptuous import MultipleInvalid, Invalid +from voluptuous import Invalid, MultipleInvalid import yaml -from homeassistant.core import SOURCE_STORAGE, HomeAssistantError import homeassistant.config as config_util -from homeassistant.loader import async_get_integration from homeassistant.const import ( + ATTR_ASSUMED_STATE, ATTR_FRIENDLY_NAME, ATTR_HIDDEN, - ATTR_ASSUMED_STATE, + CONF_AUTH_MFA_MODULES, + CONF_AUTH_PROVIDERS, + CONF_CUSTOMIZE, CONF_LATITUDE, CONF_LONGITUDE, - CONF_UNIT_SYSTEM, CONF_NAME, - CONF_CUSTOMIZE, - __version__, - CONF_UNIT_SYSTEM_METRIC, - CONF_UNIT_SYSTEM_IMPERIAL, CONF_TEMPERATURE_UNIT, - CONF_AUTH_PROVIDERS, - CONF_AUTH_MFA_MODULES, + CONF_UNIT_SYSTEM, + CONF_UNIT_SYSTEM_IMPERIAL, + CONF_UNIT_SYSTEM_METRIC, + __version__, ) +from homeassistant.core import SOURCE_STORAGE, HomeAssistantError +import homeassistant.helpers.check_config as check_config +from homeassistant.helpers.entity import Entity +from homeassistant.loader import async_get_integration from homeassistant.util import dt as dt_util from homeassistant.util.yaml import SECRET_YAML -from homeassistant.helpers.entity import Entity -import homeassistant.helpers.check_config as check_config from tests.common import get_test_config_dir, patch_yaml_files diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index d9dd614c9a5..24a0b0939be 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -12,14 +12,14 @@ from homeassistant.setup import async_setup_component from homeassistant.util import dt from tests.common import ( - MockModule, - mock_coro, MockConfigEntry, - async_fire_time_changed, - MockPlatform, MockEntity, - mock_integration, + MockModule, + MockPlatform, + async_fire_time_changed, + mock_coro, mock_entity_platform, + mock_integration, mock_registry, ) diff --git a/tests/test_core.py b/tests/test_core.py index 5ac13027f28..1999065e31d 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,40 +1,40 @@ """Test to verify that Home Assistant core works.""" # pylint: disable=protected-access import asyncio +from datetime import datetime, timedelta import functools import logging import os -import unittest -from unittest.mock import patch, MagicMock -from datetime import datetime, timedelta from tempfile import TemporaryDirectory +import unittest +from unittest.mock import MagicMock, patch -import voluptuous as vol -import pytz import pytest +import pytz +import voluptuous as vol +from homeassistant.const import ( + ATTR_FRIENDLY_NAME, + ATTR_NOW, + ATTR_SECONDS, + CONF_UNIT_SYSTEM, + EVENT_CALL_SERVICE, + EVENT_CORE_CONFIG_UPDATE, + EVENT_HOMEASSISTANT_CLOSE, + EVENT_HOMEASSISTANT_STOP, + EVENT_SERVICE_REGISTERED, + EVENT_SERVICE_REMOVED, + EVENT_STATE_CHANGED, + EVENT_TIME_CHANGED, + EVENT_TIMER_OUT_OF_SYNC, + __version__, +) import homeassistant.core as ha from homeassistant.exceptions import InvalidEntityFormatError, InvalidStateError import homeassistant.util.dt as dt_util from homeassistant.util.unit_system import METRIC_SYSTEM -from homeassistant.const import ( - __version__, - EVENT_STATE_CHANGED, - ATTR_FRIENDLY_NAME, - CONF_UNIT_SYSTEM, - ATTR_NOW, - EVENT_TIME_CHANGED, - EVENT_TIMER_OUT_OF_SYNC, - ATTR_SECONDS, - EVENT_HOMEASSISTANT_STOP, - EVENT_HOMEASSISTANT_CLOSE, - EVENT_SERVICE_REGISTERED, - EVENT_SERVICE_REMOVED, - EVENT_CALL_SERVICE, - EVENT_CORE_CONFIG_UPDATE, -) -from tests.common import get_test_home_assistant, async_mock_service +from tests.common import async_mock_service, get_test_home_assistant PST = pytz.timezone("America/Los_Angeles") diff --git a/tests/test_loader.py b/tests/test_loader.py index e7011997f73..f3e7c3bd884 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -2,9 +2,9 @@ from asynctest.mock import ANY, patch import pytest -import homeassistant.loader as loader from homeassistant.components import http, hue from homeassistant.components.hue import light as hue_light +import homeassistant.loader as loader from tests.common import MockModule, async_mock_service, mock_integration diff --git a/tests/test_main.py b/tests/test_main.py index 29454d269af..5ec6460301f 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,5 +1,5 @@ """Test methods in __main__.""" -from unittest.mock import patch, PropertyMock +from unittest.mock import PropertyMock, patch from homeassistant import __main__ as main from homeassistant.const import REQUIRED_PYTHON_VER diff --git a/tests/test_requirements.py b/tests/test_requirements.py index 2627a077a87..b807188e8a5 100644 --- a/tests/test_requirements.py +++ b/tests/test_requirements.py @@ -1,20 +1,21 @@ """Test requirements module.""" import os from pathlib import Path -from unittest.mock import patch, call +from unittest.mock import call, patch + from pytest import raises from homeassistant import setup from homeassistant.requirements import ( CONSTRAINT_FILE, + PROGRESS_FILE, + RequirementsNotFound, + _install, async_get_integration_with_requirements, async_process_requirements, - PROGRESS_FILE, - _install, - RequirementsNotFound, ) -from tests.common import get_test_home_assistant, MockModule, mock_integration +from tests.common import MockModule, get_test_home_assistant, mock_integration def env_without_wheel_links(): diff --git a/tests/test_setup.py b/tests/test_setup.py index 8fd25091eb6..c19d92db4b6 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -1,32 +1,32 @@ """Test component/platform setup.""" # pylint: disable=protected-access import asyncio -import os -from unittest import mock -import threading import logging +import os +import threading +from unittest import mock import voluptuous as vol -from homeassistant.core import callback -from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_COMPONENT_LOADED -import homeassistant.config as config_util from homeassistant import setup -import homeassistant.util.dt as dt_util +import homeassistant.config as config_util +from homeassistant.const import EVENT_COMPONENT_LOADED, EVENT_HOMEASSISTANT_START +from homeassistant.core import callback +from homeassistant.helpers import discovery from homeassistant.helpers.config_validation import ( PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE, ) -from homeassistant.helpers import discovery +import homeassistant.util.dt as dt_util from tests.common import ( - get_test_home_assistant, MockModule, MockPlatform, assert_setup_component, get_test_config_dir, - mock_integration, + get_test_home_assistant, mock_entity_platform, + mock_integration, ) ORIG_TIMEZONE = dt_util.DEFAULT_TIME_ZONE diff --git a/tests/test_util/aiohttp.py b/tests/test_util/aiohttp.py index ce13ca5a594..e0e1ded32c3 100644 --- a/tests/test_util/aiohttp.py +++ b/tests/test_util/aiohttp.py @@ -7,11 +7,10 @@ from unittest import mock from urllib.parse import parse_qs from aiohttp import ClientSession +from aiohttp.client_exceptions import ClientResponseError from aiohttp.streams import StreamReader from yarl import URL -from aiohttp.client_exceptions import ClientResponseError - from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE retype = type(re.compile("")) diff --git a/tests/test_util/test_aiohttp.py b/tests/test_util/test_aiohttp.py index 0a0ead54dca..2761f5b9ea3 100644 --- a/tests/test_util/test_aiohttp.py +++ b/tests/test_util/test_aiohttp.py @@ -1,8 +1,8 @@ """Tests for our aiohttp mocker.""" -from .aiohttp import AiohttpClientMocker - import pytest +from .aiohttp import AiohttpClientMocker + async def test_matching_url(): """Test we can match urls.""" diff --git a/tests/testing_config/custom_components/test/alarm_control_panel.py b/tests/testing_config/custom_components/test/alarm_control_panel.py index 1ffa52086e7..065ea3e3980 100644 --- a/tests/testing_config/custom_components/test/alarm_control_panel.py +++ b/tests/testing_config/custom_components/test/alarm_control_panel.py @@ -4,6 +4,12 @@ Provide a mock alarm_control_panel platform. Call init before using it in your tests to ensure clean test data. """ from homeassistant.components.alarm_control_panel import AlarmControlPanel +from homeassistant.components.alarm_control_panel.const import ( + SUPPORT_ALARM_ARM_AWAY, + SUPPORT_ALARM_ARM_HOME, + SUPPORT_ALARM_ARM_NIGHT, + SUPPORT_ALARM_TRIGGER, +) from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, @@ -11,12 +17,7 @@ from homeassistant.const import ( STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED, ) -from homeassistant.components.alarm_control_panel.const import ( - SUPPORT_ALARM_ARM_HOME, - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_NIGHT, - SUPPORT_ALARM_TRIGGER, -) + from tests.common import MockEntity ENTITIES = {} diff --git a/tests/testing_config/custom_components/test/binary_sensor.py b/tests/testing_config/custom_components/test/binary_sensor.py index 5052b8e47f1..bcff0adb4e4 100644 --- a/tests/testing_config/custom_components/test/binary_sensor.py +++ b/tests/testing_config/custom_components/test/binary_sensor.py @@ -3,9 +3,9 @@ Provide a mock binary sensor platform. Call init before using it in your tests to ensure clean test data. """ -from homeassistant.components.binary_sensor import BinarySensorDevice, DEVICE_CLASSES -from tests.common import MockEntity +from homeassistant.components.binary_sensor import DEVICE_CLASSES, BinarySensorDevice +from tests.common import MockEntity ENTITIES = {} diff --git a/tests/testing_config/custom_components/test/cover.py b/tests/testing_config/custom_components/test/cover.py index d7c771e2b28..ce5462790bb 100644 --- a/tests/testing_config/custom_components/test/cover.py +++ b/tests/testing_config/custom_components/test/cover.py @@ -4,8 +4,8 @@ Provide a mock cover platform. Call init before using it in your tests to ensure clean test data. """ from homeassistant.components.cover import CoverDevice -from tests.common import MockEntity +from tests.common import MockEntity ENTITIES = {} diff --git a/tests/testing_config/custom_components/test/light.py b/tests/testing_config/custom_components/test/light.py index 0a48388b718..4b018adb5cb 100644 --- a/tests/testing_config/custom_components/test/light.py +++ b/tests/testing_config/custom_components/test/light.py @@ -3,9 +3,9 @@ Provide a mock light platform. Call init before using it in your tests to ensure clean test data. """ -from homeassistant.const import STATE_ON, STATE_OFF -from tests.common import MockToggleEntity +from homeassistant.const import STATE_OFF, STATE_ON +from tests.common import MockToggleEntity ENTITIES = [] diff --git a/tests/testing_config/custom_components/test/lock.py b/tests/testing_config/custom_components/test/lock.py index db6ce38b097..24b04903541 100644 --- a/tests/testing_config/custom_components/test/lock.py +++ b/tests/testing_config/custom_components/test/lock.py @@ -3,7 +3,8 @@ Provide a mock lock platform. Call init before using it in your tests to ensure clean test data. """ -from homeassistant.components.lock import LockDevice, SUPPORT_OPEN +from homeassistant.components.lock import SUPPORT_OPEN, LockDevice + from tests.common import MockEntity ENTITIES = {} diff --git a/tests/testing_config/custom_components/test/sensor.py b/tests/testing_config/custom_components/test/sensor.py index 651ee17bd65..26497b16a16 100644 --- a/tests/testing_config/custom_components/test/sensor.py +++ b/tests/testing_config/custom_components/test/sensor.py @@ -4,8 +4,8 @@ Provide a mock sensor platform. Call init before using it in your tests to ensure clean test data. """ import homeassistant.components.sensor as sensor -from tests.common import MockEntity +from tests.common import MockEntity DEVICE_CLASSES = list(sensor.DEVICE_CLASSES) DEVICE_CLASSES.append("none") diff --git a/tests/testing_config/custom_components/test/switch.py b/tests/testing_config/custom_components/test/switch.py index 484c47d1190..7dd1862d88f 100644 --- a/tests/testing_config/custom_components/test/switch.py +++ b/tests/testing_config/custom_components/test/switch.py @@ -3,9 +3,9 @@ Provide a mock switch platform. Call init before using it in your tests to ensure clean test data. """ -from homeassistant.const import STATE_ON, STATE_OFF -from tests.common import MockToggleEntity +from homeassistant.const import STATE_OFF, STATE_ON +from tests.common import MockToggleEntity ENTITIES = [] diff --git a/tests/testing_config/custom_components/test_package/__init__.py b/tests/testing_config/custom_components/test_package/__init__.py index f5cd2c34edf..44f62380c92 100644 --- a/tests/testing_config/custom_components/test_package/__init__.py +++ b/tests/testing_config/custom_components/test_package/__init__.py @@ -1,7 +1,6 @@ """Provide a mock package component.""" from .const import TEST # noqa: F401 - DOMAIN = "test_package" diff --git a/tests/util/test_async.py b/tests/util/test_async.py index 9cda40c1b8b..ec16fa2ae67 100644 --- a/tests/util/test_async.py +++ b/tests/util/test_async.py @@ -1,8 +1,8 @@ """Tests for async util methods from Python source.""" import asyncio import sys -from unittest.mock import MagicMock, patch from unittest import TestCase +from unittest.mock import MagicMock, patch import pytest diff --git a/tests/util/test_distance.py b/tests/util/test_distance.py index 581257d6f49..27b77a883c7 100644 --- a/tests/util/test_distance.py +++ b/tests/util/test_distance.py @@ -2,13 +2,13 @@ import pytest -import homeassistant.util.distance as distance_util from homeassistant.const import ( + LENGTH_FEET, LENGTH_KILOMETERS, LENGTH_METERS, - LENGTH_FEET, LENGTH_MILES, ) +import homeassistant.util.distance as distance_util INVALID_SYMBOL = "bob" VALID_SYMBOL = LENGTH_KILOMETERS diff --git a/tests/util/test_init.py b/tests/util/test_init.py index 261280cc20e..2ffca07082b 100644 --- a/tests/util/test_init.py +++ b/tests/util/test_init.py @@ -1,6 +1,6 @@ """Test Home Assistant util methods.""" -from unittest.mock import patch, MagicMock from datetime import datetime, timedelta +from unittest.mock import MagicMock, patch import pytest diff --git a/tests/util/test_json.py b/tests/util/test_json.py index bec3230c01e..26245482c2f 100644 --- a/tests/util/test_json.py +++ b/tests/util/test_json.py @@ -1,16 +1,15 @@ """Test Home Assistant json utility functions.""" from json import JSONEncoder import os -import unittest -from unittest.mock import Mock import sys from tempfile import mkdtemp +import unittest +from unittest.mock import Mock import pytest -from homeassistant.util.json import SerializationError, load_json, save_json from homeassistant.exceptions import HomeAssistantError - +from homeassistant.util.json import SerializationError, load_json, save_json # Test data that can be saved as JSON TEST_JSON_A = {"a": 1, "B": "two"} diff --git a/tests/util/test_location.py b/tests/util/test_location.py index 4908018410b..6dd6eafca1d 100644 --- a/tests/util/test_location.py +++ b/tests/util/test_location.py @@ -1,5 +1,5 @@ """Test Home Assistant location util methods.""" -from unittest.mock import patch, Mock +from unittest.mock import Mock, patch import aiohttp import pytest diff --git a/tests/util/test_package.py b/tests/util/test_package.py index 79676cbfcd9..ca4ed83734a 100644 --- a/tests/util/test_package.py +++ b/tests/util/test_package.py @@ -2,8 +2,8 @@ import asyncio import logging import os -import sys from subprocess import PIPE +import sys from unittest.mock import MagicMock, call, patch import pkg_resources @@ -11,7 +11,6 @@ import pytest import homeassistant.util.package as package - RESOURCE_DIR = os.path.abspath( os.path.join(os.path.dirname(__file__), "..", "resources") ) diff --git a/tests/util/test_pressure.py b/tests/util/test_pressure.py index 18fa238e43b..df65618dc48 100644 --- a/tests/util/test_pressure.py +++ b/tests/util/test_pressure.py @@ -2,10 +2,10 @@ import pytest from homeassistant.const import ( - PRESSURE_PA, PRESSURE_HPA, - PRESSURE_MBAR, PRESSURE_INHG, + PRESSURE_MBAR, + PRESSURE_PA, PRESSURE_PSI, ) import homeassistant.util.pressure as pressure_util diff --git a/tests/util/test_ruamel_yaml.py b/tests/util/test_ruamel_yaml.py index 9ae806f31ea..79ed4a4f4d1 100644 --- a/tests/util/test_ruamel_yaml.py +++ b/tests/util/test_ruamel_yaml.py @@ -1,14 +1,13 @@ """Test Home Assistant ruamel.yaml loader.""" import os from tempfile import mkdtemp -import pytest +import pytest from ruamel.yaml import YAML from homeassistant.exceptions import HomeAssistantError import homeassistant.util.ruamel_yaml as util_yaml - TEST_YAML_A = """\ title: My Awesome Home # Include external resources diff --git a/tests/util/test_unit_system.py b/tests/util/test_unit_system.py index 827143bc447..e0e4524a2f2 100644 --- a/tests/util/test_unit_system.py +++ b/tests/util/test_unit_system.py @@ -1,20 +1,20 @@ """Test the unit system helper.""" import pytest -from homeassistant.util.unit_system import UnitSystem, METRIC_SYSTEM, IMPERIAL_SYSTEM from homeassistant.const import ( - LENGTH_METERS, - LENGTH_KILOMETERS, - MASS_GRAMS, - PRESSURE_PA, - VOLUME_LITERS, - TEMP_CELSIUS, LENGTH, + LENGTH_KILOMETERS, + LENGTH_METERS, MASS, + MASS_GRAMS, PRESSURE, + PRESSURE_PA, + TEMP_CELSIUS, TEMPERATURE, VOLUME, + VOLUME_LITERS, ) +from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM, UnitSystem SYSTEM_NAME = "TEST" INVALID_UNIT = "INVALID" diff --git a/tests/util/test_volume.py b/tests/util/test_volume.py index 4bf9b7c075d..9bd3e4b1a98 100644 --- a/tests/util/test_volume.py +++ b/tests/util/test_volume.py @@ -2,13 +2,13 @@ import pytest -import homeassistant.util.volume as volume_util from homeassistant.const import ( + VOLUME_FLUID_OUNCE, + VOLUME_GALLONS, VOLUME_LITERS, VOLUME_MILLILITERS, - VOLUME_GALLONS, - VOLUME_FLUID_OUNCE, ) +import homeassistant.util.volume as volume_util INVALID_SYMBOL = "bob" VALID_SYMBOL = VOLUME_LITERS diff --git a/tests/util/test_yaml.py b/tests/util/test_yaml.py index 1e5797e33e7..ba31ce57010 100644 --- a/tests/util/test_yaml.py +++ b/tests/util/test_yaml.py @@ -1,16 +1,17 @@ """Test Home Assistant yaml loader.""" import io +import logging import os import unittest -import logging from unittest.mock import patch import pytest -from homeassistant.exceptions import HomeAssistantError -from homeassistant.util.yaml import loader as yaml_loader -import homeassistant.util.yaml as yaml from homeassistant.config import YAML_CONFIG_FILE, load_yaml_config_file +from homeassistant.exceptions import HomeAssistantError +import homeassistant.util.yaml as yaml +from homeassistant.util.yaml import loader as yaml_loader + from tests.common import get_test_config_dir, patch_yaml_files From c804f8f9613dcb654019d76a0a42615508f1a979 Mon Sep 17 00:00:00 2001 From: Quentame Date: Mon, 9 Dec 2019 17:19:42 +0100 Subject: [PATCH 232/677] Add config flow to iCloud (#28968) * iCloud: setup ConfigFlow and prepare for more platforms - add config flow + tests - fix existing services - add play_sound & display_message services - document services - can use devices with the same name - prepare to add sensor platform * Review : not copy account conf * Review: Safer test patch * Review: remove reset_account * Review: Use executor_job while IO * Review: Use executor_job while IO 2 * Review: use hass.helpers.storage.Store() * Review: no IO in tests * Remove reset from services.yaml * Review: remove authenticate.return_value = Mock() * Review: do not initialize the api with the mocked service * isort * Review: @MartinHjelmare Test config flow with all steps * Review: Fix failed tests names * Codevov: Add one missing test --- .coveragerc | 3 +- CODEOWNERS | 1 + .../components/icloud/.translations/en.json | 38 ++ homeassistant/components/icloud/__init__.py | 607 ++++++++++++++++- .../components/icloud/config_flow.py | 230 +++++++ homeassistant/components/icloud/const.py | 89 ++- .../components/icloud/device_tracker.py | 622 +++--------------- homeassistant/components/icloud/manifest.json | 13 +- homeassistant/components/icloud/services.yaml | 80 ++- homeassistant/components/icloud/strings.json | 38 ++ homeassistant/generated/config_flows.py | 1 + requirements_test_all.txt | 3 + tests/components/icloud/__init__.py | 1 + tests/components/icloud/test_config_flow.py | 309 +++++++++ 14 files changed, 1471 insertions(+), 564 deletions(-) create mode 100644 homeassistant/components/icloud/.translations/en.json create mode 100644 homeassistant/components/icloud/config_flow.py create mode 100644 homeassistant/components/icloud/strings.json create mode 100644 tests/components/icloud/__init__.py create mode 100644 tests/components/icloud/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index 8f872a93d8d..0b73599dffa 100644 --- a/.coveragerc +++ b/.coveragerc @@ -319,7 +319,8 @@ omit = homeassistant/components/iaqualink/light.py homeassistant/components/iaqualink/sensor.py homeassistant/components/iaqualink/switch.py - homeassistant/components/icloud/* + homeassistant/components/icloud/__init__.py + homeassistant/components/icloud/device_tracker.py homeassistant/components/izone/climate.py homeassistant/components/izone/discovery.py homeassistant/components/izone/__init__.py diff --git a/CODEOWNERS b/CODEOWNERS index afea92c8847..392c363c648 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -152,6 +152,7 @@ homeassistant/components/huawei_lte/* @scop homeassistant/components/huawei_router/* @abmantis homeassistant/components/hue/* @balloob homeassistant/components/iaqualink/* @flz +homeassistant/components/icloud/* @Quentame homeassistant/components/ign_sismologia/* @exxamalte homeassistant/components/incomfort/* @zxdavb homeassistant/components/influxdb/* @fabaff diff --git a/homeassistant/components/icloud/.translations/en.json b/homeassistant/components/icloud/.translations/en.json new file mode 100644 index 00000000000..58101759356 --- /dev/null +++ b/homeassistant/components/icloud/.translations/en.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "username_exists": "Account already configured" + }, + "error": { + "login": "Login error: please check your email & password", + "send_verification_code": "Failed to send verification code", + "username_exists": "Account already configured", + "validate_verification_code": "Failed to verify your verification code, choose a trust device and start the verification again" + }, + "step": { + "trusted_device": { + "data": { + "trusted_device": "Trusted device" + }, + "description": "Select your trusted device", + "title": "iCloud trusted device" + }, + "user": { + "data": { + "password": "Password", + "username": "Email" + }, + "description": "Enter your credentials", + "title": "iCloud credentials" + }, + "verification_code": { + "data": { + "verification_code": "Verification code" + }, + "description": "Please enter the verification code you just received from iCloud", + "title": "iCloud verification code" + } + }, + "title": "Apple iCloud" + } +} \ No newline at end of file diff --git a/homeassistant/components/icloud/__init__.py b/homeassistant/components/icloud/__init__.py index 1169104c99d..2012f691938 100644 --- a/homeassistant/components/icloud/__init__.py +++ b/homeassistant/components/icloud/__init__.py @@ -1 +1,606 @@ -"""The icloud component.""" +"""The iCloud component.""" +from datetime import timedelta +import logging +import operator +from typing import Dict + +from pyicloud import PyiCloudService +from pyicloud.exceptions import PyiCloudFailedLoginException, PyiCloudNoDevicesException +from pyicloud.services.findmyiphone import AppleDevice +import voluptuous as vol + +from homeassistant.components.zone import async_active_zone +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry +from homeassistant.const import ATTR_ATTRIBUTION, CONF_PASSWORD, CONF_USERNAME +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import dispatcher_send +from homeassistant.helpers.event import track_point_in_utc_time +from homeassistant.helpers.storage import Store +from homeassistant.helpers.typing import ConfigType, HomeAssistantType, ServiceDataType +from homeassistant.util import slugify +from homeassistant.util.async_ import run_callback_threadsafe +from homeassistant.util.dt import utcnow +from homeassistant.util.location import distance + +from .const import ( + CONF_ACCOUNT_NAME, + CONF_GPS_ACCURACY_THRESHOLD, + CONF_MAX_INTERVAL, + DEFAULT_GPS_ACCURACY_THRESHOLD, + DEFAULT_MAX_INTERVAL, + DEVICE_BATTERY_LEVEL, + DEVICE_BATTERY_STATUS, + DEVICE_CLASS, + DEVICE_DISPLAY_NAME, + DEVICE_ID, + DEVICE_LOCATION, + DEVICE_LOCATION_LATITUDE, + DEVICE_LOCATION_LONGITUDE, + DEVICE_LOST_MODE_CAPABLE, + DEVICE_LOW_POWER_MODE, + DEVICE_NAME, + DEVICE_PERSON_ID, + DEVICE_RAW_DEVICE_MODEL, + DEVICE_STATUS, + DEVICE_STATUS_CODES, + DEVICE_STATUS_SET, + DOMAIN, + ICLOUD_COMPONENTS, + STORAGE_KEY, + STORAGE_VERSION, + TRACKER_UPDATE, +) + +ATTRIBUTION = "Data provided by Apple iCloud" + +# entity attributes +ATTR_ACCOUNT_FETCH_INTERVAL = "account_fetch_interval" +ATTR_BATTERY = "battery" +ATTR_BATTERY_STATUS = "battery_status" +ATTR_DEVICE_NAME = "device_name" +ATTR_DEVICE_STATUS = "device_status" +ATTR_LOW_POWER_MODE = "low_power_mode" +ATTR_OWNER_NAME = "owner_fullname" + +# services +SERVICE_ICLOUD_PLAY_SOUND = "play_sound" +SERVICE_ICLOUD_DISPLAY_MESSAGE = "display_message" +SERVICE_ICLOUD_LOST_DEVICE = "lost_device" +SERVICE_ICLOUD_UPDATE = "update" +ATTR_ACCOUNT = "account" +ATTR_LOST_DEVICE_MESSAGE = "message" +ATTR_LOST_DEVICE_NUMBER = "number" +ATTR_LOST_DEVICE_SOUND = "sound" + +SERVICE_SCHEMA = vol.Schema({vol.Optional(ATTR_ACCOUNT): cv.string}) + +SERVICE_SCHEMA_PLAY_SOUND = vol.Schema( + {vol.Required(ATTR_ACCOUNT): cv.string, vol.Required(ATTR_DEVICE_NAME): cv.string} +) + +SERVICE_SCHEMA_DISPLAY_MESSAGE = vol.Schema( + { + vol.Required(ATTR_ACCOUNT): cv.string, + vol.Required(ATTR_DEVICE_NAME): cv.string, + vol.Required(ATTR_LOST_DEVICE_MESSAGE): cv.string, + vol.Optional(ATTR_LOST_DEVICE_SOUND): cv.boolean, + } +) + +SERVICE_SCHEMA_LOST_DEVICE = vol.Schema( + { + vol.Required(ATTR_ACCOUNT): cv.string, + vol.Required(ATTR_DEVICE_NAME): cv.string, + vol.Required(ATTR_LOST_DEVICE_NUMBER): cv.string, + vol.Required(ATTR_LOST_DEVICE_MESSAGE): cv.string, + } +) + +ACCOUNT_SCHEMA = vol.Schema( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_ACCOUNT_NAME): cv.string, + vol.Optional(CONF_MAX_INTERVAL, default=DEFAULT_MAX_INTERVAL): cv.positive_int, + vol.Optional( + CONF_GPS_ACCURACY_THRESHOLD, default=DEFAULT_GPS_ACCURACY_THRESHOLD + ): cv.positive_int, + } +) + +CONFIG_SCHEMA = vol.Schema( + {DOMAIN: vol.Schema(vol.All(cv.ensure_list, [ACCOUNT_SCHEMA]))}, + extra=vol.ALLOW_EXTRA, +) + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: + """Set up iCloud from legacy config file.""" + + conf = config.get(DOMAIN) + if conf is None: + return True + + for account_conf in conf: + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=account_conf + ) + ) + + return True + + +async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: + """Set up an iCloud account from a config entry.""" + + hass.data.setdefault(DOMAIN, {}) + + username = entry.data[CONF_USERNAME] + password = entry.data[CONF_PASSWORD] + account_name = entry.data.get(CONF_ACCOUNT_NAME) + max_interval = entry.data[CONF_MAX_INTERVAL] + gps_accuracy_threshold = entry.data[CONF_GPS_ACCURACY_THRESHOLD] + + icloud_dir = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) + + account = IcloudAccount( + hass, + username, + password, + icloud_dir, + account_name, + max_interval, + gps_accuracy_threshold, + ) + await hass.async_add_executor_job(account.setup) + hass.data[DOMAIN][username] = account + + for component in ICLOUD_COMPONENTS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, component) + ) + + def play_sound(service: ServiceDataType) -> None: + """Play sound on the device.""" + account = service.data[ATTR_ACCOUNT] + device_name = service.data.get(ATTR_DEVICE_NAME) + device_name = slugify(device_name.replace(" ", "", 99)) + + for device in _get_account(account).get_devices_with_name(device_name): + device.play_sound() + + def display_message(service: ServiceDataType) -> None: + """Display a message on the device.""" + account = service.data[ATTR_ACCOUNT] + device_name = service.data.get(ATTR_DEVICE_NAME) + device_name = slugify(device_name.replace(" ", "", 99)) + message = service.data.get(ATTR_LOST_DEVICE_MESSAGE) + sound = service.data.get(ATTR_LOST_DEVICE_SOUND, False) + + for device in _get_account(account).get_devices_with_name(device_name): + device.display_message(message, sound) + + def lost_device(service: ServiceDataType) -> None: + """Make the device in lost state.""" + account = service.data[ATTR_ACCOUNT] + device_name = service.data.get(ATTR_DEVICE_NAME) + device_name = slugify(device_name.replace(" ", "", 99)) + number = service.data.get(ATTR_LOST_DEVICE_NUMBER) + message = service.data.get(ATTR_LOST_DEVICE_MESSAGE) + + for device in _get_account(account).get_devices_with_name(device_name): + device.lost_device(number, message) + + def update_account(service: ServiceDataType) -> None: + """Call the update function of an iCloud account.""" + account = service.data.get(ATTR_ACCOUNT) + + if account is None: + for account in hass.data[DOMAIN].values(): + account.keep_alive() + else: + _get_account(account).keep_alive() + + def _get_account(account_identifier: str) -> any: + if account_identifier is None: + return None + + icloud_account = hass.data[DOMAIN].get(account_identifier, None) + if icloud_account is None: + for account in hass.data[DOMAIN].values(): + if account.name == account_identifier: + icloud_account = account + + if icloud_account is None: + raise Exception( + "No iCloud account with username or name " + account_identifier + ) + return icloud_account + + hass.services.async_register( + DOMAIN, SERVICE_ICLOUD_PLAY_SOUND, play_sound, schema=SERVICE_SCHEMA_PLAY_SOUND + ) + + hass.services.async_register( + DOMAIN, + SERVICE_ICLOUD_DISPLAY_MESSAGE, + display_message, + schema=SERVICE_SCHEMA_DISPLAY_MESSAGE, + ) + + hass.services.async_register( + DOMAIN, + SERVICE_ICLOUD_LOST_DEVICE, + lost_device, + schema=SERVICE_SCHEMA_LOST_DEVICE, + ) + + hass.services.async_register( + DOMAIN, SERVICE_ICLOUD_UPDATE, update_account, schema=SERVICE_SCHEMA + ) + + return True + + +class IcloudAccount: + """Representation of an iCloud account.""" + + def __init__( + self, + hass: HomeAssistantType, + username: str, + password: str, + icloud_dir: Store, + account_name: str, + max_interval: int, + gps_accuracy_threshold: int, + ): + """Initialize an iCloud account.""" + self.hass = hass + self._username = username + self._password = password + self._name = account_name or slugify(username.partition("@")[0]) + self._fetch_interval = max_interval + self._max_interval = max_interval + self._gps_accuracy_threshold = gps_accuracy_threshold + + self._icloud_dir = icloud_dir + + self.api = None + self._owner_fullname = None + self._family_members_fullname = {} + self._devices = {} + + self.unsub_device_tracker = None + + def setup(self): + """Set up an iCloud account.""" + try: + self.api = PyiCloudService( + self._username, self._password, self._icloud_dir.path + ) + except PyiCloudFailedLoginException as error: + self.api = None + _LOGGER.error("Error logging into iCloud Service: %s", error) + return + + user_info = None + try: + # Gets device owners infos + user_info = self.api.devices.response["userInfo"] + except PyiCloudNoDevicesException: + _LOGGER.error("No iCloud Devices found") + + self._owner_fullname = f"{user_info['firstName']} {user_info['lastName']}" + + self._family_members_fullname = {} + for prs_id, member in user_info["membersInfo"].items(): + self._family_members_fullname[ + prs_id + ] = f"{member['firstName']} {member['lastName']}" + + self._devices = {} + self.update_devices() + + def update_devices(self) -> None: + """Update iCloud devices.""" + if self.api is None: + return + + api_devices = {} + try: + api_devices = self.api.devices + except PyiCloudNoDevicesException: + _LOGGER.error("No iCloud Devices found") + + # Gets devices infos + for device in api_devices: + status = device.status(DEVICE_STATUS_SET) + device_id = status[DEVICE_ID] + device_name = status[DEVICE_NAME] + + if self._devices.get(device_id, None) is not None: + # Seen device -> updating + _LOGGER.debug("Updating iCloud device: %s", device_name) + self._devices[device_id].update(status) + else: + # New device, should be unique + _LOGGER.debug( + "Adding iCloud device: %s [model: %s]", + device_name, + status[DEVICE_RAW_DEVICE_MODEL], + ) + self._devices[device_id] = IcloudDevice(self, device, status) + self._devices[device_id].update(status) + + dispatcher_send(self.hass, TRACKER_UPDATE) + self._fetch_interval = self._determine_interval() + track_point_in_utc_time( + self.hass, + self.keep_alive, + utcnow() + timedelta(minutes=self._fetch_interval), + ) + + def _determine_interval(self) -> int: + """Calculate new interval between two API fetch (in minutes).""" + intervals = {} + for device in self._devices.values(): + if device.location is None: + continue + + current_zone = run_callback_threadsafe( + self.hass.loop, + async_active_zone, + self.hass, + device.location[DEVICE_LOCATION_LATITUDE], + device.location[DEVICE_LOCATION_LONGITUDE], + ).result() + + if current_zone is not None: + intervals[device.name] = self._max_interval + continue + + zones = ( + self.hass.states.get(entity_id) + for entity_id in sorted(self.hass.states.entity_ids("zone")) + ) + + distances = [] + for zone_state in zones: + zone_state_lat = zone_state.attributes[DEVICE_LOCATION_LATITUDE] + zone_state_long = zone_state.attributes[DEVICE_LOCATION_LONGITUDE] + zone_distance = distance( + device.location[DEVICE_LOCATION_LATITUDE], + device.location[DEVICE_LOCATION_LONGITUDE], + zone_state_lat, + zone_state_long, + ) + distances.append(round(zone_distance / 1000, 1)) + + if not distances: + continue + mindistance = min(distances) + + # Calculate out how long it would take for the device to drive + # to the nearest zone at 120 km/h: + interval = round(mindistance / 2, 0) + + # Never poll more than once per minute + interval = max(interval, 1) + + if interval > 180: + # Three hour drive? + # This is far enough that they might be flying + interval = self._max_interval + + if ( + device.battery_level is not None + and device.battery_level <= 33 + and mindistance > 3 + ): + # Low battery - let's check half as often + interval = interval * 2 + + intervals[device.name] = interval + + return max( + int(min(intervals.items(), key=operator.itemgetter(1))[1]), + self._max_interval, + ) + + def keep_alive(self, now=None) -> None: + """Keep the API alive.""" + if self.api is None: + self.setup() + + if self.api is None: + return + + self.api.authenticate() + self.update_devices() + + def get_devices_with_name(self, name: str) -> [any]: + """Get devices by name.""" + result = [] + name_slug = slugify(name.replace(" ", "", 99)) + for device in self.devices.values(): + if slugify(device.name.replace(" ", "", 99)) == name_slug: + result.append(device) + if not result: + raise Exception("No device with name " + name) + return result + + @property + def name(self) -> str: + """Return the account name.""" + return self._name + + @property + def username(self) -> str: + """Return the account username.""" + return self._username + + @property + def owner_fullname(self) -> str: + """Return the account owner fullname.""" + return self._owner_fullname + + @property + def family_members_fullname(self) -> Dict[str, str]: + """Return the account family members fullname.""" + return self._family_members_fullname + + @property + def fetch_interval(self) -> int: + """Return the account fetch interval.""" + return self._fetch_interval + + @property + def devices(self) -> Dict[str, any]: + """Return the account devices.""" + return self._devices + + +class IcloudDevice: + """Representation of a iCloud device.""" + + def __init__(self, account: IcloudAccount, device: AppleDevice, status): + """Initialize the iCloud device.""" + self._account = account + account_name = account.name + + self._device = device + self._status = status + + self._name = self._status[DEVICE_NAME] + self._device_id = self._status[DEVICE_ID] + self._device_class = self._status[DEVICE_CLASS] + self._device_model = self._status[DEVICE_DISPLAY_NAME] + + if self._status[DEVICE_PERSON_ID]: + owner_fullname = account.family_members_fullname[ + self._status[DEVICE_PERSON_ID] + ] + else: + owner_fullname = account.owner_fullname + + self._battery_level = None + self._battery_status = None + self._location = None + + self._attrs = { + ATTR_ATTRIBUTION: ATTRIBUTION, + CONF_ACCOUNT_NAME: account_name, + ATTR_ACCOUNT_FETCH_INTERVAL: self._account.fetch_interval, + ATTR_DEVICE_NAME: self._device_model, + ATTR_DEVICE_STATUS: None, + ATTR_OWNER_NAME: owner_fullname, + } + + def update(self, status) -> None: + """Update the iCloud device.""" + self._status = status + + self._status[ATTR_ACCOUNT_FETCH_INTERVAL] = self._account.fetch_interval + + device_status = DEVICE_STATUS_CODES.get(self._status[DEVICE_STATUS], "error") + self._attrs[ATTR_DEVICE_STATUS] = device_status + + if self._status[DEVICE_BATTERY_STATUS] != "Unknown": + self._battery_level = int(self._status.get(DEVICE_BATTERY_LEVEL, 0) * 100) + self._battery_status = self._status[DEVICE_BATTERY_STATUS] + low_power_mode = self._status[DEVICE_LOW_POWER_MODE] + + self._attrs[ATTR_BATTERY] = self._battery_level + self._attrs[ATTR_BATTERY_STATUS] = self._battery_status + self._attrs[ATTR_LOW_POWER_MODE] = low_power_mode + + if ( + self._status[DEVICE_LOCATION] + and self._status[DEVICE_LOCATION][DEVICE_LOCATION_LATITUDE] + ): + location = self._status[DEVICE_LOCATION] + self._location = location + + def play_sound(self) -> None: + """Play sound on the device.""" + if self._account.api is None: + return + + self._account.api.authenticate() + _LOGGER.debug("Playing sound for %s", self.name) + self.device.play_sound() + + def display_message(self, message: str, sound: bool = False) -> None: + """Display a message on the device.""" + if self._account.api is None: + return + + self._account.api.authenticate() + _LOGGER.debug("Displaying message for %s", self.name) + self.device.display_message("Subject not working", message, sound) + + def lost_device(self, number: str, message: str) -> None: + """Make the device in lost state.""" + if self._account.api is None: + return + + self._account.api.authenticate() + if self._status[DEVICE_LOST_MODE_CAPABLE]: + _LOGGER.debug("Make device lost for %s", self.name) + self.device.lost_device(number, message, None) + else: + _LOGGER.error("Cannot make device lost for %s", self.name) + + @property + def unique_id(self) -> str: + """Return a unique ID.""" + return self._device_id + + @property + def dev_id(self) -> str: + """Return the device ID.""" + return self._device_id + + @property + def name(self) -> str: + """Return the Apple device name.""" + return self._name + + @property + def device(self) -> AppleDevice: + """Return the Apple device.""" + return self._device + + @property + def device_class(self) -> str: + """Return the Apple device class.""" + return self._device_class + + @property + def device_model(self) -> str: + """Return the Apple device model.""" + return self._device_model + + @property + def battery_level(self) -> int: + """Return the Apple device battery level.""" + return self._battery_level + + @property + def battery_status(self) -> str: + """Return the Apple device battery status.""" + return self._battery_status + + @property + def location(self) -> Dict[str, any]: + """Return the Apple device location.""" + return self._location + + @property + def state_attributes(self) -> Dict[str, any]: + """Return the attributes.""" + return self._attrs diff --git a/homeassistant/components/icloud/config_flow.py b/homeassistant/components/icloud/config_flow.py new file mode 100644 index 00000000000..cf05c07e26f --- /dev/null +++ b/homeassistant/components/icloud/config_flow.py @@ -0,0 +1,230 @@ +"""Config flow to configure the iCloud integration.""" +import logging +import os + +from pyicloud import PyiCloudService +from pyicloud.exceptions import PyiCloudException, PyiCloudFailedLoginException +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.util import slugify + +from .const import ( + CONF_ACCOUNT_NAME, + CONF_GPS_ACCURACY_THRESHOLD, + CONF_MAX_INTERVAL, + DEFAULT_GPS_ACCURACY_THRESHOLD, + DEFAULT_MAX_INTERVAL, + STORAGE_KEY, + STORAGE_VERSION, +) +from .const import DOMAIN # pylint: disable=unused-import + +CONF_TRUSTED_DEVICE = "trusted_device" +CONF_VERIFICATION_CODE = "verification_code" + +_LOGGER = logging.getLogger(__name__) + + +class IcloudFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a iCloud config flow.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + + def __init__(self): + """Initialize iCloud config flow.""" + self.api = None + self._username = None + self._password = None + self._account_name = None + self._max_interval = None + self._gps_accuracy_threshold = None + + self._trusted_device = None + self._verification_code = None + + def _configuration_exists(self, username: str, account_name: str) -> bool: + """Return True if username or account_name exists in configuration.""" + for entry in self._async_current_entries(): + if ( + entry.data[CONF_USERNAME] == username + or entry.data.get(CONF_ACCOUNT_NAME) == account_name + or slugify(entry.data[CONF_USERNAME].partition("@")[0]) == account_name + ): + return True + return False + + async def _show_setup_form(self, user_input=None, errors=None): + """Show the setup form to the user.""" + + if user_input is None: + user_input = {} + + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Required( + CONF_USERNAME, default=user_input.get(CONF_USERNAME, "") + ): str, + vol.Required( + CONF_PASSWORD, default=user_input.get(CONF_PASSWORD, "") + ): str, + } + ), + errors=errors or {}, + ) + + async def async_step_user(self, user_input=None): + """Handle a flow initiated by the user.""" + errors = {} + + icloud_dir = self.hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) + + if not os.path.exists(icloud_dir.path): + await self.hass.async_add_executor_job(os.makedirs, icloud_dir.path) + + if user_input is None: + return await self._show_setup_form(user_input, errors) + + self._username = user_input[CONF_USERNAME] + self._password = user_input[CONF_PASSWORD] + self._account_name = user_input.get(CONF_ACCOUNT_NAME) + self._max_interval = user_input.get(CONF_MAX_INTERVAL, DEFAULT_MAX_INTERVAL) + self._gps_accuracy_threshold = user_input.get( + CONF_GPS_ACCURACY_THRESHOLD, DEFAULT_GPS_ACCURACY_THRESHOLD + ) + + if self._configuration_exists(self._username, self._account_name): + errors[CONF_USERNAME] = "username_exists" + return await self._show_setup_form(user_input, errors) + + try: + self.api = await self.hass.async_add_executor_job( + PyiCloudService, self._username, self._password, icloud_dir.path + ) + except PyiCloudFailedLoginException as error: + _LOGGER.error("Error logging into iCloud service: %s", error) + self.api = None + errors[CONF_USERNAME] = "login" + return await self._show_setup_form(user_input, errors) + + if self.api.requires_2fa: + return await self.async_step_trusted_device() + + return self.async_create_entry( + title=self._username, + data={ + CONF_USERNAME: self._username, + CONF_PASSWORD: self._password, + CONF_ACCOUNT_NAME: self._account_name, + CONF_MAX_INTERVAL: self._max_interval, + CONF_GPS_ACCURACY_THRESHOLD: self._gps_accuracy_threshold, + }, + ) + + async def async_step_import(self, user_input): + """Import a config entry.""" + if self._configuration_exists( + user_input[CONF_USERNAME], user_input.get(CONF_ACCOUNT_NAME) + ): + return self.async_abort(reason="username_exists") + + return await self.async_step_user(user_input) + + async def async_step_trusted_device(self, user_input=None, errors=None): + """We need a trusted device.""" + if errors is None: + errors = {} + + trusted_devices = await self.hass.async_add_executor_job( + getattr, self.api, "trusted_devices" + ) + trusted_devices_for_form = {} + for i, device in enumerate(trusted_devices): + trusted_devices_for_form[i] = device.get( + "deviceName", f"SMS to {device.get('phoneNumber')}" + ) + + if user_input is None: + return await self._show_trusted_device_form( + trusted_devices_for_form, user_input, errors + ) + + self._trusted_device = trusted_devices[int(user_input[CONF_TRUSTED_DEVICE])] + + if not await self.hass.async_add_executor_job( + self.api.send_verification_code, self._trusted_device + ): + _LOGGER.error("Failed to send verification code") + self._trusted_device = None + errors[CONF_TRUSTED_DEVICE] = "send_verification_code" + + return await self._show_trusted_device_form( + trusted_devices_for_form, user_input, errors + ) + + return await self.async_step_verification_code() + + async def _show_trusted_device_form( + self, trusted_devices, user_input=None, errors=None + ): + """Show the trusted_device form to the user.""" + + return self.async_show_form( + step_id=CONF_TRUSTED_DEVICE, + data_schema=vol.Schema( + { + vol.Required(CONF_TRUSTED_DEVICE): vol.All( + vol.Coerce(int), vol.In(trusted_devices) + ) + } + ), + errors=errors or {}, + ) + + async def async_step_verification_code(self, user_input=None): + """Ask the verification code to the user.""" + errors = {} + + if user_input is None: + return await self._show_verification_code_form(user_input) + + self._verification_code = user_input[CONF_VERIFICATION_CODE] + + try: + if not await self.hass.async_add_executor_job( + self.api.validate_verification_code, + self._trusted_device, + self._verification_code, + ): + raise PyiCloudException("The code you entered is not valid.") + except PyiCloudException as error: + # Reset to the initial 2FA state to allow the user to retry + _LOGGER.error("Failed to verify verification code: %s", error) + self._trusted_device = None + self._verification_code = None + errors["base"] = "validate_verification_code" + + return await self.async_step_trusted_device(None, errors) + + return await self.async_step_user( + { + CONF_USERNAME: self._username, + CONF_PASSWORD: self._password, + CONF_ACCOUNT_NAME: self._account_name, + CONF_MAX_INTERVAL: self._max_interval, + CONF_GPS_ACCURACY_THRESHOLD: self._gps_accuracy_threshold, + } + ) + + async def _show_verification_code_form(self, user_input=None): + """Show the verification_code form to the user.""" + + return self.async_show_form( + step_id=CONF_VERIFICATION_CODE, + data_schema=vol.Schema({vol.Required(CONF_VERIFICATION_CODE): str}), + errors=None, + ) diff --git a/homeassistant/components/icloud/const.py b/homeassistant/components/icloud/const.py index fe8010df703..4e99a378077 100644 --- a/homeassistant/components/icloud/const.py +++ b/homeassistant/components/icloud/const.py @@ -1,6 +1,85 @@ -"""Constants for the iCloud component.""" +"""iCloud component constants.""" + DOMAIN = "icloud" -SERVICE_LOST_IPHONE = "lost_iphone" -SERVICE_UPDATE = "update" -SERVICE_RESET_ACCOUNT = "reset_account" -SERVICE_SET_INTERVAL = "set_interval" +TRACKER_UPDATE = f"{DOMAIN}_tracker_update" + +CONF_ACCOUNT_NAME = "account_name" +CONF_MAX_INTERVAL = "max_interval" +CONF_GPS_ACCURACY_THRESHOLD = "gps_accuracy_threshold" + +DEFAULT_MAX_INTERVAL = 30 # min +DEFAULT_GPS_ACCURACY_THRESHOLD = 500 # meters + +# to store the cookie +STORAGE_KEY = DOMAIN +STORAGE_VERSION = 1 + +# Next PR will add sensor +ICLOUD_COMPONENTS = ["device_tracker"] + +# pyicloud.AppleDevice status +DEVICE_BATTERY_LEVEL = "batteryLevel" +DEVICE_BATTERY_STATUS = "batteryStatus" +DEVICE_CLASS = "deviceClass" +DEVICE_DISPLAY_NAME = "deviceDisplayName" +DEVICE_ID = "id" +DEVICE_LOCATION = "location" +DEVICE_LOCATION_HORIZONTAL_ACCURACY = "horizontalAccuracy" +DEVICE_LOCATION_LATITUDE = "latitude" +DEVICE_LOCATION_LONGITUDE = "longitude" +DEVICE_LOST_MODE_CAPABLE = "lostModeCapable" +DEVICE_LOW_POWER_MODE = "lowPowerMode" +DEVICE_NAME = "name" +DEVICE_PERSON_ID = "prsId" +DEVICE_RAW_DEVICE_MODEL = "rawDeviceModel" +DEVICE_STATUS = "deviceStatus" + +DEVICE_STATUS_SET = [ + "features", + "maxMsgChar", + "darkWake", + "fmlyShare", + DEVICE_STATUS, + "remoteLock", + "activationLocked", + DEVICE_CLASS, + DEVICE_ID, + "deviceModel", + DEVICE_RAW_DEVICE_MODEL, + "passcodeLength", + "canWipeAfterLock", + "trackingInfo", + DEVICE_LOCATION, + "msg", + DEVICE_BATTERY_LEVEL, + "remoteWipe", + "thisDevice", + "snd", + DEVICE_PERSON_ID, + "wipeInProgress", + DEVICE_LOW_POWER_MODE, + "lostModeEnabled", + "isLocating", + DEVICE_LOST_MODE_CAPABLE, + "mesg", + DEVICE_NAME, + DEVICE_BATTERY_STATUS, + "lockedTimestamp", + "lostTimestamp", + "locationCapable", + DEVICE_DISPLAY_NAME, + "lostDevice", + "deviceColor", + "wipedTimestamp", + "modelDisplayName", + "locationEnabled", + "isMac", + "locFoundEnabled", +] + +DEVICE_STATUS_CODES = { + "200": "online", + "201": "offline", + "203": "pending", + "204": "unregistered", +} diff --git a/homeassistant/components/icloud/device_tracker.py b/homeassistant/components/icloud/device_tracker.py index 3d9fb4715da..4be34728c6d 100644 --- a/homeassistant/components/icloud/device_tracker.py +++ b/homeassistant/components/icloud/device_tracker.py @@ -1,544 +1,132 @@ -"""Platform that supports scanning iCloud.""" +"""Support for tracking for iCloud devices.""" import logging -import os -import random -from pyicloud import PyiCloudService -from pyicloud.exceptions import ( - PyiCloudException, - PyiCloudFailedLoginException, - PyiCloudNoDevicesException, -) -import voluptuous as vol - -from homeassistant.components.device_tracker import PLATFORM_SCHEMA -from homeassistant.components.device_tracker.const import ( - ATTR_ATTRIBUTES, - ENTITY_ID_FORMAT, -) -from homeassistant.components.device_tracker.legacy import DeviceScanner -from homeassistant.components.zone import async_active_zone -from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.event import track_utc_time_change -from homeassistant.util import slugify -from homeassistant.util.async_ import run_callback_threadsafe -import homeassistant.util.dt as dt_util -from homeassistant.util.location import distance +from homeassistant.components.device_tracker import SOURCE_TYPE_GPS +from homeassistant.components.device_tracker.config_entry import TrackerEntity +from homeassistant.const import CONF_USERNAME +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.typing import HomeAssistantType +from . import IcloudDevice from .const import ( + DEVICE_LOCATION_HORIZONTAL_ACCURACY, + DEVICE_LOCATION_LATITUDE, + DEVICE_LOCATION_LONGITUDE, DOMAIN, - SERVICE_LOST_IPHONE, - SERVICE_RESET_ACCOUNT, - SERVICE_SET_INTERVAL, - SERVICE_UPDATE, + TRACKER_UPDATE, ) _LOGGER = logging.getLogger(__name__) -CONF_ACCOUNTNAME = "account_name" -CONF_MAX_INTERVAL = "max_interval" -CONF_GPS_ACCURACY_THRESHOLD = "gps_accuracy_threshold" -# entity attributes -ATTR_ACCOUNTNAME = "account_name" -ATTR_INTERVAL = "interval" -ATTR_DEVICENAME = "device_name" -ATTR_BATTERY = "battery" -ATTR_DISTANCE = "distance" -ATTR_DEVICESTATUS = "device_status" -ATTR_LOWPOWERMODE = "low_power_mode" -ATTR_BATTERYSTATUS = "battery_status" - -ICLOUDTRACKERS = {} - -_CONFIGURING = {} - -DEVICESTATUSSET = [ - "features", - "maxMsgChar", - "darkWake", - "fmlyShare", - "deviceStatus", - "remoteLock", - "activationLocked", - "deviceClass", - "id", - "deviceModel", - "rawDeviceModel", - "passcodeLength", - "canWipeAfterLock", - "trackingInfo", - "location", - "msg", - "batteryLevel", - "remoteWipe", - "thisDevice", - "snd", - "prsId", - "wipeInProgress", - "lowPowerMode", - "lostModeEnabled", - "isLocating", - "lostModeCapable", - "mesg", - "name", - "batteryStatus", - "lockedTimestamp", - "lostTimestamp", - "locationCapable", - "deviceDisplayName", - "lostDevice", - "deviceColor", - "wipedTimestamp", - "modelDisplayName", - "locationEnabled", - "isMac", - "locFoundEnabled", -] - -DEVICESTATUSCODES = { - "200": "online", - "201": "offline", - "203": "pending", - "204": "unregistered", -} - -SERVICE_SCHEMA = vol.Schema( - { - vol.Optional(ATTR_ACCOUNTNAME): vol.All(cv.ensure_list, [cv.slugify]), - vol.Optional(ATTR_DEVICENAME): cv.slugify, - vol.Optional(ATTR_INTERVAL): cv.positive_int, - } -) - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(ATTR_ACCOUNTNAME): cv.slugify, - vol.Optional(CONF_MAX_INTERVAL, default=30): cv.positive_int, - vol.Optional(CONF_GPS_ACCURACY_THRESHOLD, default=1000): cv.positive_int, - } -) +async def async_setup_scanner( + hass: HomeAssistantType, config, see, discovery_info=None +): + """Old way of setting up the iCloud tracker.""" + pass -def setup_scanner(hass, config: dict, see, discovery_info=None): - """Set up the iCloud Scanner.""" - username = config.get(CONF_USERNAME) - password = config.get(CONF_PASSWORD) - account = config.get(CONF_ACCOUNTNAME, slugify(username.partition("@")[0])) - max_interval = config.get(CONF_MAX_INTERVAL) - gps_accuracy_threshold = config.get(CONF_GPS_ACCURACY_THRESHOLD) +async def async_setup_entry(hass: HomeAssistantType, entry, async_add_entities): + """Configure a dispatcher connection based on a config entry.""" + username = entry.data[CONF_USERNAME] - icloudaccount = Icloud( - hass, username, password, account, max_interval, gps_accuracy_threshold, see - ) + for device in hass.data[DOMAIN][username].devices.values(): + if device.location is None: + _LOGGER.debug("No position found for device %s", device.name) + continue - if icloudaccount.api is not None: - ICLOUDTRACKERS[account] = icloudaccount + _LOGGER.debug("Adding device_tracker for %s", device.name) - else: - _LOGGER.error("No ICLOUDTRACKERS added") + async_add_entities([IcloudTrackerEntity(device)]) + + +class IcloudTrackerEntity(TrackerEntity): + """Represent a tracked device.""" + + def __init__(self, device: IcloudDevice): + """Set up the iCloud tracker entity.""" + self._device = device + self._unsub_dispatcher = None + + @property + def unique_id(self): + """Return a unique ID.""" + return f"{self._device.unique_id}_tracker" + + @property + def name(self): + """Return the name of the device.""" + return self._device.name + + @property + def location_accuracy(self): + """Return the location accuracy of the device.""" + return self._device.location[DEVICE_LOCATION_HORIZONTAL_ACCURACY] + + @property + def latitude(self): + """Return latitude value of the device.""" + return self._device.location[DEVICE_LOCATION_LATITUDE] + + @property + def longitude(self): + """Return longitude value of the device.""" + return self._device.location[DEVICE_LOCATION_LONGITUDE] + + @property + def should_poll(self): + """No polling needed.""" return False - def lost_iphone(call): - """Call the lost iPhone function if the device is found.""" - accounts = call.data.get(ATTR_ACCOUNTNAME, ICLOUDTRACKERS) - devicename = call.data.get(ATTR_DEVICENAME) - for account in accounts: - if account in ICLOUDTRACKERS: - ICLOUDTRACKERS[account].lost_iphone(devicename) + @property + def battery_level(self): + """Return the battery level of the device.""" + return self._device.battery_level - hass.services.register( - DOMAIN, SERVICE_LOST_IPHONE, lost_iphone, schema=SERVICE_SCHEMA - ) + @property + def source_type(self): + """Return the source type, eg gps or router, of the device.""" + return SOURCE_TYPE_GPS - def update_icloud(call): - """Call the update function of an iCloud account.""" - accounts = call.data.get(ATTR_ACCOUNTNAME, ICLOUDTRACKERS) - devicename = call.data.get(ATTR_DEVICENAME) - for account in accounts: - if account in ICLOUDTRACKERS: - ICLOUDTRACKERS[account].update_icloud(devicename) + @property + def icon(self): + """Return the icon.""" + return icon_for_icloud_device(self._device) - hass.services.register(DOMAIN, SERVICE_UPDATE, update_icloud, schema=SERVICE_SCHEMA) + @property + def device_state_attributes(self): + """Return the device state attributes.""" + return self._device.state_attributes - def reset_account_icloud(call): - """Reset an iCloud account.""" - accounts = call.data.get(ATTR_ACCOUNTNAME, ICLOUDTRACKERS) - for account in accounts: - if account in ICLOUDTRACKERS: - ICLOUDTRACKERS[account].reset_account_icloud() + @property + def device_info(self): + """Return the device information.""" + return { + "identifiers": {(DOMAIN, self.unique_id)}, + "name": self.name, + "manufacturer": "Apple", + "model": self._device.device_model, + } - hass.services.register( - DOMAIN, SERVICE_RESET_ACCOUNT, reset_account_icloud, schema=SERVICE_SCHEMA - ) - - def setinterval(call): - """Call the update function of an iCloud account.""" - accounts = call.data.get(ATTR_ACCOUNTNAME, ICLOUDTRACKERS) - interval = call.data.get(ATTR_INTERVAL) - devicename = call.data.get(ATTR_DEVICENAME) - for account in accounts: - if account in ICLOUDTRACKERS: - ICLOUDTRACKERS[account].setinterval(interval, devicename) - - hass.services.register( - DOMAIN, SERVICE_SET_INTERVAL, setinterval, schema=SERVICE_SCHEMA - ) - - # Tells the bootstrapper that the component was successfully initialized - return True - - -class Icloud(DeviceScanner): - """Representation of an iCloud account.""" - - def __init__( - self, hass, username, password, name, max_interval, gps_accuracy_threshold, see - ): - """Initialize an iCloud account.""" - self.hass = hass - self.username = username - self.password = password - self.api = None - self.accountname = name - self.devices = {} - self.seen_devices = {} - self._overridestates = {} - self._intervals = {} - self._max_interval = max_interval - self._gps_accuracy_threshold = gps_accuracy_threshold - self.see = see - - self._trusted_device = None - self._verification_code = None - - self._attrs = {} - self._attrs[ATTR_ACCOUNTNAME] = name - - self.reset_account_icloud() - - randomseconds = random.randint(10, 59) - track_utc_time_change(self.hass, self.keep_alive, second=randomseconds) - - def reset_account_icloud(self): - """Reset an iCloud account.""" - icloud_dir = self.hass.config.path("icloud") - if not os.path.exists(icloud_dir): - os.makedirs(icloud_dir) - - try: - self.api = PyiCloudService( - self.username, self.password, cookie_directory=icloud_dir, verify=True - ) - except PyiCloudFailedLoginException as error: - self.api = None - _LOGGER.error("Error logging into iCloud Service: %s", error) - return - - try: - self.devices = {} - self._overridestates = {} - self._intervals = {} - for device in self.api.devices: - status = device.status(DEVICESTATUSSET) - _LOGGER.debug("Device Status is %s", status) - devicename = slugify(status["name"].replace(" ", "", 99)) - _LOGGER.info("Adding icloud device: %s", devicename) - if devicename in self.devices: - _LOGGER.error("Multiple devices with name: %s", devicename) - continue - self.devices[devicename] = device - self._intervals[devicename] = 1 - self._overridestates[devicename] = None - except PyiCloudNoDevicesException: - _LOGGER.error("No iCloud Devices found!") - - def icloud_trusted_device_callback(self, callback_data): - """Handle chosen trusted devices.""" - self._trusted_device = int(callback_data.get("trusted_device")) - self._trusted_device = self.api.trusted_devices[self._trusted_device] - - if not self.api.send_verification_code(self._trusted_device): - _LOGGER.error("Failed to send verification code") - self._trusted_device = None - return - - if self.accountname in _CONFIGURING: - request_id = _CONFIGURING.pop(self.accountname) - configurator = self.hass.components.configurator - configurator.request_done(request_id) - - # Trigger the next step immediately - self.icloud_need_verification_code() - - def icloud_need_trusted_device(self): - """We need a trusted device.""" - configurator = self.hass.components.configurator - if self.accountname in _CONFIGURING: - return - - devicesstring = "" - devices = self.api.trusted_devices - for i, device in enumerate(devices): - devicename = device.get( - "deviceName", "SMS to %s" % device.get("phoneNumber") - ) - devicesstring += f"{i}: {devicename};" - - _CONFIGURING[self.accountname] = configurator.request_config( - f"iCloud {self.accountname}", - self.icloud_trusted_device_callback, - description=( - "Please choose your trusted device by entering" - " the index from this list: " + devicesstring - ), - entity_picture="/static/images/config_icloud.png", - submit_caption="Confirm", - fields=[{"id": "trusted_device", "name": "Trusted Device"}], + async def async_added_to_hass(self): + """Register state update callback.""" + self._unsub_dispatcher = async_dispatcher_connect( + self.hass, TRACKER_UPDATE, self.async_write_ha_state ) - def icloud_verification_callback(self, callback_data): - """Handle the chosen trusted device.""" - self._verification_code = callback_data.get("code") + async def async_will_remove_from_hass(self): + """Clean up after entity before removal.""" + self._unsub_dispatcher() - try: - if not self.api.validate_verification_code( - self._trusted_device, self._verification_code - ): - raise PyiCloudException("Unknown failure") - except PyiCloudException as error: - # Reset to the initial 2FA state to allow the user to retry - _LOGGER.error("Failed to verify verification code: %s", error) - self._trusted_device = None - self._verification_code = None - # Trigger the next step immediately - self.icloud_need_trusted_device() +def icon_for_icloud_device(icloud_device: IcloudDevice) -> str: + """Return a battery icon valid identifier.""" + switcher = { + "iPad": "mdi:tablet-ipad", + "iPhone": "mdi:cellphone-iphone", + "iPod": "mdi:ipod", + "iMac": "mdi:desktop-mac", + "MacBookPro": "mdi:laptop-mac", + } - if self.accountname in _CONFIGURING: - request_id = _CONFIGURING.pop(self.accountname) - configurator = self.hass.components.configurator - configurator.request_done(request_id) - - def icloud_need_verification_code(self): - """Return the verification code.""" - configurator = self.hass.components.configurator - if self.accountname in _CONFIGURING: - return - - _CONFIGURING[self.accountname] = configurator.request_config( - f"iCloud {self.accountname}", - self.icloud_verification_callback, - description=("Please enter the validation code:"), - entity_picture="/static/images/config_icloud.png", - submit_caption="Confirm", - fields=[{"id": "code", "name": "code"}], - ) - - def keep_alive(self, now): - """Keep the API alive.""" - if self.api is None: - self.reset_account_icloud() - - if self.api is None: - return - - if self.api.requires_2fa: - try: - if self._trusted_device is None: - self.icloud_need_trusted_device() - return - - if self._verification_code is None: - self.icloud_need_verification_code() - return - - self.api.authenticate() - if self.api.requires_2fa: - raise Exception("Unknown failure") - - self._trusted_device = None - self._verification_code = None - except PyiCloudException as error: - _LOGGER.error("Error setting up 2FA: %s", error) - else: - self.api.authenticate() - - currentminutes = dt_util.now().hour * 60 + dt_util.now().minute - try: - for devicename in self.devices: - interval = self._intervals.get(devicename, 1) - if (currentminutes % interval == 0) or ( - interval > 10 and currentminutes % interval in [2, 4] - ): - self.update_device(devicename) - except ValueError: - _LOGGER.debug("iCloud API returned an error") - - def determine_interval(self, devicename, latitude, longitude, battery): - """Calculate new interval.""" - currentzone = run_callback_threadsafe( - self.hass.loop, async_active_zone, self.hass, latitude, longitude - ).result() - - if ( - currentzone is not None - and currentzone == self._overridestates.get(devicename) - ) or (currentzone is None and self._overridestates.get(devicename) == "away"): - return - - zones = ( - self.hass.states.get(entity_id) - for entity_id in sorted(self.hass.states.entity_ids("zone")) - ) - - distances = [] - for zone_state in zones: - zone_state_lat = zone_state.attributes["latitude"] - zone_state_long = zone_state.attributes["longitude"] - zone_distance = distance( - latitude, longitude, zone_state_lat, zone_state_long - ) - distances.append(round(zone_distance / 1000, 1)) - - if distances: - mindistance = min(distances) - else: - mindistance = None - - self._overridestates[devicename] = None - - if currentzone is not None: - self._intervals[devicename] = self._max_interval - return - - if mindistance is None: - return - - # Calculate out how long it would take for the device to drive to the - # nearest zone at 120 km/h: - interval = round(mindistance / 2, 0) - - # Never poll more than once per minute - interval = max(interval, 1) - - if interval > 180: - # Three hour drive? This is far enough that they might be flying - interval = 30 - - if battery is not None and battery <= 33 and mindistance > 3: - # Low battery - let's check half as often - interval = interval * 2 - - self._intervals[devicename] = interval - - def update_device(self, devicename): - """Update the device_tracker entity.""" - # An entity will not be created by see() when track=false in - # 'known_devices.yaml', but we need to see() it at least once - entity = self.hass.states.get(ENTITY_ID_FORMAT.format(devicename)) - if entity is None and devicename in self.seen_devices: - return - attrs = {} - kwargs = {} - - if self.api is None: - return - - try: - for device in self.api.devices: - if str(device) != str(self.devices[devicename]): - continue - - status = device.status(DEVICESTATUSSET) - _LOGGER.debug("Device Status is %s", status) - dev_id = status["name"].replace(" ", "", 99) - dev_id = slugify(dev_id) - attrs[ATTR_DEVICESTATUS] = DEVICESTATUSCODES.get( - status["deviceStatus"], "error" - ) - attrs[ATTR_LOWPOWERMODE] = status["lowPowerMode"] - attrs[ATTR_BATTERYSTATUS] = status["batteryStatus"] - attrs[ATTR_ACCOUNTNAME] = self.accountname - status = device.status(DEVICESTATUSSET) - battery = status.get("batteryLevel", 0) * 100 - location = status["location"] - if location and location["horizontalAccuracy"]: - horizontal_accuracy = int(location["horizontalAccuracy"]) - if horizontal_accuracy < self._gps_accuracy_threshold: - self.determine_interval( - devicename, - location["latitude"], - location["longitude"], - battery, - ) - interval = self._intervals.get(devicename, 1) - attrs[ATTR_INTERVAL] = interval - accuracy = location["horizontalAccuracy"] - kwargs["dev_id"] = dev_id - kwargs["host_name"] = status["name"] - kwargs["gps"] = (location["latitude"], location["longitude"]) - kwargs["battery"] = battery - kwargs["gps_accuracy"] = accuracy - kwargs[ATTR_ATTRIBUTES] = attrs - self.see(**kwargs) - self.seen_devices[devicename] = True - except PyiCloudNoDevicesException: - _LOGGER.error("No iCloud Devices found") - - def lost_iphone(self, devicename): - """Call the lost iPhone function if the device is found.""" - if self.api is None: - return - - self.api.authenticate() - for device in self.api.devices: - if str(device) == str(self.devices[devicename]): - _LOGGER.info("Playing Lost iPhone sound for %s", devicename) - device.play_sound() - - def update_icloud(self, devicename=None): - """Request device information from iCloud and update device_tracker.""" - if self.api is None: - return - - try: - if devicename is not None: - if devicename in self.devices: - self.update_device(devicename) - else: - _LOGGER.error( - "devicename %s unknown for account %s", - devicename, - self._attrs[ATTR_ACCOUNTNAME], - ) - else: - for device in self.devices: - self.update_device(device) - except PyiCloudNoDevicesException: - _LOGGER.error("No iCloud Devices found") - - def setinterval(self, interval=None, devicename=None): - """Set the interval of the given devices.""" - devs = [devicename] if devicename else self.devices - for device in devs: - devid = f"{DOMAIN}.{device}" - devicestate = self.hass.states.get(devid) - if interval is not None: - if devicestate is not None: - self._overridestates[device] = run_callback_threadsafe( - self.hass.loop, - async_active_zone, - self.hass, - float(devicestate.attributes.get("latitude", 0)), - float(devicestate.attributes.get("longitude", 0)), - ).result() - if self._overridestates[device] is None: - self._overridestates[device] = "away" - self._intervals[device] = interval - else: - self._overridestates[device] = None - self.update_device(device) + return switcher.get(icloud_device.device_class, "mdi:cellphone-link") diff --git a/homeassistant/components/icloud/manifest.json b/homeassistant/components/icloud/manifest.json index d3924ee61a8..f7295ceae4d 100644 --- a/homeassistant/components/icloud/manifest.json +++ b/homeassistant/components/icloud/manifest.json @@ -1,10 +1,13 @@ { "domain": "icloud", - "name": "Icloud", - "documentation": "https://www.home-assistant.io/integrations/icloud", + "name": "iCloud", + "config_flow": true, + "documentation": "https://www.home-assistant.io/components/icloud", "requirements": [ "pyicloud==0.9.1" ], - "dependencies": ["configurator"], - "codeowners": [] -} + "dependencies": [], + "codeowners": [ + "@Quentame" + ] +} \ No newline at end of file diff --git a/homeassistant/components/icloud/services.yaml b/homeassistant/components/icloud/services.yaml index 7b2d3b80e84..ce239df7564 100644 --- a/homeassistant/components/icloud/services.yaml +++ b/homeassistant/components/icloud/services.yaml @@ -1,39 +1,49 @@ -lost_iphone: - description: Service to play the lost iphone sound on an iDevice. - fields: - account_name: - description: Name of the account in the config that will be used to look for the device. This is optional, if it isn't given it will use all accounts. - example: 'bart' - device_name: - description: Name of the device that will play the sound. This is optional, if it isn't given it will play on all devices for the given account. - example: 'iphonebart' - -set_interval: - description: Service to set the interval of an iDevice. - fields: - account_name: - description: Name of the account in the config that will be used to look for the device. This is optional, if it isn't given it will use all accounts. - example: 'bart' - device_name: - description: Name of the device that will get a new interval. This is optional, if it isn't given it will change the interval for all devices for the given account. - example: 'iphonebart' - interval: - description: The interval (in minutes) that the iDevice will have until the according device_tracker entity changes from zone or until this service is used again. This is optional, if it isn't given the interval of the device will revert back to the original interval based on the current state. - example: 1 - update: - description: Service to ask for an update of an iDevice. + description: Update iCloud devices. fields: - account_name: - description: Name of the account in the config that will be used to look for the device. This is optional, if it isn't given it will use all accounts. - example: 'bart' - device_name: - description: Name of the device that will be updated. This is optional, if it isn't given it will update all devices for the given account. - example: 'iphonebart' + account: + description: Your iCloud account username (email) or account name. + example: 'steve@apple.com' -reset_account: - description: Service to restart an iCloud account. Helpful when not all devices are found after initializing or when you add a new device. +play_sound: + description: Play sound on an Apple device. fields: - account_name: - description: Name of the account in the config that will be restarted. This is optional, if it isn't given it will restart all accounts. - example: 'bart' + account: + description: (required) Your iCloud account username (email) or account name. + example: 'steve@apple.com' + device_name: + description: (required) The name of the Apple device to play a sound. + example: 'stevesiphone' + +display_message: + description: Display a message on an Apple device. + fields: + account: + description: (required) Your iCloud account username (email) or account name. + example: 'steve@apple.com' + device_name: + description: (required) The name of the Apple device to display the message. + example: 'stevesiphone' + message: + description: (required) The content of your message. + example: 'Hey Steve !' + sound: + description: To make a sound when displaying the message (boolean). + example: 'true' + +lost_device: + description: Make an Apple device in lost state. + fields: + account: + description: (required) Your iCloud account username (email) or account name. + example: 'steve@apple.com' + device_name: + description: (required) The name of the Apple device to set lost. + example: 'stevesiphone' + number: + description: (required) The phone number to call in lost mode (must contain country code). + example: '+33450020100' + message: + description: (required) The message to display in lost mode. + example: 'Call me' + diff --git a/homeassistant/components/icloud/strings.json b/homeassistant/components/icloud/strings.json new file mode 100644 index 00000000000..343a087738f --- /dev/null +++ b/homeassistant/components/icloud/strings.json @@ -0,0 +1,38 @@ +{ + "config": { + "title": "Apple iCloud", + "step": { + "user": { + "title": "iCloud credentials", + "description": "Enter your credentials", + "data": { + "username": "Email", + "password": "Password" + } + }, + "trusted_device": { + "title": "iCloud trusted device", + "description": "Select your trusted device", + "data": { + "trusted_device": "Trusted device" + } + }, + "verification_code": { + "title": "iCloud verification code", + "description": "Please enter the verification code you just received from iCloud", + "data": { + "verification_code": "Verification code" + } + } + }, + "error":{ + "username_exists": "Account already configured", + "login": "Login error: please check your email & password", + "send_verification_code": "Failed to send verification code", + "validate_verification_code": "Failed to verify your verification code, choose a trust device and start the verification again", + }, + "abort":{ + "username_exists": "Account already configured" + } + } +} diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index cf1c4b55e19..2b3940000e7 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -36,6 +36,7 @@ FLOWS = [ "huawei_lte", "hue", "iaqualink", + "icloud", "ifttt", "ios", "ipma", diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4cb7ca26a87..3812b8f0b33 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -432,6 +432,9 @@ pyheos==0.6.0 # homeassistant.components.homematic pyhomematic==0.1.62 +# homeassistant.components.icloud +pyicloud==0.9.1 + # homeassistant.components.ipma pyipma==1.2.1 diff --git a/tests/components/icloud/__init__.py b/tests/components/icloud/__init__.py new file mode 100644 index 00000000000..b85f1017e45 --- /dev/null +++ b/tests/components/icloud/__init__.py @@ -0,0 +1 @@ +"""Tests for the iCloud component.""" diff --git a/tests/components/icloud/test_config_flow.py b/tests/components/icloud/test_config_flow.py new file mode 100644 index 00000000000..b292a9e258c --- /dev/null +++ b/tests/components/icloud/test_config_flow.py @@ -0,0 +1,309 @@ +"""Tests for the iCloud config flow.""" +from unittest.mock import patch, Mock, MagicMock +import pytest + +from pyicloud.exceptions import PyiCloudFailedLoginException + +from homeassistant import data_entry_flow +from homeassistant.components.icloud import config_flow + +from homeassistant.components.icloud.config_flow import ( + CONF_TRUSTED_DEVICE, + CONF_VERIFICATION_CODE, +) +from homeassistant.components.icloud.const import ( + DOMAIN, + CONF_ACCOUNT_NAME, + CONF_GPS_ACCURACY_THRESHOLD, + CONF_MAX_INTERVAL, + DEFAULT_GPS_ACCURACY_THRESHOLD, + DEFAULT_MAX_INTERVAL, +) +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.helpers.typing import HomeAssistantType + +from tests.common import MockConfigEntry + +USERNAME = "username@me.com" +PASSWORD = "password" +ACCOUNT_NAME = "Account name 1 2 3" +ACCOUNT_NAME_FROM_USERNAME = None +MAX_INTERVAL = 15 +GPS_ACCURACY_THRESHOLD = 250 + +TRUSTED_DEVICES = [ + {"deviceType": "SMS", "areaCode": "", "phoneNumber": "*******58", "deviceId": "1"} +] + + +@pytest.fixture(name="service") +def mock_controller_service(): + """Mock a successful service.""" + with patch( + "homeassistant.components.icloud.config_flow.PyiCloudService" + ) as service_mock: + service_mock.return_value.requires_2fa = True + yield service_mock + + +@pytest.fixture(name="service_with_cookie") +def mock_controller_service_with_cookie(): + """Mock a successful service while already authenticate.""" + with patch( + "homeassistant.components.icloud.config_flow.PyiCloudService" + ) as service_mock: + service_mock.return_value.requires_2fa = False + service_mock.return_value.trusted_devices = TRUSTED_DEVICES + service_mock.return_value.send_verification_code = Mock(return_value=True) + service_mock.return_value.validate_verification_code = Mock(return_value=True) + yield service_mock + + +@pytest.fixture(name="service_send_verification_code_failed") +def mock_controller_service_send_verification_code_failed(): + """Mock a failed service during sending verification code step.""" + with patch( + "homeassistant.components.icloud.config_flow.PyiCloudService" + ) as service_mock: + service_mock.return_value.requires_2fa = False + service_mock.return_value.trusted_devices = TRUSTED_DEVICES + service_mock.return_value.send_verification_code = Mock(return_value=False) + yield service_mock + + +@pytest.fixture(name="service_validate_verification_code_failed") +def mock_controller_service_validate_verification_code_failed(): + """Mock a failed service during validation of verification code step.""" + with patch( + "homeassistant.components.icloud.config_flow.PyiCloudService" + ) as service_mock: + service_mock.return_value.requires_2fa = False + service_mock.return_value.trusted_devices = TRUSTED_DEVICES + service_mock.return_value.send_verification_code = Mock(return_value=True) + service_mock.return_value.validate_verification_code = Mock(return_value=False) + yield service_mock + + +def init_config_flow(hass: HomeAssistantType): + """Init a configuration flow.""" + flow = config_flow.IcloudFlowHandler() + flow.hass = hass + return flow + + +async def test_user(hass: HomeAssistantType, service: MagicMock): + """Test user config.""" + flow = init_config_flow(hass) + + result = await flow.async_step_user() + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + + # test with all provided + result = await flow.async_step_user( + {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == CONF_TRUSTED_DEVICE + + +async def test_user_with_cookie( + hass: HomeAssistantType, service_with_cookie: MagicMock +): + """Test user config with presence of a cookie.""" + flow = init_config_flow(hass) + + # test with all provided + result = await flow.async_step_user( + {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == USERNAME + assert result["data"][CONF_USERNAME] == USERNAME + assert result["data"][CONF_PASSWORD] == PASSWORD + assert result["data"][CONF_ACCOUNT_NAME] == ACCOUNT_NAME_FROM_USERNAME + assert result["data"][CONF_MAX_INTERVAL] == DEFAULT_MAX_INTERVAL + assert result["data"][CONF_GPS_ACCURACY_THRESHOLD] == DEFAULT_GPS_ACCURACY_THRESHOLD + + +async def test_import(hass: HomeAssistantType, service: MagicMock): + """Test import step.""" + flow = init_config_flow(hass) + + # import with username and password + result = await flow.async_step_import( + {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "trusted_device" + + # import with all + result = await flow.async_step_import( + { + CONF_USERNAME: USERNAME, + CONF_PASSWORD: PASSWORD, + CONF_ACCOUNT_NAME: ACCOUNT_NAME, + CONF_MAX_INTERVAL: MAX_INTERVAL, + CONF_GPS_ACCURACY_THRESHOLD: GPS_ACCURACY_THRESHOLD, + } + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "trusted_device" + + +async def test_import_with_cookie( + hass: HomeAssistantType, service_with_cookie: MagicMock +): + """Test import step with presence of a cookie.""" + flow = init_config_flow(hass) + + # import with username and password + result = await flow.async_step_import( + {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == USERNAME + assert result["data"][CONF_USERNAME] == USERNAME + assert result["data"][CONF_PASSWORD] == PASSWORD + assert result["data"][CONF_ACCOUNT_NAME] == ACCOUNT_NAME_FROM_USERNAME + assert result["data"][CONF_MAX_INTERVAL] == DEFAULT_MAX_INTERVAL + assert result["data"][CONF_GPS_ACCURACY_THRESHOLD] == DEFAULT_GPS_ACCURACY_THRESHOLD + + # import with all + result = await flow.async_step_import( + { + CONF_USERNAME: USERNAME, + CONF_PASSWORD: PASSWORD, + CONF_ACCOUNT_NAME: ACCOUNT_NAME, + CONF_MAX_INTERVAL: MAX_INTERVAL, + CONF_GPS_ACCURACY_THRESHOLD: GPS_ACCURACY_THRESHOLD, + } + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == USERNAME + assert result["data"][CONF_USERNAME] == USERNAME + assert result["data"][CONF_PASSWORD] == PASSWORD + assert result["data"][CONF_ACCOUNT_NAME] == ACCOUNT_NAME + assert result["data"][CONF_MAX_INTERVAL] == MAX_INTERVAL + assert result["data"][CONF_GPS_ACCURACY_THRESHOLD] == GPS_ACCURACY_THRESHOLD + + +async def test_abort_if_already_setup(hass: HomeAssistantType): + """Test we abort if the account is already setup.""" + flow = init_config_flow(hass) + MockConfigEntry( + domain=DOMAIN, data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD} + ).add_to_hass(hass) + + # Should fail, same USERNAME (import) + result = await flow.async_step_import( + {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "username_exists" + + # Should fail, same ACCOUNT_NAME (import) + result = await flow.async_step_import( + { + CONF_USERNAME: "other_username@icloud.com", + CONF_PASSWORD: PASSWORD, + CONF_ACCOUNT_NAME: ACCOUNT_NAME_FROM_USERNAME, + } + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "username_exists" + + # Should fail, same USERNAME (flow) + result = await flow.async_step_user( + {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {CONF_USERNAME: "username_exists"} + + +async def test_login_failed(hass: HomeAssistantType): + """Test when we have errors during login.""" + flow = init_config_flow(hass) + + with patch( + "pyicloud.base.PyiCloudService.authenticate", + side_effect=PyiCloudFailedLoginException(), + ): + result = await flow.async_step_user( + {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {CONF_USERNAME: "login"} + + +async def test_trusted_device(hass: HomeAssistantType, service: MagicMock): + """Test trusted_device step.""" + flow = init_config_flow(hass) + await flow.async_step_user({CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}) + + result = await flow.async_step_trusted_device() + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == CONF_TRUSTED_DEVICE + + +async def test_trusted_device_success(hass: HomeAssistantType, service: MagicMock): + """Test trusted_device step success.""" + flow = init_config_flow(hass) + await flow.async_step_user({CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}) + + result = await flow.async_step_trusted_device({CONF_TRUSTED_DEVICE: 0}) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == CONF_VERIFICATION_CODE + + +async def test_send_verification_code_failed( + hass: HomeAssistantType, service_send_verification_code_failed: MagicMock +): + """Test when we have errors during send_verification_code.""" + flow = init_config_flow(hass) + await flow.async_step_user({CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}) + + result = await flow.async_step_trusted_device({CONF_TRUSTED_DEVICE: 0}) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == CONF_TRUSTED_DEVICE + assert result["errors"] == {CONF_TRUSTED_DEVICE: "send_verification_code"} + + +async def test_verification_code(hass: HomeAssistantType): + """Test verification_code step.""" + flow = init_config_flow(hass) + await flow.async_step_user({CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}) + + result = await flow.async_step_verification_code() + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == CONF_VERIFICATION_CODE + + +async def test_verification_code_success( + hass: HomeAssistantType, service_with_cookie: MagicMock +): + """Test verification_code step success.""" + flow = init_config_flow(hass) + await flow.async_step_user({CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}) + + result = await flow.async_step_verification_code({CONF_VERIFICATION_CODE: 0}) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == USERNAME + assert result["data"][CONF_USERNAME] == USERNAME + assert result["data"][CONF_PASSWORD] == PASSWORD + assert result["data"][CONF_ACCOUNT_NAME] is None + assert result["data"][CONF_MAX_INTERVAL] == DEFAULT_MAX_INTERVAL + assert result["data"][CONF_GPS_ACCURACY_THRESHOLD] == DEFAULT_GPS_ACCURACY_THRESHOLD + + +async def test_validate_verification_code_failed( + hass: HomeAssistantType, service_validate_verification_code_failed: MagicMock +): + """Test when we have errors during validate_verification_code.""" + flow = init_config_flow(hass) + await flow.async_step_user({CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}) + + result = await flow.async_step_verification_code({CONF_VERIFICATION_CODE: 0}) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == CONF_TRUSTED_DEVICE + assert result["errors"] == {"base": "validate_verification_code"} From 08f128e9c782d46010938dc99cea1043336c1cb8 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 17:42:00 +0100 Subject: [PATCH 233/677] Sort imports according to PEP8 for components starting with "D" (#29764) * use isort to sort imports for components starting with 'd' * fix isort mistake --- .../components/denon/media_player.py | 2 +- .../components/deutsche_bahn/sensor.py | 3 +- .../device_sun_light_trigger/__init__.py | 10 ++-- homeassistant/components/dht/sensor.py | 4 +- .../components/digital_ocean/__init__.py | 4 +- .../components/digitalloggers/switch.py | 6 +-- homeassistant/components/discord/notify.py | 5 +- .../components/discovery/__init__.py | 6 +-- .../dlib_face_detect/image_processing.py | 6 ++- .../dlib_face_identify/image_processing.py | 12 ++--- .../components/doods/image_processing.py | 4 +- homeassistant/components/dovado/__init__.py | 8 +-- .../components/dublin_bus_transport/sensor.py | 8 +-- homeassistant/components/duckdns/__init__.py | 6 +-- .../components/dwd_weather_warnings/sensor.py | 10 ++-- tests/components/datadog/test_init.py | 6 +-- tests/components/default_config/test_init.py | 4 +- .../device_sun_light_trigger/test_init.py | 9 ++-- tests/components/directv/test_media_player.py | 50 +++++++++---------- tests/components/discovery/test_init.py | 4 +- tests/components/duckdns/test_init.py | 5 +- 21 files changed, 87 insertions(+), 85 deletions(-) diff --git a/homeassistant/components/denon/media_player.py b/homeassistant/components/denon/media_player.py index 7bed8423e8f..cd9d6e8feb7 100644 --- a/homeassistant/components/denon/media_player.py +++ b/homeassistant/components/denon/media_player.py @@ -4,7 +4,7 @@ import telnetlib import voluptuous as vol -from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice from homeassistant.components.media_player.const import ( SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, diff --git a/homeassistant/components/deutsche_bahn/sensor.py b/homeassistant/components/deutsche_bahn/sensor.py index ad7b40f78db..204518b2ce3 100644 --- a/homeassistant/components/deutsche_bahn/sensor.py +++ b/homeassistant/components/deutsche_bahn/sensor.py @@ -2,9 +2,8 @@ from datetime import timedelta import logging -import voluptuous as vol - import schiene +import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/device_sun_light_trigger/__init__.py b/homeassistant/components/device_sun_light_trigger/__init__.py index 9a058cfacc1..64831cfac89 100644 --- a/homeassistant/components/device_sun_light_trigger/__init__.py +++ b/homeassistant/components/device_sun_light_trigger/__init__.py @@ -1,11 +1,9 @@ """Support to turn on lights based on the states.""" -import logging from datetime import timedelta +import logging import voluptuous as vol -from homeassistant.core import callback -import homeassistant.util.dt as dt_util from homeassistant.components.light import ( ATTR_PROFILE, ATTR_TRANSITION, @@ -20,12 +18,14 @@ from homeassistant.const import ( SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET, ) +from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import ( async_track_point_in_utc_time, async_track_state_change, ) -from homeassistant.helpers.sun import is_up, get_astral_event_next -import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.sun import get_astral_event_next, is_up +import homeassistant.util.dt as dt_util DOMAIN = "device_sun_light_trigger" CONF_DEVICE_GROUP = "device_group" diff --git a/homeassistant/components/dht/sensor.py b/homeassistant/components/dht/sensor.py index 648e0e1ed72..26b0493cb99 100644 --- a/homeassistant/components/dht/sensor.py +++ b/homeassistant/components/dht/sensor.py @@ -1,13 +1,13 @@ """Support for Adafruit DHT temperature and humidity sensor.""" -import logging from datetime import timedelta +import logging import Adafruit_DHT # pylint: disable=import-error import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import CONF_MONITORED_CONDITIONS, CONF_NAME, TEMP_FAHRENHEIT import homeassistant.helpers.config_validation as cv -from homeassistant.const import TEMP_FAHRENHEIT, CONF_NAME, CONF_MONITORED_CONDITIONS from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from homeassistant.util.temperature import celsius_to_fahrenheit diff --git a/homeassistant/components/digital_ocean/__init__.py b/homeassistant/components/digital_ocean/__init__.py index bdb0c348803..33663f121d1 100644 --- a/homeassistant/components/digital_ocean/__init__.py +++ b/homeassistant/components/digital_ocean/__init__.py @@ -1,13 +1,13 @@ """Support for Digital Ocean.""" -import logging from datetime import timedelta +import logging import digitalocean import voluptuous as vol from homeassistant.const import CONF_ACCESS_TOKEN -from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv +from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/digitalloggers/switch.py b/homeassistant/components/digitalloggers/switch.py index 10c8ce73a47..824af441688 100644 --- a/homeassistant/components/digitalloggers/switch.py +++ b/homeassistant/components/digitalloggers/switch.py @@ -1,17 +1,17 @@ """Support for Digital Loggers DIN III Relays.""" -import logging from datetime import timedelta +import logging import dlipower import voluptuous as vol -from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice from homeassistant.const import ( CONF_HOST, CONF_NAME, - CONF_USERNAME, CONF_PASSWORD, CONF_TIMEOUT, + CONF_USERNAME, ) import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle diff --git a/homeassistant/components/discord/notify.py b/homeassistant/components/discord/notify.py index f35cf5b0ce9..864b7da5e55 100644 --- a/homeassistant/components/discord/notify.py +++ b/homeassistant/components/discord/notify.py @@ -5,15 +5,14 @@ import os.path import discord import voluptuous as vol -from homeassistant.const import CONF_TOKEN -import homeassistant.helpers.config_validation as cv - from homeassistant.components.notify import ( ATTR_DATA, ATTR_TARGET, PLATFORM_SCHEMA, BaseNotificationService, ) +from homeassistant.const import CONF_TOKEN +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/discovery/__init__.py b/homeassistant/components/discovery/__init__.py index 884f6680d7c..965782d1228 100644 --- a/homeassistant/components/discovery/__init__.py +++ b/homeassistant/components/discovery/__init__.py @@ -6,19 +6,19 @@ Will emit EVENT_PLATFORM_DISCOVERED whenever a new service has been discovered. Knows which components handle certain types, will make sure they are loaded before the EVENT_PLATFORM_DISCOVERED is fired. """ -import json from datetime import timedelta +import json import logging from netdisco.discovery import NetworkDiscovery import voluptuous as vol from homeassistant import config_entries -from homeassistant.core import callback from homeassistant.const import EVENT_HOMEASSISTANT_START +from homeassistant.core import callback import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.discovery import async_discover, async_load_platform from homeassistant.helpers.event import async_track_point_in_utc_time -from homeassistant.helpers.discovery import async_load_platform, async_discover import homeassistant.util.dt as dt_util DOMAIN = "discovery" diff --git a/homeassistant/components/dlib_face_detect/image_processing.py b/homeassistant/components/dlib_face_detect/image_processing.py index 6e5c49e7aba..430878ca44f 100644 --- a/homeassistant/components/dlib_face_detect/image_processing.py +++ b/homeassistant/components/dlib_face_detect/image_processing.py @@ -10,10 +10,12 @@ from homeassistant.components.image_processing import ( CONF_SOURCE, ImageProcessingFaceEntity, ) +from homeassistant.core import split_entity_id # pylint: disable=unused-import -from homeassistant.components.image_processing import PLATFORM_SCHEMA # noqa: F401 -from homeassistant.core import split_entity_id +from homeassistant.components.image_processing import ( # noqa: F401, isort:skip + PLATFORM_SCHEMA, +) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/dlib_face_identify/image_processing.py b/homeassistant/components/dlib_face_identify/image_processing.py index d5b55b6a68c..d6fbf106b0c 100644 --- a/homeassistant/components/dlib_face_identify/image_processing.py +++ b/homeassistant/components/dlib_face_identify/image_processing.py @@ -1,20 +1,20 @@ """Component that will help set the Dlib face detect processing.""" -import logging import io +import logging # pylint: disable=import-error import face_recognition import voluptuous as vol -from homeassistant.core import split_entity_id from homeassistant.components.image_processing import ( - ImageProcessingFaceEntity, - PLATFORM_SCHEMA, - CONF_SOURCE, + CONF_CONFIDENCE, CONF_ENTITY_ID, CONF_NAME, - CONF_CONFIDENCE, + CONF_SOURCE, + PLATFORM_SCHEMA, + ImageProcessingFaceEntity, ) +from homeassistant.core import split_entity_id import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/doods/image_processing.py b/homeassistant/components/doods/image_processing.py index 7f2c3fd1d42..5e4d211d497 100644 --- a/homeassistant/components/doods/image_processing.py +++ b/homeassistant/components/doods/image_processing.py @@ -3,11 +3,10 @@ import io import logging import time -import voluptuous as vol from PIL import Image, ImageDraw from pydoods import PyDOODS +import voluptuous as vol -from homeassistant.const import CONF_TIMEOUT from homeassistant.components.image_processing import ( CONF_CONFIDENCE, CONF_ENTITY_ID, @@ -17,6 +16,7 @@ from homeassistant.components.image_processing import ( ImageProcessingEntity, draw_box, ) +from homeassistant.const import CONF_TIMEOUT from homeassistant.core import split_entity_id from homeassistant.helpers import template import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/dovado/__init__.py b/homeassistant/components/dovado/__init__.py index 03f12314c5a..b8d18d90833 100644 --- a/homeassistant/components/dovado/__init__.py +++ b/homeassistant/components/dovado/__init__.py @@ -1,18 +1,18 @@ """Support for Dovado router.""" -import logging from datetime import timedelta +import logging import dovado import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.const import ( - CONF_USERNAME, - CONF_PASSWORD, CONF_HOST, + CONF_PASSWORD, CONF_PORT, + CONF_USERNAME, DEVICE_DEFAULT_NAME, ) +import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/dublin_bus_transport/sensor.py b/homeassistant/components/dublin_bus_transport/sensor.py index 203cfb1e27c..a5fe8fd6b30 100644 --- a/homeassistant/components/dublin_bus_transport/sensor.py +++ b/homeassistant/components/dublin_bus_transport/sensor.py @@ -7,17 +7,17 @@ https://data.gov.ie/dataset/real-time-passenger-information-rtpi-for-dublin-bus- For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.dublin_public_transport/ """ +from datetime import datetime, timedelta import logging -from datetime import timedelta, datetime import requests import voluptuous as vol -import homeassistant.helpers.config_validation as cv -import homeassistant.util.dt as dt_util from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONF_NAME, ATTR_ATTRIBUTION +from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity +import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) _RESOURCE = "https://data.dublinked.ie/cgi-bin/rtpi/realtimebusinformation" diff --git a/homeassistant/components/duckdns/__init__.py b/homeassistant/components/duckdns/__init__.py index 171d17faff9..b3da1ec2752 100644 --- a/homeassistant/components/duckdns/__init__.py +++ b/homeassistant/components/duckdns/__init__.py @@ -1,14 +1,14 @@ """Integrate with DuckDNS.""" -import logging from asyncio import iscoroutinefunction from datetime import timedelta +import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.const import CONF_ACCESS_TOKEN, CONF_DOMAIN -from homeassistant.core import callback, CALLBACK_TYPE +from homeassistant.core import CALLBACK_TYPE, callback from homeassistant.helpers.aiohttp_client import async_get_clientsession +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.loader import bind_hass from homeassistant.util import dt as dt_util diff --git a/homeassistant/components/dwd_weather_warnings/sensor.py b/homeassistant/components/dwd_weather_warnings/sensor.py index 4d7ad04e382..01a06209d99 100644 --- a/homeassistant/components/dwd_weather_warnings/sensor.py +++ b/homeassistant/components/dwd_weather_warnings/sensor.py @@ -12,19 +12,19 @@ Unwetterwarnungen (Stufe 3) Warnungen vor markantem Wetter (Stufe 2) Wetterwarnungen (Stufe 1) """ -import logging -import json from datetime import timedelta +import json +import logging import voluptuous as vol +from homeassistant.components.rest.sensor import RestData +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import ATTR_ATTRIBUTION, CONF_MONITORED_CONDITIONS, CONF_NAME import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME, CONF_MONITORED_CONDITIONS from homeassistant.util import Throttle import homeassistant.util.dt as dt_util -from homeassistant.components.rest.sensor import RestData _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/datadog/test_init.py b/tests/components/datadog/test_init.py index 30541c8137a..fdaec26204d 100644 --- a/tests/components/datadog/test_init.py +++ b/tests/components/datadog/test_init.py @@ -1,16 +1,16 @@ """The tests for the Datadog component.""" -from unittest import mock import unittest +from unittest import mock +import homeassistant.components.datadog as datadog from homeassistant.const import ( EVENT_LOGBOOK_ENTRY, EVENT_STATE_CHANGED, STATE_OFF, STATE_ON, ) -from homeassistant.setup import setup_component -import homeassistant.components.datadog as datadog import homeassistant.core as ha +from homeassistant.setup import setup_component from tests.common import assert_setup_component, get_test_home_assistant diff --git a/tests/components/default_config/test_init.py b/tests/components/default_config/test_init.py index 5fce4b98019..6b9004595bb 100644 --- a/tests/components/default_config/test_init.py +++ b/tests/components/default_config/test_init.py @@ -1,10 +1,10 @@ """Test the default_config init.""" from unittest.mock import patch -from homeassistant.setup import async_setup_component - import pytest +from homeassistant.setup import async_setup_component + from tests.common import MockDependency, mock_coro diff --git a/tests/components/device_sun_light_trigger/test_init.py b/tests/components/device_sun_light_trigger/test_init.py index 70681a6d150..98b027c6175 100644 --- a/tests/components/device_sun_light_trigger/test_init.py +++ b/tests/components/device_sun_light_trigger/test_init.py @@ -1,20 +1,21 @@ """The tests device sun light trigger component.""" # pylint: disable=protected-access from datetime import datetime + from asynctest import patch import pytest -from homeassistant.setup import async_setup_component -from homeassistant.const import CONF_PLATFORM, STATE_HOME, STATE_NOT_HOME from homeassistant.components import ( - device_tracker, - light, device_sun_light_trigger, + device_tracker, group, + light, ) from homeassistant.components.device_tracker.const import ( ENTITY_ID_FORMAT as DT_ENTITY_ID_FORMAT, ) +from homeassistant.const import CONF_PLATFORM, STATE_HOME, STATE_NOT_HOME +from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util from tests.common import async_fire_time_changed diff --git a/tests/components/directv/test_media_player.py b/tests/components/directv/test_media_player.py index 7f802e1a94b..449147c3648 100644 --- a/tests/components/directv/test_media_player.py +++ b/tests/components/directv/test_media_player.py @@ -1,33 +1,10 @@ """The tests for the DirecTV Media player platform.""" +from datetime import datetime, timedelta from unittest.mock import call, patch -from datetime import datetime, timedelta -import requests import pytest +import requests -from homeassistant.components.media_player.const import ( - ATTR_MEDIA_CONTENT_ID, - ATTR_MEDIA_CONTENT_TYPE, - MEDIA_TYPE_TVSHOW, - ATTR_MEDIA_ENQUEUE, - ATTR_MEDIA_DURATION, - ATTR_MEDIA_TITLE, - ATTR_MEDIA_POSITION, - ATTR_MEDIA_SERIES_TITLE, - ATTR_MEDIA_CHANNEL, - ATTR_INPUT_SOURCE, - ATTR_MEDIA_POSITION_UPDATED_AT, - DOMAIN, - SERVICE_PLAY_MEDIA, - SUPPORT_PAUSE, - SUPPORT_TURN_ON, - SUPPORT_TURN_OFF, - SUPPORT_PLAY_MEDIA, - SUPPORT_STOP, - SUPPORT_NEXT_TRACK, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_PLAY, -) from homeassistant.components.directv.media_player import ( ATTR_MEDIA_CURRENTLY_RECORDING, ATTR_MEDIA_RATING, @@ -36,6 +13,29 @@ from homeassistant.components.directv.media_player import ( DEFAULT_DEVICE, DEFAULT_PORT, ) +from homeassistant.components.media_player.const import ( + ATTR_INPUT_SOURCE, + ATTR_MEDIA_CHANNEL, + ATTR_MEDIA_CONTENT_ID, + ATTR_MEDIA_CONTENT_TYPE, + ATTR_MEDIA_DURATION, + ATTR_MEDIA_ENQUEUE, + ATTR_MEDIA_POSITION, + ATTR_MEDIA_POSITION_UPDATED_AT, + ATTR_MEDIA_SERIES_TITLE, + ATTR_MEDIA_TITLE, + DOMAIN, + MEDIA_TYPE_TVSHOW, + SERVICE_PLAY_MEDIA, + SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, + SUPPORT_PLAY, + SUPPORT_PLAY_MEDIA, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_STOP, + SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, +) from homeassistant.const import ( ATTR_ENTITY_ID, CONF_DEVICE, diff --git a/tests/components/discovery/test_init.py b/tests/components/discovery/test_init.py index 8255751517c..de63a0bf495 100644 --- a/tests/components/discovery/test_init.py +++ b/tests/components/discovery/test_init.py @@ -1,6 +1,6 @@ """The tests for the discovery component.""" import asyncio -from unittest.mock import patch, MagicMock +from unittest.mock import MagicMock, patch import pytest @@ -9,7 +9,7 @@ from homeassistant.bootstrap import async_setup_component from homeassistant.components import discovery from homeassistant.util.dt import utcnow -from tests.common import mock_coro, async_fire_time_changed +from tests.common import async_fire_time_changed, mock_coro # One might consider to "mock" services, but it's easy enough to just use # what is already available. diff --git a/tests/components/duckdns/test_init.py b/tests/components/duckdns/test_init.py index 0213d9aefa6..9bc9b3504e7 100644 --- a/tests/components/duckdns/test_init.py +++ b/tests/components/duckdns/test_init.py @@ -1,13 +1,14 @@ """Test the DuckDNS component.""" from datetime import timedelta import logging + import pytest +from homeassistant.components import duckdns +from homeassistant.components.duckdns import async_track_time_interval_backoff from homeassistant.loader import bind_hass from homeassistant.setup import async_setup_component -from homeassistant.components import duckdns from homeassistant.util.dt import utcnow -from homeassistant.components.duckdns import async_track_time_interval_backoff from tests.common import async_fire_time_changed From 6b6570e7ca3242a53b3a6117f1d4940f778ac5ed Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Mon, 9 Dec 2019 18:46:56 +0100 Subject: [PATCH 234/677] Move imports to top for ness_alarm (#29518) * Move imports to top for ness_alarm * Added patch for the ArminState in alarm_control_panel.py --- homeassistant/components/ness_alarm/__init__.py | 2 +- .../components/ness_alarm/alarm_control_panel.py | 3 ++- tests/components/ness_alarm/test_init.py | 13 ++++++++----- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/ness_alarm/__init__.py b/homeassistant/components/ness_alarm/__init__.py index cc6fad1346d..7131ac505b5 100644 --- a/homeassistant/components/ness_alarm/__init__.py +++ b/homeassistant/components/ness_alarm/__init__.py @@ -3,6 +3,7 @@ from collections import namedtuple import datetime import logging +from nessclient import ArmingState, Client import voluptuous as vol from homeassistant.components.binary_sensor import DEVICE_CLASSES @@ -82,7 +83,6 @@ SERVICE_SCHEMA_AUX = vol.Schema( async def async_setup(hass, config): """Set up the Ness Alarm platform.""" - from nessclient import Client, ArmingState conf = config[DOMAIN] diff --git a/homeassistant/components/ness_alarm/alarm_control_panel.py b/homeassistant/components/ness_alarm/alarm_control_panel.py index d2feebfb64f..f77244a584e 100644 --- a/homeassistant/components/ness_alarm/alarm_control_panel.py +++ b/homeassistant/components/ness_alarm/alarm_control_panel.py @@ -2,6 +2,8 @@ import logging +from nessclient import ArmingState + import homeassistant.components.alarm_control_panel as alarm from homeassistant.components.alarm_control_panel.const import ( SUPPORT_ALARM_ARM_AWAY, @@ -91,7 +93,6 @@ class NessAlarmPanel(alarm.AlarmControlPanel): @callback def _handle_arming_state_change(self, arming_state): """Handle arming state update.""" - from nessclient import ArmingState if arming_state == ArmingState.UNKNOWN: self._state = None diff --git a/tests/components/ness_alarm/test_init.py b/tests/components/ness_alarm/test_init.py index 31b173f9be6..9da361852e9 100644 --- a/tests/components/ness_alarm/test_init.py +++ b/tests/components/ness_alarm/test_init.py @@ -32,8 +32,6 @@ from homeassistant.const import ( ) from homeassistant.setup import async_setup_component -from tests.common import MockDependency - VALID_CONFIG = { DOMAIN: { CONF_HOST: "alarm.local", @@ -262,7 +260,12 @@ def mock_nessclient(): _mock_factory = MagicMock() _mock_factory.return_value = _mock_instance - with MockDependency("nessclient"), patch( - "nessclient.Client", new=_mock_factory, create=True - ), patch("nessclient.ArmingState", new=MockArmingState): + with patch( + "homeassistant.components.ness_alarm.Client", new=_mock_factory, create=True + ), patch( + "homeassistant.components.ness_alarm.ArmingState", new=MockArmingState + ), patch( + "homeassistant.components.ness_alarm.alarm_control_panel.ArmingState", + new=MockArmingState, + ): yield _mock_instance From 6cc75fc6f30968fc8be5517d5fa61a51ba923b3c Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 18:54:54 +0100 Subject: [PATCH 235/677] Sort imports according to PEP8 for components starting with "Z" (#29784) * use isort to sort imports for components starting with 'z' * add skip to end of zha/core/channels/__init__.py * put 'pylint: disable=import-error' at the right place * remove the import of config_flow in zha/__init__.py According to @balloob it is no longer needed. * revert previous commit * isort:skip homeassistant/components/zha/__init__.py completely --- homeassistant/components/updater/__init__.py | 4 +--- homeassistant/components/zamg/sensor.py | 6 ++--- homeassistant/components/zengge/light.py | 6 ++--- homeassistant/components/zeroconf/__init__.py | 9 ++++--- homeassistant/components/zestimate/sensor.py | 4 ++-- homeassistant/components/zha/__init__.py | 9 ++++--- .../components/zha/core/channels/__init__.py | 24 ++++++++++--------- homeassistant/components/zigbee/__init__.py | 16 ++++++------- homeassistant/components/zigbee/switch.py | 1 - tests/components/zeroconf/test_init.py | 2 +- 10 files changed, 41 insertions(+), 40 deletions(-) diff --git a/homeassistant/components/updater/__init__.py b/homeassistant/components/updater/__init__.py index 08f08e1bb64..0ddbfd63c73 100644 --- a/homeassistant/components/updater/__init__.py +++ b/homeassistant/components/updater/__init__.py @@ -1,8 +1,6 @@ """Support to check for available updates.""" import asyncio from datetime import timedelta - -# pylint: disable=import-error from distutils.version import StrictVersion import json import logging @@ -10,7 +8,7 @@ import uuid import aiohttp import async_timeout -from distro import linux_distribution +from distro import linux_distribution # pylint: disable=import-error import voluptuous as vol from homeassistant.const import __version__ as current_version diff --git a/homeassistant/components/zamg/sensor.py b/homeassistant/components/zamg/sensor.py index 9eea1f6612c..c984b13ef4b 100644 --- a/homeassistant/components/zamg/sensor.py +++ b/homeassistant/components/zamg/sensor.py @@ -12,18 +12,18 @@ import requests import voluptuous as vol from homeassistant.components.weather import ( + ATTR_WEATHER_ATTRIBUTION, ATTR_WEATHER_HUMIDITY, ATTR_WEATHER_PRESSURE, - ATTR_WEATHER_WIND_SPEED, - ATTR_WEATHER_ATTRIBUTION, ATTR_WEATHER_TEMPERATURE, ATTR_WEATHER_WIND_BEARING, + ATTR_WEATHER_WIND_SPEED, ) from homeassistant.const import ( - CONF_NAME, CONF_LATITUDE, CONF_LONGITUDE, CONF_MONITORED_CONDITIONS, + CONF_NAME, __version__, ) import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/zengge/light.py b/homeassistant/components/zengge/light.py index d890b193d72..42746c6bad3 100644 --- a/homeassistant/components/zengge/light.py +++ b/homeassistant/components/zengge/light.py @@ -1,20 +1,20 @@ """Support for Zengge lights.""" import logging -from zengge import zengge import voluptuous as vol +from zengge import zengge -from homeassistant.const import CONF_DEVICES, CONF_NAME from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_HS_COLOR, ATTR_WHITE_VALUE, + PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_WHITE_VALUE, Light, - PLATFORM_SCHEMA, ) +from homeassistant.const import CONF_DEVICES, CONF_NAME import homeassistant.helpers.config_validation as cv import homeassistant.util.color as color_util diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index 9f27a5fafc9..18ea1fdfca7 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -1,26 +1,25 @@ """Support for exposing Home Assistant via Zeroconf.""" +import ipaddress import logging import socket -import ipaddress import voluptuous as vol - from zeroconf import ( + NonUniqueNameException, ServiceBrowser, ServiceInfo, ServiceStateChange, Zeroconf, - NonUniqueNameException, ) from homeassistant import util from homeassistant.const import ( - EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP, __version__, ) -from homeassistant.generated.zeroconf import ZEROCONF, HOMEKIT +from homeassistant.generated.zeroconf import HOMEKIT, ZEROCONF _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/zestimate/sensor.py b/homeassistant/components/zestimate/sensor.py index 4b8bdf5fa2e..cdf7e6304ad 100644 --- a/homeassistant/components/zestimate/sensor.py +++ b/homeassistant/components/zestimate/sensor.py @@ -3,11 +3,11 @@ from datetime import timedelta import logging import requests -import xmltodict import voluptuous as vol +import xmltodict from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONF_API_KEY, CONF_NAME, ATTR_ATTRIBUTION +from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY, CONF_NAME import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity diff --git a/homeassistant/components/zha/__init__.py b/homeassistant/components/zha/__init__.py index 07eb3c53a95..a3c68ae3030 100644 --- a/homeassistant/components/zha/__init__.py +++ b/homeassistant/components/zha/__init__.py @@ -1,4 +1,8 @@ -"""Support for Zigbee Home Automation devices.""" +"""Support for Zigbee Home Automation devices. + +isort:skip_file +""" + import logging import voluptuous as vol @@ -7,7 +11,6 @@ from homeassistant import config_entries, const as ha_const import homeassistant.helpers.config_validation as cv from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE -# Loading the config flow file will register the flow from . import config_flow # noqa: F401 pylint: disable=unused-import from . import api from .core import ZHAGateway @@ -100,7 +103,7 @@ async def async_setup_entry(hass, config_entry): if config.get(CONF_ENABLE_QUIRKS, True): # needs to be done here so that the ZHA module is finished loading # before zhaquirks is imported - import zhaquirks # noqa: F401 pylint: disable=unused-import, import-outside-toplevel + import zhaquirks # noqa: F401 pylint: disable=unused-import, import-outside-toplevel, import-error zha_gateway = ZHAGateway(hass, config, config_entry) await zha_gateway.async_initialize() diff --git a/homeassistant/components/zha/core/channels/__init__.py b/homeassistant/components/zha/core/channels/__init__.py index 2d24c4ce045..715156c43ed 100644 --- a/homeassistant/components/zha/core/channels/__init__.py +++ b/homeassistant/components/zha/core/channels/__init__.py @@ -395,14 +395,16 @@ class EventRelayChannel(ZigbeeChannel): # pylint: disable=wrong-import-position, import-outside-toplevel -from . import closures # noqa: F401 -from . import general # noqa: F401 -from . import homeautomation # noqa: F401 -from . import hvac # noqa: F401 -from . import lighting # noqa: F401 -from . import lightlink # noqa: F401 -from . import manufacturerspecific # noqa: F401 -from . import measurement # noqa: F401 -from . import protocol # noqa: F401 -from . import security # noqa: F401 -from . import smartenergy # noqa: F401 +from . import ( # noqa: F401 isort:skip + closures, + general, + homeautomation, + hvac, + lighting, + lightlink, + manufacturerspecific, + measurement, + protocol, + security, + smartenergy, +) diff --git a/homeassistant/components/zigbee/__init__.py b/homeassistant/components/zigbee/__init__.py index e74726a70f9..d63a713045b 100644 --- a/homeassistant/components/zigbee/__init__.py +++ b/homeassistant/components/zigbee/__init__.py @@ -1,24 +1,24 @@ """Support for Zigbee devices.""" -import logging from binascii import hexlify, unhexlify +import logging -import xbee_helper.const as xb_const -from xbee_helper import ZigBee -from xbee_helper.device import convert_adc -from xbee_helper.exceptions import ZigBeeException, ZigBeeTxFailure from serial import Serial, SerialException import voluptuous as vol +from xbee_helper import ZigBee +import xbee_helper.const as xb_const +from xbee_helper.device import convert_adc +from xbee_helper.exceptions import ZigBeeException, ZigBeeTxFailure from homeassistant.const import ( - EVENT_HOMEASSISTANT_STOP, + CONF_ADDRESS, CONF_DEVICE, CONF_NAME, CONF_PIN, - CONF_ADDRESS, + EVENT_HOMEASSISTANT_STOP, ) -from homeassistant.helpers.entity import Entity from homeassistant.helpers import config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send +from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/zigbee/switch.py b/homeassistant/components/zigbee/switch.py index 16af6ec7e3a..4e8d21f438a 100644 --- a/homeassistant/components/zigbee/switch.py +++ b/homeassistant/components/zigbee/switch.py @@ -5,7 +5,6 @@ from homeassistant.components.switch import SwitchDevice from . import PLATFORM_SCHEMA, ZigBeeDigitalOut, ZigBeeDigitalOutConfig - CONF_ON_STATE = "on_state" DEFAULT_ON_STATE = "high" diff --git a/tests/components/zeroconf/test_init.py b/tests/components/zeroconf/test_init.py index f60cf1d7bbb..00651bcfc5d 100644 --- a/tests/components/zeroconf/test_init.py +++ b/tests/components/zeroconf/test_init.py @@ -4,9 +4,9 @@ from unittest.mock import patch import pytest from zeroconf import ServiceInfo, ServiceStateChange +from homeassistant.components import zeroconf from homeassistant.generated import zeroconf as zc_gen from homeassistant.setup import async_setup_component -from homeassistant.components import zeroconf @pytest.fixture From 080c702d4f3bfbb6d8cbafa7cd1d363b05c21e66 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 18:56:21 +0100 Subject: [PATCH 236/677] Sort imports according to PEP8 for components starting with "C" (#29763) --- homeassistant/components/calendar/__init__.py | 1 - homeassistant/components/clickatell/notify.py | 3 +-- homeassistant/components/clicksend/notify.py | 3 +-- homeassistant/components/clicksend_tts/notify.py | 3 +-- homeassistant/components/configurator/__init__.py | 8 ++++---- homeassistant/components/conversation/__init__.py | 2 +- homeassistant/components/coolmaster/config_flow.py | 2 +- homeassistant/components/cups/sensor.py | 4 ++-- homeassistant/components/currencylayer/sensor.py | 10 +++++----- tests/components/canary/test_init.py | 5 +++-- tests/components/canary/test_sensor.py | 10 +++++----- tests/components/coinmarketcap/test_sensor.py | 4 ++-- tests/components/configurator/test_init.py | 2 +- tests/components/conversation/test_init.py | 4 ++-- tests/components/coolmaster/test_config_flow.py | 2 +- 15 files changed, 30 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/calendar/__init__.py b/homeassistant/components/calendar/__init__.py index ca240925cf5..35b25b86d43 100644 --- a/homeassistant/components/calendar/__init__.py +++ b/homeassistant/components/calendar/__init__.py @@ -17,7 +17,6 @@ from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.template import DATE_STR_FORMAT from homeassistant.util import dt - # mypy: allow-untyped-defs, no-check-untyped-defs _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/clickatell/notify.py b/homeassistant/components/clickatell/notify.py index 26f2f30aeff..d59a553a4f6 100644 --- a/homeassistant/components/clickatell/notify.py +++ b/homeassistant/components/clickatell/notify.py @@ -4,11 +4,10 @@ import logging import requests import voluptuous as vol +from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationService from homeassistant.const import CONF_API_KEY, CONF_RECIPIENT import homeassistant.helpers.config_validation as cv -from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationService - _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = "clickatell" diff --git a/homeassistant/components/clicksend/notify.py b/homeassistant/components/clicksend/notify.py index 87fc217ac42..42136e9a09c 100644 --- a/homeassistant/components/clicksend/notify.py +++ b/homeassistant/components/clicksend/notify.py @@ -6,6 +6,7 @@ from aiohttp.hdrs import CONTENT_TYPE import requests import voluptuous as vol +from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationService from homeassistant.const import ( CONF_API_KEY, CONF_RECIPIENT, @@ -15,8 +16,6 @@ from homeassistant.const import ( ) import homeassistant.helpers.config_validation as cv -from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationService - _LOGGER = logging.getLogger(__name__) BASE_API_URL = "https://rest.clicksend.com/v3" diff --git a/homeassistant/components/clicksend_tts/notify.py b/homeassistant/components/clicksend_tts/notify.py index ba30c61e937..400e72a7d0c 100644 --- a/homeassistant/components/clicksend_tts/notify.py +++ b/homeassistant/components/clicksend_tts/notify.py @@ -6,6 +6,7 @@ from aiohttp.hdrs import CONTENT_TYPE import requests import voluptuous as vol +from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationService from homeassistant.const import ( CONF_API_KEY, CONF_RECIPIENT, @@ -14,8 +15,6 @@ from homeassistant.const import ( ) import homeassistant.helpers.config_validation as cv -from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationService - _LOGGER = logging.getLogger(__name__) BASE_API_URL = "https://rest.clicksend.com/v3" diff --git a/homeassistant/components/configurator/__init__.py b/homeassistant/components/configurator/__init__.py index f3b2a41e917..78333d96355 100644 --- a/homeassistant/components/configurator/__init__.py +++ b/homeassistant/components/configurator/__init__.py @@ -9,14 +9,14 @@ the user has submitted configuration information. import functools as ft import logging -from homeassistant.core import callback as async_callback from homeassistant.const import ( - EVENT_TIME_CHANGED, - ATTR_FRIENDLY_NAME, ATTR_ENTITY_PICTURE, + ATTR_FRIENDLY_NAME, + EVENT_TIME_CHANGED, ) -from homeassistant.loader import bind_hass +from homeassistant.core import callback as async_callback from homeassistant.helpers.entity import async_generate_entity_id +from homeassistant.loader import bind_hass from homeassistant.util.async_ import run_callback_threadsafe _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/conversation/__init__.py b/homeassistant/components/conversation/__init__.py index ec5868e86fe..158a365981b 100644 --- a/homeassistant/components/conversation/__init__.py +++ b/homeassistant/components/conversation/__init__.py @@ -11,7 +11,7 @@ from homeassistant.helpers import config_validation as cv, intent from homeassistant.loader import bind_hass from .agent import AbstractConversationAgent -from .default_agent import async_register, DefaultAgent +from .default_agent import DefaultAgent, async_register _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/coolmaster/config_flow.py b/homeassistant/components/coolmaster/config_flow.py index fe52ea17b28..e9cef562647 100644 --- a/homeassistant/components/coolmaster/config_flow.py +++ b/homeassistant/components/coolmaster/config_flow.py @@ -3,7 +3,7 @@ from pycoolmasternet import CoolMasterNet import voluptuous as vol -from homeassistant import core, config_entries +from homeassistant import config_entries, core from homeassistant.const import CONF_HOST, CONF_PORT # pylint: disable=unused-import diff --git a/homeassistant/components/cups/sensor.py b/homeassistant/components/cups/sensor.py index 17a246561a5..7581891af6a 100644 --- a/homeassistant/components/cups/sensor.py +++ b/homeassistant/components/cups/sensor.py @@ -1,14 +1,14 @@ """Details about printers which are connected to CUPS.""" +from datetime import timedelta import importlib import logging -from datetime import timedelta import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.exceptions import PlatformNotReady +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/currencylayer/sensor.py b/homeassistant/components/currencylayer/sensor.py index d4660d70286..cbad07c0284 100644 --- a/homeassistant/components/currencylayer/sensor.py +++ b/homeassistant/components/currencylayer/sensor.py @@ -5,15 +5,15 @@ import logging import requests import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_API_KEY, - CONF_NAME, - CONF_BASE, - CONF_QUOTE, ATTR_ATTRIBUTION, + CONF_API_KEY, + CONF_BASE, + CONF_NAME, + CONF_QUOTE, ) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/canary/test_init.py b/tests/components/canary/test_init.py index cb671e30618..819d1ce0e90 100644 --- a/tests/components/canary/test_init.py +++ b/tests/components/canary/test_init.py @@ -1,9 +1,10 @@ """The tests for the Canary component.""" import unittest -from unittest.mock import patch, MagicMock, PropertyMock +from unittest.mock import MagicMock, PropertyMock, patch -import homeassistant.components.canary as canary from homeassistant import setup +import homeassistant.components.canary as canary + from tests.common import get_test_home_assistant diff --git a/tests/components/canary/test_sensor.py b/tests/components/canary/test_sensor.py index cfb8d46141a..7d5e829a347 100644 --- a/tests/components/canary/test_sensor.py +++ b/tests/components/canary/test_sensor.py @@ -3,16 +3,16 @@ import copy import unittest from unittest.mock import Mock -from homeassistant.components.canary import DATA_CANARY -from homeassistant.components.canary import sensor as canary +from homeassistant.components.canary import DATA_CANARY, sensor as canary from homeassistant.components.canary.sensor import ( - CanarySensor, - SENSOR_TYPES, ATTR_AIR_QUALITY, - STATE_AIR_QUALITY_NORMAL, + SENSOR_TYPES, STATE_AIR_QUALITY_ABNORMAL, + STATE_AIR_QUALITY_NORMAL, STATE_AIR_QUALITY_VERY_ABNORMAL, + CanarySensor, ) + from tests.common import get_test_home_assistant from tests.components.canary.test_init import mock_device, mock_location diff --git a/tests/components/coinmarketcap/test_sensor.py b/tests/components/coinmarketcap/test_sensor.py index d629bf14184..9d1e89fbc24 100644 --- a/tests/components/coinmarketcap/test_sensor.py +++ b/tests/components/coinmarketcap/test_sensor.py @@ -1,12 +1,12 @@ """Tests for the CoinMarketCap sensor platform.""" import json - import unittest from unittest.mock import patch import homeassistant.components.sensor as sensor from homeassistant.setup import setup_component -from tests.common import get_test_home_assistant, load_fixture, assert_setup_component + +from tests.common import assert_setup_component, get_test_home_assistant, load_fixture VALID_CONFIG = { "platform": "coinmarketcap", diff --git a/tests/components/configurator/test_init.py b/tests/components/configurator/test_init.py index 43395c0e7e8..b572609c5a2 100644 --- a/tests/components/configurator/test_init.py +++ b/tests/components/configurator/test_init.py @@ -3,7 +3,7 @@ import unittest import homeassistant.components.configurator as configurator -from homeassistant.const import EVENT_TIME_CHANGED, ATTR_FRIENDLY_NAME +from homeassistant.const import ATTR_FRIENDLY_NAME, EVENT_TIME_CHANGED from tests.common import get_test_home_assistant diff --git a/tests/components/conversation/test_init.py b/tests/components/conversation/test_init.py index 45008ef9447..f84d2109095 100644 --- a/tests/components/conversation/test_init.py +++ b/tests/components/conversation/test_init.py @@ -1,10 +1,10 @@ """The tests for the Conversation component.""" import pytest -from homeassistant.core import DOMAIN as HASS_DOMAIN, Context -from homeassistant.setup import async_setup_component from homeassistant.components import conversation +from homeassistant.core import DOMAIN as HASS_DOMAIN, Context from homeassistant.helpers import intent +from homeassistant.setup import async_setup_component from tests.common import async_mock_intent, async_mock_service diff --git a/tests/components/coolmaster/test_config_flow.py b/tests/components/coolmaster/test_config_flow.py index d0126ff2cb6..c71f308dece 100644 --- a/tests/components/coolmaster/test_config_flow.py +++ b/tests/components/coolmaster/test_config_flow.py @@ -2,7 +2,7 @@ from unittest.mock import patch from homeassistant import config_entries, setup -from homeassistant.components.coolmaster.const import DOMAIN, AVAILABLE_MODES +from homeassistant.components.coolmaster.const import AVAILABLE_MODES, DOMAIN from tests.common import mock_coro From 897522a82d69df200b2684e97855e29ff143bbcb Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 19:04:38 +0100 Subject: [PATCH 237/677] Fix 'pytest.register_assert_rewrite("tests.common")' warning (#29797) see https://github.com/home-assistant/home-assistant/pull/29791/files/fea243c035f117275fda1895720d30e7f977aa9c#r355533273 --- tests/conftest.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 2b6a1a16739..41b4757c92e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,7 +13,9 @@ from homeassistant.auth.const import GROUP_ID_ADMIN, GROUP_ID_READ_ONLY from homeassistant.auth.providers import homeassistant, legacy_api_password from homeassistant.util import location -from tests.common import ( # noqa: E402 module level import not at top of file +pytest.register_assert_rewrite("tests.common") + +from tests.common import ( # noqa: E402, isort:skip CLIENT_ID, INSTANCES, MockUser, @@ -21,11 +23,7 @@ from tests.common import ( # noqa: E402 module level import not at top of file mock_coro, mock_storage as mock_storage, ) -from tests.test_util.aiohttp import ( # noqa: E402 module level import not at top of file - mock_aiohttp_client, -) - -pytest.register_assert_rewrite("tests.common") +from tests.test_util.aiohttp import mock_aiohttp_client # noqa: E402, isort:skip if os.environ.get("UVLOOP") == "1": import uvloop From 80c344d3a8aa137972e8616575c2fdcdccdd6a9e Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 19:06:25 +0100 Subject: [PATCH 238/677] Sort imports according to PEP8 for huawei_lte (#29664) --- homeassistant/components/huawei_lte/__init__.py | 15 +++++++++------ .../components/huawei_lte/binary_sensor.py | 2 +- .../components/huawei_lte/config_flow.py | 9 ++++----- .../components/huawei_lte/device_tracker.py | 2 +- homeassistant/components/huawei_lte/notify.py | 3 +-- homeassistant/components/huawei_lte/sensor.py | 3 +-- homeassistant/components/huawei_lte/switch.py | 2 +- tests/components/huawei_lte/test_config_flow.py | 9 ++++----- 8 files changed, 22 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/huawei_lte/__init__.py b/homeassistant/components/huawei_lte/__init__.py index 30b8eb1bc6d..4a5614543e9 100644 --- a/homeassistant/components/huawei_lte/__init__.py +++ b/homeassistant/components/huawei_lte/__init__.py @@ -3,12 +3,11 @@ from collections import defaultdict from datetime import timedelta from functools import partial -from urllib.parse import urlparse import ipaddress import logging from typing import Any, Callable, Dict, List, Set, Tuple +from urllib.parse import urlparse -import voluptuous as vol import attr from getmac import get_mac_address from huawei_lte_api.AuthorizedConnection import AuthorizedConnection @@ -20,13 +19,14 @@ from huawei_lte_api.exceptions import ( ) from requests.exceptions import Timeout from url_normalize import url_normalize +import voluptuous as vol from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.components.device_tracker import DOMAIN as DEVICE_TRACKER_DOMAIN from homeassistant.components.notify import DOMAIN as NOTIFY_DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN -from homeassistant.config_entries import ConfigEntry, SOURCE_IMPORT +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( CONF_PASSWORD, CONF_RECIPIENT, @@ -36,8 +36,11 @@ from homeassistant.const import ( ) from homeassistant.core import CALLBACK_TYPE from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.helpers import config_validation as cv, discovery -from homeassistant.helpers import device_registry as dr +from homeassistant.helpers import ( + config_validation as cv, + device_registry as dr, + discovery, +) from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, @@ -46,6 +49,7 @@ from homeassistant.helpers.dispatcher import ( from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import HomeAssistantType + from .const import ( ALL_KEYS, CONNECTION_TIMEOUT, @@ -64,7 +68,6 @@ from .const import ( UPDATE_SIGNAL, ) - _LOGGER = logging.getLogger(__name__) # dicttoxml (used by huawei-lte-api) has uselessly verbose INFO level. diff --git a/homeassistant/components/huawei_lte/binary_sensor.py b/homeassistant/components/huawei_lte/binary_sensor.py index 4fcb400c32a..104933fe714 100644 --- a/homeassistant/components/huawei_lte/binary_sensor.py +++ b/homeassistant/components/huawei_lte/binary_sensor.py @@ -11,10 +11,10 @@ from homeassistant.components.binary_sensor import ( BinarySensorDevice, ) from homeassistant.const import CONF_URL + from . import HuaweiLteBaseEntity from .const import DOMAIN, KEY_MONITORING_STATUS - _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/huawei_lte/config_flow.py b/homeassistant/components/huawei_lte/config_flow.py index 1bc3753bdd7..9151e5eb999 100644 --- a/homeassistant/components/huawei_lte/config_flow.py +++ b/homeassistant/components/huawei_lte/config_flow.py @@ -8,10 +8,10 @@ from huawei_lte_api.AuthorizedConnection import AuthorizedConnection from huawei_lte_api.Client import Client from huawei_lte_api.Connection import Connection from huawei_lte_api.exceptions import ( - LoginErrorUsernameWrongException, LoginErrorPasswordWrongException, - LoginErrorUsernamePasswordWrongException, LoginErrorUsernamePasswordOverrunException, + LoginErrorUsernamePasswordWrongException, + LoginErrorUsernameWrongException, ResponseErrorException, ) from requests.exceptions import Timeout @@ -22,12 +22,11 @@ from homeassistant import config_entries from homeassistant.components.ssdp import ATTR_HOST, ATTR_NAME, ATTR_PRESENTATIONURL from homeassistant.const import CONF_PASSWORD, CONF_RECIPIENT, CONF_URL, CONF_USERNAME from homeassistant.core import callback + +# see https://github.com/PyCQA/pylint/issues/3202 about the DOMAIN's pylint issue from .const import CONNECTION_TIMEOUT, DEFAULT_DEVICE_NAME - -# https://github.com/PyCQA/pylint/issues/3202 from .const import DOMAIN # pylint: disable=unused-import - _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/huawei_lte/device_tracker.py b/homeassistant/components/huawei_lte/device_tracker.py index f5f834fa186..a9c61831fdd 100644 --- a/homeassistant/components/huawei_lte/device_tracker.py +++ b/homeassistant/components/huawei_lte/device_tracker.py @@ -15,10 +15,10 @@ from homeassistant.components.device_tracker.config_entry import ScannerEntity from homeassistant.const import CONF_URL from homeassistant.helpers import entity_registry from homeassistant.helpers.dispatcher import async_dispatcher_connect + from . import HuaweiLteBaseEntity from .const import DOMAIN, KEY_WLAN_HOST_LIST, UPDATE_SIGNAL - _LOGGER = logging.getLogger(__name__) _DEVICE_SCAN = f"{DEVICE_TRACKER_DOMAIN}/device_scan" diff --git a/homeassistant/components/huawei_lte/notify.py b/homeassistant/components/huawei_lte/notify.py index 4b5a63756b5..494d0ec720e 100644 --- a/homeassistant/components/huawei_lte/notify.py +++ b/homeassistant/components/huawei_lte/notify.py @@ -6,13 +6,12 @@ from typing import Any, List import attr from huawei_lte_api.exceptions import ResponseErrorException -from homeassistant.components.notify import BaseNotificationService, ATTR_TARGET +from homeassistant.components.notify import ATTR_TARGET, BaseNotificationService from homeassistant.const import CONF_RECIPIENT, CONF_URL from . import Router from .const import DOMAIN - _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/huawei_lte/sensor.py b/homeassistant/components/huawei_lte/sensor.py index 3cc36b30d8e..3b6b75edfba 100644 --- a/homeassistant/components/huawei_lte/sensor.py +++ b/homeassistant/components/huawei_lte/sensor.py @@ -6,11 +6,11 @@ from typing import Optional import attr -from homeassistant.const import CONF_URL, STATE_UNKNOWN from homeassistant.components.sensor import ( DEVICE_CLASS_SIGNAL_STRENGTH, DOMAIN as SENSOR_DOMAIN, ) +from homeassistant.const import CONF_URL, STATE_UNKNOWN from . import HuaweiLteBaseEntity from .const import ( @@ -22,7 +22,6 @@ from .const import ( UNIT_SECONDS, ) - _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/huawei_lte/switch.py b/homeassistant/components/huawei_lte/switch.py index bff82227b80..44d2da0c898 100644 --- a/homeassistant/components/huawei_lte/switch.py +++ b/homeassistant/components/huawei_lte/switch.py @@ -11,10 +11,10 @@ from homeassistant.components.switch import ( SwitchDevice, ) from homeassistant.const import CONF_URL + from . import HuaweiLteBaseEntity from .const import DOMAIN, KEY_DIALUP_MOBILE_DATASWITCH - _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/huawei_lte/test_config_flow.py b/tests/components/huawei_lte/test_config_flow.py index a9f5034fcfe..b14583f13cd 100644 --- a/tests/components/huawei_lte/test_config_flow.py +++ b/tests/components/huawei_lte/test_config_flow.py @@ -2,14 +2,13 @@ from huawei_lte_api.enums.client import ResponseCodeEnum from huawei_lte_api.enums.user import LoginErrorEnum, LoginStateEnum, PasswordTypeEnum -from requests_mock import ANY -from requests.exceptions import ConnectionError import pytest +from requests.exceptions import ConnectionError +from requests_mock import ANY from homeassistant import data_entry_flow -from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_URL -from homeassistant.components.huawei_lte.const import DOMAIN from homeassistant.components.huawei_lte.config_flow import ConfigFlowHandler +from homeassistant.components.huawei_lte.const import DOMAIN from homeassistant.components.ssdp import ( ATTR_HOST, ATTR_MANUFACTURER, @@ -24,10 +23,10 @@ from homeassistant.components.ssdp import ( ATTR_UDN, ATTR_UPNP_DEVICE_TYPE, ) +from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME from tests.common import MockConfigEntry - FIXTURE_USER_INPUT = { CONF_URL: "http://192.168.1.1/", CONF_USERNAME: "admin", From e37443f10c6a3d20f4302ea093e5bec1712dcd3c Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 19:07:32 +0100 Subject: [PATCH 239/677] Sort imports according to PEP8 for components starting with "U" (#29779) * use isort to sort imports for components starting with 'u' * add 'pylint: disable=import-error' to the right place --- .../components/ue_smart_radio/media_player.py | 2 +- .../components/uk_transport/sensor.py | 6 +++--- .../components/universal/media_player.py | 2 +- homeassistant/components/updater/__init__.py | 5 ++--- .../components/updater/binary_sensor.py | 2 +- homeassistant/components/uscis/sensor.py | 9 ++++---- tests/components/uk_transport/test_sensor.py | 19 +++++++++-------- .../unifi_direct/test_device_tracker.py | 18 ++++++++-------- .../components/universal/test_media_player.py | 6 +++--- tests/components/upnp/test_init.py | 8 +++---- tests/components/uptime/test_sensor.py | 5 +++-- .../test_geo_location.py | 21 ++++++++++--------- tests/components/uvc/test_camera.py | 8 +++---- 13 files changed, 55 insertions(+), 56 deletions(-) diff --git a/homeassistant/components/ue_smart_radio/media_player.py b/homeassistant/components/ue_smart_radio/media_player.py index ae54eb76d72..d25c52608e1 100644 --- a/homeassistant/components/ue_smart_radio/media_player.py +++ b/homeassistant/components/ue_smart_radio/media_player.py @@ -5,7 +5,7 @@ import logging import requests import voluptuous as vol -from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, diff --git a/homeassistant/components/uk_transport/sensor.py b/homeassistant/components/uk_transport/sensor.py index eb325d32212..e3c5440c450 100644 --- a/homeassistant/components/uk_transport/sensor.py +++ b/homeassistant/components/uk_transport/sensor.py @@ -3,19 +3,19 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.uk_transport/ """ +from datetime import datetime, timedelta import logging import re -from datetime import datetime, timedelta import requests import voluptuous as vol -import homeassistant.helpers.config_validation as cv -import homeassistant.util.dt as dt_util from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_MODE +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle +import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/universal/media_player.py b/homeassistant/components/universal/media_player.py index 63e3ff7448d..37d4cf138f2 100644 --- a/homeassistant/components/universal/media_player.py +++ b/homeassistant/components/universal/media_player.py @@ -4,7 +4,7 @@ import logging import voluptuous as vol -from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice from homeassistant.components.media_player.const import ( ATTR_APP_ID, ATTR_APP_NAME, diff --git a/homeassistant/components/updater/__init__.py b/homeassistant/components/updater/__init__.py index 0ddbfd63c73..1d4e441f12e 100644 --- a/homeassistant/components/updater/__init__.py +++ b/homeassistant/components/updater/__init__.py @@ -12,11 +12,10 @@ from distro import linux_distribution # pylint: disable=import-error import voluptuous as vol from homeassistant.const import __version__ as current_version -from homeassistant.helpers import event +from homeassistant.helpers import discovery, event from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers import discovery -from homeassistant.helpers.dispatcher import async_dispatcher_send import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import async_dispatcher_send import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/updater/binary_sensor.py b/homeassistant/components/updater/binary_sensor.py index cae3ae32e3c..3e026a87d4d 100644 --- a/homeassistant/components/updater/binary_sensor.py +++ b/homeassistant/components/updater/binary_sensor.py @@ -1,7 +1,7 @@ """Support for Home Assistant Updater binary sensors.""" -from homeassistant.core import callback from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from . import ATTR_NEWEST_VERSION, ATTR_RELEASE_NOTES, DISPATCHER_REMOTE_UPDATE, Updater diff --git a/homeassistant/components/uscis/sensor.py b/homeassistant/components/uscis/sensor.py index 3f5175ad09d..6f94d5c38b0 100644 --- a/homeassistant/components/uscis/sensor.py +++ b/homeassistant/components/uscis/sensor.py @@ -1,16 +1,15 @@ """Support for USCIS Case Status.""" -import logging from datetime import timedelta +import logging import uscisstatus import voluptuous as vol +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import CONF_FRIENDLY_NAME +from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle -from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.helpers import config_validation as cv -from homeassistant.const import CONF_FRIENDLY_NAME - _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/uk_transport/test_sensor.py b/tests/components/uk_transport/test_sensor.py index c55de0dd389..ab115d02187 100644 --- a/tests/components/uk_transport/test_sensor.py +++ b/tests/components/uk_transport/test_sensor.py @@ -1,23 +1,24 @@ """The tests for the uk_transport platform.""" import re - -import requests_mock import unittest +import requests_mock + from homeassistant.components.uk_transport.sensor import ( - UkTransportSensor, ATTR_ATCOCODE, - ATTR_LOCALITY, - ATTR_STOP_NAME, - ATTR_NEXT_BUSES, - ATTR_STATION_CODE, ATTR_CALLING_AT, + ATTR_LOCALITY, + ATTR_NEXT_BUSES, ATTR_NEXT_TRAINS, - CONF_API_APP_KEY, + ATTR_STATION_CODE, + ATTR_STOP_NAME, CONF_API_APP_ID, + CONF_API_APP_KEY, + UkTransportSensor, ) from homeassistant.setup import setup_component -from tests.common import load_fixture, get_test_home_assistant + +from tests.common import get_test_home_assistant, load_fixture BUS_ATCOCODE = "340000368SHE" BUS_DIRECTION = "Wantage" diff --git a/tests/components/unifi_direct/test_device_tracker.py b/tests/components/unifi_direct/test_device_tracker.py index cda6dff8413..3d62a451af2 100644 --- a/tests/components/unifi_direct/test_device_tracker.py +++ b/tests/components/unifi_direct/test_device_tracker.py @@ -1,29 +1,29 @@ """The tests for the Unifi direct device tracker platform.""" -import os from datetime import timedelta -from asynctest import mock, patch +import os +from asynctest import mock, patch import pytest import voluptuous as vol -from homeassistant.setup import async_setup_component -from homeassistant.components.device_tracker.legacy import YAML_DEVICES from homeassistant.components.device_tracker import ( - CONF_CONSIDER_HOME, - CONF_TRACK_NEW, CONF_AWAY_HIDE, + CONF_CONSIDER_HOME, CONF_NEW_DEVICE_DEFAULTS, + CONF_TRACK_NEW, ) +from homeassistant.components.device_tracker.legacy import YAML_DEVICES from homeassistant.components.unifi_direct.device_tracker import ( - DOMAIN, CONF_PORT, + DOMAIN, PLATFORM_SCHEMA, _response_to_json, get_scanner, ) -from homeassistant.const import CONF_PLATFORM, CONF_PASSWORD, CONF_USERNAME, CONF_HOST +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PLATFORM, CONF_USERNAME +from homeassistant.setup import async_setup_component -from tests.common import assert_setup_component, mock_component, load_fixture +from tests.common import assert_setup_component, load_fixture, mock_component scanner_path = ( "homeassistant.components.unifi_direct.device_tracker." + "UnifiDeviceScanner" diff --git a/tests/components/universal/test_media_player.py b/tests/components/universal/test_media_player.py index 22ec96c3a21..cf985621351 100644 --- a/tests/components/universal/test_media_player.py +++ b/tests/components/universal/test_media_player.py @@ -5,14 +5,14 @@ import unittest from voluptuous.error import MultipleInvalid -from homeassistant.const import STATE_OFF, STATE_ON, STATE_PLAYING, STATE_PAUSED -import homeassistant.components.switch as switch import homeassistant.components.input_number as input_number import homeassistant.components.input_select as input_select import homeassistant.components.media_player as media_player +import homeassistant.components.switch as switch import homeassistant.components.universal.media_player as universal +from homeassistant.const import STATE_OFF, STATE_ON, STATE_PAUSED, STATE_PLAYING -from tests.common import mock_service, get_test_home_assistant +from tests.common import get_test_home_assistant, mock_service def validate_config(config): diff --git a/tests/components/upnp/test_init.py b/tests/components/upnp/test_init.py index 5e2106ff208..1daa60bcb36 100644 --- a/tests/components/upnp/test_init.py +++ b/tests/components/upnp/test_init.py @@ -1,16 +1,14 @@ """Test UPnP/IGD setup process.""" from ipaddress import ip_address -from unittest.mock import patch, MagicMock +from unittest.mock import MagicMock, patch -from homeassistant.setup import async_setup_component from homeassistant.components import upnp from homeassistant.components.upnp.device import Device from homeassistant.const import EVENT_HOMEASSISTANT_STOP +from homeassistant.setup import async_setup_component -from tests.common import MockConfigEntry -from tests.common import MockDependency -from tests.common import mock_coro +from tests.common import MockConfigEntry, MockDependency, mock_coro class MockDevice(Device): diff --git a/tests/components/uptime/test_sensor.py b/tests/components/uptime/test_sensor.py index b3dcddfba6a..0a9d227681b 100644 --- a/tests/components/uptime/test_sensor.py +++ b/tests/components/uptime/test_sensor.py @@ -1,11 +1,12 @@ """The tests for the uptime sensor platform.""" import asyncio +from datetime import timedelta import unittest from unittest.mock import patch -from datetime import timedelta -from homeassistant.setup import setup_component from homeassistant.components.uptime.sensor import UptimeSensor +from homeassistant.setup import setup_component + from tests.common import get_test_home_assistant diff --git a/tests/components/usgs_earthquakes_feed/test_geo_location.py b/tests/components/usgs_earthquakes_feed/test_geo_location.py index 65ceec4d425..646878c97bd 100644 --- a/tests/components/usgs_earthquakes_feed/test_geo_location.py +++ b/tests/components/usgs_earthquakes_feed/test_geo_location.py @@ -1,37 +1,38 @@ """The tests for the USGS Earthquake Hazards Program Feed platform.""" import datetime -from unittest.mock import patch, MagicMock, call +from unittest.mock import MagicMock, call, patch from homeassistant.components import geo_location from homeassistant.components.geo_location import ATTR_SOURCE from homeassistant.components.usgs_earthquakes_feed.geo_location import ( ATTR_ALERT, ATTR_EXTERNAL_ID, - SCAN_INTERVAL, - ATTR_PLACE, ATTR_MAGNITUDE, + ATTR_PLACE, ATTR_STATUS, - ATTR_TYPE, ATTR_TIME, + ATTR_TYPE, ATTR_UPDATED, CONF_FEED_TYPE, + SCAN_INTERVAL, ) from homeassistant.const import ( - EVENT_HOMEASSISTANT_START, - CONF_RADIUS, + ATTR_ATTRIBUTION, + ATTR_FRIENDLY_NAME, + ATTR_ICON, ATTR_LATITUDE, ATTR_LONGITUDE, - ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, - ATTR_ATTRIBUTION, CONF_LATITUDE, CONF_LONGITUDE, - ATTR_ICON, + CONF_RADIUS, + EVENT_HOMEASSISTANT_START, ) from homeassistant.setup import async_setup_component -from tests.common import assert_setup_component, async_fire_time_changed import homeassistant.util.dt as dt_util +from tests.common import assert_setup_component, async_fire_time_changed + CONFIG = { geo_location.DOMAIN: [ { diff --git a/tests/components/uvc/test_camera.py b/tests/components/uvc/test_camera.py index f4be8ac6a8b..c77b5d83749 100644 --- a/tests/components/uvc/test_camera.py +++ b/tests/components/uvc/test_camera.py @@ -3,15 +3,15 @@ import socket import unittest from unittest import mock +import pytest import requests -from uvcclient import camera -from uvcclient import nvr +from uvcclient import camera, nvr +from homeassistant.components.uvc import camera as uvc from homeassistant.exceptions import PlatformNotReady from homeassistant.setup import setup_component -from homeassistant.components.uvc import camera as uvc + from tests.common import get_test_home_assistant -import pytest class TestUVCSetup(unittest.TestCase): From bc3b7ed06bf92cc902b2703c32fc5148c81135fc Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 9 Dec 2019 19:18:41 +0100 Subject: [PATCH 240/677] Fix build, invalid JSON file in icloud component (#29798) --- homeassistant/components/icloud/strings.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/icloud/strings.json b/homeassistant/components/icloud/strings.json index 343a087738f..117e26c8830 100644 --- a/homeassistant/components/icloud/strings.json +++ b/homeassistant/components/icloud/strings.json @@ -25,14 +25,14 @@ } } }, - "error":{ + "error": { "username_exists": "Account already configured", "login": "Login error: please check your email & password", "send_verification_code": "Failed to send verification code", - "validate_verification_code": "Failed to verify your verification code, choose a trust device and start the verification again", + "validate_verification_code": "Failed to verify your verification code, choose a trust device and start the verification again" }, - "abort":{ + "abort": { "username_exists": "Account already configured" } } -} +} \ No newline at end of file From 7f948594eb687294ae75ea0ee7454e2a1239df94 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Dec 2019 20:00:14 +0100 Subject: [PATCH 241/677] Sort imports according to PEP8 for google_assistant (#29633) * sort imports and fix flake8 issue for google * add isort:skip to EVENT_SYNC_RECEIVED --- .../components/google_assistant/__init__.py | 3 +- tests/components/google_assistant/__init__.py | 1 + .../google_assistant/test_google_assistant.py | 10 ++--- .../google_assistant/test_helpers.py | 13 ++++-- .../components/google_assistant/test_http.py | 15 +++---- .../components/google_assistant/test_init.py | 2 +- .../google_assistant/test_report_state.py | 5 +-- .../google_assistant/test_smart_home.py | 41 ++++++++++--------- .../components/google_assistant/test_trait.py | 35 ++++++++-------- 9 files changed, 68 insertions(+), 57 deletions(-) diff --git a/homeassistant/components/google_assistant/__init__.py b/homeassistant/components/google_assistant/__init__.py index f34a8e342c4..107003db583 100644 --- a/homeassistant/components/google_assistant/__init__.py +++ b/homeassistant/components/google_assistant/__init__.py @@ -29,10 +29,11 @@ from .const import ( DOMAIN, SERVICE_REQUEST_SYNC, ) -from .const import EVENT_COMMAND_RECEIVED, EVENT_SYNC_RECEIVED # noqa: F401 from .const import EVENT_QUERY_RECEIVED # noqa: F401 from .http import GoogleAssistantView, GoogleConfig +from .const import EVENT_COMMAND_RECEIVED, EVENT_SYNC_RECEIVED # noqa: F401, isort:skip + _LOGGER = logging.getLogger(__name__) ENTITY_SCHEMA = vol.Schema( diff --git a/tests/components/google_assistant/__init__.py b/tests/components/google_assistant/__init__.py index 657bf930ed6..db3b2e68f20 100644 --- a/tests/components/google_assistant/__init__.py +++ b/tests/components/google_assistant/__init__.py @@ -1,5 +1,6 @@ """Tests for the Google Assistant integration.""" from asynctest.mock import MagicMock + from homeassistant.components.google_assistant import helpers diff --git a/tests/components/google_assistant/test_google_assistant.py b/tests/components/google_assistant/test_google_assistant.py index b43e913ab27..d1d58422884 100644 --- a/tests/components/google_assistant/test_google_assistant.py +++ b/tests/components/google_assistant/test_google_assistant.py @@ -6,19 +6,19 @@ import json from aiohttp.hdrs import AUTHORIZATION import pytest -from homeassistant import core, const, setup +from homeassistant import const, core, setup from homeassistant.components import ( - fan, + alarm_control_panel, cover, + fan, + google_assistant as ga, light, - switch, lock, media_player, - alarm_control_panel, + switch, ) from homeassistant.components.climate import const as climate from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES -from homeassistant.components import google_assistant as ga from . import DEMO_DEVICES diff --git a/tests/components/google_assistant/test_helpers.py b/tests/components/google_assistant/test_helpers.py index eb479a3b6b5..9c8a868e68d 100644 --- a/tests/components/google_assistant/test_helpers.py +++ b/tests/components/google_assistant/test_helpers.py @@ -1,17 +1,22 @@ """Test Google Assistant helpers.""" -from asynctest.mock import Mock, patch, call from datetime import timedelta + +from asynctest.mock import Mock, call, patch import pytest -from homeassistant.setup import async_setup_component + from homeassistant.components.google_assistant import helpers -from homeassistant.components.google_assistant.const import EVENT_COMMAND_RECEIVED +from homeassistant.components.google_assistant.const import ( # noqa: F401 + EVENT_COMMAND_RECEIVED, +) +from homeassistant.setup import async_setup_component from homeassistant.util import dt + from . import MockConfig from tests.common import ( async_capture_events, - async_mock_service, async_fire_time_changed, + async_mock_service, ) diff --git a/tests/components/google_assistant/test_http.py b/tests/components/google_assistant/test_http.py index 86ffcc87ac0..112935f0160 100644 --- a/tests/components/google_assistant/test_http.py +++ b/tests/components/google_assistant/test_http.py @@ -1,17 +1,18 @@ """Test Google http services.""" -from datetime import datetime, timezone, timedelta -from asynctest import patch, ANY +from datetime import datetime, timedelta, timezone +from asynctest import ANY, patch + +from homeassistant.components.google_assistant import GOOGLE_ASSISTANT_SCHEMA +from homeassistant.components.google_assistant.const import ( + HOMEGRAPH_TOKEN_URL, + REPORT_STATE_BASE_URL, +) from homeassistant.components.google_assistant.http import ( GoogleConfig, _get_homegraph_jwt, _get_homegraph_token, ) -from homeassistant.components.google_assistant import GOOGLE_ASSISTANT_SCHEMA -from homeassistant.components.google_assistant.const import ( - REPORT_STATE_BASE_URL, - HOMEGRAPH_TOKEN_URL, -) DUMMY_CONFIG = GOOGLE_ASSISTANT_SCHEMA( { diff --git a/tests/components/google_assistant/test_init.py b/tests/components/google_assistant/test_init.py index 9a8b9643cfe..7c5d14ae6d7 100644 --- a/tests/components/google_assistant/test_init.py +++ b/tests/components/google_assistant/test_init.py @@ -1,9 +1,9 @@ """The tests for google-assistant init.""" import asyncio +from homeassistant.components import google_assistant as ga from homeassistant.core import Context from homeassistant.setup import async_setup_component -from homeassistant.components import google_assistant as ga GA_API_KEY = "Agdgjsj399sdfkosd932ksd" diff --git a/tests/components/google_assistant/test_report_state.py b/tests/components/google_assistant/test_report_state.py index ce624c9ca95..fd9cad27ffa 100644 --- a/tests/components/google_assistant/test_report_state.py +++ b/tests/components/google_assistant/test_report_state.py @@ -1,13 +1,12 @@ """Test Google report state.""" from unittest.mock import patch -from homeassistant.components.google_assistant import report_state, error +from homeassistant.components.google_assistant import error, report_state from homeassistant.util.dt import utcnow from . import BASIC_CONFIG - -from tests.common import mock_coro, async_fire_time_changed +from tests.common import async_fire_time_changed, mock_coro async def test_report_state(hass, caplog): diff --git a/tests/components/google_assistant/test_smart_home.py b/tests/components/google_assistant/test_smart_home.py index c144ffee6de..7ffe9cda477 100644 --- a/tests/components/google_assistant/test_smart_home.py +++ b/tests/components/google_assistant/test_smart_home.py @@ -1,40 +1,41 @@ """Test Google Smart Home.""" -from unittest.mock import patch, Mock +from unittest.mock import Mock, patch + import pytest -from homeassistant.core import State, EVENT_CALL_SERVICE -from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS, __version__ -from homeassistant.setup import async_setup_component from homeassistant.components import camera from homeassistant.components.climate.const import ( - ATTR_MIN_TEMP, ATTR_MAX_TEMP, + ATTR_MIN_TEMP, HVAC_MODE_HEAT, ) -from homeassistant.components.google_assistant import ( - const, - trait, - smart_home as sh, - EVENT_COMMAND_RECEIVED, - EVENT_QUERY_RECEIVED, - EVENT_SYNC_RECEIVED, -) from homeassistant.components.demo.binary_sensor import DemoBinarySensor from homeassistant.components.demo.cover import DemoCover from homeassistant.components.demo.light import DemoLight from homeassistant.components.demo.media_player import AbstractDemoPlayer from homeassistant.components.demo.switch import DemoSwitch - -from homeassistant.helpers import device_registry -from tests.common import ( - mock_device_registry, - mock_registry, - mock_area_registry, - mock_coro, +from homeassistant.components.google_assistant import ( + EVENT_COMMAND_RECEIVED, + EVENT_QUERY_RECEIVED, + EVENT_SYNC_RECEIVED, + const, + smart_home as sh, + trait, ) +from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS, __version__ +from homeassistant.core import EVENT_CALL_SERVICE, State +from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component from . import BASIC_CONFIG, MockConfig +from tests.common import ( + mock_area_registry, + mock_coro, + mock_device_registry, + mock_registry, +) + REQ_ID = "ff36a3cc-ec34-11e6-b1a0-64510650abcf" diff --git a/tests/components/google_assistant/test_trait.py b/tests/components/google_assistant/test_trait.py index 6d24aa0942f..8a1d7384729 100644 --- a/tests/components/google_assistant/test_trait.py +++ b/tests/components/google_assistant/test_trait.py @@ -1,13 +1,16 @@ """Tests for the Google Assistant traits.""" -from unittest.mock import patch, Mock import logging +from unittest.mock import Mock, patch + import pytest from homeassistant.components import ( + alarm_control_panel, binary_sensor, camera, cover, fan, + group, input_boolean, light, lock, @@ -17,33 +20,33 @@ from homeassistant.components import ( sensor, switch, vacuum, - group, - alarm_control_panel, ) from homeassistant.components.climate import const as climate -from homeassistant.components.google_assistant import trait, helpers, const, error +from homeassistant.components.google_assistant import const, error, helpers, trait from homeassistant.const import ( - STATE_ON, - STATE_OFF, + ATTR_ASSUMED_STATE, + ATTR_DEVICE_CLASS, + ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, + ATTR_TEMPERATURE, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, STATE_ALARM_ARMED_AWAY, STATE_ALARM_DISARMED, STATE_ALARM_PENDING, - ATTR_ENTITY_ID, - SERVICE_TURN_ON, - SERVICE_TURN_OFF, + STATE_OFF, + STATE_ON, + STATE_UNKNOWN, TEMP_CELSIUS, TEMP_FAHRENHEIT, - ATTR_SUPPORTED_FEATURES, - ATTR_TEMPERATURE, - ATTR_DEVICE_CLASS, - ATTR_ASSUMED_STATE, - STATE_UNKNOWN, ) -from homeassistant.core import State, DOMAIN as HA_DOMAIN, EVENT_CALL_SERVICE +from homeassistant.core import DOMAIN as HA_DOMAIN, EVENT_CALL_SERVICE, State from homeassistant.util import color -from tests.common import async_mock_service, mock_coro + from . import BASIC_CONFIG, MockConfig +from tests.common import async_mock_service, mock_coro + _LOGGER = logging.getLogger(__name__) REQ_ID = "ff36a3cc-ec34-11e6-b1a0-64510650abcf" From 1222aa8c56f7d7f853996fa58ab0f962ed0bb4ef Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Mon, 9 Dec 2019 14:50:04 -0500 Subject: [PATCH 242/677] Add ZHA group API (#29641) * add skeleton to retrieve zigbee groups * get single group * add a group * return group members with group * add comment * fix group members * add function to add device to group * add group members * add remove from group method * add api to remove members from group * add remove groups method * clean up group add and remove * fix remove group * fix remove groups * add api to get only groupable devices * change var init * add tests * address review comment --- homeassistant/components/zha/api.py | 250 ++++++++++++++++++++ homeassistant/components/zha/core/const.py | 5 + homeassistant/components/zha/core/device.py | 20 ++ tests/components/zha/common.py | 2 + tests/components/zha/conftest.py | 11 + tests/components/zha/test_api.py | 176 +++++++++++++- 6 files changed, 461 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zha/api.py b/homeassistant/components/zha/api.py index 438b93244cf..e796c48c3f3 100644 --- a/homeassistant/components/zha/api.py +++ b/homeassistant/components/zha/api.py @@ -23,6 +23,7 @@ from .core.const import ( ATTR_ENDPOINT_ID, ATTR_LEVEL, ATTR_MANUFACTURER, + ATTR_MEMBERS, ATTR_NAME, ATTR_VALUE, ATTR_WARNING_DEVICE_DURATION, @@ -39,6 +40,9 @@ from .core.const import ( DATA_ZHA, DATA_ZHA_GATEWAY, DOMAIN, + GROUP_ID, + GROUP_IDS, + GROUP_NAME, MFG_CLUSTER_ID_START, WARNING_DEVICE_MODE_EMERGENCY, WARNING_DEVICE_SOUND_HIGH, @@ -211,6 +215,34 @@ async def websocket_get_devices(hass, connection, msg): connection.send_result(msg[ID], devices) +@websocket_api.require_admin +@websocket_api.async_response +@websocket_api.websocket_command({vol.Required(TYPE): "zha/devices/groupable"}) +async def websocket_get_groupable_devices(hass, connection, msg): + """Get ZHA devices that can be grouped.""" + zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] + ha_device_registry = await async_get_registry(hass) + + devices = [] + for device in zha_gateway.devices.values(): + if device.is_groupable: + devices.append( + async_get_device_info( + hass, device, ha_device_registry=ha_device_registry + ) + ) + connection.send_result(msg[ID], devices) + + +@websocket_api.require_admin +@websocket_api.async_response +@websocket_api.websocket_command({vol.Required(TYPE): "zha/groups"}) +async def websocket_get_groups(hass, connection, msg): + """Get ZHA groups.""" + groups = await get_groups(hass) + connection.send_result(msg[ID], groups) + + @websocket_api.require_admin @websocket_api.async_response @websocket_api.websocket_command( @@ -236,6 +268,161 @@ async def websocket_get_device(hass, connection, msg): connection.send_result(msg[ID], device) +@websocket_api.require_admin +@websocket_api.async_response +@websocket_api.websocket_command( + {vol.Required(TYPE): "zha/group", vol.Required(GROUP_ID): cv.positive_int} +) +async def websocket_get_group(hass, connection, msg): + """Get ZHA group.""" + zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] + ha_device_registry = await async_get_registry(hass) + group_id = msg[GROUP_ID] + group = None + + if group_id in zha_gateway.application_controller.groups: + group = async_get_group_info( + hass, + zha_gateway, + zha_gateway.application_controller.groups[group_id], + ha_device_registry, + ) + if not group: + connection.send_message( + websocket_api.error_message( + msg[ID], websocket_api.const.ERR_NOT_FOUND, "ZHA Group not found" + ) + ) + return + connection.send_result(msg[ID], group) + + +@websocket_api.require_admin +@websocket_api.async_response +@websocket_api.websocket_command( + { + vol.Required(TYPE): "zha/group/add", + vol.Required(GROUP_NAME): cv.string, + vol.Optional(ATTR_MEMBERS): vol.All(cv.ensure_list, [EUI64.convert]), + } +) +async def websocket_add_group(hass, connection, msg): + """Add a new ZHA group.""" + zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] + ha_device_registry = await async_get_registry(hass) + group_id = len(zha_gateway.application_controller.groups) + 1 + group_name = msg[GROUP_NAME] + zigpy_group = async_get_group_by_name(zha_gateway, group_name) + ret_group = None + members = msg.get(ATTR_MEMBERS) + + # guard against group already existing + if zigpy_group is None: + zigpy_group = zha_gateway.application_controller.groups.add_group( + group_id, group_name + ) + if members is not None: + tasks = [] + for ieee in members: + tasks.append(zha_gateway.devices[ieee].async_add_to_group(group_id)) + await asyncio.gather(*tasks) + ret_group = async_get_group_info(hass, zha_gateway, zigpy_group, ha_device_registry) + connection.send_result(msg[ID], ret_group) + + +@websocket_api.require_admin +@websocket_api.async_response +@websocket_api.websocket_command( + { + vol.Required(TYPE): "zha/group/remove", + vol.Required(GROUP_IDS): vol.All(cv.ensure_list, [cv.positive_int]), + } +) +async def websocket_remove_groups(hass, connection, msg): + """Remove the specified ZHA groups.""" + zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] + groups = zha_gateway.application_controller.groups + group_ids = msg[GROUP_IDS] + + if len(group_ids) > 1: + tasks = [] + for group_id in group_ids: + tasks.append(remove_group(groups[group_id], zha_gateway)) + await asyncio.gather(*tasks) + else: + await remove_group(groups[group_ids[0]], zha_gateway) + ret_groups = await get_groups(hass) + connection.send_result(msg[ID], ret_groups) + + +@websocket_api.require_admin +@websocket_api.async_response +@websocket_api.websocket_command( + { + vol.Required(TYPE): "zha/group/members/add", + vol.Required(GROUP_ID): cv.positive_int, + vol.Required(ATTR_MEMBERS): vol.All(cv.ensure_list, [EUI64.convert]), + } +) +async def websocket_add_group_members(hass, connection, msg): + """Add members to a ZHA group.""" + zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] + ha_device_registry = await async_get_registry(hass) + group_id = msg[GROUP_ID] + members = msg[ATTR_MEMBERS] + zigpy_group = None + + if group_id in zha_gateway.application_controller.groups: + zigpy_group = zha_gateway.application_controller.groups[group_id] + tasks = [] + for ieee in members: + tasks.append(zha_gateway.devices[ieee].async_add_to_group(group_id)) + await asyncio.gather(*tasks) + if not zigpy_group: + connection.send_message( + websocket_api.error_message( + msg[ID], websocket_api.const.ERR_NOT_FOUND, "ZHA Group not found" + ) + ) + return + ret_group = async_get_group_info(hass, zha_gateway, zigpy_group, ha_device_registry) + connection.send_result(msg[ID], ret_group) + + +@websocket_api.require_admin +@websocket_api.async_response +@websocket_api.websocket_command( + { + vol.Required(TYPE): "zha/group/members/remove", + vol.Required(GROUP_ID): cv.positive_int, + vol.Required(ATTR_MEMBERS): vol.All(cv.ensure_list, [EUI64.convert]), + } +) +async def websocket_remove_group_members(hass, connection, msg): + """Remove members from a ZHA group.""" + zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] + ha_device_registry = await async_get_registry(hass) + group_id = msg[GROUP_ID] + members = msg[ATTR_MEMBERS] + zigpy_group = None + + if group_id in zha_gateway.application_controller.groups: + zigpy_group = zha_gateway.application_controller.groups[group_id] + tasks = [] + for ieee in members: + tasks.append(zha_gateway.devices[ieee].async_remove_from_group(group_id)) + await asyncio.gather(*tasks) + if not zigpy_group: + connection.send_message( + websocket_api.error_message( + msg[ID], websocket_api.const.ERR_NOT_FOUND, "ZHA Group not found" + ) + ) + return + ret_group = async_get_group_info(hass, zha_gateway, zigpy_group, ha_device_registry) + connection.send_result(msg[ID], ret_group) + + @callback def async_get_device_info(hass, device, ha_device_registry=None): """Get ZHA device.""" @@ -261,6 +448,62 @@ def async_get_device_info(hass, device, ha_device_registry=None): return ret_device +async def get_groups(hass,): + """Get ZHA Groups.""" + zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] + ha_device_registry = await async_get_registry(hass) + + groups = [] + for group in zha_gateway.application_controller.groups.values(): + groups.append( + async_get_group_info(hass, zha_gateway, group, ha_device_registry) + ) + return groups + + +async def remove_group(group, zha_gateway): + """Remove ZHA Group.""" + if group.members: + tasks = [] + for member_ieee in group.members.keys(): + if member_ieee[0] in zha_gateway.devices: + tasks.append( + zha_gateway.devices[member_ieee[0]].async_remove_from_group( + group.group_id + ) + ) + await asyncio.gather(*tasks) + else: + zha_gateway.application_controller.groups.pop(group.group_id) + + +@callback +def async_get_group_info(hass, zha_gateway, group, ha_device_registry): + """Get ZHA group.""" + ret_group = {} + ret_group["group_id"] = group.group_id + ret_group["name"] = group.name + ret_group["members"] = [ + async_get_device_info( + hass, + zha_gateway.get_device(member_ieee[0]), + ha_device_registry=ha_device_registry, + ) + for member_ieee in group.members.keys() + if member_ieee[0] in zha_gateway.devices + ] + return ret_group + + +@callback +def async_get_group_by_name(zha_gateway, group_name): + """Get ZHA group by name.""" + for group in zha_gateway.application_controller.groups.values(): + if group.name == group_name: + return group + return None + + @websocket_api.require_admin @websocket_api.async_response @websocket_api.websocket_command( @@ -785,7 +1028,14 @@ def async_load_api(hass): websocket_api.async_register_command(hass, websocket_permit_devices) websocket_api.async_register_command(hass, websocket_get_devices) + websocket_api.async_register_command(hass, websocket_get_groupable_devices) + websocket_api.async_register_command(hass, websocket_get_groups) websocket_api.async_register_command(hass, websocket_get_device) + websocket_api.async_register_command(hass, websocket_get_group) + websocket_api.async_register_command(hass, websocket_add_group) + websocket_api.async_register_command(hass, websocket_remove_groups) + websocket_api.async_register_command(hass, websocket_add_group_members) + websocket_api.async_register_command(hass, websocket_remove_group_members) websocket_api.async_register_command(hass, websocket_reconfigure_node) websocket_api.async_register_command(hass, websocket_device_clusters) websocket_api.async_register_command(hass, websocket_device_cluster_attributes) diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index ac83c2cdcd8..24c0126ba60 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -24,6 +24,7 @@ ATTR_LEVEL = "level" ATTR_LQI = "lqi" ATTR_MANUFACTURER = "manufacturer" ATTR_MANUFACTURER_CODE = "manufacturer_code" +ATTR_MEMBERS = "members" ATTR_MODEL = "model" ATTR_NAME = "name" ATTR_NWK = "nwk" @@ -105,6 +106,10 @@ DISCOVERY_KEY = "zha_discovery_info" DOMAIN = "zha" +GROUP_ID = "group_id" +GROUP_IDS = "group_ids" +GROUP_NAME = "group_name" + MFG_CLUSTER_ID_START = 0xFC00 POWER_MAINS_POWERED = "Mains" diff --git a/homeassistant/components/zha/core/device.py b/homeassistant/components/zha/core/device.py index 9ace477d621..77e0263c06c 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -13,6 +13,7 @@ import time import zigpy.exceptions from zigpy.profiles import zha, zll import zigpy.quirks +from zigpy.zcl.clusters.general import Groups from homeassistant.core import callback from homeassistant.helpers.dispatcher import ( @@ -179,6 +180,17 @@ class ZHADevice(LogMixin): """Return true if this device is an end device.""" return self._zigpy_device.node_desc.is_end_device + @property + def is_groupable(self): + """Return true if this device has a group cluster.""" + if not self.available: + return False + clusters = self.async_get_clusters() + for cluster_map in clusters.values(): + for clusters in cluster_map.values(): + if Groups.cluster_id in clusters: + return True + @property def gateway(self): """Return the gateway for this device.""" @@ -506,6 +518,14 @@ class ZHADevice(LogMixin): ) return response + async def async_add_to_group(self, group_id): + """Add this device to the provided zigbee group.""" + await self._zigpy_device.add_to_group(group_id) + + async def async_remove_from_group(self, group_id): + """Remove this device from the provided zigbee group.""" + await self._zigpy_device.remove_from_group(group_id) + def log(self, level, msg, *args): """Log a message.""" msg = "[%s](%s): " + msg diff --git a/tests/components/zha/common.py b/tests/components/zha/common.py index 583b4e0738b..57fb26db7f0 100644 --- a/tests/components/zha/common.py +++ b/tests/components/zha/common.py @@ -93,6 +93,8 @@ class FakeDevice: self.manufacturer = manufacturer self.model = model self.node_desc = zigpy.zdo.types.NodeDescriptor() + self.add_to_group = CoroutineMock() + self.remove_from_group = CoroutineMock() def make_device( diff --git a/tests/components/zha/conftest.py b/tests/components/zha/conftest.py index e34ad208744..cc8f9366ecb 100644 --- a/tests/components/zha/conftest.py +++ b/tests/components/zha/conftest.py @@ -1,7 +1,10 @@ """Test configuration for the ZHA component.""" +from unittest import mock from unittest.mock import patch import pytest +import zigpy +from zigpy.application import ControllerApplication from homeassistant import config_entries from homeassistant.components.zha.core.const import COMPONENTS, DATA_ZHA, DOMAIN @@ -12,6 +15,9 @@ from homeassistant.helpers.device_registry import async_get_registry as get_dev_ from .common import async_setup_entry +FIXTURE_GRP_ID = 0x1001 +FIXTURE_GRP_NAME = "fixture group" + @pytest.fixture(name="config_entry") def config_entry_fixture(hass): @@ -43,6 +49,11 @@ async def zha_gateway_fixture(hass, config_entry): gateway = ZHAGateway(hass, {}, config_entry) gateway.zha_storage = zha_storage gateway.ha_device_registry = dev_reg + gateway.application_controller = mock.MagicMock(spec_set=ControllerApplication) + groups = zigpy.group.Groups(gateway.application_controller) + groups.listener_event = mock.MagicMock() + groups.add_group(FIXTURE_GRP_ID, FIXTURE_GRP_NAME, suppress_event=True) + gateway.application_controller.groups = groups return gateway diff --git a/tests/components/zha/test_api.py b/tests/components/zha/test_api.py index 3fea9dfe088..f01d27eb167 100644 --- a/tests/components/zha/test_api.py +++ b/tests/components/zha/test_api.py @@ -1,7 +1,10 @@ """Test ZHA API.""" + import pytest +import zigpy import zigpy.zcl.clusters.general as general +from homeassistant.components.light import DOMAIN as light_domain from homeassistant.components.switch import DOMAIN from homeassistant.components.websocket_api import const from homeassistant.components.zha.api import ID, TYPE, async_load_api @@ -15,9 +18,13 @@ from homeassistant.components.zha.core.const import ( ATTR_NAME, ATTR_QUIRK_APPLIED, CLUSTER_TYPE_IN, + GROUP_ID, + GROUP_IDS, + GROUP_NAME, ) from .common import async_init_zigpy_device +from .conftest import FIXTURE_GRP_ID, FIXTURE_GRP_NAME @pytest.fixture @@ -36,9 +43,22 @@ async def zha_client(hass, config_entry, zha_gateway, hass_ws_client): zha_gateway, ) + await async_init_zigpy_device( + hass, + [general.OnOff.cluster_id, general.Basic.cluster_id, general.Groups.cluster_id], + [], + zigpy.profiles.zha.DeviceType.ON_OFF_LIGHT, + zha_gateway, + manufacturer="FakeGroupManufacturer", + model="FakeGroupModel", + ieee="01:2d:6f:00:0a:90:69:e8", + ) + # load up switch domain await hass.config_entries.async_forward_entry_setup(config_entry, DOMAIN) await hass.async_block_till_done() + await hass.config_entries.async_forward_entry_setup(config_entry, light_domain) + await hass.async_block_till_done() return await hass_ws_client(hass) @@ -114,15 +134,17 @@ async def test_device_cluster_commands(hass, config_entry, zha_gateway, zha_clie async def test_list_devices(hass, config_entry, zha_gateway, zha_client): - """Test getting entity cluster commands.""" + """Test getting zha devices.""" await zha_client.send_json({ID: 5, TYPE: "zha/devices"}) msg = await zha_client.receive_json() devices = msg["result"] - assert len(devices) == 1 + assert len(devices) == 2 + msg_id = 100 for device in devices: + msg_id += 1 assert device[ATTR_IEEE] is not None assert device[ATTR_MANUFACTURER] is not None assert device[ATTR_MODEL] is not None @@ -135,7 +157,7 @@ async def test_list_devices(hass, config_entry, zha_gateway, zha_client): assert entity_reference["entity_id"] is not None await zha_client.send_json( - {ID: 6, TYPE: "zha/device", ATTR_IEEE: device[ATTR_IEEE]} + {ID: msg_id, TYPE: "zha/device", ATTR_IEEE: device[ATTR_IEEE]} ) msg = await zha_client.receive_json() device2 = msg["result"] @@ -152,3 +174,151 @@ async def test_device_not_found(hass, config_entry, zha_gateway, zha_client): assert msg["type"] == const.TYPE_RESULT assert not msg["success"] assert msg["error"]["code"] == const.ERR_NOT_FOUND + + +async def test_list_groups(hass, config_entry, zha_gateway, zha_client): + """Test getting zha zigbee groups.""" + await zha_client.send_json({ID: 7, TYPE: "zha/groups"}) + + msg = await zha_client.receive_json() + assert msg["id"] == 7 + assert msg["type"] == const.TYPE_RESULT + + groups = msg["result"] + assert len(groups) == 1 + + for group in groups: + assert group["group_id"] == FIXTURE_GRP_ID + assert group["name"] == FIXTURE_GRP_NAME + assert group["members"] == [] + + +async def test_get_group(hass, config_entry, zha_gateway, zha_client): + """Test getting a specific zha zigbee group.""" + await zha_client.send_json({ID: 8, TYPE: "zha/group", GROUP_ID: FIXTURE_GRP_ID}) + + msg = await zha_client.receive_json() + assert msg["id"] == 8 + assert msg["type"] == const.TYPE_RESULT + + group = msg["result"] + assert group is not None + assert group["group_id"] == FIXTURE_GRP_ID + assert group["name"] == FIXTURE_GRP_NAME + assert group["members"] == [] + + +async def test_get_group_not_found(hass, config_entry, zha_gateway, zha_client): + """Test not found response from get group API.""" + await zha_client.send_json({ID: 9, TYPE: "zha/group", GROUP_ID: 1234567}) + + msg = await zha_client.receive_json() + + assert msg["id"] == 9 + assert msg["type"] == const.TYPE_RESULT + assert not msg["success"] + assert msg["error"]["code"] == const.ERR_NOT_FOUND + + +async def test_list_groupable_devices(hass, config_entry, zha_gateway, zha_client): + """Test getting zha devices that have a group cluster.""" + + # Make device available + zha_gateway.devices[ + zigpy.types.EUI64.convert("01:2d:6f:00:0a:90:69:e8") + ].set_available(True) + + await zha_client.send_json({ID: 10, TYPE: "zha/devices/groupable"}) + + msg = await zha_client.receive_json() + assert msg["id"] == 10 + assert msg["type"] == const.TYPE_RESULT + + devices = msg["result"] + assert len(devices) == 1 + + for device in devices: + assert device[ATTR_IEEE] == "01:2d:6f:00:0a:90:69:e8" + assert device[ATTR_MANUFACTURER] is not None + assert device[ATTR_MODEL] is not None + assert device[ATTR_NAME] is not None + assert device[ATTR_QUIRK_APPLIED] is not None + assert device["entities"] is not None + + for entity_reference in device["entities"]: + assert entity_reference[ATTR_NAME] is not None + assert entity_reference["entity_id"] is not None + + # Make sure there are no groupable devices when the device is unavailable + # Make device unavailable + zha_gateway.devices[ + zigpy.types.EUI64.convert("01:2d:6f:00:0a:90:69:e8") + ].set_available(False) + + await zha_client.send_json({ID: 11, TYPE: "zha/devices/groupable"}) + + msg = await zha_client.receive_json() + assert msg["id"] == 11 + assert msg["type"] == const.TYPE_RESULT + + devices = msg["result"] + assert len(devices) == 0 + + +async def test_add_group(hass, config_entry, zha_gateway, zha_client): + """Test adding and getting a new zha zigbee group.""" + await zha_client.send_json({ID: 12, TYPE: "zha/group/add", GROUP_NAME: "new_group"}) + + msg = await zha_client.receive_json() + assert msg["id"] == 12 + assert msg["type"] == const.TYPE_RESULT + + added_group = msg["result"] + + assert added_group["name"] == "new_group" + assert added_group["members"] == [] + + await zha_client.send_json({ID: 13, TYPE: "zha/groups"}) + + msg = await zha_client.receive_json() + assert msg["id"] == 13 + assert msg["type"] == const.TYPE_RESULT + + groups = msg["result"] + assert len(groups) == 2 + + for group in groups: + assert group["name"] == FIXTURE_GRP_NAME or group["name"] == "new_group" + + +async def test_remove_group(hass, config_entry, zha_gateway, zha_client): + """Test removing a new zha zigbee group.""" + + await zha_client.send_json({ID: 14, TYPE: "zha/groups"}) + + msg = await zha_client.receive_json() + assert msg["id"] == 14 + assert msg["type"] == const.TYPE_RESULT + + groups = msg["result"] + assert len(groups) == 1 + + await zha_client.send_json( + {ID: 15, TYPE: "zha/group/remove", GROUP_IDS: [FIXTURE_GRP_ID]} + ) + + msg = await zha_client.receive_json() + assert msg["id"] == 15 + assert msg["type"] == const.TYPE_RESULT + + groups_remaining = msg["result"] + assert len(groups_remaining) == 0 + + await zha_client.send_json({ID: 16, TYPE: "zha/groups"}) + + msg = await zha_client.receive_json() + assert msg["id"] == 16 + assert msg["type"] == const.TYPE_RESULT + + groups = msg["result"] + assert len(groups) == 0 From 8c1cdc0cf7a2012c0e68001650054a1fc8f874eb Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Mon, 9 Dec 2019 15:15:24 -0500 Subject: [PATCH 243/677] Add input_text reload service. (#29644) * Add input_text reload service. * Add test. --- .../components/input_text/__init__.py | 43 +++++++++--- .../components/input_text/services.yaml | 2 + tests/components/input_text/test_init.py | 67 ++++++++++++++++++- 3 files changed, 102 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/input_text/__init__.py b/homeassistant/components/input_text/__init__.py index 2d5a23e2a76..77f007c5ba8 100644 --- a/homeassistant/components/input_text/__init__.py +++ b/homeassistant/components/input_text/__init__.py @@ -9,10 +9,12 @@ from homeassistant.const import ( CONF_ICON, CONF_MODE, CONF_NAME, + SERVICE_RELOAD, ) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.restore_state import RestoreEntity +import homeassistant.helpers.service _LOGGER = logging.getLogger(__name__) @@ -76,12 +78,43 @@ CONFIG_SCHEMA = vol.Schema( required=True, extra=vol.ALLOW_EXTRA, ) +RELOAD_SERVICE_SCHEMA = vol.Schema({}) async def async_setup(hass, config): """Set up an input text box.""" component = EntityComponent(_LOGGER, DOMAIN, hass) + entities = await _async_process_config(config) + + async def reload_service_handler(service_call): + """Remove all entities and load new ones from config.""" + conf = await component.async_prepare_reload() + if conf is None: + return + new_entities = await _async_process_config(conf) + if new_entities: + await component.async_add_entities(new_entities) + + homeassistant.helpers.service.async_register_admin_service( + hass, + DOMAIN, + SERVICE_RELOAD, + reload_service_handler, + schema=RELOAD_SERVICE_SCHEMA, + ) + + component.async_register_entity_service( + SERVICE_SET_VALUE, {vol.Required(ATTR_VALUE): cv.string}, "async_set_value" + ) + + if entities: + await component.async_add_entities(entities) + return True + + +async def _async_process_config(config): + """Process config and create list of entities.""" entities = [] for object_id, cfg in config[DOMAIN].items(): @@ -102,15 +135,7 @@ async def async_setup(hass, config): ) ) - if not entities: - return False - - component.async_register_entity_service( - SERVICE_SET_VALUE, {vol.Required(ATTR_VALUE): cv.string}, "async_set_value" - ) - - await component.async_add_entities(entities) - return True + return entities class InputText(RestoreEntity): diff --git a/homeassistant/components/input_text/services.yaml b/homeassistant/components/input_text/services.yaml index 219eecf2fd6..e9b709b0c03 100644 --- a/homeassistant/components/input_text/services.yaml +++ b/homeassistant/components/input_text/services.yaml @@ -4,3 +4,5 @@ set_value: entity_id: {description: Entity id of the input text to set the new value., example: input_text.text1} value: {description: The target value the entity should be set to., example: This is an example text} +reload: + description: Reload the input_text configuration. diff --git a/tests/components/input_text/test_init.py b/tests/components/input_text/test_init.py index b758b245092..1bcf612c39b 100644 --- a/tests/components/input_text/test_init.py +++ b/tests/components/input_text/test_init.py @@ -1,10 +1,14 @@ """The tests for the Input text component.""" # pylint: disable=protected-access import asyncio +from unittest.mock import patch + +import pytest from homeassistant.components.input_text import ATTR_VALUE, DOMAIN, SERVICE_SET_VALUE -from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.const import ATTR_ENTITY_ID, SERVICE_RELOAD from homeassistant.core import Context, CoreState, State +from homeassistant.exceptions import Unauthorized from homeassistant.loader import bind_hass from homeassistant.setup import async_setup_component @@ -195,3 +199,64 @@ async def test_config_none(hass): state = hass.states.get("input_text.b1") assert state assert str(state.state) == "unknown" + + +async def test_reload(hass, hass_admin_user, hass_read_only_user): + """Test reload service.""" + count_start = len(hass.states.async_entity_ids()) + + assert await async_setup_component( + hass, + DOMAIN, + {DOMAIN: {"test_1": {"initial": "test 1"}, "test_2": {"initial": "test 2"}}}, + ) + + assert count_start + 2 == len(hass.states.async_entity_ids()) + + state_1 = hass.states.get("input_text.test_1") + state_2 = hass.states.get("input_text.test_2") + state_3 = hass.states.get("input_text.test_3") + + assert state_1 is not None + assert state_2 is not None + assert state_3 is None + assert "test 1" == state_1.state + assert "test 2" == state_2.state + + with patch( + "homeassistant.config.load_yaml_config_file", + autospec=True, + return_value={ + DOMAIN: { + "test_2": {"initial": "test reloaded"}, + "test_3": {"initial": "test 3"}, + } + }, + ): + with patch("homeassistant.config.find_config_file", return_value=""): + with pytest.raises(Unauthorized): + await hass.services.async_call( + DOMAIN, + SERVICE_RELOAD, + blocking=True, + context=Context(user_id=hass_read_only_user.id), + ) + await hass.services.async_call( + DOMAIN, + SERVICE_RELOAD, + blocking=True, + context=Context(user_id=hass_admin_user.id), + ) + await hass.async_block_till_done() + + assert count_start + 2 == len(hass.states.async_entity_ids()) + + state_1 = hass.states.get("input_text.test_1") + state_2 = hass.states.get("input_text.test_2") + state_3 = hass.states.get("input_text.test_3") + + assert state_1 is None + assert state_2 is not None + assert state_3 is not None + assert "test reloaded" == state_2.state + assert "test 3" == state_3.state From 454cc684e43447153f5374e5c31cd97ca3f38bc3 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Mon, 9 Dec 2019 15:15:32 -0500 Subject: [PATCH 244/677] Add input_select reload service. (#29647) * Add input_select reload service. * Add test. --- .../components/input_select/__init__.py | 46 +++++++--- .../components/input_select/services.yaml | 2 + tests/components/input_select/test_init.py | 89 ++++++++++++++++++- 3 files changed, 125 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/input_select/__init__.py b/homeassistant/components/input_select/__init__.py index ae609e09271..b2b4b2083e8 100644 --- a/homeassistant/components/input_select/__init__.py +++ b/homeassistant/components/input_select/__init__.py @@ -3,10 +3,11 @@ import logging import voluptuous as vol -from homeassistant.const import CONF_ICON, CONF_NAME +from homeassistant.const import CONF_ICON, CONF_NAME, SERVICE_RELOAD import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.restore_state import RestoreEntity +import homeassistant.helpers.service _LOGGER = logging.getLogger(__name__) @@ -61,23 +62,31 @@ CONFIG_SCHEMA = vol.Schema( required=True, extra=vol.ALLOW_EXTRA, ) +RELOAD_SERVICE_SCHEMA = vol.Schema({}) async def async_setup(hass, config): """Set up an input select.""" component = EntityComponent(_LOGGER, DOMAIN, hass) - entities = [] + entities = await _async_process_config(config) - for object_id, cfg in config[DOMAIN].items(): - name = cfg.get(CONF_NAME) - options = cfg.get(CONF_OPTIONS) - initial = cfg.get(CONF_INITIAL) - icon = cfg.get(CONF_ICON) - entities.append(InputSelect(object_id, name, initial, options, icon)) + async def reload_service_handler(service_call): + """Remove all entities and load new ones from config.""" + conf = await component.async_prepare_reload() + if conf is None: + return + new_entities = await _async_process_config(conf) + if new_entities: + await component.async_add_entities(new_entities) - if not entities: - return False + homeassistant.helpers.service.async_register_admin_service( + hass, + DOMAIN, + SERVICE_RELOAD, + reload_service_handler, + schema=RELOAD_SERVICE_SCHEMA, + ) component.async_register_entity_service( SERVICE_SELECT_OPTION, @@ -103,10 +112,25 @@ async def async_setup(hass, config): "async_set_options", ) - await component.async_add_entities(entities) + if entities: + await component.async_add_entities(entities) return True +async def _async_process_config(config): + """Process config and create list of entities.""" + entities = [] + + for object_id, cfg in config[DOMAIN].items(): + name = cfg.get(CONF_NAME) + options = cfg.get(CONF_OPTIONS) + initial = cfg.get(CONF_INITIAL) + icon = cfg.get(CONF_ICON) + entities.append(InputSelect(object_id, name, initial, options, icon)) + + return entities + + class InputSelect(RestoreEntity): """Representation of a select input.""" diff --git a/homeassistant/components/input_select/services.yaml b/homeassistant/components/input_select/services.yaml index 8084e56b731..2cce496d0b6 100644 --- a/homeassistant/components/input_select/services.yaml +++ b/homeassistant/components/input_select/services.yaml @@ -20,3 +20,5 @@ set_options: for., example: input_select.my_select} options: {description: Options for the input select entity., example: '["Item A", "Item B", "Item C"]'} +reload: + description: Reload the input_select configuration. diff --git a/tests/components/input_select/test_init.py b/tests/components/input_select/test_init.py index 6c5d8501239..51f0b24bc8a 100644 --- a/tests/components/input_select/test_init.py +++ b/tests/components/input_select/test_init.py @@ -1,6 +1,9 @@ """The tests for the Input select component.""" # pylint: disable=protected-access import asyncio +from unittest.mock import patch + +import pytest from homeassistant.components.input_select import ( ATTR_OPTION, @@ -11,8 +14,14 @@ from homeassistant.components.input_select import ( SERVICE_SELECT_PREVIOUS, SERVICE_SET_OPTIONS, ) -from homeassistant.const import ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, ATTR_ICON +from homeassistant.const import ( + ATTR_ENTITY_ID, + ATTR_FRIENDLY_NAME, + ATTR_ICON, + SERVICE_RELOAD, +) from homeassistant.core import Context, State +from homeassistant.exceptions import Unauthorized from homeassistant.loader import bind_hass from homeassistant.setup import async_setup_component @@ -322,3 +331,81 @@ async def test_input_select_context(hass, hass_admin_user): assert state2 is not None assert state.state != state2.state assert state2.context.user_id == hass_admin_user.id + + +async def test_reload(hass, hass_admin_user, hass_read_only_user): + """Test reload service.""" + count_start = len(hass.states.async_entity_ids()) + + assert await async_setup_component( + hass, + DOMAIN, + { + DOMAIN: { + "test_1": { + "options": ["first option", "middle option", "last option"], + "initial": "middle option", + }, + "test_2": { + "options": ["an option", "not an option"], + "initial": "an option", + }, + } + }, + ) + + assert count_start + 2 == len(hass.states.async_entity_ids()) + + state_1 = hass.states.get("input_select.test_1") + state_2 = hass.states.get("input_select.test_2") + state_3 = hass.states.get("input_select.test_3") + + assert state_1 is not None + assert state_2 is not None + assert state_3 is None + assert "middle option" == state_1.state + assert "an option" == state_2.state + + with patch( + "homeassistant.config.load_yaml_config_file", + autospec=True, + return_value={ + DOMAIN: { + "test_2": { + "options": ["an option", "reloaded option"], + "initial": "reloaded option", + }, + "test_3": { + "options": ["new option", "newer option"], + "initial": "newer option", + }, + } + }, + ): + with patch("homeassistant.config.find_config_file", return_value=""): + with pytest.raises(Unauthorized): + await hass.services.async_call( + DOMAIN, + SERVICE_RELOAD, + blocking=True, + context=Context(user_id=hass_read_only_user.id), + ) + await hass.services.async_call( + DOMAIN, + SERVICE_RELOAD, + blocking=True, + context=Context(user_id=hass_admin_user.id), + ) + await hass.async_block_till_done() + + assert count_start + 2 == len(hass.states.async_entity_ids()) + + state_1 = hass.states.get("input_select.test_1") + state_2 = hass.states.get("input_select.test_2") + state_3 = hass.states.get("input_select.test_3") + + assert state_1 is None + assert state_2 is not None + assert state_3 is not None + assert "reloaded option" == state_2.state + assert "newer option" == state_3.state From 38a6fffecb0009bd72b44830256032ae219e1c2d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 9 Dec 2019 22:43:38 +0100 Subject: [PATCH 245/677] Add JSON files validation to hassfest (#29799) --- script/hassfest/__main__.py | 22 ++++++++++++++++++++-- script/hassfest/json.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 script/hassfest/json.py diff --git a/script/hassfest/__main__.py b/script/hassfest/__main__.py index 78b46b8db57..99e32e57f43 100644 --- a/script/hassfest/__main__.py +++ b/script/hassfest/__main__.py @@ -2,10 +2,28 @@ import pathlib import sys -from . import codeowners, config_flow, dependencies, manifest, services, ssdp, zeroconf +from . import ( + codeowners, + config_flow, + dependencies, + json, + manifest, + services, + ssdp, + zeroconf, +) from .model import Config, Integration -PLUGINS = [codeowners, config_flow, dependencies, manifest, services, ssdp, zeroconf] +PLUGINS = [ + json, + codeowners, + config_flow, + dependencies, + manifest, + services, + ssdp, + zeroconf, +] def get_config() -> Config: diff --git a/script/hassfest/json.py b/script/hassfest/json.py new file mode 100644 index 00000000000..73b6c372b4f --- /dev/null +++ b/script/hassfest/json.py @@ -0,0 +1,29 @@ +"""Validate integration JSON files.""" +import json +from typing import Dict + +from .model import Integration + + +def validate_json_files(integration: Integration): + """Validate JSON files for integration.""" + for json_file in integration.path.glob("**/*.json"): + if not json_file.is_file(): + continue + + try: + json.loads(json_file.read_text()) + except json.JSONDecodeError: + relative_path = json_file.relative_to(integration.path) + integration.add_error("json", f"Invalid JSON file {relative_path}") + + return + + +def validate(integrations: Dict[str, Integration], config): + """Handle JSON files inside integrations.""" + for integration in integrations.values(): + if not integration.manifest: + continue + + validate_json_files(integration) From 12f273eb11fc4bb282f158cf7dfe721eed9eefdd Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Tue, 10 Dec 2019 00:32:16 +0000 Subject: [PATCH 246/677] [ci skip] Translation update --- .../alarm_control_panel/.translations/es.json | 7 ++++ .../.translations/pt-BR.json | 2 + .../almond/.translations/pt-BR.json | 9 +++++ .../components/cover/.translations/ru.json | 2 + .../components/demo/.translations/pt-BR.json | 5 +++ .../components/elgato/.translations/es.json | 27 +++++++++++++ .../components/elgato/.translations/it.json | 27 +++++++++++++ .../components/elgato/.translations/no.json | 20 +++++++++- .../components/elgato/.translations/pl.json | 27 +++++++++++++ .../components/elgato/.translations/ru.json | 27 +++++++++++++ .../components/fan/.translations/pt-BR.json | 8 ++++ .../geonetnz_volcano/.translations/pt-BR.json | 14 +++++++ .../components/hangouts/.translations/ru.json | 2 + .../components/icloud/.translations/es.json | 38 +++++++++++++++++++ .../components/plex/.translations/es.json | 1 + .../starline/.translations/pt-BR.json | 31 +++++++++++++++ .../tellduslive/.translations/ru.json | 1 + .../components/unifi/.translations/ru.json | 8 ++++ .../components/upnp/.translations/ru.json | 6 +++ .../components/vacuum/.translations/es.json | 2 +- .../components/zha/.translations/ru.json | 2 +- 21 files changed, 262 insertions(+), 4 deletions(-) create mode 100644 homeassistant/components/almond/.translations/pt-BR.json create mode 100644 homeassistant/components/demo/.translations/pt-BR.json create mode 100644 homeassistant/components/elgato/.translations/es.json create mode 100644 homeassistant/components/elgato/.translations/it.json create mode 100644 homeassistant/components/elgato/.translations/pl.json create mode 100644 homeassistant/components/elgato/.translations/ru.json create mode 100644 homeassistant/components/fan/.translations/pt-BR.json create mode 100644 homeassistant/components/geonetnz_volcano/.translations/pt-BR.json create mode 100644 homeassistant/components/icloud/.translations/es.json create mode 100644 homeassistant/components/starline/.translations/pt-BR.json diff --git a/homeassistant/components/alarm_control_panel/.translations/es.json b/homeassistant/components/alarm_control_panel/.translations/es.json index 273efeeaba5..8200755de0f 100644 --- a/homeassistant/components/alarm_control_panel/.translations/es.json +++ b/homeassistant/components/alarm_control_panel/.translations/es.json @@ -6,6 +6,13 @@ "arm_night": "Armar {entity_name} por la noche", "disarm": "Desarmar {entity_name}", "trigger": "Lanzar {entity_name}" + }, + "trigger_type": { + "armed_away": "{entity_name} armado fuera", + "armed_home": "{entity_name} armado en casa", + "armed_night": "{entity_name} armado modo noche", + "disarmed": "{entity_name} desarmado", + "triggered": "{entity_name} activado" } } } \ No newline at end of file diff --git a/homeassistant/components/alarm_control_panel/.translations/pt-BR.json b/homeassistant/components/alarm_control_panel/.translations/pt-BR.json index f72ae0e820e..032756f48f2 100644 --- a/homeassistant/components/alarm_control_panel/.translations/pt-BR.json +++ b/homeassistant/components/alarm_control_panel/.translations/pt-BR.json @@ -8,6 +8,8 @@ "trigger": "Disparar {entidade_nome}" }, "trigger_type": { + "armed_away": "{entity_name} armado modo longe", + "armed_home": "{entidade_nome} armadado modo casa ", "armed_night": "{entity_name} armadado para noite", "disarmed": "{entity_name} desarmado", "triggered": "{entity_name} acionado" diff --git a/homeassistant/components/almond/.translations/pt-BR.json b/homeassistant/components/almond/.translations/pt-BR.json new file mode 100644 index 00000000000..94dfbefb86a --- /dev/null +++ b/homeassistant/components/almond/.translations/pt-BR.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "pick_implementation": { + "title": "Escolha o m\u00e9todo de autentica\u00e7\u00e3o" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cover/.translations/ru.json b/homeassistant/components/cover/.translations/ru.json index 043e5a42d2a..ebe81486cf5 100644 --- a/homeassistant/components/cover/.translations/ru.json +++ b/homeassistant/components/cover/.translations/ru.json @@ -9,7 +9,9 @@ "is_tilt_position": "{entity_name} \u0432 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0438 \u043d\u0430\u043a\u043b\u043e\u043d\u0430" }, "trigger_type": { + "closed": "{entity_name} \u0437\u0430\u043a\u0440\u044b\u0442\u043e", "closing": "{entity_name} \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u0442\u0441\u044f", + "opened": "{entity_name} \u043e\u0442\u043a\u0440\u044b\u0442\u043e", "opening": "{entity_name} \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442\u0441\u044f", "position": "{entity_name} \u0438\u0437\u043c\u0435\u043d\u044f\u0435\u0442 \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435", "tilt_position": "{entity_name} \u0438\u0437\u043c\u0435\u043d\u044f\u0435\u0442 \u043d\u0430\u043a\u043b\u043e\u043d" diff --git a/homeassistant/components/demo/.translations/pt-BR.json b/homeassistant/components/demo/.translations/pt-BR.json new file mode 100644 index 00000000000..8183f28aed3 --- /dev/null +++ b/homeassistant/components/demo/.translations/pt-BR.json @@ -0,0 +1,5 @@ +{ + "config": { + "title": "Demonstra\u00e7\u00e3o" + } +} \ No newline at end of file diff --git a/homeassistant/components/elgato/.translations/es.json b/homeassistant/components/elgato/.translations/es.json new file mode 100644 index 00000000000..2e689b5e064 --- /dev/null +++ b/homeassistant/components/elgato/.translations/es.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Este dispositivo Elgato Key Light ya est\u00e1 configurado.", + "connection_error": "No se pudo conectar al dispositivo Elgato Key Light." + }, + "error": { + "connection_error": "No se pudo conectar al dispositivo Elgato Key Light." + }, + "flow_title": "Elgato Key Light: {serial_number}", + "step": { + "user": { + "data": { + "host": "Host o direcci\u00f3n IP", + "port": "N\u00famero de puerto" + }, + "description": "Configura tu Elgato Key Light para integrarlo con Home Assistant.", + "title": "Conecte su Elgato Key Light" + }, + "zeroconf_confirm": { + "description": "\u00bfDesea agregar Elgato Key Light con el n\u00famero de serie `{serial_number}` a Home Assistant?", + "title": "Descubierto dispositivo Elgato Key Light" + } + }, + "title": "Elgato Key Light" + } +} \ No newline at end of file diff --git a/homeassistant/components/elgato/.translations/it.json b/homeassistant/components/elgato/.translations/it.json new file mode 100644 index 00000000000..81e363aa01b --- /dev/null +++ b/homeassistant/components/elgato/.translations/it.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Questo dispositivo Elgato Key Light \u00e8 gi\u00e0 configurato.", + "connection_error": "Impossibile connettersi al dispositivo Elgato Key Light." + }, + "error": { + "connection_error": "Impossibile connettersi al dispositivo Elgato Key Light." + }, + "flow_title": "Elgato Key Light: {serial_number}", + "step": { + "user": { + "data": { + "host": "Host o indirizzo IP", + "port": "Numero porta" + }, + "description": "Configura Elgato Key Light per l'integrazione con Home Assistant.", + "title": "Collega il tuo Elgato Key Light" + }, + "zeroconf_confirm": { + "description": "Vuoi aggiungere il dispositivo Elgato Key Light con il numero di serie {serial_number} a Home Assistant?", + "title": "Dispositivo Elgato Key Light rilevato" + } + }, + "title": "Elgato Key Light" + } +} \ No newline at end of file diff --git a/homeassistant/components/elgato/.translations/no.json b/homeassistant/components/elgato/.translations/no.json index df7d6db2621..8642ae75025 100644 --- a/homeassistant/components/elgato/.translations/no.json +++ b/homeassistant/components/elgato/.translations/no.json @@ -1,11 +1,27 @@ { "config": { + "abort": { + "already_configured": "Denne Elgato Key Light-enheten er allerede konfigurert.", + "connection_error": "Kunne ikke koble til Elgato Key Light-enheten." + }, + "error": { + "connection_error": "Kunne ikke koble til Elgato Key Light-enheten." + }, + "flow_title": "Elgato Key Light: {serial_number}", "step": { "user": { "data": { + "host": "Vert eller IP-adresse", "port": "Portnummer" - } + }, + "description": "Sett opp Elgato Key Light for \u00e5 integrere med Home Assistant.", + "title": "Linken ditt Elgato Key Light" + }, + "zeroconf_confirm": { + "description": "Vil du legge Elgato Key Light med serienummer ` {serial_number} til Home Assistant?", + "title": "Oppdaget Elgato Key Light-enheten" } - } + }, + "title": "Elgato Key Light" } } \ No newline at end of file diff --git a/homeassistant/components/elgato/.translations/pl.json b/homeassistant/components/elgato/.translations/pl.json new file mode 100644 index 00000000000..97e10b451f0 --- /dev/null +++ b/homeassistant/components/elgato/.translations/pl.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "To urz\u0105dzenie Elgato Key Light jest ju\u017c skonfigurowane.", + "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia z urz\u0105dzeniem Elgato Key Light." + }, + "error": { + "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia z urz\u0105dzeniem Elgato Key Light." + }, + "flow_title": "Elgato Key Light: {serial_number}", + "step": { + "user": { + "data": { + "host": "Nazwa hosta lub adres IP", + "port": "Port" + }, + "description": "Konfiguracja Elgato Key Light w celu integracji z Home Assistant'em.", + "title": "Po\u0142\u0105cz swoje Elgato Key Light" + }, + "zeroconf_confirm": { + "description": "Czy chcesz doda\u0107 urz\u0105dzenie Elgato Key Light o numerze seryjnym `{serial_number}` do Home Assistant'a?", + "title": "Wykryto urz\u0105dzenie Elgato Key Light" + } + }, + "title": "Elgato Key Light" + } +} \ No newline at end of file diff --git a/homeassistant/components/elgato/.translations/ru.json b/homeassistant/components/elgato/.translations/ru.json new file mode 100644 index 00000000000..3eeeed631c6 --- /dev/null +++ b/homeassistant/components/elgato/.translations/ru.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e Elgato Key Light \u0443\u0436\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u043e.", + "connection_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443 Elgato Key Light." + }, + "error": { + "connection_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443 Elgato Key Light." + }, + "flow_title": "Elgato Key Light: {serial_number}", + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442 \u0438\u043b\u0438 IP-\u0430\u0434\u0440\u0435\u0441", + "port": "\u041d\u043e\u043c\u0435\u0440 \u043f\u043e\u0440\u0442\u0430" + }, + "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Elgato Key Light \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 Home Assistant.", + "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0432\u0430\u0448 Elgato Key Light" + }, + "zeroconf_confirm": { + "description": "\u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c Elgato Key Light \u0441 \u0441\u0435\u0440\u0438\u0439\u043d\u044b\u043c \u043d\u043e\u043c\u0435\u0440\u043e\u043c ` {serial_number} ` \u0432 Home Assistant?", + "title": "\u041d\u0430\u0439\u0434\u0435\u043d\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e Elgado Key Light" + } + }, + "title": "\u041e\u0441\u0432\u0435\u0442\u0438\u0442\u0435\u043b\u044c Elgado Key Light" + } +} \ No newline at end of file diff --git a/homeassistant/components/fan/.translations/pt-BR.json b/homeassistant/components/fan/.translations/pt-BR.json new file mode 100644 index 00000000000..86b10b0f909 --- /dev/null +++ b/homeassistant/components/fan/.translations/pt-BR.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "condtion_type": { + "is_off": "{entity_name} est\u00e1 desligado", + "is_on": "{entity_name} est\u00e1 ligado" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geonetnz_volcano/.translations/pt-BR.json b/homeassistant/components/geonetnz_volcano/.translations/pt-BR.json new file mode 100644 index 00000000000..b1629599926 --- /dev/null +++ b/homeassistant/components/geonetnz_volcano/.translations/pt-BR.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "identifier_exists": "Local j\u00e1 registrado" + }, + "step": { + "user": { + "data": { + "radius": "Raio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hangouts/.translations/ru.json b/homeassistant/components/hangouts/.translations/ru.json index 15d90a672de..5bb98effb9f 100644 --- a/homeassistant/components/hangouts/.translations/ru.json +++ b/homeassistant/components/hangouts/.translations/ru.json @@ -14,6 +14,7 @@ "data": { "2fa": "\u041f\u0438\u043d-\u043a\u043e\u0434 \u0434\u043b\u044f \u0434\u0432\u0443\u0445\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u043e\u0439 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438" }, + "description": "\u043f\u0443\u0441\u0442\u043e", "title": "\u0414\u0432\u0443\u0445\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f" }, "user": { @@ -22,6 +23,7 @@ "email": "\u0410\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b", "password": "\u041f\u0430\u0440\u043e\u043b\u044c" }, + "description": "\u043f\u0443\u0441\u0442\u043e", "title": "Google Hangouts" } }, diff --git a/homeassistant/components/icloud/.translations/es.json b/homeassistant/components/icloud/.translations/es.json new file mode 100644 index 00000000000..13355fa2b8e --- /dev/null +++ b/homeassistant/components/icloud/.translations/es.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "username_exists": "Cuenta ya configurada" + }, + "error": { + "login": "Error de inicio de sesi\u00f3n: compruebe su direcci\u00f3n de correo electr\u00f3nico y contrase\u00f1a", + "send_verification_code": "Error al enviar el c\u00f3digo de verificaci\u00f3n", + "username_exists": "Cuenta ya configurada", + "validate_verification_code": "No se pudo verificar el c\u00f3digo de verificaci\u00f3n, elegir un dispositivo de confianza e iniciar la verificaci\u00f3n de nuevo" + }, + "step": { + "trusted_device": { + "data": { + "trusted_device": "Dispositivo de confianza" + }, + "description": "Seleccione su dispositivo de confianza", + "title": "Dispositivo de confianza iCloud" + }, + "user": { + "data": { + "password": "Contrase\u00f1a", + "username": "Correo electr\u00f3nico" + }, + "description": "Ingrese sus credenciales", + "title": "Credenciales iCloud" + }, + "verification_code": { + "data": { + "verification_code": "C\u00f3digo de verificaci\u00f3n" + }, + "description": "Por favor, introduzca el c\u00f3digo de verificaci\u00f3n que acaba de recibir de iCloud", + "title": "C\u00f3digo de verificaci\u00f3n de iCloud" + } + }, + "title": "iCloud de Apple" + } +} \ No newline at end of file diff --git a/homeassistant/components/plex/.translations/es.json b/homeassistant/components/plex/.translations/es.json index 261ca951490..53dd3228288 100644 --- a/homeassistant/components/plex/.translations/es.json +++ b/homeassistant/components/plex/.translations/es.json @@ -6,6 +6,7 @@ "already_in_progress": "Plex se est\u00e1 configurando", "discovery_no_file": "No se ha encontrado ning\u00fan archivo de configuraci\u00f3n antiguo", "invalid_import": "La configuraci\u00f3n importada no es v\u00e1lida", + "non-interactive": "Importaci\u00f3n no interactiva", "token_request_timeout": "Tiempo de espera agotado para la obtenci\u00f3n del token", "unknown": "Fall\u00f3 por razones desconocidas" }, diff --git a/homeassistant/components/starline/.translations/pt-BR.json b/homeassistant/components/starline/.translations/pt-BR.json new file mode 100644 index 00000000000..158c2b01cf9 --- /dev/null +++ b/homeassistant/components/starline/.translations/pt-BR.json @@ -0,0 +1,31 @@ +{ + "config": { + "error": { + "error_auth_mfa": "C\u00f3digo incorreto", + "error_auth_user": "Usu\u00e1rio ou senha incorretos" + }, + "step": { + "auth_app": { + "title": "Credenciais do aplicativo" + }, + "auth_captcha": { + "data": { + "captcha_code": "C\u00f3digo da imagem" + }, + "title": "Captcha" + }, + "auth_mfa": { + "data": { + "mfa_code": "C\u00f3digo SMS" + }, + "description": "Digite o c\u00f3digo enviado para o telefone {phone_number}", + "title": "Autoriza\u00e7\u00e3o de dois fatores" + }, + "auth_user": { + "data": { + "password": "Senha" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tellduslive/.translations/ru.json b/homeassistant/components/tellduslive/.translations/ru.json index 41dc39146e8..fa5b7e2d319 100644 --- a/homeassistant/components/tellduslive/.translations/ru.json +++ b/homeassistant/components/tellduslive/.translations/ru.json @@ -18,6 +18,7 @@ "data": { "host": "\u0425\u043e\u0441\u0442" }, + "description": "\u043f\u0443\u0441\u0442\u043e", "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043a\u043e\u043d\u0435\u0447\u043d\u0443\u044e \u0442\u043e\u0447\u043a\u0443." } }, diff --git a/homeassistant/components/unifi/.translations/ru.json b/homeassistant/components/unifi/.translations/ru.json index b01cdb84fbf..3a67d483c0c 100644 --- a/homeassistant/components/unifi/.translations/ru.json +++ b/homeassistant/components/unifi/.translations/ru.json @@ -33,6 +33,14 @@ "track_wired_clients": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432 \u043f\u0440\u043e\u0432\u043e\u0434\u043d\u043e\u0439 \u0441\u0435\u0442\u0438" } }, + "init": { + "data": { + "few": "\u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e", + "many": "\u043c\u043d\u043e\u0433\u043e", + "one": "\u043e\u0434\u043d\u0438", + "other": "\u0434\u0440\u0443\u0433\u0438\u0435" + } + }, "statistics_sensors": { "data": { "allow_bandwidth_sensors": "\u0421\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0434\u0430\u0442\u0447\u0438\u043a\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u043e\u043b\u043e\u0441\u044b \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u0430\u043d\u0438\u044f \u0434\u043b\u044f \u0441\u0435\u0442\u0435\u0432\u044b\u0445 \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432" diff --git a/homeassistant/components/upnp/.translations/ru.json b/homeassistant/components/upnp/.translations/ru.json index 9599832799f..6dce1b3d76c 100644 --- a/homeassistant/components/upnp/.translations/ru.json +++ b/homeassistant/components/upnp/.translations/ru.json @@ -8,6 +8,12 @@ "no_sensors_or_port_mapping": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0434\u0430\u0442\u0447\u0438\u043a\u0438 \u0438\u043b\u0438 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u043e\u0440\u0442\u043e\u0432.", "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." }, + "error": { + "few": "\u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e", + "many": "\u043c\u043d\u043e\u0433\u043e", + "one": "\u043e\u0434\u0438\u043d", + "other": "\u0434\u0440\u0443\u0433\u0438\u0435" + }, "step": { "confirm": { "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c UPnP / IGD?", diff --git a/homeassistant/components/vacuum/.translations/es.json b/homeassistant/components/vacuum/.translations/es.json index 9ecf3ade99c..f99e94a40a3 100644 --- a/homeassistant/components/vacuum/.translations/es.json +++ b/homeassistant/components/vacuum/.translations/es.json @@ -6,7 +6,7 @@ }, "condtion_type": { "is_cleaning": "{entity_name} est\u00e1 limpiando", - "is_docked": "{entity_name} est\u00e1 acoplado" + "is_docked": "{entity_name} en la base" }, "trigger_type": { "cleaning": "{entity_name} empez\u00f3 a limpiar", diff --git a/homeassistant/components/zha/.translations/ru.json b/homeassistant/components/zha/.translations/ru.json index c0bc7c176a2..27682ebfd82 100644 --- a/homeassistant/components/zha/.translations/ru.json +++ b/homeassistant/components/zha/.translations/ru.json @@ -19,7 +19,7 @@ }, "device_automation": { "action_type": { - "squawk": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0441\u0438\u0440\u0435\u043d\u0443", + "squawk": "\u0422\u0440\u0430\u043d\u0441\u043f\u043e\u043d\u0434\u0435\u0440", "warn": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043e\u043f\u043e\u0432\u0435\u0449\u0435\u043d\u0438\u0435" }, "trigger_subtype": { From 315d0064fe0deaa35a459979f668fee9f22b9f91 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Tue, 10 Dec 2019 00:00:04 -0500 Subject: [PATCH 247/677] Fix zha circular import (#29802) * Refactor zha.core.helpers. * Make zha isort-able. * Const import reorg. * Keep DATA_ZHA config key on entry unload. * Cleanup ZHA config flow. * isort. * Add test. --- homeassistant/components/zha/__init__.py | 7 +-- homeassistant/components/zha/api.py | 31 ++-------- homeassistant/components/zha/config_flow.py | 23 +++++++- homeassistant/components/zha/core/gateway.py | 2 +- homeassistant/components/zha/core/helpers.py | 62 +++++++++----------- tests/components/zha/test_config_flow.py | 61 +++++++++++++++++-- 6 files changed, 113 insertions(+), 73 deletions(-) diff --git a/homeassistant/components/zha/__init__.py b/homeassistant/components/zha/__init__.py index a3c68ae3030..7303367d485 100644 --- a/homeassistant/components/zha/__init__.py +++ b/homeassistant/components/zha/__init__.py @@ -1,7 +1,4 @@ -"""Support for Zigbee Home Automation devices. - -isort:skip_file -""" +"""Support for Zigbee Home Automation devices.""" import logging @@ -11,7 +8,6 @@ from homeassistant import config_entries, const as ha_const import homeassistant.helpers.config_validation as cv from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE -from . import config_flow # noqa: F401 pylint: disable=unused-import from . import api from .core import ZHAGateway from .core.const import ( @@ -147,5 +143,4 @@ async def async_unload_entry(hass, config_entry): for component in COMPONENTS: await hass.config_entries.async_forward_entry_unload(config_entry, component) - del hass.data[DATA_ZHA] return True diff --git a/homeassistant/components/zha/api.py b/homeassistant/components/zha/api.py index e796c48c3f3..462afd777b9 100644 --- a/homeassistant/components/zha/api.py +++ b/homeassistant/components/zha/api.py @@ -50,7 +50,11 @@ from .core.const import ( WARNING_DEVICE_STROBE_HIGH, WARNING_DEVICE_STROBE_YES, ) -from .core.helpers import async_is_bindable_target, get_matched_clusters +from .core.helpers import ( + async_get_device_info, + async_is_bindable_target, + get_matched_clusters, +) _LOGGER = logging.getLogger(__name__) @@ -423,31 +427,6 @@ async def websocket_remove_group_members(hass, connection, msg): connection.send_result(msg[ID], ret_group) -@callback -def async_get_device_info(hass, device, ha_device_registry=None): - """Get ZHA device.""" - zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] - ret_device = {} - ret_device.update(device.device_info) - ret_device["entities"] = [ - { - "entity_id": entity_ref.reference_id, - ATTR_NAME: entity_ref.device_info[ATTR_NAME], - } - for entity_ref in zha_gateway.device_registry[device.ieee] - ] - - if ha_device_registry is not None: - reg_device = ha_device_registry.async_get_device( - {(DOMAIN, str(device.ieee))}, set() - ) - if reg_device is not None: - ret_device["user_given_name"] = reg_device.name_by_user - ret_device["device_reg_id"] = reg_device.id - ret_device["area_id"] = reg_device.area_id - return ret_device - - async def get_groups(hass,): """Get ZHA Groups.""" zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] diff --git a/homeassistant/components/zha/config_flow.py b/homeassistant/components/zha/config_flow.py index 474cb15b41a..5ee0d0ee9bb 100644 --- a/homeassistant/components/zha/config_flow.py +++ b/homeassistant/components/zha/config_flow.py @@ -1,4 +1,5 @@ """Config flow for ZHA.""" +import asyncio from collections import OrderedDict import os @@ -9,11 +10,14 @@ from homeassistant import config_entries from .core.const import ( CONF_RADIO_TYPE, CONF_USB_PATH, + CONTROLLER, + DEFAULT_BAUDRATE, DEFAULT_DATABASE_NAME, DOMAIN, + ZHA_GW_RADIO, RadioType, ) -from .core.helpers import check_zigpy_connection +from .core.registries import RADIO_TYPES @config_entries.HANDLERS.register(DOMAIN) @@ -57,3 +61,20 @@ class ZhaFlowHandler(config_entries.ConfigFlow): return self.async_create_entry( title=import_info[CONF_USB_PATH], data=import_info ) + + +async def check_zigpy_connection(usb_path, radio_type, database_path): + """Test zigpy radio connection.""" + try: + radio = RADIO_TYPES[radio_type][ZHA_GW_RADIO]() + controller_application = RADIO_TYPES[radio_type][CONTROLLER] + except KeyError: + return False + try: + await radio.connect(usb_path, DEFAULT_BAUDRATE) + controller = controller_application(radio, database_path) + await asyncio.wait_for(controller.startup(auto_form=True), timeout=30) + await controller.shutdown() + except Exception: # pylint: disable=broad-except + return False + return True diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py index ef81705ce47..72931c665ee 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -20,7 +20,6 @@ from homeassistant.helpers.device_registry import ( ) from homeassistant.helpers.dispatcher import async_dispatcher_send -from ..api import async_get_device_info from .const import ( ATTR_IEEE, ATTR_MANUFACTURER, @@ -65,6 +64,7 @@ from .const import ( ) from .device import DeviceStatus, ZHADevice from .discovery import async_dispatch_discovery_info, async_process_endpoint +from .helpers import async_get_device_info from .patches import apply_application_controller_patch from .registries import RADIO_TYPES from .store import async_get_registry diff --git a/homeassistant/components/zha/core/helpers.py b/homeassistant/components/zha/core/helpers.py index d3f06090dae..981a03fe7b5 100644 --- a/homeassistant/components/zha/core/helpers.py +++ b/homeassistant/components/zha/core/helpers.py @@ -4,29 +4,20 @@ Helpers for Zigbee Home Automation. For more details about this component, please refer to the documentation at https://home-assistant.io/integrations/zha/ """ -import asyncio import collections import logging -import bellows.ezsp -import bellows.zigbee.application import zigpy.types -import zigpy_deconz.api -import zigpy_deconz.zigbee.application -import zigpy_xbee.api -import zigpy_xbee.zigbee.application -import zigpy_zigate.api -import zigpy_zigate.zigbee.application from homeassistant.core import callback from .const import ( + ATTR_NAME, CLUSTER_TYPE_IN, CLUSTER_TYPE_OUT, DATA_ZHA, DATA_ZHA_GATEWAY, - DEFAULT_BAUDRATE, - RadioType, + DOMAIN, ) from .registries import BINDABLE_CLUSTERS @@ -56,30 +47,6 @@ async def safe_read( return {} -async def check_zigpy_connection(usb_path, radio_type, database_path): - """Test zigpy radio connection.""" - if radio_type == RadioType.ezsp.name: - radio = bellows.ezsp.EZSP() - ControllerApplication = bellows.zigbee.application.ControllerApplication - elif radio_type == RadioType.xbee.name: - radio = zigpy_xbee.api.XBee() - ControllerApplication = zigpy_xbee.zigbee.application.ControllerApplication - elif radio_type == RadioType.deconz.name: - radio = zigpy_deconz.api.Deconz() - ControllerApplication = zigpy_deconz.zigbee.application.ControllerApplication - elif radio_type == RadioType.zigate.name: - radio = zigpy_zigate.api.ZiGate() - ControllerApplication = zigpy_zigate.zigbee.application.ControllerApplication - try: - await radio.connect(usb_path, DEFAULT_BAUDRATE) - controller = ControllerApplication(radio, database_path) - await asyncio.wait_for(controller.startup(auto_form=True), timeout=30) - await controller.shutdown() - except Exception: # pylint: disable=broad-except - return False - return True - - def get_attr_id_by_name(cluster, attr_name): """Get the attribute id for a cluster attribute by its name.""" return next( @@ -164,3 +131,28 @@ class LogMixin: def error(self, msg, *args): """Error level log.""" return self.log(logging.ERROR, msg, *args) + + +@callback +def async_get_device_info(hass, device, ha_device_registry=None): + """Get ZHA device.""" + zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] + ret_device = {} + ret_device.update(device.device_info) + ret_device["entities"] = [ + { + "entity_id": entity_ref.reference_id, + ATTR_NAME: entity_ref.device_info[ATTR_NAME], + } + for entity_ref in zha_gateway.device_registry[device.ieee] + ] + + if ha_device_registry is not None: + reg_device = ha_device_registry.async_get_device( + {(DOMAIN, str(device.ieee))}, set() + ) + if reg_device is not None: + ret_device["user_given_name"] = reg_device.name_by_user + ret_device["device_reg_id"] = reg_device.id + ret_device["area_id"] = reg_device.area_id + return ret_device diff --git a/tests/components/zha/test_config_flow.py b/tests/components/zha/test_config_flow.py index 5e6bf51afd6..fdff064a1c5 100644 --- a/tests/components/zha/test_config_flow.py +++ b/tests/components/zha/test_config_flow.py @@ -1,8 +1,11 @@ """Tests for ZHA config flow.""" -from asynctest import patch +from unittest import mock + +import asynctest from homeassistant.components.zha import config_flow -from homeassistant.components.zha.core.const import DOMAIN +from homeassistant.components.zha.core.const import CONTROLLER, DOMAIN, ZHA_GW_RADIO +import homeassistant.components.zha.core.registries from tests.common import MockConfigEntry @@ -12,7 +15,7 @@ async def test_user_flow(hass): flow = config_flow.ZhaFlowHandler() flow.hass = hass - with patch( + with asynctest.patch( "homeassistant.components.zha.config_flow" ".check_zigpy_connection", return_value=False, ): @@ -22,7 +25,7 @@ async def test_user_flow(hass): assert result["errors"] == {"base": "cannot_connect"} - with patch( + with asynctest.patch( "homeassistant.components.zha.config_flow" ".check_zigpy_connection", return_value=True, ): @@ -71,3 +74,53 @@ async def test_import_flow_existing_config_entry(hass): ) assert result["type"] == "abort" + + +async def test_check_zigpy_connection(): + """Test config flow validator.""" + + mock_radio = asynctest.MagicMock() + mock_radio.connect = asynctest.CoroutineMock() + radio_cls = asynctest.MagicMock(return_value=mock_radio) + + bad_radio = asynctest.MagicMock() + bad_radio.connect = asynctest.CoroutineMock(side_effect=Exception) + bad_radio_cls = asynctest.MagicMock(return_value=bad_radio) + + mock_ctrl = asynctest.MagicMock() + mock_ctrl.startup = asynctest.CoroutineMock() + mock_ctrl.shutdown = asynctest.CoroutineMock() + ctrl_cls = asynctest.MagicMock(return_value=mock_ctrl) + new_radios = { + mock.sentinel.radio: {ZHA_GW_RADIO: radio_cls, CONTROLLER: ctrl_cls}, + mock.sentinel.bad_radio: {ZHA_GW_RADIO: bad_radio_cls, CONTROLLER: ctrl_cls}, + } + + with mock.patch.dict( + homeassistant.components.zha.core.registries.RADIO_TYPES, new_radios, clear=True + ): + assert not await config_flow.check_zigpy_connection( + mock.sentinel.usb_path, mock.sentinel.unk_radio, mock.sentinel.zigbee_db + ) + assert mock_radio.connect.call_count == 0 + assert bad_radio.connect.call_count == 0 + assert mock_ctrl.startup.call_count == 0 + assert mock_ctrl.shutdown.call_count == 0 + + # unsuccessful radio connect + assert not await config_flow.check_zigpy_connection( + mock.sentinel.usb_path, mock.sentinel.bad_radio, mock.sentinel.zigbee_db + ) + assert mock_radio.connect.call_count == 0 + assert bad_radio.connect.call_count == 1 + assert mock_ctrl.startup.call_count == 0 + assert mock_ctrl.shutdown.call_count == 0 + + # successful radio connect + assert await config_flow.check_zigpy_connection( + mock.sentinel.usb_path, mock.sentinel.radio, mock.sentinel.zigbee_db + ) + assert mock_radio.connect.call_count == 1 + assert bad_radio.connect.call_count == 1 + assert mock_ctrl.startup.call_count == 1 + assert mock_ctrl.shutdown.call_count == 1 From 9049e090f96f6d18bf6a84657d0696b58045d0a0 Mon Sep 17 00:00:00 2001 From: "Brett T. Warden" Date: Tue, 10 Dec 2019 00:20:52 -0800 Subject: [PATCH 248/677] Bump Roku to 4.0.0 (#29809) --- homeassistant/components/roku/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/roku/manifest.json b/homeassistant/components/roku/manifest.json index f2639b31d15..1b5f07eb87a 100644 --- a/homeassistant/components/roku/manifest.json +++ b/homeassistant/components/roku/manifest.json @@ -3,7 +3,7 @@ "name": "Roku", "documentation": "https://www.home-assistant.io/integrations/roku", "requirements": [ - "roku==3.1" + "roku==4.0.0" ], "dependencies": [], "codeowners": [] diff --git a/requirements_all.txt b/requirements_all.txt index e5af926a51e..5f6e9ac78aa 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1751,7 +1751,7 @@ rjpl==0.3.5 rocketchat-API==0.6.1 # homeassistant.components.roku -roku==3.1 +roku==4.0.0 # homeassistant.components.roomba roombapy==1.4.2 From 4f1f4a1b4ff056cc9595ce6f7fb801d2a7b35b00 Mon Sep 17 00:00:00 2001 From: Ian Date: Tue, 10 Dec 2019 00:22:13 -0800 Subject: [PATCH 249/677] Nextbus: Sort results for upcoming times (#29811) Sort upcoming times across all direction's for a particular route and stop. --- homeassistant/components/nextbus/sensor.py | 4 +++- tests/components/nextbus/test_sensor.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/nextbus/sensor.py b/homeassistant/components/nextbus/sensor.py index 983c2272adc..5909804ebd1 100644 --- a/homeassistant/components/nextbus/sensor.py +++ b/homeassistant/components/nextbus/sensor.py @@ -225,7 +225,9 @@ class NextBusDepartureSensor(Entity): return # Generate list of upcoming times - self._attributes["upcoming"] = ", ".join(p["minutes"] for p in predictions) + self._attributes["upcoming"] = ", ".join( + sorted(p["minutes"] for p in predictions) + ) latest_prediction = maybe_first(predictions) self._state = utc_from_timestamp( diff --git a/tests/components/nextbus/test_sensor.py b/tests/components/nextbus/test_sensor.py index bc74ebcbe1e..bee9db445e2 100644 --- a/tests/components/nextbus/test_sensor.py +++ b/tests/components/nextbus/test_sensor.py @@ -206,7 +206,7 @@ async def test_direction_list( }, { "title": "Outbound 2", - "prediction": {"minutes": "4", "epochTime": "1553807374000"}, + "prediction": {"minutes": "0", "epochTime": "1553807374000"}, }, ], } @@ -221,7 +221,7 @@ async def test_direction_list( assert state.attributes["route"] == VALID_ROUTE_TITLE assert state.attributes["stop"] == VALID_STOP_TITLE assert state.attributes["direction"] == "Outbound, Outbound 2" - assert state.attributes["upcoming"] == "1, 2, 3, 4" + assert state.attributes["upcoming"] == "0, 1, 2, 3" async def test_custom_name( From 0ed6a434f8a68093597dc9c3bda42400c595a754 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 10 Dec 2019 09:22:37 +0100 Subject: [PATCH 250/677] Adjusts repository README (#29805) --- README.rst | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/README.rst b/README.rst index ae9531456fd..0de30d43c65 100644 --- a/README.rst +++ b/README.rst @@ -1,14 +1,7 @@ Home Assistant |Chat Status| ================================================================================= -Home Assistant is a home automation platform running on Python 3. It is able to track and control all devices at home and offer a platform for automating control. - -To get started: - -.. code:: bash - - python3 -m pip install homeassistant - hass --open-ui +Open source home automation that puts local control and privacy first. Powered by a worldwide community of tinkerers and DIY enthusiasts. Perfect to run on a Raspberry Pi or a local server. Check out `home-assistant.io `__ for `a demo `__, `installation instructions `__, From 27244e29c40bfa2093028199e7a33af4a7068053 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 10 Dec 2019 09:24:49 +0100 Subject: [PATCH 251/677] Install discovery requirements if used (#29795) * Install discovery requirements if used * Update loader.py * Fix types --- .../components/deconz/config_flow.py | 5 +- homeassistant/components/hue/config_flow.py | 4 +- homeassistant/loader.py | 41 +++++++++++--- homeassistant/requirements.py | 18 +++++- script/hassfest/dependencies.py | 10 +++- tests/test_requirements.py | 55 +++++++++++++++++-- 6 files changed, 108 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/deconz/config_flow.py b/homeassistant/components/deconz/config_flow.py index b757f1f4d03..8c8ba318e83 100644 --- a/homeassistant/components/deconz/config_flow.py +++ b/homeassistant/components/deconz/config_flow.py @@ -7,6 +7,7 @@ from pydeconz.utils import async_discovery, async_get_api_key, async_get_gateway import voluptuous as vol from homeassistant import config_entries +from homeassistant.components.ssdp import ATTR_MANUFACTURERURL, ATTR_SERIAL from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT from homeassistant.core import callback from homeassistant.helpers import aiohttp_client @@ -169,10 +170,6 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_ssdp(self, discovery_info): """Handle a discovered deCONZ bridge.""" - # Import it here, because only now do we know ssdp integration loaded. - # pylint: disable=import-outside-toplevel - from homeassistant.components.ssdp import ATTR_MANUFACTURERURL, ATTR_SERIAL - if discovery_info[ATTR_MANUFACTURERURL] != DECONZ_MANUFACTURERURL: return self.async_abort(reason="not_deconz_bridge") diff --git a/homeassistant/components/hue/config_flow.py b/homeassistant/components/hue/config_flow.py index 84b435d02ed..0423dc6fc2b 100644 --- a/homeassistant/components/hue/config_flow.py +++ b/homeassistant/components/hue/config_flow.py @@ -8,6 +8,7 @@ import async_timeout import voluptuous as vol from homeassistant import config_entries +from homeassistant.components.ssdp import ATTR_MANUFACTURERURL, ATTR_NAME from homeassistant.core import callback from homeassistant.helpers import aiohttp_client @@ -134,9 +135,6 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): This flow is triggered by the SSDP component. It will check if the host is already configured and delegate to the import step if not. """ - # pylint: disable=import-outside-toplevel - from homeassistant.components.ssdp import ATTR_MANUFACTURERURL, ATTR_NAME - if discovery_info[ATTR_MANUFACTURERURL] != HUE_MANUFACTURERURL: return self.async_abort(reason="not_hue_bridge") diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 0e1ee8ae756..9e8ea9fc7ca 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -195,22 +195,45 @@ class Integration: hass: "HomeAssistant", pkg_path: str, file_path: pathlib.Path, - manifest: Dict, + manifest: Dict[str, Any], ): """Initialize an integration.""" self.hass = hass self.pkg_path = pkg_path self.file_path = file_path - self.name: str = manifest["name"] - self.domain: str = manifest["domain"] - self.dependencies: List[str] = manifest["dependencies"] - self.after_dependencies: Optional[List[str]] = manifest.get( - "after_dependencies" - ) - self.requirements: List[str] = manifest["requirements"] - self.config_flow: bool = manifest.get("config_flow", False) + self.manifest = manifest _LOGGER.info("Loaded %s from %s", self.domain, pkg_path) + @property + def name(self) -> str: + """Return name.""" + return cast(str, self.manifest["name"]) + + @property + def domain(self) -> str: + """Return domain.""" + return cast(str, self.manifest["domain"]) + + @property + def dependencies(self) -> List[str]: + """Return dependencies.""" + return cast(List[str], self.manifest.get("dependencies", [])) + + @property + def after_dependencies(self) -> List[str]: + """Return after_dependencies.""" + return cast(List[str], self.manifest.get("after_dependencies", [])) + + @property + def requirements(self) -> List[str]: + """Return requirements.""" + return cast(List[str], self.manifest.get("requirements", [])) + + @property + def config_flow(self) -> bool: + """Return config_flow.""" + return cast(bool, self.manifest.get("config_flow", False)) + @property def is_built_in(self) -> bool: """Test if package is a built-in integration.""" diff --git a/homeassistant/requirements.py b/homeassistant/requirements.py index bd52253cdb1..670f3af7dcc 100644 --- a/homeassistant/requirements.py +++ b/homeassistant/requirements.py @@ -3,7 +3,7 @@ import asyncio import logging import os from pathlib import Path -from typing import Any, Dict, List, Optional, Set +from typing import Any, Dict, List, Optional, Set, Iterable from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError @@ -15,6 +15,10 @@ DATA_PKG_CACHE = "pkg_cache" CONSTRAINT_FILE = "package_constraints.txt" PROGRESS_FILE = ".pip_progress" _LOGGER = logging.getLogger(__name__) +DISCOVERY_INTEGRATIONS: Dict[str, Iterable[str]] = { + "ssdp": ("ssdp",), + "zeroconf": ("zeroconf", "homekit"), +} class RequirementsNotFound(HomeAssistantError): @@ -30,7 +34,7 @@ class RequirementsNotFound(HomeAssistantError): async def async_get_integration_with_requirements( hass: HomeAssistant, domain: str, done: Set[str] = None ) -> Integration: - """Get an integration with installed requirements. + """Get an integration with all requirements installed, including the dependencies. This can raise IntegrationNotFound if manifest or integration is invalid, RequirementNotFound if there was some type of @@ -53,10 +57,18 @@ async def async_get_integration_with_requirements( deps_to_check = [ dep - for dep in integration.dependencies + (integration.after_dependencies or []) + for dep in integration.dependencies + integration.after_dependencies if dep not in done ] + for check_domain, to_check in DISCOVERY_INTEGRATIONS.items(): + if ( + check_domain not in done + and check_domain not in deps_to_check + and any(check in integration.manifest for check in to_check) + ): + deps_to_check.append(check_domain) + if deps_to_check: await asyncio.gather( *[ diff --git a/script/hassfest/dependencies.py b/script/hassfest/dependencies.py index 71936411b75..b6f277438b4 100644 --- a/script/hassfest/dependencies.py +++ b/script/hassfest/dependencies.py @@ -3,6 +3,8 @@ import pathlib import re from typing import Dict, Set +from homeassistant.requirements import DISCOVERY_INTEGRATIONS + from .model import Integration @@ -49,7 +51,6 @@ ALLOWED_USED_COMPONENTS = { "system_log", "person", # Discovery - "ssdp", "discovery", # Other "mjpeg", # base class, has no reqs or component to load. @@ -93,6 +94,13 @@ def validate_dependencies(integration: Integration): referenced -= set(integration.manifest["dependencies"]) referenced -= set(integration.manifest.get("after_dependencies", [])) + # Discovery requirements are ok if referenced in manifest + for check_domain, to_check in DISCOVERY_INTEGRATIONS.items(): + if check_domain in referenced and any( + check in integration.manifest for check in to_check + ): + referenced.remove(check_domain) + if referenced: for domain in sorted(referenced): if ( diff --git a/tests/test_requirements.py b/tests/test_requirements.py index b807188e8a5..87bbf38e465 100644 --- a/tests/test_requirements.py +++ b/tests/test_requirements.py @@ -2,10 +2,9 @@ import os from pathlib import Path from unittest.mock import call, patch +import pytest -from pytest import raises - -from homeassistant import setup +from homeassistant import setup, loader from homeassistant.requirements import ( CONSTRAINT_FILE, PROGRESS_FILE, @@ -15,7 +14,12 @@ from homeassistant.requirements import ( async_process_requirements, ) -from tests.common import MockModule, get_test_home_assistant, mock_integration +from tests.common import ( + MockModule, + get_test_home_assistant, + mock_coro, + mock_integration, +) def env_without_wheel_links(): @@ -104,7 +108,7 @@ async def test_install_missing_package(hass): with patch( "homeassistant.util.package.install_package", return_value=False ) as mock_inst: - with raises(RequirementsNotFound): + with pytest.raises(RequirementsNotFound): await async_process_requirements(hass, "test_component", ["hello==1.0.0"]) assert len(mock_inst.mock_calls) == 1 @@ -222,3 +226,44 @@ async def test_progress_lock(hass): _install(hass, "hello", kwargs) assert not progress_path.exists() + + +async def test_discovery_requirements_ssdp(hass): + """Test that we load discovery requirements.""" + hass.config.skip_pip = False + ssdp = await loader.async_get_integration(hass, "ssdp") + + mock_integration( + hass, MockModule("ssdp_comp", partial_manifest={"ssdp": [{"st": "roku:ecp"}]}) + ) + with patch( + "homeassistant.requirements.async_process_requirements", + side_effect=lambda _, _2, _3: mock_coro(), + ) as mock_process: + await async_get_integration_with_requirements(hass, "ssdp_comp") + + assert len(mock_process.mock_calls) == 1 + assert mock_process.mock_calls[0][1][2] == ssdp.requirements + + +@pytest.mark.parametrize( + "partial_manifest", + [{"zeroconf": ["_googlecast._tcp.local."]}, {"homekit": {"models": ["LIFX"]}}], +) +async def test_discovery_requirements_zeroconf(hass, partial_manifest): + """Test that we load discovery requirements.""" + hass.config.skip_pip = False + zeroconf = await loader.async_get_integration(hass, "zeroconf") + + mock_integration( + hass, MockModule("comp", partial_manifest=partial_manifest), + ) + + with patch( + "homeassistant.requirements.async_process_requirements", + side_effect=lambda _, _2, _3: mock_coro(), + ) as mock_process: + await async_get_integration_with_requirements(hass, "comp") + + assert len(mock_process.mock_calls) == 2 # zeroconf also depends on http + assert mock_process.mock_calls[0][1][2] == zeroconf.requirements From f5d4878992d63683d3da661ef02ae7b51421beb4 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 10 Dec 2019 09:25:42 +0100 Subject: [PATCH 252/677] Fix isort on a small set of misc files (#29803) --- docs/source/_ext/edit_on_github.py | 1 - docs/source/conf.py | 6 +++--- homeassistant/components/xiaomi_miio/fan.py | 1 - tests/components/feedreader/test_init.py | 3 +-- tests/components/icloud/test_config_flow.py | 7 +++---- .../jewish_calendar/test_binary_sensor.py | 4 ++-- tests/components/jewish_calendar/test_sensor.py | 4 ++-- tests/components/xiaomi_miio/test_vacuum.py | 14 +++++++------- 8 files changed, 18 insertions(+), 22 deletions(-) diff --git a/docs/source/_ext/edit_on_github.py b/docs/source/_ext/edit_on_github.py index eef249a3f01..a31fb13ebf1 100644 --- a/docs/source/_ext/edit_on_github.py +++ b/docs/source/_ext/edit_on_github.py @@ -8,7 +8,6 @@ Loosely based on https://github.com/astropy/astropy/pull/347 import os import warnings - __licence__ = 'BSD (3 clause)' diff --git a/docs/source/conf.py b/docs/source/conf.py index b5428ede8fa..f36b5b8124a 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -17,11 +17,11 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # -import sys -import os import inspect +import os +import sys -from homeassistant.const import __version__, __short_version__ +from homeassistant.const import __short_version__, __version__ PROJECT_NAME = 'Home Assistant' PROJECT_PACKAGE_NAME = 'homeassistant' diff --git a/homeassistant/components/xiaomi_miio/fan.py b/homeassistant/components/xiaomi_miio/fan.py index bf3691eb486..7cb45296506 100644 --- a/homeassistant/components/xiaomi_miio/fan.py +++ b/homeassistant/components/xiaomi_miio/fan.py @@ -58,7 +58,6 @@ from .const import ( SERVICE_SET_VOLUME, ) - _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = "Xiaomi Miio Device" diff --git a/tests/components/feedreader/test_init.py b/tests/components/feedreader/test_init.py index d74bbd79d80..bffbe9676fa 100644 --- a/tests/components/feedreader/test_init.py +++ b/tests/components/feedreader/test_init.py @@ -1,5 +1,6 @@ """The tests for the feedreader component.""" from datetime import timedelta +from genericpath import exists from logging import getLogger from os import remove import time @@ -7,8 +8,6 @@ import unittest from unittest import mock from unittest.mock import patch -from genericpath import exists - from homeassistant.components import feedreader from homeassistant.components.feedreader import ( CONF_MAX_ENTRIES, diff --git a/tests/components/icloud/test_config_flow.py b/tests/components/icloud/test_config_flow.py index b292a9e258c..5555150befc 100644 --- a/tests/components/icloud/test_config_flow.py +++ b/tests/components/icloud/test_config_flow.py @@ -1,23 +1,22 @@ """Tests for the iCloud config flow.""" -from unittest.mock import patch, Mock, MagicMock -import pytest +from unittest.mock import MagicMock, Mock, patch from pyicloud.exceptions import PyiCloudFailedLoginException +import pytest from homeassistant import data_entry_flow from homeassistant.components.icloud import config_flow - from homeassistant.components.icloud.config_flow import ( CONF_TRUSTED_DEVICE, CONF_VERIFICATION_CODE, ) from homeassistant.components.icloud.const import ( - DOMAIN, CONF_ACCOUNT_NAME, CONF_GPS_ACCURACY_THRESHOLD, CONF_MAX_INTERVAL, DEFAULT_GPS_ACCURACY_THRESHOLD, DEFAULT_MAX_INTERVAL, + DOMAIN, ) from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers.typing import HomeAssistantType diff --git a/tests/components/jewish_calendar/test_binary_sensor.py b/tests/components/jewish_calendar/test_binary_sensor.py index 0daa7c68993..a9ea2449c6f 100644 --- a/tests/components/jewish_calendar/test_binary_sensor.py +++ b/tests/components/jewish_calendar/test_binary_sensor.py @@ -8,10 +8,10 @@ from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util -from tests.common import async_fire_time_changed - from . import alter_time, make_jerusalem_test_params, make_nyc_test_params +from tests.common import async_fire_time_changed + MELACHA_PARAMS = [ make_nyc_test_params(dt(2018, 9, 1, 16, 0), STATE_ON), make_nyc_test_params(dt(2018, 9, 1, 20, 21), STATE_OFF), diff --git a/tests/components/jewish_calendar/test_sensor.py b/tests/components/jewish_calendar/test_sensor.py index 7ed3fdccb62..e630702c6b2 100644 --- a/tests/components/jewish_calendar/test_sensor.py +++ b/tests/components/jewish_calendar/test_sensor.py @@ -7,10 +7,10 @@ from homeassistant.components import jewish_calendar from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util -from tests.common import async_fire_time_changed - from . import alter_time, make_jerusalem_test_params, make_nyc_test_params +from tests.common import async_fire_time_changed + async def test_jewish_calendar_min_config(hass): """Test minimum jewish calendar configuration.""" diff --git a/tests/components/xiaomi_miio/test_vacuum.py b/tests/components/xiaomi_miio/test_vacuum.py index 5b6ce578c8b..6b2eb87f153 100644 --- a/tests/components/xiaomi_miio/test_vacuum.py +++ b/tests/components/xiaomi_miio/test_vacuum.py @@ -1,6 +1,6 @@ """The tests for the Xiaomi vacuum platform.""" import asyncio -from datetime import timedelta, time +from datetime import time, timedelta from unittest import mock import pytest @@ -22,26 +22,26 @@ from homeassistant.components.vacuum import ( ) from homeassistant.components.xiaomi_miio.vacuum import ( ATTR_CLEANED_AREA, + ATTR_CLEANED_TOTAL_AREA, + ATTR_CLEANING_COUNT, ATTR_CLEANING_TIME, + ATTR_CLEANING_TOTAL_TIME, ATTR_DO_NOT_DISTURB, - ATTR_DO_NOT_DISTURB_START, ATTR_DO_NOT_DISTURB_END, + ATTR_DO_NOT_DISTURB_START, ATTR_ERROR, + ATTR_FILTER_LEFT, ATTR_MAIN_BRUSH_LEFT, ATTR_SIDE_BRUSH_LEFT, - ATTR_FILTER_LEFT, - ATTR_CLEANING_COUNT, - ATTR_CLEANED_TOTAL_AREA, - ATTR_CLEANING_TOTAL_TIME, CONF_HOST, CONF_NAME, CONF_TOKEN, DOMAIN as XIAOMI_DOMAIN, + SERVICE_CLEAN_ZONE, SERVICE_MOVE_REMOTE_CONTROL, SERVICE_MOVE_REMOTE_CONTROL_STEP, SERVICE_START_REMOTE_CONTROL, SERVICE_STOP_REMOTE_CONTROL, - SERVICE_CLEAN_ZONE, ) from homeassistant.const import ( ATTR_ENTITY_ID, From db0baab692a2e4fbe7ba1f378cda3f6cd9fe42e2 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 10 Dec 2019 11:02:26 +0100 Subject: [PATCH 253/677] Only update disabled_by when enabled default and entity enabled states differ (#29643) --- homeassistant/components/unifi/device_tracker.py | 10 ++++++---- homeassistant/components/unifi/sensor.py | 3 +++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index 0806a2c04e3..159949f2985 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -75,6 +75,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Update the values of the controller.""" for entity in tracked.values(): + if entity.entity_registry_enabled_default == entity.enabled: + continue + disabled_by = None if not entity.entity_registry_enabled_default and entity.enabled: disabled_by = DISABLED_CONFIG_ENTRY @@ -227,10 +230,9 @@ class UniFiDeviceTracker(ScannerEntity): @property def entity_registry_enabled_default(self): """Return if the entity should be enabled when first added to the entity registry.""" - if not self.controller.option_track_devices: - return False - - return True + if self.controller.option_track_devices: + return True + return False async def async_added_to_hass(self): """Subscribe to device events.""" diff --git a/homeassistant/components/unifi/sensor.py b/homeassistant/components/unifi/sensor.py index e4f9b0df6c9..9145fd8e00f 100644 --- a/homeassistant/components/unifi/sensor.py +++ b/homeassistant/components/unifi/sensor.py @@ -40,6 +40,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Update the values of the controller.""" for entity in sensors.values(): + if entity.entity_registry_enabled_default == entity.enabled: + continue + disabled_by = None if not entity.entity_registry_enabled_default and entity.enabled: disabled_by = DISABLED_CONFIG_ENTRY From e28fd16c84ec04eaae3a489f2f9815f4e607a628 Mon Sep 17 00:00:00 2001 From: proferabg Date: Tue, 10 Dec 2019 13:25:22 -0500 Subject: [PATCH 254/677] Fix tank utility token (#29801) * Tank Utility Token Fix Fix 400 Bad Request on Invalid Token * Format Fix for Pull Request #29801 * Pylint Annotation Fix for Pull Request #29801 --- homeassistant/components/tank_utility/sensor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/tank_utility/sensor.py b/homeassistant/components/tank_utility/sensor.py index 3ab4a027b04..6848b263633 100644 --- a/homeassistant/components/tank_utility/sensor.py +++ b/homeassistant/components/tank_utility/sensor.py @@ -116,6 +116,8 @@ class TankUtilitySensor(Entity): if ( http_error.response.status_code == requests.codes.unauthorized # pylint: disable=no-member + or http_error.response.status_code + == requests.codes.bad_request # pylint: disable=no-member ): _LOGGER.info("Getting new token") self._token = tank_utility.auth.get_token( From 47e5142ddbc78d09a767c126e13b7a157fbc03e1 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 10 Dec 2019 20:04:48 +0100 Subject: [PATCH 255/677] UniFi - Handle disabled switches (#29824) --- homeassistant/components/unifi/switch.py | 33 ++++++++++++++---------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/unifi/switch.py b/homeassistant/components/unifi/switch.py index 18bfbb4066e..e4192d0b6c7 100644 --- a/homeassistant/components/unifi/switch.py +++ b/homeassistant/components/unifi/switch.py @@ -73,12 +73,13 @@ def update_items(controller, async_add_entities, switches, switches_off): block_client_id = f"block-{client_id}" if block_client_id in switches: - LOGGER.debug( - "Updating UniFi block switch %s (%s)", - switches[block_client_id].entity_id, - switches[block_client_id].client.mac, - ) - switches[block_client_id].async_schedule_update_ha_state() + if switches[block_client_id].enabled: + LOGGER.debug( + "Updating UniFi block switch %s (%s)", + switches[block_client_id].entity_id, + switches[block_client_id].client.mac, + ) + switches[block_client_id].async_schedule_update_ha_state() continue if client_id not in controller.api.clients_all: @@ -87,7 +88,6 @@ def update_items(controller, async_add_entities, switches, switches_off): client = controller.api.clients_all[client_id] switches[block_client_id] = UniFiBlockClientSwitch(client, controller) new_switches.append(switches[block_client_id]) - LOGGER.debug("New UniFi Block switch %s (%s)", client.hostname, client.mac) # control POE for client_id in controller.api.clients: @@ -95,12 +95,13 @@ def update_items(controller, async_add_entities, switches, switches_off): poe_client_id = f"poe-{client_id}" if poe_client_id in switches: - LOGGER.debug( - "Updating UniFi POE switch %s (%s)", - switches[poe_client_id].entity_id, - switches[poe_client_id].client.mac, - ) - switches[poe_client_id].async_schedule_update_ha_state() + if switches[poe_client_id].enabled: + LOGGER.debug( + "Updating UniFi POE switch %s (%s)", + switches[poe_client_id].entity_id, + switches[poe_client_id].client.mac, + ) + switches[poe_client_id].async_schedule_update_ha_state() continue client = controller.api.clients[client_id] @@ -138,7 +139,6 @@ def update_items(controller, async_add_entities, switches, switches_off): switches[poe_client_id] = UniFiPOEClientSwitch(client, controller) new_switches.append(switches[poe_client_id]) - LOGGER.debug("New UniFi POE switch %s (%s)", client.hostname, client.mac) if new_switches: async_add_entities(new_switches) @@ -179,6 +179,7 @@ class UniFiPOEClientSwitch(UniFiClient, SwitchDevice, RestoreEntity): async def async_added_to_hass(self): """Call when entity about to be added to Home Assistant.""" + LOGGER.debug("New UniFi POE switch %s (%s)", self.name, self.client.mac) state = await self.async_get_last_state() if state is None: @@ -252,6 +253,10 @@ class UniFiPOEClientSwitch(UniFiClient, SwitchDevice, RestoreEntity): class UniFiBlockClientSwitch(UniFiClient, SwitchDevice): """Representation of a blockable client.""" + async def async_added_to_hass(self): + """Call when entity about to be added to Home Assistant.""" + LOGGER.debug("New UniFi Block switch %s (%s)", self.name, self.client.mac) + @property def unique_id(self): """Return a unique identifier for this switch.""" From 99328bd4c1704819ea3bd3bed19bb69646a4e333 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 10 Dec 2019 20:05:18 +0100 Subject: [PATCH 256/677] UniFi - honor detection time when UniFi wire bug happens (#29820) --- .../components/unifi/device_tracker.py | 18 ++++++++++++------ tests/components/unifi/test_device_tracker.py | 19 ++++++++++++++++--- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index 159949f2985..6c3701e883d 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -127,6 +127,7 @@ class UniFiClientTracker(ScannerEntity): self.client = client self.controller = controller self.is_wired = self.client.mac not in controller.wireless_clients + self.wired_bug = None @property def entity_registry_enabled_default(self): @@ -169,13 +170,18 @@ class UniFiClientTracker(ScannerEntity): If is_wired and client.is_wired differ it means that the device is offline and UniFi bug shows device as wired. """ - if self.is_wired == self.client.is_wired and ( - ( - dt_util.utcnow() - - dt_util.utc_from_timestamp(float(self.client.last_seen)) + if self.is_wired != self.client.is_wired: + if not self.wired_bug: + self.wired_bug = dt_util.utcnow() + since_last_seen = dt_util.utcnow() - self.wired_bug + + else: + self.wired_bug = None + since_last_seen = dt_util.utcnow() - dt_util.utc_from_timestamp( + float(self.client.last_seen) ) - < self.controller.option_detection_time - ): + + if since_last_seen < self.controller.option_detection_time: return True return False diff --git a/tests/components/unifi/test_device_tracker.py b/tests/components/unifi/test_device_tracker.py index 634247a5283..42ba83a1612 100644 --- a/tests/components/unifi/test_device_tracker.py +++ b/tests/components/unifi/test_device_tracker.py @@ -2,6 +2,8 @@ from copy import copy from datetime import timedelta +from asynctest import patch + from homeassistant import config_entries from homeassistant.components import unifi import homeassistant.components.device_tracker as device_tracker @@ -17,8 +19,6 @@ import homeassistant.util.dt as dt_util from .test_controller import ENTRY_CONFIG, SITES, setup_unifi_integration -DEFAULT_DETECTION_TIME = timedelta(seconds=300) - CLIENT_1 = { "essid": "ssid", "hostname": "client_1", @@ -202,7 +202,20 @@ async def test_wireless_client_go_wired_issue(hass): await hass.async_block_till_done() client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "not_home" + assert client_1.state == "home" + + with patch.object( + unifi.device_tracker.dt_util, + "utcnow", + return_value=(dt_util.utcnow() + timedelta(minutes=5)), + ): + controller.mock_client_responses.append([client_1_client]) + controller.mock_device_responses.append({}) + await controller.async_update() + await hass.async_block_till_done() + + client_1 = hass.states.get("device_tracker.client_1") + assert client_1.state == "not_home" client_1_client["is_wired"] = False client_1_client["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) From 899f6cf1a34d3ba274460b2612948cf5a8df7828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Tue, 10 Dec 2019 22:41:29 +0200 Subject: [PATCH 257/677] Re-authorize Huawei LTE on login required error (#29597) * Re-authorize Huawei LTE on login required error Closes https://github.com/home-assistant/home-assistant/issues/29531 * Eliminate one indented "else" block * Convert get_data from inline to instance method * Use .get instead of [] for checking subscriptions for clarity --- .../components/huawei_lte/__init__.py | 65 +++++++++++-------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/huawei_lte/__init__.py b/homeassistant/components/huawei_lte/__init__.py index 4a5614543e9..531529b17cd 100644 --- a/homeassistant/components/huawei_lte/__init__.py +++ b/homeassistant/components/huawei_lte/__init__.py @@ -157,40 +157,53 @@ class Router: """Get router connections for device registry.""" return {(dr.CONNECTION_NETWORK_MAC, self.mac)} if self.mac else set() + def _get_data(self, key: str, func: Callable[[None], Any]) -> None: + if not self.subscriptions.get(key): + return + _LOGGER.debug("Getting %s for subscribers %s", key, self.subscriptions[key]) + try: + self.data[key] = func() + except ResponseErrorNotSupportedException: + _LOGGER.info( + "%s not supported by device, excluding from future updates", key + ) + self.subscriptions.pop(key) + except ResponseErrorLoginRequiredException: + if isinstance(self.connection, AuthorizedConnection): + _LOGGER.debug("Trying to authorize again...") + if self.connection.enforce_authorized_connection(): + _LOGGER.debug( + "...success, %s will be updated by a future periodic run", key, + ) + else: + _LOGGER.debug("...failed") + return + _LOGGER.info( + "%s requires authorization, excluding from future updates", key + ) + self.subscriptions.pop(key) + finally: + _LOGGER.debug("%s=%s", key, self.data.get(key)) + def update(self) -> None: """Update router data.""" - def get_data(key: str, func: Callable[[None], Any]) -> None: - if not self.subscriptions[key]: - return - _LOGGER.debug("Getting %s for subscribers %s", key, self.subscriptions[key]) - try: - self.data[key] = func() - except ResponseErrorNotSupportedException: - _LOGGER.info( - "%s not supported by device, excluding from future updates", key - ) - self.subscriptions.pop(key) - except ResponseErrorLoginRequiredException: - _LOGGER.info( - "%s requires authorization, excluding from future updates", key - ) - self.subscriptions.pop(key) - finally: - _LOGGER.debug("%s=%s", key, self.data.get(key)) - - get_data(KEY_DEVICE_INFORMATION, self.client.device.information) + self._get_data(KEY_DEVICE_INFORMATION, self.client.device.information) if self.data.get(KEY_DEVICE_INFORMATION): # Full information includes everything in basic self.subscriptions.pop(KEY_DEVICE_BASIC_INFORMATION, None) - get_data(KEY_DEVICE_BASIC_INFORMATION, self.client.device.basic_information) - get_data(KEY_DEVICE_SIGNAL, self.client.device.signal) - get_data(KEY_DIALUP_MOBILE_DATASWITCH, self.client.dial_up.mobile_dataswitch) - get_data(KEY_MONITORING_STATUS, self.client.monitoring.status) - get_data( + self._get_data( + KEY_DEVICE_BASIC_INFORMATION, self.client.device.basic_information + ) + self._get_data(KEY_DEVICE_SIGNAL, self.client.device.signal) + self._get_data( + KEY_DIALUP_MOBILE_DATASWITCH, self.client.dial_up.mobile_dataswitch + ) + self._get_data(KEY_MONITORING_STATUS, self.client.monitoring.status) + self._get_data( KEY_MONITORING_TRAFFIC_STATISTICS, self.client.monitoring.traffic_statistics ) - get_data(KEY_WLAN_HOST_LIST, self.client.wlan.host_list) + self._get_data(KEY_WLAN_HOST_LIST, self.client.wlan.host_list) self.signal_update() From 66d2f5f61de6a9c2cfed63efe686b3eaeef74fe9 Mon Sep 17 00:00:00 2001 From: Robert Van Gorkom Date: Tue, 10 Dec 2019 12:54:50 -0800 Subject: [PATCH 258/677] Fix withings wrong sleep state entry (#29651) * Fixing issue where wrong sleep state entry was being used. closes #28370,#29397 * Fixing formatting. * Sorting imports to get black checks to pass. * Using lambda for getting latest sleep serie. --- homeassistant/components/withings/__init__.py | 4 +- homeassistant/components/withings/common.py | 6 +-- homeassistant/components/withings/sensor.py | 11 ++--- tests/components/withings/test_common.py | 5 +-- tests/components/withings/test_init.py | 40 ++++++++++++++----- 5 files changed, 43 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/withings/__init__.py b/homeassistant/components/withings/__init__.py index 482c4e96e5c..92c3f2ae155 100644 --- a/homeassistant/components/withings/__init__.py +++ b/homeassistant/components/withings/__init__.py @@ -7,11 +7,11 @@ import voluptuous as vol from withings_api import WithingsAuth from homeassistant.config_entries import ConfigEntry +from homeassistant.helpers import config_entry_oauth2_flow, config_validation as cv from homeassistant.helpers.typing import ConfigType, HomeAssistantType -from homeassistant.helpers import config_validation as cv, config_entry_oauth2_flow from . import config_flow, const -from .common import _LOGGER, get_data_manager, NotAuthenticatedError +from .common import _LOGGER, NotAuthenticatedError, get_data_manager DOMAIN = const.DOMAIN diff --git a/homeassistant/components/withings/common.py b/homeassistant/components/withings/common.py index 655776ae004..5ec15eeac54 100644 --- a/homeassistant/components/withings/common.py +++ b/homeassistant/components/withings/common.py @@ -1,4 +1,5 @@ """Common code for Withings.""" +from asyncio import run_coroutine_threadsafe import datetime from functools import partial import logging @@ -6,15 +7,14 @@ import re import time from typing import Any, Dict -from asyncio import run_coroutine_threadsafe import requests from withings_api import ( AbstractWithingsApi, - SleepGetResponse, MeasureGetMeasResponse, + SleepGetResponse, SleepGetSummaryResponse, ) -from withings_api.common import UnauthorizedException, AuthFailedException +from withings_api.common import AuthFailedException, UnauthorizedException from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/withings/sensor.py b/homeassistant/components/withings/sensor.py index 17eae93ec0d..9b5ceab64bf 100644 --- a/homeassistant/components/withings/sensor.py +++ b/homeassistant/components/withings/sensor.py @@ -2,21 +2,21 @@ from typing import Callable, List, Union from withings_api.common import ( - MeasureType, GetSleepSummaryField, MeasureGetMeasResponse, + MeasureGroupAttribs, + MeasureType, SleepGetResponse, SleepGetSummaryResponse, - get_measure_value, - MeasureGroupAttribs, SleepState, + get_measure_value, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant +from homeassistant.helpers import config_entry_oauth2_flow from homeassistant.helpers.entity import Entity from homeassistant.util import slugify -from homeassistant.helpers import config_entry_oauth2_flow from . import const from .common import _LOGGER, WithingsDataManager, get_data_manager @@ -382,7 +382,8 @@ class WithingsHealthSensor(Entity): self._state = None return - serie = data.series[len(data.series) - 1] + sorted_series = sorted(data.series, key=lambda serie: serie.startdate) + serie = sorted_series[len(sorted_series) - 1] state = None if serie.state == SleepState.AWAKE: state = const.STATE_AWAKE diff --git a/tests/components/withings/test_common.py b/tests/components/withings/test_common.py index f6075c0f734..9328526d6ef 100644 --- a/tests/components/withings/test_common.py +++ b/tests/components/withings/test_common.py @@ -1,15 +1,14 @@ """Tests for the Withings component.""" from asynctest import MagicMock - import pytest from withings_api import WithingsApi -from withings_api.common import UnauthorizedException, TimeoutException +from withings_api.common import TimeoutException, UnauthorizedException -from homeassistant.exceptions import PlatformNotReady from homeassistant.components.withings.common import ( NotAuthenticatedError, WithingsDataManager, ) +from homeassistant.exceptions import PlatformNotReady @pytest.fixture(name="withings_api") diff --git a/tests/components/withings/test_init.py b/tests/components/withings/test_init.py index bd4940d9504..286b28b61ff 100644 --- a/tests/components/withings/test_init.py +++ b/tests/components/withings/test_init.py @@ -10,26 +10,26 @@ from withings_api.common import SleepModel, SleepState import homeassistant.components.http as http from homeassistant.components.withings import ( + CONFIG_SCHEMA, async_setup, async_setup_entry, const, - CONFIG_SCHEMA, ) from homeassistant.const import STATE_UNKNOWN from homeassistant.core import HomeAssistant from .common import ( - assert_state_equals, - configure_integration, - setup_hass, WITHINGS_GET_DEVICE_RESPONSE, WITHINGS_GET_DEVICE_RESPONSE_EMPTY, + WITHINGS_MEASURES_RESPONSE, + WITHINGS_MEASURES_RESPONSE_EMPTY, WITHINGS_SLEEP_RESPONSE, WITHINGS_SLEEP_RESPONSE_EMPTY, WITHINGS_SLEEP_SUMMARY_RESPONSE, WITHINGS_SLEEP_SUMMARY_RESPONSE_EMPTY, - WITHINGS_MEASURES_RESPONSE, - WITHINGS_MEASURES_RESPONSE_EMPTY, + assert_state_equals, + configure_integration, + setup_hass, ) @@ -308,8 +308,13 @@ async def test_full_setup(hass: HomeAssistant, aiohttp_client, aioclient_mock) - { "startdate": "2019-02-01 00:00:00", "enddate": "2019-02-01 01:00:00", + "state": SleepState.REM.real, + }, + { + "startdate": "2019-02-01 01:00:00", + "enddate": "2019-02-01 02:00:00", "state": SleepState.AWAKE.real, - } + }, ], }, }, @@ -329,11 +334,16 @@ async def test_full_setup(hass: HomeAssistant, aiohttp_client, aioclient_mock) - "body": { "model": SleepModel.TRACKER.real, "series": [ + { + "startdate": "2019-02-01 01:00:00", + "enddate": "2019-02-01 02:00:00", + "state": SleepState.LIGHT.real, + }, { "startdate": "2019-02-01 00:00:00", "enddate": "2019-02-01 01:00:00", - "state": SleepState.LIGHT.real, - } + "state": SleepState.REM.real, + }, ], }, }, @@ -356,8 +366,18 @@ async def test_full_setup(hass: HomeAssistant, aiohttp_client, aioclient_mock) - { "startdate": "2019-02-01 00:00:00", "enddate": "2019-02-01 01:00:00", + "state": SleepState.LIGHT.real, + }, + { + "startdate": "2019-02-01 02:00:00", + "enddate": "2019-02-01 03:00:00", "state": SleepState.REM.real, - } + }, + { + "startdate": "2019-02-01 01:00:00", + "enddate": "2019-02-01 02:00:00", + "state": SleepState.AWAKE.real, + }, ], }, }, From 3ed1738f769cbaf050a132f3f16ffb95e2154ab3 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Tue, 10 Dec 2019 17:24:33 -0500 Subject: [PATCH 259/677] Fix input_text initialization with empty config. (#29829) --- homeassistant/components/input_text/__init__.py | 10 ++++++---- tests/components/input_text/test_init.py | 9 +++------ 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/input_text/__init__.py b/homeassistant/components/input_text/__init__.py index 77f007c5ba8..2049de7ab27 100644 --- a/homeassistant/components/input_text/__init__.py +++ b/homeassistant/components/input_text/__init__.py @@ -23,7 +23,9 @@ ENTITY_ID_FORMAT = DOMAIN + ".{}" CONF_INITIAL = "initial" CONF_MIN = "min" +CONF_MIN_VALUE = 0 CONF_MAX = "max" +CONF_MAX_VALUE = 100 MODE_TEXT = "text" MODE_PASSWORD = "password" @@ -59,8 +61,8 @@ CONFIG_SCHEMA = vol.Schema( vol.All( { vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_MIN, default=0): vol.Coerce(int), - vol.Optional(CONF_MAX, default=100): vol.Coerce(int), + vol.Optional(CONF_MIN, default=CONF_MIN_VALUE): vol.Coerce(int), + vol.Optional(CONF_MAX, default=CONF_MAX_VALUE): vol.Coerce(int), vol.Optional(CONF_INITIAL, ""): cv.string, vol.Optional(CONF_ICON): cv.icon, vol.Optional(ATTR_UNIT_OF_MEASUREMENT): cv.string, @@ -121,8 +123,8 @@ async def _async_process_config(config): if cfg is None: cfg = {} name = cfg.get(CONF_NAME) - minimum = cfg.get(CONF_MIN) - maximum = cfg.get(CONF_MAX) + minimum = cfg.get(CONF_MIN, CONF_MIN_VALUE) + maximum = cfg.get(CONF_MAX, CONF_MAX_VALUE) initial = cfg.get(CONF_INITIAL) icon = cfg.get(CONF_ICON) unit = cfg.get(ATTR_UNIT_OF_MEASUREMENT) diff --git a/tests/components/input_text/test_init.py b/tests/components/input_text/test_init.py index 1bcf612c39b..d37fe01cd29 100644 --- a/tests/components/input_text/test_init.py +++ b/tests/components/input_text/test_init.py @@ -100,8 +100,7 @@ async def test_mode(hass): assert "password" == state.attributes["mode"] -@asyncio.coroutine -def test_restore_state(hass): +async def test_restore_state(hass): """Ensure states are restored on startup.""" mock_restore_cache( hass, @@ -110,10 +109,8 @@ def test_restore_state(hass): hass.state = CoreState.starting - yield from async_setup_component( - hass, - DOMAIN, - {DOMAIN: {"b1": {"min": 0, "max": 10}, "b2": {"min": 0, "max": 10}}}, + assert await async_setup_component( + hass, DOMAIN, {DOMAIN: {"b1": None, "b2": {"min": 0, "max": 10}}}, ) state = hass.states.get("input_text.b1") From a12cf7211da9eb9e47403a09f2147f6ee5e32f32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Wed, 11 Dec 2019 00:25:06 +0200 Subject: [PATCH 260/677] Upgrade pydocstyle to 5.0.1 (#29830) * Upgrade pydocstyle to 5.0.1 http://www.pydocstyle.org/en/5.0.1/release_notes.html * Pydocstyle and other docstring fixes --- .pre-commit-config-all.yaml | 2 +- .pre-commit-config.yaml | 2 +- .../components/bmw_connected_drive/__init__.py | 2 +- .../bmw_connected_drive/binary_sensor.py | 2 +- .../components/bmw_connected_drive/sensor.py | 2 +- homeassistant/components/dlna_dmr/media_player.py | 2 +- homeassistant/components/emulated_hue/__init__.py | 2 +- homeassistant/components/homeworks/__init__.py | 2 +- homeassistant/components/keba/__init__.py | 2 +- homeassistant/components/nissan_leaf/sensor.py | 2 +- homeassistant/components/starline/account.py | 2 +- homeassistant/components/starline/binary_sensor.py | 2 +- homeassistant/components/starline/entity.py | 2 +- homeassistant/components/starline/sensor.py | 2 +- homeassistant/components/tplink/common.py | 2 +- homeassistant/components/upnp/device.py | 4 ++-- homeassistant/components/upnp/sensor.py | 2 +- homeassistant/components/withings/common.py | 4 ++-- homeassistant/components/withings/sensor.py | 4 ++-- homeassistant/components/wunderground/sensor.py | 14 +++++++------- requirements_test_pre_commit.txt | 2 +- tests/components/mqtt/test_light_json.py | 2 +- tests/components/upnp/test_init.py | 2 +- tests/components/vera/common.py | 2 +- 24 files changed, 33 insertions(+), 33 deletions(-) diff --git a/.pre-commit-config-all.yaml b/.pre-commit-config-all.yaml index f7b29cddc4f..cb01bff85cb 100644 --- a/.pre-commit-config-all.yaml +++ b/.pre-commit-config-all.yaml @@ -24,7 +24,7 @@ repos: - id: flake8 additional_dependencies: - flake8-docstrings==1.5.0 - - pydocstyle==4.0.1 + - pydocstyle==5.0.1 files: ^(homeassistant|script|tests)/.+\.py$ - repo: https://github.com/PyCQA/bandit rev: 1.6.2 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 216bac95f29..e6f37ae6355 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,7 +20,7 @@ repos: - id: flake8 additional_dependencies: - flake8-docstrings==1.5.0 - - pydocstyle==4.0.1 + - pydocstyle==5.0.1 files: ^(homeassistant|script|tests)/.+\.py$ - repo: https://github.com/PyCQA/bandit rev: 1.6.2 diff --git a/homeassistant/components/bmw_connected_drive/__init__.py b/homeassistant/components/bmw_connected_drive/__init__.py index 455d821e669..6e7723b16ec 100644 --- a/homeassistant/components/bmw_connected_drive/__init__.py +++ b/homeassistant/components/bmw_connected_drive/__init__.py @@ -119,7 +119,7 @@ class BMWConnectedDriveAccount: def __init__( self, username: str, password: str, region_str: str, name: str, read_only ) -> None: - """Constructor.""" + """Initialize account.""" region = get_region_from_name(region_str) diff --git a/homeassistant/components/bmw_connected_drive/binary_sensor.py b/homeassistant/components/bmw_connected_drive/binary_sensor.py index 8163ae4eae3..d0cb4289d37 100644 --- a/homeassistant/components/bmw_connected_drive/binary_sensor.py +++ b/homeassistant/components/bmw_connected_drive/binary_sensor.py @@ -59,7 +59,7 @@ class BMWConnectedDriveSensor(BinarySensorDevice): def __init__( self, account, vehicle, attribute: str, sensor_name, device_class, icon ): - """Constructor.""" + """Initialize sensor.""" self._account = account self._vehicle = vehicle self._attribute = attribute diff --git a/homeassistant/components/bmw_connected_drive/sensor.py b/homeassistant/components/bmw_connected_drive/sensor.py index f919bba6b95..3c40900bed8 100644 --- a/homeassistant/components/bmw_connected_drive/sensor.py +++ b/homeassistant/components/bmw_connected_drive/sensor.py @@ -69,7 +69,7 @@ class BMWConnectedDriveSensor(Entity): """Representation of a BMW vehicle sensor.""" def __init__(self, account, vehicle, attribute: str, attribute_info): - """Constructor.""" + """Initialize BMW vehicle sensor.""" self._vehicle = vehicle self._account = account self._attribute = attribute diff --git a/homeassistant/components/dlna_dmr/media_player.py b/homeassistant/components/dlna_dmr/media_player.py index a9ea9b21d59..a6ebf95424a 100644 --- a/homeassistant/components/dlna_dmr/media_player.py +++ b/homeassistant/components/dlna_dmr/media_player.py @@ -198,7 +198,7 @@ class DlnaDmrDevice(MediaPlayerDevice): """Representation of a DLNA DMR device.""" def __init__(self, dmr_device, name=None): - """Initializer.""" + """Initialize DLNA DMR device.""" self._device = dmr_device self._name = name diff --git a/homeassistant/components/emulated_hue/__init__.py b/homeassistant/components/emulated_hue/__init__.py index c40a22df451..0a358c6e894 100644 --- a/homeassistant/components/emulated_hue/__init__.py +++ b/homeassistant/components/emulated_hue/__init__.py @@ -312,7 +312,7 @@ class Config: def _load_json(filename): - """Wrapper, because we actually want to handle invalid json.""" + """Load JSON, handling invalid syntax.""" try: return load_json(filename) except HomeAssistantError: diff --git a/homeassistant/components/homeworks/__init__.py b/homeassistant/components/homeworks/__init__.py index 55357acdad4..c6296d8f4c6 100644 --- a/homeassistant/components/homeworks/__init__.py +++ b/homeassistant/components/homeworks/__init__.py @@ -98,7 +98,7 @@ class HomeworksDevice: """Base class of a Homeworks device.""" def __init__(self, controller, addr, name): - """Controller, address, and name of the device.""" + """Initialize Homeworks device.""" self._addr = addr self._name = name self._controller = controller diff --git a/homeassistant/components/keba/__init__.py b/homeassistant/components/keba/__init__.py index 5a9a49a005a..830ebe7ffab 100644 --- a/homeassistant/components/keba/__init__.py +++ b/homeassistant/components/keba/__init__.py @@ -106,7 +106,7 @@ class KebaHandler(KebaKeContact): """Representation of a KEBA charging station connection.""" def __init__(self, hass, host, rfid, refresh_interval): - """Constructor.""" + """Initialize charging station connection.""" super().__init__(host, self.hass_callback) self._update_listeners = [] diff --git a/homeassistant/components/nissan_leaf/sensor.py b/homeassistant/components/nissan_leaf/sensor.py index cd82735d207..6e5d119c7a3 100644 --- a/homeassistant/components/nissan_leaf/sensor.py +++ b/homeassistant/components/nissan_leaf/sensor.py @@ -69,7 +69,7 @@ class LeafRangeSensor(LeafEntity): """Nissan Leaf Range Sensor.""" def __init__(self, car, ac_on): - """Set-up range sensor. Store if AC on.""" + """Set up range sensor. Store if AC on.""" self._ac_on = ac_on super().__init__(car) diff --git a/homeassistant/components/starline/account.py b/homeassistant/components/starline/account.py index aee88c0bd3f..8d0214d1b5c 100644 --- a/homeassistant/components/starline/account.py +++ b/homeassistant/components/starline/account.py @@ -23,7 +23,7 @@ class StarlineAccount: """StarLine Account class.""" def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry): - """Constructor.""" + """Initialize StarLine account.""" self._hass: HomeAssistant = hass self._config_entry: ConfigEntry = config_entry self._update_interval: int = DEFAULT_SCAN_INTERVAL diff --git a/homeassistant/components/starline/binary_sensor.py b/homeassistant/components/starline/binary_sensor.py index 21074069135..f2288e9363b 100644 --- a/homeassistant/components/starline/binary_sensor.py +++ b/homeassistant/components/starline/binary_sensor.py @@ -44,7 +44,7 @@ class StarlineSensor(StarlineEntity, BinarySensorDevice): name: str, device_class: str, ): - """Constructor.""" + """Initialize sensor.""" super().__init__(account, device, key, name) self._device_class = device_class diff --git a/homeassistant/components/starline/entity.py b/homeassistant/components/starline/entity.py index 31d1c79b9f0..5db4d369f5e 100644 --- a/homeassistant/components/starline/entity.py +++ b/homeassistant/components/starline/entity.py @@ -12,7 +12,7 @@ class StarlineEntity(Entity): def __init__( self, account: StarlineAccount, device: StarlineDevice, key: str, name: str ): - """Constructor.""" + """Initialize StarLine entity.""" self._account = account self._device = device self._key = key diff --git a/homeassistant/components/starline/sensor.py b/homeassistant/components/starline/sensor.py index 874fc5a9a7e..0c6cd8de683 100644 --- a/homeassistant/components/starline/sensor.py +++ b/homeassistant/components/starline/sensor.py @@ -42,7 +42,7 @@ class StarlineSensor(StarlineEntity, Entity): unit: str, icon: str, ): - """Constructor.""" + """Initialize StarLine sensor.""" super().__init__(account, device, key, name) self._device_class = device_class self._unit = unit diff --git a/homeassistant/components/tplink/common.py b/homeassistant/components/tplink/common.py index 548edc6822c..0e06babbd52 100644 --- a/homeassistant/components/tplink/common.py +++ b/homeassistant/components/tplink/common.py @@ -32,7 +32,7 @@ class SmartDevices: def __init__( self, lights: List[SmartDevice] = None, switches: List[SmartDevice] = None ): - """Constructor.""" + """Initialize device holder.""" self._lights = lights or [] self._switches = switches or [] diff --git a/homeassistant/components/upnp/device.py b/homeassistant/components/upnp/device.py index fffee57b411..1f34e63bcdf 100644 --- a/homeassistant/components/upnp/device.py +++ b/homeassistant/components/upnp/device.py @@ -17,13 +17,13 @@ class Device: """Hass representation of an UPnP/IGD.""" def __init__(self, igd_device): - """Initializer.""" + """Initialize UPnP/IGD device.""" self._igd_device = igd_device self._mapped_ports = [] @classmethod async def async_discover(cls, hass: HomeAssistantType): - """Discovery UPNP/IGD devices.""" + """Discover UPnP/IGD devices.""" _LOGGER.debug("Discovering UPnP/IGD devices") local_ip = None if DOMAIN in hass.data and "config" in hass.data[DOMAIN]: diff --git a/homeassistant/components/upnp/sensor.py b/homeassistant/components/upnp/sensor.py index 7b5071aabd3..06e4a86401f 100644 --- a/homeassistant/components/upnp/sensor.py +++ b/homeassistant/components/upnp/sensor.py @@ -154,7 +154,7 @@ class PerSecondUPnPIGDSensor(UpnpSensor): """Abstract representation of a X Sent/Received per second sensor.""" def __init__(self, device, direction): - """Initializer.""" + """Initialize sensor.""" super().__init__(device) self._direction = direction diff --git a/homeassistant/components/withings/common.py b/homeassistant/components/withings/common.py index 5ec15eeac54..4c3772d77e7 100644 --- a/homeassistant/components/withings/common.py +++ b/homeassistant/components/withings/common.py @@ -51,7 +51,7 @@ class ThrottleData: """Throttle data.""" def __init__(self, interval: int, data: Any): - """Constructor.""" + """Initialize throttle data.""" self._time = int(time.time()) self._interval = interval self._data = data @@ -126,7 +126,7 @@ class WithingsDataManager: service_available = None def __init__(self, hass: HomeAssistant, profile: str, api: ConfigEntryWithingsApi): - """Constructor.""" + """Initialize data manager.""" self._hass = hass self._api = api self._profile = profile diff --git a/homeassistant/components/withings/sensor.py b/homeassistant/components/withings/sensor.py index 9b5ceab64bf..aae706dec61 100644 --- a/homeassistant/components/withings/sensor.py +++ b/homeassistant/components/withings/sensor.py @@ -55,7 +55,7 @@ class WithingsAttribute: unit_of_measurement: str, icon: str, ) -> None: - """Constructor.""" + """Initialize attribute.""" self.measurement = measurement self.measure_type = measure_type self.friendly_name = friendly_name @@ -73,7 +73,7 @@ class WithingsSleepStateAttribute(WithingsAttribute): def __init__( self, measurement: str, friendly_name: str, unit_of_measurement: str, icon: str ) -> None: - """Constructor.""" + """Initialize sleep state attribute.""" super().__init__(measurement, None, friendly_name, unit_of_measurement, icon) diff --git a/homeassistant/components/wunderground/sensor.py b/homeassistant/components/wunderground/sensor.py index 84c870af54d..cc71e71a1d0 100644 --- a/homeassistant/components/wunderground/sensor.py +++ b/homeassistant/components/wunderground/sensor.py @@ -66,7 +66,7 @@ class WUSensorConfig: device_state_attributes=None, device_class=None, ): - """Constructor. + """Initialize sensor configuration. :param friendly_name: Friendly name :param feature: WU feature. See: @@ -98,7 +98,7 @@ class WUCurrentConditionsSensorConfig(WUSensorConfig): unit_of_measurement: Optional[str] = None, device_class=None, ): - """Constructor. + """Initialize current conditions sensor configuration. :param friendly_name: Friendly name of sensor :field: Field name in the "current_observation" dictionary. @@ -127,7 +127,7 @@ class WUDailyTextForecastSensorConfig(WUSensorConfig): def __init__( self, period: int, field: str, unit_of_measurement: Optional[str] = None ): - """Constructor. + """Initialize daily text forecast sensor configuration. :param period: forecast period number :param field: field name to use as value @@ -164,7 +164,7 @@ class WUDailySimpleForecastSensorConfig(WUSensorConfig): icon=None, device_class=None, ): - """Constructor. + """Initialize daily simple forecast sensor configuration. :param friendly_name: friendly_name of the sensor :param period: forecast period number @@ -207,7 +207,7 @@ class WUHourlyForecastSensorConfig(WUSensorConfig): """Helper for defining sensor configurations for hourly text forecasts.""" def __init__(self, period: int, field: int): - """Constructor. + """Initialize hourly forecast sensor configuration. :param period: forecast period number :param field: field name to use as value @@ -274,7 +274,7 @@ class WUAlmanacSensorConfig(WUSensorConfig): icon: str, device_class=None, ): - """Constructor. + """Initialize almanac sensor configuration. :param friendly_name: Friendly name :param field: value name returned in 'almanac' dict as returned by the WU API @@ -297,7 +297,7 @@ class WUAlertsSensorConfig(WUSensorConfig): """Helper for defining field configuration for alerts.""" def __init__(self, friendly_name: Union[str, Callable]): - """Constructor. + """Initialiize alerts sensor configuration. :param friendly_name: Friendly name """ diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index 3f4d05a4908..14b866c7034 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -4,4 +4,4 @@ bandit==1.6.2 black==19.10b0 flake8-docstrings==1.5.0 flake8==3.7.9 -pydocstyle==4.0.1 +pydocstyle==5.0.1 diff --git a/tests/components/mqtt/test_light_json.py b/tests/components/mqtt/test_light_json.py index f424b36ded6..52adeb61514 100644 --- a/tests/components/mqtt/test_light_json.py +++ b/tests/components/mqtt/test_light_json.py @@ -117,7 +117,7 @@ class JsonValidator(object): """Helper to compare JSON.""" def __init__(self, jsondata): - """Constructor.""" + """Initialize JSON validator.""" self.jsondata = jsondata def __eq__(self, other): diff --git a/tests/components/upnp/test_init.py b/tests/components/upnp/test_init.py index 1daa60bcb36..86ed017ae8d 100644 --- a/tests/components/upnp/test_init.py +++ b/tests/components/upnp/test_init.py @@ -15,7 +15,7 @@ class MockDevice(Device): """Mock device for Device.""" def __init__(self, udn): - """Initializer.""" + """Initialize mock device.""" device = MagicMock() device.manufacturer = "mock-manuf" device.name = "mock-name" diff --git a/tests/components/vera/common.py b/tests/components/vera/common.py index d78a536e95c..bd87e8bc9f2 100644 --- a/tests/components/vera/common.py +++ b/tests/components/vera/common.py @@ -16,7 +16,7 @@ class ComponentFactory: """Factory class.""" def __init__(self, init_controller_mock): - """Constructor.""" + """Initialize component factory.""" self.init_controller_mock = init_controller_mock async def configure_component( From c2357d843b6bcc96bb613e0b31e4e3c84fe2d04e Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Wed, 11 Dec 2019 00:32:21 +0000 Subject: [PATCH 261/677] [ci skip] Translation update --- .../alarm_control_panel/.translations/fr.json | 13 +++++-- .../components/deconz/.translations/fr.json | 11 +++++- .../components/elgato/.translations/ca.json | 27 +++++++++++++ .../components/elgato/.translations/fr.json | 27 +++++++++++++ .../components/elgato/.translations/lb.json | 27 +++++++++++++ .../components/icloud/.translations/ca.json | 38 +++++++++++++++++++ .../components/icloud/.translations/fr.json | 38 +++++++++++++++++++ .../components/icloud/.translations/lb.json | 38 +++++++++++++++++++ .../components/icloud/.translations/no.json | 38 +++++++++++++++++++ .../components/icloud/.translations/ru.json | 38 +++++++++++++++++++ .../components/linky/.translations/fr.json | 2 +- .../components/linky/.translations/pt.json | 2 +- .../components/starline/.translations/fr.json | 3 ++ 13 files changed, 296 insertions(+), 6 deletions(-) create mode 100644 homeassistant/components/elgato/.translations/ca.json create mode 100644 homeassistant/components/elgato/.translations/fr.json create mode 100644 homeassistant/components/elgato/.translations/lb.json create mode 100644 homeassistant/components/icloud/.translations/ca.json create mode 100644 homeassistant/components/icloud/.translations/fr.json create mode 100644 homeassistant/components/icloud/.translations/lb.json create mode 100644 homeassistant/components/icloud/.translations/no.json create mode 100644 homeassistant/components/icloud/.translations/ru.json diff --git a/homeassistant/components/alarm_control_panel/.translations/fr.json b/homeassistant/components/alarm_control_panel/.translations/fr.json index c3ba6db0c62..fbdc6a5605f 100644 --- a/homeassistant/components/alarm_control_panel/.translations/fr.json +++ b/homeassistant/components/alarm_control_panel/.translations/fr.json @@ -1,11 +1,18 @@ { "device_automation": { "action_type": { - "arm_away": "Armer {entity_name} mode sortie", - "arm_home": "Armer {entity_name} mode \u00e0 la maison", - "arm_night": "Armer {entity_name} mode nuit", + "arm_away": "Armer {entity_name} en mode \"sortie\"", + "arm_home": "Armer {entity_name} en mode \"maison\"", + "arm_night": "Armer {entity_name} en mode \"nuit\"", "disarm": "D\u00e9sarmer {entity_name}", "trigger": "D\u00e9clencheur {entity_name}" + }, + "trigger_type": { + "armed_away": "Armer {entity_name} en mode \"sortie\"", + "armed_home": "Armer {entity_name} en mode \"maison\"", + "armed_night": "Armer {entity_name} en mode \"nuit\"", + "disarmed": "{entity_name} d\u00e9sarm\u00e9", + "triggered": "{entity_name} d\u00e9clench\u00e9" } } } \ No newline at end of file diff --git a/homeassistant/components/deconz/.translations/fr.json b/homeassistant/components/deconz/.translations/fr.json index 4d49bd18d1e..1a4232e0817 100644 --- a/homeassistant/components/deconz/.translations/fr.json +++ b/homeassistant/components/deconz/.translations/fr.json @@ -76,7 +76,16 @@ "remote_button_short_press": "Bouton \"{subtype}\" appuy\u00e9", "remote_button_short_release": "Bouton \"{subtype}\" rel\u00e2ch\u00e9", "remote_button_triple_press": "Bouton \"{subtype}\" triple cliqu\u00e9", - "remote_gyro_activated": "Appareil secou\u00e9" + "remote_double_tap": "Appareil \"{subtype}\" tapot\u00e9 deux fois", + "remote_falling": "Appareil en chute libre", + "remote_gyro_activated": "Appareil secou\u00e9", + "remote_moved": "Appareil d\u00e9plac\u00e9 avec \"{subtype}\" vers le haut", + "remote_rotate_from_side_1": "Appareil tourn\u00e9 de \"c\u00f4t\u00e9 1\" \u00e0 \"{subtype}\"", + "remote_rotate_from_side_2": "Appareil tourn\u00e9 de \"c\u00f4t\u00e9 2\" \u00e0 \"{subtype}\"", + "remote_rotate_from_side_3": "Appareil tourn\u00e9 de \"c\u00f4t\u00e9 3\" \u00e0 \"{subtype}\"", + "remote_rotate_from_side_4": "Appareil tourn\u00e9 de \"c\u00f4t\u00e9 4\" \u00e0 \"{subtype}\"", + "remote_rotate_from_side_5": "Appareil tourn\u00e9 de \"c\u00f4t\u00e9 5\" \u00e0 \"{subtype}\"", + "remote_rotate_from_side_6": "Appareil tourn\u00e9 de \"c\u00f4t\u00e9 6\" \u00e0 \"{subtype}\"" } }, "options": { diff --git a/homeassistant/components/elgato/.translations/ca.json b/homeassistant/components/elgato/.translations/ca.json new file mode 100644 index 00000000000..b717a5abade --- /dev/null +++ b/homeassistant/components/elgato/.translations/ca.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Aquest dispositiu Elgato Key Light ja est\u00e0 configurat.", + "connection_error": "No s'ha pogut connectar amb el dispositiu Elgato Key Light." + }, + "error": { + "connection_error": "No s'ha pogut connectar amb el dispositiu Elgato Key Light." + }, + "flow_title": "Elgato Key Light: {serial_number}", + "step": { + "user": { + "data": { + "host": "Amfitri\u00f3 o adre\u00e7a IP", + "port": "N\u00famero de port" + }, + "description": "Configura l'Elgato Key Light per integrar-lo amb Home Assistant.", + "title": "Enlla\u00e7a Elgato Key Light" + }, + "zeroconf_confirm": { + "description": "Vols afegir l'Elgato Key Light amb n\u00famero de s\u00e8rie `{serial_number}` a Home Assistant?", + "title": "S'ha descobert un dispositiu Elgato Key Light" + } + }, + "title": "Elgato Key Light" + } +} \ No newline at end of file diff --git a/homeassistant/components/elgato/.translations/fr.json b/homeassistant/components/elgato/.translations/fr.json new file mode 100644 index 00000000000..e8465a56728 --- /dev/null +++ b/homeassistant/components/elgato/.translations/fr.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Cet appareil Elgato Key Light est d\u00e9j\u00e0 configur\u00e9.", + "connection_error": "Impossible de se connecter au p\u00e9riph\u00e9rique Elgato Key Light." + }, + "error": { + "connection_error": "Impossible de se connecter au p\u00e9riph\u00e9rique Elgato Key Light." + }, + "flow_title": "Elgato Key Light: {serial_number}", + "step": { + "user": { + "data": { + "host": "H\u00f4te ou adresse IP", + "port": "Port" + }, + "description": "Configurez votre Elgato Key Light pour l'int\u00e9grer \u00e0 Home Assistant.", + "title": "Associez votre Elgato Key Light" + }, + "zeroconf_confirm": { + "description": "Voulez-vous ajouter l'Elgato Key Light avec le num\u00e9ro de s\u00e9rie `{serial_number}` \u00e0 Home Assistant?", + "title": "Appareil Elgato Key Light d\u00e9couvert" + } + }, + "title": "Elgato Key Light" + } +} \ No newline at end of file diff --git a/homeassistant/components/elgato/.translations/lb.json b/homeassistant/components/elgato/.translations/lb.json new file mode 100644 index 00000000000..d53eea87c4c --- /dev/null +++ b/homeassistant/components/elgato/.translations/lb.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "D\u00ebsen Elgato Key Light Apparat ass scho konfigur\u00e9iert.", + "connection_error": "Feeler beim verbannen mam Elgato key Light Apparat." + }, + "error": { + "connection_error": "Feeler beim verbannen mam Elgato key Light Apparat." + }, + "flow_title": "Elgato Key Light: {serial_number}", + "step": { + "user": { + "data": { + "host": "Numm oder IP Adresse", + "port": "Port Nummer" + }, + "description": "\u00c4ren Elgator Key Light als Integratioun mam Home Assistant ariichten.", + "title": "\u00c4ren Elgato Key Light verbannen" + }, + "zeroconf_confirm": { + "description": "W\u00ebllt dir den Elgato Key Light mat der Seriennummer `{serial_number}` am 'Home Assistant dob\u00e4isetzen?", + "title": "Entdeckten Elgato Key Light Apparat" + } + }, + "title": "Elgato Key Light" + } +} \ No newline at end of file diff --git a/homeassistant/components/icloud/.translations/ca.json b/homeassistant/components/icloud/.translations/ca.json new file mode 100644 index 00000000000..30e6c50b81b --- /dev/null +++ b/homeassistant/components/icloud/.translations/ca.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "username_exists": "El compte ja ha estat configurat" + }, + "error": { + "login": "Error d\u2019inici de sessi\u00f3: comprova el correu electr\u00f2nic i la contrasenya", + "send_verification_code": "No s'ha pogut enviar el codi de verificaci\u00f3", + "username_exists": "El compte ja ha estat configurat", + "validate_verification_code": "No s'ha pogut verificar el codi de verificaci\u00f3, tria un dispositiu de confian\u00e7a i torna a iniciar el proc\u00e9s" + }, + "step": { + "trusted_device": { + "data": { + "trusted_device": "Dispositiu de confian\u00e7a" + }, + "description": "Selecciona el teu dispositiu de confian\u00e7a", + "title": "Dispositiu de confian\u00e7a iCloud" + }, + "user": { + "data": { + "password": "Contrasenya", + "username": "Correu electr\u00f2nic" + }, + "description": "Introdueix les teves credencials", + "title": "credencials d'iCloud" + }, + "verification_code": { + "data": { + "verification_code": "Codi de verificaci\u00f3" + }, + "description": "Introdueix el codi de verificaci\u00f3 que rebis d'iCloud", + "title": "Codi de verificaci\u00f3 iCloud" + } + }, + "title": "Apple iCloud" + } +} \ No newline at end of file diff --git a/homeassistant/components/icloud/.translations/fr.json b/homeassistant/components/icloud/.translations/fr.json new file mode 100644 index 00000000000..81996d908a6 --- /dev/null +++ b/homeassistant/components/icloud/.translations/fr.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "username_exists": "Compte d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "login": "Erreur de connexion: veuillez v\u00e9rifier votre e-mail et votre mot de passe", + "send_verification_code": "\u00c9chec de l'envoi du code de v\u00e9rification", + "username_exists": "Compte d\u00e9j\u00e0 configur\u00e9", + "validate_verification_code": "Impossible de v\u00e9rifier votre code de v\u00e9rification, choisissez un appareil de confiance et recommencez la v\u00e9rification" + }, + "step": { + "trusted_device": { + "data": { + "trusted_device": "Appareil de confiance" + }, + "description": "S\u00e9lectionnez votre appareil de confiance", + "title": "Appareil de confiance iCloud" + }, + "user": { + "data": { + "password": "Mot de passe", + "username": "Email" + }, + "description": "Entrez vos identifiants", + "title": "Identifiants iCloud" + }, + "verification_code": { + "data": { + "verification_code": "Code de v\u00e9rification" + }, + "description": "Veuillez entrer le code de v\u00e9rification que vous venez de recevoir d'iCloud", + "title": "Code de v\u00e9rification iCloud" + } + }, + "title": "Apple iCloud" + } +} \ No newline at end of file diff --git a/homeassistant/components/icloud/.translations/lb.json b/homeassistant/components/icloud/.translations/lb.json new file mode 100644 index 00000000000..eaeb300f7a8 --- /dev/null +++ b/homeassistant/components/icloud/.translations/lb.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "username_exists": "Kont ass scho konfigur\u00e9iert" + }, + "error": { + "login": "Feeler beim Login: iwwerpr\u00e9ift \u00e4r E-Mail & Passwuert", + "send_verification_code": "Feeler beim sch\u00e9cken vum Verifikatiouns Code", + "username_exists": "Kont ass scho konfigur\u00e9iert", + "validate_verification_code": "Feeler beim iwwerpr\u00e9iwe vum Verifikatiouns Code, wielt ee vertrauten Apparat aus a start d'Iwwerpr\u00e9iwung nei" + }, + "step": { + "trusted_device": { + "data": { + "trusted_device": "Vertrauten Apparat" + }, + "description": "Wielt \u00e4ren vertrauten Apparat aus", + "title": "iCloud vertrauten Apparat" + }, + "user": { + "data": { + "password": "Passwuert", + "username": "E-Mail" + }, + "description": "F\u00ebllt \u00e4r Umeldungs Informatiounen aus", + "title": "iCloud Umeldungs Informatiounen" + }, + "verification_code": { + "data": { + "verification_code": "Verifikatiouns Code" + }, + "description": "Gitt de Verifikatiouns Code an deen dir elo grad vun iCloud kritt hutt", + "title": "iCloud Verifikatiouns Code" + } + }, + "title": "Apple iCloud" + } +} \ No newline at end of file diff --git a/homeassistant/components/icloud/.translations/no.json b/homeassistant/components/icloud/.translations/no.json new file mode 100644 index 00000000000..a582b916310 --- /dev/null +++ b/homeassistant/components/icloud/.translations/no.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "username_exists": "Kontoen er allerede konfigurert" + }, + "error": { + "login": "Innloggingsfeil: vennligst sjekk e-postadressen og passordet ditt", + "send_verification_code": "Kunne ikke sende bekreftelseskode", + "username_exists": "Kontoen er allerede konfigurert", + "validate_verification_code": "Kunne ikke bekrefte bekreftelseskoden din, velg en tillitsenhet og start bekreftelsen p\u00e5 nytt" + }, + "step": { + "trusted_device": { + "data": { + "trusted_device": "P\u00e5litelig enhet" + }, + "description": "Velg den p\u00e5litelige enheten din", + "title": "iCloud p\u00e5litelig enhet" + }, + "user": { + "data": { + "password": "Passord", + "username": "E-post" + }, + "description": "Angi legitimasjonsbeskrivelsen", + "title": "iCloud-legitimasjon" + }, + "verification_code": { + "data": { + "verification_code": "iCloud-bekreftelseskode" + }, + "description": "Vennligst skriv inn bekreftelseskoden du nettopp har f\u00e5tt fra iCloud", + "title": "iCloud-bekreftelseskode" + } + }, + "title": "Apple iCloud" + } +} \ No newline at end of file diff --git a/homeassistant/components/icloud/.translations/ru.json b/homeassistant/components/icloud/.translations/ru.json new file mode 100644 index 00000000000..000edd71e00 --- /dev/null +++ b/homeassistant/components/icloud/.translations/ru.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "username_exists": "\u0423\u0447\u0451\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430." + }, + "error": { + "login": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0432\u0445\u043e\u0434\u0430: \u043f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0430\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b \u0438 \u043f\u0430\u0440\u043e\u043b\u044c.", + "send_verification_code": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u043a\u043e\u0434 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f.", + "username_exists": "\u0423\u0447\u0451\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430.", + "validate_verification_code": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c \u043a\u043e\u0434 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f, \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0434\u043e\u0432\u0435\u0440\u0435\u043d\u043d\u043e\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0438 \u043d\u0430\u0447\u043d\u0438\u0442\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u0441\u043d\u043e\u0432\u0430." + }, + "step": { + "trusted_device": { + "data": { + "trusted_device": "\u0414\u043e\u0432\u0435\u0440\u0435\u043d\u043d\u043e\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e" + }, + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0434\u043e\u0432\u0435\u0440\u0435\u043d\u043d\u043e\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e", + "title": "\u0414\u043e\u0432\u0435\u0440\u0435\u043d\u043d\u043e\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e iCloud" + }, + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0410\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b" + }, + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0412\u0430\u0448\u0438 \u0443\u0447\u0451\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435.", + "title": "\u0423\u0447\u0435\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 iCloud" + }, + "verification_code": { + "data": { + "verification_code": "\u041a\u043e\u0434 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f" + }, + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043a\u043e\u0434 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f, \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0439 \u043e\u0442 iCloud", + "title": "\u041a\u043e\u0434 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f iCloud" + } + }, + "title": "Apple iCloud" + } +} \ No newline at end of file diff --git a/homeassistant/components/linky/.translations/fr.json b/homeassistant/components/linky/.translations/fr.json index af12c2b654d..6ff99c41a16 100644 --- a/homeassistant/components/linky/.translations/fr.json +++ b/homeassistant/components/linky/.translations/fr.json @@ -8,7 +8,7 @@ "enedis": "Erreur d'Enedis.fr: merci de r\u00e9essayer plus tard (pas entre 23h et 2h)", "unknown": "Erreur inconnue: merci de r\u00e9essayer plus tard (pas entre 23h et 2h)", "username_exists": "Compte d\u00e9j\u00e0 configur\u00e9", - "wrong_login": "Impossible de vous identifier: merci de v\u00e9rifier vos identifiants" + "wrong_login": "Erreur de connexion: veuillez v\u00e9rifier votre e-mail et votre mot de passe" }, "step": { "user": { diff --git a/homeassistant/components/linky/.translations/pt.json b/homeassistant/components/linky/.translations/pt.json index daf1ce75181..67e742c5813 100644 --- a/homeassistant/components/linky/.translations/pt.json +++ b/homeassistant/components/linky/.translations/pt.json @@ -10,7 +10,7 @@ "user": { "data": { "password": "Palavra-passe", - "username": "" + "username": "O email" } } } diff --git a/homeassistant/components/starline/.translations/fr.json b/homeassistant/components/starline/.translations/fr.json index 67a7dae4afc..d15f5c37edf 100644 --- a/homeassistant/components/starline/.translations/fr.json +++ b/homeassistant/components/starline/.translations/fr.json @@ -1,6 +1,7 @@ { "config": { "error": { + "error_auth_app": "ID applicatif ou code secret incorrect", "error_auth_mfa": "code incorrect", "error_auth_user": "identifiant ou mot de passe incorrect" }, @@ -10,6 +11,7 @@ "app_id": "ID de l'application", "app_secret": "Secret" }, + "description": "ID applicatif et code secret du compte d\u00e9veloppeur StarLine", "title": "Informations d'identification de l'application" }, "auth_captcha": { @@ -23,6 +25,7 @@ "data": { "mfa_code": "Code SMS" }, + "description": "Entrez le code envoy\u00e9 au t\u00e9l\u00e9phone {phone_number}", "title": "Autorisation \u00e0 deux facteurs" }, "auth_user": { From 004af97699f892093c1c25f1bba0a6359773f61b Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Wed, 11 Dec 2019 12:12:06 +0100 Subject: [PATCH 262/677] Sort imports for requirements.py and its test using isort (#29836) Unblocks https://github.com/home-assistant/home-assistant/pull/29739 --- homeassistant/requirements.py | 2 +- tests/test_requirements.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/requirements.py b/homeassistant/requirements.py index 670f3af7dcc..7b2d3fe9bc3 100644 --- a/homeassistant/requirements.py +++ b/homeassistant/requirements.py @@ -3,7 +3,7 @@ import asyncio import logging import os from pathlib import Path -from typing import Any, Dict, List, Optional, Set, Iterable +from typing import Any, Dict, Iterable, List, Optional, Set from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError diff --git a/tests/test_requirements.py b/tests/test_requirements.py index 87bbf38e465..e95db4e533d 100644 --- a/tests/test_requirements.py +++ b/tests/test_requirements.py @@ -2,9 +2,10 @@ import os from pathlib import Path from unittest.mock import call, patch + import pytest -from homeassistant import setup, loader +from homeassistant import loader, setup from homeassistant.requirements import ( CONSTRAINT_FILE, PROGRESS_FILE, From 01ef44fd681b4c2bd37dd51bc794289f3a0a4dfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiit=20R=C3=A4tsep?= Date: Wed, 11 Dec 2019 14:27:28 +0200 Subject: [PATCH 263/677] Fix Soma integration connection issue (#27692) * Added a check for Connect actually returning something before telling the user the setup succeeded * Added handling for KeyError in case API returns empty response, formatting * Trying to please the linter --- .../components/soma/.translations/en.json | 4 +++- homeassistant/components/soma/config_flow.py | 18 ++++++++++---- homeassistant/components/soma/strings.json | 4 +++- tests/components/soma/test_config_flow.py | 24 +++++++++++++++++-- 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/soma/.translations/en.json b/homeassistant/components/soma/.translations/en.json index 42e09a8762c..ae2f9900162 100644 --- a/homeassistant/components/soma/.translations/en.json +++ b/homeassistant/components/soma/.translations/en.json @@ -3,7 +3,9 @@ "abort": { "already_setup": "You can only configure one Soma account.", "authorize_url_timeout": "Timeout generating authorize url.", - "missing_configuration": "The Soma component is not configured. Please follow the documentation." + "missing_configuration": "The Soma component is not configured. Please follow the documentation.", + "result_error": "SOMA Connect responded with error status.", + "connection_error": "Failed to connect to SOMA Connect." }, "create_entry": { "default": "Successfully authenticated with Soma." diff --git a/homeassistant/components/soma/config_flow.py b/homeassistant/components/soma/config_flow.py index f46066e23ca..afb5d05b77e 100644 --- a/homeassistant/components/soma/config_flow.py +++ b/homeassistant/components/soma/config_flow.py @@ -40,14 +40,22 @@ class SomaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Finish config flow.""" api = SomaApi(user_input["host"], user_input["port"]) try: - await self.hass.async_add_executor_job(api.list_devices) + result = await self.hass.async_add_executor_job(api.list_devices) _LOGGER.info("Successfully set up Soma Connect") - return self.async_create_entry( - title="Soma Connect", - data={"host": user_input["host"], "port": user_input["port"]}, + if result["result"] == "success": + return self.async_create_entry( + title="Soma Connect", + data={"host": user_input["host"], "port": user_input["port"]}, + ) + _LOGGER.error( + "Connection to SOMA Connect failed (result:%s)", result["result"] ) + return self.async_abort(reason="result_error") except RequestException: - _LOGGER.error("Connection to SOMA Connect failed") + _LOGGER.error("Connection to SOMA Connect failed with RequestException") + return self.async_abort(reason="connection_error") + except KeyError: + _LOGGER.error("Connection to SOMA Connect failed with KeyError") return self.async_abort(reason="connection_error") async def async_step_import(self, user_input=None): diff --git a/homeassistant/components/soma/strings.json b/homeassistant/components/soma/strings.json index aa2f92f0be6..67f1f6b7d46 100644 --- a/homeassistant/components/soma/strings.json +++ b/homeassistant/components/soma/strings.json @@ -3,7 +3,9 @@ "abort": { "already_setup": "You can only configure one Soma account.", "authorize_url_timeout": "Timeout generating authorize url.", - "missing_configuration": "The Soma component is not configured. Please follow the documentation." + "missing_configuration": "The Soma component is not configured. Please follow the documentation.", + "result_error": "SOMA Connect responded with error status.", + "connection_error": "Failed to connect to SOMA Connect." }, "create_entry": { "default": "Successfully authenticated with Soma." diff --git a/tests/components/soma/test_config_flow.py b/tests/components/soma/test_config_flow.py index 15f90516c17..1d00f83a608 100644 --- a/tests/components/soma/test_config_flow.py +++ b/tests/components/soma/test_config_flow.py @@ -35,11 +35,31 @@ async def test_import_create(hass): """Test configuration from YAML.""" flow = config_flow.SomaFlowHandler() flow.hass = hass - with patch.object(SomaApi, "list_devices", return_value={}): + with patch.object(SomaApi, "list_devices", return_value={"result": "success"}): result = await flow.async_step_import({"host": MOCK_HOST, "port": MOCK_PORT}) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY +async def test_error_status(hass): + """Test Connect successfully returning error status.""" + flow = config_flow.SomaFlowHandler() + flow.hass = hass + with patch.object(SomaApi, "list_devices", return_value={"result": "error"}): + result = await flow.async_step_import({"host": MOCK_HOST, "port": MOCK_PORT}) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "result_error" + + +async def test_key_error(hass): + """Test Connect returning empty string.""" + flow = config_flow.SomaFlowHandler() + flow.hass = hass + with patch.object(SomaApi, "list_devices", return_value={}): + result = await flow.async_step_import({"host": MOCK_HOST, "port": MOCK_PORT}) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "connection_error" + + async def test_exception(hass): """Test if RequestException fires when no connection can be made.""" flow = config_flow.SomaFlowHandler() @@ -55,6 +75,6 @@ async def test_full_flow(hass): hass.data[DOMAIN] = {} flow = config_flow.SomaFlowHandler() flow.hass = hass - with patch.object(SomaApi, "list_devices", return_value={}): + with patch.object(SomaApi, "list_devices", return_value={"result": "success"}): result = await flow.async_step_user({"host": MOCK_HOST, "port": MOCK_PORT}) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY From 0c796fc3c3cb79d2655283f1808e1f2938c78368 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Wed, 11 Dec 2019 13:28:50 +0100 Subject: [PATCH 264/677] Remove uvloop event policy (#29835) * Remove uvloop event policy * Clean tests * Fix lint * Cleanup statment --- homeassistant/__main__.py | 12 +----------- tests/conftest.py | 6 ------ 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index 2cecd1217f9..bf3042c3f88 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -15,12 +15,10 @@ if TYPE_CHECKING: def set_loop() -> None: - """Attempt to use uvloop.""" + """Attempt to use different loop.""" import asyncio from asyncio.events import BaseDefaultEventLoopPolicy - policy = None - if sys.platform == "win32": if hasattr(asyncio, "WindowsProactorEventLoopPolicy"): # pylint: disable=no-member @@ -33,15 +31,7 @@ def set_loop() -> None: _loop_factory = asyncio.ProactorEventLoop policy = ProactorPolicy() - else: - try: - import uvloop - except ImportError: - pass - else: - policy = uvloop.EventLoopPolicy() - if policy is not None: asyncio.set_event_loop_policy(policy) diff --git a/tests/conftest.py b/tests/conftest.py index 41b4757c92e..262acda6314 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,8 +1,6 @@ """Set up some common test helper things.""" -import asyncio import functools import logging -import os from unittest.mock import patch import pytest @@ -25,10 +23,6 @@ from tests.common import ( # noqa: E402, isort:skip ) from tests.test_util.aiohttp import mock_aiohttp_client # noqa: E402, isort:skip -if os.environ.get("UVLOOP") == "1": - import uvloop - - asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) logging.basicConfig(level=logging.DEBUG) logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO) From 7b8dff2aa92a13cb530cd6a02a85b6147039ef25 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Wed, 11 Dec 2019 15:43:54 +0100 Subject: [PATCH 265/677] Add user-agent to fix dwd_weather_warnings setup error (#29596) * Added dummy user-agent to http request to fix setup error * Replace dummy user-agent with the user-agent of the global home assistant session * Adjust comment and rename user-agent constant --- homeassistant/components/dwd_weather_warnings/sensor.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/dwd_weather_warnings/sensor.py b/homeassistant/components/dwd_weather_warnings/sensor.py index 01a06209d99..46b9ac560e5 100644 --- a/homeassistant/components/dwd_weather_warnings/sensor.py +++ b/homeassistant/components/dwd_weather_warnings/sensor.py @@ -22,6 +22,7 @@ from homeassistant.components.rest.sensor import RestData from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ATTR_ATTRIBUTION, CONF_MONITORED_CONDITIONS, CONF_NAME import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.aiohttp_client import SERVER_SOFTWARE as HA_USER_AGENT from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle import homeassistant.util.dt as dt_util @@ -184,7 +185,10 @@ class DwdWeatherWarningsAPI: "jsonp=loadWarnings", ) - self._rest = RestData("GET", resource, None, None, None, True) + # a User-Agent is necessary for this rest api endpoint (#29496) + headers = {"User-Agent": HA_USER_AGENT} + + self._rest = RestData("GET", resource, None, headers, None, True) self.region_name = region_name self.region_id = None self.region_state = None From 856dd6368085e6caf1ecce64a7642f3ad9af11ff Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Wed, 11 Dec 2019 15:45:21 +0100 Subject: [PATCH 266/677] Add more logging to help future debug situations (#29800) --- .../components/unifi/device_tracker.py | 19 +++++++++++++------ homeassistant/components/unifi/switch.py | 11 +++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index 6c3701e883d..6dfa6a6ecad 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -1,5 +1,6 @@ """Track devices using UniFi controllers.""" import logging +from pprint import pformat from homeassistant.components.device_tracker import DOMAIN as DEVICE_TRACKER_DOMAIN from homeassistant.components.device_tracker.config_entry import ScannerEntity @@ -156,14 +157,17 @@ class UniFiClientTracker(ScannerEntity): Make sure to update self.is_wired if client is wireless, there is an issue when clients go offline that they get marked as wired. """ - LOGGER.debug( - "Updating UniFi tracked client %s (%s)", self.entity_id, self.client.mac - ) await self.controller.request_update() if self.is_wired and self.client.mac in self.controller.wireless_clients: self.is_wired = False + LOGGER.debug( + "Updating UniFi tracked client %s\n%s", + self.entity_id, + pformat(self.client.raw), + ) + @property def is_connected(self): """Return true if the client is connected to the network. @@ -246,11 +250,14 @@ class UniFiDeviceTracker(ScannerEntity): async def async_update(self): """Synchronize state with controller.""" - LOGGER.debug( - "Updating UniFi tracked device %s (%s)", self.entity_id, self.device.mac - ) await self.controller.request_update() + LOGGER.debug( + "Updating UniFi tracked device %s\n%s", + self.entity_id, + pformat(self.device.raw), + ) + @property def is_connected(self): """Return true if the device is connected to the network.""" diff --git a/homeassistant/components/unifi/switch.py b/homeassistant/components/unifi/switch.py index e4192d0b6c7..5b64f573ccd 100644 --- a/homeassistant/components/unifi/switch.py +++ b/homeassistant/components/unifi/switch.py @@ -1,5 +1,6 @@ """Support for devices connected to UniFi POE.""" import logging +from pprint import pformat from homeassistant.components.switch import SwitchDevice from homeassistant.components.unifi.config_flow import get_controller_from_config_entry @@ -194,6 +195,16 @@ class UniFiPOEClientSwitch(UniFiClient, SwitchDevice, RestoreEntity): if not self.client.sw_port: self.client.raw["sw_port"] = state.attributes["port"] + async def async_update(self): + """Log client information after update.""" + await super().async_update() + + LOGGER.debug( + "Updating UniFi POE controlled client %s\n%s", + self.entity_id, + pformat(self.client.raw), + ) + @property def unique_id(self): """Return a unique identifier for this switch.""" From a08e3d73524891d6357ba1bc9dca8db2399c03f9 Mon Sep 17 00:00:00 2001 From: foxy82 Date: Wed, 11 Dec 2019 14:58:49 +0000 Subject: [PATCH 267/677] Update rfxtrx component so it can be run as a custom_component (#29638) * Updating rfxtrx component so it can be run as a custom_component * Fix pylint errors * Fix pylint errors * Fix pylint error on dict. * isort --- .../components/rfxtrx/binary_sensor.py | 36 ++++++++++--------- homeassistant/components/rfxtrx/cover.py | 18 ++++++---- homeassistant/components/rfxtrx/light.py | 18 ++++++---- homeassistant/components/rfxtrx/sensor.py | 20 ++++++----- homeassistant/components/rfxtrx/switch.py | 18 ++++++---- 5 files changed, 64 insertions(+), 46 deletions(-) diff --git a/homeassistant/components/rfxtrx/binary_sensor.py b/homeassistant/components/rfxtrx/binary_sensor.py index 6465dc36326..a88594dccea 100644 --- a/homeassistant/components/rfxtrx/binary_sensor.py +++ b/homeassistant/components/rfxtrx/binary_sensor.py @@ -4,7 +4,6 @@ import logging import RFXtrx as rfxtrxmod import voluptuous as vol -from homeassistant.components import rfxtrx from homeassistant.components.binary_sensor import ( DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, @@ -26,6 +25,14 @@ from . import ( CONF_DEVICES, CONF_FIRE_EVENT, CONF_OFF_DELAY, + RECEIVED_EVT_SUBSCRIBERS, + RFX_DEVICES, + apply_received_command, + find_possible_pt2262_device, + get_pt2262_cmd, + get_pt2262_device, + get_pt2262_deviceid, + get_rfx_object, ) _LOGGER = logging.getLogger(__name__) @@ -58,16 +65,16 @@ def setup_platform(hass, config, add_entities, discovery_info=None): sensors = [] for packet_id, entity in config[CONF_DEVICES].items(): - event = rfxtrx.get_rfx_object(packet_id) + event = get_rfx_object(packet_id) device_id = slugify(event.device.id_string.lower()) - if device_id in rfxtrx.RFX_DEVICES: + if device_id in RFX_DEVICES: continue if entity.get(CONF_DATA_BITS) is not None: _LOGGER.debug( "Masked device id: %s", - rfxtrx.get_pt2262_deviceid(device_id, entity.get(CONF_DATA_BITS)), + get_pt2262_deviceid(device_id, entity.get(CONF_DATA_BITS)), ) _LOGGER.debug( @@ -88,7 +95,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) device.hass = hass sensors.append(device) - rfxtrx.RFX_DEVICES[device_id] = device + RFX_DEVICES[device_id] = device add_entities(sensors) @@ -99,10 +106,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): device_id = slugify(event.device.id_string.lower()) - if device_id in rfxtrx.RFX_DEVICES: - sensor = rfxtrx.RFX_DEVICES[device_id] - else: - sensor = rfxtrx.get_pt2262_device(device_id) + sensor = RFX_DEVICES.get(device_id, get_pt2262_device(device_id)) if sensor is None: # Add the entity if not exists and automatic_add is True @@ -110,7 +114,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return if event.device.packettype == 0x13: - poss_dev = rfxtrx.find_possible_pt2262_device(device_id) + poss_dev = find_possible_pt2262_device(device_id) if poss_dev is not None: poss_id = slugify(poss_dev.event.device.id_string.lower()) _LOGGER.debug("Found possible matching device ID: %s", poss_id) @@ -118,7 +122,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): pkt_id = "".join(f"{x:02x}" for x in event.data) sensor = RfxtrxBinarySensor(event, pkt_id) sensor.hass = hass - rfxtrx.RFX_DEVICES[device_id] = sensor + RFX_DEVICES[device_id] = sensor add_entities([sensor]) _LOGGER.info( "Added binary sensor %s (Device ID: %s Class: %s Sub: %s)", @@ -140,12 +144,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if sensor.is_lighting4: if sensor.data_bits is not None: - cmd = rfxtrx.get_pt2262_cmd(device_id, sensor.data_bits) + cmd = get_pt2262_cmd(device_id, sensor.data_bits) sensor.apply_cmd(int(cmd, 16)) else: sensor.update_state(True) else: - rfxtrx.apply_received_command(event) + apply_received_command(event) if ( sensor.is_on @@ -163,8 +167,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) # Subscribe to main RFXtrx events - if binary_sensor_update not in rfxtrx.RECEIVED_EVT_SUBSCRIBERS: - rfxtrx.RECEIVED_EVT_SUBSCRIBERS.append(binary_sensor_update) + if binary_sensor_update not in RECEIVED_EVT_SUBSCRIBERS: + RECEIVED_EVT_SUBSCRIBERS.append(binary_sensor_update) class RfxtrxBinarySensor(BinarySensorDevice): @@ -195,7 +199,7 @@ class RfxtrxBinarySensor(BinarySensorDevice): self._cmd_off = cmd_off if data_bits is not None: - self._masked_id = rfxtrx.get_pt2262_deviceid( + self._masked_id = get_pt2262_deviceid( event.device.id_string.lower(), data_bits ) else: diff --git a/homeassistant/components/rfxtrx/cover.py b/homeassistant/components/rfxtrx/cover.py index 7aff22bd124..4806fd9a6b7 100644 --- a/homeassistant/components/rfxtrx/cover.py +++ b/homeassistant/components/rfxtrx/cover.py @@ -2,7 +2,6 @@ import RFXtrx as rfxtrxmod import voluptuous as vol -from homeassistant.components import rfxtrx from homeassistant.components.cover import PLATFORM_SCHEMA, CoverDevice from homeassistant.const import CONF_NAME from homeassistant.helpers import config_validation as cv @@ -13,6 +12,11 @@ from . import ( CONF_FIRE_EVENT, CONF_SIGNAL_REPETITIONS, DEFAULT_SIGNAL_REPETITIONS, + RECEIVED_EVT_SUBSCRIBERS, + RfxtrxDevice, + apply_received_command, + get_devices_from_config, + get_new_device, ) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( @@ -35,7 +39,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the RFXtrx cover.""" - covers = rfxtrx.get_devices_from_config(config, RfxtrxCover) + covers = get_devices_from_config(config, RfxtrxCover) add_entities(covers) def cover_update(event): @@ -47,18 +51,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ): return - new_device = rfxtrx.get_new_device(event, config, RfxtrxCover) + new_device = get_new_device(event, config, RfxtrxCover) if new_device: add_entities([new_device]) - rfxtrx.apply_received_command(event) + apply_received_command(event) # Subscribe to main RFXtrx events - if cover_update not in rfxtrx.RECEIVED_EVT_SUBSCRIBERS: - rfxtrx.RECEIVED_EVT_SUBSCRIBERS.append(cover_update) + if cover_update not in RECEIVED_EVT_SUBSCRIBERS: + RECEIVED_EVT_SUBSCRIBERS.append(cover_update) -class RfxtrxCover(rfxtrx.RfxtrxDevice, CoverDevice): +class RfxtrxCover(RfxtrxDevice, CoverDevice): """Representation of a RFXtrx cover.""" @property diff --git a/homeassistant/components/rfxtrx/light.py b/homeassistant/components/rfxtrx/light.py index a745a11388a..a29b8bfa660 100644 --- a/homeassistant/components/rfxtrx/light.py +++ b/homeassistant/components/rfxtrx/light.py @@ -4,7 +4,6 @@ import logging import RFXtrx as rfxtrxmod import voluptuous as vol -from homeassistant.components import rfxtrx from homeassistant.components.light import ( ATTR_BRIGHTNESS, PLATFORM_SCHEMA, @@ -20,6 +19,11 @@ from . import ( CONF_FIRE_EVENT, CONF_SIGNAL_REPETITIONS, DEFAULT_SIGNAL_REPETITIONS, + RECEIVED_EVT_SUBSCRIBERS, + RfxtrxDevice, + apply_received_command, + get_devices_from_config, + get_new_device, ) _LOGGER = logging.getLogger(__name__) @@ -46,7 +50,7 @@ SUPPORT_RFXTRX = SUPPORT_BRIGHTNESS def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the RFXtrx platform.""" - lights = rfxtrx.get_devices_from_config(config, RfxtrxLight) + lights = get_devices_from_config(config, RfxtrxLight) add_entities(lights) def light_update(event): @@ -57,18 +61,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ): return - new_device = rfxtrx.get_new_device(event, config, RfxtrxLight) + new_device = get_new_device(event, config, RfxtrxLight) if new_device: add_entities([new_device]) - rfxtrx.apply_received_command(event) + apply_received_command(event) # Subscribe to main RFXtrx events - if light_update not in rfxtrx.RECEIVED_EVT_SUBSCRIBERS: - rfxtrx.RECEIVED_EVT_SUBSCRIBERS.append(light_update) + if light_update not in RECEIVED_EVT_SUBSCRIBERS: + RECEIVED_EVT_SUBSCRIBERS.append(light_update) -class RfxtrxLight(rfxtrx.RfxtrxDevice, Light): +class RfxtrxLight(RfxtrxDevice, Light): """Representation of a RFXtrx light.""" @property diff --git a/homeassistant/components/rfxtrx/sensor.py b/homeassistant/components/rfxtrx/sensor.py index 5429943a7a6..3f74ff18695 100644 --- a/homeassistant/components/rfxtrx/sensor.py +++ b/homeassistant/components/rfxtrx/sensor.py @@ -4,7 +4,6 @@ import logging from RFXtrx import SensorEvent import voluptuous as vol -from homeassistant.components import rfxtrx from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ATTR_ENTITY_ID, ATTR_NAME, CONF_NAME import homeassistant.helpers.config_validation as cv @@ -19,6 +18,9 @@ from . import ( CONF_DEVICES, CONF_FIRE_EVENT, DATA_TYPES, + RECEIVED_EVT_SUBSCRIBERS, + RFX_DEVICES, + get_rfx_object, ) _LOGGER = logging.getLogger(__name__) @@ -46,9 +48,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the RFXtrx platform.""" sensors = [] for packet_id, entity_info in config[CONF_DEVICES].items(): - event = rfxtrx.get_rfx_object(packet_id) + event = get_rfx_object(packet_id) device_id = "sensor_{}".format(slugify(event.device.id_string.lower())) - if device_id in rfxtrx.RFX_DEVICES: + if device_id in RFX_DEVICES: continue _LOGGER.info("Add %s rfxtrx.sensor", entity_info[ATTR_NAME]) @@ -66,7 +68,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) sensors.append(new_sensor) sub_sensors[_data_type] = new_sensor - rfxtrx.RFX_DEVICES[device_id] = sub_sensors + RFX_DEVICES[device_id] = sub_sensors add_entities(sensors) def sensor_update(event): @@ -76,8 +78,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): device_id = "sensor_" + slugify(event.device.id_string.lower()) - if device_id in rfxtrx.RFX_DEVICES: - sensors = rfxtrx.RFX_DEVICES[device_id] + if device_id in RFX_DEVICES: + sensors = RFX_DEVICES[device_id] for data_type in sensors: # Some multi-sensor devices send individual messages for each # of their sensors. Update only if event contains the @@ -108,11 +110,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): new_sensor = RfxtrxSensor(event, pkt_id, data_type) sub_sensors = {} sub_sensors[new_sensor.data_type] = new_sensor - rfxtrx.RFX_DEVICES[device_id] = sub_sensors + RFX_DEVICES[device_id] = sub_sensors add_entities([new_sensor]) - if sensor_update not in rfxtrx.RECEIVED_EVT_SUBSCRIBERS: - rfxtrx.RECEIVED_EVT_SUBSCRIBERS.append(sensor_update) + if sensor_update not in RECEIVED_EVT_SUBSCRIBERS: + RECEIVED_EVT_SUBSCRIBERS.append(sensor_update) class RfxtrxSensor(Entity): diff --git a/homeassistant/components/rfxtrx/switch.py b/homeassistant/components/rfxtrx/switch.py index 6d91b261a4f..49bcd1b3924 100644 --- a/homeassistant/components/rfxtrx/switch.py +++ b/homeassistant/components/rfxtrx/switch.py @@ -4,7 +4,6 @@ import logging import RFXtrx as rfxtrxmod import voluptuous as vol -from homeassistant.components import rfxtrx from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice from homeassistant.const import CONF_NAME from homeassistant.helpers import config_validation as cv @@ -15,6 +14,11 @@ from . import ( CONF_FIRE_EVENT, CONF_SIGNAL_REPETITIONS, DEFAULT_SIGNAL_REPETITIONS, + RECEIVED_EVT_SUBSCRIBERS, + RfxtrxDevice, + apply_received_command, + get_devices_from_config, + get_new_device, ) _LOGGER = logging.getLogger(__name__) @@ -40,7 +44,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( def setup_platform(hass, config, add_entities_callback, discovery_info=None): """Set up the RFXtrx platform.""" # Add switch from config file - switches = rfxtrx.get_devices_from_config(config, RfxtrxSwitch) + switches = get_devices_from_config(config, RfxtrxSwitch) add_entities_callback(switches) def switch_update(event): @@ -52,18 +56,18 @@ def setup_platform(hass, config, add_entities_callback, discovery_info=None): ): return - new_device = rfxtrx.get_new_device(event, config, RfxtrxSwitch) + new_device = get_new_device(event, config, RfxtrxSwitch) if new_device: add_entities_callback([new_device]) - rfxtrx.apply_received_command(event) + apply_received_command(event) # Subscribe to main RFXtrx events - if switch_update not in rfxtrx.RECEIVED_EVT_SUBSCRIBERS: - rfxtrx.RECEIVED_EVT_SUBSCRIBERS.append(switch_update) + if switch_update not in RECEIVED_EVT_SUBSCRIBERS: + RECEIVED_EVT_SUBSCRIBERS.append(switch_update) -class RfxtrxSwitch(rfxtrx.RfxtrxDevice, SwitchDevice): +class RfxtrxSwitch(RfxtrxDevice, SwitchDevice): """Representation of a RFXtrx switch.""" def turn_on(self, **kwargs): From 7bd98d108244f3f3206b1ca0e83c385df3711413 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Thu, 12 Dec 2019 00:32:15 +0000 Subject: [PATCH 268/677] [ci skip] Translation update --- .../icloud/.translations/zh-Hant.json | 38 +++++++++++++++++++ .../components/soma/.translations/ca.json | 4 +- .../components/soma/.translations/en.json | 4 +- .../components/soma/.translations/no.json | 4 +- 4 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 homeassistant/components/icloud/.translations/zh-Hant.json diff --git a/homeassistant/components/icloud/.translations/zh-Hant.json b/homeassistant/components/icloud/.translations/zh-Hant.json new file mode 100644 index 00000000000..80d8ba1485b --- /dev/null +++ b/homeassistant/components/icloud/.translations/zh-Hant.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "username_exists": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "login": "\u767b\u5165\u932f\u8aa4\uff1a\u8acb\u78ba\u8a8d\u96fb\u5b50\u90f5\u4ef6\u8207\u79d8\u5bc6\u6b63\u78ba\u6027", + "send_verification_code": "\u50b3\u9001\u9a57\u8b49\u78bc\u5931\u6557", + "username_exists": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "validate_verification_code": "\u7121\u6cd5\u9a57\u8b49\u8f38\u5165\u9a57\u8b49\u78bc\uff0c\u9078\u64c7\u4e00\u90e8\u4fe1\u4efb\u8a2d\u5099\u3001\u7136\u5f8c\u91cd\u65b0\u57f7\u884c\u9a57\u8b49\u3002" + }, + "step": { + "trusted_device": { + "data": { + "trusted_device": "\u4fe1\u4efb\u8a2d\u5099" + }, + "description": "\u9078\u64c7\u4fe1\u4efb\u8a2d\u5099", + "title": "iCloud \u4fe1\u4efb\u8a2d\u5099" + }, + "user": { + "data": { + "password": "\u5bc6\u78bc", + "username": "\u96fb\u5b50\u90f5\u4ef6" + }, + "description": "\u8f38\u5165\u6191\u8b49", + "title": "iCloud \u6191\u8b49" + }, + "verification_code": { + "data": { + "verification_code": "\u9a57\u8b49\u78bc" + }, + "description": "\u8acb\u8f38\u5165\u6240\u6536\u5230\u7684 iCloud \u9a57\u8b49\u78bc", + "title": "iCloud \u9a57\u8b49\u78bc" + } + }, + "title": "Apple iCloud" + } +} \ No newline at end of file diff --git a/homeassistant/components/soma/.translations/ca.json b/homeassistant/components/soma/.translations/ca.json index 18b33d1bc9b..a1a5b9489fa 100644 --- a/homeassistant/components/soma/.translations/ca.json +++ b/homeassistant/components/soma/.translations/ca.json @@ -3,7 +3,9 @@ "abort": { "already_setup": "Nom\u00e9s pots configurar un compte de Soma.", "authorize_url_timeout": "S'ha acabat el temps d'espera durant la generaci\u00f3 de l'URL d'autoritzaci\u00f3.", - "missing_configuration": "El component Soma no est\u00e0 configurat. Mira'n la documentaci\u00f3." + "connection_error": "No s'ha pogut connectar amb SOMA Connect.", + "missing_configuration": "El component Soma no est\u00e0 configurat. Mira'n la documentaci\u00f3.", + "result_error": "SOMA Connect ha respost amb un estat d'error." }, "create_entry": { "default": "Autenticaci\u00f3 exitosa amb Soma." diff --git a/homeassistant/components/soma/.translations/en.json b/homeassistant/components/soma/.translations/en.json index ae2f9900162..46bfd441fc4 100644 --- a/homeassistant/components/soma/.translations/en.json +++ b/homeassistant/components/soma/.translations/en.json @@ -3,9 +3,9 @@ "abort": { "already_setup": "You can only configure one Soma account.", "authorize_url_timeout": "Timeout generating authorize url.", + "connection_error": "Failed to connect to SOMA Connect.", "missing_configuration": "The Soma component is not configured. Please follow the documentation.", - "result_error": "SOMA Connect responded with error status.", - "connection_error": "Failed to connect to SOMA Connect." + "result_error": "SOMA Connect responded with error status." }, "create_entry": { "default": "Successfully authenticated with Soma." diff --git a/homeassistant/components/soma/.translations/no.json b/homeassistant/components/soma/.translations/no.json index b2d80208b83..518cbc6a37e 100644 --- a/homeassistant/components/soma/.translations/no.json +++ b/homeassistant/components/soma/.translations/no.json @@ -3,7 +3,9 @@ "abort": { "already_setup": "Du kan bare konfigurere \u00e9n Soma-konto.", "authorize_url_timeout": "Tidsavbrudd ved generering av autoriseringsadresse.", - "missing_configuration": "Soma-komponenten er ikke konfigurert. Vennligst f\u00f8lg dokumentasjonen." + "connection_error": "Kunne ikke koble til SOMA Connect.", + "missing_configuration": "Soma-komponenten er ikke konfigurert. Vennligst f\u00f8lg dokumentasjonen.", + "result_error": "SOMA Connect svarte med feilstatus." }, "create_entry": { "default": "Vellykket autentisering med Somfy." From b9eb831d291d41b4673325b57be410bb7efb53d5 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 12 Dec 2019 06:37:55 +0100 Subject: [PATCH 269/677] Revert Tahoma removal (#29840) * Revert "Remove Tahoma component #29744 (#29745)" This reverts commit df74272ba6311d527fd07198929c80a45d9fed15. * Revert "Cleanup removed component (#29788)" This reverts commit 3a28361bebbe11d124793a0ec269de74ef180665. --- .coveragerc | 1 + CODEOWNERS | 1 + homeassistant/components/tahoma/__init__.py | 140 ++++++++++ .../components/tahoma/binary_sensor.py | 95 +++++++ homeassistant/components/tahoma/cover.py | 249 ++++++++++++++++++ homeassistant/components/tahoma/manifest.json | 12 + homeassistant/components/tahoma/scene.py | 41 +++ homeassistant/components/tahoma/sensor.py | 106 ++++++++ homeassistant/components/tahoma/switch.py | 109 ++++++++ requirements_all.txt | 3 + 10 files changed, 757 insertions(+) create mode 100644 homeassistant/components/tahoma/__init__.py create mode 100644 homeassistant/components/tahoma/binary_sensor.py create mode 100644 homeassistant/components/tahoma/cover.py create mode 100644 homeassistant/components/tahoma/manifest.json create mode 100644 homeassistant/components/tahoma/scene.py create mode 100644 homeassistant/components/tahoma/sensor.py create mode 100644 homeassistant/components/tahoma/switch.py diff --git a/.coveragerc b/.coveragerc index 0b73599dffa..f4794b59381 100644 --- a/.coveragerc +++ b/.coveragerc @@ -676,6 +676,7 @@ omit = homeassistant/components/systemmonitor/sensor.py homeassistant/components/tado/* homeassistant/components/tado/device_tracker.py + homeassistant/components/tahoma/* homeassistant/components/tank_utility/sensor.py homeassistant/components/tapsaff/binary_sensor.py homeassistant/components/tautulli/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 392c363c648..4fbdca20686 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -318,6 +318,7 @@ homeassistant/components/syncthru/* @nielstron homeassistant/components/synology_srm/* @aerialls homeassistant/components/syslog/* @fabaff homeassistant/components/tado/* @michaelarnauts +homeassistant/components/tahoma/* @philklei homeassistant/components/tautulli/* @ludeeus homeassistant/components/tellduslive/* @fredrike homeassistant/components/template/* @PhracturedBlue diff --git a/homeassistant/components/tahoma/__init__.py b/homeassistant/components/tahoma/__init__.py new file mode 100644 index 00000000000..640cc6418d0 --- /dev/null +++ b/homeassistant/components/tahoma/__init__.py @@ -0,0 +1,140 @@ +"""Support for Tahoma devices.""" +from collections import defaultdict +import logging + +from requests.exceptions import RequestException +from tahoma_api import Action, TahomaApi +import voluptuous as vol + +from homeassistant.const import CONF_EXCLUDE, CONF_PASSWORD, CONF_USERNAME +from homeassistant.helpers import config_validation as cv, discovery +from homeassistant.helpers.entity import Entity + +_LOGGER = logging.getLogger(__name__) + +DOMAIN = "tahoma" + +TAHOMA_ID_FORMAT = "{}_{}" + +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_EXCLUDE, default=[]): vol.All( + cv.ensure_list, [cv.string] + ), + } + ) + }, + extra=vol.ALLOW_EXTRA, +) + +TAHOMA_COMPONENTS = ["scene", "sensor", "cover", "switch", "binary_sensor"] + +TAHOMA_TYPES = { + "io:ExteriorVenetianBlindIOComponent": "cover", + "io:HorizontalAwningIOComponent": "cover", + "io:LightIOSystemSensor": "sensor", + "io:OnOffIOComponent": "switch", + "io:OnOffLightIOComponent": "switch", + "io:RollerShutterGenericIOComponent": "cover", + "io:RollerShutterUnoIOComponent": "cover", + "io:RollerShutterVeluxIOComponent": "cover", + "io:RollerShutterWithLowSpeedManagementIOComponent": "cover", + "io:SomfyBasicContactIOSystemSensor": "sensor", + "io:SomfyContactIOSystemSensor": "sensor", + "io:VerticalExteriorAwningIOComponent": "cover", + "io:VerticalInteriorBlindVeluxIOComponent": "cover", + "io:WindowOpenerVeluxIOComponent": "cover", + "io:GarageOpenerIOComponent": "cover", + "io:DiscreteGarageOpenerIOComponent": "cover", + "rtds:RTDSContactSensor": "sensor", + "rtds:RTDSMotionSensor": "sensor", + "rtds:RTDSSmokeSensor": "smoke", + "rts:BlindRTSComponent": "cover", + "rts:CurtainRTSComponent": "cover", + "rts:DualCurtainRTSComponent": "cover", + "rts:ExteriorVenetianBlindRTSComponent": "cover", + "rts:GarageDoor4TRTSComponent": "switch", + "rts:RollerShutterRTSComponent": "cover", + "rts:VenetianBlindRTSComponent": "cover", +} + + +def setup(hass, config): + """Activate Tahoma component.""" + + conf = config[DOMAIN] + username = conf.get(CONF_USERNAME) + password = conf.get(CONF_PASSWORD) + exclude = conf.get(CONF_EXCLUDE) + try: + api = TahomaApi(username, password) + except RequestException: + _LOGGER.exception("Error when trying to log in to the Tahoma API") + return False + + try: + api.get_setup() + devices = api.get_devices() + scenes = api.get_action_groups() + except RequestException: + _LOGGER.exception("Error when getting devices from the Tahoma API") + return False + + hass.data[DOMAIN] = {"controller": api, "devices": defaultdict(list), "scenes": []} + + for device in devices: + _device = api.get_device(device) + if all(ext not in _device.type for ext in exclude): + device_type = map_tahoma_device(_device) + if device_type is None: + _LOGGER.warning( + "Unsupported type %s for Tahoma device %s", + _device.type, + _device.label, + ) + continue + hass.data[DOMAIN]["devices"][device_type].append(_device) + + for scene in scenes: + hass.data[DOMAIN]["scenes"].append(scene) + + for component in TAHOMA_COMPONENTS: + discovery.load_platform(hass, component, DOMAIN, {}, config) + + return True + + +def map_tahoma_device(tahoma_device): + """Map Tahoma device types to Home Assistant components.""" + return TAHOMA_TYPES.get(tahoma_device.type) + + +class TahomaDevice(Entity): + """Representation of a Tahoma device entity.""" + + def __init__(self, tahoma_device, controller): + """Initialize the device.""" + self.tahoma_device = tahoma_device + self.controller = controller + self._name = self.tahoma_device.label + + @property + def name(self): + """Return the name of the device.""" + return self._name + + @property + def device_state_attributes(self): + """Return the state attributes of the device.""" + return {"tahoma_device_id": self.tahoma_device.url} + + def apply_action(self, cmd_name, *args): + """Apply Action to Device.""" + + action = Action(self.tahoma_device.url) + action.add_command(cmd_name, *args) + self.controller.apply_actions("HomeAssistant", [action]) diff --git a/homeassistant/components/tahoma/binary_sensor.py b/homeassistant/components/tahoma/binary_sensor.py new file mode 100644 index 00000000000..81078ab480b --- /dev/null +++ b/homeassistant/components/tahoma/binary_sensor.py @@ -0,0 +1,95 @@ +"""Support for Tahoma binary sensors.""" +from datetime import timedelta +import logging + +from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.const import ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON + +from . import DOMAIN as TAHOMA_DOMAIN, TahomaDevice + +_LOGGER = logging.getLogger(__name__) + +SCAN_INTERVAL = timedelta(seconds=120) + + +def setup_platform(hass, config, add_entities, 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_entities(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 + self._available = False + + @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 + + @property + def available(self): + """Return True if entity is available.""" + return self._available + + 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: + # 'lowBattery' for low battery warning. 'dead' for not available. + self._battery = self.tahoma_device.active_states["core:SensorDefectState"] + self._available = bool(self._battery != "dead") + else: + self._battery = None + self._available = True + + 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/cover.py b/homeassistant/components/tahoma/cover.py new file mode 100644 index 00000000000..e11c2f4cdf5 --- /dev/null +++ b/homeassistant/components/tahoma/cover.py @@ -0,0 +1,249 @@ +"""Support for Tahoma cover - shutters etc.""" +from datetime import timedelta +import logging + +from homeassistant.components.cover import ( + ATTR_POSITION, + DEVICE_CLASS_AWNING, + DEVICE_CLASS_BLIND, + DEVICE_CLASS_CURTAIN, + DEVICE_CLASS_GARAGE, + DEVICE_CLASS_SHUTTER, + DEVICE_CLASS_WINDOW, + CoverDevice, +) +from homeassistant.util.dt import utcnow + +from . import DOMAIN as TAHOMA_DOMAIN, TahomaDevice + +_LOGGER = logging.getLogger(__name__) + +ATTR_MEM_POS = "memorized_position" +ATTR_RSSI_LEVEL = "rssi_level" +ATTR_LOCK_START_TS = "lock_start_ts" +ATTR_LOCK_END_TS = "lock_end_ts" +ATTR_LOCK_LEVEL = "lock_level" +ATTR_LOCK_ORIG = "lock_originator" + +HORIZONTAL_AWNING = "io:HorizontalAwningIOComponent" + +TAHOMA_DEVICE_CLASSES = { + "io:ExteriorVenetianBlindIOComponent": DEVICE_CLASS_BLIND, + HORIZONTAL_AWNING: DEVICE_CLASS_AWNING, + "io:RollerShutterGenericIOComponent": DEVICE_CLASS_SHUTTER, + "io:RollerShutterUnoIOComponent": DEVICE_CLASS_SHUTTER, + "io:RollerShutterVeluxIOComponent": DEVICE_CLASS_SHUTTER, + "io:RollerShutterWithLowSpeedManagementIOComponent": DEVICE_CLASS_SHUTTER, + "io:VerticalExteriorAwningIOComponent": DEVICE_CLASS_AWNING, + "io:VerticalInteriorBlindVeluxIOComponent": DEVICE_CLASS_BLIND, + "io:WindowOpenerVeluxIOComponent": DEVICE_CLASS_WINDOW, + "io:GarageOpenerIOComponent": DEVICE_CLASS_GARAGE, + "io:DiscreteGarageOpenerIOComponent": DEVICE_CLASS_GARAGE, + "rts:BlindRTSComponent": DEVICE_CLASS_BLIND, + "rts:CurtainRTSComponent": DEVICE_CLASS_CURTAIN, + "rts:DualCurtainRTSComponent": DEVICE_CLASS_CURTAIN, + "rts:ExteriorVenetianBlindRTSComponent": DEVICE_CLASS_BLIND, + "rts:RollerShutterRTSComponent": DEVICE_CLASS_SHUTTER, + "rts:VenetianBlindRTSComponent": DEVICE_CLASS_BLIND, +} + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up the Tahoma covers.""" + controller = hass.data[TAHOMA_DOMAIN]["controller"] + devices = [] + for device in hass.data[TAHOMA_DOMAIN]["devices"]["cover"]: + devices.append(TahomaCover(device, controller)) + add_entities(devices, True) + + +class TahomaCover(TahomaDevice, CoverDevice): + """Representation a Tahoma Cover.""" + + def __init__(self, tahoma_device, controller): + """Initialize the device.""" + super().__init__(tahoma_device, controller) + + self._closure = 0 + # 100 equals open + self._position = 100 + self._closed = False + self._rssi_level = None + self._icon = None + # Can be 0 and bigger + self._lock_timer = 0 + self._lock_start_ts = None + self._lock_end_ts = None + # Can be 'comfortLevel1', 'comfortLevel2', 'comfortLevel3', + # 'comfortLevel4', 'environmentProtection', 'humanProtection', + # 'userLevel1', 'userLevel2' + self._lock_level = None + # Can be 'LSC', 'SAAC', 'SFC', 'UPS', 'externalGateway', 'localUser', + # 'myself', 'rain', 'security', 'temperature', 'timer', 'user', 'wind' + self._lock_originator = None + + def update(self): + """Update method.""" + self.controller.get_states([self.tahoma_device]) + + # For vertical covers + self._closure = self.tahoma_device.active_states.get("core:ClosureState") + # For horizontal covers + if self._closure is None: + self._closure = self.tahoma_device.active_states.get("core:DeploymentState") + + # For all, if available + if "core:PriorityLockTimerState" in self.tahoma_device.active_states: + old_lock_timer = self._lock_timer + self._lock_timer = self.tahoma_device.active_states[ + "core:PriorityLockTimerState" + ] + # Derive timestamps from _lock_timer, only if not already set or + # something has changed + if self._lock_timer > 0: + _LOGGER.debug("Update %s, lock_timer: %d", self._name, self._lock_timer) + if self._lock_start_ts is None: + self._lock_start_ts = utcnow() + if self._lock_end_ts is None or old_lock_timer != self._lock_timer: + self._lock_end_ts = utcnow() + timedelta(seconds=self._lock_timer) + else: + self._lock_start_ts = None + self._lock_end_ts = None + else: + self._lock_timer = 0 + self._lock_start_ts = None + self._lock_end_ts = None + + self._lock_level = self.tahoma_device.active_states.get( + "io:PriorityLockLevelState" + ) + + self._lock_originator = self.tahoma_device.active_states.get( + "io:PriorityLockOriginatorState" + ) + + self._rssi_level = self.tahoma_device.active_states.get("core:RSSILevelState") + + # Define which icon to use + if self._lock_timer > 0: + if self._lock_originator == "wind": + self._icon = "mdi:weather-windy" + else: + self._icon = "mdi:lock-alert" + else: + self._icon = None + + # Define current position. + # _position: 0 is closed, 100 is fully open. + # 'core:ClosureState': 100 is closed, 0 is fully open. + if self._closure is not None: + if self.tahoma_device.type == HORIZONTAL_AWNING: + self._position = self._closure + else: + self._position = 100 - self._closure + if self._position <= 5: + self._position = 0 + if self._position >= 95: + self._position = 100 + self._closed = self._position == 0 + else: + self._position = None + if "core:OpenClosedState" in self.tahoma_device.active_states: + self._closed = ( + self.tahoma_device.active_states["core:OpenClosedState"] == "closed" + ) + else: + self._closed = False + + _LOGGER.debug("Update %s, position: %d", self._name, self._position) + + @property + def current_cover_position(self): + """Return current position of cover.""" + return self._position + + def set_cover_position(self, **kwargs): + """Move the cover to a specific position.""" + if self.tahoma_device.type == "io:WindowOpenerVeluxIOComponent": + command = "setClosure" + else: + command = "setPosition" + + if self.tahoma_device.type == HORIZONTAL_AWNING: + self.apply_action(command, kwargs.get(ATTR_POSITION, 0)) + else: + self.apply_action(command, 100 - kwargs.get(ATTR_POSITION, 0)) + + @property + def is_closed(self): + """Return if the cover is closed.""" + return self._closed + + @property + def device_class(self): + """Return the class of the device.""" + return TAHOMA_DEVICE_CLASSES.get(self.tahoma_device.type) + + @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 "core:Memorized1PositionState" in self.tahoma_device.active_states: + attr[ATTR_MEM_POS] = self.tahoma_device.active_states[ + "core:Memorized1PositionState" + ] + if self._rssi_level is not None: + attr[ATTR_RSSI_LEVEL] = self._rssi_level + if self._lock_start_ts is not None: + attr[ATTR_LOCK_START_TS] = self._lock_start_ts.isoformat() + if self._lock_end_ts is not None: + attr[ATTR_LOCK_END_TS] = self._lock_end_ts.isoformat() + if self._lock_level is not None: + attr[ATTR_LOCK_LEVEL] = self._lock_level + if self._lock_originator is not None: + attr[ATTR_LOCK_ORIG] = self._lock_originator + return attr + + @property + def icon(self): + """Return the icon to use in the frontend, if any.""" + return self._icon + + def open_cover(self, **kwargs): + """Open the cover.""" + self.apply_action("open") + + def close_cover(self, **kwargs): + """Close the cover.""" + self.apply_action("close") + + def stop_cover(self, **kwargs): + """Stop the cover.""" + if ( + self.tahoma_device.type + == "io:RollerShutterWithLowSpeedManagementIOComponent" + ): + self.apply_action("setPosition", "secured") + elif self.tahoma_device.type in ( + "rts:BlindRTSComponent", + "io:ExteriorVenetianBlindIOComponent", + "rts:VenetianBlindRTSComponent", + "rts:DualCurtainRTSComponent", + "rts:ExteriorVenetianBlindRTSComponent", + "rts:BlindRTSComponent", + ): + self.apply_action("my") + elif self.tahoma_device.type in ( + HORIZONTAL_AWNING, + "io:RollerShutterGenericIOComponent", + "io:VerticalExteriorAwningIOComponent", + "io:VerticalInteriorBlindVeluxIOComponent", + "io:WindowOpenerVeluxIOComponent", + ): + self.apply_action("stop") + else: + self.apply_action("stopIdentify") diff --git a/homeassistant/components/tahoma/manifest.json b/homeassistant/components/tahoma/manifest.json new file mode 100644 index 00000000000..1e99d4b288d --- /dev/null +++ b/homeassistant/components/tahoma/manifest.json @@ -0,0 +1,12 @@ +{ + "domain": "tahoma", + "name": "Tahoma", + "documentation": "https://www.home-assistant.io/integrations/tahoma", + "requirements": [ + "tahoma-api==0.0.14" + ], + "dependencies": [], + "codeowners": [ + "@philklei" + ] +} diff --git a/homeassistant/components/tahoma/scene.py b/homeassistant/components/tahoma/scene.py new file mode 100644 index 00000000000..e54ff91a0f6 --- /dev/null +++ b/homeassistant/components/tahoma/scene.py @@ -0,0 +1,41 @@ +"""Support for Tahoma scenes.""" +import logging + +from homeassistant.components.scene import Scene + +from . import DOMAIN as TAHOMA_DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up the Tahoma scenes.""" + controller = hass.data[TAHOMA_DOMAIN]["controller"] + scenes = [] + for scene in hass.data[TAHOMA_DOMAIN]["scenes"]: + scenes.append(TahomaScene(scene, controller)) + add_entities(scenes, True) + + +class TahomaScene(Scene): + """Representation of a Tahoma scene entity.""" + + def __init__(self, tahoma_scene, controller): + """Initialize the scene.""" + self.tahoma_scene = tahoma_scene + self.controller = controller + self._name = self.tahoma_scene.name + + def activate(self): + """Activate the scene.""" + self.controller.launch_action_group(self.tahoma_scene.oid) + + @property + def name(self): + """Return the name of the scene.""" + return self._name + + @property + def device_state_attributes(self): + """Return the state attributes of the scene.""" + return {"tahoma_scene_oid": self.tahoma_scene.oid} diff --git a/homeassistant/components/tahoma/sensor.py b/homeassistant/components/tahoma/sensor.py new file mode 100644 index 00000000000..5279b160d9c --- /dev/null +++ b/homeassistant/components/tahoma/sensor.py @@ -0,0 +1,106 @@ +"""Support for Tahoma sensors.""" +from datetime import timedelta +import logging + +from homeassistant.const import ATTR_BATTERY_LEVEL +from homeassistant.helpers.entity import Entity + +from . import DOMAIN as TAHOMA_DOMAIN, TahomaDevice + +_LOGGER = logging.getLogger(__name__) + +SCAN_INTERVAL = timedelta(seconds=60) + +ATTR_RSSI_LEVEL = "rssi_level" + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up Tahoma controller devices.""" + controller = hass.data[TAHOMA_DOMAIN]["controller"] + devices = [] + for device in hass.data[TAHOMA_DOMAIN]["devices"]["sensor"]: + devices.append(TahomaSensor(device, controller)) + add_entities(devices, True) + + +class TahomaSensor(TahomaDevice, Entity): + """Representation of a Tahoma Sensor.""" + + def __init__(self, tahoma_device, controller): + """Initialize the sensor.""" + self.current_value = None + self._available = False + super().__init__(tahoma_device, controller) + + @property + def state(self): + """Return the name of the sensor.""" + return self.current_value + + @property + def unit_of_measurement(self): + """Return the unit of measurement of this entity, if any.""" + if self.tahoma_device.type == "Temperature Sensor": + return None + if self.tahoma_device.type == "io:SomfyContactIOSystemSensor": + return None + if self.tahoma_device.type == "io:SomfyBasicContactIOSystemSensor": + return None + if self.tahoma_device.type == "io:LightIOSystemSensor": + return "lx" + if self.tahoma_device.type == "Humidity Sensor": + return "%" + if self.tahoma_device.type == "rtds:RTDSContactSensor": + return None + if self.tahoma_device.type == "rtds:RTDSMotionSensor": + return None + + def update(self): + """Update the state.""" + self.controller.get_states([self.tahoma_device]) + if self.tahoma_device.type == "io:LightIOSystemSensor": + self.current_value = self.tahoma_device.active_states["core:LuminanceState"] + self._available = bool( + self.tahoma_device.active_states.get("core:StatusState") == "available" + ) + if self.tahoma_device.type == "io:SomfyContactIOSystemSensor": + self.current_value = self.tahoma_device.active_states["core:ContactState"] + self._available = bool( + self.tahoma_device.active_states.get("core:StatusState") == "available" + ) + if self.tahoma_device.type == "io:SomfyBasicContactIOSystemSensor": + self.current_value = self.tahoma_device.active_states["core:ContactState"] + self._available = bool( + self.tahoma_device.active_states.get("core:StatusState") == "available" + ) + if self.tahoma_device.type == "rtds:RTDSContactSensor": + self.current_value = self.tahoma_device.active_states["core:ContactState"] + self._available = True + if self.tahoma_device.type == "rtds:RTDSMotionSensor": + self.current_value = self.tahoma_device.active_states["core:OccupancyState"] + self._available = True + + _LOGGER.debug("Update %s, value: %d", self._name, self.current_value) + + @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 "core:RSSILevelState" in self.tahoma_device.active_states: + attr[ATTR_RSSI_LEVEL] = self.tahoma_device.active_states[ + "core:RSSILevelState" + ] + if "core:SensorDefectState" in self.tahoma_device.active_states: + attr[ATTR_BATTERY_LEVEL] = self.tahoma_device.active_states[ + "core:SensorDefectState" + ] + return attr + + @property + def available(self): + """Return True if entity is available.""" + return self._available diff --git a/homeassistant/components/tahoma/switch.py b/homeassistant/components/tahoma/switch.py new file mode 100644 index 00000000000..a0a95ab47ce --- /dev/null +++ b/homeassistant/components/tahoma/switch.py @@ -0,0 +1,109 @@ +"""Support for Tahoma switches.""" +import logging + +from homeassistant.components.switch import SwitchDevice +from homeassistant.const import STATE_OFF, STATE_ON + +from . import DOMAIN as TAHOMA_DOMAIN, TahomaDevice + +_LOGGER = logging.getLogger(__name__) + +ATTR_RSSI_LEVEL = "rssi_level" + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up Tahoma switches.""" + controller = hass.data[TAHOMA_DOMAIN]["controller"] + devices = [] + for switch in hass.data[TAHOMA_DOMAIN]["devices"]["switch"]: + devices.append(TahomaSwitch(switch, controller)) + add_entities(devices, True) + + +class TahomaSwitch(TahomaDevice, SwitchDevice): + """Representation a Tahoma Switch.""" + + def __init__(self, tahoma_device, controller): + """Initialize the switch.""" + super().__init__(tahoma_device, controller) + self._state = STATE_OFF + self._skip_update = False + self._available = False + + def update(self): + """Update method.""" + # Postpone the immediate state check for changes that take time. + if self._skip_update: + self._skip_update = False + return + + self.controller.get_states([self.tahoma_device]) + + if self.tahoma_device.type == "io:OnOffLightIOComponent": + if self.tahoma_device.active_states.get("core:OnOffState") == "on": + self._state = STATE_ON + else: + self._state = STATE_OFF + + self._available = bool( + self.tahoma_device.active_states.get("core:StatusState") == "available" + ) + + _LOGGER.debug("Update %s, state: %s", self._name, self._state) + + @property + def device_class(self): + """Return the class of the device.""" + if self.tahoma_device.type == "rts:GarageDoor4TRTSComponent": + return "garage" + return None + + def turn_on(self, **kwargs): + """Send the on command.""" + _LOGGER.debug("Turn on: %s", self._name) + if self.tahoma_device.type == "rts:GarageDoor4TRTSComponent": + self.toggle() + else: + self.apply_action("on") + self._skip_update = True + self._state = STATE_ON + + def turn_off(self, **kwargs): + """Send the off command.""" + _LOGGER.debug("Turn off: %s", self._name) + if self.tahoma_device.type == "rts:GarageDoor4TRTSComponent": + return + + self.apply_action("off") + self._skip_update = True + self._state = STATE_OFF + + def toggle(self, **kwargs): + """Click the switch.""" + self.apply_action("cycle") + + @property + def is_on(self): + """Get whether the switch is in on state.""" + if self.tahoma_device.type == "rts:GarageDoor4TRTSComponent": + return False + return bool(self._state == STATE_ON) + + @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 "core:RSSILevelState" in self.tahoma_device.active_states: + attr[ATTR_RSSI_LEVEL] = self.tahoma_device.active_states[ + "core:RSSILevelState" + ] + return attr + + @property + def available(self): + """Return True if entity is available.""" + return self._available diff --git a/requirements_all.txt b/requirements_all.txt index 5f6e9ac78aa..c91ffeace3f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1906,6 +1906,9 @@ swisshydrodata==0.0.3 # homeassistant.components.synology_srm synology-srm==0.0.7 +# homeassistant.components.tahoma +tahoma-api==0.0.14 + # homeassistant.components.tank_utility tank_utility==1.4.0 From 7931a0b675d43e7f83a3f572799645bec31cc8f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Thu, 12 Dec 2019 08:15:32 +0200 Subject: [PATCH 270/677] Use Bionic's ffmpeg on Travis, jonathonf/ffmpeg-4 is N/A at the moment (#29860) --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index e35787bb1e8..2660d805726 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,6 @@ sudo: false dist: bionic addons: apt: - sources: - - sourceline: "ppa:jonathonf/ffmpeg-4" packages: - libudev-dev - libavformat-dev From 914b49566a812ee96646f5a42d11cc017ad24937 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Thu, 12 Dec 2019 02:24:57 -0700 Subject: [PATCH 271/677] Bump aioambient to 1.0.2 (#29850) --- homeassistant/components/ambient_station/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/ambient_station/manifest.json b/homeassistant/components/ambient_station/manifest.json index 8f363ba219f..1e6c06f260a 100644 --- a/homeassistant/components/ambient_station/manifest.json +++ b/homeassistant/components/ambient_station/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/ambient_station", "requirements": [ - "aioambient==0.3.2" + "aioambient==1.0.2" ], "dependencies": [], "codeowners": [ diff --git a/requirements_all.txt b/requirements_all.txt index c91ffeace3f..fb7b193abab 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -133,7 +133,7 @@ aio_geojson_geonetnz_volcano==0.5 aio_geojson_nsw_rfs_incidents==0.1 # homeassistant.components.ambient_station -aioambient==0.3.2 +aioambient==1.0.2 # homeassistant.components.asuswrt aioasuswrt==1.1.22 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3812b8f0b33..9716cd2f950 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -44,7 +44,7 @@ aio_geojson_geonetnz_volcano==0.5 aio_geojson_nsw_rfs_incidents==0.1 # homeassistant.components.ambient_station -aioambient==0.3.2 +aioambient==1.0.2 # homeassistant.components.asuswrt aioasuswrt==1.1.22 From 4b335787577a20c935f696c0e8673dc91eaac67b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 12 Dec 2019 14:45:00 +0100 Subject: [PATCH 272/677] Fix package import sort on dwd_weather_warnings (#29874) --- homeassistant/components/dwd_weather_warnings/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/dwd_weather_warnings/sensor.py b/homeassistant/components/dwd_weather_warnings/sensor.py index 46b9ac560e5..695b839d18c 100644 --- a/homeassistant/components/dwd_weather_warnings/sensor.py +++ b/homeassistant/components/dwd_weather_warnings/sensor.py @@ -21,8 +21,8 @@ import voluptuous as vol from homeassistant.components.rest.sensor import RestData from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ATTR_ATTRIBUTION, CONF_MONITORED_CONDITIONS, CONF_NAME -import homeassistant.helpers.config_validation as cv from homeassistant.helpers.aiohttp_client import SERVER_SOFTWARE as HA_USER_AGENT +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle import homeassistant.util.dt as dt_util From 7d68e88d31246e268db809225e3d924acb1fc352 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Thu, 12 Dec 2019 15:56:32 +0100 Subject: [PATCH 273/677] Sort import for tests/components/feedreader/test_init.py (#29878) This unblocks https://github.com/home-assistant/home-assistant/pull/29739 --- tests/components/feedreader/test_init.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/components/feedreader/test_init.py b/tests/components/feedreader/test_init.py index bffbe9676fa..d74bbd79d80 100644 --- a/tests/components/feedreader/test_init.py +++ b/tests/components/feedreader/test_init.py @@ -1,6 +1,5 @@ """The tests for the feedreader component.""" from datetime import timedelta -from genericpath import exists from logging import getLogger from os import remove import time @@ -8,6 +7,8 @@ import unittest from unittest import mock from unittest.mock import patch +from genericpath import exists + from homeassistant.components import feedreader from homeassistant.components.feedreader import ( CONF_MAX_ENTRIES, From c58c10ab7cd75d093c4a64891c1193a2e01a3f55 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Thu, 12 Dec 2019 15:58:47 +0100 Subject: [PATCH 274/677] Add isort to CI and pre-commit (#29739) * add isort to CI and pre-commit * disable wrong-import-order in pylintrc * :pencil2: Tweak Co-authored-by: Franck Nijhof --- .pre-commit-config-all.yaml | 4 ++++ .pre-commit-config.yaml | 4 ++++ azure-pipelines-ci.yml | 4 ++++ pylintrc | 4 +++- requirements_test_pre_commit.txt | 1 + 5 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config-all.yaml b/.pre-commit-config-all.yaml index cb01bff85cb..ec9492e0210 100644 --- a/.pre-commit-config-all.yaml +++ b/.pre-commit-config-all.yaml @@ -35,6 +35,10 @@ repos: - --format=custom - --configfile=tests/bandit.yaml files: ^(homeassistant|script|tests)/.+\.py$ +- repo: https://github.com/pre-commit/mirrors-isort + rev: v4.3.21 + hooks: + - id: isort # Using a local "system" mypy instead of the mypy hook, because its # results depend on what is installed. And the mypy hook runs in a # virtualenv of its own, meaning we'd need to install and maintain diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e6f37ae6355..23d1d9c73f9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,3 +31,7 @@ repos: - --format=custom - --configfile=tests/bandit.yaml files: ^(homeassistant|script|tests)/.+\.py$ +- repo: https://github.com/pre-commit/mirrors-isort + rev: v4.3.21 + hooks: + - id: isort diff --git a/azure-pipelines-ci.yml b/azure-pipelines-ci.yml index cbad0c9af08..5a289cbbc70 100644 --- a/azure-pipelines-ci.yml +++ b/azure-pipelines-ci.yml @@ -54,6 +54,10 @@ stages: . venv/bin/activate pre-commit run bandit --all-files displayName: 'Run bandit' + - script: | + . venv/bin/activate + pre-commit run isort --all-files + displayName: 'Run isort' - job: 'Validate' pool: vmImage: 'ubuntu-latest' diff --git a/pylintrc b/pylintrc index 3235d583865..0ffbb138f9e 100644 --- a/pylintrc +++ b/pylintrc @@ -25,6 +25,7 @@ good-names=id,i,j,k,ex,Run,_,fp # unnecessary-pass - readability for functions which only contain pass # import-outside-toplevel - TODO # too-many-ancestors - it's too strict. +# wrong-import-order - isort guards this disable= format, abstract-class-little-used, @@ -49,7 +50,8 @@ disable= too-many-statements, too-many-boolean-expressions, unnecessary-pass, - unused-argument + unused-argument, + wrong-import-order enable= use-symbolic-message-instead diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index 14b866c7034..7a20962ff7c 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -4,4 +4,5 @@ bandit==1.6.2 black==19.10b0 flake8-docstrings==1.5.0 flake8==3.7.9 +isort==v4.3.21 pydocstyle==5.0.1 From 130571c478b08a06e566ab3c08965d2c784ceead Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Thu, 12 Dec 2019 17:46:33 +0200 Subject: [PATCH 275/677] Remove no longer needed auth.util, use secrets instead (#29861) --- homeassistant/auth/models.py | 6 +++--- homeassistant/auth/util.py | 13 ------------- homeassistant/components/http/auth.py | 4 ++-- homeassistant/components/mobile_app/http_api.py | 6 +++--- homeassistant/components/owntracks/config_flow.py | 7 ++++--- homeassistant/components/rachio/__init__.py | 4 ++-- homeassistant/components/smartthings/smartapp.py | 3 ++- homeassistant/components/stream/__init__.py | 4 ++-- homeassistant/components/webhook/__init__.py | 4 ++-- homeassistant/helpers/config_entry_oauth2_flow.py | 4 ++-- tests/components/smartthings/conftest.py | 3 ++- 11 files changed, 24 insertions(+), 34 deletions(-) delete mode 100644 homeassistant/auth/util.py diff --git a/homeassistant/auth/models.py b/homeassistant/auth/models.py index 6889d17a25f..08f2f375b41 100644 --- a/homeassistant/auth/models.py +++ b/homeassistant/auth/models.py @@ -1,5 +1,6 @@ """Auth models.""" from datetime import datetime, timedelta +import secrets from typing import Dict, List, NamedTuple, Optional import uuid @@ -9,7 +10,6 @@ from homeassistant.util import dt as dt_util from . import permissions as perm_mdl from .const import GROUP_ID_ADMIN -from .util import generate_secret TOKEN_TYPE_NORMAL = "normal" TOKEN_TYPE_SYSTEM = "system" @@ -96,8 +96,8 @@ class RefreshToken: ) id = attr.ib(type=str, factory=lambda: uuid.uuid4().hex) created_at = attr.ib(type=datetime, factory=dt_util.utcnow) - token = attr.ib(type=str, factory=lambda: generate_secret(64)) - jwt_key = attr.ib(type=str, factory=lambda: generate_secret(64)) + token = attr.ib(type=str, factory=lambda: secrets.token_hex(64)) + jwt_key = attr.ib(type=str, factory=lambda: secrets.token_hex(64)) last_used_at = attr.ib(type=Optional[datetime], default=None) last_used_ip = attr.ib(type=Optional[str], default=None) diff --git a/homeassistant/auth/util.py b/homeassistant/auth/util.py deleted file mode 100644 index 83834fa7683..00000000000 --- a/homeassistant/auth/util.py +++ /dev/null @@ -1,13 +0,0 @@ -"""Auth utils.""" -import binascii -import os - - -def generate_secret(entropy: int = 32) -> str: - """Generate a secret. - - Backport of secrets.token_hex from Python 3.6 - - Event loop friendly. - """ - return binascii.hexlify(os.urandom(entropy)).decode("ascii") diff --git a/homeassistant/components/http/auth.py b/homeassistant/components/http/auth.py index 3866e770de0..58814b77e2d 100644 --- a/homeassistant/components/http/auth.py +++ b/homeassistant/components/http/auth.py @@ -1,11 +1,11 @@ """Authentication for HTTP component.""" import logging +import secrets from aiohttp import hdrs from aiohttp.web import middleware import jwt -from homeassistant.auth.util import generate_secret from homeassistant.core import callback from homeassistant.util import dt as dt_util @@ -26,7 +26,7 @@ def async_sign_path(hass, refresh_token_id, path, expiration): secret = hass.data.get(DATA_SIGN_SECRET) if secret is None: - secret = hass.data[DATA_SIGN_SECRET] = generate_secret() + secret = hass.data[DATA_SIGN_SECRET] = secrets.token_hex() now = dt_util.utcnow() return "{}?{}={}".format( diff --git a/homeassistant/components/mobile_app/http_api.py b/homeassistant/components/mobile_app/http_api.py index ede18528f81..9581a374384 100644 --- a/homeassistant/components/mobile_app/http_api.py +++ b/homeassistant/components/mobile_app/http_api.py @@ -1,11 +1,11 @@ """Provides an HTTP API for mobile_app.""" +import secrets from typing import Dict import uuid from aiohttp.web import Request, Response from nacl.secret import SecretBox -from homeassistant.auth.util import generate_secret from homeassistant.components.http import HomeAssistantView from homeassistant.components.http.data_validator import RequestDataValidator from homeassistant.const import CONF_WEBHOOK_ID, HTTP_CREATED @@ -34,7 +34,7 @@ class RegistrationsView(HomeAssistantView): """Handle the POST request for registration.""" hass = request.app["hass"] - webhook_id = generate_secret() + webhook_id = secrets.token_hex() if hass.components.cloud.async_active_subscription(): data[ @@ -46,7 +46,7 @@ class RegistrationsView(HomeAssistantView): data[CONF_WEBHOOK_ID] = webhook_id if data[ATTR_SUPPORTS_ENCRYPTION] and supports_encryption(): - data[CONF_SECRET] = generate_secret(SecretBox.KEY_SIZE) + data[CONF_SECRET] = secrets.token_hex(SecretBox.KEY_SIZE) data[CONF_USER_ID] = request["hass_user"].id diff --git a/homeassistant/components/owntracks/config_flow.py b/homeassistant/components/owntracks/config_flow.py index 82f89de6363..0aba24217cc 100644 --- a/homeassistant/components/owntracks/config_flow.py +++ b/homeassistant/components/owntracks/config_flow.py @@ -1,6 +1,7 @@ """Config flow for OwnTracks.""" +import secrets + from homeassistant import config_entries -from homeassistant.auth.util import generate_secret from homeassistant.const import CONF_WEBHOOK_ID from .const import DOMAIN # noqa pylint: disable=unused-import @@ -25,7 +26,7 @@ class OwnTracksFlow(config_entries.ConfigFlow, domain=DOMAIN): webhook_id, webhook_url, cloudhook = await self._get_webhook_id() - secret = generate_secret(16) + secret = secrets.token_hex(16) if supports_encryption(): secret_desc = f"The encryption key is {secret} (on Android under preferences -> advanced)" @@ -53,7 +54,7 @@ class OwnTracksFlow(config_entries.ConfigFlow, domain=DOMAIN): if self._async_current_entries(): return self.async_abort(reason="one_instance_allowed") webhook_id, _webhook_url, cloudhook = await self._get_webhook_id() - secret = generate_secret(16) + secret = secrets.token_hex(16) return self.async_create_entry( title="OwnTracks", data={ diff --git a/homeassistant/components/rachio/__init__.py b/homeassistant/components/rachio/__init__.py index 4d67175ecd5..1b24f4e0071 100644 --- a/homeassistant/components/rachio/__init__.py +++ b/homeassistant/components/rachio/__init__.py @@ -1,13 +1,13 @@ """Integration with the Rachio Iro sprinkler system controller.""" import asyncio import logging +import secrets from typing import Optional from aiohttp import web from rachiopy import Rachio import voluptuous as vol -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 from homeassistant.helpers import config_validation as cv, discovery @@ -115,7 +115,7 @@ def setup(hass, config) -> bool: # Get the URL of this server custom_url = config[DOMAIN].get(CONF_CUSTOM_URL) hass_url = hass.config.api.base_url if custom_url is None else custom_url - rachio.webhook_auth = generate_secret() + rachio.webhook_auth = secrets.token_hex() rachio.webhook_url = hass_url + WEBHOOK_PATH # Get the API user diff --git a/homeassistant/components/smartthings/smartapp.py b/homeassistant/components/smartthings/smartapp.py index 9b67df21491..74feb93eec4 100644 --- a/homeassistant/components/smartthings/smartapp.py +++ b/homeassistant/components/smartthings/smartapp.py @@ -2,6 +2,7 @@ import asyncio import functools import logging +import secrets from urllib.parse import urlparse from uuid import uuid4 @@ -208,7 +209,7 @@ async def setup_smartapp_endpoint(hass: HomeAssistantType): # Create config config = { CONF_INSTANCE_ID: str(uuid4()), - CONF_WEBHOOK_ID: webhook.generate_secret(), + CONF_WEBHOOK_ID: secrets.token_hex(), CONF_CLOUDHOOK_URL: None, } await store.async_save(config) diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index 2bfd37a2641..d88f90a83f8 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -1,10 +1,10 @@ """Provide functionality to stream video source.""" import logging +import secrets import threading import voluptuous as vol -from homeassistant.auth.util import generate_secret from homeassistant.const import CONF_FILENAME, EVENT_HOMEASSISTANT_STOP from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError @@ -72,7 +72,7 @@ def request_stream(hass, stream_source, *, fmt="hls", keepalive=False, options=N stream.add_provider(fmt) if not stream.access_token: - stream.access_token = generate_secret() + stream.access_token = secrets.token_hex() stream.start() return hass.data[DOMAIN][ATTR_ENDPOINTS][fmt].format(stream.access_token) except Exception: diff --git a/homeassistant/components/webhook/__init__.py b/homeassistant/components/webhook/__init__.py index d51771d0b16..217baf42f3a 100644 --- a/homeassistant/components/webhook/__init__.py +++ b/homeassistant/components/webhook/__init__.py @@ -1,10 +1,10 @@ """Webhooks for Home Assistant.""" import logging +import secrets from aiohttp.web import Request, Response import voluptuous as vol -from homeassistant.auth.util import generate_secret from homeassistant.components import websocket_api from homeassistant.components.http.view import HomeAssistantView from homeassistant.core import callback @@ -46,7 +46,7 @@ def async_unregister(hass, webhook_id): @callback def async_generate_id(): """Generate a webhook_id.""" - return generate_secret(entropy=32) + return secrets.token_hex(32) @callback diff --git a/homeassistant/helpers/config_entry_oauth2_flow.py b/homeassistant/helpers/config_entry_oauth2_flow.py index 4ea82f5f047..2fdfea8673f 100644 --- a/homeassistant/helpers/config_entry_oauth2_flow.py +++ b/homeassistant/helpers/config_entry_oauth2_flow.py @@ -8,6 +8,7 @@ This module exists of the following parts: from abc import ABC, ABCMeta, abstractmethod import asyncio import logging +import secrets import time from typing import Any, Awaitable, Callable, Dict, Optional, cast @@ -18,7 +19,6 @@ import voluptuous as vol from yarl import URL from homeassistant import config_entries -from homeassistant.auth.util import generate_secret from homeassistant.components.http import HomeAssistantView from homeassistant.core import HomeAssistant, callback @@ -441,7 +441,7 @@ def _encode_jwt(hass: HomeAssistant, data: dict) -> str: secret = hass.data.get(DATA_JWT_SECRET) if secret is None: - secret = hass.data[DATA_JWT_SECRET] = generate_secret() + secret = hass.data[DATA_JWT_SECRET] = secrets.token_hex() return jwt.encode(data, secret, algorithm="HS256").decode() diff --git a/tests/components/smartthings/conftest.py b/tests/components/smartthings/conftest.py index b3b172e7606..0dc71ea72b9 100644 --- a/tests/components/smartthings/conftest.py +++ b/tests/components/smartthings/conftest.py @@ -1,4 +1,5 @@ """Test configuration and mocks for the SmartThings component.""" +import secrets from uuid import uuid4 from asynctest import Mock, patch @@ -160,7 +161,7 @@ def installed_apps_fixture(installed_app, locations, app): @pytest.fixture(name="config_file") def config_file_fixture(): """Fixture representing the local config file contents.""" - return {CONF_INSTANCE_ID: str(uuid4()), CONF_WEBHOOK_ID: webhook.generate_secret()} + return {CONF_INSTANCE_ID: str(uuid4()), CONF_WEBHOOK_ID: secrets.token_hex()} @pytest.fixture(name="smartthings_mock") From 1fee400dcd94229db56a2ada5e3c335aa139146f Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Thu, 12 Dec 2019 11:10:43 -0500 Subject: [PATCH 276/677] =?UTF-8?q?Revert=20"Sort=20import=20for=20tests/c?= =?UTF-8?q?omponents/feedreader/test=5Finit.=E2=80=A6=20(#29882)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 7d68e88d31246e268db809225e3d924acb1fc352. --- tests/components/feedreader/test_init.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/components/feedreader/test_init.py b/tests/components/feedreader/test_init.py index d74bbd79d80..bffbe9676fa 100644 --- a/tests/components/feedreader/test_init.py +++ b/tests/components/feedreader/test_init.py @@ -1,5 +1,6 @@ """The tests for the feedreader component.""" from datetime import timedelta +from genericpath import exists from logging import getLogger from os import remove import time @@ -7,8 +8,6 @@ import unittest from unittest import mock from unittest.mock import patch -from genericpath import exists - from homeassistant.components import feedreader from homeassistant.components.feedreader import ( CONF_MAX_ENTRIES, From 327b5c3c94cb8e98bb31411edda6f60d47ee249a Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Thu, 12 Dec 2019 12:16:51 -0500 Subject: [PATCH 277/677] Log ZHA bind/unbind operations status (#29842) * Log bind/unbind operation result. * Use ZDO consts. * Use device logger for bind/unbind results. * Lint. --- homeassistant/components/zha/api.py | 50 ++++++++++++++++++----------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/zha/api.py b/homeassistant/components/zha/api.py index 462afd777b9..5f1b8e16512 100644 --- a/homeassistant/components/zha/api.py +++ b/homeassistant/components/zha/api.py @@ -70,8 +70,6 @@ ATTR_IEEE_ADDRESS = "ieee_address" ATTR_IEEE = "ieee" ATTR_SOURCE_IEEE = "source_ieee" ATTR_TARGET_IEEE = "target_ieee" -BIND_REQUEST = 0x0021 -UNBIND_REQUEST = 0x0022 SERVICE_PERMIT = "permit" SERVICE_REMOVE = "remove" @@ -717,9 +715,11 @@ async def websocket_bind_devices(hass, connection, msg): zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] source_ieee = msg[ATTR_SOURCE_IEEE] target_ieee = msg[ATTR_TARGET_IEEE] - await async_binding_operation(zha_gateway, source_ieee, target_ieee, BIND_REQUEST) + await async_binding_operation( + zha_gateway, source_ieee, target_ieee, zdo_types.ZDOCmd.Bind_req + ) _LOGGER.info( - "Issue bind devices: %s %s", + "Issued bind devices: %s %s", f"{ATTR_SOURCE_IEEE}: [{source_ieee}]", f"{ATTR_TARGET_IEEE}: [{target_ieee}]", ) @@ -739,9 +739,11 @@ async def websocket_unbind_devices(hass, connection, msg): zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] source_ieee = msg[ATTR_SOURCE_IEEE] target_ieee = msg[ATTR_TARGET_IEEE] - await async_binding_operation(zha_gateway, source_ieee, target_ieee, UNBIND_REQUEST) + await async_binding_operation( + zha_gateway, source_ieee, target_ieee, zdo_types.ZDOCmd.Unbind_req + ) _LOGGER.info( - "Issue unbind devices: %s %s", + "Issued unbind devices: %s %s", f"{ATTR_SOURCE_IEEE}: [{source_ieee}]", f"{ATTR_TARGET_IEEE}: [{target_ieee}]", ) @@ -764,22 +766,34 @@ async def async_binding_operation(zha_gateway, source_ieee, target_ieee, operati zdo = cluster_pair.source_cluster.endpoint.device.zdo - _LOGGER.debug( - "processing binding operation for: %s %s %s", - f"{ATTR_SOURCE_IEEE}: [{source_ieee}]", - f"{ATTR_TARGET_IEEE}: [{target_ieee}]", - "{}: {}".format("cluster", cluster_pair.source_cluster.cluster_id), + op_msg = "cluster: %s %s --> [%s]" + op_params = ( + cluster_pair.source_cluster.cluster_id, + operation.name, + target_ieee, ) + zdo.debug("processing " + op_msg, *op_params) + bind_tasks.append( - zdo.request( - operation, - source_device.ieee, - cluster_pair.source_cluster.endpoint.endpoint_id, - cluster_pair.source_cluster.cluster_id, - destination_address, + ( + zdo.request( + operation, + source_device.ieee, + cluster_pair.source_cluster.endpoint.endpoint_id, + cluster_pair.source_cluster.cluster_id, + destination_address, + ), + op_msg, + op_params, ) ) - await asyncio.gather(*bind_tasks) + res = await asyncio.gather(*(t[0] for t in bind_tasks), return_exceptions=True) + for outcome, log_msg in zip(res, bind_tasks): + if isinstance(outcome, Exception): + fmt = log_msg[1] + " failed: %s" + else: + fmt = log_msg[1] + " completed: %s" + zdo.debug(fmt, *(log_msg[2] + (outcome,))) def async_load_api(hass): From 7c42f4b45b62a0d7cbbcf97630b637047dd049ef Mon Sep 17 00:00:00 2001 From: John Luetke Date: Thu, 12 Dec 2019 10:43:49 -0800 Subject: [PATCH 278/677] (Re)Add support for multiple Pi-Holes (#27569) * Update configuration schema to support multiple Pi-holes * Construct sensors for each configured Pi-hole * Ensure each Pi-hole has a unique name * Update services to handle multiple Pi-holes * Update tests for multiple configurations * Refactor tests to support service testing * Fix else-raise per pyliunt * Per code review, add all entities in a single call * Per code review, add the default name as default. * Per code review, add cv.ensure_list to prevent breaking change * Per code review, move name validation to schema * Remove default name * Per code review, validate api_key in schema definition * Per code review, rename variables * Per code review, use list comprehension * Ensure unique slug names in config validation * Per code review, refactor to CoroutineMock * Fix adding sensor entities * Per code review, refactor mock function creation * Per code review, refactor mock function return values --- homeassistant/components/pi_hole/__init__.py | 202 ++++++++++++++---- homeassistant/components/pi_hole/const.py | 6 +- homeassistant/components/pi_hole/sensor.py | 8 +- .../components/pi_hole/services.yaml | 11 +- tests/components/pi_hole/test_init.py | 125 ++++++++--- 5 files changed, 268 insertions(+), 84 deletions(-) diff --git a/homeassistant/components/pi_hole/__init__.py b/homeassistant/components/pi_hole/__init__.py index 8ee21af7858..ed6144af47e 100644 --- a/homeassistant/components/pi_hole/__init__.py +++ b/homeassistant/components/pi_hole/__init__.py @@ -20,7 +20,7 @@ from homeassistant.util import Throttle from .const import ( CONF_LOCATION, - DEFAULT_HOST, + CONF_SLUG, DEFAULT_LOCATION, DEFAULT_NAME, DEFAULT_SSL, @@ -29,82 +29,194 @@ from .const import ( MIN_TIME_BETWEEN_UPDATES, SERVICE_DISABLE, SERVICE_DISABLE_ATTR_DURATION, + SERVICE_DISABLE_ATTR_NAME, SERVICE_ENABLE, + SERVICE_ENABLE_ATTR_NAME, ) + +def ensure_unique_names_and_slugs(config): + """Ensure that each configuration dict contains a unique `name` value.""" + names = {} + slugs = {} + for conf in config: + if conf[CONF_NAME] not in names and conf[CONF_SLUG] not in slugs: + names[conf[CONF_NAME]] = conf[CONF_HOST] + slugs[conf[CONF_SLUG]] = conf[CONF_HOST] + else: + raise vol.Invalid( + "Duplicate name '{}' (or slug '{}') for '{}' (already in use by '{}'). Each configured Pi-hole must have a unique name.".format( + conf[CONF_NAME], + conf[CONF_SLUG], + conf[CONF_HOST], + names.get(conf[CONF_NAME], slugs[conf[CONF_SLUG]]), + ) + ) + return config + + +def coerce_slug(config): + """Coerce the name of the Pi-Hole into a slug.""" + config[CONF_SLUG] = cv.slugify(config[CONF_NAME]) + return config + + LOGGER = logging.getLogger(__name__) +PI_HOLE_SCHEMA = vol.Schema( + vol.All( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_API_KEY): cv.string, + vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean, + vol.Optional(CONF_LOCATION, default=DEFAULT_LOCATION): cv.string, + vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean, + }, + coerce_slug, + ) +) + CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.Schema( - { - vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_API_KEY): cv.string, - vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean, - vol.Optional(CONF_LOCATION, default=DEFAULT_LOCATION): cv.string, - vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean, - } + vol.All(cv.ensure_list, [PI_HOLE_SCHEMA], ensure_unique_names_and_slugs) ) }, extra=vol.ALLOW_EXTRA, ) -SERVICE_DISABLE_SCHEMA = vol.Schema( - { - vol.Required(SERVICE_DISABLE_ATTR_DURATION): vol.All( - cv.time_period_str, cv.positive_timedelta - ) - } -) - async def async_setup(hass, config): """Set up the pi_hole integration.""" - conf = config[DOMAIN] - name = conf[CONF_NAME] - host = conf[CONF_HOST] - use_tls = conf[CONF_SSL] - verify_tls = conf[CONF_VERIFY_SSL] - location = conf[CONF_LOCATION] - api_key = conf.get(CONF_API_KEY) + def get_data(): + """Retrive component data.""" + return hass.data[DOMAIN] - LOGGER.debug("Setting up %s integration with host %s", DOMAIN, host) + def ensure_api_token(call_data): + """Ensure the Pi-Hole to be enabled/disabled has a api_token configured.""" + data = get_data() + if SERVICE_DISABLE_ATTR_NAME not in call_data: + for slug in data: + call_data[SERVICE_DISABLE_ATTR_NAME] = data[slug].name + ensure_api_token(call_data) - session = async_get_clientsession(hass, verify_tls) - pi_hole = PiHoleData( - Hole( - host, hass.loop, session, location=location, tls=use_tls, api_token=api_key - ), - name, + call_data[SERVICE_DISABLE_ATTR_NAME] = None + else: + slug = cv.slugify(call_data[SERVICE_DISABLE_ATTR_NAME]) + + if (data[slug]).api.api_token is None: + raise vol.Invalid( + "Pi-hole '{}' must have an api_key provided in configuration to be enabled.".format( + pi_hole.name + ) + ) + + return call_data + + service_disable_schema = vol.Schema( # pylint: disable=invalid-name + vol.All( + { + vol.Required(SERVICE_DISABLE_ATTR_DURATION): vol.All( + cv.time_period_str, cv.positive_timedelta + ), + vol.Optional(SERVICE_DISABLE_ATTR_NAME): vol.In( + [conf[CONF_NAME] for conf in config[DOMAIN]], msg="Unknown Pi-Hole", + ), + }, + ensure_api_token, + ) ) - await pi_hole.async_update() + service_enable_schema = vol.Schema( + { + vol.Optional(SERVICE_ENABLE_ATTR_NAME): vol.In( + [conf[CONF_NAME] for conf in config[DOMAIN]], msg="Unknown Pi-Hole" + ) + } + ) - hass.data[DOMAIN] = pi_hole + hass.data[DOMAIN] = {} - async def handle_disable(call): - if api_key is None: - raise vol.Invalid("Pi-hole api_key must be provided in configuration") + for conf in config[DOMAIN]: + name = conf[CONF_NAME] + slug = conf[CONF_SLUG] + host = conf[CONF_HOST] + use_tls = conf[CONF_SSL] + verify_tls = conf[CONF_VERIFY_SSL] + location = conf[CONF_LOCATION] + api_key = conf.get(CONF_API_KEY) + LOGGER.debug("Setting up %s integration with host %s", DOMAIN, host) + + session = async_get_clientsession(hass, verify_tls) + pi_hole = PiHoleData( + Hole( + host, + hass.loop, + session, + location=location, + tls=use_tls, + api_token=api_key, + ), + name, + ) + + await pi_hole.async_update() + + hass.data[DOMAIN][slug] = pi_hole + + async def disable_service_handler(call): + """Handle the service call to disable a single Pi-Hole or all configured Pi-Holes.""" duration = call.data[SERVICE_DISABLE_ATTR_DURATION].total_seconds() + name = call.data.get(SERVICE_DISABLE_ATTR_NAME) - LOGGER.debug("Disabling %s %s for %d seconds", DOMAIN, host, duration) - await pi_hole.api.disable(duration) + async def do_disable(name): + """Disable the named Pi-Hole.""" + slug = cv.slugify(name) + pi_hole = hass.data[DOMAIN][slug] - async def handle_enable(call): - if api_key is None: - raise vol.Invalid("Pi-hole api_key must be provided in configuration") + LOGGER.debug( + "Disabling Pi-hole '%s' (%s) for %d seconds", + name, + pi_hole.api.host, + duration, + ) + await pi_hole.api.disable(duration) - LOGGER.debug("Enabling %s %s", DOMAIN, host) - await pi_hole.api.enable() + if name is not None: + await do_disable(name) + else: + for pi_hole in hass.data[DOMAIN].values(): + await do_disable(pi_hole.name) + + async def enable_service_handler(call): + """Handle the service call to enable a single Pi-Hole or all configured Pi-Holes.""" + + name = call.data.get(SERVICE_ENABLE_ATTR_NAME) + + async def do_enable(name): + """Enable the named Pi-Hole.""" + slug = cv.slugify(name) + pi_hole = hass.data[DOMAIN][slug] + + LOGGER.debug("Enabling Pi-hole '%s' (%s)", name, pi_hole.api.host) + await pi_hole.api.enable() + + if name is not None: + await do_enable(name) + else: + for pi_hole in hass.data[DOMAIN].values(): + await do_enable(pi_hole.name) hass.services.async_register( - DOMAIN, SERVICE_DISABLE, handle_disable, schema=SERVICE_DISABLE_SCHEMA + DOMAIN, SERVICE_DISABLE, disable_service_handler, schema=service_disable_schema ) - hass.services.async_register(DOMAIN, SERVICE_ENABLE, handle_enable) + hass.services.async_register( + DOMAIN, SERVICE_ENABLE, enable_service_handler, schema=service_enable_schema + ) hass.async_create_task(async_load_platform(hass, SENSOR_DOMAIN, DOMAIN, {}, config)) diff --git a/homeassistant/components/pi_hole/const.py b/homeassistant/components/pi_hole/const.py index 54220547950..0ae62b31865 100644 --- a/homeassistant/components/pi_hole/const.py +++ b/homeassistant/components/pi_hole/const.py @@ -4,8 +4,8 @@ from datetime import timedelta DOMAIN = "pi_hole" CONF_LOCATION = "location" +CONF_SLUG = "slug" -DEFAULT_HOST = "pi.hole" DEFAULT_LOCATION = "admin" DEFAULT_METHOD = "GET" DEFAULT_NAME = "Pi-Hole" @@ -13,8 +13,10 @@ DEFAULT_SSL = False DEFAULT_VERIFY_SSL = True SERVICE_DISABLE = "disable" -SERVICE_ENABLE = "enable" SERVICE_DISABLE_ATTR_DURATION = "duration" +SERVICE_DISABLE_ATTR_NAME = "name" +SERVICE_ENABLE = "enable" +SERVICE_ENABLE_ATTR_NAME = SERVICE_DISABLE_ATTR_NAME ATTR_BLOCKED_DOMAINS = "domains_blocked" diff --git a/homeassistant/components/pi_hole/sensor.py b/homeassistant/components/pi_hole/sensor.py index f76e756aec4..c01a0167e53 100644 --- a/homeassistant/components/pi_hole/sensor.py +++ b/homeassistant/components/pi_hole/sensor.py @@ -18,10 +18,12 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= if discovery_info is None: return - pi_hole = hass.data[PIHOLE_DOMAIN] - sensors = [] - sensors = [PiHoleSensor(pi_hole, sensor_name) for sensor_name in SENSOR_LIST] + for pi_hole in hass.data[PIHOLE_DOMAIN].values(): + for sensor in [ + PiHoleSensor(pi_hole, sensor_name) for sensor_name in SENSOR_LIST + ]: + sensors.append(sensor) async_add_entities(sensors, True) diff --git a/homeassistant/components/pi_hole/services.yaml b/homeassistant/components/pi_hole/services.yaml index b16ed21a5d3..e3cc8624e36 100644 --- a/homeassistant/components/pi_hole/services.yaml +++ b/homeassistant/components/pi_hole/services.yaml @@ -1,8 +1,15 @@ disable: - description: Disable Pi-hole for an amount of time + description: Disable configured Pi-hole(s) for an amount of time fields: duration: description: Time that the Pi-hole should be disabled for example: "00:00:15" + name: + description: "[Optional] When multiple Pi-holes are configured, the name of the one to disable. If omitted, all configured Pi-holes will be disabled." + example: "Pi-Hole" enable: - description: Enable Pi-hole \ No newline at end of file + description: Enable configured Pi-hole(s) + fields: + name: + description: "[Optional] When multiple Pi-holes are configured, the name of the one to enable. If omitted, all configured Pi-holes will be enabled." + example: "Pi-Hole" \ No newline at end of file diff --git a/tests/components/pi_hole/test_init.py b/tests/components/pi_hole/test_init.py index b2f4b3f28af..c2d9ec77f03 100644 --- a/tests/components/pi_hole/test_init.py +++ b/tests/components/pi_hole/test_init.py @@ -3,39 +3,34 @@ from unittest.mock import patch from asynctest import CoroutineMock -from hole import Hole from homeassistant.components import pi_hole from tests.common import async_setup_component - -def mock_pihole_data_call(Hole): - """Need to override so as to allow mocked data.""" - Hole.__init__ = ( - lambda self, host, loop, session, location, tls, verify_tls=True, api_token=None: None - ) - Hole.data = { - "ads_blocked_today": 0, - "ads_percentage_today": 0, - "clients_ever_seen": 0, - "dns_queries_today": 0, - "domains_being_blocked": 0, - "queries_cached": 0, - "queries_forwarded": 0, - "status": 0, - "unique_clients": 0, - "unique_domains": 0, - } - pass +ZERO_DATA = { + "ads_blocked_today": 0, + "ads_percentage_today": 0, + "clients_ever_seen": 0, + "dns_queries_today": 0, + "domains_being_blocked": 0, + "queries_cached": 0, + "queries_forwarded": 0, + "status": 0, + "unique_clients": 0, + "unique_domains": 0, +} -async def test_setup_no_config(hass): - """Tests component setup with no config.""" - with patch.object( - Hole, "get_data", new=CoroutineMock(side_effect=mock_pihole_data_call(Hole)) - ): - assert await async_setup_component(hass, pi_hole.DOMAIN, {pi_hole.DOMAIN: {}}) +async def test_setup_minimal_config(hass): + """Tests component setup with minimal config.""" + with patch("homeassistant.components.pi_hole.Hole") as _hole: + _hole.return_value.get_data = CoroutineMock(return_value=None) + _hole.return_value.data = ZERO_DATA + + assert await async_setup_component( + hass, pi_hole.DOMAIN, {pi_hole.DOMAIN: [{"host": "pi.hole"}]} + ) await hass.async_block_till_done() @@ -84,13 +79,16 @@ async def test_setup_no_config(hass): assert hass.states.get("sensor.pi_hole_seen_clients").state == "0" -async def test_setup_custom_config(hass): - """Tests component setup with custom config.""" - with patch.object( - Hole, "get_data", new=CoroutineMock(side_effect=mock_pihole_data_call(Hole)) - ): +async def test_setup_name_config(hass): + """Tests component setup with a custom name.""" + with patch("homeassistant.components.pi_hole.Hole") as _hole: + _hole.return_value.get_data = CoroutineMock(return_value=None) + _hole.return_value.data = ZERO_DATA + assert await async_setup_component( - hass, pi_hole.DOMAIN, {pi_hole.DOMAIN: {"name": "Custom"}} + hass, + pi_hole.DOMAIN, + {pi_hole.DOMAIN: [{"host": "pi.hole", "name": "Custom"}]}, ) await hass.async_block_till_done() @@ -99,3 +97,66 @@ async def test_setup_custom_config(hass): hass.states.get("sensor.custom_ads_blocked_today").name == "Custom Ads Blocked Today" ) + + +async def test_disable_service_call(hass): + """Test disable service call with no Pi-hole named.""" + with patch("homeassistant.components.pi_hole.Hole") as _hole: + mock_disable = CoroutineMock(return_value=None) + _hole.return_value.disable = mock_disable + _hole.return_value.get_data = CoroutineMock(return_value=None) + _hole.return_value.data = ZERO_DATA + + assert await async_setup_component( + hass, + pi_hole.DOMAIN, + { + pi_hole.DOMAIN: [ + {"host": "pi.hole", "api_key": "1"}, + {"host": "pi.hole", "name": "Custom", "api_key": "2"}, + ] + }, + ) + + await hass.async_block_till_done() + + await hass.services.async_call( + pi_hole.DOMAIN, + pi_hole.SERVICE_DISABLE, + {pi_hole.SERVICE_DISABLE_ATTR_DURATION: "00:00:01"}, + blocking=True, + ) + + await hass.async_block_till_done() + + assert mock_disable.call_count == 2 + + +async def test_enable_service_call(hass): + """Test enable service call with no Pi-hole named.""" + with patch("homeassistant.components.pi_hole.Hole") as _hole: + mock_enable = CoroutineMock(return_value=None) + _hole.return_value.enable = mock_enable + _hole.return_value.get_data = CoroutineMock(return_value=None) + _hole.return_value.data = ZERO_DATA + + assert await async_setup_component( + hass, + pi_hole.DOMAIN, + { + pi_hole.DOMAIN: [ + {"host": "pi.hole", "api_key": "1"}, + {"host": "pi.hole", "name": "Custom", "api_key": "2"}, + ] + }, + ) + + await hass.async_block_till_done() + + await hass.services.async_call( + pi_hole.DOMAIN, pi_hole.SERVICE_ENABLE, {}, blocking=True + ) + + await hass.async_block_till_done() + + assert mock_enable.call_count == 2 From 9d662d61149f4ddac251c0ddcd97a70fab4925b4 Mon Sep 17 00:00:00 2001 From: randellhodges Date: Thu, 12 Dec 2019 12:47:38 -0600 Subject: [PATCH 279/677] ISY994 Node Filter Update (#28155) * Adding filters for keypad dimmers and smoke sensor * ran black --fast as suggested * Adding filters for keypad dimmers and smoke sensor * ran black --fast as suggested --- homeassistant/components/isy994/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/isy994/__init__.py b/homeassistant/components/isy994/__init__.py index 96796e37a6a..6c5a668c51a 100644 --- a/homeassistant/components/isy994/__init__.py +++ b/homeassistant/components/isy994/__init__.py @@ -62,7 +62,7 @@ NODE_FILTERS = { "binary_sensor": { "uom": [], "states": [], - "node_def_id": ["BinaryAlarm"], + "node_def_id": ["BinaryAlarm", "BinaryAlarm_ADV"], "insteon_type": ["16."], # Does a startswith() match; include the dot }, "sensor": { @@ -112,6 +112,8 @@ NODE_FILTERS = { "BallastRelayLampSwitch_ADV", "RemoteLinc2", "RemoteLinc2_ADV", + "KeypadDimmer", + "KeypadDimmer_ADV", ], "insteon_type": ["1."], }, From 7685c76b9b49408dea07c947c2eb54fe1f01f92f Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Thu, 12 Dec 2019 14:16:02 -0500 Subject: [PATCH 280/677] Defer log formatting. (#29888) --- homeassistant/components/zha/api.py | 185 ++++++++++++++++++---------- 1 file changed, 117 insertions(+), 68 deletions(-) diff --git a/homeassistant/components/zha/api.py b/homeassistant/components/zha/api.py index 5f1b8e16512..d9c1db2ae5d 100644 --- a/homeassistant/components/zha/api.py +++ b/homeassistant/components/zha/api.py @@ -565,11 +565,15 @@ async def websocket_device_cluster_attributes(hass, connection, msg): {ID: attr_id, ATTR_NAME: attributes[attr_id][0]} ) _LOGGER.debug( - "Requested attributes for: %s %s %s %s", - f"{ATTR_CLUSTER_ID}: [{cluster_id}]", - f"{ATTR_CLUSTER_TYPE}: [{cluster_type}]", - f"{ATTR_ENDPOINT_ID}: [{endpoint_id}]", - f"{RESPONSE}: [{cluster_attributes}]", + "Requested attributes for: %s: %s, %s: '%s', %s: %s, %s: %s", + ATTR_CLUSTER_ID, + cluster_id, + ATTR_CLUSTER_TYPE, + cluster_type, + ATTR_ENDPOINT_ID, + endpoint_id, + RESPONSE, + cluster_attributes, ) connection.send_result(msg[ID], cluster_attributes) @@ -619,11 +623,15 @@ async def websocket_device_cluster_commands(hass, connection, msg): } ) _LOGGER.debug( - "Requested commands for: %s %s %s %s", - f"{ATTR_CLUSTER_ID}: [{cluster_id}]", - f"{ATTR_CLUSTER_TYPE}: [{cluster_type}]", - f"{ATTR_ENDPOINT_ID}: [{endpoint_id}]", - f"{RESPONSE}: [{cluster_commands}]", + "Requested commands for: %s: %s, %s: '%s', %s: %s, %s: %s", + ATTR_CLUSTER_ID, + cluster_id, + ATTR_CLUSTER_TYPE, + cluster_type, + ATTR_ENDPOINT_ID, + endpoint_id, + RESPONSE, + cluster_commands, ) connection.send_result(msg[ID], cluster_commands) @@ -663,14 +671,21 @@ async def websocket_read_zigbee_cluster_attributes(hass, connection, msg): [attribute], allow_cache=False, only_cache=False, manufacturer=manufacturer ) _LOGGER.debug( - "Read attribute for: %s %s %s %s %s %s %s", - f"{ATTR_CLUSTER_ID}: [{cluster_id}]", - f"{ATTR_CLUSTER_TYPE}: [{cluster_type}]", - f"{ATTR_ENDPOINT_ID}: [{endpoint_id}]", - f"{ATTR_ATTRIBUTE}: [{attribute}]", - f"{ATTR_MANUFACTURER}: [{manufacturer}]", - "{}: [{}]".format(RESPONSE, str(success.get(attribute))), - "{}: [{}]".format("failure", failure), + "Read attribute for: %s: [%s] %s: [%s] %s: [%s] %s: [%s] %s: [%s] %s: [%s] %s: [%s],", + ATTR_CLUSTER_ID, + cluster_id, + ATTR_CLUSTER_TYPE, + cluster_type, + ATTR_ENDPOINT_ID, + endpoint_id, + ATTR_ATTRIBUTE, + attribute, + ATTR_MANUFACTURER, + manufacturer, + RESPONSE, + str(success.get(attribute)), + "failure", + failure, ) connection.send_result(msg[ID], str(success.get(attribute))) @@ -693,9 +708,11 @@ async def websocket_get_bindable_devices(hass, connection, msg): ] _LOGGER.debug( - "Get bindable devices: %s %s", - f"{ATTR_SOURCE_IEEE}: [{source_ieee}]", - "{}: [{}]".format("bindable devices:", devices), + "Get bindable devices: %s: [%s], %s: [%s]", + ATTR_SOURCE_IEEE, + source_ieee, + "bindable devices", + devices, ) connection.send_message(websocket_api.result_message(msg[ID], devices)) @@ -719,9 +736,11 @@ async def websocket_bind_devices(hass, connection, msg): zha_gateway, source_ieee, target_ieee, zdo_types.ZDOCmd.Bind_req ) _LOGGER.info( - "Issued bind devices: %s %s", - f"{ATTR_SOURCE_IEEE}: [{source_ieee}]", - f"{ATTR_TARGET_IEEE}: [{target_ieee}]", + "Devices bound: %s: [%s] %s: [%s]", + ATTR_SOURCE_IEEE, + source_ieee, + ATTR_TARGET_IEEE, + target_ieee, ) @@ -743,9 +762,11 @@ async def websocket_unbind_devices(hass, connection, msg): zha_gateway, source_ieee, target_ieee, zdo_types.ZDOCmd.Unbind_req ) _LOGGER.info( - "Issued unbind devices: %s %s", - f"{ATTR_SOURCE_IEEE}: [{source_ieee}]", - f"{ATTR_TARGET_IEEE}: [{target_ieee}]", + "Devices un-bound: %s: [%s] %s: [%s]", + ATTR_SOURCE_IEEE, + source_ieee, + ATTR_TARGET_IEEE, + target_ieee, ) @@ -848,14 +869,21 @@ def async_load_api(hass): manufacturer=manufacturer, ) _LOGGER.debug( - "Set attribute for: %s %s %s %s %s %s %s", - f"{ATTR_CLUSTER_ID}: [{cluster_id}]", - f"{ATTR_CLUSTER_TYPE}: [{cluster_type}]", - f"{ATTR_ENDPOINT_ID}: [{endpoint_id}]", - f"{ATTR_ATTRIBUTE}: [{attribute}]", - f"{ATTR_VALUE}: [{value}]", - f"{ATTR_MANUFACTURER}: [{manufacturer}]", - f"{RESPONSE}: [{response}]", + "Set attribute for: %s: [%s] %s: [%s] %s: [%s] %s: [%s] %s: [%s] %s: [%s] %s: [%s]", + ATTR_CLUSTER_ID, + cluster_id, + ATTR_CLUSTER_TYPE, + cluster_type, + ATTR_ENDPOINT_ID, + endpoint_id, + ATTR_ATTRIBUTE, + attribute, + ATTR_VALUE, + value, + ATTR_MANUFACTURER, + manufacturer, + RESPONSE, + response, ) hass.helpers.service.async_register_admin_service( @@ -890,15 +918,23 @@ def async_load_api(hass): manufacturer=manufacturer, ) _LOGGER.debug( - "Issue command for: %s %s %s %s %s %s %s %s", - f"{ATTR_CLUSTER_ID}: [{cluster_id}]", - f"{ATTR_CLUSTER_TYPE}: [{cluster_type}]", - f"{ATTR_ENDPOINT_ID}: [{endpoint_id}]", - f"{ATTR_COMMAND}: [{command}]", - f"{ATTR_COMMAND_TYPE}: [{command_type}]", - f"{ATTR_ARGS}: [{args}]", - f"{ATTR_MANUFACTURER}: [{manufacturer}]", - f"{RESPONSE}: [{response}]", + "Issued command for: %s: [%s] %s: [%s] %s: [%s] %s: [%s] %s: [%s] %s: %s %s: [%s] %s: %s", + ATTR_CLUSTER_ID, + cluster_id, + ATTR_CLUSTER_TYPE, + cluster_type, + ATTR_ENDPOINT_ID, + endpoint_id, + ATTR_COMMAND, + command, + ATTR_COMMAND_TYPE, + command_type, + ATTR_ARGS, + args, + ATTR_MANUFACTURER, + manufacturer, + RESPONSE, + response, ) hass.helpers.service.async_register_admin_service( @@ -925,12 +961,17 @@ def async_load_api(hass): command, *args, manufacturer=manufacturer, expect_reply=True ) _LOGGER.debug( - "Issue group command for: %s %s %s %s %s", - f"{ATTR_CLUSTER_ID}: [{cluster_id}]", - f"{ATTR_COMMAND}: [{command}]", - f"{ATTR_ARGS}: [{args}]", - f"{ATTR_MANUFACTURER}: [{manufacturer}]", - f"{RESPONSE}: [{response}]", + "Issued group command for: %s: [%s] %s: [%s] %s: %s %s: [%s] %s: %s", + ATTR_CLUSTER_ID, + cluster_id, + ATTR_COMMAND, + command, + ATTR_ARGS, + args, + ATTR_MANUFACTURER, + manufacturer, + RESPONSE, + response, ) hass.helpers.service.async_register_admin_service( @@ -954,20 +995,24 @@ def async_load_api(hass): await channel.squawk(mode, strobe, level) else: _LOGGER.error( - "Squawking IASWD: %s is missing the required IASWD channel!", - "{}: [{}]".format(ATTR_IEEE, str(ieee)), + "Squawking IASWD: %s: [%s] is missing the required IASWD channel!", + ATTR_IEEE, + str(ieee), ) else: _LOGGER.error( - "Squawking IASWD: %s could not be found!", - "{}: [{}]".format(ATTR_IEEE, str(ieee)), + "Squawking IASWD: %s: [%s] could not be found!", ATTR_IEEE, str(ieee) ) _LOGGER.debug( - "Squawking IASWD: %s %s %s %s", - "{}: [{}]".format(ATTR_IEEE, str(ieee)), - "{}: [{}]".format(ATTR_WARNING_DEVICE_MODE, mode), - "{}: [{}]".format(ATTR_WARNING_DEVICE_STROBE, strobe), - "{}: [{}]".format(ATTR_LEVEL, level), + "Squawking IASWD: %s: [%s] %s: [%s] %s: [%s] %s: [%s]", + ATTR_IEEE, + str(ieee), + ATTR_WARNING_DEVICE_MODE, + mode, + ATTR_WARNING_DEVICE_STROBE, + strobe, + ATTR_LEVEL, + level, ) hass.helpers.service.async_register_admin_service( @@ -996,20 +1041,24 @@ def async_load_api(hass): ) else: _LOGGER.error( - "Warning IASWD: %s is missing the required IASWD channel!", - "{}: [{}]".format(ATTR_IEEE, str(ieee)), + "Warning IASWD: %s: [%s] is missing the required IASWD channel!", + ATTR_IEEE, + str(ieee), ) else: _LOGGER.error( - "Warning IASWD: %s could not be found!", - "{}: [{}]".format(ATTR_IEEE, str(ieee)), + "Warning IASWD: %s: [%s] could not be found!", ATTR_IEEE, str(ieee) ) _LOGGER.debug( - "Warning IASWD: %s %s %s %s", - "{}: [{}]".format(ATTR_IEEE, str(ieee)), - "{}: [{}]".format(ATTR_WARNING_DEVICE_MODE, mode), - "{}: [{}]".format(ATTR_WARNING_DEVICE_STROBE, strobe), - "{}: [{}]".format(ATTR_LEVEL, level), + "Warning IASWD: %s: [%s] %s: [%s] %s: [%s] %s: [%s]", + ATTR_IEEE, + str(ieee), + ATTR_WARNING_DEVICE_MODE, + mode, + ATTR_WARNING_DEVICE_STROBE, + strobe, + ATTR_LEVEL, + level, ) hass.helpers.service.async_register_admin_service( From c051ae0bfbce437c3e3f8ae1ae519d9343661c4f Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Thu, 12 Dec 2019 22:39:11 +0100 Subject: [PATCH 281/677] Bump python-miio version to 0.4.8 (#29890) --- homeassistant/components/xiaomi_miio/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/manifest.json b/homeassistant/components/xiaomi_miio/manifest.json index 849e4573bbf..4f2e752feb1 100644 --- a/homeassistant/components/xiaomi_miio/manifest.json +++ b/homeassistant/components/xiaomi_miio/manifest.json @@ -4,7 +4,7 @@ "documentation": "https://www.home-assistant.io/integrations/xiaomi_miio", "requirements": [ "construct==2.9.45", - "python-miio==0.4.7" + "python-miio==0.4.8" ], "dependencies": [], "codeowners": [ diff --git a/requirements_all.txt b/requirements_all.txt index fb7b193abab..0bf32b8d23c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1579,7 +1579,7 @@ python-juicenet==0.1.6 # python-lirc==1.2.3 # homeassistant.components.xiaomi_miio -python-miio==0.4.7 +python-miio==0.4.8 # homeassistant.components.mpd python-mpd2==1.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9716cd2f950..bb02be746de 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -522,7 +522,7 @@ python-forecastio==1.4.0 python-izone==1.1.1 # homeassistant.components.xiaomi_miio -python-miio==0.4.7 +python-miio==0.4.8 # homeassistant.components.nest python-nest==4.1.0 From 5b32ee566c87c2becc063e67b54daea56d992f75 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Fri, 13 Dec 2019 00:32:17 +0000 Subject: [PATCH 282/677] [ci skip] Translation update --- homeassistant/components/soma/.translations/fr.json | 4 +++- homeassistant/components/soma/.translations/ru.json | 6 ++++-- homeassistant/components/soma/.translations/zh-Hant.json | 4 +++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/soma/.translations/fr.json b/homeassistant/components/soma/.translations/fr.json index a758ab0f615..0889cdea2ec 100644 --- a/homeassistant/components/soma/.translations/fr.json +++ b/homeassistant/components/soma/.translations/fr.json @@ -3,7 +3,9 @@ "abort": { "already_setup": "Vous ne pouvez configurer qu'un seul compte Soma.", "authorize_url_timeout": "D\u00e9lai d'attente g\u00e9n\u00e9rant l'autorisation de l'URL.", - "missing_configuration": "Le composant Soma n'est pas configur\u00e9. Veuillez suivre la documentation." + "connection_error": "Impossible de se connecter \u00e0 SOMA Connect.", + "missing_configuration": "Le composant Soma n'est pas configur\u00e9. Veuillez suivre la documentation.", + "result_error": "SOMA Connect a r\u00e9pondu avec l'\u00e9tat d'erreur." }, "create_entry": { "default": "Authentifi\u00e9 avec succ\u00e8s avec Soma." diff --git a/homeassistant/components/soma/.translations/ru.json b/homeassistant/components/soma/.translations/ru.json index f28d672d3f2..fa581eb0821 100644 --- a/homeassistant/components/soma/.translations/ru.json +++ b/homeassistant/components/soma/.translations/ru.json @@ -3,7 +3,9 @@ "abort": { "already_setup": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", "authorize_url_timeout": "\u0418\u0441\u0442\u0435\u043a\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u0441\u044b\u043b\u043a\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.", - "missing_configuration": "\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 Soma \u043d\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438." + "connection_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a SOMA Connect.", + "missing_configuration": "\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 Soma \u043d\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438.", + "result_error": "SOMA Connect \u043e\u0442\u0432\u0435\u0442\u0438\u043b \u0441\u043e \u0441\u0442\u0430\u0442\u0443\u0441\u043e\u043c \u043e\u0448\u0438\u0431\u043a\u0438." }, "create_entry": { "default": "\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u043f\u0440\u043e\u0439\u0434\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e." @@ -15,7 +17,7 @@ "port": "\u041f\u043e\u0440\u0442" }, "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u043a SOMA Connect.", - "title": "Soma" + "title": "SOMA Connect" } }, "title": "Soma" diff --git a/homeassistant/components/soma/.translations/zh-Hant.json b/homeassistant/components/soma/.translations/zh-Hant.json index 893abe82ee1..73b26cb91f1 100644 --- a/homeassistant/components/soma/.translations/zh-Hant.json +++ b/homeassistant/components/soma/.translations/zh-Hant.json @@ -3,7 +3,9 @@ "abort": { "already_setup": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44 Soma \u5e33\u865f\u3002", "authorize_url_timeout": "\u7522\u751f\u8a8d\u8b49 URL \u6642\u903e\u6642", - "missing_configuration": "Soma \u5143\u4ef6\u5c1a\u672a\u8a2d\u7f6e\uff0c\u8acb\u53c3\u95b1\u6587\u4ef6\u8aaa\u660e\u3002" + "connection_error": "SOMA \u9023\u7dda\u5931\u6557\u3002", + "missing_configuration": "Soma \u5143\u4ef6\u5c1a\u672a\u8a2d\u7f6e\uff0c\u8acb\u53c3\u95b1\u6587\u4ef6\u8aaa\u660e\u3002", + "result_error": "SOMA \u9023\u7dda\u56de\u61c9\u72c0\u614b\u932f\u8aa4\u3002" }, "create_entry": { "default": "\u5df2\u6210\u529f\u8a8d\u8b49 Soma \u8a2d\u5099\u3002" From c59bf0bff6eb9f5edfba4272f6b5f80b1634167f Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Fri, 13 Dec 2019 09:47:09 +0100 Subject: [PATCH 283/677] `genericpath` is an internal Python module and shouldn't be imported according to core Python devs. (see [this](https://bugs.python.org/msg358136) comment) (#29903) For a reason unknown to me, @exxamalte introduced this in https://github.com/home-assistant/home-assistant/pull/14342. The problem is that Linux and macOS implement `os.path` differently, one imports from [`ntpath.py`](https://github.com/python/cpython/blob/master/Lib/ntpath.py) and the other one from [`posixpath.py`](https://github.com/python/cpython/blob/master/Lib/posixpath.py), and both these files use `genericpath.py`. Somehow, `isort` on macOS will see `genericpath` as a third party library and sort it accordingly. Other Unix-based OSes will correctly treat `genericpath` as an internal library. This problem led to a sorting sequence in the following commits: - ca0fad2cbb0544125d87d21ffe308cf3addcde5a - f5d4878992d63683d3da661ef02ae7b51421beb4 - 7d68e88d31246e268db809225e3d924acb1fc352 - 1fee400dcd94229db56a2ada5e3c335aa139146f This supersedes https://github.com/home-assistant/home-assistant/pull/29893. --- tests/components/feedreader/test_init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/components/feedreader/test_init.py b/tests/components/feedreader/test_init.py index bffbe9676fa..62412e53900 100644 --- a/tests/components/feedreader/test_init.py +++ b/tests/components/feedreader/test_init.py @@ -1,8 +1,8 @@ """The tests for the feedreader component.""" from datetime import timedelta -from genericpath import exists from logging import getLogger from os import remove +from os.path import exists import time import unittest from unittest import mock From 9fbb6d981a6c20c47a0d47d9e5d563c6526688eb Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Fri, 13 Dec 2019 10:31:53 +0100 Subject: [PATCH 284/677] Fix setup for tank_utility component (#29902) --- homeassistant/components/tank_utility/sensor.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/tank_utility/sensor.py b/homeassistant/components/tank_utility/sensor.py index 6848b263633..23446257eab 100644 --- a/homeassistant/components/tank_utility/sensor.py +++ b/homeassistant/components/tank_utility/sensor.py @@ -4,7 +4,7 @@ import datetime import logging import requests -import tank_utility +from tank_utility import auth, device as tank_monitor import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA @@ -47,7 +47,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): devices = config.get(CONF_DEVICES) try: - token = tank_utility.auth.get_token(email, password) + token = auth.get_token(email, password) except requests.exceptions.HTTPError as http_error: if ( http_error.response.status_code @@ -111,7 +111,7 @@ class TankUtilitySensor(Entity): data = {} try: - data = tank_utility.device.get_device_data(self._token, self.device) + data = tank_monitor.get_device_data(self._token, self.device) except requests.exceptions.HTTPError as http_error: if ( http_error.response.status_code @@ -120,10 +120,8 @@ class TankUtilitySensor(Entity): == requests.codes.bad_request # pylint: disable=no-member ): _LOGGER.info("Getting new token") - self._token = tank_utility.auth.get_token( - self._email, self._password, force=True - ) - data = tank_utility.device.get_device_data(self._token, self.device) + self._token = auth.get_token(self._email, self._password, force=True) + data = tank_monitor.get_device_data(self._token, self.device) else: raise http_error data.update(data.pop("device", {})) From 4d57de335c8b088b4baf17d89fbfb2d78c9c4639 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Fri, 13 Dec 2019 11:39:57 +0200 Subject: [PATCH 285/677] Make Python deprecation notice easier to maintain (#29900) --- homeassistant/bootstrap.py | 17 ++++++++++++----- homeassistant/const.py | 3 +++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 79de0b25453..12fbc6f232f 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -11,7 +11,11 @@ from typing import Any, Dict, Optional, Set import voluptuous as vol from homeassistant import config as conf_util, config_entries, core, loader -from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE +from homeassistant.const import ( + EVENT_HOMEASSISTANT_CLOSE, + REQUIRED_NEXT_PYTHON_DATE, + REQUIRED_NEXT_PYTHON_VER, +) from homeassistant.exceptions import HomeAssistantError from homeassistant.setup import async_setup_component from homeassistant.util.logging import AsyncHandler @@ -95,11 +99,14 @@ async def async_from_config_dict( stop = time() _LOGGER.info("Home Assistant initialized in %.2fs", stop - start) - if sys.version_info[:3] < (3, 7, 0): + if REQUIRED_NEXT_PYTHON_DATE and sys.version_info[:3] < REQUIRED_NEXT_PYTHON_VER: msg = ( - "Python 3.6 support is deprecated and will " - "be removed in the first release after December 15, 2019. Please " - "upgrade Python to 3.7.0 or higher." + "Support for the running Python version " + f"{'.'.join(str(x) for x in sys.version_info[:3])} is deprecated and will " + f"be removed in the first release after {REQUIRED_NEXT_PYTHON_DATE}. " + "Please upgrade Python to " + f"{'.'.join(str(x) for x in REQUIRED_NEXT_PYTHON_VER)} or " + "higher." ) _LOGGER.warning(msg) hass.components.persistent_notification.async_create( diff --git a/homeassistant/const.py b/homeassistant/const.py index dc937f81482..ba7e7a66126 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -5,6 +5,9 @@ PATCH_VERSION = "0.dev0" __short_version__ = "{}.{}".format(MAJOR_VERSION, MINOR_VERSION) __version__ = "{}.{}".format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 6, 1) +# Truthy date string triggers showing related deprecation warning messages. +REQUIRED_NEXT_PYTHON_VER = (3, 7, 0) +REQUIRED_NEXT_PYTHON_DATE = "December 15, 2019" # Format for platform files PLATFORM_FORMAT = "{platform}.{domain}" From 1301a4fcc671e458fb2c6c8274ac593ce079905d Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Fri, 13 Dec 2019 11:15:26 +0100 Subject: [PATCH 286/677] Upgrade Sphinx to 2.2.2 and sphinx-autodoc-typehintsi to 1.10.3 (#29906) --- requirements_docs.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements_docs.txt b/requirements_docs.txt index b3dd4616f49..55f0f2d162d 100644 --- a/requirements_docs.txt +++ b/requirements_docs.txt @@ -1,3 +1,3 @@ -Sphinx==2.1.2 -sphinx-autodoc-typehints==1.6.0 +Sphinx==2.2.2 +sphinx-autodoc-typehints==1.10.3 sphinx-autodoc-annotation==1.0.post1 \ No newline at end of file From b91a8f510cc8e7f2f5e940ce93231e3e046feee6 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 13 Dec 2019 12:29:24 +0100 Subject: [PATCH 287/677] Fix incorrect file format yr test fixure (#29910) --- tests/components/yr/test_sensor.py | 6 +++--- tests/fixtures/{yr.no.json => yr.no.xml} | 0 2 files changed, 3 insertions(+), 3 deletions(-) rename tests/fixtures/{yr.no.json => yr.no.xml} (100%) diff --git a/tests/components/yr/test_sensor.py b/tests/components/yr/test_sensor.py index 1e8282a58ff..21ce0bbe7ce 100644 --- a/tests/components/yr/test_sensor.py +++ b/tests/components/yr/test_sensor.py @@ -16,7 +16,7 @@ def test_default_setup(hass, aioclient_mock): """Test the default setup.""" aioclient_mock.get( "https://aa015h6buqvih86i1.api.met.no/" "weatherapi/locationforecast/1.9/", - text=load_fixture("yr.no.json"), + text=load_fixture("yr.no.xml"), ) config = {"platform": "yr", "elevation": 0} hass.allow_pool = True @@ -36,7 +36,7 @@ def test_custom_setup(hass, aioclient_mock): """Test a custom setup.""" aioclient_mock.get( "https://aa015h6buqvih86i1.api.met.no/" "weatherapi/locationforecast/1.9/", - text=load_fixture("yr.no.json"), + text=load_fixture("yr.no.xml"), ) config = { @@ -82,7 +82,7 @@ def test_forecast_setup(hass, aioclient_mock): """Test a custom setup with 24h forecast.""" aioclient_mock.get( "https://aa015h6buqvih86i1.api.met.no/" "weatherapi/locationforecast/1.9/", - text=load_fixture("yr.no.json"), + text=load_fixture("yr.no.xml"), ) config = { diff --git a/tests/fixtures/yr.no.json b/tests/fixtures/yr.no.xml similarity index 100% rename from tests/fixtures/yr.no.json rename to tests/fixtures/yr.no.xml From c8cc8acc81104056d0978259af9e550c06b1581f Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 13 Dec 2019 12:41:56 +0100 Subject: [PATCH 288/677] Fixes invalid JSON syntax in devcontainer (#29911) --- .devcontainer/devcontainer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 5bfd37fab36..7b56b66d0b5 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,4 +1,3 @@ -// See https://aka.ms/vscode-remote/devcontainer.json for format details. { "name": "Home Assistant Dev", "context": "..", From 6b3260357fe08d830087d5ed3691cfc4211a6ab2 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Fri, 13 Dec 2019 14:08:30 +0100 Subject: [PATCH 289/677] Fix setup error for logbook (#29908) * Fix setup error by moving an import back into the setup function * Revert c741664d4da76611b5bb7502dda61a24dce22c61 * Add homekit as after_dependency to logbook manifest.json --- homeassistant/components/logbook/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/logbook/manifest.json b/homeassistant/components/logbook/manifest.json index 08083ea4024..9d5c78dc34d 100644 --- a/homeassistant/components/logbook/manifest.json +++ b/homeassistant/components/logbook/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/logbook", "requirements": [], "dependencies": ["frontend", "http", "recorder"], + "after_dependencies": ["homekit"], "codeowners": [] } From a470a72ec592e58cdbb6b0c00cd76ce2d59371e4 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 13 Dec 2019 15:38:41 +0100 Subject: [PATCH 290/677] Add integration platform helper (#29914) --- homeassistant/components/intent/__init__.py | 34 +++----------- homeassistant/helpers/integration_platform.py | 46 +++++++++++++++++++ tests/helpers/test_integration_platform.py | 37 +++++++++++++++ 3 files changed, 90 insertions(+), 27 deletions(-) create mode 100644 homeassistant/helpers/integration_platform.py create mode 100644 tests/helpers/test_integration_platform.py diff --git a/homeassistant/components/intent/__init__.py b/homeassistant/components/intent/__init__.py index 53960851f6c..bdf612b2e83 100644 --- a/homeassistant/components/intent/__init__.py +++ b/homeassistant/components/intent/__init__.py @@ -1,16 +1,12 @@ """The Intent integration.""" -import asyncio import logging import voluptuous as vol from homeassistant.components import http from homeassistant.components.http.data_validator import RequestDataValidator -from homeassistant.const import EVENT_COMPONENT_LOADED from homeassistant.core import HomeAssistant -from homeassistant.helpers import config_validation as cv, intent -from homeassistant.loader import IntegrationNotFound, async_get_integration -from homeassistant.setup import ATTR_COMPONENT +from homeassistant.helpers import config_validation as cv, integration_platform, intent from .const import DOMAIN @@ -22,32 +18,16 @@ async def async_setup(hass: HomeAssistant, config: dict): """Set up the Intent component.""" hass.http.register_view(IntentHandleView()) - tasks = [_async_process_intent(hass, comp) for comp in hass.config.components] - - async def async_component_loaded(event): - """Handle a new component loaded.""" - await _async_process_intent(hass, event.data[ATTR_COMPONENT]) - - hass.bus.async_listen(EVENT_COMPONENT_LOADED, async_component_loaded) - - if tasks: - await asyncio.gather(*tasks) + await integration_platform.async_process_integration_platforms( + hass, DOMAIN, _async_process_intent + ) return True -async def _async_process_intent(hass: HomeAssistant, component_name: str): - """Process the intents of a component.""" - try: - integration = await async_get_integration(hass, component_name) - platform = integration.get_platform(DOMAIN) - except (IntegrationNotFound, ImportError): - return - - try: - await platform.async_setup_intents(hass) - except Exception: # pylint: disable=broad-except - _LOGGER.exception("Error setting up intents for %s", component_name) +async def _async_process_intent(hass: HomeAssistant, domain: str, platform): + """Process the intents of an integration.""" + await platform.async_setup_intents(hass) class IntentHandleView(http.HomeAssistantView): diff --git a/homeassistant/helpers/integration_platform.py b/homeassistant/helpers/integration_platform.py new file mode 100644 index 00000000000..01567c72c7b --- /dev/null +++ b/homeassistant/helpers/integration_platform.py @@ -0,0 +1,46 @@ +"""Helpers to help with integration platforms.""" +import asyncio +import logging +from typing import Any, Awaitable, Callable + +from homeassistant.core import Event, HomeAssistant +from homeassistant.loader import IntegrationNotFound, async_get_integration, bind_hass +from homeassistant.setup import ATTR_COMPONENT, EVENT_COMPONENT_LOADED + +_LOGGER = logging.getLogger(__name__) + + +@bind_hass +async def async_process_integration_platforms( + hass: HomeAssistant, + platform_name: str, + # Any = platform. + process_platform: Callable[[HomeAssistant, str, Any], Awaitable[None]], +) -> None: + """Process a specific platform for all current and future loaded integrations.""" + + async def _process(component_name: str) -> None: + """Process the intents of a component.""" + try: + integration = await async_get_integration(hass, component_name) + platform = integration.get_platform(platform_name) + except (IntegrationNotFound, ImportError): + return + + try: + await process_platform(hass, component_name, platform) + except Exception: # pylint: disable=broad-except + _LOGGER.exception( + "Error processing platform %s.%s", component_name, platform_name + ) + + async def async_component_loaded(event: Event) -> None: + """Handle a new component loaded.""" + await _process(event.data[ATTR_COMPONENT]) + + hass.bus.async_listen(EVENT_COMPONENT_LOADED, async_component_loaded) + + tasks = [_process(comp) for comp in hass.config.components] + + if tasks: + await asyncio.gather(*tasks) diff --git a/tests/helpers/test_integration_platform.py b/tests/helpers/test_integration_platform.py new file mode 100644 index 00000000000..d6c844c0d91 --- /dev/null +++ b/tests/helpers/test_integration_platform.py @@ -0,0 +1,37 @@ +"""Test integration platform helpers.""" +from unittest.mock import Mock + +from homeassistant.setup import ATTR_COMPONENT, EVENT_COMPONENT_LOADED + +from tests.common import mock_platform + + +async def test_process_integration_platforms(hass): + """Test processing integrations.""" + loaded_platform = Mock() + mock_platform(hass, "loaded.platform_to_check", loaded_platform) + hass.config.components.add("loaded") + + event_platform = Mock() + mock_platform(hass, "event.platform_to_check", event_platform) + + processed = [] + + async def _process_platform(hass, domain, platform): + """Process platform.""" + processed.append((domain, platform)) + + await hass.helpers.integration_platform.async_process_integration_platforms( + "platform_to_check", _process_platform + ) + + assert len(processed) == 1 + assert processed[0][0] == "loaded" + assert processed[0][1] == loaded_platform + + hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: "event"}) + await hass.async_block_till_done() + + assert len(processed) == 2 + assert processed[1][0] == "event" + assert processed[1][1] == event_platform From 8f5a00a98bddc6a42424c5a72dc40cc4f33f7e26 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Sat, 14 Dec 2019 00:32:08 +0000 Subject: [PATCH 291/677] [ci skip] Translation update --- .../components/icloud/.translations/it.json | 38 +++++++++++++++++++ .../components/soma/.translations/it.json | 4 +- 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/icloud/.translations/it.json diff --git a/homeassistant/components/icloud/.translations/it.json b/homeassistant/components/icloud/.translations/it.json new file mode 100644 index 00000000000..0a986f1fe77 --- /dev/null +++ b/homeassistant/components/icloud/.translations/it.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "username_exists": "Account gi\u00e0 configurato" + }, + "error": { + "login": "Errore di accesso: si prega di controllare la tua e-mail e la password", + "send_verification_code": "Impossibile inviare il codice di verifica", + "username_exists": "Account gi\u00e0 configurato", + "validate_verification_code": "Impossibile verificare il codice di verifica, scegliere un dispositivo attendibile e riavviare la verifica" + }, + "step": { + "trusted_device": { + "data": { + "trusted_device": "Dispositivo attendibile" + }, + "description": "Selezionare il dispositivo attendibile", + "title": "Dispositivo attendibile iCloud" + }, + "user": { + "data": { + "password": "Password", + "username": "E-mail" + }, + "description": "Inserisci le tue credenziali", + "title": "Credenziali iCloud" + }, + "verification_code": { + "data": { + "verification_code": "Codice di verifica" + }, + "description": "Inserisci il codice di verifica che hai appena ricevuto da iCloud", + "title": "Codice di verifica iCloud" + } + }, + "title": "Apple iCloud" + } +} \ No newline at end of file diff --git a/homeassistant/components/soma/.translations/it.json b/homeassistant/components/soma/.translations/it.json index 1398b2a66be..6c7d0129708 100644 --- a/homeassistant/components/soma/.translations/it.json +++ b/homeassistant/components/soma/.translations/it.json @@ -3,7 +3,9 @@ "abort": { "already_setup": "\u00c8 possibile configurare un solo account Soma.", "authorize_url_timeout": "Timeout durante la generazione dell'URL di autorizzazione.", - "missing_configuration": "Il componente Soma non \u00e8 configurato. Si prega di seguire la documentazione." + "connection_error": "Impossibile connettersi a SOMA Connect.", + "missing_configuration": "Il componente Soma non \u00e8 configurato. Si prega di seguire la documentazione.", + "result_error": "SOMA Connect ha risposto con stato di errore." }, "create_entry": { "default": "Autenticato con successo con Soma." From 114390c95e2a18745659e712e9576ffdd75c84fd Mon Sep 17 00:00:00 2001 From: Justin Bassett Date: Sat, 14 Dec 2019 01:36:12 -0500 Subject: [PATCH 292/677] Fix mobile app device identifiers (#29920) Fix identifiers when updating device registration. --- homeassistant/components/mobile_app/webhook.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index 46f17b401bc..c2bc6c94112 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -190,10 +190,7 @@ async def handle_webhook( device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, - identifiers={ - (ATTR_DEVICE_ID, registration[ATTR_DEVICE_ID]), - (CONF_WEBHOOK_ID, registration[CONF_WEBHOOK_ID]), - }, + identifiers={(DOMAIN, registration[ATTR_DEVICE_ID])}, manufacturer=new_registration[ATTR_MANUFACTURER], model=new_registration[ATTR_MODEL], name=new_registration[ATTR_DEVICE_NAME], From 2cb92c66ef3c18eb5a8bfa7744263fcf68b27ad2 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Sat, 14 Dec 2019 07:36:33 +0100 Subject: [PATCH 293/677] Support entity_id: all in lifx.set_state (#29919) --- homeassistant/components/lifx/light.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/lifx/light.py b/homeassistant/components/lifx/light.py index 1fb614f856f..aa63be04f0d 100644 --- a/homeassistant/components/lifx/light.py +++ b/homeassistant/components/lifx/light.py @@ -35,7 +35,12 @@ from homeassistant.components.light import ( Light, preprocess_turn_on_alternatives, ) -from homeassistant.const import ATTR_ENTITY_ID, ATTR_MODE, EVENT_HOMEASSISTANT_STOP +from homeassistant.const import ( + ATTR_ENTITY_ID, + ATTR_MODE, + ENTITY_MATCH_ALL, + EVENT_HOMEASSISTANT_STOP, +) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv import homeassistant.helpers.device_registry as dr @@ -369,6 +374,9 @@ class LIFXManager: async def async_service_to_entities(self, service): """Return the known entities that a service call mentions.""" + if service.data.get(ATTR_ENTITY_ID) == ENTITY_MATCH_ALL: + return self.entities.values() + entity_ids = await async_extract_entity_ids(self.hass, service) return [ entity From 3c86825e253192bdd71871a265c6882ef611d51d Mon Sep 17 00:00:00 2001 From: DjMoren Date: Sat, 14 Dec 2019 08:00:19 +0100 Subject: [PATCH 294/677] Update Tahoma component's tahoma-api requirement's version (#29918) --- homeassistant/components/tahoma/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/tahoma/manifest.json b/homeassistant/components/tahoma/manifest.json index 1e99d4b288d..4efe9aed97f 100644 --- a/homeassistant/components/tahoma/manifest.json +++ b/homeassistant/components/tahoma/manifest.json @@ -3,7 +3,7 @@ "name": "Tahoma", "documentation": "https://www.home-assistant.io/integrations/tahoma", "requirements": [ - "tahoma-api==0.0.14" + "tahoma-api==0.0.16" ], "dependencies": [], "codeowners": [ diff --git a/requirements_all.txt b/requirements_all.txt index 0bf32b8d23c..46c1b0f2ade 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1907,7 +1907,7 @@ swisshydrodata==0.0.3 synology-srm==0.0.7 # homeassistant.components.tahoma -tahoma-api==0.0.14 +tahoma-api==0.0.16 # homeassistant.components.tank_utility tank_utility==1.4.0 From 3db7e8f5e9218a5721e8cc46e32e8e9969b6ce2e Mon Sep 17 00:00:00 2001 From: ochlocracy <5885236+ochlocracy@users.noreply.github.com> Date: Sat, 14 Dec 2019 02:47:45 -0500 Subject: [PATCH 295/677] Implement Alexa.EventDetectionSensor for Alexa (#28276) * Implement Alexa.EventDetectionSensor Interface * Removed references to PR #28218 not yet merged into dev. * Update tests to include Alexa Interface * Guard for `unknown` and `unavailible` states. * Fixed Unnecessary "elif" after "return" --- .../components/alexa/capabilities.py | 59 +++++++++++++- homeassistant/components/alexa/entities.py | 37 ++++++++- tests/components/alexa/__init__.py | 1 + tests/components/alexa/test_capabilities.py | 42 ++++++++++ tests/components/alexa/test_smart_home.py | 81 +++++++++++++++++++ 5 files changed, 217 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/alexa/capabilities.py b/homeassistant/components/alexa/capabilities.py index 9f217d2e9c9..b5ffb1ef7e6 100644 --- a/homeassistant/components/alexa/capabilities.py +++ b/homeassistant/components/alexa/capabilities.py @@ -1,7 +1,7 @@ """Alexa capabilities.""" import logging -from homeassistant.components import cover, fan, light +from homeassistant.components import cover, fan, image_processing, light from homeassistant.components.alarm_control_panel import ATTR_CODE_FORMAT, FORMAT_NUMBER import homeassistant.components.climate.const as climate import homeassistant.components.media_player.const as media_player @@ -1265,3 +1265,60 @@ class AlexaSeekController(AlexaCapability): def name(self): """Return the Alexa API name of this interface.""" return "Alexa.SeekController" + + +class AlexaEventDetectionSensor(AlexaCapability): + """Implements Alexa.EventDetectionSensor. + + https://developer.amazon.com/docs/device-apis/alexa-eventdetectionsensor.html + """ + + def __init__(self, hass, entity): + """Initialize the entity.""" + super().__init__(entity) + self.hass = hass + + def name(self): + """Return the Alexa API name of this interface.""" + return "Alexa.EventDetectionSensor" + + def properties_supported(self): + """Return what properties this entity supports.""" + return [{"name": "humanPresenceDetectionState"}] + + def properties_proactively_reported(self): + """Return True if properties asynchronously reported.""" + return True + + def get_property(self, name): + """Read and return a property.""" + if name != "humanPresenceDetectionState": + raise UnsupportedProperty(name) + + human_presence = "NOT_DETECTED" + state = self.entity.state + + # Return None for unavailable and unknown states. + # Allows the Alexa.EndpointHealth Interface to handle the unavailable state in a stateReport. + if state in (STATE_UNAVAILABLE, STATE_UNKNOWN, None): + return None + + if self.entity.domain == image_processing.DOMAIN: + if int(state): + human_presence = "DETECTED" + elif state == STATE_ON: + human_presence = "DETECTED" + + return {"value": human_presence} + + def configuration(self): + """Return supported detection types.""" + return { + "detectionMethods": ["AUDIO", "VIDEO"], + "detectionModes": { + "humanPresence": { + "featureAvailability": "ENABLED", + "supportsNotDetected": True, + } + }, + } diff --git a/homeassistant/components/alexa/entities.py b/homeassistant/components/alexa/entities.py index 733ea73f998..017686df607 100644 --- a/homeassistant/components/alexa/entities.py +++ b/homeassistant/components/alexa/entities.py @@ -9,6 +9,7 @@ from homeassistant.components import ( cover, fan, group, + image_processing, input_boolean, light, lock, @@ -40,6 +41,7 @@ from .capabilities import ( AlexaContactSensor, AlexaDoorbellEventSource, AlexaEndpointHealth, + AlexaEventDetectionSensor, AlexaInputController, AlexaLockController, AlexaModeController, @@ -522,6 +524,7 @@ class BinarySensorCapabilities(AlexaEntity): TYPE_CONTACT = "contact" TYPE_MOTION = "motion" + TYPE_PRESENCE = "presence" def default_display_categories(self): """Return the display categories for this entity.""" @@ -530,6 +533,8 @@ class BinarySensorCapabilities(AlexaEntity): return [DisplayCategory.CONTACT_SENSOR] if sensor_type is self.TYPE_MOTION: return [DisplayCategory.MOTION_SENSOR] + if sensor_type is self.TYPE_PRESENCE: + return [DisplayCategory.CAMERA] def interfaces(self): """Yield the supported interfaces.""" @@ -538,7 +543,10 @@ class BinarySensorCapabilities(AlexaEntity): yield AlexaContactSensor(self.hass, self.entity) elif sensor_type is self.TYPE_MOTION: yield AlexaMotionSensor(self.hass, self.entity) + elif sensor_type is self.TYPE_PRESENCE: + yield AlexaEventDetectionSensor(self.hass, self.entity) + # yield additional interfaces based on specified display category in config. entity_conf = self.config.entity_config.get(self.entity.entity_id, {}) if CONF_DISPLAY_CATEGORIES in entity_conf: if entity_conf[CONF_DISPLAY_CATEGORIES] == DisplayCategory.DOORBELL: @@ -547,6 +555,8 @@ class BinarySensorCapabilities(AlexaEntity): yield AlexaContactSensor(self.hass, self.entity) elif entity_conf[CONF_DISPLAY_CATEGORIES] == DisplayCategory.MOTION_SENSOR: yield AlexaMotionSensor(self.hass, self.entity) + elif entity_conf[CONF_DISPLAY_CATEGORIES] == DisplayCategory.CAMERA: + yield AlexaEventDetectionSensor(self.hass, self.entity) yield AlexaEndpointHealth(self.hass, self.entity) yield Alexa(self.hass) @@ -554,11 +564,20 @@ class BinarySensorCapabilities(AlexaEntity): def get_type(self): """Return the type of binary sensor.""" attrs = self.entity.attributes - if attrs.get(ATTR_DEVICE_CLASS) in ("door", "garage_door", "opening", "window"): + if attrs.get(ATTR_DEVICE_CLASS) in ( + binary_sensor.DEVICE_CLASS_DOOR, + binary_sensor.DEVICE_CLASS_GARAGE_DOOR, + binary_sensor.DEVICE_CLASS_OPENING, + binary_sensor.DEVICE_CLASS_WINDOW, + ): return self.TYPE_CONTACT - if attrs.get(ATTR_DEVICE_CLASS) == "motion": + + if attrs.get(ATTR_DEVICE_CLASS) == binary_sensor.DEVICE_CLASS_MOTION: return self.TYPE_MOTION + if attrs.get(ATTR_DEVICE_CLASS) == binary_sensor.DEVICE_CLASS_PRESENCE: + return self.TYPE_PRESENCE + @ENTITY_ADAPTERS.register(alarm_control_panel.DOMAIN) class AlarmControlPanelCapabilities(AlexaEntity): @@ -574,3 +593,17 @@ class AlarmControlPanelCapabilities(AlexaEntity): yield AlexaSecurityPanelController(self.hass, self.entity) yield AlexaEndpointHealth(self.hass, self.entity) yield Alexa(self.hass) + + +@ENTITY_ADAPTERS.register(image_processing.DOMAIN) +class ImageProcessingCapabilities(AlexaEntity): + """Class to represent image_processing capabilities.""" + + def default_display_categories(self): + """Return the display categories for this entity.""" + return [DisplayCategory.CAMERA] + + def interfaces(self): + """Yield the supported interfaces.""" + yield AlexaEventDetectionSensor(self.hass, self.entity) + yield AlexaEndpointHealth(self.hass, self.entity) diff --git a/tests/components/alexa/__init__.py b/tests/components/alexa/__init__.py index 79fdb86c3ef..473a3c6e12b 100644 --- a/tests/components/alexa/__init__.py +++ b/tests/components/alexa/__init__.py @@ -17,6 +17,7 @@ class MockConfig(config.AbstractConfig): "binary_sensor.test_doorbell": {"display_categories": "DOORBELL"}, "binary_sensor.test_contact_forced": {"display_categories": "CONTACT_SENSOR"}, "binary_sensor.test_motion_forced": {"display_categories": "MOTION_SENSOR"}, + "binary_sensor.test_motion_camera_event": {"display_categories": "CAMERA"}, } @property diff --git a/tests/components/alexa/test_capabilities.py b/tests/components/alexa/test_capabilities.py index 952af543aab..ab9c375103a 100644 --- a/tests/components/alexa/test_capabilities.py +++ b/tests/components/alexa/test_capabilities.py @@ -667,3 +667,45 @@ async def test_report_playback_state(hass): properties.assert_equal( "Alexa.PlaybackStateReporter", "playbackState", {"state": "STOPPED"} ) + + +async def test_report_image_processing(hass): + """Test EventDetectionSensor implements humanPresenceDetectionState property.""" + hass.states.async_set( + "image_processing.test_face", + 0, + { + "friendly_name": "Test face", + "device_class": "face", + "faces": [], + "total_faces": 0, + }, + ) + + properties = await reported_properties(hass, "image_processing#test_face") + properties.assert_equal( + "Alexa.EventDetectionSensor", + "humanPresenceDetectionState", + {"value": "NOT_DETECTED"}, + ) + + hass.states.async_set( + "image_processing.test_classifier", + 3, + { + "friendly_name": "Test classifier", + "device_class": "face", + "faces": [ + {"confidence": 98.34, "name": "Hans", "age": 16.0, "gender": "male"}, + {"name": "Helena", "age": 28.0, "gender": "female"}, + {"confidence": 62.53, "name": "Luna"}, + ], + "total_faces": 3, + }, + ) + properties = await reported_properties(hass, "image_processing#test_classifier") + properties.assert_equal( + "Alexa.EventDetectionSensor", + "humanPresenceDetectionState", + {"value": "DETECTED"}, + ) diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index ea1e6f50fcf..25c8f2a864f 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -2386,3 +2386,84 @@ async def test_cover_position(hass): assert properties["name"] == "mode" assert properties["namespace"] == "Alexa.ModeController" assert properties["value"] == "position.open" + + +async def test_image_processing(hass): + """Test image_processing discovery as event detection.""" + device = ( + "image_processing.test_face", + 0, + { + "friendly_name": "Test face", + "device_class": "face", + "faces": [], + "total_faces": 0, + }, + ) + appliance = await discovery_test(device, hass) + + assert appliance["endpointId"] == "image_processing#test_face" + assert appliance["displayCategories"][0] == "CAMERA" + assert appliance["friendlyName"] == "Test face" + + assert_endpoint_capabilities( + appliance, "Alexa.EventDetectionSensor", "Alexa.EndpointHealth" + ) + + +async def test_motion_sensor_event_detection(hass): + """Test motion sensor with EventDetectionSensor discovery.""" + device = ( + "binary_sensor.test_motion_camera_event", + "off", + {"friendly_name": "Test motion camera event", "device_class": "motion"}, + ) + appliance = await discovery_test(device, hass) + + assert appliance["endpointId"] == "binary_sensor#test_motion_camera_event" + assert appliance["displayCategories"][0] == "CAMERA" + assert appliance["friendlyName"] == "Test motion camera event" + + capabilities = assert_endpoint_capabilities( + appliance, + "Alexa", + "Alexa.MotionSensor", + "Alexa.EventDetectionSensor", + "Alexa.EndpointHealth", + ) + + event_detection_capability = get_capability( + capabilities, "Alexa.EventDetectionSensor" + ) + assert event_detection_capability is not None + properties = event_detection_capability["properties"] + assert properties["proactivelyReported"] is True + assert not properties["retrievable"] + assert {"name": "humanPresenceDetectionState"} in properties["supported"] + + +async def test_presence_sensor(hass): + """Test presence sensor.""" + device = ( + "binary_sensor.test_presence_sensor", + "off", + {"friendly_name": "Test presence sensor", "device_class": "presence"}, + ) + appliance = await discovery_test(device, hass) + + assert appliance["endpointId"] == "binary_sensor#test_presence_sensor" + assert appliance["displayCategories"][0] == "CAMERA" + assert appliance["friendlyName"] == "Test presence sensor" + + capabilities = assert_endpoint_capabilities( + appliance, "Alexa", "Alexa.EventDetectionSensor", "Alexa.EndpointHealth" + ) + + event_detection_capability = get_capability( + capabilities, "Alexa.EventDetectionSensor" + ) + assert event_detection_capability is not None + properties = event_detection_capability["properties"] + assert properties["proactivelyReported"] is True + assert not properties["retrievable"] + assert {"name": "humanPresenceDetectionState"} in properties["supported"] From 003658a3f084c5be5e4959f69c57e526423a7ef3 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Sat, 14 Dec 2019 10:54:41 -0500 Subject: [PATCH 296/677] Update androidtv version to improve source selection support (#29579) * Change androidtv module versions and add support for select_source for all device types * Update and add tests * Update requirements_test_all.txt * Update requirements_all.txt * Consolidate tests * Fix typo * Remove 'self._device' --- .../components/androidtv/manifest.json | 4 +- .../components/androidtv/media_player.py | 91 ++++----- requirements_all.txt | 4 +- requirements_test_all.txt | 4 +- tests/components/androidtv/patchers.py | 21 ++- .../components/androidtv/test_media_player.py | 176 +++++++++++++++--- 6 files changed, 224 insertions(+), 76 deletions(-) diff --git a/homeassistant/components/androidtv/manifest.json b/homeassistant/components/androidtv/manifest.json index 8b68f089617..47768cbd4dc 100644 --- a/homeassistant/components/androidtv/manifest.json +++ b/homeassistant/components/androidtv/manifest.json @@ -3,8 +3,8 @@ "name": "Androidtv", "documentation": "https://www.home-assistant.io/integrations/androidtv", "requirements": [ - "adb-shell==0.0.8", - "androidtv==0.0.34", + "adb-shell==0.0.9", + "androidtv==0.0.35", "pure-python-adb==0.2.2.dev0" ], "dependencies": [], diff --git a/homeassistant/components/androidtv/media_player.py b/homeassistant/components/androidtv/media_player.py index 8b7f1880264..a1fb4cea9cd 100644 --- a/homeassistant/components/androidtv/media_player.py +++ b/homeassistant/components/androidtv/media_player.py @@ -55,6 +55,7 @@ SUPPORT_ANDROIDTV = ( | SUPPORT_TURN_OFF | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK + | SUPPORT_SELECT_SOURCE | SUPPORT_STOP | SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_STEP @@ -199,6 +200,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): aftv, config[CONF_NAME], config[CONF_APPS], + config[CONF_GET_SOURCES], config.get(CONF_TURN_ON_COMMAND), config.get(CONF_TURN_OFF_COMMAND), ) @@ -287,7 +289,9 @@ def adb_decorator(override_available=False): class ADBDevice(MediaPlayerDevice): """Representation of an Android TV or Fire TV device.""" - def __init__(self, aftv, name, apps, turn_on_command, turn_off_command): + def __init__( + self, aftv, name, apps, get_sources, turn_on_command, turn_off_command + ): """Initialize the Android TV / Fire TV device.""" self.aftv = aftv self._name = name @@ -296,6 +300,7 @@ class ADBDevice(MediaPlayerDevice): self._app_name_to_id = { value: key for key, value in self._app_id_to_name.items() } + self._get_sources = get_sources self._keys = KEYS self._device_properties = self.aftv.device_properties @@ -325,6 +330,7 @@ class ADBDevice(MediaPlayerDevice): self._adb_response = None self._available = True self._current_app = None + self._sources = None self._state = None @property @@ -357,6 +363,16 @@ class ADBDevice(MediaPlayerDevice): """Device should be polled.""" return True + @property + def source(self): + """Return the current app.""" + return self._app_id_to_name.get(self._current_app, self._current_app) + + @property + def source_list(self): + """Return a list of running apps.""" + return self._sources + @property def state(self): """Return the state of the player.""" @@ -408,6 +424,20 @@ class ADBDevice(MediaPlayerDevice): """Send next track command (results in fast-forward).""" self.aftv.media_next_track() + @adb_decorator() + def select_source(self, source): + """Select input source. + + If the source starts with a '!', then it will close the app instead of + opening it. + """ + if isinstance(source, str): + if not source.startswith("!"): + self.aftv.launch_app(self._app_name_to_id.get(source, source)) + else: + source_ = source[1:].lstrip() + self.aftv.stop_app(self._app_name_to_id.get(source_, source_)) + @adb_decorator() def adb_command(self, cmd): """Send an ADB command to an Android TV / Fire TV device.""" @@ -436,11 +466,14 @@ class ADBDevice(MediaPlayerDevice): class AndroidTVDevice(ADBDevice): """Representation of an Android TV device.""" - def __init__(self, aftv, name, apps, turn_on_command, turn_off_command): + def __init__( + self, aftv, name, apps, get_sources, turn_on_command, turn_off_command + ): """Initialize the Android TV device.""" - super().__init__(aftv, name, apps, turn_on_command, turn_off_command) + super().__init__( + aftv, name, apps, get_sources, turn_on_command, turn_off_command + ) - self._device = None self._is_volume_muted = None self._volume_level = None @@ -465,25 +498,28 @@ class AndroidTVDevice(ADBDevice): ( state, self._current_app, - self._device, + running_apps, + _, self._is_volume_muted, self._volume_level, - ) = self.aftv.update() + ) = self.aftv.update(self._get_sources) self._state = ANDROIDTV_STATES.get(state) if self._state is None: self._available = False + if running_apps: + self._sources = [ + self._app_id_to_name.get(app_id, app_id) for app_id in running_apps + ] + else: + self._sources = None + @property def is_volume_muted(self): """Boolean if volume is currently muted.""" return self._is_volume_muted - @property - def source(self): - """Return the current playback device.""" - return self._device - @property def supported_features(self): """Flag media player features that are supported.""" @@ -518,15 +554,6 @@ class AndroidTVDevice(ADBDevice): class FireTVDevice(ADBDevice): """Representation of a Fire TV device.""" - def __init__( - self, aftv, name, apps, get_sources, turn_on_command, turn_off_command - ): - """Initialize the Fire TV device.""" - super().__init__(aftv, name, apps, turn_on_command, turn_off_command) - - self._get_sources = get_sources - self._sources = None - @adb_decorator(override_available=True) def update(self): """Update the device state and, if necessary, re-connect.""" @@ -558,16 +585,6 @@ class FireTVDevice(ADBDevice): else: self._sources = None - @property - def source(self): - """Return the current app.""" - return self._app_id_to_name.get(self._current_app, self._current_app) - - @property - def source_list(self): - """Return a list of running apps.""" - return self._sources - @property def supported_features(self): """Flag media player features that are supported.""" @@ -577,17 +594,3 @@ class FireTVDevice(ADBDevice): def media_stop(self): """Send stop (back) command.""" self.aftv.back() - - @adb_decorator() - def select_source(self, source): - """Select input source. - - If the source starts with a '!', then it will close the app instead of - opening it. - """ - if isinstance(source, str): - if not source.startswith("!"): - self.aftv.launch_app(self._app_name_to_id.get(source, source)) - else: - source_ = source[1:].lstrip() - self.aftv.stop_app(self._app_name_to_id.get(source_, source_)) diff --git a/requirements_all.txt b/requirements_all.txt index 46c1b0f2ade..f93db416543 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -115,7 +115,7 @@ adafruit-blinka==1.2.1 adafruit-circuitpython-mcp230xx==1.1.2 # homeassistant.components.androidtv -adb-shell==0.0.8 +adb-shell==0.0.9 # homeassistant.components.adguard adguardhome==0.3.0 @@ -215,7 +215,7 @@ ambiclimate==0.2.1 amcrest==1.5.3 # homeassistant.components.androidtv -androidtv==0.0.34 +androidtv==0.0.35 # homeassistant.components.anel_pwrctrl anel_pwrctrl-homeassistant==0.0.1.dev2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index bb02be746de..812af1a2b13 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -29,7 +29,7 @@ YesssSMS==0.4.1 abodepy==0.16.7 # homeassistant.components.androidtv -adb-shell==0.0.8 +adb-shell==0.0.9 # homeassistant.components.adguard adguardhome==0.3.0 @@ -84,7 +84,7 @@ airly==0.0.2 ambiclimate==0.2.1 # homeassistant.components.androidtv -androidtv==0.0.34 +androidtv==0.0.35 # homeassistant.components.apns apns2==0.3.0 diff --git a/tests/components/androidtv/patchers.py b/tests/components/androidtv/patchers.py index 0549ad995e1..bd05cab2a74 100644 --- a/tests/components/androidtv/patchers.py +++ b/tests/components/androidtv/patchers.py @@ -149,5 +149,22 @@ def patch_firetv_update(state, current_app, running_apps): ) -PATCH_LAUNCH_APP = patch("androidtv.firetv.FireTV.launch_app") -PATCH_STOP_APP = patch("androidtv.firetv.FireTV.stop_app") +def patch_androidtv_update( + state, current_app, running_apps, device, is_volume_muted, volume_level +): + """Patch the `AndroidTV.update()` method.""" + return patch( + "androidtv.androidtv.AndroidTV.update", + return_value=( + state, + current_app, + running_apps, + device, + is_volume_muted, + volume_level, + ), + ) + + +PATCH_LAUNCH_APP = patch("androidtv.basetv.BaseTV.launch_app") +PATCH_STOP_APP = patch("androidtv.basetv.BaseTV.stop_app") diff --git a/tests/components/androidtv/test_media_player.py b/tests/components/androidtv/test_media_player.py index d7a6a8c1ce6..860b8738607 100644 --- a/tests/components/androidtv/test_media_player.py +++ b/tests/components/androidtv/test_media_player.py @@ -33,6 +33,7 @@ CONFIG_ANDROIDTV_PYTHON_ADB = { CONF_PLATFORM: ANDROIDTV_DOMAIN, CONF_HOST: "127.0.0.1", CONF_NAME: "Android TV", + CONF_DEVICE_CLASS: "androidtv", } } @@ -42,6 +43,7 @@ CONFIG_ANDROIDTV_ADB_SERVER = { CONF_PLATFORM: ANDROIDTV_DOMAIN, CONF_HOST: "127.0.0.1", CONF_NAME: "Android TV", + CONF_DEVICE_CLASS: "androidtv", CONF_ADB_SERVER_IP: "127.0.0.1", } } @@ -284,9 +286,9 @@ async def test_setup_with_adbkey(hass): assert state.state == STATE_OFF -async def test_firetv_sources(hass): - """Test that sources (i.e., apps) are handled correctly for Fire TV devices.""" - config = CONFIG_FIRETV_ADB_SERVER.copy() +async def _test_sources(hass, config0): + """Test that sources (i.e., apps) are handled correctly for Android TV and Fire TV devices.""" + config = config0.copy() config[DOMAIN][CONF_APPS] = {"com.app.test1": "TEST 1"} patch_key, entity_id = _setup(hass, config) @@ -299,9 +301,21 @@ async def test_firetv_sources(hass): assert state is not None assert state.state == STATE_OFF - with patchers.patch_firetv_update( - "playing", "com.app.test1", ["com.app.test1", "com.app.test2"] - ): + if config[DOMAIN].get(CONF_DEVICE_CLASS) != "firetv": + patch_update = patchers.patch_androidtv_update( + "playing", + "com.app.test1", + ["com.app.test1", "com.app.test2"], + "hdmi", + False, + 1, + ) + else: + patch_update = patchers.patch_firetv_update( + "playing", "com.app.test1", ["com.app.test1", "com.app.test2"] + ) + + with patch_update: await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) assert state is not None @@ -309,9 +323,21 @@ async def test_firetv_sources(hass): assert state.attributes["source"] == "TEST 1" assert state.attributes["source_list"] == ["TEST 1", "com.app.test2"] - with patchers.patch_firetv_update( - "playing", "com.app.test2", ["com.app.test2", "com.app.test1"] - ): + if config[DOMAIN].get(CONF_DEVICE_CLASS) != "firetv": + patch_update = patchers.patch_androidtv_update( + "playing", + "com.app.test2", + ["com.app.test2", "com.app.test1"], + "hdmi", + True, + 0, + ) + else: + patch_update = patchers.patch_firetv_update( + "playing", "com.app.test2", ["com.app.test2", "com.app.test1"] + ) + + with patch_update: await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) assert state is not None @@ -319,10 +345,22 @@ async def test_firetv_sources(hass): assert state.attributes["source"] == "com.app.test2" assert state.attributes["source_list"] == ["com.app.test2", "TEST 1"] + return True -async def _test_firetv_select_source(hass, source, expected_arg, method_patch): - """Test that the `FireTV.launch_app` and `FireTV.stop_app` methods are called with the right parameter.""" - config = CONFIG_FIRETV_ADB_SERVER.copy() + +async def test_androidtv_sources(hass): + """Test that sources (i.e., apps) are handled correctly for Android TV devices.""" + assert await _test_sources(hass, CONFIG_ANDROIDTV_ADB_SERVER) + + +async def test_firetv_sources(hass): + """Test that sources (i.e., apps) are handled correctly for Fire TV devices.""" + assert await _test_sources(hass, CONFIG_FIRETV_ADB_SERVER) + + +async def _test_select_source(hass, config0, source, expected_arg, method_patch): + """Test that the methods for launching and stopping apps are called correctly when selecting a source.""" + config = config0.copy() config[DOMAIN][CONF_APPS] = {"com.app.test1": "TEST 1"} patch_key, entity_id = _setup(hass, config) @@ -347,43 +385,133 @@ async def _test_firetv_select_source(hass, source, expected_arg, method_patch): return True +async def test_androidtv_select_source_launch_app_id(hass): + """Test that an app can be launched using its app ID.""" + assert await _test_select_source( + hass, + CONFIG_ANDROIDTV_ADB_SERVER, + "com.app.test1", + "com.app.test1", + patchers.PATCH_LAUNCH_APP, + ) + + +async def test_androidtv_select_source_launch_app_name(hass): + """Test that an app can be launched using its friendly name.""" + assert await _test_select_source( + hass, + CONFIG_ANDROIDTV_ADB_SERVER, + "TEST 1", + "com.app.test1", + patchers.PATCH_LAUNCH_APP, + ) + + +async def test_androidtv_select_source_launch_app_id_no_name(hass): + """Test that an app can be launched using its app ID when it has no friendly name.""" + assert await _test_select_source( + hass, + CONFIG_ANDROIDTV_ADB_SERVER, + "com.app.test2", + "com.app.test2", + patchers.PATCH_LAUNCH_APP, + ) + + +async def test_androidtv_select_source_stop_app_id(hass): + """Test that an app can be stopped using its app ID.""" + assert await _test_select_source( + hass, + CONFIG_ANDROIDTV_ADB_SERVER, + "!com.app.test1", + "com.app.test1", + patchers.PATCH_STOP_APP, + ) + + +async def test_androidtv_select_source_stop_app_name(hass): + """Test that an app can be stopped using its friendly name.""" + assert await _test_select_source( + hass, + CONFIG_ANDROIDTV_ADB_SERVER, + "!TEST 1", + "com.app.test1", + patchers.PATCH_STOP_APP, + ) + + +async def test_androidtv_select_source_stop_app_id_no_name(hass): + """Test that an app can be stopped using its app ID when it has no friendly name.""" + assert await _test_select_source( + hass, + CONFIG_ANDROIDTV_ADB_SERVER, + "!com.app.test2", + "com.app.test2", + patchers.PATCH_STOP_APP, + ) + + async def test_firetv_select_source_launch_app_id(hass): """Test that an app can be launched using its app ID.""" - assert await _test_firetv_select_source( - hass, "com.app.test1", "com.app.test1", patchers.PATCH_LAUNCH_APP + assert await _test_select_source( + hass, + CONFIG_FIRETV_ADB_SERVER, + "com.app.test1", + "com.app.test1", + patchers.PATCH_LAUNCH_APP, ) async def test_firetv_select_source_launch_app_name(hass): """Test that an app can be launched using its friendly name.""" - assert await _test_firetv_select_source( - hass, "TEST 1", "com.app.test1", patchers.PATCH_LAUNCH_APP + assert await _test_select_source( + hass, + CONFIG_FIRETV_ADB_SERVER, + "TEST 1", + "com.app.test1", + patchers.PATCH_LAUNCH_APP, ) async def test_firetv_select_source_launch_app_id_no_name(hass): """Test that an app can be launched using its app ID when it has no friendly name.""" - assert await _test_firetv_select_source( - hass, "com.app.test2", "com.app.test2", patchers.PATCH_LAUNCH_APP + assert await _test_select_source( + hass, + CONFIG_FIRETV_ADB_SERVER, + "com.app.test2", + "com.app.test2", + patchers.PATCH_LAUNCH_APP, ) async def test_firetv_select_source_stop_app_id(hass): """Test that an app can be stopped using its app ID.""" - assert await _test_firetv_select_source( - hass, "!com.app.test1", "com.app.test1", patchers.PATCH_STOP_APP + assert await _test_select_source( + hass, + CONFIG_FIRETV_ADB_SERVER, + "!com.app.test1", + "com.app.test1", + patchers.PATCH_STOP_APP, ) async def test_firetv_select_source_stop_app_name(hass): """Test that an app can be stopped using its friendly name.""" - assert await _test_firetv_select_source( - hass, "!TEST 1", "com.app.test1", patchers.PATCH_STOP_APP + assert await _test_select_source( + hass, + CONFIG_FIRETV_ADB_SERVER, + "!TEST 1", + "com.app.test1", + patchers.PATCH_STOP_APP, ) async def test_firetv_select_source_stop_app_id_no_name(hass): """Test that an app can be stopped using its app ID when it has no friendly name.""" - assert await _test_firetv_select_source( - hass, "!com.app.test2", "com.app.test2", patchers.PATCH_STOP_APP + assert await _test_select_source( + hass, + CONFIG_FIRETV_ADB_SERVER, + "!com.app.test2", + "com.app.test2", + patchers.PATCH_STOP_APP, ) From 820780996a30768c33d94d0218892ac27d222f10 Mon Sep 17 00:00:00 2001 From: Quentame Date: Sat, 14 Dec 2019 23:06:00 +0100 Subject: [PATCH 297/677] Add battery sensor to iCloud (#29818) * Add battery sensor to iCloud * Update .coveragerc * Review: @balloob & @MartinHjelmare * Review: use f string --- .coveragerc | 1 + homeassistant/components/icloud/__init__.py | 5 -- homeassistant/components/icloud/const.py | 3 +- .../components/icloud/device_tracker.py | 30 ++++--- homeassistant/components/icloud/sensor.py | 85 +++++++++++++++++++ 5 files changed, 104 insertions(+), 20 deletions(-) create mode 100644 homeassistant/components/icloud/sensor.py diff --git a/.coveragerc b/.coveragerc index f4794b59381..c6e7182d326 100644 --- a/.coveragerc +++ b/.coveragerc @@ -321,6 +321,7 @@ omit = homeassistant/components/iaqualink/switch.py homeassistant/components/icloud/__init__.py homeassistant/components/icloud/device_tracker.py + homeassistant/components/icloud/sensor.py homeassistant/components/izone/climate.py homeassistant/components/izone/discovery.py homeassistant/components/izone/__init__.py diff --git a/homeassistant/components/icloud/__init__.py b/homeassistant/components/icloud/__init__.py index 2012f691938..c59f4098951 100644 --- a/homeassistant/components/icloud/__init__.py +++ b/homeassistant/components/icloud/__init__.py @@ -560,11 +560,6 @@ class IcloudDevice: """Return a unique ID.""" return self._device_id - @property - def dev_id(self) -> str: - """Return the device ID.""" - return self._device_id - @property def name(self) -> str: """Return the Apple device name.""" diff --git a/homeassistant/components/icloud/const.py b/homeassistant/components/icloud/const.py index 4e99a378077..ed2fc78fe6d 100644 --- a/homeassistant/components/icloud/const.py +++ b/homeassistant/components/icloud/const.py @@ -14,8 +14,7 @@ DEFAULT_GPS_ACCURACY_THRESHOLD = 500 # meters STORAGE_KEY = DOMAIN STORAGE_VERSION = 1 -# Next PR will add sensor -ICLOUD_COMPONENTS = ["device_tracker"] +ICLOUD_COMPONENTS = ["device_tracker", "sensor"] # pyicloud.AppleDevice status DEVICE_BATTERY_LEVEL = "batteryLevel" diff --git a/homeassistant/components/icloud/device_tracker.py b/homeassistant/components/icloud/device_tracker.py index 4be34728c6d..511ce7f9447 100644 --- a/homeassistant/components/icloud/device_tracker.py +++ b/homeassistant/components/icloud/device_tracker.py @@ -1,8 +1,10 @@ """Support for tracking for iCloud devices.""" import logging +from typing import Dict from homeassistant.components.device_tracker import SOURCE_TYPE_GPS from homeassistant.components.device_tracker.config_entry import TrackerEntity +from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_USERNAME from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.typing import HomeAssistantType @@ -26,13 +28,15 @@ async def async_setup_scanner( pass -async def async_setup_entry(hass: HomeAssistantType, entry, async_add_entities): +async def async_setup_entry( + hass: HomeAssistantType, entry: ConfigEntry, async_add_entities +): """Configure a dispatcher connection based on a config entry.""" username = entry.data[CONF_USERNAME] for device in hass.data[DOMAIN][username].devices.values(): if device.location is None: - _LOGGER.debug("No position found for device %s", device.name) + _LOGGER.debug("No position found for %s", device.name) continue _LOGGER.debug("Adding device_tracker for %s", device.name) @@ -49,12 +53,12 @@ class IcloudTrackerEntity(TrackerEntity): self._unsub_dispatcher = None @property - def unique_id(self): + def unique_id(self) -> str: """Return a unique ID.""" - return f"{self._device.unique_id}_tracker" + return self._device.unique_id @property - def name(self): + def name(self) -> str: """Return the name of the device.""" return self._device.name @@ -74,36 +78,36 @@ class IcloudTrackerEntity(TrackerEntity): return self._device.location[DEVICE_LOCATION_LONGITUDE] @property - def should_poll(self): + def should_poll(self) -> bool: """No polling needed.""" return False @property - def battery_level(self): + def battery_level(self) -> int: """Return the battery level of the device.""" return self._device.battery_level @property - def source_type(self): + def source_type(self) -> str: """Return the source type, eg gps or router, of the device.""" return SOURCE_TYPE_GPS @property - def icon(self): + def icon(self) -> str: """Return the icon.""" return icon_for_icloud_device(self._device) @property - def device_state_attributes(self): + def device_state_attributes(self) -> Dict[str, any]: """Return the device state attributes.""" return self._device.state_attributes @property - def device_info(self): + def device_info(self) -> Dict[str, any]: """Return the device information.""" return { - "identifiers": {(DOMAIN, self.unique_id)}, - "name": self.name, + "identifiers": {(DOMAIN, self._device.unique_id)}, + "name": self._device.name, "manufacturer": "Apple", "model": self._device.device_model, } diff --git a/homeassistant/components/icloud/sensor.py b/homeassistant/components/icloud/sensor.py new file mode 100644 index 00000000000..4351d4ffa19 --- /dev/null +++ b/homeassistant/components/icloud/sensor.py @@ -0,0 +1,85 @@ +"""Support for iCloud sensors.""" +import logging +from typing import Dict + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_USERNAME, DEVICE_CLASS_BATTERY +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.icon import icon_for_battery_level +from homeassistant.helpers.typing import HomeAssistantType + +from . import IcloudDevice +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry( + hass: HomeAssistantType, entry: ConfigEntry, async_add_entities +) -> None: + """Set up iCloud devices sensors based on a config entry.""" + username = entry.data[CONF_USERNAME] + + entities = [] + for device in hass.data[DOMAIN][username].devices.values(): + if device.battery_level is not None: + _LOGGER.debug("Adding battery sensor for %s", device.name) + entities.append(IcloudDeviceBatterySensor(device)) + + async_add_entities(entities, True) + + +class IcloudDeviceBatterySensor(Entity): + """Representation of a iCloud device battery sensor.""" + + def __init__(self, device: IcloudDevice): + """Initialize the battery sensor.""" + self._device = device + + @property + def unique_id(self) -> str: + """Return a unique ID.""" + return f"{self._device.unique_id}_battery" + + @property + def name(self) -> str: + """Sensor name.""" + return f"{self._device.name} battery state" + + @property + def device_class(self) -> str: + """Return the device class of the sensor.""" + return DEVICE_CLASS_BATTERY + + @property + def state(self) -> int: + """Battery state percentage.""" + return self._device.battery_level + + @property + def unit_of_measurement(self) -> str: + """Battery state measured in percentage.""" + return "%" + + @property + def icon(self) -> str: + """Battery state icon handling.""" + return icon_for_battery_level( + battery_level=self._device.battery_level, + charging=self._device.battery_status == "Charging", + ) + + @property + def device_state_attributes(self) -> Dict[str, any]: + """Return default attributes for the iCloud device entity.""" + return self._device.state_attributes + + @property + def device_info(self) -> Dict[str, any]: + """Return the device information.""" + return { + "identifiers": {(DOMAIN, self._device.unique_id)}, + "name": self._device.name, + "manufacturer": "Apple", + "model": self._device.device_model, + } From 6dd496deb49b9d3a2a3cc35309479dabdf24807b Mon Sep 17 00:00:00 2001 From: Chris Mandich Date: Sat, 14 Dec 2019 18:45:29 -0800 Subject: [PATCH 298/677] Fix loading flume integration (#29926) * Fix https://github.com/home-assistant/home-assistant/issues/29853 * Run script.gen_requirements * Update to store Token File in config directory * Update to store Token File in config directory * Update to store Token File in config directory --- homeassistant/components/flume/manifest.json | 4 ++-- homeassistant/components/flume/sensor.py | 6 +++++- requirements_all.txt | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/flume/manifest.json b/homeassistant/components/flume/manifest.json index 800751e80ef..6a9fb7a1fd8 100644 --- a/homeassistant/components/flume/manifest.json +++ b/homeassistant/components/flume/manifest.json @@ -3,9 +3,9 @@ "name": "Flume", "documentation": "https://www.home-assistant.io/integrations/flume/", "requirements": [ - "pyflume==0.2.1" + "pyflume==0.2.4" ], "dependencies": [], "codeowners": ["@ChrisMandich"] } - \ No newline at end of file + diff --git a/homeassistant/components/flume/sensor.py b/homeassistant/components/flume/sensor.py index 5fee408e0dc..e96ce0d96ef 100644 --- a/homeassistant/components/flume/sensor.py +++ b/homeassistant/components/flume/sensor.py @@ -37,11 +37,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None): password = config[CONF_PASSWORD] client_id = config[CONF_CLIENT_ID] client_secret = config[CONF_CLIENT_SECRET] + flume_token_file = hass.config.path("FLUME_TOKEN_FILE") time_zone = str(hass.config.time_zone) name = config[CONF_NAME] flume_entity_list = [] - flume_devices = FlumeDeviceList(username, password, client_id, client_secret) + flume_devices = FlumeDeviceList( + username, password, client_id, client_secret, flume_token_file + ) for device in flume_devices.device_list: if device["type"] == FLUME_TYPE_SENSOR: @@ -53,6 +56,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): device["id"], time_zone, SCAN_INTERVAL, + flume_token_file, ) flume_entity_list.append(FlumeSensor(flume, f"{name} {device['id']}")) diff --git a/requirements_all.txt b/requirements_all.txt index f93db416543..84b67a09c18 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1234,7 +1234,7 @@ pyflexit==0.3 pyflic-homeassistant==0.4.dev0 # homeassistant.components.flume -pyflume==0.2.1 +pyflume==0.2.4 # homeassistant.components.flunearyou pyflunearyou==1.0.3 From a28545b69bdbc91a807bf8821785b852fa0e97c7 Mon Sep 17 00:00:00 2001 From: Tyler <8901422+tyler-public@users.noreply.github.com> Date: Sun, 15 Dec 2019 07:16:20 +0000 Subject: [PATCH 299/677] bump venstar 0.12 (#29954) * bump venstar 0.12 * Update manifest.json --- homeassistant/components/venstar/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/venstar/manifest.json b/homeassistant/components/venstar/manifest.json index e8e36d04467..78243844a93 100644 --- a/homeassistant/components/venstar/manifest.json +++ b/homeassistant/components/venstar/manifest.json @@ -3,7 +3,7 @@ "name": "Venstar", "documentation": "https://www.home-assistant.io/integrations/venstar", "requirements": [ - "venstarcolortouch==0.9" + "venstarcolortouch==0.12" ], "dependencies": [], "codeowners": [] diff --git a/requirements_all.txt b/requirements_all.txt index 84b67a09c18..2f6247ec18e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1988,7 +1988,7 @@ uvcclient==0.11.0 vallox-websocket-api==2.2.0 # homeassistant.components.venstar -venstarcolortouch==0.9 +venstarcolortouch==0.12 # homeassistant.components.meteo_france vigilancemeteo==3.0.0 From 8a5bce81c8f7f3f9d0b2d67564860498bdcab14a Mon Sep 17 00:00:00 2001 From: Jeff Irion Date: Sun, 15 Dec 2019 02:31:59 -0800 Subject: [PATCH 300/677] Bump adb-shell to 0.1.0 and androidtv to 0.0.36 (#29938) * Bump adb-shell to 0.1.0 and androidtv to 0.0.36 * Add test for setting up two devices * Add test_setup_same_device_twice * Fix test_setup_two_devices * Fix coverage * Coverage * Fix flaky 'test_setup_two_devices' * Another stab at coverage * Rename 'address' back to 'host' --- .../components/androidtv/manifest.json | 4 +- .../components/androidtv/media_player.py | 9 ++- requirements_all.txt | 4 +- requirements_test_all.txt | 4 +- tests/components/androidtv/patchers.py | 28 +++---- .../components/androidtv/test_media_player.py | 75 +++++++++++++++++-- 6 files changed, 96 insertions(+), 28 deletions(-) diff --git a/homeassistant/components/androidtv/manifest.json b/homeassistant/components/androidtv/manifest.json index 47768cbd4dc..39e5bfb2cdf 100644 --- a/homeassistant/components/androidtv/manifest.json +++ b/homeassistant/components/androidtv/manifest.json @@ -3,8 +3,8 @@ "name": "Androidtv", "documentation": "https://www.home-assistant.io/integrations/androidtv", "requirements": [ - "adb-shell==0.0.9", - "androidtv==0.0.35", + "adb-shell==0.1.0", + "androidtv==0.0.36", "pure-python-adb==0.2.2.dev0" ], "dependencies": [], diff --git a/homeassistant/components/androidtv/media_player.py b/homeassistant/components/androidtv/media_player.py index a1fb4cea9cd..15acd594bee 100644 --- a/homeassistant/components/androidtv/media_player.py +++ b/homeassistant/components/androidtv/media_player.py @@ -146,7 +146,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): adb_log = f"using Python ADB implementation with adbkey='{adbkey}'" aftv = setup( - host, + config[CONF_HOST], + config[CONF_PORT], adbkey, device_class=config[CONF_DEVICE_CLASS], state_detection_rules=config[CONF_STATE_DETECTION_RULES], @@ -159,7 +160,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) aftv = setup( - host, + config[CONF_HOST], + config[CONF_PORT], config[CONF_ADBKEY], device_class=config[CONF_DEVICE_CLASS], state_detection_rules=config[CONF_STATE_DETECTION_RULES], @@ -171,7 +173,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): adb_log = f"using ADB server at {config[CONF_ADB_SERVER_IP]}:{config[CONF_ADB_SERVER_PORT]}" aftv = setup( - host, + config[CONF_HOST], + config[CONF_PORT], adb_server_ip=config[CONF_ADB_SERVER_IP], adb_server_port=config[CONF_ADB_SERVER_PORT], device_class=config[CONF_DEVICE_CLASS], diff --git a/requirements_all.txt b/requirements_all.txt index 2f6247ec18e..2c4d1c9e5a0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -115,7 +115,7 @@ adafruit-blinka==1.2.1 adafruit-circuitpython-mcp230xx==1.1.2 # homeassistant.components.androidtv -adb-shell==0.0.9 +adb-shell==0.1.0 # homeassistant.components.adguard adguardhome==0.3.0 @@ -215,7 +215,7 @@ ambiclimate==0.2.1 amcrest==1.5.3 # homeassistant.components.androidtv -androidtv==0.0.35 +androidtv==0.0.36 # homeassistant.components.anel_pwrctrl anel_pwrctrl-homeassistant==0.0.1.dev2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 812af1a2b13..472ed8df9a3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -29,7 +29,7 @@ YesssSMS==0.4.1 abodepy==0.16.7 # homeassistant.components.androidtv -adb-shell==0.0.9 +adb-shell==0.1.0 # homeassistant.components.adguard adguardhome==0.3.0 @@ -84,7 +84,7 @@ airly==0.0.2 ambiclimate==0.2.1 # homeassistant.components.androidtv -androidtv==0.0.35 +androidtv==0.0.36 # homeassistant.components.apns apns2==0.3.0 diff --git a/tests/components/androidtv/patchers.py b/tests/components/androidtv/patchers.py index bd05cab2a74..c49b6ad11e9 100644 --- a/tests/components/androidtv/patchers.py +++ b/tests/components/androidtv/patchers.py @@ -3,11 +3,11 @@ from unittest.mock import mock_open, patch -class AdbDeviceFake: - """A fake of the `adb_shell.adb_device.AdbDevice` class.""" +class AdbDeviceTcpFake: + """A fake of the `adb_shell.adb_device.AdbDeviceTcp` class.""" def __init__(self, *args, **kwargs): - """Initialize a fake `adb_shell.adb_device.AdbDevice` instance.""" + """Initialize a fake `adb_shell.adb_device.AdbDeviceTcp` instance.""" self.available = False def close(self): @@ -74,39 +74,39 @@ class DeviceFake: def patch_connect(success): - """Mock the `adb_shell.adb_device.AdbDevice` and `ppadb.client.Client` classes.""" + """Mock the `adb_shell.adb_device.AdbDeviceTcp` and `ppadb.client.Client` classes.""" def connect_success_python(self, *args, **kwargs): - """Mock the `AdbDeviceFake.connect` method when it succeeds.""" + """Mock the `AdbDeviceTcpFake.connect` method when it succeeds.""" self.available = True def connect_fail_python(self, *args, **kwargs): - """Mock the `AdbDeviceFake.connect` method when it fails.""" + """Mock the `AdbDeviceTcpFake.connect` method when it fails.""" raise OSError if success: return { "python": patch( - f"{__name__}.AdbDeviceFake.connect", connect_success_python + f"{__name__}.AdbDeviceTcpFake.connect", connect_success_python ), "server": patch("androidtv.adb_manager.Client", ClientFakeSuccess), } return { - "python": patch(f"{__name__}.AdbDeviceFake.connect", connect_fail_python), + "python": patch(f"{__name__}.AdbDeviceTcpFake.connect", connect_fail_python), "server": patch("androidtv.adb_manager.Client", ClientFakeFail), } def patch_shell(response=None, error=False): - """Mock the `AdbDeviceFake.shell` and `DeviceFake.shell` methods.""" + """Mock the `AdbDeviceTcpFake.shell` and `DeviceFake.shell` methods.""" def shell_success(self, cmd): - """Mock the `AdbDeviceFake.shell` and `DeviceFake.shell` methods when they are successful.""" + """Mock the `AdbDeviceTcpFake.shell` and `DeviceFake.shell` methods when they are successful.""" self.shell_cmd = cmd return response def shell_fail_python(self, cmd): - """Mock the `AdbDeviceFake.shell` method when it fails.""" + """Mock the `AdbDeviceTcpFake.shell` method when it fails.""" self.shell_cmd = cmd raise AttributeError @@ -117,16 +117,16 @@ def patch_shell(response=None, error=False): if not error: return { - "python": patch(f"{__name__}.AdbDeviceFake.shell", shell_success), + "python": patch(f"{__name__}.AdbDeviceTcpFake.shell", shell_success), "server": patch(f"{__name__}.DeviceFake.shell", shell_success), } return { - "python": patch(f"{__name__}.AdbDeviceFake.shell", shell_fail_python), + "python": patch(f"{__name__}.AdbDeviceTcpFake.shell", shell_fail_python), "server": patch(f"{__name__}.DeviceFake.shell", shell_fail_server), } -PATCH_ADB_DEVICE = patch("androidtv.adb_manager.AdbDevice", AdbDeviceFake) +PATCH_ADB_DEVICE_TCP = patch("androidtv.adb_manager.AdbDeviceTcp", AdbDeviceTcpFake) PATCH_ANDROIDTV_OPEN = patch("androidtv.adb_manager.open", mock_open()) PATCH_KEYGEN = patch("homeassistant.components.androidtv.media_player.keygen") PATCH_SIGNER = patch("androidtv.adb_manager.PythonRSASigner") diff --git a/tests/components/androidtv/test_media_player.py b/tests/components/androidtv/test_media_player.py index 860b8738607..15c4897c136 100644 --- a/tests/components/androidtv/test_media_player.py +++ b/tests/components/androidtv/test_media_player.py @@ -95,7 +95,7 @@ async def _test_reconnect(hass, caplog, config): """ patch_key, entity_id = _setup(hass, config) - with patchers.PATCH_ADB_DEVICE, patchers.patch_connect(True)[ + with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell("")[ patch_key @@ -166,7 +166,7 @@ async def _test_adb_shell_returns_none(hass, config): """ patch_key, entity_id = _setup(hass, config) - with patchers.PATCH_ADB_DEVICE, patchers.patch_connect(True)[ + with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell("")[ patch_key @@ -274,7 +274,7 @@ async def test_setup_with_adbkey(hass): config[DOMAIN][CONF_ADBKEY] = hass.config.path("user_provided_adbkey") patch_key, entity_id = _setup(hass, config) - with patchers.PATCH_ADB_DEVICE, patchers.patch_connect(True)[ + with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell("")[ patch_key @@ -292,7 +292,7 @@ async def _test_sources(hass, config0): config[DOMAIN][CONF_APPS] = {"com.app.test1": "TEST 1"} patch_key, entity_id = _setup(hass, config) - with patchers.PATCH_ADB_DEVICE, patchers.patch_connect(True)[ + with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell("")[patch_key]: assert await async_setup_component(hass, DOMAIN, config) @@ -364,7 +364,7 @@ async def _test_select_source(hass, config0, source, expected_arg, method_patch) config[DOMAIN][CONF_APPS] = {"com.app.test1": "TEST 1"} patch_key, entity_id = _setup(hass, config) - with patchers.PATCH_ADB_DEVICE, patchers.patch_connect(True)[ + with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell("")[patch_key]: assert await async_setup_component(hass, DOMAIN, config) @@ -515,3 +515,68 @@ async def test_firetv_select_source_stop_app_id_no_name(hass): "com.app.test2", patchers.PATCH_STOP_APP, ) + + +async def _test_setup_fail(hass, config): + """Test that the entity is not created when the ADB connection is not established.""" + patch_key, entity_id = _setup(hass, config) + + with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(False)[ + patch_key + ], patchers.patch_shell("")[ + patch_key + ], patchers.PATCH_KEYGEN, patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: + assert await async_setup_component(hass, DOMAIN, config) + await hass.helpers.entity_component.async_update_entity(entity_id) + state = hass.states.get(entity_id) + assert state is None + + return True + + +async def test_setup_fail_androidtv(hass): + """Test that the Android TV entity is not created when the ADB connection is not established.""" + assert await _test_setup_fail(hass, CONFIG_ANDROIDTV_PYTHON_ADB) + + +async def test_setup_fail_firetv(hass): + """Test that the Fire TV entity is not created when the ADB connection is not established.""" + assert await _test_setup_fail(hass, CONFIG_FIRETV_PYTHON_ADB) + + +async def test_setup_two_devices(hass): + """Test that two devices can be set up.""" + config = { + DOMAIN: [ + CONFIG_ANDROIDTV_ADB_SERVER[DOMAIN], + CONFIG_FIRETV_ADB_SERVER[DOMAIN].copy(), + ] + } + config[DOMAIN][1][CONF_HOST] = "127.0.0.2" + + patch_key = "server" + with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ + patch_key + ], patchers.patch_shell("")[patch_key]: + assert await async_setup_component(hass, DOMAIN, config) + + for entity_id in ["media_player.android_tv", "media_player.fire_tv"]: + await hass.helpers.entity_component.async_update_entity(entity_id) + state = hass.states.get(entity_id) + assert state is not None + assert state.state == STATE_OFF + + +async def test_setup_same_device_twice(hass): + """Test that setup succeeds with a duplicated config entry.""" + patch_key = "server" + + with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ + patch_key + ], patchers.patch_shell("")[patch_key]: + assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER) + + with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ + patch_key + ], patchers.patch_shell("")[patch_key]: + assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER) From 84a711543599ef9a162e92cf5cf74b5778231cc3 Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Sun, 15 Dec 2019 13:14:50 +0100 Subject: [PATCH 301/677] Start of using hass state for tests rather than direct object (#29377) --- tests/components/arcam_fmj/conftest.py | 7 ++ .../components/arcam_fmj/test_media_player.py | 71 +++++++++++++------ 2 files changed, 56 insertions(+), 22 deletions(-) diff --git a/tests/components/arcam_fmj/conftest.py b/tests/components/arcam_fmj/conftest.py index 10405dbeb11..6611cbaf9eb 100644 --- a/tests/components/arcam_fmj/conftest.py +++ b/tests/components/arcam_fmj/conftest.py @@ -16,6 +16,7 @@ MOCK_TURN_ON = { "data": {"entity_id": "switch.test"}, } MOCK_NAME = "dummy" +MOCK_ENTITY_ID = "media_player.arcam_fmj_1" MOCK_CONFIG = DEVICE_SCHEMA({CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}) @@ -41,6 +42,10 @@ def state_fixture(client): state.client = client state.zn = 1 state.get_power.return_value = True + state.get_volume.return_value = 0.0 + state.get_source_list.return_value = [] + state.get_incoming_audio_format.return_value = (0, 0) + state.get_mute.return_value = None return state @@ -48,5 +53,7 @@ def state_fixture(client): def player_fixture(hass, state): """Get standard player.""" player = ArcamFmj(state, MOCK_NAME, None) + player.entity_id = MOCK_ENTITY_ID + player.hass = hass player.async_schedule_update_ha_state = Mock() return player diff --git a/tests/components/arcam_fmj/test_media_player.py b/tests/components/arcam_fmj/test_media_player.py index 2d2c14f8e18..06dab76ec87 100644 --- a/tests/components/arcam_fmj/test_media_player.py +++ b/tests/components/arcam_fmj/test_media_player.py @@ -5,18 +5,10 @@ from arcam.fmj import DecodeMode2CH, DecodeModeMCH, IncomingAudioFormat, SourceC from asynctest.mock import ANY, MagicMock, Mock, PropertyMock, patch import pytest -from homeassistant.components.arcam_fmj.const import ( - DOMAIN, - SIGNAL_CLIENT_DATA, - SIGNAL_CLIENT_STARTED, - SIGNAL_CLIENT_STOPPED, -) -from homeassistant.components.arcam_fmj.media_player import ArcamFmj from homeassistant.components.media_player.const import MEDIA_TYPE_MUSIC -from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant -from .conftest import MOCK_HOST, MOCK_NAME, MOCK_PORT +from .conftest import MOCK_HOST, MOCK_NAME, MOCK_PORT, MOCK_ENTITY_ID MOCK_TURN_ON = { "service": "switch.turn_on", @@ -24,50 +16,69 @@ MOCK_TURN_ON = { } +async def update(player, force_refresh=False): + """Force a update of player and return current state data.""" + await player.async_update_ha_state(force_refresh=force_refresh) + return player.hass.states.get(player.entity_id) + + async def test_properties(player, state): """Test standard properties.""" assert player.unique_id is None assert player.device_info == { - "identifiers": {(DOMAIN, MOCK_HOST, MOCK_PORT)}, + "identifiers": {("arcam_fmj", MOCK_HOST, MOCK_PORT)}, "model": "FMJ", "manufacturer": "Arcam", } assert not player.should_poll -async def test_powered_off(player, state): +async def test_powered_off(hass, player, state): """Test properties in powered off state.""" state.get_source.return_value = None state.get_power.return_value = None - assert player.source is None - assert player.state == STATE_OFF + + data = await update(player) + assert "source" not in data.attributes + assert data.state == "off" async def test_powered_on(player, state): """Test properties in powered on state.""" state.get_source.return_value = SourceCodes.PVR state.get_power.return_value = True - assert player.source == "PVR" - assert player.state == STATE_ON + + data = await update(player) + assert data.attributes["source"] == "PVR" + assert data.state == "on" async def test_supported_features_no_service(player, state): """Test support when turn on service exist.""" state.get_power.return_value = None - assert player.supported_features == 68876 + data = await update(player) + assert data.attributes["supported_features"] == 68876 state.get_power.return_value = False - assert player.supported_features == 69004 + data = await update(player) + assert data.attributes["supported_features"] == 69004 async def test_supported_features_service(hass, state): """Test support when turn on service exist.""" + from homeassistant.components.arcam_fmj.media_player import ArcamFmj + player = ArcamFmj(state, "dummy", MOCK_TURN_ON) + player.hass = hass + player.entity_id = MOCK_ENTITY_ID + state.get_power.return_value = None - assert player.supported_features == 69004 + data = await update(player) + assert data.attributes["supported_features"] == 69004 state.get_power.return_value = False - assert player.supported_features == 69004 + data = await update(player) + assert data.attributes["supported_features"] == 69004 async def test_turn_on_without_service(player, state): @@ -83,8 +94,11 @@ async def test_turn_on_without_service(player, state): async def test_turn_on_with_service(hass, state): """Test support when turn on service exist.""" + from homeassistant.components.arcam_fmj.media_player import ArcamFmj + player = ArcamFmj(state, "dummy", MOCK_TURN_ON) player.hass = Mock(HomeAssistant) + player.entity_id = MOCK_ENTITY_ID with patch( "homeassistant.components.arcam_fmj.media_player.async_call_from_config" ) as async_call_from_config: @@ -122,7 +136,7 @@ async def test_name(player): async def test_update(player, state): """Test update.""" - await player.async_update() + await update(player, force_refresh=True) state.update.assert_called_with() @@ -157,7 +171,8 @@ async def test_select_source(player, state, source, value): async def test_source_list(player, state): """Test source list.""" state.get_source_list.return_value = [SourceCodes.BD] - assert player.source_list == ["BD"] + data = await update(player) + assert data.attributes["source_list"] == ["BD"] @pytest.mark.parametrize( @@ -317,16 +332,28 @@ async def test_media_artist(player, state, source, dls, artist): ) async def test_media_title(player, state, source, channel, title): """Test media title.""" + from homeassistant.components.arcam_fmj.media_player import ArcamFmj + state.get_source.return_value = source with patch.object( ArcamFmj, "media_channel", new_callable=PropertyMock ) as media_channel: media_channel.return_value = channel - assert player.media_title == title + data = await update(player) + if title is None: + assert "media_title" not in data.attributes + else: + assert data.attributes["media_title"] == title async def test_added_to_hass(player, state): """Test addition to hass.""" + from homeassistant.components.arcam_fmj.const import ( + SIGNAL_CLIENT_DATA, + SIGNAL_CLIENT_STARTED, + SIGNAL_CLIENT_STOPPED, + ) + connectors = {} def _connect(signal, fun): From 09f3362cc6f7a317854254ed06701ea023f0ac59 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Sun, 15 Dec 2019 10:07:57 -0500 Subject: [PATCH 302/677] isort fix on test_media_player (#29965) --- tests/components/arcam_fmj/test_media_player.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/components/arcam_fmj/test_media_player.py b/tests/components/arcam_fmj/test_media_player.py index 06dab76ec87..8448a25a7fd 100644 --- a/tests/components/arcam_fmj/test_media_player.py +++ b/tests/components/arcam_fmj/test_media_player.py @@ -8,7 +8,7 @@ import pytest from homeassistant.components.media_player.const import MEDIA_TYPE_MUSIC from homeassistant.core import HomeAssistant -from .conftest import MOCK_HOST, MOCK_NAME, MOCK_PORT, MOCK_ENTITY_ID +from .conftest import MOCK_ENTITY_ID, MOCK_HOST, MOCK_NAME, MOCK_PORT MOCK_TURN_ON = { "service": "switch.turn_on", From 9c0799eb0ac0e2dd6edb52199597579a45abc0aa Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 15 Dec 2019 17:41:56 +0100 Subject: [PATCH 303/677] Upgrade keyring to 20.0.0 and keyrings.alt to 3.4.0 (#29960) --- homeassistant/scripts/keyring.py | 2 +- requirements_all.txt | 4 ++-- requirements_test_all.txt | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/scripts/keyring.py b/homeassistant/scripts/keyring.py index 63b4fd1a37e..594d897ee4c 100644 --- a/homeassistant/scripts/keyring.py +++ b/homeassistant/scripts/keyring.py @@ -6,7 +6,7 @@ import os from homeassistant.util.yaml import _SECRET_NAMESPACE # mypy: allow-untyped-defs -REQUIREMENTS = ["keyring==19.3.0", "keyrings.alt==3.2.0"] +REQUIREMENTS = ["keyring==20.0.0", "keyrings.alt==3.4.0"] def run(args): diff --git a/requirements_all.txt b/requirements_all.txt index 2c4d1c9e5a0..33cc5c725a8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -742,10 +742,10 @@ kaiterra-async-client==0.0.2 keba-kecontact==0.2.0 # homeassistant.scripts.keyring -keyring==19.3.0 +keyring==20.0.0 # homeassistant.scripts.keyring -keyrings.alt==3.2.0 +keyrings.alt==3.4.0 # homeassistant.components.kiwi kiwiki-client==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 472ed8df9a3..9e80c2585e6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -259,10 +259,10 @@ influxdb==5.2.3 jsonpath==0.82 # homeassistant.scripts.keyring -keyring==19.3.0 +keyring==20.0.0 # homeassistant.scripts.keyring -keyrings.alt==3.2.0 +keyrings.alt==3.4.0 # homeassistant.components.dyson libpurecool==0.5.0 From 012c09ce00d1443401e22ccfae9cdd15d193053b Mon Sep 17 00:00:00 2001 From: Aaron Godfrey Date: Sun, 15 Dec 2019 09:47:11 -0800 Subject: [PATCH 304/677] Fix example value for Todoist service (#29953) --- homeassistant/components/todoist/services.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/todoist/services.yaml b/homeassistant/components/todoist/services.yaml index c2d23cc4bec..3382e27693d 100644 --- a/homeassistant/components/todoist/services.yaml +++ b/homeassistant/components/todoist/services.yaml @@ -21,5 +21,5 @@ new_task: example: en due_date: description: The day this task is due, in format YYYY-MM-DD. - example: 2019-10-22 + example: "2019-10-22" From d6f317c0a988a6fdc492fbcec55c59b479533ecc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sun, 15 Dec 2019 22:57:23 +0200 Subject: [PATCH 305/677] Remove deprecated rflink configs (#29972) They've been deprecated and automatically replaced since July 2017 already, fe6a4b8ae5953c8d565dc93acc9d34c715a80aee --- homeassistant/components/rflink/__init__.py | 19 ------------------- homeassistant/components/rflink/light.py | 13 ------------- homeassistant/components/rflink/sensor.py | 5 ----- homeassistant/components/rflink/switch.py | 13 ------------- tests/components/rflink/test_sensor.py | 6 +++--- 5 files changed, 3 insertions(+), 53 deletions(-) diff --git a/homeassistant/components/rflink/__init__.py b/homeassistant/components/rflink/__init__.py index b3e1d2b16b7..2e5875b9d08 100644 --- a/homeassistant/components/rflink/__init__.py +++ b/homeassistant/components/rflink/__init__.py @@ -19,7 +19,6 @@ from homeassistant.const import ( from homeassistant.core import CoreState, callback from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.deprecation import get_deprecated from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, @@ -33,12 +32,9 @@ ATTR_EVENT = "event" ATTR_STATE = "state" CONF_ALIASES = "aliases" -CONF_ALIASSES = "aliasses" CONF_GROUP_ALIASES = "group_aliases" -CONF_GROUP_ALIASSES = "group_aliasses" CONF_GROUP = "group" CONF_NOGROUP_ALIASES = "nogroup_aliases" -CONF_NOGROUP_ALIASSES = "nogroup_aliasses" CONF_DEVICE_DEFAULTS = "device_defaults" CONF_DEVICE_ID = "device_id" CONF_DEVICES = "devices" @@ -563,18 +559,3 @@ class SwitchableRflinkDevice(RflinkCommand, RestoreEntity): def async_turn_off(self, **kwargs): """Turn the device off.""" return self._async_handle_command("turn_off") - - -DEPRECATED_CONFIG_OPTIONS = [CONF_ALIASSES, CONF_GROUP_ALIASSES, CONF_NOGROUP_ALIASSES] -REPLACEMENT_CONFIG_OPTIONS = [CONF_ALIASES, CONF_GROUP_ALIASES, CONF_NOGROUP_ALIASES] - - -def remove_deprecated(config): - """Remove deprecated config options from device config.""" - for index, deprecated_option in enumerate(DEPRECATED_CONFIG_OPTIONS): - if deprecated_option in config: - replacement_option = REPLACEMENT_CONFIG_OPTIONS[index] - # generate deprecation warning - get_deprecated(config, replacement_option, deprecated_option) - # remove old config value replacing new one - config[replacement_option] = config.pop(deprecated_option) diff --git a/homeassistant/components/rflink/light.py b/homeassistant/components/rflink/light.py index 682d45f8f42..db616b92fc4 100644 --- a/homeassistant/components/rflink/light.py +++ b/homeassistant/components/rflink/light.py @@ -14,23 +14,19 @@ import homeassistant.helpers.config_validation as cv from . import ( CONF_ALIASES, - CONF_ALIASSES, CONF_AUTOMATIC_ADD, CONF_DEVICE_DEFAULTS, CONF_DEVICES, CONF_FIRE_EVENT, CONF_GROUP, CONF_GROUP_ALIASES, - CONF_GROUP_ALIASSES, CONF_NOGROUP_ALIASES, - CONF_NOGROUP_ALIASSES, CONF_SIGNAL_REPETITIONS, DATA_DEVICE_REGISTER, DEVICE_DEFAULTS_SCHEMA, EVENT_KEY_COMMAND, EVENT_KEY_ID, SwitchableRflinkDevice, - remove_deprecated, ) _LOGGER = logging.getLogger(__name__) @@ -65,14 +61,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( vol.Optional(CONF_FIRE_EVENT): cv.boolean, vol.Optional(CONF_SIGNAL_REPETITIONS): vol.Coerce(int), vol.Optional(CONF_GROUP, default=True): cv.boolean, - # deprecated config options - vol.Optional(CONF_ALIASSES): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_GROUP_ALIASSES): vol.All( - cv.ensure_list, [cv.string] - ), - vol.Optional(CONF_NOGROUP_ALIASSES): vol.All( - cv.ensure_list, [cv.string] - ), } ) }, @@ -131,7 +119,6 @@ def devices_from_config(domain_config): entity_class = entity_class_for_type(entity_type) device_config = dict(domain_config[CONF_DEVICE_DEFAULTS], **config) - remove_deprecated(device_config) is_hybrid = entity_class is HybridRflinkLight diff --git a/homeassistant/components/rflink/sensor.py b/homeassistant/components/rflink/sensor.py index aa0ef4f9c62..bc736a1ede6 100644 --- a/homeassistant/components/rflink/sensor.py +++ b/homeassistant/components/rflink/sensor.py @@ -15,7 +15,6 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from . import ( CONF_ALIASES, - CONF_ALIASSES, CONF_AUTOMATIC_ADD, CONF_DEVICES, DATA_DEVICE_REGISTER, @@ -27,7 +26,6 @@ from . import ( SIGNAL_HANDLE_EVENT, TMP_ENTITY, RflinkDevice, - remove_deprecated, ) _LOGGER = logging.getLogger(__name__) @@ -52,8 +50,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( vol.Optional(CONF_ALIASES, default=[]): vol.All( cv.ensure_list, [cv.string] ), - # deprecated config options - vol.Optional(CONF_ALIASSES): vol.All(cv.ensure_list, [cv.string]), } ) }, @@ -80,7 +76,6 @@ def devices_from_config(domain_config): config[ATTR_UNIT_OF_MEASUREMENT] = lookup_unit_for_sensor_type( config[CONF_SENSOR_TYPE] ) - remove_deprecated(config) device = RflinkSensor(device_id, **config) devices.append(device) diff --git a/homeassistant/components/rflink/switch.py b/homeassistant/components/rflink/switch.py index c9173acc1a5..8e0ce9a0c8e 100644 --- a/homeassistant/components/rflink/switch.py +++ b/homeassistant/components/rflink/switch.py @@ -9,19 +9,15 @@ import homeassistant.helpers.config_validation as cv from . import ( CONF_ALIASES, - CONF_ALIASSES, CONF_DEVICE_DEFAULTS, CONF_DEVICES, CONF_FIRE_EVENT, CONF_GROUP, CONF_GROUP_ALIASES, - CONF_GROUP_ALIASSES, CONF_NOGROUP_ALIASES, - CONF_NOGROUP_ALIASSES, CONF_SIGNAL_REPETITIONS, DEVICE_DEFAULTS_SCHEMA, SwitchableRflinkDevice, - remove_deprecated, ) _LOGGER = logging.getLogger(__name__) @@ -47,14 +43,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( vol.Optional(CONF_FIRE_EVENT): cv.boolean, vol.Optional(CONF_SIGNAL_REPETITIONS): vol.Coerce(int), vol.Optional(CONF_GROUP, default=True): cv.boolean, - # deprecated config options - vol.Optional(CONF_ALIASSES): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_GROUP_ALIASSES): vol.All( - cv.ensure_list, [cv.string] - ), - vol.Optional(CONF_NOGROUP_ALIASSES): vol.All( - cv.ensure_list, [cv.string] - ), } ) }, @@ -68,7 +56,6 @@ def devices_from_config(domain_config): devices = [] for device_id, config in domain_config[CONF_DEVICES].items(): device_config = dict(domain_config[CONF_DEVICE_DEFAULTS], **config) - remove_deprecated(device_config) device = RflinkSwitch(device_id, **device_config) devices.append(device) diff --git a/tests/components/rflink/test_sensor.py b/tests/components/rflink/test_sensor.py index 3fea3ef6ef4..b68e1f959f1 100644 --- a/tests/components/rflink/test_sensor.py +++ b/tests/components/rflink/test_sensor.py @@ -115,8 +115,8 @@ async def test_entity_availability(hass, monkeypatch): assert hass.states.get("sensor.test").state == STATE_UNKNOWN -async def test_aliasses(hass, monkeypatch): - """Validate the response to sensor's alias (with aliasses).""" +async def test_aliases(hass, monkeypatch): + """Validate the response to sensor's alias (with aliases).""" config = { "rflink": {"port": "/dev/ttyABC0"}, DOMAIN: { @@ -125,7 +125,7 @@ async def test_aliasses(hass, monkeypatch): "test_02": { "name": "test_02", "sensor_type": "humidity", - "aliasses": ["test_alias_02_0"], + "aliases": ["test_alias_02_0"], } }, }, From 95a6a7502ac253a049a65380125d537dffe9e7cd Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Mon, 16 Dec 2019 00:32:25 +0000 Subject: [PATCH 306/677] [ci skip] Translation update --- .../components/adguard/.translations/it.json | 4 +- .../components/deconz/.translations/it.json | 4 +- .../components/demo/.translations/ja.json | 5 +++ .../components/icloud/.translations/ja.json | 27 +++++++++++++ .../components/icloud/.translations/pl.json | 38 +++++++++++++++++++ .../components/mqtt/.translations/it.json | 4 +- .../components/soma/.translations/pl.json | 4 +- .../components/zha/.translations/fr.json | 2 +- 8 files changed, 80 insertions(+), 8 deletions(-) create mode 100644 homeassistant/components/demo/.translations/ja.json create mode 100644 homeassistant/components/icloud/.translations/ja.json create mode 100644 homeassistant/components/icloud/.translations/pl.json diff --git a/homeassistant/components/adguard/.translations/it.json b/homeassistant/components/adguard/.translations/it.json index 1b3ce014d90..6dc6ae18d81 100644 --- a/homeassistant/components/adguard/.translations/it.json +++ b/homeassistant/components/adguard/.translations/it.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "adguard_home_addon_outdated": "Questa integrazione richiede AdGuard Home {minimal_version} o versione successiva, si dispone di {current_version}. Aggiorna il componente aggiuntivo Hass.io AdGuard Home.", + "adguard_home_addon_outdated": "Questa integrazione richiede AdGuard Home {minimal_version} o versione successiva, si dispone di {current_version}. Aggiorna il componente aggiuntivo AdGuard Home di Hass.io.", "adguard_home_outdated": "Questa integrazione richiede AdGuard Home {minimal_version} o versione successiva, si dispone di {current_version}.", "existing_instance_updated": "Configurazione esistente aggiornata.", "single_instance_allowed": "\u00c8 consentita solo una singola configurazione di AdGuard Home." @@ -11,7 +11,7 @@ }, "step": { "hassio_confirm": { - "description": "Vuoi configurare Home Assistant per connettersi alla AdGuard Home fornita dal componente aggiuntivo di Hass.io: {addon} ?", + "description": "Vuoi configurare Home Assistant per connettersi alla AdGuard Home fornita dal componente aggiuntivo di Hass.io: {addon}?", "title": "AdGuard Home tramite il componente aggiuntivo di Hass.io" }, "user": { diff --git a/homeassistant/components/deconz/.translations/it.json b/homeassistant/components/deconz/.translations/it.json index 33d49dfca46..99e5622129f 100644 --- a/homeassistant/components/deconz/.translations/it.json +++ b/homeassistant/components/deconz/.translations/it.json @@ -18,8 +18,8 @@ "allow_clip_sensor": "Consenti l'importazione di sensori virtuali", "allow_deconz_groups": "Consenti l'importazione di gruppi deCONZ" }, - "description": "Vuoi configurare Home Assistant per connettersi al gateway deCONZ fornito dal componente aggiuntivo hass.io {addon} ?", - "title": "Gateway Zigbee deCONZ tramite l'add-on Hass.io" + "description": "Vuoi configurare Home Assistant per connettersi al gateway deCONZ fornito dal componente aggiuntivo di Hass.io: {addon}?", + "title": "Gateway Pigmee deCONZ tramite il componente aggiuntivo di Hass.io" }, "init": { "data": { diff --git a/homeassistant/components/demo/.translations/ja.json b/homeassistant/components/demo/.translations/ja.json new file mode 100644 index 00000000000..529170b111d --- /dev/null +++ b/homeassistant/components/demo/.translations/ja.json @@ -0,0 +1,5 @@ +{ + "config": { + "title": "\u30c7\u30e2" + } +} \ No newline at end of file diff --git a/homeassistant/components/icloud/.translations/ja.json b/homeassistant/components/icloud/.translations/ja.json new file mode 100644 index 00000000000..f5c3be53639 --- /dev/null +++ b/homeassistant/components/icloud/.translations/ja.json @@ -0,0 +1,27 @@ +{ + "config": { + "step": { + "trusted_device": { + "data": { + "trusted_device": "\u4fe1\u983c\u3067\u304d\u308b\u30c7\u30d0\u30a4\u30b9" + }, + "description": "\u4fe1\u983c\u3067\u304d\u308b\u30c7\u30d0\u30a4\u30b9\u3092\u9078\u629e\u3057\u3066\u304f\u3060\u3055\u3044", + "title": "iCloud \u306e\u4fe1\u983c\u3067\u304d\u308b\u30c7\u30d0\u30a4\u30b9" + }, + "user": { + "data": { + "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", + "username": "E\u30e1\u30fc\u30eb" + }, + "description": "\u8cc7\u683c\u60c5\u5831\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044", + "title": "iCloud \u306e\u8cc7\u683c\u60c5\u5831" + }, + "verification_code": { + "data": { + "verification_code": "\u8a8d\u8a3c\u30b3\u30fc\u30c9" + }, + "title": "iCloud \u306e\u8a8d\u8a3c\u30b3\u30fc\u30c9" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/icloud/.translations/pl.json b/homeassistant/components/icloud/.translations/pl.json new file mode 100644 index 00000000000..f154f77f186 --- /dev/null +++ b/homeassistant/components/icloud/.translations/pl.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "username_exists": "Konto jest ju\u017c skonfigurowane" + }, + "error": { + "login": "B\u0142\u0105d logowania: sprawd\u017a adres e-mail i has\u0142o", + "send_verification_code": "Nie uda\u0142o si\u0119 wys\u0142a\u0107 kodu weryfikacyjnego", + "username_exists": "Konto jest ju\u017c skonfigurowane", + "validate_verification_code": "Nie uda\u0142o si\u0119 zweryfikowa\u0107 kodu weryfikacyjnego, wybierz urz\u0105dzenie zaufane i ponownie rozpocznij weryfikacj\u0119" + }, + "step": { + "trusted_device": { + "data": { + "trusted_device": "Zaufane urz\u0105dzenie" + }, + "description": "Wybierz zaufane urz\u0105dzenie", + "title": "Zaufane urz\u0105dzenie iCloud" + }, + "user": { + "data": { + "password": "Has\u0142o", + "username": "E-mail" + }, + "description": "Wprowad\u017a dane uwierzytelniaj\u0105ce", + "title": "Dane uwierzytelniaj\u0105ce iCloud" + }, + "verification_code": { + "data": { + "verification_code": "Kod weryfikacyjny" + }, + "description": "Wprowad\u017a kod weryfikacyjny, kt\u00f3ry w\u0142a\u015bnie otrzyma\u0142e\u015b z iCloud", + "title": "Kod weryfikacyjny iCloud" + } + }, + "title": "Apple iCloud" + } +} \ No newline at end of file diff --git a/homeassistant/components/mqtt/.translations/it.json b/homeassistant/components/mqtt/.translations/it.json index ed33b182a96..cf2b3ddf7d5 100644 --- a/homeassistant/components/mqtt/.translations/it.json +++ b/homeassistant/components/mqtt/.translations/it.json @@ -22,8 +22,8 @@ "data": { "discovery": "Attiva l'individuazione" }, - "description": "Vuoi configurare Home Assistant per connettersi al broker MQTT fornito dall'add-on di Hass.io {addon}?", - "title": "Broker MQTT tramite l'add-on di Hass.io" + "description": "Vuoi configurare Home Assistant per connettersi al broker MQTT fornito dal componente aggiuntivo di Hass.io: {addon}?", + "title": "Broker MQTT tramite il componente aggiuntivo di Hass.io" } }, "title": "MQTT" diff --git a/homeassistant/components/soma/.translations/pl.json b/homeassistant/components/soma/.translations/pl.json index c71e160142e..102413bf446 100644 --- a/homeassistant/components/soma/.translations/pl.json +++ b/homeassistant/components/soma/.translations/pl.json @@ -3,7 +3,9 @@ "abort": { "already_setup": "Mo\u017cesz skonfigurowa\u0107 tylko jedno konto Soma.", "authorize_url_timeout": "Przekroczono limit czasu generowania URL autoryzacji.", - "missing_configuration": "Komponent Soma nie jest skonfigurowany. Post\u0119puj zgodnie z dokumentacj\u0105." + "connection_error": "Nie uda\u0142o si\u0119 po\u0142\u0105czy\u0107 z SOMA Connect.", + "missing_configuration": "Komponent Soma nie jest skonfigurowany. Post\u0119puj zgodnie z dokumentacj\u0105.", + "result_error": "SOMA Connect odpowiedzia\u0142 statusem b\u0142\u0119du." }, "create_entry": { "default": "Pomy\u015blnie uwierzytelniono z Soma" diff --git a/homeassistant/components/zha/.translations/fr.json b/homeassistant/components/zha/.translations/fr.json index 9b1ba025d7c..5d8bdfa82eb 100644 --- a/homeassistant/components/zha/.translations/fr.json +++ b/homeassistant/components/zha/.translations/fr.json @@ -59,7 +59,7 @@ "remote_button_long_release": "Bouton \" {subtype} \" rel\u00e2ch\u00e9 apr\u00e8s un appui long", "remote_button_quadruple_press": "bouton \" {subtype} \" quadruple clics", "remote_button_quintuple_press": "bouton \" {subtype} \" quintuple clics", - "remote_button_short_press": "bouton \" {subtype} \" enfonc\u00e9", + "remote_button_short_press": "bouton \"{subtype}\" est press\u00e9", "remote_button_short_release": "Bouton \" {subtype} \" est rel\u00e2ch\u00e9", "remote_button_triple_press": "Bouton \"{subtype}\" \u00e0 trois clics" } From bfafa77016cc260d53702c7d3d9323b183c29e88 Mon Sep 17 00:00:00 2001 From: Andrew Onyshchuk Date: Sun, 15 Dec 2019 19:02:18 -0600 Subject: [PATCH 307/677] Fix support for legacy Z-Wave thermostats (#29955) This brings back support for Z-Wave thermostats of SETPOINT_THERMOSTAT specific device class. Such devices don't have COMMAND_CLASS_THERMOSTAT_MODE and are now handled separately. --- homeassistant/components/zwave/climate.py | 91 ++++++-- .../components/zwave/discovery_schemas.py | 51 +++- tests/components/zwave/test_climate.py | 217 +++++++++++++++++- tests/components/zwave/test_init.py | 42 +++- 4 files changed, 372 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/zwave/climate.py b/homeassistant/components/zwave/climate.py index 81b37aa5cb6..2b421db70b5 100644 --- a/homeassistant/components/zwave/climate.py +++ b/homeassistant/components/zwave/climate.py @@ -1,7 +1,7 @@ """Support for Z-Wave climate devices.""" # Because we do not compile openzwave on CI import logging -from typing import Optional +from typing import Optional, Tuple from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( @@ -34,7 +34,7 @@ from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from . import ZWaveDeviceEntity +from . import ZWaveDeviceEntity, const _LOGGER = logging.getLogger(__name__) @@ -147,10 +147,14 @@ async def async_setup_entry(hass, config_entry, async_add_entities): def get_device(hass, values, **kwargs): """Create Z-Wave entity device.""" temp_unit = hass.config.units.temperature_unit - return ZWaveClimate(values, temp_unit) + if values.primary.command_class == const.COMMAND_CLASS_THERMOSTAT_SETPOINT: + return ZWaveClimateSingleSetpoint(values, temp_unit) + if values.primary.command_class == const.COMMAND_CLASS_THERMOSTAT_MODE: + return ZWaveClimateMultipleSetpoint(values, temp_unit) + return None -class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): +class ZWaveClimateBase(ZWaveDeviceEntity, ClimateDevice): """Representation of a Z-Wave Climate device.""" def __init__(self, values, temp_unit): @@ -188,18 +192,21 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): self._zxt_120 = 1 self.update_properties() - def _current_mode_setpoints(self): - current_mode = str(self.values.primary.data).lower() - setpoints_names = MODE_SETPOINT_MAPPINGS.get(current_mode, ()) - return tuple(getattr(self.values, name, None) for name in setpoints_names) + def _mode(self) -> None: + """Return thermostat mode Z-Wave value.""" + raise NotImplementedError() + + def _current_mode_setpoints(self) -> Tuple: + """Return a tuple of current setpoint Z-Wave value(s).""" + raise NotImplementedError() @property def supported_features(self): """Return the list of supported features.""" support = SUPPORT_TARGET_TEMPERATURE - if HVAC_MODE_HEAT_COOL in self._hvac_list: + if self._hvac_list and HVAC_MODE_HEAT_COOL in self._hvac_list: support |= SUPPORT_TARGET_TEMPERATURE_RANGE - if PRESET_AWAY in self._preset_list: + if self._preset_list and PRESET_AWAY in self._preset_list: support |= SUPPORT_TARGET_TEMPERATURE_RANGE if self.values.fan_mode: @@ -237,13 +244,13 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): def _update_operation_mode(self): """Update hvac and preset modes.""" - if self.values.primary: + if self._mode(): self._hvac_list = [] self._hvac_mapping = {} self._preset_list = [] self._preset_mapping = {} - mode_list = self.values.primary.data_items + mode_list = self._mode().data_items if mode_list: for mode in mode_list: ha_mode = HVAC_STATE_MAPPINGS.get(str(mode).lower()) @@ -271,7 +278,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): # Presets are supported self._preset_list.append(PRESET_NONE) - current_mode = self.values.primary.data + current_mode = self._mode().data _LOGGER.debug("current_mode=%s", current_mode) _hvac_temp = next( ( @@ -424,7 +431,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): Need to be one of HVAC_MODE_*. """ - if self.values.primary: + if self._mode(): return self._hvac_mode return self._default_hvac_mode @@ -434,7 +441,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): Need to be a subset of HVAC_MODES. """ - if self.values.primary: + if self._mode(): return self._hvac_list return [] @@ -451,7 +458,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): """Return true if aux heater.""" if not self._aux_heat: return None - if self.values.primary.data == AUX_HEAT_ZWAVE_MODE: + if self._mode().data == AUX_HEAT_ZWAVE_MODE: return True return False @@ -461,7 +468,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): Need to be one of PRESET_*. """ - if self.values.primary: + if self._mode(): return self._preset_mode return PRESET_NONE @@ -471,7 +478,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): Need to be a subset of PRESET_MODES. """ - if self.values.primary: + if self._mode(): return self._preset_list return [] @@ -520,11 +527,11 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): def set_hvac_mode(self, hvac_mode): """Set new target hvac mode.""" _LOGGER.debug("Set hvac_mode to %s", hvac_mode) - if not self.values.primary: + if not self._mode(): return operation_mode = self._hvac_mapping.get(hvac_mode) _LOGGER.debug("Set operation_mode to %s", operation_mode) - self.values.primary.data = operation_mode + self._mode().data = operation_mode def turn_aux_heat_on(self): """Turn auxillary heater on.""" @@ -532,7 +539,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): return operation_mode = AUX_HEAT_ZWAVE_MODE _LOGGER.debug("Aux heat on. Set operation mode to %s", operation_mode) - self.values.primary.data = operation_mode + self._mode().data = operation_mode def turn_aux_heat_off(self): """Turn auxillary heater off.""" @@ -543,23 +550,23 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): else: operation_mode = self._hvac_mapping.get(HVAC_MODE_OFF) _LOGGER.debug("Aux heat off. Set operation mode to %s", operation_mode) - self.values.primary.data = operation_mode + self._mode().data = operation_mode def set_preset_mode(self, preset_mode): """Set new target preset mode.""" _LOGGER.debug("Set preset_mode to %s", preset_mode) - if not self.values.primary: + if not self._mode(): return if preset_mode == PRESET_NONE: # Activate the current hvac mode self._update_operation_mode() operation_mode = self._hvac_mapping.get(self.hvac_mode) _LOGGER.debug("Set operation_mode to %s", operation_mode) - self.values.primary.data = operation_mode + self._mode().data = operation_mode else: operation_mode = self._preset_mapping.get(preset_mode, preset_mode) _LOGGER.debug("Set operation_mode to %s", operation_mode) - self.values.primary.data = operation_mode + self._mode().data = operation_mode def set_swing_mode(self, swing_mode): """Set new target swing mode.""" @@ -575,3 +582,37 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): if self._fan_action: data[ATTR_FAN_ACTION] = self._fan_action return data + + +class ZWaveClimateSingleSetpoint(ZWaveClimateBase): + """Representation of a single setpoint Z-Wave thermostat device.""" + + def __init__(self, values, temp_unit): + """Initialize the Z-Wave climate device.""" + ZWaveClimateBase.__init__(self, values, temp_unit) + + def _mode(self) -> None: + """Return thermostat mode Z-Wave value.""" + return self.values.mode + + def _current_mode_setpoints(self) -> Tuple: + """Return a tuple of current setpoint Z-Wave value(s).""" + return (self.values.primary,) + + +class ZWaveClimateMultipleSetpoint(ZWaveClimateBase): + """Representation of a multiple setpoint Z-Wave thermostat device.""" + + def __init__(self, values, temp_unit): + """Initialize the Z-Wave climate device.""" + ZWaveClimateBase.__init__(self, values, temp_unit) + + def _mode(self) -> None: + """Return thermostat mode Z-Wave value.""" + return self.values.primary + + def _current_mode_setpoints(self) -> Tuple: + """Return a tuple of current setpoint Z-Wave value(s).""" + current_mode = str(self.values.primary.data).lower() + setpoints_names = MODE_SETPOINT_MAPPINGS.get(current_mode, ()) + return tuple(getattr(self.values, name, None) for name in setpoints_names) diff --git a/homeassistant/components/zwave/discovery_schemas.py b/homeassistant/components/zwave/discovery_schemas.py index 2d6f08169ea..5e4b83d81e1 100644 --- a/homeassistant/components/zwave/discovery_schemas.py +++ b/homeassistant/components/zwave/discovery_schemas.py @@ -48,11 +48,60 @@ DISCOVERY_SCHEMAS = [ ), }, { - const.DISC_COMPONENT: "climate", + const.DISC_COMPONENT: "climate", # thermostat without COMMAND_CLASS_THERMOSTAT_MODE const.DISC_GENERIC_DEVICE_CLASS: [ const.GENERIC_TYPE_THERMOSTAT, const.GENERIC_TYPE_SENSOR_MULTILEVEL, ], + const.DISC_SPECIFIC_DEVICE_CLASS: [ + const.SPECIFIC_TYPE_THERMOSTAT_HEATING, + const.SPECIFIC_TYPE_SETPOINT_THERMOSTAT, + ], + const.DISC_VALUES: dict( + DEFAULT_VALUES_SCHEMA, + **{ + const.DISC_PRIMARY: { + const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_THERMOSTAT_SETPOINT] + }, + "temperature": { + const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_SENSOR_MULTILEVEL], + const.DISC_INDEX: [const.INDEX_SENSOR_MULTILEVEL_TEMPERATURE], + const.DISC_OPTIONAL: True, + }, + "fan_mode": { + const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_THERMOSTAT_FAN_MODE], + const.DISC_OPTIONAL: True, + }, + "operating_state": { + const.DISC_COMMAND_CLASS: [ + const.COMMAND_CLASS_THERMOSTAT_OPERATING_STATE + ], + const.DISC_OPTIONAL: True, + }, + "fan_action": { + const.DISC_COMMAND_CLASS: [ + const.COMMAND_CLASS_THERMOSTAT_FAN_ACTION + ], + const.DISC_OPTIONAL: True, + }, + "mode": { + const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_THERMOSTAT_MODE], + const.DISC_OPTIONAL: True, + }, + }, + ), + }, + { + const.DISC_COMPONENT: "climate", # thermostat with COMMAND_CLASS_THERMOSTAT_MODE + const.DISC_GENERIC_DEVICE_CLASS: [ + const.GENERIC_TYPE_THERMOSTAT, + const.GENERIC_TYPE_SENSOR_MULTILEVEL, + ], + const.DISC_SPECIFIC_DEVICE_CLASS: [ + const.SPECIFIC_TYPE_THERMOSTAT_GENERAL, + const.SPECIFIC_TYPE_THERMOSTAT_GENERAL_V2, + const.SPECIFIC_TYPE_SETBACK_THERMOSTAT, + ], const.DISC_VALUES: dict( DEFAULT_VALUES_SCHEMA, **{ diff --git a/tests/components/zwave/test_climate.py b/tests/components/zwave/test_climate.py index b820e496226..631bf0a0ce8 100644 --- a/tests/components/zwave/test_climate.py +++ b/tests/components/zwave/test_climate.py @@ -15,14 +15,18 @@ from homeassistant.components.climate.const import ( PRESET_BOOST, PRESET_ECO, PRESET_NONE, + SUPPORT_AUX_HEAT, SUPPORT_FAN_MODE, SUPPORT_PRESET_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_RANGE, ) -from homeassistant.components.zwave import climate -from homeassistant.components.zwave.climate import DEFAULT_HVAC_MODES +from homeassistant.components.zwave import climate, const +from homeassistant.components.zwave.climate import ( + AUX_HEAT_ZWAVE_MODE, + DEFAULT_HVAC_MODES, +) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT from tests.mock.zwave import MockEntityValues, MockNode, MockValue, value_changed @@ -34,6 +38,7 @@ def device(hass, mock_openzwave): node = MockNode() values = MockEntityValues( primary=MockValue( + command_class=const.COMMAND_CLASS_THERMOSTAT_MODE, data=HVAC_MODE_HEAT, data_items=[ HVAC_MODE_OFF, @@ -62,6 +67,7 @@ def device_zxt_120(hass, mock_openzwave): values = MockEntityValues( primary=MockValue( + command_class=const.COMMAND_CLASS_THERMOSTAT_MODE, data=HVAC_MODE_HEAT, data_items=[ HVAC_MODE_OFF, @@ -90,6 +96,7 @@ def device_mapping(hass, mock_openzwave): node = MockNode() values = MockEntityValues( primary=MockValue( + command_class=const.COMMAND_CLASS_THERMOSTAT_MODE, data="Heat", data_items=["Off", "Cool", "Heat", "Full Power", "Auto"], node=node, @@ -112,6 +119,7 @@ def device_unknown(hass, mock_openzwave): node = MockNode() values = MockEntityValues( primary=MockValue( + command_class=const.COMMAND_CLASS_THERMOSTAT_MODE, data="Heat", data_items=["Off", "Cool", "Heat", "heat_cool", "Abcdefg"], node=node, @@ -134,6 +142,7 @@ def device_heat_cool(hass, mock_openzwave): node = MockNode() values = MockEntityValues( primary=MockValue( + command_class=const.COMMAND_CLASS_THERMOSTAT_MODE, data=HVAC_MODE_HEAT, data_items=[ HVAC_MODE_OFF, @@ -162,6 +171,7 @@ def device_heat_cool_range(hass, mock_openzwave): node = MockNode() values = MockEntityValues( primary=MockValue( + command_class=const.COMMAND_CLASS_THERMOSTAT_MODE, data=HVAC_MODE_HEAT_COOL, data_items=[ HVAC_MODE_OFF, @@ -189,6 +199,7 @@ def device_heat_cool_away(hass, mock_openzwave): node = MockNode() values = MockEntityValues( primary=MockValue( + command_class=const.COMMAND_CLASS_THERMOSTAT_MODE, data=HVAC_MODE_HEAT_COOL, data_items=[ HVAC_MODE_OFF, @@ -219,6 +230,7 @@ def device_heat_eco(hass, mock_openzwave): node = MockNode() values = MockEntityValues( primary=MockValue( + command_class=const.COMMAND_CLASS_THERMOSTAT_MODE, data=HVAC_MODE_HEAT, data_items=[HVAC_MODE_OFF, HVAC_MODE_HEAT, "heat econ"], node=node, @@ -235,6 +247,100 @@ def device_heat_eco(hass, mock_openzwave): yield device +@pytest.fixture +def device_aux_heat(hass, mock_openzwave): + """Fixture to provide a precreated climate device. aux heat.""" + node = MockNode() + values = MockEntityValues( + primary=MockValue( + command_class=const.COMMAND_CLASS_THERMOSTAT_MODE, + data=HVAC_MODE_HEAT, + data_items=[HVAC_MODE_OFF, HVAC_MODE_HEAT, "Aux Heat"], + node=node, + ), + setpoint_heating=MockValue(data=2, node=node), + setpoint_eco_heating=MockValue(data=1, node=node), + temperature=MockValue(data=5, node=node, units=None), + fan_mode=MockValue(data="test2", data_items=[3, 4, 5], node=node), + operating_state=MockValue(data="test4", node=node), + fan_action=MockValue(data=7, node=node), + ) + device = climate.get_device(hass, node=node, values=values, node_config={}) + + yield device + + +@pytest.fixture +def device_single_setpoint(hass, mock_openzwave): + """Fixture to provide a precreated climate device. + + SETPOINT_THERMOSTAT device class. + """ + + node = MockNode() + values = MockEntityValues( + primary=MockValue( + command_class=const.COMMAND_CLASS_THERMOSTAT_SETPOINT, data=1, node=node + ), + mode=None, + temperature=MockValue(data=5, node=node, units=None), + fan_mode=MockValue(data="test2", data_items=[3, 4, 5], node=node), + operating_state=MockValue(data=CURRENT_HVAC_HEAT, node=node), + fan_action=MockValue(data=7, node=node), + ) + device = climate.get_device(hass, node=node, values=values, node_config={}) + + yield device + + +@pytest.fixture +def device_single_setpoint_with_mode(hass, mock_openzwave): + """Fixture to provide a precreated climate device. + + SETPOINT_THERMOSTAT device class with COMMAND_CLASS_THERMOSTAT_MODE command class + """ + + node = MockNode() + values = MockEntityValues( + primary=MockValue( + command_class=const.COMMAND_CLASS_THERMOSTAT_SETPOINT, data=1, node=node + ), + mode=MockValue( + command_class=const.COMMAND_CLASS_THERMOSTAT_MODE, + data=HVAC_MODE_HEAT, + data_items=[HVAC_MODE_OFF, HVAC_MODE_HEAT], + node=node, + ), + temperature=MockValue(data=5, node=node, units=None), + fan_mode=MockValue(data="test2", data_items=[3, 4, 5], node=node), + operating_state=MockValue(data=CURRENT_HVAC_HEAT, node=node), + fan_action=MockValue(data=7, node=node), + ) + device = climate.get_device(hass, node=node, values=values, node_config={}) + + yield device + + +def test_get_device_detects_none(hass, mock_openzwave): + """Test get_device returns None.""" + node = MockNode() + value = MockValue(data=0, node=node) + values = MockEntityValues(primary=value) + + device = climate.get_device(hass, node=node, values=values, node_config={}) + assert device is None + + +def test_get_device_detects_multiple_setpoint_device(device): + """Test get_device returns a Z-Wave multiple setpoint device.""" + assert isinstance(device, climate.ZWaveClimateMultipleSetpoint) + + +def test_get_device_detects_single_setpoint_device(device_single_setpoint): + """Test get_device returns a Z-Wave single setpoint device.""" + assert isinstance(device_single_setpoint, climate.ZWaveClimateSingleSetpoint) + + def test_default_hvac_modes(): """Test wether all hvac modes are included in default_hvac_modes.""" for hvac_mode in HVAC_MODES: @@ -274,6 +380,18 @@ def test_supported_features_preset_mode(device_mapping): ) +def test_supported_features_preset_mode_away(device_heat_cool_away): + """Test supported features flags with swing mode.""" + device = device_heat_cool_away + assert ( + device.supported_features + == SUPPORT_FAN_MODE + + SUPPORT_TARGET_TEMPERATURE + + SUPPORT_TARGET_TEMPERATURE_RANGE + + SUPPORT_PRESET_MODE + ) + + def test_supported_features_swing_mode(device_zxt_120): """Test supported features flags with swing mode.""" device = device_zxt_120 @@ -286,6 +404,27 @@ def test_supported_features_swing_mode(device_zxt_120): ) +def test_supported_features_aux_heat(device_aux_heat): + """Test supported features flags with aux heat.""" + device = device_aux_heat + assert ( + device.supported_features + == SUPPORT_FAN_MODE + SUPPORT_TARGET_TEMPERATURE + SUPPORT_AUX_HEAT + ) + + +def test_supported_features_single_setpoint(device_single_setpoint): + """Test supported features flags for SETPOINT_THERMOSTAT.""" + device = device_single_setpoint + assert device.supported_features == SUPPORT_FAN_MODE + SUPPORT_TARGET_TEMPERATURE + + +def test_supported_features_single_setpoint_with_mode(device_single_setpoint_with_mode): + """Test supported features flags for SETPOINT_THERMOSTAT.""" + device = device_single_setpoint_with_mode + assert device.supported_features == SUPPORT_FAN_MODE + SUPPORT_TARGET_TEMPERATURE + + def test_zxt_120_swing_mode(device_zxt_120): """Test operation of the zxt 120 swing mode.""" device = device_zxt_120 @@ -331,6 +470,22 @@ def test_data_lists(device): assert device.preset_modes == [] +def test_data_lists_single_setpoint(device_single_setpoint): + """Test data lists from zwave value items.""" + device = device_single_setpoint + assert device.fan_modes == [3, 4, 5] + assert device.hvac_modes == [] + assert device.preset_modes == [] + + +def test_data_lists_single_setpoint_with_mode(device_single_setpoint_with_mode): + """Test data lists from zwave value items.""" + device = device_single_setpoint_with_mode + assert device.fan_modes == [3, 4, 5] + assert device.hvac_modes == [HVAC_MODE_OFF, HVAC_MODE_HEAT] + assert device.preset_modes == [] + + def test_data_lists_mapping(device_mapping): """Test data lists from zwave value items.""" device = device_mapping @@ -404,6 +559,14 @@ def test_target_value_set_eco(device_heat_eco): assert device.values.setpoint_eco_heating.data == 0 +def test_target_value_set_single_setpoint(device_single_setpoint): + """Test values changed for climate device.""" + device = device_single_setpoint + assert device.values.primary.data == 1 + device.set_temperature(**{ATTR_TEMPERATURE: 2}) + assert device.values.primary.data == 2 + + def test_operation_value_set(device): """Test values changed for climate device.""" assert device.values.primary.data == HVAC_MODE_HEAT @@ -546,6 +709,15 @@ def test_target_changed_with_mode(device): assert device.target_temperature_high == 10 +def test_target_value_changed_single_setpoint(device_single_setpoint): + """Test values changed for climate device.""" + device = device_single_setpoint + assert device.target_temperature == 1 + device.values.primary.data = 2 + value_changed(device.values.primary) + assert device.target_temperature == 2 + + def test_temperature_value_changed(device): """Test values changed for climate device.""" assert device.current_temperature == 5 @@ -677,3 +849,44 @@ def test_fan_action_value_changed(device): device.values.fan_action.data = 9 value_changed(device.values.fan_action) assert device.device_state_attributes[climate.ATTR_FAN_ACTION] == 9 + + +def test_aux_heat_unsupported_set(device): + """Test aux heat for climate device.""" + device = device + assert device.values.primary.data == HVAC_MODE_HEAT + device.turn_aux_heat_on() + assert device.values.primary.data == HVAC_MODE_HEAT + device.turn_aux_heat_off() + assert device.values.primary.data == HVAC_MODE_HEAT + + +def test_aux_heat_unsupported_value_changed(device): + """Test aux heat for climate device.""" + device = device + assert device.is_aux_heat is None + device.values.primary.data = HVAC_MODE_HEAT + value_changed(device.values.primary) + assert device.is_aux_heat is None + + +def test_aux_heat_set(device_aux_heat): + """Test aux heat for climate device.""" + device = device_aux_heat + assert device.values.primary.data == HVAC_MODE_HEAT + device.turn_aux_heat_on() + assert device.values.primary.data == AUX_HEAT_ZWAVE_MODE + device.turn_aux_heat_off() + assert device.values.primary.data == HVAC_MODE_HEAT + + +def test_aux_heat_value_changed(device_aux_heat): + """Test aux heat for climate device.""" + device = device_aux_heat + assert device.is_aux_heat is False + device.values.primary.data = AUX_HEAT_ZWAVE_MODE + value_changed(device.values.primary) + assert device.is_aux_heat is True + device.values.primary.data = HVAC_MODE_HEAT + value_changed(device.values.primary) + assert device.is_aux_heat is False diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py index 8f717b2903c..36c91823220 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -573,7 +573,11 @@ async def test_value_discovery_existing_entity(hass, mock_openzwave): assert len(mock_receivers) == 1 - node = MockNode(node_id=11, generic=const.GENERIC_TYPE_THERMOSTAT) + node = MockNode( + node_id=11, + generic=const.GENERIC_TYPE_THERMOSTAT, + specific=const.SPECIFIC_TYPE_THERMOSTAT_GENERAL_V2, + ) thermostat_mode = MockValue( data="Heat", data_items=["Off", "Heat"], @@ -638,6 +642,42 @@ async def test_value_discovery_existing_entity(hass, mock_openzwave): ) +async def test_value_discovery_legacy_thermostat(hass, mock_openzwave): + """Test discovery of a node. Special case for legacy thermostats.""" + mock_receivers = [] + + def mock_connect(receiver, signal, *args, **kwargs): + if signal == MockNetwork.SIGNAL_VALUE_ADDED: + mock_receivers.append(receiver) + + with patch("pydispatch.dispatcher.connect", new=mock_connect): + await async_setup_component(hass, "zwave", {"zwave": {}}) + await hass.async_block_till_done() + + assert len(mock_receivers) == 1 + + node = MockNode( + node_id=11, + generic=const.GENERIC_TYPE_THERMOSTAT, + specific=const.SPECIFIC_TYPE_SETPOINT_THERMOSTAT, + ) + setpoint_heating = MockValue( + data=22.0, + node=node, + command_class=const.COMMAND_CLASS_THERMOSTAT_SETPOINT, + index=1, + genre=const.GENRE_USER, + ) + + hass.async_add_job(mock_receivers[0], node, setpoint_heating) + await hass.async_block_till_done() + + assert ( + hass.states.get("climate.mock_node_mock_value").attributes["temperature"] + == 22.0 + ) + + async def test_power_schemes(hass, mock_openzwave): """Test power attribute.""" mock_receivers = [] From 445fd15f769f966cb7df0d0ed0c4cfbe59a7852e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Mon, 16 Dec 2019 08:29:19 +0200 Subject: [PATCH 308/677] Drop Python 3.6 support (#29978) --- .coveragerc | 1 - .readthedocs.yml | 2 +- .travis.yml | 10 ++-- azure-pipelines-ci.yml | 6 +-- homeassistant/__main__.py | 14 ++--- homeassistant/const.py | 6 +-- homeassistant/monkey_patch.py | 73 --------------------------- homeassistant/package_constraints.txt | 3 +- homeassistant/util/async_.py | 21 +------- pyproject.toml | 2 +- requirements_all.txt | 1 - script/gen_requirements_all.py | 2 +- setup.cfg | 5 +- setup.py | 1 - tests/util/test_async.py | 7 +-- 15 files changed, 19 insertions(+), 135 deletions(-) delete mode 100644 homeassistant/monkey_patch.py diff --git a/.coveragerc b/.coveragerc index c6e7182d326..e16a622cc61 100644 --- a/.coveragerc +++ b/.coveragerc @@ -5,7 +5,6 @@ omit = homeassistant/__main__.py homeassistant/helpers/signal.py homeassistant/helpers/typing.py - homeassistant/monkey_patch.py homeassistant/scripts/*.py homeassistant/util/async.py diff --git a/.readthedocs.yml b/.readthedocs.yml index 923a03f03dd..0303f84d51c 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -4,7 +4,7 @@ build: image: latest python: - version: 3.6 + version: 3.7 setup_py_install: true requirements_file: requirements_docs.txt diff --git a/.travis.yml b/.travis.yml index 2660d805726..6add8c15bfc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,15 +14,13 @@ addons: matrix: fast_finish: true include: - - python: "3.6.1" + - python: "3.7.0" env: TOXENV=lint - - python: "3.6.1" + - python: "3.7.0" env: TOXENV=pylint PYLINT_ARGS=--jobs=0 TRAVIS_WAIT=30 - - python: "3.6.1" + - python: "3.7.0" env: TOXENV=typing - - python: "3.6.1" - env: TOXENV=py36 - - python: "3.7" + - python: "3.7.0" env: TOXENV=py37 cache: diff --git a/azure-pipelines-ci.yml b/azure-pipelines-ci.yml index 5a289cbbc70..464b1079957 100644 --- a/azure-pipelines-ci.yml +++ b/azure-pipelines-ci.yml @@ -14,8 +14,6 @@ pr: resources: containers: - - container: 36 - image: homeassistant/ci-azure:3.6 - container: 37 image: homeassistant/ci-azure:3.7 repositories: @@ -25,7 +23,7 @@ resources: endpoint: 'home-assistant' variables: - name: PythonMain - value: '36' + value: '37' - group: codecov stages: @@ -108,8 +106,6 @@ stages: strategy: maxParallel: 3 matrix: - Python36: - python.container: '36' Python37: python.container: '37' container: $[ variables['python.container'] ] diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index bf3042c3f88..bcc97252255 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -1,5 +1,6 @@ """Start Home Assistant.""" import argparse +import asyncio import os import platform import subprocess @@ -7,7 +8,6 @@ import sys import threading from typing import TYPE_CHECKING, Any, Dict, List -from homeassistant import monkey_patch from homeassistant.const import REQUIRED_PYTHON_VER, RESTART_EXIT_CODE, __version__ if TYPE_CHECKING: @@ -16,7 +16,6 @@ if TYPE_CHECKING: def set_loop() -> None: """Attempt to use different loop.""" - import asyncio from asyncio.events import BaseDefaultEventLoopPolicy if sys.platform == "win32": @@ -345,11 +344,6 @@ def main() -> int: """Start Home Assistant.""" validate_python() - monkey_patch_needed = sys.version_info[:3] < (3, 6, 3) - if monkey_patch_needed and os.environ.get("HASS_NO_MONKEY") != "1": - monkey_patch.disable_c_asyncio() - monkey_patch.patch_weakref_tasks() - set_loop() # Run a simple daemon runner process on Windows to handle restarts @@ -383,13 +377,11 @@ def main() -> int: if args.pid_file: write_pid(args.pid_file) - from homeassistant.util.async_ import asyncio_run - - exit_code = asyncio_run(setup_and_run_hass(config_dir, args)) + exit_code = asyncio.run(setup_and_run_hass(config_dir, args)) if exit_code == RESTART_EXIT_CODE and not args.runner: try_to_restart() - return exit_code # type: ignore + return exit_code if __name__ == "__main__": diff --git a/homeassistant/const.py b/homeassistant/const.py index ba7e7a66126..15dc5a099bc 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -4,10 +4,10 @@ MINOR_VERSION = 104 PATCH_VERSION = "0.dev0" __short_version__ = "{}.{}".format(MAJOR_VERSION, MINOR_VERSION) __version__ = "{}.{}".format(__short_version__, PATCH_VERSION) -REQUIRED_PYTHON_VER = (3, 6, 1) +REQUIRED_PYTHON_VER = (3, 7, 0) # Truthy date string triggers showing related deprecation warning messages. -REQUIRED_NEXT_PYTHON_VER = (3, 7, 0) -REQUIRED_NEXT_PYTHON_DATE = "December 15, 2019" +REQUIRED_NEXT_PYTHON_VER = (3, 8, 0) +REQUIRED_NEXT_PYTHON_DATE = "" # Format for platform files PLATFORM_FORMAT = "{platform}.{domain}" diff --git a/homeassistant/monkey_patch.py b/homeassistant/monkey_patch.py deleted file mode 100644 index c6e2e66ab13..00000000000 --- a/homeassistant/monkey_patch.py +++ /dev/null @@ -1,73 +0,0 @@ -"""Monkey patch Python to work around issues causing segfaults. - -Under heavy threading operations that schedule calls into -the asyncio event loop, Task objects are created. Due to -a bug in Python, GC may have an issue when switching between -the threads and objects with __del__ (which various components -in HASS have). - -This monkey-patch removes the weakref.Weakset, and replaces it -with an object that ignores the only call utilizing it (the -Task.__init__ which calls _all_tasks.add(self)). It also removes -the __del__ which could trigger the future objects __del__ at -unpredictable times. - -The side-effect of this manipulation of the Task is that -Task.all_tasks() is no longer accurate, and there will be no -warning emitted if a Task is GC'd while in use. - -Related Python bugs: - - https://bugs.python.org/issue26617 -""" -import sys -from typing import Any - - -def patch_weakref_tasks() -> None: - """Replace weakref.WeakSet to address Python 3 bug.""" - # pylint: disable=no-self-use, protected-access - import asyncio.tasks - - class IgnoreCalls: - """Ignore add calls.""" - - def add(self, other: Any) -> None: - """No-op add.""" - return - - asyncio.tasks.Task._all_tasks = IgnoreCalls() # type: ignore - try: - del asyncio.tasks.Task.__del__ - except: # noqa: E722 pylint: disable=bare-except - pass - - -def disable_c_asyncio() -> None: - """Disable using C implementation of asyncio. - - Required to be able to apply the weakref monkey patch. - - Requires Python 3.6+. - """ - - class AsyncioImportFinder: - """Finder that blocks C version of asyncio being loaded.""" - - PATH_TRIGGER = "_asyncio" - - def __init__(self, path_entry: str) -> None: - if path_entry != self.PATH_TRIGGER: - raise ImportError() - - def find_module(self, fullname: str, path: Any = None) -> None: - """Find a module.""" - if fullname == self.PATH_TRIGGER: - raise ModuleNotFoundError() - - sys.path_hooks.append(AsyncioImportFinder) - sys.path.insert(0, AsyncioImportFinder.PATH_TRIGGER) - - try: - import _asyncio # noqa: F401 - except ImportError: - pass diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index afc92f92ef7..c0b7933fcca 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -7,7 +7,6 @@ async_timeout==3.0.1 attrs==19.3.0 bcrypt==3.1.7 certifi>=2019.11.28 -contextvars==2.4;python_version<"3.7" cryptography==2.8 defusedxml==0.6.0 distro==1.4.0 @@ -29,7 +28,7 @@ zeroconf==0.24.0 pycryptodome>=3.6.6 -# Breaks Python 3.6 and is not needed for our supported Python versions +# Not needed for our supported Python versions enum34==1000000000.0.0 # This is a old unmaintained library and is replaced with pycryptodome diff --git a/homeassistant/util/async_.py b/homeassistant/util/async_.py index b1d3a7dd8e7..212c2bff910 100644 --- a/homeassistant/util/async_.py +++ b/homeassistant/util/async_.py @@ -1,33 +1,14 @@ """Asyncio backports for Python 3.6 compatibility.""" -import asyncio from asyncio import coroutines, ensure_future from asyncio.events import AbstractEventLoop import concurrent.futures import logging import threading -from typing import Any, Awaitable, Callable, Coroutine, TypeVar +from typing import Any, Callable, Coroutine _LOGGER = logging.getLogger(__name__) -try: - # pylint: disable=invalid-name - asyncio_run = asyncio.run # type: ignore -except AttributeError: - _T = TypeVar("_T") - - def asyncio_run(main: Awaitable[_T], *, debug: bool = False) -> _T: - """Minimal re-implementation of asyncio.run (since 3.7).""" - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - loop.set_debug(debug) - try: - return loop.run_until_complete(main) - finally: - asyncio.set_event_loop(None) - loop.close() - - def fire_coroutine_threadsafe(coro: Coroutine, loop: AbstractEventLoop) -> None: """Submit a coroutine object to a given event loop. diff --git a/pyproject.toml b/pyproject.toml index 7a75060c8e9..7c0c5eeb433 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,3 @@ [tool.black] -target-version = ["py36", "py37", "py38"] +target-version = ["py37", "py38"] exclude = 'generated' diff --git a/requirements_all.txt b/requirements_all.txt index 33cc5c725a8..f0b547b4a25 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -5,7 +5,6 @@ async_timeout==3.0.1 attrs==19.3.0 bcrypt==3.1.7 certifi>=2019.11.28 -contextvars==2.4;python_version<"3.7" importlib-metadata==0.23 jinja2>=2.10.3 PyJWT==1.7.1 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index c18b51ba5d1..0dfefc958c3 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -58,7 +58,7 @@ CONSTRAINT_PATH = os.path.join( CONSTRAINT_BASE = """ pycryptodome>=3.6.6 -# Breaks Python 3.6 and is not needed for our supported Python versions +# Not needed for our supported Python versions enum34==1000000000.0.0 # This is a old unmaintained library and is replaced with pycryptodome diff --git a/setup.cfg b/setup.cfg index bb2b1652ffa..f9e9852812c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -11,7 +11,6 @@ classifier = Intended Audience :: Developers License :: OSI Approved :: Apache Software License Operating System :: OS Independent - Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Topic :: Home Automation @@ -57,7 +56,7 @@ forced_separate = tests combine_as_imports = true [mypy] -python_version = 3.6 +python_version = 3.7 ignore_errors = true follow_imports = silent ignore_missing_imports = true @@ -65,7 +64,7 @@ warn_incomplete_stub = true warn_redundant_casts = true warn_unused_configs = true -[mypy-homeassistant.bootstrap,homeassistant.components,homeassistant.config_entries,homeassistant.config,homeassistant.const,homeassistant.core,homeassistant.data_entry_flow,homeassistant.exceptions,homeassistant.loader,homeassistant.__main__,homeassistant.monkey_patch,homeassistant.requirements,homeassistant.setup,homeassistant.util,homeassistant.auth.*,homeassistant.components.automation.*,homeassistant.components.binary_sensor.*,homeassistant.components.calendar.*,homeassistant.components.cover.*,homeassistant.components.device_automation.*,homeassistant.components.frontend.*,homeassistant.components.geo_location.*,homeassistant.components.group.*,homeassistant.components.history.*,homeassistant.components.http.*,homeassistant.components.image_processing.*,homeassistant.components.integration.*,homeassistant.components.light.*,homeassistant.components.lock.*,homeassistant.components.mailbox.*,homeassistant.components.media_player.*,homeassistant.components.notify.*,homeassistant.components.persistent_notification.*,homeassistant.components.proximity.*,homeassistant.components.remote.*,homeassistant.components.scene.*,homeassistant.components.sensor.*,homeassistant.components.sun.*,homeassistant.components.switch.*,homeassistant.components.systemmonitor.*,homeassistant.components.tts.*,homeassistant.components.vacuum.*,homeassistant.components.water_heater.*,homeassistant.components.weather.*,homeassistant.components.websocket_api.*,homeassistant.components.zone.*,homeassistant.helpers.*,homeassistant.scripts.*,homeassistant.util.*] +[mypy-homeassistant.bootstrap,homeassistant.components,homeassistant.config_entries,homeassistant.config,homeassistant.const,homeassistant.core,homeassistant.data_entry_flow,homeassistant.exceptions,homeassistant.loader,homeassistant.__main__,homeassistant.requirements,homeassistant.setup,homeassistant.util,homeassistant.auth.*,homeassistant.components.automation.*,homeassistant.components.binary_sensor.*,homeassistant.components.calendar.*,homeassistant.components.cover.*,homeassistant.components.device_automation.*,homeassistant.components.frontend.*,homeassistant.components.geo_location.*,homeassistant.components.group.*,homeassistant.components.history.*,homeassistant.components.http.*,homeassistant.components.image_processing.*,homeassistant.components.integration.*,homeassistant.components.light.*,homeassistant.components.lock.*,homeassistant.components.mailbox.*,homeassistant.components.media_player.*,homeassistant.components.notify.*,homeassistant.components.persistent_notification.*,homeassistant.components.proximity.*,homeassistant.components.remote.*,homeassistant.components.scene.*,homeassistant.components.sensor.*,homeassistant.components.sun.*,homeassistant.components.switch.*,homeassistant.components.systemmonitor.*,homeassistant.components.tts.*,homeassistant.components.vacuum.*,homeassistant.components.water_heater.*,homeassistant.components.weather.*,homeassistant.components.websocket_api.*,homeassistant.components.zone.*,homeassistant.helpers.*,homeassistant.scripts.*,homeassistant.util.*] ignore_errors = false check_untyped_defs = true disallow_incomplete_defs = true diff --git a/setup.py b/setup.py index f9cb24d1e91..720406ab91b 100755 --- a/setup.py +++ b/setup.py @@ -38,7 +38,6 @@ REQUIRES = [ "attrs==19.3.0", "bcrypt==3.1.7", "certifi>=2019.11.28", - 'contextvars==2.4;python_version<"3.7"', "importlib-metadata==0.23", "jinja2>=2.10.3", "PyJWT==1.7.1", diff --git a/tests/util/test_async.py b/tests/util/test_async.py index ec16fa2ae67..098b04a3048 100644 --- a/tests/util/test_async.py +++ b/tests/util/test_async.py @@ -1,6 +1,5 @@ """Tests for async util methods from Python source.""" import asyncio -import sys from unittest import TestCase from unittest.mock import MagicMock, patch @@ -112,11 +111,7 @@ class RunThreadsafeTests(TestCase): """Wait 0.05 second and return a + b.""" yield from asyncio.sleep(0.05, loop=self.loop) if cancel: - if sys.version_info[:2] >= (3, 7): - current_task = asyncio.current_task - else: - current_task = asyncio.tasks.Task.current_task - current_task(self.loop).cancel() + asyncio.current_task(self.loop).cancel() yield return self.add_callback(a, b, fail, invalid) From 039cc98278612cbfc08d2c242ba6182a0f399a4d Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Mon, 16 Dec 2019 08:04:59 +0100 Subject: [PATCH 309/677] Support case of unknown/unavailable temperature/humidity (#29959) * Support case of unknown/unavailable temperature/humidity State is never None, just a string. * Lint suggestion --- .../components/google_assistant/trait.py | 5 +-- .../components/google_assistant/test_trait.py | 36 +++++++++++++++---- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py index 40def0cb464..d49632755cd 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -44,6 +44,7 @@ from homeassistant.const import ( STATE_LOCKED, STATE_OFF, STATE_ON, + STATE_UNAVAILABLE, STATE_UNKNOWN, TEMP_CELSIUS, TEMP_FAHRENHEIT, @@ -666,7 +667,7 @@ class TemperatureSettingTrait(_Trait): device_class = attrs.get(ATTR_DEVICE_CLASS) if device_class == sensor.DEVICE_CLASS_TEMPERATURE: current_temp = self.state.state - if current_temp is not None: + if current_temp not in (STATE_UNKNOWN, STATE_UNAVAILABLE): response["thermostatTemperatureAmbient"] = round( temp_util.convert(float(current_temp), unit, TEMP_CELSIUS), 1 ) @@ -887,7 +888,7 @@ class HumiditySettingTrait(_Trait): device_class = attrs.get(ATTR_DEVICE_CLASS) if device_class == sensor.DEVICE_CLASS_HUMIDITY: current_humidity = self.state.state - if current_humidity is not None: + if current_humidity not in (STATE_UNKNOWN, STATE_UNAVAILABLE): response["humidityAmbientPercent"] = round(float(current_humidity)) return response diff --git a/tests/components/google_assistant/test_trait.py b/tests/components/google_assistant/test_trait.py index 8a1d7384729..98e5149de1d 100644 --- a/tests/components/google_assistant/test_trait.py +++ b/tests/components/google_assistant/test_trait.py @@ -1615,22 +1615,37 @@ async def test_temperature_setting_sensor(hass): sensor.DOMAIN, 0, sensor.DEVICE_CLASS_TEMPERATURE ) - hass.config.units.temperature_unit = TEMP_FAHRENHEIT + +@pytest.mark.parametrize( + "unit_in,unit_out,state,ambient", + [ + (TEMP_FAHRENHEIT, "F", "70", 21.1), + (TEMP_CELSIUS, "C", "21.1", 21.1), + (TEMP_FAHRENHEIT, "F", "unavailable", None), + (TEMP_FAHRENHEIT, "F", "unknown", None), + ], +) +async def test_temperature_setting_sensor_data(hass, unit_in, unit_out, state, ambient): + """Test TemperatureSetting trait support for temperature sensor.""" + hass.config.units.temperature_unit = unit_in trt = trait.TemperatureSettingTrait( hass, State( - "sensor.test", "70", {ATTR_DEVICE_CLASS: sensor.DEVICE_CLASS_TEMPERATURE} + "sensor.test", state, {ATTR_DEVICE_CLASS: sensor.DEVICE_CLASS_TEMPERATURE} ), BASIC_CONFIG, ) assert trt.sync_attributes() == { "queryOnlyTemperatureSetting": True, - "thermostatTemperatureUnit": "F", + "thermostatTemperatureUnit": unit_out, } - assert trt.query_attributes() == {"thermostatTemperatureAmbient": 21.1} + if ambient: + assert trt.query_attributes() == {"thermostatTemperatureAmbient": ambient} + else: + assert trt.query_attributes() == {} hass.config.units.temperature_unit = TEMP_CELSIUS @@ -1646,14 +1661,23 @@ async def test_humidity_setting_sensor(hass): sensor.DOMAIN, 0, sensor.DEVICE_CLASS_HUMIDITY ) + +@pytest.mark.parametrize( + "state,ambient", [("70", 70), ("unavailable", None), ("unknown", None)] +) +async def test_humidity_setting_sensor_data(hass, state, ambient): + """Test HumiditySetting trait support for humidity sensor.""" trt = trait.HumiditySettingTrait( hass, - State("sensor.test", "70", {ATTR_DEVICE_CLASS: sensor.DEVICE_CLASS_HUMIDITY}), + State("sensor.test", state, {ATTR_DEVICE_CLASS: sensor.DEVICE_CLASS_HUMIDITY}), BASIC_CONFIG, ) assert trt.sync_attributes() == {"queryOnlyHumiditySetting": True} - assert trt.query_attributes() == {"humidityAmbientPercent": 70} + if ambient: + assert trt.query_attributes() == {"humidityAmbientPercent": ambient} + else: + assert trt.query_attributes() == {} with pytest.raises(helpers.SmartHomeError) as err: await trt.execute(trait.COMMAND_ONOFF, BASIC_DATA, {"on": False}, {}) From fc01da8933dcc87a7dcb54df1e9b2cb6138d0287 Mon Sep 17 00:00:00 2001 From: Kevin Eifinger Date: Mon, 16 Dec 2019 08:20:41 +0100 Subject: [PATCH 310/677] Migrate to api_key (#29966) --- .../components/here_travel_time/manifest.json | 2 +- .../components/here_travel_time/sensor.py | 11 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../here_travel_time/test_sensor.py | 113 +++++++----------- 5 files changed, 48 insertions(+), 82 deletions(-) diff --git a/homeassistant/components/here_travel_time/manifest.json b/homeassistant/components/here_travel_time/manifest.json index 5ef71a249e6..da2c03b1ac8 100644 --- a/homeassistant/components/here_travel_time/manifest.json +++ b/homeassistant/components/here_travel_time/manifest.json @@ -3,7 +3,7 @@ "name": "HERE travel time", "documentation": "https://www.home-assistant.io/integrations/here_travel_time", "requirements": [ - "herepy==0.6.3.3" + "herepy==2.0.0" ], "dependencies": [], "codeowners": [ diff --git a/homeassistant/components/here_travel_time/sensor.py b/homeassistant/components/here_travel_time/sensor.py index 0b688a770c5..e482943eff3 100644 --- a/homeassistant/components/here_travel_time/sensor.py +++ b/homeassistant/components/here_travel_time/sensor.py @@ -32,8 +32,7 @@ CONF_DESTINATION_ENTITY_ID = "destination_entity_id" CONF_ORIGIN_LATITUDE = "origin_latitude" CONF_ORIGIN_LONGITUDE = "origin_longitude" CONF_ORIGIN_ENTITY_ID = "origin_entity_id" -CONF_APP_ID = "app_id" -CONF_APP_CODE = "app_code" +CONF_API_KEY = "api_key" CONF_TRAFFIC_MODE = "traffic_mode" CONF_ROUTE_MODE = "route_mode" @@ -97,8 +96,7 @@ PLATFORM_SCHEMA = vol.All( cv.has_at_least_one_key(CONF_ORIGIN_LATITUDE, CONF_ORIGIN_ENTITY_ID), PLATFORM_SCHEMA.extend( { - vol.Required(CONF_APP_ID): cv.string, - vol.Required(CONF_APP_CODE): cv.string, + vol.Required(CONF_API_KEY): cv.string, vol.Inclusive( CONF_DESTINATION_LATITUDE, "destination_coordinates" ): cv.latitude, @@ -131,9 +129,8 @@ async def async_setup_platform( ) -> None: """Set up the HERE travel time platform.""" - app_id = config[CONF_APP_ID] - app_code = config[CONF_APP_CODE] - here_client = herepy.RoutingApi(app_id, app_code) + api_key = config[CONF_API_KEY] + here_client = herepy.RoutingApi(api_key) if not await hass.async_add_executor_job( _are_valid_client_credentials, here_client diff --git a/requirements_all.txt b/requirements_all.txt index f0b547b4a25..873e056eb72 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -648,7 +648,7 @@ hdate==0.9.3 heatmiserV3==1.1.18 # homeassistant.components.here_travel_time -herepy==0.6.3.3 +herepy==2.0.0 # homeassistant.components.hikvisioncam hikvision==0.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9e80c2585e6..074d5bd6c8e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -222,7 +222,7 @@ hbmqtt==0.9.5 hdate==0.9.3 # homeassistant.components.here_travel_time -herepy==0.6.3.3 +herepy==2.0.0 # homeassistant.components.pi_hole hole==0.5.0 diff --git a/tests/components/here_travel_time/test_sensor.py b/tests/components/here_travel_time/test_sensor.py index 296efbdad53..6b9c52b1042 100644 --- a/tests/components/here_travel_time/test_sensor.py +++ b/tests/components/here_travel_time/test_sensor.py @@ -48,8 +48,7 @@ DOMAIN = "sensor" PLATFORM = "here_travel_time" -APP_ID = "test" -APP_CODE = "test" +API_KEY = "test" TRUCK_ORIGIN_LATITUDE = "41.9798" TRUCK_ORIGIN_LONGITUDE = "-87.8801" @@ -67,15 +66,14 @@ CAR_DESTINATION_LATITUDE = "39.0" CAR_DESTINATION_LONGITUDE = "-77.1" -def _build_mock_url(origin, destination, modes, app_id, app_code, departure): +def _build_mock_url(origin, destination, modes, api_key, departure): """Construct a url for HERE.""" - base_url = "https://route.cit.api.here.com/routing/7.2/calculateroute.json?" + base_url = "https://route.ls.hereapi.com/routing/7.2/calculateroute.json?" parameters = { "waypoint0": f"geo!{origin}", "waypoint1": f"geo!{destination}", "mode": ";".join(str(herepy.RouteMode[mode]) for mode in modes), - "app_id": app_id, - "app_code": app_code, + "apikey": api_key, "departure": departure, } url = base_url + urllib.parse.urlencode(parameters) @@ -118,8 +116,7 @@ def requests_mock_credentials_check(requests_mock): ",".join([CAR_ORIGIN_LATITUDE, CAR_ORIGIN_LONGITUDE]), ",".join([CAR_DESTINATION_LATITUDE, CAR_DESTINATION_LONGITUDE]), modes, - APP_ID, - APP_CODE, + API_KEY, "now", ) requests_mock.get( @@ -136,8 +133,7 @@ def requests_mock_truck_response(requests_mock_credentials_check): ",".join([TRUCK_ORIGIN_LATITUDE, TRUCK_ORIGIN_LONGITUDE]), ",".join([TRUCK_DESTINATION_LATITUDE, TRUCK_DESTINATION_LONGITUDE]), modes, - APP_ID, - APP_CODE, + API_KEY, "now", ) requests_mock_credentials_check.get( @@ -153,8 +149,7 @@ def requests_mock_car_disabled_response(requests_mock_credentials_check): ",".join([CAR_ORIGIN_LATITUDE, CAR_ORIGIN_LONGITUDE]), ",".join([CAR_DESTINATION_LATITUDE, CAR_DESTINATION_LONGITUDE]), modes, - APP_ID, - APP_CODE, + API_KEY, "now", ) requests_mock_credentials_check.get( @@ -172,8 +167,7 @@ async def test_car(hass, requests_mock_car_disabled_response): "origin_longitude": CAR_ORIGIN_LONGITUDE, "destination_latitude": CAR_DESTINATION_LATITUDE, "destination_longitude": CAR_DESTINATION_LONGITUDE, - "app_id": APP_ID, - "app_code": APP_CODE, + "api_key": API_KEY, } } assert await async_setup_component(hass, DOMAIN, config) @@ -219,8 +213,7 @@ async def test_traffic_mode_enabled(hass, requests_mock_credentials_check): ",".join([CAR_ORIGIN_LATITUDE, CAR_ORIGIN_LONGITUDE]), ",".join([CAR_DESTINATION_LATITUDE, CAR_DESTINATION_LONGITUDE]), modes, - APP_ID, - APP_CODE, + API_KEY, "now", ) requests_mock_credentials_check.get( @@ -235,8 +228,7 @@ async def test_traffic_mode_enabled(hass, requests_mock_credentials_check): "origin_longitude": CAR_ORIGIN_LONGITUDE, "destination_latitude": CAR_DESTINATION_LATITUDE, "destination_longitude": CAR_DESTINATION_LONGITUDE, - "app_id": APP_ID, - "app_code": APP_CODE, + "api_key": API_KEY, "traffic_mode": True, } } @@ -262,8 +254,7 @@ async def test_imperial(hass, requests_mock_car_disabled_response): "origin_longitude": CAR_ORIGIN_LONGITUDE, "destination_latitude": CAR_DESTINATION_LATITUDE, "destination_longitude": CAR_DESTINATION_LONGITUDE, - "app_id": APP_ID, - "app_code": APP_CODE, + "api_key": API_KEY, "unit_system": "imperial", } } @@ -281,7 +272,7 @@ async def test_route_mode_shortest(hass, requests_mock_credentials_check): origin = "38.902981,-77.048338" destination = "39.042158,-77.119116" modes = [ROUTE_MODE_SHORTEST, TRAVEL_MODE_CAR, TRAFFIC_MODE_DISABLED] - response_url = _build_mock_url(origin, destination, modes, APP_ID, APP_CODE, "now") + response_url = _build_mock_url(origin, destination, modes, API_KEY, "now") requests_mock_credentials_check.get( response_url, text=load_fixture("here_travel_time/car_shortest_response.json") ) @@ -294,8 +285,7 @@ async def test_route_mode_shortest(hass, requests_mock_credentials_check): "origin_longitude": origin.split(",")[1], "destination_latitude": destination.split(",")[0], "destination_longitude": destination.split(",")[1], - "app_id": APP_ID, - "app_code": APP_CODE, + "api_key": API_KEY, "route_mode": ROUTE_MODE_SHORTEST, } } @@ -313,7 +303,7 @@ async def test_route_mode_fastest(hass, requests_mock_credentials_check): origin = "38.902981,-77.048338" destination = "39.042158,-77.119116" modes = [ROUTE_MODE_FASTEST, TRAVEL_MODE_CAR, TRAFFIC_MODE_ENABLED] - response_url = _build_mock_url(origin, destination, modes, APP_ID, APP_CODE, "now") + response_url = _build_mock_url(origin, destination, modes, API_KEY, "now") requests_mock_credentials_check.get( response_url, text=load_fixture("here_travel_time/car_enabled_response.json") ) @@ -326,8 +316,7 @@ async def test_route_mode_fastest(hass, requests_mock_credentials_check): "origin_longitude": origin.split(",")[1], "destination_latitude": destination.split(",")[0], "destination_longitude": destination.split(",")[1], - "app_id": APP_ID, - "app_code": APP_CODE, + "api_key": API_KEY, "traffic_mode": True, } } @@ -350,8 +339,7 @@ async def test_truck(hass, requests_mock_truck_response): "origin_longitude": TRUCK_ORIGIN_LONGITUDE, "destination_latitude": TRUCK_DESTINATION_LATITUDE, "destination_longitude": TRUCK_DESTINATION_LONGITUDE, - "app_id": APP_ID, - "app_code": APP_CODE, + "api_key": API_KEY, "mode": TRAVEL_MODE_TRUCK, } } @@ -369,7 +357,7 @@ async def test_public_transport(hass, requests_mock_credentials_check): origin = "41.9798,-87.8801" destination = "41.9043,-87.9216" modes = [ROUTE_MODE_FASTEST, TRAVEL_MODE_PUBLIC, TRAFFIC_MODE_DISABLED] - response_url = _build_mock_url(origin, destination, modes, APP_ID, APP_CODE, "now") + response_url = _build_mock_url(origin, destination, modes, API_KEY, "now") requests_mock_credentials_check.get( response_url, text=load_fixture("here_travel_time/public_response.json") ) @@ -382,8 +370,7 @@ async def test_public_transport(hass, requests_mock_credentials_check): "origin_longitude": origin.split(",")[1], "destination_latitude": destination.split(",")[0], "destination_longitude": destination.split(",")[1], - "app_id": APP_ID, - "app_code": APP_CODE, + "api_key": API_KEY, "mode": TRAVEL_MODE_PUBLIC, } } @@ -419,7 +406,7 @@ async def test_public_transport_time_table(hass, requests_mock_credentials_check origin = "41.9798,-87.8801" destination = "41.9043,-87.9216" modes = [ROUTE_MODE_FASTEST, TRAVEL_MODE_PUBLIC_TIME_TABLE, TRAFFIC_MODE_DISABLED] - response_url = _build_mock_url(origin, destination, modes, APP_ID, APP_CODE, "now") + response_url = _build_mock_url(origin, destination, modes, API_KEY, "now") requests_mock_credentials_check.get( response_url, text=load_fixture("here_travel_time/public_time_table_response.json"), @@ -433,8 +420,7 @@ async def test_public_transport_time_table(hass, requests_mock_credentials_check "origin_longitude": origin.split(",")[1], "destination_latitude": destination.split(",")[0], "destination_longitude": destination.split(",")[1], - "app_id": APP_ID, - "app_code": APP_CODE, + "api_key": API_KEY, "mode": TRAVEL_MODE_PUBLIC_TIME_TABLE, } } @@ -470,7 +456,7 @@ async def test_pedestrian(hass, requests_mock_credentials_check): origin = "41.9798,-87.8801" destination = "41.9043,-87.9216" modes = [ROUTE_MODE_FASTEST, TRAVEL_MODE_PEDESTRIAN, TRAFFIC_MODE_DISABLED] - response_url = _build_mock_url(origin, destination, modes, APP_ID, APP_CODE, "now") + response_url = _build_mock_url(origin, destination, modes, API_KEY, "now") requests_mock_credentials_check.get( response_url, text=load_fixture("here_travel_time/pedestrian_response.json") ) @@ -483,8 +469,7 @@ async def test_pedestrian(hass, requests_mock_credentials_check): "origin_longitude": origin.split(",")[1], "destination_latitude": destination.split(",")[0], "destination_longitude": destination.split(",")[1], - "app_id": APP_ID, - "app_code": APP_CODE, + "api_key": API_KEY, "mode": TRAVEL_MODE_PEDESTRIAN, } } @@ -523,7 +508,7 @@ async def test_bicycle(hass, requests_mock_credentials_check): origin = "41.9798,-87.8801" destination = "41.9043,-87.9216" modes = [ROUTE_MODE_FASTEST, TRAVEL_MODE_BICYCLE, TRAFFIC_MODE_DISABLED] - response_url = _build_mock_url(origin, destination, modes, APP_ID, APP_CODE, "now") + response_url = _build_mock_url(origin, destination, modes, API_KEY, "now") requests_mock_credentials_check.get( response_url, text=load_fixture("here_travel_time/bike_response.json") ) @@ -536,8 +521,7 @@ async def test_bicycle(hass, requests_mock_credentials_check): "origin_longitude": origin.split(",")[1], "destination_latitude": destination.split(",")[0], "destination_longitude": destination.split(",")[1], - "app_id": APP_ID, - "app_code": APP_CODE, + "api_key": API_KEY, "mode": TRAVEL_MODE_BICYCLE, } } @@ -599,8 +583,7 @@ async def test_location_zone(hass, requests_mock_truck_response): "name": "test", "origin_entity_id": "zone.origin", "destination_entity_id": "zone.destination", - "app_id": APP_ID, - "app_code": APP_CODE, + "api_key": API_KEY, "mode": TRAVEL_MODE_TRUCK, } } @@ -640,8 +623,7 @@ async def test_location_sensor(hass, requests_mock_truck_response): "name": "test", "origin_entity_id": "sensor.origin", "destination_entity_id": "sensor.destination", - "app_id": APP_ID, - "app_code": APP_CODE, + "api_key": API_KEY, "mode": TRAVEL_MODE_TRUCK, } } @@ -689,8 +671,7 @@ async def test_location_person(hass, requests_mock_truck_response): "name": "test", "origin_entity_id": "person.origin", "destination_entity_id": "person.destination", - "app_id": APP_ID, - "app_code": APP_CODE, + "api_key": API_KEY, "mode": TRAVEL_MODE_TRUCK, } } @@ -738,8 +719,7 @@ async def test_location_device_tracker(hass, requests_mock_truck_response): "name": "test", "origin_entity_id": "device_tracker.origin", "destination_entity_id": "device_tracker.destination", - "app_id": APP_ID, - "app_code": APP_CODE, + "api_key": API_KEY, "mode": TRAVEL_MODE_TRUCK, } } @@ -773,8 +753,7 @@ async def test_location_device_tracker_added_after_update( "name": "test", "origin_entity_id": "device_tracker.origin", "destination_entity_id": "device_tracker.destination", - "app_id": APP_ID, - "app_code": APP_CODE, + "api_key": API_KEY, "mode": TRAVEL_MODE_TRUCK, } } @@ -842,8 +821,7 @@ async def test_location_device_tracker_in_zone( "origin_entity_id": "device_tracker.origin", "destination_latitude": TRUCK_DESTINATION_LATITUDE, "destination_longitude": TRUCK_DESTINATION_LONGITUDE, - "app_id": APP_ID, - "app_code": APP_CODE, + "api_key": API_KEY, "mode": TRAVEL_MODE_TRUCK, } } @@ -863,7 +841,7 @@ async def test_route_not_found(hass, requests_mock_credentials_check, caplog): origin = "52.516,13.3779" destination = "47.013399,-10.171986" modes = [ROUTE_MODE_FASTEST, TRAVEL_MODE_CAR, TRAFFIC_MODE_DISABLED] - response_url = _build_mock_url(origin, destination, modes, APP_ID, APP_CODE, "now") + response_url = _build_mock_url(origin, destination, modes, API_KEY, "now") requests_mock_credentials_check.get( response_url, text=load_fixture("here_travel_time/routing_error_no_route_found.json"), @@ -877,8 +855,7 @@ async def test_route_not_found(hass, requests_mock_credentials_check, caplog): "origin_longitude": origin.split(",")[1], "destination_latitude": destination.split(",")[0], "destination_longitude": destination.split(",")[1], - "app_id": APP_ID, - "app_code": APP_CODE, + "api_key": API_KEY, } } assert await async_setup_component(hass, DOMAIN, config) @@ -901,8 +878,7 @@ async def test_pattern_origin(hass, caplog): "origin_longitude": "-77.04833", "destination_latitude": CAR_DESTINATION_LATITUDE, "destination_longitude": CAR_DESTINATION_LONGITUDE, - "app_id": APP_ID, - "app_code": APP_CODE, + "api_key": API_KEY, } } assert await async_setup_component(hass, DOMAIN, config) @@ -921,8 +897,7 @@ async def test_pattern_destination(hass, caplog): "origin_longitude": CAR_ORIGIN_LONGITUDE, "destination_latitude": "139.0", "destination_longitude": "-77.1", - "app_id": APP_ID, - "app_code": APP_CODE, + "api_key": API_KEY, } } assert await async_setup_component(hass, DOMAIN, config) @@ -938,8 +913,7 @@ async def test_invalid_credentials(hass, requests_mock, caplog): ",".join([CAR_ORIGIN_LATITUDE, CAR_ORIGIN_LONGITUDE]), ",".join([CAR_DESTINATION_LATITUDE, CAR_DESTINATION_LONGITUDE]), modes, - APP_ID, - APP_CODE, + API_KEY, "now", ) requests_mock.get( @@ -955,8 +929,7 @@ async def test_invalid_credentials(hass, requests_mock, caplog): "origin_longitude": CAR_ORIGIN_LONGITUDE, "destination_latitude": CAR_DESTINATION_LATITUDE, "destination_longitude": CAR_DESTINATION_LONGITUDE, - "app_id": APP_ID, - "app_code": APP_CODE, + "api_key": API_KEY, } } assert await async_setup_component(hass, DOMAIN, config) @@ -969,7 +942,7 @@ async def test_attribution(hass, requests_mock_credentials_check): origin = "50.037751372637686,14.39233448220898" destination = "50.07993838201255,14.42582157361062" modes = [ROUTE_MODE_SHORTEST, TRAVEL_MODE_PUBLIC_TIME_TABLE, TRAFFIC_MODE_ENABLED] - response_url = _build_mock_url(origin, destination, modes, APP_ID, APP_CODE, "now") + response_url = _build_mock_url(origin, destination, modes, API_KEY, "now") requests_mock_credentials_check.get( response_url, text=load_fixture("here_travel_time/attribution_response.json") ) @@ -982,8 +955,7 @@ async def test_attribution(hass, requests_mock_credentials_check): "origin_longitude": origin.split(",")[1], "destination_latitude": destination.split(",")[0], "destination_longitude": destination.split(",")[1], - "app_id": APP_ID, - "app_code": APP_CODE, + "api_key": API_KEY, "traffic_mode": True, "route_mode": ROUTE_MODE_SHORTEST, "mode": TRAVEL_MODE_PUBLIC_TIME_TABLE, @@ -1013,8 +985,7 @@ async def test_pattern_entity_state(hass, requests_mock_truck_response, caplog): "origin_entity_id": "sensor.origin", "destination_latitude": TRUCK_DESTINATION_LATITUDE, "destination_longitude": TRUCK_DESTINATION_LONGITUDE, - "app_id": APP_ID, - "app_code": APP_CODE, + "api_key": API_KEY, "mode": TRAVEL_MODE_TRUCK, } } @@ -1040,8 +1011,7 @@ async def test_pattern_entity_state_with_space(hass, requests_mock_truck_respons "origin_entity_id": "sensor.origin", "destination_latitude": TRUCK_DESTINATION_LATITUDE, "destination_longitude": TRUCK_DESTINATION_LONGITUDE, - "app_id": APP_ID, - "app_code": APP_CODE, + "api_key": API_KEY, "mode": TRAVEL_MODE_TRUCK, } } @@ -1059,8 +1029,7 @@ async def test_delayed_update(hass, requests_mock_truck_response, caplog): "origin_entity_id": "sensor.origin", "destination_latitude": TRUCK_DESTINATION_LATITUDE, "destination_longitude": TRUCK_DESTINATION_LONGITUDE, - "app_id": APP_ID, - "app_code": APP_CODE, + "api_key": API_KEY, "mode": TRAVEL_MODE_TRUCK, } } From 8fe17c0933a379c0466bf4ff5198bd4e878c19bd Mon Sep 17 00:00:00 2001 From: Jeff Irion Date: Sun, 15 Dec 2019 23:21:29 -0800 Subject: [PATCH 311/677] Remove 'SUPPORT_PLAY_MEDIA' from Volumio (#29969) --- homeassistant/components/volumio/media_player.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/homeassistant/components/volumio/media_player.py b/homeassistant/components/volumio/media_player.py index b25520c3f38..f62a74345b1 100644 --- a/homeassistant/components/volumio/media_player.py +++ b/homeassistant/components/volumio/media_player.py @@ -21,7 +21,6 @@ from homeassistant.components.media_player.const import ( SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_SELECT_SOURCE, @@ -61,7 +60,6 @@ SUPPORT_VOLUMIO = ( | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK - | SUPPORT_PLAY_MEDIA | SUPPORT_STOP | SUPPORT_PLAY | SUPPORT_VOLUME_STEP From 9e51a18845bde3baa873dcd37c3e1b0a8476473e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 16 Dec 2019 08:22:20 +0100 Subject: [PATCH 312/677] Make hassfest import detection better (#29932) * Make hassfest import detection better * Fix tests --- homeassistant/components/filter/manifest.json | 6 +- homeassistant/components/history/__init__.py | 4 +- script/hassfest/dependencies.py | 110 ++++++++++++---- tests/components/filter/test_sensor.py | 1 + tests/hassfest/__init__.py | 1 + tests/hassfest/test_dependencies.py | 120 ++++++++++++++++++ 6 files changed, 209 insertions(+), 33 deletions(-) create mode 100644 tests/hassfest/__init__.py create mode 100644 tests/hassfest/test_dependencies.py diff --git a/homeassistant/components/filter/manifest.json b/homeassistant/components/filter/manifest.json index f28007ba552..f84a8b5192f 100644 --- a/homeassistant/components/filter/manifest.json +++ b/homeassistant/components/filter/manifest.json @@ -3,8 +3,6 @@ "name": "Filter", "documentation": "https://www.home-assistant.io/integrations/filter", "requirements": [], - "dependencies": [], - "codeowners": [ - "@dgomes" - ] + "dependencies": ["history"], + "codeowners": ["@dgomes"] } diff --git a/homeassistant/components/history/__init__.py b/homeassistant/components/history/__init__.py index 133151c7f73..7fcbf519bf3 100644 --- a/homeassistant/components/history/__init__.py +++ b/homeassistant/components/history/__init__.py @@ -8,7 +8,7 @@ import time from sqlalchemy import and_, func import voluptuous as vol -from homeassistant.components import recorder, script +from homeassistant.components import recorder from homeassistant.components.http import HomeAssistantView from homeassistant.components.recorder.models import States from homeassistant.components.recorder.util import execute, session_scope @@ -430,4 +430,4 @@ def _is_significant(state): Will only test for things that are not filtered out in SQL. """ # scripts that are not cancellable will never change state - return state.domain != "script" or state.attributes.get(script.ATTR_CAN_CANCEL) + return state.domain != "script" or state.attributes.get("can_cancel") diff --git a/script/hassfest/dependencies.py b/script/hassfest/dependencies.py index b6f277438b4..6ba228b5bc7 100644 --- a/script/hassfest/dependencies.py +++ b/script/hassfest/dependencies.py @@ -1,6 +1,5 @@ """Validate dependencies.""" -import pathlib -import re +import ast from typing import Dict, Set from homeassistant.requirements import DISCOVERY_INTEGRATIONS @@ -8,31 +7,80 @@ from homeassistant.requirements import DISCOVERY_INTEGRATIONS from .model import Integration -def grep_dir(path: pathlib.Path, glob_pattern: str, search_pattern: str) -> Set[str]: - """Recursively go through a dir and it's children and find the regex.""" - pattern = re.compile(search_pattern) - found = set() +class ImportCollector(ast.NodeVisitor): + """Collect all integrations referenced.""" - for fil in path.glob(glob_pattern): - if not fil.is_file(): - continue + def __init__(self, integration: Integration): + """Initialize the import collector.""" + self.integration = integration + self.referenced: Set[str] = set() - for match in pattern.finditer(fil.read_text()): - integration = match.groups()[1] + def maybe_add_reference(self, reference_domain: str): + """Add a reference.""" + if ( + # If it's importing something from itself + reference_domain == self.integration.path.name + # Platform file + or (self.integration.path / f"{reference_domain}.py").exists() + # Platform dir + or (self.integration.path / reference_domain).exists() + ): + return - if ( - # If it's importing something from itself - integration == path.name - # Platform file - or (path / f"{integration}.py").exists() - # Dir for platform - or (path / integration).exists() - ): - continue + self.referenced.add(reference_domain) - found.add(match.groups()[1]) + def visit_ImportFrom(self, node): + """Visit ImportFrom node.""" + if node.module is None: + return - return found + if node.module.startswith("homeassistant.components."): + # from homeassistant.components.alexa.smart_home import EVENT_ALEXA_SMART_HOME + # from homeassistant.components.logbook import bla + self.maybe_add_reference(node.module.split(".")[2]) + + elif node.module == "homeassistant.components": + # from homeassistant.components import sun + for name_node in node.names: + self.maybe_add_reference(name_node.name) + + def visit_Import(self, node): + """Visit Import node.""" + # import homeassistant.components.hue as hue + for name_node in node.names: + if name_node.name.startswith("homeassistant.components."): + self.maybe_add_reference(name_node.name.split(".")[2]) + + def visit_Attribute(self, node): + """Visit Attribute node.""" + # hass.components.hue.async_create() + # Name(id=hass) + # .Attribute(attr=hue) + # .Attribute(attr=async_create) + + # self.hass.components.hue.async_create() + # Name(id=self) + # .Attribute(attr=hass) + # .Attribute(attr=hue) + # .Attribute(attr=async_create) + if ( + isinstance(node.value, ast.Attribute) + and node.value.attr == "components" + and ( + ( + isinstance(node.value.value, ast.Name) + and node.value.value.id == "hass" + ) + or ( + isinstance(node.value.value, ast.Attribute) + and node.value.value.attr == "hass" + ) + ) + ): + self.maybe_add_reference(node.attr) + else: + # Have it visit other kids + self.generic_visit(node) ALLOWED_USED_COMPONENTS = { @@ -87,12 +135,20 @@ IGNORE_VIOLATIONS = [ def validate_dependencies(integration: Integration): """Validate all dependencies.""" # Find usage of hass.components - referenced = grep_dir( - integration.path, "**/*.py", r"(hass|homeassistant)\.components\.(\w+)" + collector = ImportCollector(integration) + + for fil in integration.path.glob("**/*.py"): + if not fil.is_file(): + continue + + collector.visit(ast.parse(fil.read_text())) + + referenced = ( + collector.referenced + - ALLOWED_USED_COMPONENTS + - set(integration.manifest["dependencies"]) + - set(integration.manifest.get("after_dependencies", [])) ) - referenced -= ALLOWED_USED_COMPONENTS - referenced -= set(integration.manifest["dependencies"]) - referenced -= set(integration.manifest.get("after_dependencies", [])) # Discovery requirements are ok if referenced in manifest for check_domain, to_check in DISCOVERY_INTEGRATIONS.items(): diff --git a/tests/components/filter/test_sensor.py b/tests/components/filter/test_sensor.py index 17e5bd8fd5d..a5f23b464dd 100644 --- a/tests/components/filter/test_sensor.py +++ b/tests/components/filter/test_sensor.py @@ -28,6 +28,7 @@ class TestFilterSensor(unittest.TestCase): def setup_method(self, method): """Set up things to be run when tests are started.""" self.hass = get_test_home_assistant() + self.hass.config.components.add("history") raw_values = [20, 19, 18, 21, 22, 0] self.values = [] diff --git a/tests/hassfest/__init__.py b/tests/hassfest/__init__.py new file mode 100644 index 00000000000..1ec5a22a567 --- /dev/null +++ b/tests/hassfest/__init__.py @@ -0,0 +1 @@ +"""Tests for hassfest.""" diff --git a/tests/hassfest/test_dependencies.py b/tests/hassfest/test_dependencies.py new file mode 100644 index 00000000000..0f07c45317e --- /dev/null +++ b/tests/hassfest/test_dependencies.py @@ -0,0 +1,120 @@ +"""Tests for hassfest dependency finder.""" +import ast + +import pytest +from script.hassfest.dependencies import ImportCollector + + +@pytest.fixture +def mock_collector(): + """Fixture with import collector that adds all referenced nodes.""" + collector = ImportCollector(None) + collector.maybe_add_reference = collector.referenced.add + return collector + + +def test_child_import(mock_collector): + """Test detecting a child_import reference.""" + mock_collector.visit( + ast.parse( + """ +from homeassistant.components import child_import +""" + ) + ) + assert mock_collector.referenced == {"child_import"} + + +def test_subimport(mock_collector): + """Test detecting a subimport reference.""" + mock_collector.visit( + ast.parse( + """ +from homeassistant.components.subimport.smart_home import EVENT_ALEXA_SMART_HOME +""" + ) + ) + assert mock_collector.referenced == {"subimport"} + + +def test_child_import_field(mock_collector): + """Test detecting a child_import_field reference.""" + mock_collector.visit( + ast.parse( + """ +from homeassistant.components.child_import_field import bla +""" + ) + ) + assert mock_collector.referenced == {"child_import_field"} + + +def test_renamed_absolute(mock_collector): + """Test detecting a renamed_absolute reference.""" + mock_collector.visit( + ast.parse( + """ +import homeassistant.components.renamed_absolute as hue +""" + ) + ) + assert mock_collector.referenced == {"renamed_absolute"} + + +def test_hass_components_var(mock_collector): + """Test detecting a hass_components_var reference.""" + mock_collector.visit( + ast.parse( + """ +def bla(hass): + hass.components.hass_components_var.async_do_something() +""" + ) + ) + assert mock_collector.referenced == {"hass_components_var"} + + +def test_hass_components_class(mock_collector): + """Test detecting a hass_components_class reference.""" + mock_collector.visit( + ast.parse( + """ +class Hello: + def something(self): + self.hass.components.hass_components_class.async_yo() +""" + ) + ) + assert mock_collector.referenced == {"hass_components_class"} + + +def test_all_imports(mock_collector): + """Test all imports together.""" + mock_collector.visit( + ast.parse( + """ +from homeassistant.components import child_import + +from homeassistant.components.subimport.smart_home import EVENT_ALEXA_SMART_HOME + +from homeassistant.components.child_import_field import bla + +import homeassistant.components.renamed_absolute as hue + +def bla(hass): + hass.components.hass_components_var.async_do_something() + +class Hello: + def something(self): + self.hass.components.hass_components_class.async_yo() +""" + ) + ) + assert mock_collector.referenced == { + "child_import", + "subimport", + "child_import_field", + "renamed_absolute", + "hass_components_var", + "hass_components_class", + } From 3f32490ae62ba757e081e4a80d9a54a676647f62 Mon Sep 17 00:00:00 2001 From: Ryan <2199132+rsnodgrass@users.noreply.github.com> Date: Sun, 15 Dec 2019 23:41:32 -0800 Subject: [PATCH 313/677] Fixed "condtion_type" to "condition_type" (#29984) Fixed "condtion_type" to "condition_type" --- homeassistant/components/fan/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/fan/strings.json b/homeassistant/components/fan/strings.json index 134119f41ff..98c3012c123 100644 --- a/homeassistant/components/fan/strings.json +++ b/homeassistant/components/fan/strings.json @@ -1,6 +1,6 @@ { "device_automation": { - "condtion_type": { + "condition_type": { "is_on": "{entity_name} is on", "is_off": "{entity_name} is off" }, From b058742404a6bcac48b6a1df1336591f13206ce1 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 16 Dec 2019 09:33:11 +0100 Subject: [PATCH 314/677] Fix condition typo (#29989) --- homeassistant/components/climate/strings.json | 2 +- homeassistant/components/device_tracker/strings.json | 4 ++-- homeassistant/components/vacuum/strings.json | 2 +- script/scaffold/generate.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/climate/strings.json b/homeassistant/components/climate/strings.json index a2ceeff2143..ff071aed083 100644 --- a/homeassistant/components/climate/strings.json +++ b/homeassistant/components/climate/strings.json @@ -1,6 +1,6 @@ { "device_automation": { - "condtion_type": { + "condition_type": { "is_hvac_mode": "{entity_name} is set to a specific HVAC mode", "is_preset_mode": "{entity_name} is set to a specific preset mode" }, diff --git a/homeassistant/components/device_tracker/strings.json b/homeassistant/components/device_tracker/strings.json index 7e0691654a0..285bac2cb4b 100644 --- a/homeassistant/components/device_tracker/strings.json +++ b/homeassistant/components/device_tracker/strings.json @@ -1,8 +1,8 @@ { "device_automation": { - "condtion_type": { + "condition_type": { "is_home": "{entity_name} is home", "is_not_home": "{entity_name} is not home" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/vacuum/strings.json b/homeassistant/components/vacuum/strings.json index 0300242a506..4eee3f359b5 100644 --- a/homeassistant/components/vacuum/strings.json +++ b/homeassistant/components/vacuum/strings.json @@ -1,6 +1,6 @@ { "device_automation": { - "condtion_type": { + "condition_type": { "is_docked": "{entity_name} is docked", "is_cleaning": "{entity_name} is cleaning" }, diff --git a/script/scaffold/generate.py b/script/scaffold/generate.py index 59767249d29..b2f669006a9 100644 --- a/script/scaffold/generate.py +++ b/script/scaffold/generate.py @@ -92,7 +92,7 @@ def _custom_tasks(template, info) -> None: info.update_strings( device_automation={ **info.strings().get("device_automation", {}), - "condtion_type": { + "condition_type": { "is_on": "{entity_name} is on", "is_off": "{entity_name} is off", }, From 33cbb398ad9873cc3ac7fc1f5d5c5b11b0721ccd Mon Sep 17 00:00:00 2001 From: Louis-Dominique Dubeau Date: Mon, 16 Dec 2019 03:39:20 -0500 Subject: [PATCH 315/677] Don't use the locals parameter on exec. (#29979) Using the locals parameter makes it so that the code of a Python script runs as if it were in the body of a ``class``. One effect of this is that functions defined as part of a script cannot call one another directly. Fixes: #24704, #13653 --- .../components/python_script/__init__.py | 8 ++++--- tests/components/python_script/test_init.py | 23 +++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/python_script/__init__.py b/homeassistant/components/python_script/__init__.py index e36ac397c0f..ddae8a81db1 100644 --- a/homeassistant/components/python_script/__init__.py +++ b/homeassistant/components/python_script/__init__.py @@ -175,6 +175,7 @@ def execute(hass, filename, source, data=None): builtins["sorted"] = sorted builtins["time"] = TimeWrapper() builtins["dt_util"] = dt_util + logger = logging.getLogger(f"{__name__}.{filename}") restricted_globals = { "__builtins__": builtins, "_print_": StubPrinter, @@ -184,14 +185,15 @@ def execute(hass, filename, source, data=None): "_getitem_": default_guarded_getitem, "_iter_unpack_sequence_": guarded_iter_unpack_sequence, "_unpack_sequence_": guarded_unpack_sequence, + "hass": hass, + "data": data or {}, + "logger": logger, } - logger = logging.getLogger(f"{__name__}.{filename}") - local = {"hass": hass, "data": data or {}, "logger": logger} try: _LOGGER.info("Executing %s: %s", filename, data) # pylint: disable=exec-used - exec(compiled.code, restricted_globals, local) + exec(compiled.code, restricted_globals) except ScriptError as err: logger.error("Error executing script: %s", err) except Exception as err: # pylint: disable=broad-except diff --git a/tests/components/python_script/test_init.py b/tests/components/python_script/test_init.py index 75616c7400a..b5879479837 100644 --- a/tests/components/python_script/test_init.py +++ b/tests/components/python_script/test_init.py @@ -258,6 +258,29 @@ hass.states.set('module.datetime', assert caplog.text == "" +@asyncio.coroutine +def test_execute_functions(hass, caplog): + """Test functions defined in script can call one another.""" + caplog.set_level(logging.ERROR) + source = """ +def a(): + hass.states.set('hello.a', 'one') + +def b(): + a() + hass.states.set('hello.b', 'two') + +b() +""" + hass.async_add_job(execute, hass, "test.py", source, {}) + yield from hass.async_block_till_done() + + assert hass.states.is_state("hello.a", "one") + assert hass.states.is_state("hello.b", "two") + # No errors logged = good + assert caplog.text == "" + + @asyncio.coroutine def test_reload(hass): """Test we can re-discover scripts.""" From be042f3d91ec3e5a0a92a607c0a4df1061058ad2 Mon Sep 17 00:00:00 2001 From: Emacee Date: Mon, 16 Dec 2019 09:42:48 +0100 Subject: [PATCH 316/677] Update binary_sensor.py (#29977) Change sensor type for central locking from safety into lock. --- homeassistant/components/bmw_connected_drive/binary_sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/bmw_connected_drive/binary_sensor.py b/homeassistant/components/bmw_connected_drive/binary_sensor.py index d0cb4289d37..591cdadda35 100644 --- a/homeassistant/components/bmw_connected_drive/binary_sensor.py +++ b/homeassistant/components/bmw_connected_drive/binary_sensor.py @@ -13,7 +13,7 @@ _LOGGER = logging.getLogger(__name__) SENSOR_TYPES = { "lids": ["Doors", "opening", "mdi:car-door-lock"], "windows": ["Windows", "opening", "mdi:car-door"], - "door_lock_state": ["Door lock state", "safety", "mdi:car-key"], + "door_lock_state": ["Door lock state", "lock", "mdi:car-key"], "lights_parking": ["Parking lights", "light", "mdi:car-parking-lights"], "condition_based_services": ["Condition based services", "problem", "mdi:wrench"], "check_control_messages": ["Control messages", "problem", "mdi:car-tire-alert"], From dd0f0034f34a01df6a156779ca29712e8d4484a3 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 16 Dec 2019 10:57:42 +0100 Subject: [PATCH 317/677] Bump shodan to 1.21.0 (#29991) --- homeassistant/components/shodan/manifest.json | 10 +++------- requirements_all.txt | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/shodan/manifest.json b/homeassistant/components/shodan/manifest.json index 36af63da9f8..0ff3e44ece5 100644 --- a/homeassistant/components/shodan/manifest.json +++ b/homeassistant/components/shodan/manifest.json @@ -2,11 +2,7 @@ "domain": "shodan", "name": "Shodan", "documentation": "https://www.home-assistant.io/integrations/shodan", - "requirements": [ - "shodan==1.20.0" - ], + "requirements": ["shodan==1.21.0"], "dependencies": [], - "codeowners": [ - "@fabaff" - ] -} \ No newline at end of file + "codeowners": ["@fabaff"] +} diff --git a/requirements_all.txt b/requirements_all.txt index 873e056eb72..a8d454aac99 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1795,7 +1795,7 @@ sense_energy==0.7.0 sharp_aquos_rc==0.3.2 # homeassistant.components.shodan -shodan==1.20.0 +shodan==1.21.0 # homeassistant.components.simplepush simplepush==1.1.4 From d1e59b20c8b779a52710b81a855cf4077134d7bd Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 16 Dec 2019 10:59:32 +0100 Subject: [PATCH 318/677] Bump pytest to 5.3.2 (#29990) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 70e78222644..328ad1d5b9a 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -14,6 +14,6 @@ pytest-aiohttp==0.3.0 pytest-cov==2.8.1 pytest-sugar==0.9.2 pytest-timeout==1.3.3 -pytest==5.3.1 +pytest==5.3.2 requests_mock==1.7.0 responses==0.10.6 From 87ca61ddd7e5316ba9e703e8145dfeeaf5a024fd Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 16 Dec 2019 11:06:17 +0100 Subject: [PATCH 319/677] Add check-json to CI and Pre-commit (#29912) * Add check-json to CI and Pre-commit * Add ignore pre-commit hooks to gen_requirements_all --- .pre-commit-config-all.yaml | 4 ++++ .pre-commit-config.yaml | 6 +++++- azure-pipelines-ci.yml | 4 ++++ script/gen_requirements_all.py | 7 +++++-- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config-all.yaml b/.pre-commit-config-all.yaml index ec9492e0210..1eabfcb0017 100644 --- a/.pre-commit-config-all.yaml +++ b/.pre-commit-config-all.yaml @@ -39,6 +39,10 @@ repos: rev: v4.3.21 hooks: - id: isort +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.4.0 + hooks: + - id: check-json # Using a local "system" mypy instead of the mypy hook, because its # results depend on what is installed. And the mypy hook runs in a # virtualenv of its own, meaning we'd need to install and maintain diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 23d1d9c73f9..226708bb947 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,7 +25,7 @@ repos: - repo: https://github.com/PyCQA/bandit rev: 1.6.2 hooks: - - id: bandit + - id: bandit args: - --quiet - --format=custom @@ -35,3 +35,7 @@ repos: rev: v4.3.21 hooks: - id: isort +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.4.0 + hooks: + - id: check-json diff --git a/azure-pipelines-ci.yml b/azure-pipelines-ci.yml index 464b1079957..78de90c8552 100644 --- a/azure-pipelines-ci.yml +++ b/azure-pipelines-ci.yml @@ -56,6 +56,10 @@ stages: . venv/bin/activate pre-commit run isort --all-files displayName: 'Run isort' + - script: | + . venv/bin/activate + pre-commit run check-json --all-files + displayName: 'Run check-json' - job: 'Validate' pool: vmImage: 'ubuntu-latest' diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 0dfefc958c3..f40a89a9d9a 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -65,6 +65,8 @@ enum34==1000000000.0.0 pycrypto==1000000000.0.0 """ +IGNORE_PRE_COMMIT_HOOK_ID = ("check-json",) + def has_tests(module: str): """Test if a module has tests. @@ -256,8 +258,9 @@ def requirements_pre_commit_output(): reqs = [] for repo in (x for x in pre_commit_conf["repos"] if x.get("rev")): for hook in repo["hooks"]: - reqs.append(f"{hook['id']}=={repo['rev']}") - reqs.extend(x for x in hook.get("additional_dependencies", ())) + if hook["id"] not in IGNORE_PRE_COMMIT_HOOK_ID: + reqs.append(f"{hook['id']}=={repo['rev']}") + reqs.extend(x for x in hook.get("additional_dependencies", ())) output = [ f"# Automatically generated " f"from {source} by {Path(__file__).name}, do not edit", From d851cb6f9e116dddb8ebeed4dd3910eb811993bd Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 16 Dec 2019 12:27:43 +0100 Subject: [PATCH 320/677] Add unique ID to config entries (#29806) * Add unique ID to config entries * Unload existing entries with same unique ID if flow with unique ID is finished * Remove unused exception * Fix typing * silence pylint * Fix tests * Add unique ID to Hue * Address typing comment * Tweaks to comments * lint --- homeassistant/components/hue/__init__.py | 15 ++- homeassistant/components/hue/bridge.py | 22 ++++ homeassistant/components/hue/config_flow.py | 31 +++--- homeassistant/config_entries.py | 61 ++++++++++- homeassistant/data_entry_flow.py | 46 +++++++-- tests/common.py | 2 + tests/components/hue/test_config_flow.py | 35 ++++--- tests/components/hue/test_init.py | 16 +++ tests/test_config_entries.py | 107 ++++++++++++++++++++ tests/test_data_entry_flow.py | 16 ++- 10 files changed, 305 insertions(+), 46 deletions(-) diff --git a/homeassistant/components/hue/__init__.py b/homeassistant/components/hue/__init__.py index f2b9bd1a229..57057004479 100644 --- a/homeassistant/components/hue/__init__.py +++ b/homeassistant/components/hue/__init__.py @@ -4,11 +4,11 @@ import logging import voluptuous as vol -from homeassistant import config_entries +from homeassistant import config_entries, core from homeassistant.const import CONF_FILENAME, CONF_HOST from homeassistant.helpers import config_validation as cv, device_registry as dr -from .bridge import HueBridge +from .bridge import HueBridge, normalize_bridge_id from .config_flow import ( # Loading the config flow file will register the flow configured_hosts, ) @@ -102,7 +102,9 @@ async def async_setup(hass, config): return True -async def async_setup_entry(hass, entry): +async def async_setup_entry( + hass: core.HomeAssistant, entry: config_entries.ConfigEntry +): """Set up a bridge from a config entry.""" host = entry.data["host"] config = hass.data[DATA_CONFIGS].get(host) @@ -121,6 +123,13 @@ async def async_setup_entry(hass, entry): hass.data[DOMAIN][host] = bridge config = bridge.api.config + + # For backwards compat + if entry.unique_id is None: + hass.config_entries.async_update_entry( + entry, unique_id=normalize_bridge_id(config.bridgeid) + ) + device_registry = await dr.async_get_registry(hass) device_registry.async_get_or_create( config_entry_id=entry.entry_id, diff --git a/homeassistant/components/hue/bridge.py b/homeassistant/components/hue/bridge.py index 5a5e55773a5..0ed6e3a9911 100644 --- a/homeassistant/components/hue/bridge.py +++ b/homeassistant/components/hue/bridge.py @@ -201,3 +201,25 @@ async def get_bridge(hass, host, username=None): except aiohue.AiohueException: LOGGER.exception("Unknown Hue linking error occurred") raise AuthenticationRequired + + +def normalize_bridge_id(bridge_id: str): + """Normalize a bridge identifier. + + There are three sources where we receive bridge ID from: + - ssdp/upnp: /description.xml, field root/device/serialNumber + - nupnp: "id" field + - Hue Bridge API: config.bridgeid + + The SSDP/UPNP source does not contain the middle 4 characters compared + to the other sources. In all our tests the middle 4 characters are "fffe". + """ + if len(bridge_id) == 16: + return bridge_id[0:6] + bridge_id[-6:] + + if len(bridge_id) == 12: + return bridge_id + + LOGGER.warning("Unexpected bridge id number found: %s", bridge_id) + + return bridge_id diff --git a/homeassistant/components/hue/config_flow.py b/homeassistant/components/hue/config_flow.py index 0423dc6fc2b..882bf5b70db 100644 --- a/homeassistant/components/hue/config_flow.py +++ b/homeassistant/components/hue/config_flow.py @@ -12,7 +12,7 @@ from homeassistant.components.ssdp import ATTR_MANUFACTURERURL, ATTR_NAME from homeassistant.core import callback from homeassistant.helpers import aiohttp_client -from .bridge import get_bridge +from .bridge import get_bridge, normalize_bridge_id from .const import DOMAIN, LOGGER from .errors import AuthenticationRequired, CannotConnect @@ -154,17 +154,15 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): if host in configured_hosts(self.hass): return self.async_abort(reason="already_configured") - # This value is based off host/description.xml and is, weirdly, missing - # 4 characters in the middle of the serial compared to results returned - # from the NUPNP API or when querying the bridge API for bridgeid. - # (on first gen Hue hub) - serial = discovery_info.get("serial") + bridge_id = discovery_info.get("serial") + + await self.async_set_unique_id(normalize_bridge_id(bridge_id)) return await self.async_step_import( { "host": host, # This format is the legacy format that Hue used for discovery - "path": f"phue-{serial}.conf", + "path": f"phue-{bridge_id}.conf", } ) @@ -180,6 +178,10 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): if host in configured_hosts(self.hass): return self.async_abort(reason="already_configured") + await self.async_set_unique_id( + normalize_bridge_id(homekit_info["properties"]["id"].replace(":", "")) + ) + return await self.async_step_import({"host": host}) async def async_step_import(self, import_info): @@ -234,18 +236,9 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): host = bridge.host bridge_id = bridge.config.bridgeid - same_hub_entries = [ - entry.entry_id - for entry in self.hass.config_entries.async_entries(DOMAIN) - if entry.data["bridge_id"] == bridge_id or entry.data["host"] == host - ] - - if same_hub_entries: - await asyncio.wait( - [ - self.hass.config_entries.async_remove(entry_id) - for entry_id in same_hub_entries - ] + if self.unique_id is None: + await self.async_set_unique_id( + normalize_bridge_id(bridge_id), raise_on_progress=False ) return self.async_create_entry( diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 07a287c387c..09ee186da0f 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -2,7 +2,7 @@ import asyncio import functools import logging -from typing import Any, Callable, Dict, List, Optional, Set, cast +from typing import Any, Callable, Dict, List, Optional, Set, Union, cast import uuid import weakref @@ -75,6 +75,10 @@ class OperationNotAllowed(ConfigError): """Raised when a config entry operation is not allowed.""" +class UniqueIdInProgress(data_entry_flow.AbortFlow): + """Error to indicate that the unique Id is in progress.""" + + class ConfigEntry: """Hold a configuration entry.""" @@ -85,6 +89,7 @@ class ConfigEntry: "title", "data", "options", + "unique_id", "system_options", "source", "connection_class", @@ -104,6 +109,7 @@ class ConfigEntry: connection_class: str, system_options: dict, options: Optional[dict] = None, + unique_id: Optional[str] = None, entry_id: Optional[str] = None, state: str = ENTRY_STATE_NOT_LOADED, ) -> None: @@ -138,6 +144,9 @@ class ConfigEntry: # State of the entry (LOADED, NOT_LOADED) self.state = state + # Unique ID of this entry. + self.unique_id = unique_id + # Listeners to call on update self.update_listeners: List = [] @@ -533,11 +542,15 @@ class ConfigEntries: self, entry: ConfigEntry, *, + unique_id: Union[str, dict, None] = _UNDEF, data: dict = _UNDEF, options: dict = _UNDEF, system_options: dict = _UNDEF, ) -> None: """Update a config entry.""" + if unique_id is not _UNDEF: + entry.unique_id = cast(Optional[str], unique_id) + if data is not _UNDEF: entry.data = data @@ -602,6 +615,25 @@ class ConfigEntries: if result["type"] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY: return result + # Check if config entry exists with unique ID. Unload it. + existing_entry = None + unique_id = flow.context.get("unique_id") + + if unique_id is not None: + for check_entry in self.async_entries(result["handler"]): + if check_entry.unique_id == unique_id: + existing_entry = check_entry + break + + # Unload the entry before setting up the new one. + # We will remove it only after the other one is set up, + # so that device customizations are not getting lost. + if ( + existing_entry is not None + and existing_entry.state not in UNRECOVERABLE_STATES + ): + await self.async_unload(existing_entry.entry_id) + entry = ConfigEntry( version=result["version"], domain=result["handler"], @@ -611,12 +643,16 @@ class ConfigEntries: system_options={}, source=flow.context["source"], connection_class=flow.CONNECTION_CLASS, + unique_id=unique_id, ) self._entries.append(entry) self._async_schedule_save() await self.async_setup(entry.entry_id) + if existing_entry is not None: + await self.async_remove(existing_entry.entry_id) + result["result"] = entry return result @@ -687,6 +723,8 @@ async def _old_conf_migrator(old_config: Dict[str, Any]) -> Dict[str, Any]: class ConfigFlow(data_entry_flow.FlowHandler): """Base class for config flows with some helpers.""" + unique_id = None + def __init_subclass__(cls, domain: Optional[str] = None, **kwargs: Any) -> None: """Initialize a subclass, register if possible.""" super().__init_subclass__(**kwargs) # type: ignore @@ -701,6 +739,27 @@ class ConfigFlow(data_entry_flow.FlowHandler): """Get the options flow for this handler.""" raise data_entry_flow.UnknownHandler + async def async_set_unique_id( + self, unique_id: str, *, raise_on_progress: bool = True + ) -> Optional[ConfigEntry]: + """Set a unique ID for the config flow. + + Returns optionally existing config entry with same ID. + """ + if raise_on_progress: + for progress in self._async_in_progress(): + if progress["context"].get("unique_id") == unique_id: + raise UniqueIdInProgress("already_in_progress") + + # pylint: disable=no-member + self.context["unique_id"] = unique_id + + for entry in self._async_current_entries(): + if entry.unique_id == unique_id: + return entry + + return None + @callback def _async_current_entries(self) -> List[ConfigEntry]: """Return current entries.""" diff --git a/homeassistant/data_entry_flow.py b/homeassistant/data_entry_flow.py index e7432cd52f7..7c2b4ab6ddc 100644 --- a/homeassistant/data_entry_flow.py +++ b/homeassistant/data_entry_flow.py @@ -1,6 +1,6 @@ """Classes to help gather user submissions.""" import logging -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Callable, Dict, List, Optional, cast import uuid import voluptuous as vol @@ -36,6 +36,16 @@ class UnknownStep(FlowError): """Unknown step specified.""" +class AbortFlow(FlowError): + """Exception to indicate a flow needs to be aborted.""" + + def __init__(self, reason: str, description_placeholders: Optional[Dict] = None): + """Initialize an abort flow exception.""" + super().__init__(f"Flow aborted: {reason}") + self.reason = reason + self.description_placeholders = description_placeholders + + class FlowManager: """Manage all the flows that are in progress.""" @@ -131,7 +141,12 @@ class FlowManager: ) ) - result: Dict = await getattr(flow, method)(user_input) + try: + result: Dict = await getattr(flow, method)(user_input) + except AbortFlow as err: + result = _create_abort_data( + flow.flow_id, flow.handler, err.reason, err.description_placeholders + ) if result["type"] not in ( RESULT_TYPE_FORM, @@ -228,13 +243,9 @@ class FlowHandler: self, *, reason: str, description_placeholders: Optional[Dict] = None ) -> Dict[str, Any]: """Abort the config flow.""" - return { - "type": RESULT_TYPE_ABORT, - "flow_id": self.flow_id, - "handler": self.handler, - "reason": reason, - "description_placeholders": description_placeholders, - } + return _create_abort_data( + self.flow_id, cast(str, self.handler), reason, description_placeholders + ) @callback def async_external_step( @@ -259,3 +270,20 @@ class FlowHandler: "handler": self.handler, "step_id": next_step_id, } + + +@callback +def _create_abort_data( + flow_id: str, + handler: str, + reason: str, + description_placeholders: Optional[Dict] = None, +) -> Dict[str, Any]: + """Return the definition of an external step for the user to take.""" + return { + "type": RESULT_TYPE_ABORT, + "flow_id": flow_id, + "handler": handler, + "reason": reason, + "description_placeholders": description_placeholders, + } diff --git a/tests/common.py b/tests/common.py index a54b3899698..5d13da74e88 100644 --- a/tests/common.py +++ b/tests/common.py @@ -671,6 +671,7 @@ class MockConfigEntry(config_entries.ConfigEntry): options={}, system_options={}, connection_class=config_entries.CONN_CLASS_UNKNOWN, + unique_id=None, ): """Initialize a mock config entry.""" kwargs = { @@ -682,6 +683,7 @@ class MockConfigEntry(config_entries.ConfigEntry): "version": version, "title": title, "connection_class": connection_class, + "unique_id": unique_id, } if source is not None: kwargs["source"] = source diff --git a/tests/components/hue/test_config_flow.py b/tests/components/hue/test_config_flow.py index a6d221ef323..030f6ade1fa 100644 --- a/tests/components/hue/test_config_flow.py +++ b/tests/components/hue/test_config_flow.py @@ -19,6 +19,7 @@ async def test_flow_works(hass, aioclient_mock): flow = config_flow.HueFlowHandler() flow.hass = hass + flow.context = {} await flow.async_step_init() with patch("aiohue.Bridge") as mock_bridge: @@ -349,28 +350,33 @@ async def test_creating_entry_removes_entries_for_same_host_or_bridge(hass): accessible via a single IP. So when we create a new entry, we'll remove all existing entries that either have same IP or same bridge_id. """ - MockConfigEntry( - domain="hue", data={"host": "0.0.0.0", "bridge_id": "id-1234"} - ).add_to_hass(hass) + orig_entry = MockConfigEntry( + domain="hue", + data={"host": "0.0.0.0", "bridge_id": "id-1234"}, + unique_id="id-1234", + ) + orig_entry.add_to_hass(hass) MockConfigEntry( - domain="hue", data={"host": "1.2.3.4", "bridge_id": "id-1234"} + domain="hue", + data={"host": "1.2.3.4", "bridge_id": "id-5678"}, + unique_id="id-5678", ).add_to_hass(hass) assert len(hass.config_entries.async_entries("hue")) == 2 - flow = config_flow.HueFlowHandler() - flow.hass = hass - flow.context = {} - bridge = Mock() bridge.username = "username-abc" bridge.config.bridgeid = "id-1234" bridge.config.name = "Mock Bridge" bridge.host = "0.0.0.0" - with patch.object(config_flow, "get_bridge", return_value=mock_coro(bridge)): - result = await flow.async_step_import({"host": "0.0.0.0"}) + with patch.object( + config_flow, "_find_username_from_config", return_value="mock-user" + ), patch.object(config_flow, "get_bridge", return_value=mock_coro(bridge)): + result = await hass.config_entries.flow.async_init( + "hue", data={"host": "2.2.2.2"}, context={"source": "import"} + ) assert result["type"] == "create_entry" assert result["title"] == "Mock Bridge" @@ -379,9 +385,11 @@ async def test_creating_entry_removes_entries_for_same_host_or_bridge(hass): "bridge_id": "id-1234", "username": "username-abc", } - # We did not process the result of this entry but already removed the old - # ones. So we should have 0 entries. - assert len(hass.config_entries.async_entries("hue")) == 0 + entries = hass.config_entries.async_entries("hue") + assert len(entries) == 2 + new_entry = entries[-1] + assert orig_entry.entry_id != new_entry.entry_id + assert new_entry.unique_id == "id-1234" async def test_bridge_homekit(hass): @@ -398,6 +406,7 @@ async def test_bridge_homekit(hass): "host": "0.0.0.0", "serial": "1234", "manufacturerURL": config_flow.HUE_MANUFACTURERURL, + "properties": {"id": "aa:bb:cc:dd:ee:ff"}, } ) diff --git a/tests/components/hue/test_init.py b/tests/components/hue/test_init.py index 58f004ec540..d064ff9f340 100644 --- a/tests/components/hue/test_init.py +++ b/tests/components/hue/test_init.py @@ -175,3 +175,19 @@ async def test_unload_entry(hass): assert await hue.async_unload_entry(hass, entry) assert len(mock_bridge.return_value.async_reset.mock_calls) == 1 assert hass.data[hue.DOMAIN] == {} + + +async def test_setting_unique_id(hass): + """Test we set unique ID if not set yet.""" + entry = MockConfigEntry(domain=hue.DOMAIN, data={"host": "0.0.0.0"}) + entry.add_to_hass(hass) + + with patch.object(hue, "HueBridge") as mock_bridge, patch( + "homeassistant.helpers.device_registry.async_get_registry", + return_value=mock_coro(Mock()), + ): + mock_bridge.return_value.async_setup.return_value = mock_coro(True) + mock_bridge.return_value.api.config = Mock(bridgeid="mock-id") + assert await async_setup_component(hass, hue.DOMAIN, {}) is True + + assert entry.unique_id == "mock-id" diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 24a0b0939be..a9ae4eb59ac 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -1001,3 +1001,110 @@ async def test_reload_entry_entity_registry_works(hass): await hass.async_block_till_done() assert len(mock_unload_entry.mock_calls) == 1 + + +async def test_unqiue_id_persisted(hass, manager): + """Test that a unique ID is stored in the config entry.""" + mock_setup_entry = MagicMock(return_value=mock_coro(True)) + + mock_integration(hass, MockModule("comp", async_setup_entry=mock_setup_entry)) + mock_entity_platform(hass, "config_flow.comp", None) + + class TestFlow(config_entries.ConfigFlow): + + VERSION = 1 + + async def async_step_user(self, user_input=None): + await self.async_set_unique_id("mock-unique-id") + return self.async_create_entry(title="mock-title", data={}) + + with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}): + await manager.flow.async_init( + "comp", context={"source": config_entries.SOURCE_USER} + ) + + assert len(mock_setup_entry.mock_calls) == 1 + p_hass, p_entry = mock_setup_entry.mock_calls[0][1] + + assert p_hass is hass + assert p_entry.unique_id == "mock-unique-id" + + +async def test_unique_id_existing_entry(hass, manager): + """Test that we remove an entry if there already is an entry with unique ID.""" + hass.config.components.add("comp") + MockConfigEntry( + domain="comp", + state=config_entries.ENTRY_STATE_LOADED, + unique_id="mock-unique-id", + ).add_to_hass(hass) + + async_setup_entry = MagicMock(side_effect=lambda _, _2: mock_coro(True)) + async_unload_entry = MagicMock(side_effect=lambda _, _2: mock_coro(True)) + async_remove_entry = MagicMock(side_effect=lambda _, _2: mock_coro(True)) + + mock_integration( + hass, + MockModule( + "comp", + async_setup_entry=async_setup_entry, + async_unload_entry=async_unload_entry, + async_remove_entry=async_remove_entry, + ), + ) + mock_entity_platform(hass, "config_flow.comp", None) + + class TestFlow(config_entries.ConfigFlow): + + VERSION = 1 + + async def async_step_user(self, user_input=None): + existing_entry = await self.async_set_unique_id("mock-unique-id") + + assert existing_entry is not None + + return self.async_create_entry(title="mock-title", data={"via": "flow"}) + + with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}): + result = await manager.flow.async_init( + "comp", context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + + entries = hass.config_entries.async_entries("comp") + assert len(entries) == 1 + assert entries[0].data == {"via": "flow"} + + assert len(async_setup_entry.mock_calls) == 1 + assert len(async_unload_entry.mock_calls) == 1 + assert len(async_remove_entry.mock_calls) == 1 + + +async def test_unique_id_in_progress(hass, manager): + """Test that we abort if there is already a flow in progress with same unique id.""" + mock_integration(hass, MockModule("comp")) + mock_entity_platform(hass, "config_flow.comp", None) + + class TestFlow(config_entries.ConfigFlow): + + VERSION = 1 + + async def async_step_user(self, user_input=None): + await self.async_set_unique_id("mock-unique-id") + return self.async_show_form(step_id="discovery") + + with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}): + # Create one to be in progress + result = await manager.flow.async_init( + "comp", context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + + # Will be canceled + result2 = await manager.flow.async_init( + "comp", context={"source": config_entries.SOURCE_USER} + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result2["reason"] == "already_in_progress" diff --git a/tests/test_data_entry_flow.py b/tests/test_data_entry_flow.py index 175efebd755..a6bdd2b5cb6 100644 --- a/tests/test_data_entry_flow.py +++ b/tests/test_data_entry_flow.py @@ -94,7 +94,7 @@ async def test_configure_two_steps(manager): async def test_show_form(manager): - """Test that abort removes the flow from progress.""" + """Test that we can show a form.""" schema = vol.Schema({vol.Required("username"): str, vol.Required("password"): str}) @manager.mock_reg_handler("test") @@ -271,3 +271,17 @@ async def test_external_step(hass, manager): result = await manager.async_configure(result["flow_id"]) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["title"] == "Hello" + + +async def test_abort_flow_exception(manager): + """Test that the AbortFlow exception works.""" + + @manager.mock_reg_handler("test") + class TestFlow(data_entry_flow.FlowHandler): + async def async_step_init(self, user_input=None): + raise data_entry_flow.AbortFlow("mock-reason", {"placeholder": "yo"}) + + form = await manager.async_init("test") + assert form["type"] == "abort" + assert form["reason"] == "mock-reason" + assert form["description_placeholders"] == {"placeholder": "yo"} From 575eb48febe9a5eeff424faa82209d96bce15eef Mon Sep 17 00:00:00 2001 From: zewelor Date: Mon, 16 Dec 2019 16:23:05 +0100 Subject: [PATCH 321/677] Show current effect in yeelight device (#28975) * Show current effect in yeelight device * Use device_state_attributes instead of state_attributes * Add early return in set effect * Make single if elif chain * Fix if elif * Fix if elif --- homeassistant/components/yeelight/__init__.py | 11 +++ homeassistant/components/yeelight/light.py | 93 ++++++++++++------- 2 files changed, 68 insertions(+), 36 deletions(-) diff --git a/homeassistant/components/yeelight/__init__.py b/homeassistant/components/yeelight/__init__.py index ddd3cf2a053..b947f6b448c 100644 --- a/homeassistant/components/yeelight/__init__.py +++ b/homeassistant/components/yeelight/__init__.py @@ -49,6 +49,7 @@ ACTION_STAY = "stay" ACTION_OFF = "off" ACTIVE_MODE_NIGHTLIGHT = "1" +ACTIVE_COLOR_FLOWING = "1" NIGHTLIGHT_SWITCH_TYPE_LIGHT = "light" @@ -124,6 +125,7 @@ UPDATE_REQUEST_PROPERTIES = [ "hue", "sat", "color_mode", + "flowing", "bg_power", "bg_lmode", "bg_flowing", @@ -251,10 +253,19 @@ class YeelightDevice: return self._active_mode is not None + @property + def is_color_flow_enabled(self) -> bool: + """Return true / false if color flow is currently running.""" + return self._color_flow == ACTIVE_COLOR_FLOWING + @property def _active_mode(self): return self.bulb.last_properties.get("active_mode") + @property + def _color_flow(self): + return self.bulb.last_properties.get("flowing") + @property def type(self): """Return bulb type.""" diff --git a/homeassistant/components/yeelight/light.py b/homeassistant/components/yeelight/light.py index 3e98403587f..0039755a6af 100644 --- a/homeassistant/components/yeelight/light.py +++ b/homeassistant/components/yeelight/light.py @@ -142,6 +142,21 @@ MODEL_TO_DEVICE_TYPE = { "ceiling4": BulbType.WhiteTempMood, } +EFFECTS_MAP = { + EFFECT_DISCO: yee_transitions.disco, + EFFECT_TEMP: yee_transitions.temp, + EFFECT_STROBE: yee_transitions.strobe, + EFFECT_STROBE_COLOR: yee_transitions.strobe_color, + EFFECT_ALARM: yee_transitions.alarm, + EFFECT_POLICE: yee_transitions.police, + EFFECT_POLICE2: yee_transitions.police2, + EFFECT_CHRISTMAS: yee_transitions.christmas, + EFFECT_RGB: yee_transitions.rgb, + EFFECT_RANDOM_LOOP: yee_transitions.randomloop, + EFFECT_LSD: yee_transitions.lsd, + EFFECT_SLOWDOWN: yee_transitions.slowdown, +} + VALID_BRIGHTNESS = vol.All(vol.Coerce(int), vol.Range(min=1, max=100)) SERVICE_SCHEMA_SET_MODE = YEELIGHT_SERVICE_SCHEMA.extend( @@ -416,6 +431,7 @@ class YeelightGenericLight(Light): self._brightness = None self._color_temp = None self._hs = None + self._effect = None model_specs = self._bulb.get_model_specs() self._min_mireds = kelvin_to_mired(model_specs["color_temp"]["max"]) @@ -516,6 +532,11 @@ class YeelightGenericLight(Light): """Return the color property.""" return self._hs + @property + def effect(self): + """Return the current effect.""" + return self._effect + # F821: https://github.com/PyCQA/pyflakes/issues/373 @property def _bulb(self) -> "Bulb": # noqa: F821 @@ -546,6 +567,16 @@ class YeelightGenericLight(Light): def _predefined_effects(self): return YEELIGHT_MONO_EFFECT_LIST + @property + def device_state_attributes(self): + """Return the device specific state attributes.""" + + attributes = {"flowing": self.device.is_color_flow_enabled} + if self.device.is_nightlight_supported: + attributes["night_light"] = self.device.is_nightlight_enabled + + return attributes + @property def device(self): """Return yeelight device.""" @@ -554,6 +585,8 @@ class YeelightGenericLight(Light): def update(self): """Update light properties.""" self._hs = self._get_hs_from_properties() + if not self.device.is_color_flow_enabled: + self._effect = None def _get_hs_from_properties(self): rgb = self._get_property("rgb") @@ -658,45 +691,33 @@ class YeelightGenericLight(Light): @_cmd def set_effect(self, effect) -> None: """Activate effect.""" - if effect: - if effect == EFFECT_STOP: - self._bulb.stop_flow(light_type=self.light_type) - return + if not effect: + return - effects_map = { - EFFECT_DISCO: yee_transitions.disco, - EFFECT_TEMP: yee_transitions.temp, - EFFECT_STROBE: yee_transitions.strobe, - EFFECT_STROBE_COLOR: yee_transitions.strobe_color, - EFFECT_ALARM: yee_transitions.alarm, - EFFECT_POLICE: yee_transitions.police, - EFFECT_POLICE2: yee_transitions.police2, - EFFECT_CHRISTMAS: yee_transitions.christmas, - EFFECT_RGB: yee_transitions.rgb, - EFFECT_RANDOM_LOOP: yee_transitions.randomloop, - EFFECT_LSD: yee_transitions.lsd, - EFFECT_SLOWDOWN: yee_transitions.slowdown, - } + if effect == EFFECT_STOP: + self._bulb.stop_flow(light_type=self.light_type) + return - if effect in self.custom_effects_names: - flow = Flow(**self.custom_effects[effect]) - elif effect in effects_map: - flow = Flow(count=0, transitions=effects_map[effect]()) - elif effect == EFFECT_FAST_RANDOM_LOOP: - flow = Flow( - count=0, transitions=yee_transitions.randomloop(duration=250) - ) - elif effect == EFFECT_WHATSAPP: - flow = Flow(count=2, transitions=yee_transitions.pulse(37, 211, 102)) - elif effect == EFFECT_FACEBOOK: - flow = Flow(count=2, transitions=yee_transitions.pulse(59, 89, 152)) - elif effect == EFFECT_TWITTER: - flow = Flow(count=2, transitions=yee_transitions.pulse(0, 172, 237)) + if effect in self.custom_effects_names: + flow = Flow(**self.custom_effects[effect]) + elif effect in EFFECTS_MAP: + flow = Flow(count=0, transitions=EFFECTS_MAP[effect]()) + elif effect == EFFECT_FAST_RANDOM_LOOP: + flow = Flow(count=0, transitions=yee_transitions.randomloop(duration=250)) + elif effect == EFFECT_WHATSAPP: + flow = Flow(count=2, transitions=yee_transitions.pulse(37, 211, 102)) + elif effect == EFFECT_FACEBOOK: + flow = Flow(count=2, transitions=yee_transitions.pulse(59, 89, 152)) + elif effect == EFFECT_TWITTER: + flow = Flow(count=2, transitions=yee_transitions.pulse(0, 172, 237)) + else: + return - try: - self._bulb.start_flow(flow, light_type=self.light_type) - except BulbException as ex: - _LOGGER.error("Unable to set effect: %s", ex) + try: + self._bulb.start_flow(flow, light_type=self.light_type) + self._effect = effect + except BulbException as ex: + _LOGGER.error("Unable to set effect: %s", ex) def turn_on(self, **kwargs) -> None: """Turn the bulb on.""" From 58b5833d64cd66401d313206595ce582125f8f9f Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 16 Dec 2019 19:45:09 +0100 Subject: [PATCH 322/677] Convert Hue to use unique ID (#30000) * Convert Hue to use unique ID * Fix normalization * Store/restore unique ID * Fix tests --- homeassistant/components/hue/__init__.py | 32 ++- homeassistant/components/hue/bridge.py | 43 ++-- homeassistant/components/hue/config_flow.py | 233 ++++++++----------- homeassistant/components/hue/manifest.json | 12 +- homeassistant/config_entries.py | 58 ++++- homeassistant/core.py | 3 + requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/hue/test_bridge.py | 103 ++++----- tests/components/hue/test_config_flow.py | 235 ++++++++------------ tests/components/hue/test_init.py | 60 ++--- tests/test_config_entries.py | 42 +++- 12 files changed, 385 insertions(+), 440 deletions(-) diff --git a/homeassistant/components/hue/__init__.py b/homeassistant/components/hue/__init__.py index 57057004479..7239efafd10 100644 --- a/homeassistant/components/hue/__init__.py +++ b/homeassistant/components/hue/__init__.py @@ -2,16 +2,14 @@ import ipaddress import logging +from aiohue.util import normalize_bridge_id import voluptuous as vol from homeassistant import config_entries, core -from homeassistant.const import CONF_FILENAME, CONF_HOST +from homeassistant.const import CONF_HOST from homeassistant.helpers import config_validation as cv, device_registry as dr -from .bridge import HueBridge, normalize_bridge_id -from .config_flow import ( # Loading the config flow file will register the flow - configured_hosts, -) +from .bridge import HueBridge from .const import DOMAIN _LOGGER = logging.getLogger(__name__) @@ -32,8 +30,6 @@ BRIDGE_CONFIG_SCHEMA = vol.Schema( { # Validate as IP address and then convert back to a string. vol.Required(CONF_HOST): vol.All(ipaddress.ip_address, cv.string), - # This is for legacy reasons and is only used for importing auth. - vol.Optional(CONF_FILENAME, default=PHUE_CONFIG_FILE): cv.string, vol.Optional( CONF_ALLOW_UNREACHABLE, default=DEFAULT_ALLOW_UNREACHABLE ): cv.boolean, @@ -65,7 +61,6 @@ async def async_setup(hass, config): hass.data[DOMAIN] = {} hass.data[DATA_CONFIGS] = {} - configured = configured_hosts(hass) # User has configured bridges if CONF_BRIDGES not in conf: @@ -73,29 +68,28 @@ async def async_setup(hass, config): bridges = conf[CONF_BRIDGES] + configured_hosts = set( + entry.data["host"] for entry in hass.config_entries.async_entries(DOMAIN) + ) + for bridge_conf in bridges: host = bridge_conf[CONF_HOST] # Store config in hass.data so the config entry can find it hass.data[DATA_CONFIGS][host] = bridge_conf - # If configured, the bridge will be set up during config entry phase - if host in configured: + if host in configured_hosts: continue - # No existing config entry found, try importing it or trigger link - # config flow if no existing auth. Because we're inside the setup of - # this component we'll have to use hass.async_add_job to avoid a - # deadlock: creating a config entry will set up the component but the - # setup would block till the entry is created! + # No existing config entry found, trigger link config flow. Because we're + # inside the setup of this component we'll have to use hass.async_add_job + # to avoid a deadlock: creating a config entry will set up the component + # but the setup would block till the entry is created! hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data={ - "host": bridge_conf[CONF_HOST], - "path": bridge_conf[CONF_FILENAME], - }, + data={"host": bridge_conf[CONF_HOST]}, ) ) diff --git a/homeassistant/components/hue/bridge.py b/homeassistant/components/hue/bridge.py index 0ed6e3a9911..58a744dd5b0 100644 --- a/homeassistant/components/hue/bridge.py +++ b/homeassistant/components/hue/bridge.py @@ -6,6 +6,7 @@ import async_timeout import slugify as unicode_slug import voluptuous as vol +from homeassistant import core from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import aiohttp_client, config_validation as cv @@ -45,8 +46,15 @@ class HueBridge: host = self.host hass = self.hass + bridge = aiohue.Bridge( + host, + username=self.config_entry.data["username"], + websession=aiohttp_client.async_get_clientsession(hass), + ) + try: - self.api = await get_bridge(hass, host, self.config_entry.data["username"]) + await authenticate_bridge(hass, bridge) + except AuthenticationRequired: # Usernames can become invalid if hub is reset or user removed. # We are going to fail the config entry setup and initiate a new @@ -63,6 +71,8 @@ class HueBridge: LOGGER.exception("Unknown error connecting with Hue bridge at %s", host) return False + self.api = bridge + hass.async_create_task( hass.config_entries.async_forward_entry_setup(self.config_entry, "light") ) @@ -175,16 +185,12 @@ class HueBridge: create_config_flow(self.hass, self.host) -async def get_bridge(hass, host, username=None): +async def authenticate_bridge(hass: core.HomeAssistant, bridge: aiohue.Bridge): """Create a bridge object and verify authentication.""" - bridge = aiohue.Bridge( - host, username=username, websession=aiohttp_client.async_get_clientsession(hass) - ) - try: with async_timeout.timeout(10): # Create username if we don't have one - if not username: + if not bridge.username: device_name = unicode_slug.slugify( hass.config.location_name, max_length=19 ) @@ -193,7 +199,6 @@ async def get_bridge(hass, host, username=None): # Initialize bridge (and validate our username) await bridge.initialize() - return bridge except (aiohue.LinkButtonNotPressed, aiohue.Unauthorized): raise AuthenticationRequired except (asyncio.TimeoutError, aiohue.RequestError): @@ -201,25 +206,3 @@ async def get_bridge(hass, host, username=None): except aiohue.AiohueException: LOGGER.exception("Unknown Hue linking error occurred") raise AuthenticationRequired - - -def normalize_bridge_id(bridge_id: str): - """Normalize a bridge identifier. - - There are three sources where we receive bridge ID from: - - ssdp/upnp: /description.xml, field root/device/serialNumber - - nupnp: "id" field - - Hue Bridge API: config.bridgeid - - The SSDP/UPNP source does not contain the middle 4 characters compared - to the other sources. In all our tests the middle 4 characters are "fffe". - """ - if len(bridge_id) == 16: - return bridge_id[0:6] + bridge_id[-6:] - - if len(bridge_id) == 12: - return bridge_id - - LOGGER.warning("Unexpected bridge id number found: %s", bridge_id) - - return bridge_id diff --git a/homeassistant/components/hue/config_flow.py b/homeassistant/components/hue/config_flow.py index 882bf5b70db..f2d7c6d1e8a 100644 --- a/homeassistant/components/hue/config_flow.py +++ b/homeassistant/components/hue/config_flow.py @@ -1,51 +1,24 @@ """Config flow to configure Philips Hue.""" import asyncio -import json -import os +from typing import Dict, Optional -from aiohue.discovery import discover_nupnp +import aiohue +from aiohue.discovery import discover_nupnp, normalize_bridge_id import async_timeout import voluptuous as vol -from homeassistant import config_entries +from homeassistant import config_entries, core from homeassistant.components.ssdp import ATTR_MANUFACTURERURL, ATTR_NAME -from homeassistant.core import callback from homeassistant.helpers import aiohttp_client -from .bridge import get_bridge, normalize_bridge_id -from .const import DOMAIN, LOGGER +from .bridge import authenticate_bridge +from .const import DOMAIN, LOGGER # pylint: disable=unused-import from .errors import AuthenticationRequired, CannotConnect HUE_MANUFACTURERURL = "http://www.philips.com" HUE_IGNORED_BRIDGE_NAMES = ["HASS Bridge", "Espalexa"] -@callback -def configured_hosts(hass): - """Return a set of the configured hosts.""" - return set( - entry.data["host"] for entry in hass.config_entries.async_entries(DOMAIN) - ) - - -def _find_username_from_config(hass, filename): - """Load username from config. - - This was a legacy way of configuring Hue until Home Assistant 0.67. - """ - path = hass.config.path(filename) - - if not os.path.isfile(path): - return None - - with open(path) as inp: - try: - return list(json.load(inp).values())[0]["username"] - except ValueError: - # If we get invalid JSON - return None - - class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Handle a Hue config flow.""" @@ -56,23 +29,45 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self): """Initialize the Hue flow.""" - self.host = None + self.bridge: Optional[aiohue.Bridge] = None + self.discovered_bridges: Optional[Dict[str, aiohue.Bridge]] = None async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" + # This is for backwards compatibility. return await self.async_step_init(user_input) + @core.callback + def _async_get_bridge(self, host: str, bridge_id: Optional[str] = None): + """Return a bridge object.""" + if bridge_id is not None: + bridge_id = normalize_bridge_id(bridge_id) + + return aiohue.Bridge( + host, + websession=aiohttp_client.async_get_clientsession(self.hass), + bridge_id=bridge_id, + ) + async def async_step_init(self, user_input=None): """Handle a flow start.""" - if user_input is not None: - self.host = self.context["host"] = user_input["host"] - return await self.async_step_link() - - websession = aiohttp_client.async_get_clientsession(self.hass) + if ( + user_input is not None + and self.discovered_bridges is not None + # pylint: disable=unsupported-membership-test + and user_input["id"] in self.discovered_bridges + ): + # pylint: disable=unsubscriptable-object + self.bridge = self.discovered_bridges[user_input["id"]] + await self.async_set_unique_id(self.bridge.id, raise_on_progress=False) + # We pass user input to link so it will attempt to link right away + return await self.async_step_link({}) try: with async_timeout.timeout(5): - bridges = await discover_nupnp(websession=websession) + bridges = await discover_nupnp( + websession=aiohttp_client.async_get_clientsession(self.hass) + ) except asyncio.TimeoutError: return self.async_abort(reason="discover_timeout") @@ -80,20 +75,28 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return self.async_abort(reason="no_bridges") # Find already configured hosts - configured = configured_hosts(self.hass) + already_configured = self._async_current_ids() + bridges = [bridge for bridge in bridges if bridge.id not in already_configured] - hosts = [bridge.host for bridge in bridges if bridge.host not in configured] - - if not hosts: + if not bridges: return self.async_abort(reason="all_configured") - if len(hosts) == 1: - self.host = hosts[0] + if len(bridges) == 1: + self.bridge = bridges[0] + await self.async_set_unique_id(self.bridge.id, raise_on_progress=False) return await self.async_step_link() + self.discovered_bridges = {bridge.id: bridge for bridge in bridges} + return self.async_show_form( step_id="init", - data_schema=vol.Schema({vol.Required("host"): vol.In(hosts)}), + data_schema=vol.Schema( + { + vol.Required("id"): vol.In( + {bridge.id: bridge.host for bridge in bridges} + ) + } + ), ) async def async_step_link(self, user_input=None): @@ -102,31 +105,39 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): Given a configured host, will ask the user to press the link button to connect to the bridge. """ + if user_input is None: + return self.async_show_form(step_id="link") + + bridge = self.bridge + assert bridge is not None errors = {} - # We will always try linking in case the user has already pressed - # the link button. try: - bridge = await get_bridge(self.hass, self.host, username=None) + await authenticate_bridge(self.hass, bridge) - return await self._entry_from_bridge(bridge) + # Can happen if we come from import. + if self.unique_id is None: + await self.async_set_unique_id( + normalize_bridge_id(bridge.id), raise_on_progress=False + ) + + return self.async_create_entry( + title=bridge.config.name, + data={"host": bridge.host, "username": bridge.username}, + ) except AuthenticationRequired: errors["base"] = "register_failed" except CannotConnect: - LOGGER.error("Error connecting to the Hue bridge at %s", self.host) + LOGGER.error("Error connecting to the Hue bridge at %s", bridge.host) errors["base"] = "linking" except Exception: # pylint: disable=broad-except LOGGER.exception( - "Unknown error connecting with Hue bridge at %s", self.host + "Unknown error connecting with Hue bridge at %s", bridge.host ) errors["base"] = "linking" - # If there was no user input, do not show the errors. - if user_input is None: - errors = {} - return self.async_show_form(step_id="link", errors=errors) async def async_step_ssdp(self, discovery_info): @@ -135,113 +146,55 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): This flow is triggered by the SSDP component. It will check if the host is already configured and delegate to the import step if not. """ + # Filter out non-Hue bridges #1 if discovery_info[ATTR_MANUFACTURERURL] != HUE_MANUFACTURERURL: return self.async_abort(reason="not_hue_bridge") + # Filter out non-Hue bridges #2 if any( name in discovery_info.get(ATTR_NAME, "") for name in HUE_IGNORED_BRIDGE_NAMES ): return self.async_abort(reason="not_hue_bridge") - host = self.context["host"] = discovery_info.get("host") + if "host" not in discovery_info or "serial" not in discovery_info: + return self.async_abort(reason="not_hue_bridge") - if any( - host == flow["context"].get("host") for flow in self._async_in_progress() - ): - return self.async_abort(reason="already_in_progress") - - if host in configured_hosts(self.hass): - return self.async_abort(reason="already_configured") - - bridge_id = discovery_info.get("serial") - - await self.async_set_unique_id(normalize_bridge_id(bridge_id)) - - return await self.async_step_import( - { - "host": host, - # This format is the legacy format that Hue used for discovery - "path": f"phue-{bridge_id}.conf", - } + bridge = self._async_get_bridge( + discovery_info["host"], discovery_info["serial"] ) + await self.async_set_unique_id(bridge.id) + self._abort_if_unique_id_configured() + self.bridge = bridge + return await self.async_step_link() + async def async_step_homekit(self, homekit_info): """Handle HomeKit discovery.""" - host = self.context["host"] = homekit_info.get("host") - - if any( - host == flow["context"].get("host") for flow in self._async_in_progress() - ): - return self.async_abort(reason="already_in_progress") - - if host in configured_hosts(self.hass): - return self.async_abort(reason="already_configured") - - await self.async_set_unique_id( - normalize_bridge_id(homekit_info["properties"]["id"].replace(":", "")) + bridge = self._async_get_bridge( + homekit_info["host"], homekit_info["properties"]["id"] ) - return await self.async_step_import({"host": host}) + await self.async_set_unique_id(bridge.id) + self._abort_if_unique_id_configured() + self.bridge = bridge + return await self.async_step_link() async def async_step_import(self, import_info): """Import a new bridge as a config entry. - Will read authentication from Phue config file if available. - This flow is triggered by `async_setup` for both configured and discovered bridges. Triggered for any bridge that does not have a config entry yet (based on host). This flow is also triggered by `async_step_discovery`. - - If an existing config file is found, we will validate the credentials - and create an entry. Otherwise we will delegate to `link` step which - will ask user to link the bridge. """ - host = self.context["host"] = import_info["host"] - path = import_info.get("path") + # Check if host exists, abort if so. + if any( + import_info["host"] == entry.data["host"] + for entry in self._async_current_entries() + ): + return self.async_abort(reason="already_configured") - if path is not None: - username = await self.hass.async_add_job( - _find_username_from_config, self.hass, self.hass.config.path(path) - ) - else: - username = None - - try: - bridge = await get_bridge(self.hass, host, username) - - LOGGER.info("Imported authentication for %s from %s", host, path) - - return await self._entry_from_bridge(bridge) - except AuthenticationRequired: - self.host = host - - LOGGER.info("Invalid authentication for %s, requesting link.", host) - - return await self.async_step_link() - - except CannotConnect: - LOGGER.error("Error connecting to the Hue bridge at %s", host) - return self.async_abort(reason="cannot_connect") - - except Exception: # pylint: disable=broad-except - LOGGER.exception("Unknown error connecting with Hue bridge at %s", host) - return self.async_abort(reason="unknown") - - async def _entry_from_bridge(self, bridge): - """Return a config entry from an initialized bridge.""" - # Remove all other entries of hubs with same ID or host - host = bridge.host - bridge_id = bridge.config.bridgeid - - if self.unique_id is None: - await self.async_set_unique_id( - normalize_bridge_id(bridge_id), raise_on_progress=False - ) - - return self.async_create_entry( - title=bridge.config.name, - data={"host": host, "bridge_id": bridge_id, "username": bridge.username}, - ) + self.bridge = self._async_get_bridge(import_info["host"]) + return await self.async_step_link() diff --git a/homeassistant/components/hue/manifest.json b/homeassistant/components/hue/manifest.json index c90b6181559..75384e012e0 100644 --- a/homeassistant/components/hue/manifest.json +++ b/homeassistant/components/hue/manifest.json @@ -3,21 +3,15 @@ "name": "Philips Hue", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/hue", - "requirements": [ - "aiohue==1.9.2" - ], + "requirements": ["aiohue==1.10.1"], "ssdp": [ { "manufacturer": "Royal Philips Electronics" } ], "homekit": { - "models": [ - "BSB002" - ] + "models": ["BSB002"] }, "dependencies": [], - "codeowners": [ - "@balloob" - ] + "codeowners": ["@balloob"] } diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 09ee186da0f..d39fc4803ea 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -75,10 +75,6 @@ class OperationNotAllowed(ConfigError): """Raised when a config entry operation is not allowed.""" -class UniqueIdInProgress(data_entry_flow.AbortFlow): - """Error to indicate that the unique Id is in progress.""" - - class ConfigEntry: """Hold a configuration entry.""" @@ -379,6 +375,7 @@ class ConfigEntry: "system_options": self.system_options.as_dict(), "source": self.source, "connection_class": self.connection_class, + "unique_id": self.unique_id, } @@ -482,6 +479,8 @@ class ConfigEntries: options=entry.get("options"), # New in 0.98 system_options=entry.get("system_options", {}), + # New in 0.104 + unique_id=entry.get("unique_id"), ) for entry in config["entries"] ] @@ -617,11 +616,20 @@ class ConfigEntries: # Check if config entry exists with unique ID. Unload it. existing_entry = None - unique_id = flow.context.get("unique_id") - if unique_id is not None: + if flow.unique_id is not None: + # Abort all flows in progress with same unique ID. + for progress_flow in self.flow.async_progress(): + if ( + progress_flow["handler"] == flow.handler + and progress_flow["flow_id"] != flow.flow_id + and progress_flow["context"].get("unique_id") == flow.unique_id + ): + self.flow.async_abort(progress_flow["flow_id"]) + + # Find existing entry. for check_entry in self.async_entries(result["handler"]): - if check_entry.unique_id == unique_id: + if check_entry.unique_id == flow.unique_id: existing_entry = check_entry break @@ -643,16 +651,17 @@ class ConfigEntries: system_options={}, source=flow.context["source"], connection_class=flow.CONNECTION_CLASS, - unique_id=unique_id, + unique_id=flow.unique_id, ) self._entries.append(entry) - self._async_schedule_save() await self.async_setup(entry.entry_id) if existing_entry is not None: await self.async_remove(existing_entry.entry_id) + self._async_schedule_save() + result["result"] = entry return result @@ -723,8 +732,6 @@ async def _old_conf_migrator(old_config: Dict[str, Any]) -> Dict[str, Any]: class ConfigFlow(data_entry_flow.FlowHandler): """Base class for config flows with some helpers.""" - unique_id = None - def __init_subclass__(cls, domain: Optional[str] = None, **kwargs: Any) -> None: """Initialize a subclass, register if possible.""" super().__init_subclass__(**kwargs) # type: ignore @@ -733,12 +740,30 @@ class ConfigFlow(data_entry_flow.FlowHandler): CONNECTION_CLASS = CONN_CLASS_UNKNOWN + @property + def unique_id(self) -> Optional[str]: + """Return unique ID if available.""" + # pylint: disable=no-member + if not self.context: + return None + + return cast(Optional[str], self.context.get("unique_id")) + @staticmethod @callback def async_get_options_flow(config_entry: ConfigEntry) -> "OptionsFlow": """Get the options flow for this handler.""" raise data_entry_flow.UnknownHandler + @callback + def _abort_if_unique_id_configured(self) -> None: + """Abort if the unique ID is already configured.""" + if self.unique_id is None: + return + + if self.unique_id in self._async_current_ids(): + raise data_entry_flow.AbortFlow("already_configured") + async def async_set_unique_id( self, unique_id: str, *, raise_on_progress: bool = True ) -> Optional[ConfigEntry]: @@ -749,7 +774,7 @@ class ConfigFlow(data_entry_flow.FlowHandler): if raise_on_progress: for progress in self._async_in_progress(): if progress["context"].get("unique_id") == unique_id: - raise UniqueIdInProgress("already_in_progress") + raise data_entry_flow.AbortFlow("already_in_progress") # pylint: disable=no-member self.context["unique_id"] = unique_id @@ -766,6 +791,15 @@ class ConfigFlow(data_entry_flow.FlowHandler): assert self.hass is not None return self.hass.config_entries.async_entries(self.handler) + @callback + def _async_current_ids(self) -> Set[Optional[str]]: + """Return current unique IDs.""" + assert self.hass is not None + return set( + entry.unique_id + for entry in self.hass.config_entries.async_entries(self.handler) + ) + @callback def _async_in_progress(self) -> List[Dict]: """Return other in progress flows for current domain.""" diff --git a/homeassistant/core.py b/homeassistant/core.py index 0002019fdfa..e76673f5727 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -1134,6 +1134,9 @@ class ServiceRegistry: self._services[domain].pop(service) + if not self._services[domain]: + self._services.pop(domain) + self._hass.bus.async_fire( EVENT_SERVICE_REMOVED, {ATTR_DOMAIN: domain, ATTR_SERVICE: service} ) diff --git a/requirements_all.txt b/requirements_all.txt index a8d454aac99..7984cd4a32a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -163,7 +163,7 @@ aioharmony==0.1.13 aiohttp_cors==0.7.0 # homeassistant.components.hue -aiohue==1.9.2 +aiohue==1.10.1 # homeassistant.components.imap aioimaplib==0.7.15 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 074d5bd6c8e..20f8e8efc76 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -63,7 +63,7 @@ aioesphomeapi==2.6.1 aiohttp_cors==0.7.0 # homeassistant.components.hue -aiohue==1.9.2 +aiohue==1.10.1 # homeassistant.components.notion aionotion==1.1.0 diff --git a/tests/components/hue/test_bridge.py b/tests/components/hue/test_bridge.py index b66733e7c76..03966560d8d 100644 --- a/tests/components/hue/test_bridge.py +++ b/tests/components/hue/test_bridge.py @@ -9,107 +9,110 @@ from homeassistant.exceptions import ConfigEntryNotReady from tests.common import mock_coro -async def test_bridge_setup(): +async def test_bridge_setup(hass): """Test a successful setup.""" - hass = Mock() entry = Mock() - api = Mock() + api = Mock(initialize=mock_coro) entry.data = {"host": "1.2.3.4", "username": "mock-username"} hue_bridge = bridge.HueBridge(hass, entry, False, False) - with patch.object(bridge, "get_bridge", return_value=mock_coro(api)): + with patch("aiohue.Bridge", return_value=api), patch.object( + hass.config_entries, "async_forward_entry_setup" + ) as mock_forward: assert await hue_bridge.async_setup() is True assert hue_bridge.api is api - forward_entries = set( - c[1][1] for c in hass.config_entries.async_forward_entry_setup.mock_calls - ) - assert len(hass.config_entries.async_forward_entry_setup.mock_calls) == 3 + assert len(mock_forward.mock_calls) == 3 + forward_entries = set(c[1][1] for c in mock_forward.mock_calls) assert forward_entries == set(["light", "binary_sensor", "sensor"]) -async def test_bridge_setup_invalid_username(): +async def test_bridge_setup_invalid_username(hass): """Test we start config flow if username is no longer whitelisted.""" - hass = Mock() - entry = Mock() - entry.data = {"host": "1.2.3.4", "username": "mock-username"} - hue_bridge = bridge.HueBridge(hass, entry, False, False) - - with patch.object(bridge, "get_bridge", side_effect=errors.AuthenticationRequired): - assert await hue_bridge.async_setup() is False - - assert len(hass.async_create_task.mock_calls) == 1 - assert len(hass.config_entries.flow.async_init.mock_calls) == 1 - assert hass.config_entries.flow.async_init.mock_calls[0][2]["data"] == { - "host": "1.2.3.4" - } - - -async def test_bridge_setup_timeout(hass): - """Test we retry to connect if we cannot connect.""" - hass = Mock() entry = Mock() entry.data = {"host": "1.2.3.4", "username": "mock-username"} hue_bridge = bridge.HueBridge(hass, entry, False, False) with patch.object( - bridge, "get_bridge", side_effect=errors.CannotConnect + bridge, "authenticate_bridge", side_effect=errors.AuthenticationRequired + ), patch.object( + hass.config_entries.flow, "async_init", return_value=mock_coro() + ) as mock_init: + assert await hue_bridge.async_setup() is False + + assert len(mock_init.mock_calls) == 1 + assert mock_init.mock_calls[0][2]["data"] == {"host": "1.2.3.4"} + + +async def test_bridge_setup_timeout(hass): + """Test we retry to connect if we cannot connect.""" + entry = Mock() + entry.data = {"host": "1.2.3.4", "username": "mock-username"} + hue_bridge = bridge.HueBridge(hass, entry, False, False) + + with patch.object( + bridge, "authenticate_bridge", side_effect=errors.CannotConnect ), pytest.raises(ConfigEntryNotReady): await hue_bridge.async_setup() -async def test_reset_if_entry_had_wrong_auth(): +async def test_reset_if_entry_had_wrong_auth(hass): """Test calling reset when the entry contained wrong auth.""" - hass = Mock() entry = Mock() entry.data = {"host": "1.2.3.4", "username": "mock-username"} hue_bridge = bridge.HueBridge(hass, entry, False, False) - with patch.object(bridge, "get_bridge", side_effect=errors.AuthenticationRequired): + with patch.object( + bridge, "authenticate_bridge", side_effect=errors.AuthenticationRequired + ), patch.object(bridge, "create_config_flow") as mock_create: assert await hue_bridge.async_setup() is False - assert len(hass.async_create_task.mock_calls) == 1 + assert len(mock_create.mock_calls) == 1 assert await hue_bridge.async_reset() -async def test_reset_unloads_entry_if_setup(): +async def test_reset_unloads_entry_if_setup(hass): """Test calling reset while the entry has been setup.""" - hass = Mock() entry = Mock() entry.data = {"host": "1.2.3.4", "username": "mock-username"} hue_bridge = bridge.HueBridge(hass, entry, False, False) - with patch.object(bridge, "get_bridge", return_value=mock_coro(Mock())): + with patch.object( + bridge, "authenticate_bridge", return_value=mock_coro(Mock()) + ), patch("aiohue.Bridge", return_value=Mock()), patch.object( + hass.config_entries, "async_forward_entry_setup" + ) as mock_forward: assert await hue_bridge.async_setup() is True - assert len(hass.services.async_register.mock_calls) == 1 - assert len(hass.config_entries.async_forward_entry_setup.mock_calls) == 3 + assert len(hass.services.async_services()) == 1 + assert len(mock_forward.mock_calls) == 3 - hass.config_entries.async_forward_entry_unload.return_value = mock_coro(True) - assert await hue_bridge.async_reset() + with patch.object( + hass.config_entries, "async_forward_entry_unload", return_value=mock_coro(True) + ) as mock_forward: + assert await hue_bridge.async_reset() - assert len(hass.config_entries.async_forward_entry_unload.mock_calls) == 3 - assert len(hass.services.async_remove.mock_calls) == 1 + assert len(mock_forward.mock_calls) == 3 + assert len(hass.services.async_services()) == 0 -async def test_handle_unauthorized(): +async def test_handle_unauthorized(hass): """Test handling an unauthorized error on update.""" - hass = Mock() entry = Mock() entry.data = {"host": "1.2.3.4", "username": "mock-username"} hue_bridge = bridge.HueBridge(hass, entry, False, False) - with patch.object(bridge, "get_bridge", return_value=mock_coro(Mock())): + with patch.object( + bridge, "authenticate_bridge", return_value=mock_coro(Mock()) + ), patch("aiohue.Bridge", return_value=Mock()): assert await hue_bridge.async_setup() is True assert hue_bridge.authorized is True - await hue_bridge.handle_unauthorized_error() + with patch.object(bridge, "create_config_flow") as mock_create: + await hue_bridge.handle_unauthorized_error() assert hue_bridge.authorized is False - assert len(hass.async_create_task.mock_calls) == 4 - assert len(hass.config_entries.flow.async_init.mock_calls) == 1 - assert hass.config_entries.flow.async_init.mock_calls[0][2]["data"] == { - "host": "1.2.3.4" - } + assert len(mock_create.mock_calls) == 1 + assert mock_create.mock_calls[0][1][1] == "1.2.3.4" diff --git a/tests/components/hue/test_config_flow.py b/tests/components/hue/test_config_flow.py index 030f6ade1fa..fe9a1f0e32c 100644 --- a/tests/components/hue/test_config_flow.py +++ b/tests/components/hue/test_config_flow.py @@ -6,50 +6,52 @@ import aiohue import pytest import voluptuous as vol -from homeassistant.components.hue import config_flow, const, errors +from homeassistant import data_entry_flow +from homeassistant.components.hue import config_flow, const from tests.common import MockConfigEntry, mock_coro -async def test_flow_works(hass, aioclient_mock): +async def test_flow_works(hass): """Test config flow .""" - aioclient_mock.get( - const.API_NUPNP, json=[{"internalipaddress": "1.2.3.4", "id": "bla"}] - ) + mock_bridge = Mock() + mock_bridge.host = "1.2.3.4" + mock_bridge.username = None + mock_bridge.config.name = "Mock Bridge" + mock_bridge.id = "aabbccddeeff" + + async def mock_create_user(username): + mock_bridge.username = username + + mock_bridge.create_user = mock_create_user + mock_bridge.initialize.return_value = mock_coro() flow = config_flow.HueFlowHandler() flow.hass = hass flow.context = {} - await flow.async_step_init() - with patch("aiohue.Bridge") as mock_bridge: + with patch( + "homeassistant.components.hue.config_flow.discover_nupnp", + return_value=mock_coro([mock_bridge]), + ): + result = await flow.async_step_init() - def mock_constructor(host, websession, username=None): - """Fake the bridge constructor.""" - mock_bridge.host = host - return mock_bridge + assert result["type"] == "form" + assert result["step_id"] == "link" - mock_bridge.side_effect = mock_constructor - mock_bridge.username = "username-abc" - mock_bridge.config.name = "Mock Bridge" - mock_bridge.config.bridgeid = "bridge-id-1234" - mock_bridge.create_user.return_value = mock_coro() - mock_bridge.initialize.return_value = mock_coro() + assert flow.context["unique_id"] == "aabbccddeeff" - result = await flow.async_step_link(user_input={}) - - assert mock_bridge.host == "1.2.3.4" - assert len(mock_bridge.create_user.mock_calls) == 1 - assert len(mock_bridge.initialize.mock_calls) == 1 + result = await flow.async_step_link(user_input={}) assert result["type"] == "create_entry" assert result["title"] == "Mock Bridge" assert result["data"] == { "host": "1.2.3.4", - "bridge_id": "bridge-id-1234", - "username": "username-abc", + "username": "home-assistant#test-home", } + assert len(mock_bridge.initialize.mock_calls) == 1 + async def test_flow_no_discovered_bridges(hass, aioclient_mock): """Test config flow discovers no bridges.""" @@ -66,9 +68,12 @@ async def test_flow_all_discovered_bridges_exist(hass, aioclient_mock): aioclient_mock.get( const.API_NUPNP, json=[{"internalipaddress": "1.2.3.4", "id": "bla"}] ) - MockConfigEntry(domain="hue", data={"host": "1.2.3.4"}).add_to_hass(hass) + MockConfigEntry( + domain="hue", unique_id="bla", data={"host": "1.2.3.4"} + ).add_to_hass(hass) flow = config_flow.HueFlowHandler() flow.hass = hass + flow.context = {} result = await flow.async_step_init() assert result["type"] == "abort" @@ -81,6 +86,7 @@ async def test_flow_one_bridge_discovered(hass, aioclient_mock): ) flow = config_flow.HueFlowHandler() flow.hass = hass + flow.context = {} result = await flow.async_step_init() assert result["type"] == "form" @@ -104,10 +110,10 @@ async def test_flow_two_bridges_discovered(hass, aioclient_mock): assert result["step_id"] == "init" with pytest.raises(vol.Invalid): - assert result["data_schema"]({"host": "0.0.0.0"}) + assert result["data_schema"]({"id": "not-discovered"}) - result["data_schema"]({"host": "1.2.3.4"}) - result["data_schema"]({"host": "5.6.7.8"}) + result["data_schema"]({"id": "bla"}) + result["data_schema"]({"id": "beer"}) async def test_flow_two_bridges_discovered_one_new(hass, aioclient_mock): @@ -119,14 +125,17 @@ async def test_flow_two_bridges_discovered_one_new(hass, aioclient_mock): {"internalipaddress": "5.6.7.8", "id": "beer"}, ], ) - MockConfigEntry(domain="hue", data={"host": "1.2.3.4"}).add_to_hass(hass) + MockConfigEntry( + domain="hue", unique_id="bla", data={"host": "1.2.3.4"} + ).add_to_hass(hass) flow = config_flow.HueFlowHandler() flow.hass = hass + flow.context = {} result = await flow.async_step_init() assert result["type"] == "form" assert result["step_id"] == "link" - assert flow.host == "5.6.7.8" + assert flow.bridge.host == "5.6.7.8" async def test_flow_timeout_discovery(hass): @@ -147,6 +156,7 @@ async def test_flow_link_timeout(hass): """Test config flow .""" flow = config_flow.HueFlowHandler() flow.hass = hass + flow.bridge = Mock() with patch("aiohue.Bridge.create_user", side_effect=asyncio.TimeoutError): result = await flow.async_step_link({}) @@ -160,9 +170,11 @@ async def test_flow_link_button_not_pressed(hass): """Test config flow .""" flow = config_flow.HueFlowHandler() flow.hass = hass + flow.bridge = Mock( + username=None, create_user=Mock(side_effect=aiohue.LinkButtonNotPressed) + ) - with patch("aiohue.Bridge.create_user", side_effect=aiohue.LinkButtonNotPressed): - result = await flow.async_step_link({}) + result = await flow.async_step_link({}) assert result["type"] == "form" assert result["step_id"] == "link" @@ -173,6 +185,7 @@ async def test_flow_link_unknown_host(hass): """Test config flow .""" flow = config_flow.HueFlowHandler() flow.hass = hass + flow.bridge = Mock() with patch("aiohue.Bridge.create_user", side_effect=aiohue.RequestError): result = await flow.async_step_link({}) @@ -188,16 +201,13 @@ async def test_bridge_ssdp(hass): flow.hass = hass flow.context = {} - with patch.object( - config_flow, "get_bridge", side_effect=errors.AuthenticationRequired - ): - result = await flow.async_step_ssdp( - { - "host": "0.0.0.0", - "serial": "1234", - "manufacturerURL": config_flow.HUE_MANUFACTURERURL, - } - ) + result = await flow.async_step_ssdp( + { + "host": "0.0.0.0", + "serial": "1234", + "manufacturerURL": config_flow.HUE_MANUFACTURERURL, + } + ) assert result["type"] == "form" assert result["step_id"] == "link" @@ -255,47 +265,22 @@ async def test_bridge_ssdp_espalexa(hass): async def test_bridge_ssdp_already_configured(hass): """Test if a discovered bridge has already been configured.""" - MockConfigEntry(domain="hue", data={"host": "0.0.0.0"}).add_to_hass(hass) + MockConfigEntry( + domain="hue", unique_id="1234", data={"host": "0.0.0.0"} + ).add_to_hass(hass) flow = config_flow.HueFlowHandler() flow.hass = hass flow.context = {} - result = await flow.async_step_ssdp( - { - "host": "0.0.0.0", - "serial": "1234", - "manufacturerURL": config_flow.HUE_MANUFACTURERURL, - } - ) - - assert result["type"] == "abort" - - -async def test_import_with_existing_config(hass): - """Test importing a host with an existing config file.""" - flow = config_flow.HueFlowHandler() - flow.hass = hass - flow.context = {} - - bridge = Mock() - bridge.username = "username-abc" - bridge.config.bridgeid = "bridge-id-1234" - bridge.config.name = "Mock Bridge" - bridge.host = "0.0.0.0" - - with patch.object( - config_flow, "_find_username_from_config", return_value="mock-user" - ), patch.object(config_flow, "get_bridge", return_value=mock_coro(bridge)): - result = await flow.async_step_import({"host": "0.0.0.0", "path": "bla.conf"}) - - assert result["type"] == "create_entry" - assert result["title"] == "Mock Bridge" - assert result["data"] == { - "host": "0.0.0.0", - "bridge_id": "bridge-id-1234", - "username": "username-abc", - } + with pytest.raises(data_entry_flow.AbortFlow): + await flow.async_step_ssdp( + { + "host": "0.0.0.0", + "serial": "1234", + "manufacturerURL": config_flow.HUE_MANUFACTURERURL, + } + ) async def test_import_with_no_config(hass): @@ -304,45 +289,12 @@ async def test_import_with_no_config(hass): flow.hass = hass flow.context = {} - with patch.object( - config_flow, "get_bridge", side_effect=errors.AuthenticationRequired - ): - result = await flow.async_step_import({"host": "0.0.0.0"}) + result = await flow.async_step_import({"host": "0.0.0.0"}) assert result["type"] == "form" assert result["step_id"] == "link" -async def test_import_with_existing_but_invalid_config(hass): - """Test importing a host with a config file with invalid username.""" - flow = config_flow.HueFlowHandler() - flow.hass = hass - flow.context = {} - - with patch.object( - config_flow, "_find_username_from_config", return_value="mock-user" - ), patch.object( - config_flow, "get_bridge", side_effect=errors.AuthenticationRequired - ): - result = await flow.async_step_import({"host": "0.0.0.0", "path": "bla.conf"}) - - assert result["type"] == "form" - assert result["step_id"] == "link" - - -async def test_import_cannot_connect(hass): - """Test importing a host that we cannot conncet to.""" - flow = config_flow.HueFlowHandler() - flow.hass = hass - flow.context = {} - - with patch.object(config_flow, "get_bridge", side_effect=errors.CannotConnect): - result = await flow.async_step_import({"host": "0.0.0.0"}) - - assert result["type"] == "abort" - assert result["reason"] == "cannot_connect" - - async def test_creating_entry_removes_entries_for_same_host_or_bridge(hass): """Test that we clean up entries for same host and bridge. @@ -351,38 +303,45 @@ async def test_creating_entry_removes_entries_for_same_host_or_bridge(hass): all existing entries that either have same IP or same bridge_id. """ orig_entry = MockConfigEntry( - domain="hue", - data={"host": "0.0.0.0", "bridge_id": "id-1234"}, - unique_id="id-1234", + domain="hue", data={"host": "0.0.0.0", "username": "aaaa"}, unique_id="id-1234", ) orig_entry.add_to_hass(hass) MockConfigEntry( - domain="hue", - data={"host": "1.2.3.4", "bridge_id": "id-5678"}, - unique_id="id-5678", + domain="hue", data={"host": "1.2.3.4", "username": "bbbb"}, unique_id="id-5678", ).add_to_hass(hass) assert len(hass.config_entries.async_entries("hue")) == 2 bridge = Mock() bridge.username = "username-abc" - bridge.config.bridgeid = "id-1234" bridge.config.name = "Mock Bridge" bridge.host = "0.0.0.0" + bridge.id = "id-1234" - with patch.object( - config_flow, "_find_username_from_config", return_value="mock-user" - ), patch.object(config_flow, "get_bridge", return_value=mock_coro(bridge)): + with patch( + "aiohue.Bridge", return_value=bridge, + ): result = await hass.config_entries.flow.async_init( "hue", data={"host": "2.2.2.2"}, context={"source": "import"} ) + assert result["type"] == "form" + assert result["step_id"] == "link" + + with patch( + "homeassistant.components.hue.config_flow.authenticate_bridge", + return_value=mock_coro(), + ), patch( + "homeassistant.components.hue.async_setup_entry", + side_effect=lambda _, _2: mock_coro(True), + ): + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + assert result["type"] == "create_entry" assert result["title"] == "Mock Bridge" assert result["data"] == { "host": "0.0.0.0", - "bridge_id": "id-1234", "username": "username-abc", } entries = hass.config_entries.async_entries("hue") @@ -398,17 +357,14 @@ async def test_bridge_homekit(hass): flow.hass = hass flow.context = {} - with patch.object( - config_flow, "get_bridge", side_effect=errors.AuthenticationRequired - ): - result = await flow.async_step_homekit( - { - "host": "0.0.0.0", - "serial": "1234", - "manufacturerURL": config_flow.HUE_MANUFACTURERURL, - "properties": {"id": "aa:bb:cc:dd:ee:ff"}, - } - ) + result = await flow.async_step_homekit( + { + "host": "0.0.0.0", + "serial": "1234", + "manufacturerURL": config_flow.HUE_MANUFACTURERURL, + "properties": {"id": "aa:bb:cc:dd:ee:ff"}, + } + ) assert result["type"] == "form" assert result["step_id"] == "link" @@ -416,12 +372,15 @@ async def test_bridge_homekit(hass): async def test_bridge_homekit_already_configured(hass): """Test if a HomeKit discovered bridge has already been configured.""" - MockConfigEntry(domain="hue", data={"host": "0.0.0.0"}).add_to_hass(hass) + MockConfigEntry( + domain="hue", unique_id="aabbccddeeff", data={"host": "0.0.0.0"} + ).add_to_hass(hass) flow = config_flow.HueFlowHandler() flow.hass = hass flow.context = {} - result = await flow.async_step_homekit({"host": "0.0.0.0"}) - - assert result["type"] == "abort" + with pytest.raises(data_entry_flow.AbortFlow): + await flow.async_step_homekit( + {"host": "0.0.0.0", "properties": {"id": "aa:bb:cc:dd:ee:ff"}} + ) diff --git a/tests/components/hue/test_init.py b/tests/components/hue/test_init.py index d064ff9f340..b48d66990e8 100644 --- a/tests/components/hue/test_init.py +++ b/tests/components/hue/test_init.py @@ -9,13 +9,10 @@ from tests.common import MockConfigEntry, mock_coro async def test_setup_with_no_config(hass): """Test that we do not discover anything or try to set up a bridge.""" - with patch.object(hass, "config_entries") as mock_config_entries, patch.object( - hue, "configured_hosts", return_value=[] - ): - assert await async_setup_component(hass, hue.DOMAIN, {}) is True + assert await async_setup_component(hass, hue.DOMAIN, {}) is True # No flows started - assert len(mock_config_entries.flow.mock_calls) == 0 + assert len(hass.config_entries.flow.async_progress()) == 0 # No configs stored assert hass.data[hue.DOMAIN] == {} @@ -23,9 +20,9 @@ async def test_setup_with_no_config(hass): async def test_setup_defined_hosts_known_auth(hass): """Test we don't initiate a config entry if config bridge is known.""" - with patch.object(hass, "config_entries") as mock_config_entries, patch.object( - hue, "configured_hosts", return_value=["0.0.0.0"] - ): + MockConfigEntry(domain="hue", data={"host": "0.0.0.0"}).add_to_hass(hass) + + with patch.object(hue, "async_setup_entry", return_value=mock_coro(True)): assert ( await async_setup_component( hass, @@ -34,7 +31,6 @@ async def test_setup_defined_hosts_known_auth(hass): hue.DOMAIN: { hue.CONF_BRIDGES: { hue.CONF_HOST: "0.0.0.0", - hue.CONF_FILENAME: "bla.conf", hue.CONF_ALLOW_HUE_GROUPS: False, hue.CONF_ALLOW_UNREACHABLE: True, } @@ -45,13 +41,12 @@ async def test_setup_defined_hosts_known_auth(hass): ) # Flow started for discovered bridge - assert len(mock_config_entries.flow.mock_calls) == 0 + assert len(hass.config_entries.flow.async_progress()) == 0 # Config stored for domain. assert hass.data[hue.DATA_CONFIGS] == { "0.0.0.0": { hue.CONF_HOST: "0.0.0.0", - hue.CONF_FILENAME: "bla.conf", hue.CONF_ALLOW_HUE_GROUPS: False, hue.CONF_ALLOW_UNREACHABLE: True, } @@ -60,40 +55,30 @@ async def test_setup_defined_hosts_known_auth(hass): async def test_setup_defined_hosts_no_known_auth(hass): """Test we initiate config entry if config bridge is not known.""" - with patch.object(hass, "config_entries") as mock_config_entries, patch.object( - hue, "configured_hosts", return_value=[] - ): - mock_config_entries.flow.async_init.return_value = mock_coro() - assert ( - await async_setup_component( - hass, - hue.DOMAIN, - { - hue.DOMAIN: { - hue.CONF_BRIDGES: { - hue.CONF_HOST: "0.0.0.0", - hue.CONF_FILENAME: "bla.conf", - hue.CONF_ALLOW_HUE_GROUPS: False, - hue.CONF_ALLOW_UNREACHABLE: True, - } + assert ( + await async_setup_component( + hass, + hue.DOMAIN, + { + hue.DOMAIN: { + hue.CONF_BRIDGES: { + hue.CONF_HOST: "0.0.0.0", + hue.CONF_ALLOW_HUE_GROUPS: False, + hue.CONF_ALLOW_UNREACHABLE: True, } - }, - ) - is True + } + }, ) + is True + ) # Flow started for discovered bridge - assert len(mock_config_entries.flow.mock_calls) == 1 - assert mock_config_entries.flow.mock_calls[0][2]["data"] == { - "host": "0.0.0.0", - "path": "bla.conf", - } + assert len(hass.config_entries.flow.async_progress()) == 1 # Config stored for domain. assert hass.data[hue.DATA_CONFIGS] == { "0.0.0.0": { hue.CONF_HOST: "0.0.0.0", - hue.CONF_FILENAME: "bla.conf", hue.CONF_ALLOW_HUE_GROUPS: False, hue.CONF_ALLOW_UNREACHABLE: True, } @@ -126,7 +111,6 @@ async def test_config_passed_to_config_entry(hass): hue.DOMAIN: { hue.CONF_BRIDGES: { hue.CONF_HOST: "0.0.0.0", - hue.CONF_FILENAME: "bla.conf", hue.CONF_ALLOW_HUE_GROUPS: False, hue.CONF_ALLOW_UNREACHABLE: True, } @@ -166,7 +150,7 @@ async def test_unload_entry(hass): return_value=mock_coro(Mock()), ): mock_bridge.return_value.async_setup.return_value = mock_coro(True) - mock_bridge.return_value.api.config = Mock() + mock_bridge.return_value.api.config = Mock(bridgeid="aabbccddeeff") assert await async_setup_component(hass, hue.DOMAIN, {}) is True assert len(mock_bridge.return_value.mock_calls) == 1 diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index a9ae4eb59ac..d83de450227 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -434,8 +434,8 @@ async def test_saving_and_loading(hass): VERSION = 5 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL - @asyncio.coroutine - def async_step_user(self, user_input=None): + async def async_step_user(self, user_input=None): + await self.async_set_unique_id("unique") return self.async_create_entry(title="Test Title", data={"token": "abcd"}) with patch.dict(config_entries.HANDLERS, {"test": TestFlow}): @@ -477,6 +477,7 @@ async def test_saving_and_loading(hass): assert orig.data == loaded.data assert orig.source == loaded.source assert orig.connection_class == loaded.connection_class + assert orig.unique_id == loaded.unique_id async def test_forward_entry_sets_up_component(hass): @@ -1108,3 +1109,40 @@ async def test_unique_id_in_progress(hass, manager): assert result2["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result2["reason"] == "already_in_progress" + + +async def test_finish_flow_aborts_progress(hass, manager): + """Test that when finishing a flow, we abort other flows in progress with unique ID.""" + mock_integration( + hass, + MockModule("comp", async_setup_entry=MagicMock(return_value=mock_coro(True))), + ) + mock_entity_platform(hass, "config_flow.comp", None) + + class TestFlow(config_entries.ConfigFlow): + + VERSION = 1 + + async def async_step_user(self, user_input=None): + await self.async_set_unique_id("mock-unique-id", raise_on_progress=False) + + if user_input is None: + return self.async_show_form(step_id="discovery") + + return self.async_create_entry(title="yo", data={}) + + with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}): + # Create one to be in progress + result = await manager.flow.async_init( + "comp", context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + + # Will finish and cancel other one. + result2 = await manager.flow.async_init( + "comp", context={"source": config_entries.SOURCE_USER}, data={} + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + + assert len(hass.config_entries.flow.async_progress()) == 0 From c16fae2c0b7a36099d900bcdcba044bef3e427cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Z=C3=A1hradn=C3=ADk?= Date: Mon, 16 Dec 2019 20:07:46 +0100 Subject: [PATCH 323/677] Fix modbus service description (#30005) --- homeassistant/components/modbus/services.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/modbus/services.yaml b/homeassistant/components/modbus/services.yaml index 8713257b47c..2158528814f 100644 --- a/homeassistant/components/modbus/services.yaml +++ b/homeassistant/components/modbus/services.yaml @@ -1,7 +1,7 @@ write_coil: description: Write to a modbus coil. fields: - address: {description: Address of the register to read., example: 0} + address: {description: Address of the register to write to., example: 0} state: {description: State to write., example: false} unit: {description: Address of the modbus unit., example: 21} write_register: From 0439d6964c7adf56ae32e019de08d64541631b04 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 16 Dec 2019 20:16:23 +0100 Subject: [PATCH 324/677] Fix persistent setup error notification content (#29995) * Fix persistent setup error notification content * Use documentation from manifest, enriched error messages * Fix issue caught by mypy --- homeassistant/config.py | 43 ++++++++++++++++++++++++----------------- homeassistant/loader.py | 5 +++++ homeassistant/setup.py | 12 ++++++------ 3 files changed, 36 insertions(+), 24 deletions(-) diff --git a/homeassistant/config.py b/homeassistant/config.py index 353a717778b..c3a97a1184c 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -60,7 +60,6 @@ _LOGGER = logging.getLogger(__name__) DATA_PERSISTENT_ERRORS = "bootstrap_persistent_errors" RE_YAML_ERROR = re.compile(r"homeassistant\.util\.yaml") RE_ASCII = re.compile(r"\033\[[^m]*m") -HA_COMPONENT_URL = "[{}](https://home-assistant.io/integrations/{}/)" YAML_CONFIG_FILE = "configuration.yaml" VERSION_FILE = ".HA_VERSION" CONFIG_DIR_NAME = ".homeassistant" @@ -412,19 +411,25 @@ def process_ha_config_upgrade(hass: HomeAssistant) -> None: @callback def async_log_exception( - ex: Exception, domain: str, config: Dict, hass: HomeAssistant + ex: Exception, + domain: str, + config: Dict, + hass: HomeAssistant, + link: Optional[str] = None, ) -> None: """Log an error for configuration validation. This method must be run in the event loop. """ if hass is not None: - async_notify_setup_error(hass, domain, True) - _LOGGER.error(_format_config_error(ex, domain, config)) + async_notify_setup_error(hass, domain, link) + _LOGGER.error(_format_config_error(ex, domain, config, link)) @callback -def _format_config_error(ex: Exception, domain: str, config: Dict) -> str: +def _format_config_error( + ex: Exception, domain: str, config: Dict, link: Optional[str] = None +) -> str: """Generate log exception for configuration validation. This method must be run in the event loop. @@ -455,12 +460,8 @@ def _format_config_error(ex: Exception, domain: str, config: Dict) -> str: getattr(domain_config, "__line__", "?"), ) - if domain != CONF_CORE: - integration = domain.split(".")[-1] - message += ( - "Please check the docs at " - f"https://home-assistant.io/integrations/{integration}/" - ) + if domain != CONF_CORE and link: + message += f"Please check the docs at {link}" return message @@ -717,7 +718,7 @@ async def async_process_component_config( hass, config ) except (vol.Invalid, HomeAssistantError) as ex: - async_log_exception(ex, domain, config, hass) + async_log_exception(ex, domain, config, hass, integration.documentation) return None # No custom config validator, proceed with schema validation @@ -725,7 +726,7 @@ async def async_process_component_config( try: return component.CONFIG_SCHEMA(config) # type: ignore except vol.Invalid as ex: - async_log_exception(ex, domain, config, hass) + async_log_exception(ex, domain, config, hass, integration.documentation) return None component_platform_schema = getattr( @@ -741,7 +742,7 @@ async def async_process_component_config( try: p_validated = component_platform_schema(p_config) except vol.Invalid as ex: - async_log_exception(ex, domain, p_config, hass) + async_log_exception(ex, domain, p_config, hass, integration.documentation) continue # Not all platform components follow same pattern for platforms @@ -770,7 +771,13 @@ async def async_process_component_config( p_config ) except vol.Invalid as ex: - async_log_exception(ex, f"{domain}.{p_name}", p_config, hass) + async_log_exception( + ex, + f"{domain}.{p_name}", + p_config, + hass, + p_integration.documentation, + ) continue platforms.append(p_validated) @@ -806,7 +813,7 @@ async def async_check_ha_config_file(hass: HomeAssistant) -> Optional[str]: @callback def async_notify_setup_error( - hass: HomeAssistant, component: str, display_link: bool = False + hass: HomeAssistant, component: str, display_link: Optional[str] = None ) -> None: """Print a persistent notification. @@ -821,11 +828,11 @@ def async_notify_setup_error( errors[component] = errors.get(component) or display_link - message = "The following components and platforms could not be set up:\n\n" + message = "The following integrations and platforms could not be set up:\n\n" for name, link in errors.items(): if link: - part = HA_COMPONENT_URL.format(name.replace("_", "-"), name) + part = f"[{name}]({link})" else: part = name diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 9e8ea9fc7ca..de05c944aaf 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -234,6 +234,11 @@ class Integration: """Return config_flow.""" return cast(bool, self.manifest.get("config_flow", False)) + @property + def documentation(self) -> Optional[str]: + """Return documentation.""" + return cast(str, self.manifest.get("documentation")) + @property def is_built_in(self) -> bool: """Test if package is a built-in integration.""" diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 01b13629e7b..2424f5fc465 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -92,7 +92,7 @@ async def _async_setup_component( This method is a coroutine. """ - def log_error(msg: str, link: bool = True) -> None: + def log_error(msg: str, link: Optional[str] = None) -> None: """Log helper.""" _LOGGER.error("Setup failed for %s: %s", domain, msg) async_notify_setup_error(hass, domain, link) @@ -100,7 +100,7 @@ async def _async_setup_component( try: integration = await loader.async_get_integration(hass, domain) except loader.IntegrationNotFound: - log_error("Integration not found.", False) + log_error("Integration not found.") return False # Validate all dependencies exist and there are no circular dependencies @@ -127,7 +127,7 @@ async def _async_setup_component( try: await async_process_deps_reqs(hass, config, integration) except HomeAssistantError as err: - log_error(str(err)) + log_error(str(err), integration.documentation) return False # Some integrations fail on import because they call functions incorrectly. @@ -135,7 +135,7 @@ async def _async_setup_component( try: component = integration.get_component() except ImportError: - log_error("Unable to import component", False) + log_error("Unable to import component", integration.documentation) return False except Exception: # pylint: disable=broad-except _LOGGER.exception("Setup failed for %s: unknown error", domain) @@ -146,7 +146,7 @@ async def _async_setup_component( ) if processed_config is None: - log_error("Invalid config.") + log_error("Invalid config.", integration.documentation) return False start = timer() @@ -178,7 +178,7 @@ async def _async_setup_component( return False except Exception: # pylint: disable=broad-except _LOGGER.exception("Error during setup of component %s", domain) - async_notify_setup_error(hass, domain, True) + async_notify_setup_error(hass, domain, integration.documentation) return False finally: end = timer() From ad8278c078bc3ce7d8912bb3a280069939dafd02 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Tue, 17 Dec 2019 00:32:23 +0000 Subject: [PATCH 325/677] [ci skip] Translation update --- .../alarm_control_panel/.translations/hu.json | 18 +++++++++++++++++ .../components/climate/.translations/bg.json | 2 +- .../components/climate/.translations/ca.json | 2 +- .../components/climate/.translations/en.json | 2 +- .../components/climate/.translations/es.json | 2 +- .../components/climate/.translations/fr.json | 2 +- .../components/climate/.translations/it.json | 2 +- .../components/climate/.translations/lb.json | 2 +- .../components/climate/.translations/no.json | 2 +- .../components/climate/.translations/pl.json | 2 +- .../components/climate/.translations/ru.json | 2 +- .../components/climate/.translations/sl.json | 2 +- .../climate/.translations/zh-Hant.json | 2 +- .../components/cover/.translations/hu.json | 20 +++++++++++++++++++ .../components/demo/.translations/hu.json | 5 +++++ .../device_tracker/.translations/bg.json | 2 +- .../device_tracker/.translations/ca.json | 2 +- .../device_tracker/.translations/cs.json | 2 +- .../device_tracker/.translations/de.json | 2 +- .../device_tracker/.translations/en.json | 2 +- .../device_tracker/.translations/es.json | 2 +- .../device_tracker/.translations/fr.json | 2 +- .../device_tracker/.translations/it.json | 2 +- .../device_tracker/.translations/ko.json | 2 +- .../device_tracker/.translations/lb.json | 2 +- .../device_tracker/.translations/nl.json | 2 +- .../device_tracker/.translations/no.json | 2 +- .../device_tracker/.translations/pl.json | 2 +- .../device_tracker/.translations/pt.json | 2 +- .../device_tracker/.translations/ru.json | 2 +- .../device_tracker/.translations/sl.json | 2 +- .../device_tracker/.translations/zh-Hant.json | 2 +- .../components/fan/.translations/bg.json | 2 +- .../components/fan/.translations/ca.json | 2 +- .../components/fan/.translations/de.json | 2 +- .../components/fan/.translations/en.json | 2 +- .../components/fan/.translations/es.json | 2 +- .../components/fan/.translations/fr.json | 2 +- .../components/fan/.translations/it.json | 2 +- .../components/fan/.translations/lb.json | 2 +- .../components/fan/.translations/nl.json | 2 +- .../components/fan/.translations/no.json | 2 +- .../components/fan/.translations/pl.json | 2 +- .../components/fan/.translations/pt-BR.json | 2 +- .../components/fan/.translations/pt.json | 2 +- .../components/fan/.translations/ru.json | 2 +- .../components/fan/.translations/sl.json | 2 +- .../components/fan/.translations/zh-Hant.json | 2 +- .../components/neato/.translations/ko.json | 4 ++-- .../components/soma/.translations/lb.json | 4 +++- .../components/vacuum/.translations/bg.json | 2 +- .../components/vacuum/.translations/ca.json | 2 +- .../components/vacuum/.translations/de.json | 2 +- .../components/vacuum/.translations/en.json | 2 +- .../components/vacuum/.translations/es.json | 2 +- .../components/vacuum/.translations/fr.json | 2 +- .../components/vacuum/.translations/it.json | 2 +- .../components/vacuum/.translations/lb.json | 2 +- .../components/vacuum/.translations/nl.json | 2 +- .../components/vacuum/.translations/no.json | 2 +- .../components/vacuum/.translations/pl.json | 2 +- .../components/vacuum/.translations/pt.json | 2 +- .../components/vacuum/.translations/ru.json | 2 +- .../components/vacuum/.translations/sl.json | 2 +- .../vacuum/.translations/zh-Hant.json | 2 +- 65 files changed, 108 insertions(+), 63 deletions(-) create mode 100644 homeassistant/components/alarm_control_panel/.translations/hu.json create mode 100644 homeassistant/components/cover/.translations/hu.json create mode 100644 homeassistant/components/demo/.translations/hu.json diff --git a/homeassistant/components/alarm_control_panel/.translations/hu.json b/homeassistant/components/alarm_control_panel/.translations/hu.json new file mode 100644 index 00000000000..b249a16c9f1 --- /dev/null +++ b/homeassistant/components/alarm_control_panel/.translations/hu.json @@ -0,0 +1,18 @@ +{ + "device_automation": { + "action_type": { + "arm_away": "{entity_name} \u00e9les\u00edt\u00e9se t\u00e1voz\u00f3 m\u00f3dban", + "arm_home": "{entity_name} \u00e9les\u00edt\u00e9se otthon marad\u00f3 m\u00f3dban", + "arm_night": "{entity_name} \u00e9les\u00edt\u00e9se \u00e9jszakai m\u00f3dban", + "disarm": "{entity_name} hat\u00e1stalan\u00edt\u00e1sa", + "trigger": "{entity_name} riaszt\u00e1si esem\u00e9ny ind\u00edt\u00e1sa" + }, + "trigger_type": { + "armed_away": "{entity_name} t\u00e1voz\u00f3 m\u00f3dban lett \u00e9les\u00edtve", + "armed_home": "{entity_name} otthon marad\u00f3 m\u00f3dban lett \u00e9les\u00edtve", + "armed_night": "{entity_name} \u00e9jszakai m\u00f3dban lett \u00e9les\u00edtve", + "disarmed": "{entity_name} hat\u00e1stalan\u00edtva lett", + "triggered": "{entity_name} riaszt\u00e1sba ker\u00fclt" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/climate/.translations/bg.json b/homeassistant/components/climate/.translations/bg.json index d7901d29884..ac1b05b096a 100644 --- a/homeassistant/components/climate/.translations/bg.json +++ b/homeassistant/components/climate/.translations/bg.json @@ -4,7 +4,7 @@ "set_hvac_mode": "\u041f\u0440\u043e\u043c\u044f\u043d\u0430 \u043d\u0430 \u0440\u0435\u0436\u0438\u043c \u043d\u0430 \u041e\u0412\u041a \u043d\u0430 {entity_name}", "set_preset_mode": "\u041f\u0440\u043e\u043c\u0435\u043d\u0438 \u043f\u0440\u0435\u0434\u0432\u0430\u0440\u0438\u0442\u0435\u043b\u043d\u043e \u0437\u0430\u0434\u0430\u0434\u0435\u043d \u0440\u0435\u0436\u0438\u043c \u043d\u0430 {entity_name}" }, - "condtion_type": { + "condition_type": { "is_hvac_mode": "{entity_name} \u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d \u043d\u0430 \u0441\u043f\u0435\u0446\u0438\u0444\u0438\u0447\u0435\u043d \u041e\u0412\u041a \u0440\u0435\u0436\u0438\u043c", "is_preset_mode": "{entity_name} \u0435 \u0432 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d \u043f\u0440\u0435\u0434\u0432\u0430\u0440\u0438\u0442\u0435\u043b\u043d\u043e \u0437\u0430\u0434\u0430\u0434\u0435\u043d \u0440\u0435\u0436\u0438\u043c" }, diff --git a/homeassistant/components/climate/.translations/ca.json b/homeassistant/components/climate/.translations/ca.json index 743729041ab..bde91c26b7e 100644 --- a/homeassistant/components/climate/.translations/ca.json +++ b/homeassistant/components/climate/.translations/ca.json @@ -4,7 +4,7 @@ "set_hvac_mode": "Canvia el mode HVAC de {entity_name}", "set_preset_mode": "Canvia la configuraci\u00f3 preestablerta de {entity_name}" }, - "condtion_type": { + "condition_type": { "is_hvac_mode": "{entity_name} est\u00e0 configurat/ada en un mode HVAC espec\u00edfic", "is_preset_mode": "{entity_name} est\u00e0 configurat/ada en un mode preestablert espec\u00edfic" }, diff --git a/homeassistant/components/climate/.translations/en.json b/homeassistant/components/climate/.translations/en.json index 942d9a2761f..2a56426e988 100644 --- a/homeassistant/components/climate/.translations/en.json +++ b/homeassistant/components/climate/.translations/en.json @@ -4,7 +4,7 @@ "set_hvac_mode": "Change HVAC mode on {entity_name}", "set_preset_mode": "Change preset on {entity_name}" }, - "condtion_type": { + "condition_type": { "is_hvac_mode": "{entity_name} is set to a specific HVAC mode", "is_preset_mode": "{entity_name} is set to a specific preset mode" }, diff --git a/homeassistant/components/climate/.translations/es.json b/homeassistant/components/climate/.translations/es.json index baae9b97436..e873427e694 100644 --- a/homeassistant/components/climate/.translations/es.json +++ b/homeassistant/components/climate/.translations/es.json @@ -4,7 +4,7 @@ "set_hvac_mode": "Cambiar el modo HVAC de {entity_name}.", "set_preset_mode": "Cambiar la configuraci\u00f3n prefijada de {entity_name}" }, - "condtion_type": { + "condition_type": { "is_hvac_mode": "{entity_name} est\u00e1 configurado en un modo HVAC espec\u00edfico", "is_preset_mode": "{entity_name} se establece en un modo predeterminado espec\u00edfico" }, diff --git a/homeassistant/components/climate/.translations/fr.json b/homeassistant/components/climate/.translations/fr.json index db29f8424d5..0358a60f180 100644 --- a/homeassistant/components/climate/.translations/fr.json +++ b/homeassistant/components/climate/.translations/fr.json @@ -4,7 +4,7 @@ "set_hvac_mode": "Changer le mode HVAC sur {entity_name}.", "set_preset_mode": "Changer les pr\u00e9r\u00e9glages de {entity_name}" }, - "condtion_type": { + "condition_type": { "is_hvac_mode": "{entity_name} est d\u00e9fini sur un mode HVAC sp\u00e9cifique", "is_preset_mode": "{entity_name} est d\u00e9fini sur un mode pr\u00e9d\u00e9fini sp\u00e9cifique" }, diff --git a/homeassistant/components/climate/.translations/it.json b/homeassistant/components/climate/.translations/it.json index 34ecbf5e9f2..25a09b7d66d 100644 --- a/homeassistant/components/climate/.translations/it.json +++ b/homeassistant/components/climate/.translations/it.json @@ -4,7 +4,7 @@ "set_hvac_mode": "Cambia modalit\u00e0 HVAC su {entity_name}", "set_preset_mode": "Modifica preimpostazione su {entity_name}" }, - "condtion_type": { + "condition_type": { "is_hvac_mode": "{entity_name} \u00e8 impostato su una modalit\u00e0 HVAC specifica", "is_preset_mode": "{entity_name} \u00e8 impostato su una modalit\u00e0 preimpostata specifica" }, diff --git a/homeassistant/components/climate/.translations/lb.json b/homeassistant/components/climate/.translations/lb.json index 72ab7efc623..cfb49f29f05 100644 --- a/homeassistant/components/climate/.translations/lb.json +++ b/homeassistant/components/climate/.translations/lb.json @@ -4,7 +4,7 @@ "set_hvac_mode": "HVAC Modus \u00e4nnere fir {entity_name}", "set_preset_mode": "Preset \u00e4nnere fir {entity_name}" }, - "condtion_type": { + "condition_type": { "is_hvac_mode": "\n{entity_name} ass op e spezifesche HVAC Modus gesat", "is_preset_mode": "{entity_name} ass op e spezifesche preset Modus gesat" }, diff --git a/homeassistant/components/climate/.translations/no.json b/homeassistant/components/climate/.translations/no.json index 2d95c63a6ae..bc6e97b9aa5 100644 --- a/homeassistant/components/climate/.translations/no.json +++ b/homeassistant/components/climate/.translations/no.json @@ -4,7 +4,7 @@ "set_hvac_mode": "Endre HVAC-modus p\u00e5 {entity_name}", "set_preset_mode": "Endre forh\u00e5ndsinnstilling p\u00e5 {entity_name}" }, - "condtion_type": { + "condition_type": { "is_hvac_mode": "{entity_name} er satt til en spesifikk HVAC-modus", "is_preset_mode": "{entity_name} er satt til en spesifikk forh\u00e5ndsinnstilt modus" }, diff --git a/homeassistant/components/climate/.translations/pl.json b/homeassistant/components/climate/.translations/pl.json index c5b0c483ca9..f2a09eee3ef 100644 --- a/homeassistant/components/climate/.translations/pl.json +++ b/homeassistant/components/climate/.translations/pl.json @@ -4,7 +4,7 @@ "set_hvac_mode": "zmie\u0144 tryb HVAC na {entity_name}", "set_preset_mode": "zmie\u0144 ustawienia dla {entity_name}" }, - "condtion_type": { + "condition_type": { "is_hvac_mode": "na {entity_name} jest ustawiony okre\u015blony tryb HVAC", "is_preset_mode": "na {entity_name} jest okre\u015blone ustawienie" }, diff --git a/homeassistant/components/climate/.translations/ru.json b/homeassistant/components/climate/.translations/ru.json index 045f96137d2..6a9c52be209 100644 --- a/homeassistant/components/climate/.translations/ru.json +++ b/homeassistant/components/climate/.translations/ru.json @@ -4,7 +4,7 @@ "set_hvac_mode": "\u0421\u043c\u0435\u043d\u0438\u0442\u044c \u0440\u0435\u0436\u0438\u043c \u0440\u0430\u0431\u043e\u0442\u044b \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \"{entity_name}\"", "set_preset_mode": "\u0421\u043c\u0435\u043d\u0438\u0442\u044c \u043d\u0430\u0431\u043e\u0440 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \"{entity_name}\"" }, - "condtion_type": { + "condition_type": { "is_hvac_mode": "{entity_name} \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u0437\u0430\u0434\u0430\u043d\u043d\u043e\u043c \u0440\u0435\u0436\u0438\u043c\u0435 \u0440\u0430\u0431\u043e\u0442\u044b", "is_preset_mode": "{entity_name} \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u043f\u0440\u0435\u0434\u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u043d\u0430\u0431\u043e\u0440\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a" }, diff --git a/homeassistant/components/climate/.translations/sl.json b/homeassistant/components/climate/.translations/sl.json index 4ba4cb02a4b..ecaf24fed80 100644 --- a/homeassistant/components/climate/.translations/sl.json +++ b/homeassistant/components/climate/.translations/sl.json @@ -4,7 +4,7 @@ "set_hvac_mode": "Spremeni na\u010din HVAC na {entity_name}", "set_preset_mode": "Spremenite prednastavitev na {entity_name}" }, - "condtion_type": { + "condition_type": { "is_hvac_mode": "{entity_name} je nastavljen na dolo\u010den na\u010din HVAC", "is_preset_mode": "{entity_name} je nastavljen na dolo\u010den prednastavljeni na\u010din" }, diff --git a/homeassistant/components/climate/.translations/zh-Hant.json b/homeassistant/components/climate/.translations/zh-Hant.json index 1d39eecc056..17e6c955046 100644 --- a/homeassistant/components/climate/.translations/zh-Hant.json +++ b/homeassistant/components/climate/.translations/zh-Hant.json @@ -4,7 +4,7 @@ "set_hvac_mode": "\u8b8a\u66f4 {entity_name} HVAC \u6a21\u5f0f", "set_preset_mode": "\u8b8a\u66f4 {entity_name} \u8a2d\u5b9a\u6a21\u5f0f" }, - "condtion_type": { + "condition_type": { "is_hvac_mode": "{entity_name} \u8a2d\u5b9a\u70ba\u6307\u5b9a HVAC \u6a21\u5f0f", "is_preset_mode": "{entity_name} \u8a2d\u5b9a\u70ba\u6307\u5b9a\u8a2d\u5b9a\u6a21\u5f0f" }, diff --git a/homeassistant/components/cover/.translations/hu.json b/homeassistant/components/cover/.translations/hu.json new file mode 100644 index 00000000000..d460c53109d --- /dev/null +++ b/homeassistant/components/cover/.translations/hu.json @@ -0,0 +1,20 @@ +{ + "device_automation": { + "condition_type": { + "is_closed": "{entity_name} z\u00e1rva van", + "is_closing": "{entity_name} z\u00e1r\u00f3dik", + "is_open": "{entity_name} nyitva van", + "is_opening": "{entity_name} ny\u00edlik", + "is_position": "{entity_name} jelenlegi poz\u00edci\u00f3ja", + "is_tilt_position": "{entity_name} jelenlegi d\u00f6nt\u00e9si poz\u00edci\u00f3ja" + }, + "trigger_type": { + "closed": "{entity_name} bez\u00e1r\u00f3dott", + "closing": "{entity_name} z\u00e1r\u00f3dik", + "opened": "{entity_name} kiny\u00edlt", + "opening": "{entity_name} ny\u00edlik", + "position": "{entity_name} poz\u00edci\u00f3ja v\u00e1ltozik", + "tilt_position": "{entity_name} d\u00f6nt\u00e9si poz\u00edci\u00f3ja v\u00e1ltozik" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/demo/.translations/hu.json b/homeassistant/components/demo/.translations/hu.json new file mode 100644 index 00000000000..51f0cd00642 --- /dev/null +++ b/homeassistant/components/demo/.translations/hu.json @@ -0,0 +1,5 @@ +{ + "config": { + "title": "Dem\u00f3" + } +} \ No newline at end of file diff --git a/homeassistant/components/device_tracker/.translations/bg.json b/homeassistant/components/device_tracker/.translations/bg.json index 471cbc6a53a..68affa5afd0 100644 --- a/homeassistant/components/device_tracker/.translations/bg.json +++ b/homeassistant/components/device_tracker/.translations/bg.json @@ -1,6 +1,6 @@ { "device_automation": { - "condtion_type": { + "condition_type": { "is_home": "{entity_name} \u0435 \u0443 \u0434\u043e\u043c\u0430", "is_not_home": "{entity_name} \u043d\u0435 \u0435 \u0443 \u0434\u043e\u043c\u0430" } diff --git a/homeassistant/components/device_tracker/.translations/ca.json b/homeassistant/components/device_tracker/.translations/ca.json index de5aed41e3c..3a95841559b 100644 --- a/homeassistant/components/device_tracker/.translations/ca.json +++ b/homeassistant/components/device_tracker/.translations/ca.json @@ -1,6 +1,6 @@ { "device_automation": { - "condtion_type": { + "condition_type": { "is_home": "{entity_name} \u00e9s a casa", "is_not_home": "{entity_name} no \u00e9s a casa" } diff --git a/homeassistant/components/device_tracker/.translations/cs.json b/homeassistant/components/device_tracker/.translations/cs.json index 778ea0208c4..7e82f1a34f8 100644 --- a/homeassistant/components/device_tracker/.translations/cs.json +++ b/homeassistant/components/device_tracker/.translations/cs.json @@ -1,6 +1,6 @@ { "device_automation": { - "condtion_type": { + "condition_type": { "is_home": "{entity_name} je doma", "is_not_home": "{entity_name} nen\u00ed doma" } diff --git a/homeassistant/components/device_tracker/.translations/de.json b/homeassistant/components/device_tracker/.translations/de.json index 7e72bd5595a..90a81db6b90 100644 --- a/homeassistant/components/device_tracker/.translations/de.json +++ b/homeassistant/components/device_tracker/.translations/de.json @@ -1,6 +1,6 @@ { "device_automation": { - "condtion_type": { + "condition_type": { "is_home": "{entity_name} ist Zuhause", "is_not_home": "{entity_name} ist nicht zu Hause" } diff --git a/homeassistant/components/device_tracker/.translations/en.json b/homeassistant/components/device_tracker/.translations/en.json index 25045e62b15..1022608477e 100644 --- a/homeassistant/components/device_tracker/.translations/en.json +++ b/homeassistant/components/device_tracker/.translations/en.json @@ -1,6 +1,6 @@ { "device_automation": { - "condtion_type": { + "condition_type": { "is_home": "{entity_name} is home", "is_not_home": "{entity_name} is not home" } diff --git a/homeassistant/components/device_tracker/.translations/es.json b/homeassistant/components/device_tracker/.translations/es.json index 00bda928b56..cfbf7bcfe3e 100644 --- a/homeassistant/components/device_tracker/.translations/es.json +++ b/homeassistant/components/device_tracker/.translations/es.json @@ -1,6 +1,6 @@ { "device_automation": { - "condtion_type": { + "condition_type": { "is_home": "{entity_name} est\u00e1 en casa", "is_not_home": "{entity_name} no est\u00e1 en casa" } diff --git a/homeassistant/components/device_tracker/.translations/fr.json b/homeassistant/components/device_tracker/.translations/fr.json index bf9033170c1..4c59d5ea1c8 100644 --- a/homeassistant/components/device_tracker/.translations/fr.json +++ b/homeassistant/components/device_tracker/.translations/fr.json @@ -1,6 +1,6 @@ { "device_automation": { - "condtion_type": { + "condition_type": { "is_home": "{entity_name} est \u00e0 la maison", "is_not_home": "{entity_name} n'est pas \u00e0 la maison" } diff --git a/homeassistant/components/device_tracker/.translations/it.json b/homeassistant/components/device_tracker/.translations/it.json index e2d35296152..112afc6689f 100644 --- a/homeassistant/components/device_tracker/.translations/it.json +++ b/homeassistant/components/device_tracker/.translations/it.json @@ -1,6 +1,6 @@ { "device_automation": { - "condtion_type": { + "condition_type": { "is_home": "{entity_name} \u00e8 in casa", "is_not_home": "{entity_name} non \u00e8 in casa" } diff --git a/homeassistant/components/device_tracker/.translations/ko.json b/homeassistant/components/device_tracker/.translations/ko.json index d258f67db22..92137ea2768 100644 --- a/homeassistant/components/device_tracker/.translations/ko.json +++ b/homeassistant/components/device_tracker/.translations/ko.json @@ -1,6 +1,6 @@ { "device_automation": { - "condtion_type": { + "condition_type": { "is_home": "{entity_name} \uc774(\uac00) \uc9d1\uc5d0 \uc788\uc2b5\ub2c8\ub2e4", "is_not_home": "{entity_name} \uc774(\uac00) \uc678\ucd9c\uc911\uc785\ub2c8\ub2e4" } diff --git a/homeassistant/components/device_tracker/.translations/lb.json b/homeassistant/components/device_tracker/.translations/lb.json index 98a066ef8e8..2c49f692662 100644 --- a/homeassistant/components/device_tracker/.translations/lb.json +++ b/homeassistant/components/device_tracker/.translations/lb.json @@ -1,6 +1,6 @@ { "device_automation": { - "condtion_type": { + "condition_type": { "is_home": "{entity_name} ass doheem", "is_not_home": "{entity_name} ass net doheem" } diff --git a/homeassistant/components/device_tracker/.translations/nl.json b/homeassistant/components/device_tracker/.translations/nl.json index d4de8b1f66a..31ab788f171 100644 --- a/homeassistant/components/device_tracker/.translations/nl.json +++ b/homeassistant/components/device_tracker/.translations/nl.json @@ -1,6 +1,6 @@ { "device_automation": { - "condtion_type": { + "condition_type": { "is_home": "{entity_name} is thuis", "is_not_home": "{entity_name} is niet thuis" } diff --git a/homeassistant/components/device_tracker/.translations/no.json b/homeassistant/components/device_tracker/.translations/no.json index 7034378b066..d714b5b7d31 100644 --- a/homeassistant/components/device_tracker/.translations/no.json +++ b/homeassistant/components/device_tracker/.translations/no.json @@ -1,6 +1,6 @@ { "device_automation": { - "condtion_type": { + "condition_type": { "is_home": "{entity_name} er hjemme", "is_not_home": "{entity_name} er ikke hjemme" } diff --git a/homeassistant/components/device_tracker/.translations/pl.json b/homeassistant/components/device_tracker/.translations/pl.json index 8f0f7953a2d..3930031ad38 100644 --- a/homeassistant/components/device_tracker/.translations/pl.json +++ b/homeassistant/components/device_tracker/.translations/pl.json @@ -1,6 +1,6 @@ { "device_automation": { - "condtion_type": { + "condition_type": { "is_home": "urz\u0105dzenie {entity_name} jest w domu", "is_not_home": "urz\u0105dzenie {entity_name} jest poza domem" } diff --git a/homeassistant/components/device_tracker/.translations/pt.json b/homeassistant/components/device_tracker/.translations/pt.json index 952eb4b1475..8a8f662183a 100644 --- a/homeassistant/components/device_tracker/.translations/pt.json +++ b/homeassistant/components/device_tracker/.translations/pt.json @@ -1,6 +1,6 @@ { "device_automation": { - "condtion_type": { + "condition_type": { "is_home": "{entity_name} est\u00e1 em casa", "is_not_home": "{entity_name} n\u00e3o est\u00e1 em casa" } diff --git a/homeassistant/components/device_tracker/.translations/ru.json b/homeassistant/components/device_tracker/.translations/ru.json index 50a48ce942b..58767361fd4 100644 --- a/homeassistant/components/device_tracker/.translations/ru.json +++ b/homeassistant/components/device_tracker/.translations/ru.json @@ -1,6 +1,6 @@ { "device_automation": { - "condtion_type": { + "condition_type": { "is_home": "{entity_name} \u0434\u043e\u043c\u0430", "is_not_home": "{entity_name} \u043d\u0435 \u0434\u043e\u043c\u0430" } diff --git a/homeassistant/components/device_tracker/.translations/sl.json b/homeassistant/components/device_tracker/.translations/sl.json index f4784fbc664..11d876883d3 100644 --- a/homeassistant/components/device_tracker/.translations/sl.json +++ b/homeassistant/components/device_tracker/.translations/sl.json @@ -1,6 +1,6 @@ { "device_automation": { - "condtion_type": { + "condition_type": { "is_home": "{entity_name} je doma", "is_not_home": "{entity_name} ni doma" } diff --git a/homeassistant/components/device_tracker/.translations/zh-Hant.json b/homeassistant/components/device_tracker/.translations/zh-Hant.json index 4092031434c..456e09ebf0e 100644 --- a/homeassistant/components/device_tracker/.translations/zh-Hant.json +++ b/homeassistant/components/device_tracker/.translations/zh-Hant.json @@ -1,6 +1,6 @@ { "device_automation": { - "condtion_type": { + "condition_type": { "is_home": "{entity_name} \u5728\u5bb6", "is_not_home": "{entity_name} \u4e0d\u5728\u5bb6" } diff --git a/homeassistant/components/fan/.translations/bg.json b/homeassistant/components/fan/.translations/bg.json index 62452e67179..f678c870968 100644 --- a/homeassistant/components/fan/.translations/bg.json +++ b/homeassistant/components/fan/.translations/bg.json @@ -4,7 +4,7 @@ "turn_off": "\u0418\u0437\u043a\u043b\u044e\u0447\u0438 {entity_name}", "turn_on": "\u0412\u043a\u043b\u044e\u0447\u0438 {entity_name}" }, - "condtion_type": { + "condition_type": { "is_off": "{entity_name} \u0435 \u0438\u0437\u043a\u043b\u044e\u0447\u0435\u043d", "is_on": "{entity_name} \u0435 \u0432\u043a\u043b\u044e\u0447\u0435\u043d" }, diff --git a/homeassistant/components/fan/.translations/ca.json b/homeassistant/components/fan/.translations/ca.json index 0530ccf5a85..e2f3ce2b0a4 100644 --- a/homeassistant/components/fan/.translations/ca.json +++ b/homeassistant/components/fan/.translations/ca.json @@ -4,7 +4,7 @@ "turn_off": "Apaga {entity_name}", "turn_on": "Enc\u00e9n {entity_name}" }, - "condtion_type": { + "condition_type": { "is_off": "{entity_name} est\u00e0 apagat", "is_on": "{entity_name} est\u00e0 enc\u00e8s" }, diff --git a/homeassistant/components/fan/.translations/de.json b/homeassistant/components/fan/.translations/de.json index 9ac3d999370..9c3559b7cfc 100644 --- a/homeassistant/components/fan/.translations/de.json +++ b/homeassistant/components/fan/.translations/de.json @@ -4,7 +4,7 @@ "turn_off": "Schalte {entity_name} aus.", "turn_on": "Schalte {entity_name} ein." }, - "condtion_type": { + "condition_type": { "is_off": "{entity_name} ist ausgeschaltet", "is_on": "{entity_name} ist eingeschaltet" }, diff --git a/homeassistant/components/fan/.translations/en.json b/homeassistant/components/fan/.translations/en.json index b085e7baa45..c27d983ca2e 100644 --- a/homeassistant/components/fan/.translations/en.json +++ b/homeassistant/components/fan/.translations/en.json @@ -4,7 +4,7 @@ "turn_off": "Turn off {entity_name}", "turn_on": "Turn on {entity_name}" }, - "condtion_type": { + "condition_type": { "is_off": "{entity_name} is off", "is_on": "{entity_name} is on" }, diff --git a/homeassistant/components/fan/.translations/es.json b/homeassistant/components/fan/.translations/es.json index d92153a6302..4ceefe9c721 100644 --- a/homeassistant/components/fan/.translations/es.json +++ b/homeassistant/components/fan/.translations/es.json @@ -4,7 +4,7 @@ "turn_off": "Desactivar {entity_name}", "turn_on": "Activar {entity_name}" }, - "condtion_type": { + "condition_type": { "is_off": "{entity_name} est\u00e1 desactivado", "is_on": "{entity_name} est\u00e1 activado" }, diff --git a/homeassistant/components/fan/.translations/fr.json b/homeassistant/components/fan/.translations/fr.json index 5c5a65b6bcd..e6944dab781 100644 --- a/homeassistant/components/fan/.translations/fr.json +++ b/homeassistant/components/fan/.translations/fr.json @@ -4,7 +4,7 @@ "turn_off": "\u00c9teindre {entity_name}", "turn_on": "Allumer {entity_name}" }, - "condtion_type": { + "condition_type": { "is_off": "{entity_name} est d\u00e9sactiv\u00e9", "is_on": "{entity_name} est activ\u00e9" }, diff --git a/homeassistant/components/fan/.translations/it.json b/homeassistant/components/fan/.translations/it.json index b62d80c793b..4fab847f1cb 100644 --- a/homeassistant/components/fan/.translations/it.json +++ b/homeassistant/components/fan/.translations/it.json @@ -4,7 +4,7 @@ "turn_off": "Spegnere {entity_name}", "turn_on": "Accendere {entity_name}" }, - "condtion_type": { + "condition_type": { "is_off": "{entity_name} \u00e8 spento", "is_on": "{entity_name} \u00e8 acceso" }, diff --git a/homeassistant/components/fan/.translations/lb.json b/homeassistant/components/fan/.translations/lb.json index 316a77d471d..f5170949bad 100644 --- a/homeassistant/components/fan/.translations/lb.json +++ b/homeassistant/components/fan/.translations/lb.json @@ -4,7 +4,7 @@ "turn_off": "{entity_name} ausschalten", "turn_on": "{entity_name} uschalten" }, - "condtion_type": { + "condition_type": { "is_off": "{entity_name} ass aus", "is_on": "{entity_name} ass un" }, diff --git a/homeassistant/components/fan/.translations/nl.json b/homeassistant/components/fan/.translations/nl.json index 706c2c92b19..4837b301ea7 100644 --- a/homeassistant/components/fan/.translations/nl.json +++ b/homeassistant/components/fan/.translations/nl.json @@ -4,7 +4,7 @@ "turn_off": "Schakel {entity_name} uit", "turn_on": "Schakel {entity_name} in" }, - "condtion_type": { + "condition_type": { "is_off": "{entity_name} is uitgeschakeld", "is_on": "{entity_name} is ingeschakeld" }, diff --git a/homeassistant/components/fan/.translations/no.json b/homeassistant/components/fan/.translations/no.json index 73917ac45c4..aa6320f0a65 100644 --- a/homeassistant/components/fan/.translations/no.json +++ b/homeassistant/components/fan/.translations/no.json @@ -4,7 +4,7 @@ "turn_off": "Sl\u00e5 av {entity_name}", "turn_on": "Sl\u00e5 p\u00e5 {entity_name}" }, - "condtion_type": { + "condition_type": { "is_off": "{entity_name} er av", "is_on": "{entity_name} er p\u00e5" }, diff --git a/homeassistant/components/fan/.translations/pl.json b/homeassistant/components/fan/.translations/pl.json index 424794a5b64..709a63c2389 100644 --- a/homeassistant/components/fan/.translations/pl.json +++ b/homeassistant/components/fan/.translations/pl.json @@ -4,7 +4,7 @@ "turn_off": "wy\u0142\u0105cz {entity_name}", "turn_on": "w\u0142\u0105cz {entity_name}" }, - "condtion_type": { + "condition_type": { "is_off": "wentylator (entity_name} jest wy\u0142\u0105czony", "is_on": "wentylator (entity_name} jest w\u0142\u0105czony" }, diff --git a/homeassistant/components/fan/.translations/pt-BR.json b/homeassistant/components/fan/.translations/pt-BR.json index 86b10b0f909..6b95464bdbc 100644 --- a/homeassistant/components/fan/.translations/pt-BR.json +++ b/homeassistant/components/fan/.translations/pt-BR.json @@ -1,6 +1,6 @@ { "device_automation": { - "condtion_type": { + "condition_type": { "is_off": "{entity_name} est\u00e1 desligado", "is_on": "{entity_name} est\u00e1 ligado" } diff --git a/homeassistant/components/fan/.translations/pt.json b/homeassistant/components/fan/.translations/pt.json index a76550cbedd..ab78bc776bd 100644 --- a/homeassistant/components/fan/.translations/pt.json +++ b/homeassistant/components/fan/.translations/pt.json @@ -4,7 +4,7 @@ "turn_off": "Desligar {entity_name}", "turn_on": "Ligar {entity_name}" }, - "condtion_type": { + "condition_type": { "is_off": "{entity_name} est\u00e1 desligada", "is_on": "{entity_name} est\u00e1 ligada" }, diff --git a/homeassistant/components/fan/.translations/ru.json b/homeassistant/components/fan/.translations/ru.json index 4fd5ebe28c5..157c78975cb 100644 --- a/homeassistant/components/fan/.translations/ru.json +++ b/homeassistant/components/fan/.translations/ru.json @@ -4,7 +4,7 @@ "turn_off": "\u0412\u044b\u043a\u043b\u044e\u0447\u0438\u0442\u044c {entity_name}", "turn_on": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c {entity_name}" }, - "condtion_type": { + "condition_type": { "is_off": "{entity_name} \u0432 \u0432\u044b\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u043e\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0438", "is_on": "{entity_name} \u0432\u043e \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u043e\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0438" }, diff --git a/homeassistant/components/fan/.translations/sl.json b/homeassistant/components/fan/.translations/sl.json index a5de109f764..a2bca3352ab 100644 --- a/homeassistant/components/fan/.translations/sl.json +++ b/homeassistant/components/fan/.translations/sl.json @@ -4,7 +4,7 @@ "turn_off": "Izklopite {entity_name}", "turn_on": "Vklopite {entity_name}" }, - "condtion_type": { + "condition_type": { "is_off": "{entity_name} je izklopljen", "is_on": "{entity_name} je vklopljen" }, diff --git a/homeassistant/components/fan/.translations/zh-Hant.json b/homeassistant/components/fan/.translations/zh-Hant.json index 4b34f6e0165..78c0d991125 100644 --- a/homeassistant/components/fan/.translations/zh-Hant.json +++ b/homeassistant/components/fan/.translations/zh-Hant.json @@ -4,7 +4,7 @@ "turn_off": "\u95dc\u9589 {entity_name}", "turn_on": "\u958b\u555f {entity_name}" }, - "condtion_type": { + "condition_type": { "is_off": "{entity_name} \u95dc\u9589", "is_on": "{entity_name} \u958b\u555f" }, diff --git a/homeassistant/components/neato/.translations/ko.json b/homeassistant/components/neato/.translations/ko.json index aeb591f7b20..391d0aee191 100644 --- a/homeassistant/components/neato/.translations/ko.json +++ b/homeassistant/components/neato/.translations/ko.json @@ -5,7 +5,7 @@ "invalid_credentials": "\uc0ac\uc6a9\uc790 \uc774\ub984 \ud639\uc740 \ube44\ubc00\ubc88\ud638\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "create_entry": { - "default": "[Neato \uc124\uba85\uc11c] ({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." + "default": "[Neato \uc124\uba85\uc11c]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "error": { "invalid_credentials": "\uc0ac\uc6a9\uc790 \uc774\ub984 \ud639\uc740 \ube44\ubc00\ubc88\ud638\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", @@ -18,7 +18,7 @@ "username": "\uc0ac\uc6a9\uc790 \uc774\ub984", "vendor": "\uacf5\uae09 \uc5c5\uccb4" }, - "description": "[Neato \uc124\uba85\uc11c] ({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.", + "description": "[Neato \uc124\uba85\uc11c]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.", "title": "Neato \uacc4\uc815 \uc815\ubcf4" } }, diff --git a/homeassistant/components/soma/.translations/lb.json b/homeassistant/components/soma/.translations/lb.json index 93e9a1e66c4..fdf180a1a61 100644 --- a/homeassistant/components/soma/.translations/lb.json +++ b/homeassistant/components/soma/.translations/lb.json @@ -3,7 +3,9 @@ "abort": { "already_setup": "Dir k\u00ebnnt n\u00ebmmen een eenzegen Soma Kont konfigur\u00e9ieren.", "authorize_url_timeout": "Z\u00e4it Iwwerschreidung beim gener\u00e9ieren vun der Autorisatiouns URL.", - "missing_configuration": "D'Soma Komponent ass nach net konfigur\u00e9iert. Follegt w.e.g der Dokumentatioun." + "connection_error": "Feeler beim verbannen mat SOMA Connect.", + "missing_configuration": "D'Soma Komponent ass nach net konfigur\u00e9iert. Follegt w.e.g der Dokumentatioun.", + "result_error": "SOMA Connect \u00e4ntwert mat engem Feeler Code." }, "create_entry": { "default": "Erfollegr\u00e4ich mat Soma authentifiz\u00e9iert." diff --git a/homeassistant/components/vacuum/.translations/bg.json b/homeassistant/components/vacuum/.translations/bg.json index 2d422284a38..1ab7fce7abe 100644 --- a/homeassistant/components/vacuum/.translations/bg.json +++ b/homeassistant/components/vacuum/.translations/bg.json @@ -4,7 +4,7 @@ "clean": "\u041d\u0435\u043a\u0430 {entity_name} \u043f\u043e\u0447\u0438\u0441\u0442\u0438", "dock": "\u041d\u0435\u043a\u0430 {entity_name} \u0434\u0430 \u0441\u0435 \u0432\u044a\u0440\u043d\u0435 \u0432 \u0431\u0430\u0437\u043e\u0432\u0430\u0442\u0430 \u0441\u0442\u0430\u043d\u0446\u0438\u044f" }, - "condtion_type": { + "condition_type": { "is_cleaning": "{entity_name} \u043f\u043e\u0447\u0438\u0441\u0442\u0432\u0430", "is_docked": "{entity_name} \u0435 \u0432 \u0431\u0430\u0437\u043e\u0432\u0430\u0442\u0430 \u0441\u0442\u0430\u043d\u0446\u0438\u044f" }, diff --git a/homeassistant/components/vacuum/.translations/ca.json b/homeassistant/components/vacuum/.translations/ca.json index ee69152ed5c..b3cdbb2f6c7 100644 --- a/homeassistant/components/vacuum/.translations/ca.json +++ b/homeassistant/components/vacuum/.translations/ca.json @@ -4,7 +4,7 @@ "clean": "Fes que {entity_name} netegi", "dock": "Fes que {entity_name} torni a la base" }, - "condtion_type": { + "condition_type": { "is_cleaning": "{entity_name} est\u00e0 netejant", "is_docked": "{entity_name} est\u00e0 acoblada" }, diff --git a/homeassistant/components/vacuum/.translations/de.json b/homeassistant/components/vacuum/.translations/de.json index 7aed7da23e3..3fe2d57eb01 100644 --- a/homeassistant/components/vacuum/.translations/de.json +++ b/homeassistant/components/vacuum/.translations/de.json @@ -4,7 +4,7 @@ "clean": "Lass {entity_name} reinigen", "dock": "Lass {entity_name} zum Dock zur\u00fcckkehren" }, - "condtion_type": { + "condition_type": { "is_cleaning": "{entity_name} reinigt", "is_docked": "{entity_name} ist angedockt" }, diff --git a/homeassistant/components/vacuum/.translations/en.json b/homeassistant/components/vacuum/.translations/en.json index 396c6a83be9..3feb8eada72 100644 --- a/homeassistant/components/vacuum/.translations/en.json +++ b/homeassistant/components/vacuum/.translations/en.json @@ -4,7 +4,7 @@ "clean": "Let {entity_name} clean", "dock": "Let {entity_name} return to the dock" }, - "condtion_type": { + "condition_type": { "is_cleaning": "{entity_name} is cleaning", "is_docked": "{entity_name} is docked" }, diff --git a/homeassistant/components/vacuum/.translations/es.json b/homeassistant/components/vacuum/.translations/es.json index f99e94a40a3..376058faafa 100644 --- a/homeassistant/components/vacuum/.translations/es.json +++ b/homeassistant/components/vacuum/.translations/es.json @@ -4,7 +4,7 @@ "clean": "Deje que {entity_name} limpie", "dock": "Deje que {entity_name} regrese a la base" }, - "condtion_type": { + "condition_type": { "is_cleaning": "{entity_name} est\u00e1 limpiando", "is_docked": "{entity_name} en la base" }, diff --git a/homeassistant/components/vacuum/.translations/fr.json b/homeassistant/components/vacuum/.translations/fr.json index 4a0ab7f8de7..84d5e17bda1 100644 --- a/homeassistant/components/vacuum/.translations/fr.json +++ b/homeassistant/components/vacuum/.translations/fr.json @@ -4,7 +4,7 @@ "clean": "Laisser {entity_name} vide", "dock": "Laisser {entity_name} retourner \u00e0 la base" }, - "condtion_type": { + "condition_type": { "is_cleaning": "{entity_name} nettoie", "is_docked": "{entity_name} est connect\u00e9" }, diff --git a/homeassistant/components/vacuum/.translations/it.json b/homeassistant/components/vacuum/.translations/it.json index 0b879f154fa..32ecd1e0377 100644 --- a/homeassistant/components/vacuum/.translations/it.json +++ b/homeassistant/components/vacuum/.translations/it.json @@ -4,7 +4,7 @@ "clean": "Lascia pulire {entity_name}", "dock": "Lascia che {entity_name} ritorni alla base" }, - "condtion_type": { + "condition_type": { "is_cleaning": "{entity_name} sta pulendo", "is_docked": "{entity_name} \u00e8 agganciato alla base" }, diff --git a/homeassistant/components/vacuum/.translations/lb.json b/homeassistant/components/vacuum/.translations/lb.json index 6d984b997fa..d6776ccd619 100644 --- a/homeassistant/components/vacuum/.translations/lb.json +++ b/homeassistant/components/vacuum/.translations/lb.json @@ -4,7 +4,7 @@ "clean": "Looss {entity_name} botzen", "dock": "Sch\u00e9ck {entity_name} z\u00e9reck zur Statioun" }, - "condtion_type": { + "condition_type": { "is_cleaning": "{entity_name} botzt", "is_docked": "{entity_name} ass an der Statioun" }, diff --git a/homeassistant/components/vacuum/.translations/nl.json b/homeassistant/components/vacuum/.translations/nl.json index 3032fc22508..3e49c926d2d 100644 --- a/homeassistant/components/vacuum/.translations/nl.json +++ b/homeassistant/components/vacuum/.translations/nl.json @@ -1,6 +1,6 @@ { "device_automation": { - "condtion_type": { + "condition_type": { "is_cleaning": "{entity_name} is aan het schoonmaken" }, "trigger_type": { diff --git a/homeassistant/components/vacuum/.translations/no.json b/homeassistant/components/vacuum/.translations/no.json index 7d6475f8cef..0c34081cb2f 100644 --- a/homeassistant/components/vacuum/.translations/no.json +++ b/homeassistant/components/vacuum/.translations/no.json @@ -4,7 +4,7 @@ "clean": "La {entity_name} rengj\u00f8res", "dock": "La {entity_name} tilbake til dock" }, - "condtion_type": { + "condition_type": { "is_cleaning": "{entity_name} rengj\u00f8res", "is_docked": "{entity_name} er docked" }, diff --git a/homeassistant/components/vacuum/.translations/pl.json b/homeassistant/components/vacuum/.translations/pl.json index e637c26b3ed..09eef23ac9a 100644 --- a/homeassistant/components/vacuum/.translations/pl.json +++ b/homeassistant/components/vacuum/.translations/pl.json @@ -4,7 +4,7 @@ "clean": "niech {entity_name} sprz\u0105ta", "dock": "niech {entity_name} wr\u00f3ci do bazy" }, - "condtion_type": { + "condition_type": { "is_cleaning": "{entity_name} sprz\u0105ta", "is_docked": "{entity_name} jest w bazie" }, diff --git a/homeassistant/components/vacuum/.translations/pt.json b/homeassistant/components/vacuum/.translations/pt.json index 42b8bdabc0f..15b8ac3fd19 100644 --- a/homeassistant/components/vacuum/.translations/pt.json +++ b/homeassistant/components/vacuum/.translations/pt.json @@ -3,7 +3,7 @@ "action_type": { "clean": "Deixar {entity_name} limpar" }, - "condtion_type": { + "condition_type": { "is_cleaning": "{entity_name} est\u00e1 a limpar" } } diff --git a/homeassistant/components/vacuum/.translations/ru.json b/homeassistant/components/vacuum/.translations/ru.json index c727e8f6ea3..c42f0310fae 100644 --- a/homeassistant/components/vacuum/.translations/ru.json +++ b/homeassistant/components/vacuum/.translations/ru.json @@ -4,7 +4,7 @@ "clean": "\u041e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c {entity_name} \u0434\u0435\u043b\u0430\u0442\u044c \u0443\u0431\u043e\u0440\u043a\u0443", "dock": "\u0412\u0435\u0440\u043d\u0443\u0442\u044c {entity_name} \u043d\u0430 \u0434\u043e\u043a-\u0441\u0442\u0430\u043d\u0446\u0438\u044e" }, - "condtion_type": { + "condition_type": { "is_cleaning": "{entity_name} \u0434\u0435\u043b\u0430\u0435\u0442 \u0443\u0431\u043e\u0440\u043a\u0443", "is_docked": "{entity_name} \u0443 \u0434\u043e\u043a-\u0441\u0442\u0430\u043d\u0446\u0438\u0438" }, diff --git a/homeassistant/components/vacuum/.translations/sl.json b/homeassistant/components/vacuum/.translations/sl.json index 25de303b157..c594c4f1bdd 100644 --- a/homeassistant/components/vacuum/.translations/sl.json +++ b/homeassistant/components/vacuum/.translations/sl.json @@ -4,7 +4,7 @@ "clean": "Naj {entity_name} \u010disti", "dock": "Pustite, da se {entity_name} vrne na dok" }, - "condtion_type": { + "condition_type": { "is_cleaning": "{entity_name} \u010disti", "is_docked": "{entity_name} je priklju\u010den" }, diff --git a/homeassistant/components/vacuum/.translations/zh-Hant.json b/homeassistant/components/vacuum/.translations/zh-Hant.json index f0ad431afc9..b406e1baede 100644 --- a/homeassistant/components/vacuum/.translations/zh-Hant.json +++ b/homeassistant/components/vacuum/.translations/zh-Hant.json @@ -4,7 +4,7 @@ "clean": "\u555f\u52d5 {entity_name} \u6e05\u9664", "dock": "\u555f\u52d5 {entity_name} \u56de\u5230\u5145\u96fb\u7ad9" }, - "condtion_type": { + "condition_type": { "is_cleaning": "{entity_name} \u6b63\u5728\u6e05\u6383", "is_docked": "{entity_name} \u65bc\u5145\u96fb\u7ad9" }, From 78e831b08ed1eae81620cef4cb43672d8c566eb2 Mon Sep 17 00:00:00 2001 From: Robert Van Gorkom Date: Mon, 16 Dec 2019 17:24:50 -0800 Subject: [PATCH 326/677] Make tplink light more responsive (#28652) * Making tplink light more responsive. * Adding light platform tests. * Addressing PR feedback. * Mocking the module, not the api. * Using sync method for background update. --- .coveragerc | 1 - homeassistant/components/tplink/const.py | 2 + homeassistant/components/tplink/light.py | 27 ++- tests/components/tplink/test_light.py | 220 +++++++++++++++++++++++ 4 files changed, 242 insertions(+), 8 deletions(-) create mode 100644 tests/components/tplink/test_light.py diff --git a/.coveragerc b/.coveragerc index e16a622cc61..fc7a4691ef2 100644 --- a/.coveragerc +++ b/.coveragerc @@ -708,7 +708,6 @@ omit = homeassistant/components/totalconnect/* homeassistant/components/touchline/climate.py homeassistant/components/tplink/device_tracker.py - homeassistant/components/tplink/light.py homeassistant/components/tplink/switch.py homeassistant/components/tplink_lte/* homeassistant/components/traccar/device_tracker.py diff --git a/homeassistant/components/tplink/const.py b/homeassistant/components/tplink/const.py index 583c25e285c..8b85b8afd74 100644 --- a/homeassistant/components/tplink/const.py +++ b/homeassistant/components/tplink/const.py @@ -1,3 +1,5 @@ """Const for TP-Link.""" +import datetime DOMAIN = "tplink" +MIN_TIME_BETWEEN_UPDATES = datetime.timedelta(seconds=8) diff --git a/homeassistant/components/tplink/light.py b/homeassistant/components/tplink/light.py index 117ebf75025..ec3307fc87e 100644 --- a/homeassistant/components/tplink/light.py +++ b/homeassistant/components/tplink/light.py @@ -126,23 +126,27 @@ class TPLinkSmartBulb(Light): def turn_on(self, **kwargs): """Turn the light on.""" + self._state = True self.smartbulb.state = SmartBulb.BULB_STATE_ON if ATTR_COLOR_TEMP in kwargs: - self.smartbulb.color_temp = mired_to_kelvin(kwargs[ATTR_COLOR_TEMP]) + self._color_temp = kwargs.get(ATTR_COLOR_TEMP) + self.smartbulb.color_temp = mired_to_kelvin(self._color_temp) - brightness = brightness_to_percentage( - kwargs.get(ATTR_BRIGHTNESS, self.brightness or 255) - ) + brightness_value = kwargs.get(ATTR_BRIGHTNESS, self.brightness or 255) + brightness_pct = brightness_to_percentage(brightness_value) if ATTR_HS_COLOR in kwargs: - hue, sat = kwargs.get(ATTR_HS_COLOR) - hsv = (int(hue), int(sat), brightness) + self._hs = kwargs.get(ATTR_HS_COLOR) + hue, sat = self._hs + hsv = (int(hue), int(sat), brightness_pct) self.smartbulb.hsv = hsv elif ATTR_BRIGHTNESS in kwargs: - self.smartbulb.brightness = brightness + self._brightness = brightness_value + self.smartbulb.brightness = brightness_pct def turn_off(self, **kwargs): """Turn the light off.""" + self._state = False self.smartbulb.state = SmartBulb.BULB_STATE_OFF @property @@ -177,6 +181,15 @@ class TPLinkSmartBulb(Light): def update(self): """Update the TP-Link Bulb's state.""" + if self._supported_features is None: + # First run, update by blocking. + self.do_update() + else: + # Not first run, update in the background. + self.hass.add_job(self.do_update) + + def do_update(self): + """Update states.""" try: if self._supported_features is None: self.get_features() diff --git a/tests/components/tplink/test_light.py b/tests/components/tplink/test_light.py new file mode 100644 index 00000000000..8d1d4d94738 --- /dev/null +++ b/tests/components/tplink/test_light.py @@ -0,0 +1,220 @@ +"""Tests for light platform.""" +from unittest.mock import patch + +from pyHS100 import SmartBulb + +from homeassistant.components import tplink +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + ATTR_COLOR_TEMP, + ATTR_HS_COLOR, + DOMAIN as LIGHT_DOMAIN, +) +from homeassistant.components.tplink.common import CONF_DISCOVERY, CONF_LIGHT +from homeassistant.const import ( + ATTR_ENTITY_ID, + CONF_HOST, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, +) +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + + +async def test_light(hass: HomeAssistant) -> None: + """Test function.""" + sys_info = { + "sw_ver": "1.2.3", + "hw_ver": "2.3.4", + "mac": "aa:bb:cc:dd:ee:ff", + "mic_mac": "00:11:22:33:44", + "type": "light", + "hwId": "1234", + "fwId": "4567", + "oemId": "891011", + "dev_name": "light1", + "rssi": 11, + "latitude": "0", + "longitude": "0", + "is_color": True, + "is_dimmable": True, + "is_variable_color_temp": True, + "model": "LB120", + "alias": "light1", + } + + light_state = { + "on_off": SmartBulb.BULB_STATE_ON, + "dft_on_state": { + "brightness": 12, + "color_temp": 3200, + "hue": 100, + "saturation": 200, + }, + "brightness": 13, + "color_temp": 3300, + "hue": 110, + "saturation": 210, + } + + def set_light_state(state): + nonlocal light_state + light_state.update(state) + + set_light_state_patch = patch( + "homeassistant.components.tplink.common.SmartBulb.set_light_state", + side_effect=set_light_state, + ) + get_light_state_patch = patch( + "homeassistant.components.tplink.common.SmartBulb.get_light_state", + return_value=light_state, + ) + current_consumption_patch = patch( + "homeassistant.components.tplink.common.SmartDevice.current_consumption", + return_value=3.23, + ) + get_sysinfo_patch = patch( + "homeassistant.components.tplink.common.SmartDevice.get_sysinfo", + return_value=sys_info, + ) + get_emeter_daily_patch = patch( + "homeassistant.components.tplink.common.SmartDevice.get_emeter_daily", + return_value={ + 1: 1.01, + 2: 1.02, + 3: 1.03, + 4: 1.04, + 5: 1.05, + 6: 1.06, + 7: 1.07, + 8: 1.08, + 9: 1.09, + 10: 1.10, + 11: 1.11, + 12: 1.12, + }, + ) + get_emeter_monthly_patch = patch( + "homeassistant.components.tplink.common.SmartDevice.get_emeter_monthly", + return_value={ + 1: 2.01, + 2: 2.02, + 3: 2.03, + 4: 2.04, + 5: 2.05, + 6: 2.06, + 7: 2.07, + 8: 2.08, + 9: 2.09, + 10: 2.10, + 11: 2.11, + 12: 2.12, + }, + ) + + with set_light_state_patch, get_light_state_patch, current_consumption_patch, get_sysinfo_patch, get_emeter_daily_patch, get_emeter_monthly_patch: + await async_setup_component( + hass, + tplink.DOMAIN, + { + tplink.DOMAIN: { + CONF_DISCOVERY: False, + CONF_LIGHT: [{CONF_HOST: "123.123.123.123"}], + } + }, + ) + await hass.async_block_till_done() + + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "light.light1"}, + blocking=True, + ) + + assert hass.states.get("light.light1").state == "off" + assert light_state["on_off"] == 0 + + await hass.async_block_till_done() + + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + { + ATTR_ENTITY_ID: "light.light1", + ATTR_COLOR_TEMP: 312, + ATTR_BRIGHTNESS: 50, + }, + blocking=True, + ) + + await hass.async_block_till_done() + + state = hass.states.get("light.light1") + assert state.state == "on" + assert state.attributes["brightness"] == 48.45 + assert state.attributes["hs_color"] == (110, 210) + assert state.attributes["color_temp"] == 312 + assert light_state["on_off"] == 1 + + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + { + ATTR_ENTITY_ID: "light.light1", + ATTR_BRIGHTNESS: 55, + ATTR_HS_COLOR: (23, 27), + }, + blocking=True, + ) + + await hass.async_block_till_done() + + state = hass.states.get("light.light1") + assert state.state == "on" + assert state.attributes["brightness"] == 53.55 + assert state.attributes["hs_color"] == (23, 27) + assert state.attributes["color_temp"] == 312 + assert light_state["brightness"] == 21 + assert light_state["hue"] == 23 + assert light_state["saturation"] == 27 + + light_state["on_off"] = 0 + light_state["dft_on_state"]["on_off"] = 0 + light_state["brightness"] = 66 + light_state["dft_on_state"]["brightness"] = 66 + light_state["color_temp"] = 6400 + light_state["dft_on_state"]["color_temp"] = 123 + light_state["hue"] = 77 + light_state["dft_on_state"]["hue"] = 77 + light_state["saturation"] = 78 + light_state["dft_on_state"]["saturation"] = 78 + + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "light.light1"}, + blocking=True, + ) + + await hass.async_block_till_done() + + state = hass.states.get("light.light1") + assert state.state == "off" + + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "light.light1"}, + blocking=True, + ) + + await hass.async_block_till_done() + + state = hass.states.get("light.light1") + assert state.attributes["brightness"] == 168.3 + assert state.attributes["hs_color"] == (77, 78) + assert state.attributes["color_temp"] == 156 + assert light_state["brightness"] == 66 + assert light_state["hue"] == 77 + assert light_state["saturation"] == 78 From 115aa2e49c97afeb9f77e7271fdd104a2261a482 Mon Sep 17 00:00:00 2001 From: Andre Richter Date: Tue, 17 Dec 2019 15:26:19 +0100 Subject: [PATCH 327/677] Z-Wave: Fibaro FGR*-222: Add venetian blind support (#29701) The Fibaro FGR-222/FGRM-2222 ZWave roller shutter devices have a proprietary command class to support setting the tilt angle of venetian blinds (= type of window cover). This PR adds the support to HA for this. This allows the user to set the height of the blinds and the tilt angle separately. Original patch by @ChristianKuehnel #24405. --- homeassistant/components/zwave/__init__.py | 5 + homeassistant/components/zwave/cover.py | 144 +++++++++++++++++++++ 2 files changed, 149 insertions(+) diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 6dd007fb8a8..cb494b5fa6f 100644 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -79,12 +79,14 @@ CONF_REFRESH_DELAY = "delay" CONF_DEVICE_CONFIG = "device_config" CONF_DEVICE_CONFIG_GLOB = "device_config_glob" CONF_DEVICE_CONFIG_DOMAIN = "device_config_domain" +CONF_TILT_OPEN_POSITION = "tilt_open_position" DEFAULT_CONF_IGNORED = False DEFAULT_CONF_INVERT_OPENCLOSE_BUTTONS = False DEFAULT_CONF_INVERT_PERCENT = False DEFAULT_CONF_REFRESH_VALUE = False DEFAULT_CONF_REFRESH_DELAY = 5 +DEFAULT_CONF_TILT_OPEN_POSITION = 50 SUPPORTED_PLATFORMS = [ "binary_sensor", @@ -214,6 +216,9 @@ DEVICE_CONFIG_SCHEMA_ENTRY = vol.Schema( vol.Optional( CONF_REFRESH_DELAY, default=DEFAULT_CONF_REFRESH_DELAY ): cv.positive_int, + vol.Optional( + CONF_TILT_OPEN_POSITION, default=DEFAULT_CONF_TILT_OPEN_POSITION + ): cv.positive_int, } ) diff --git a/homeassistant/components/zwave/cover.py b/homeassistant/components/zwave/cover.py index 95cc994e4ff..5b4fb0c9934 100644 --- a/homeassistant/components/zwave/cover.py +++ b/homeassistant/components/zwave/cover.py @@ -3,6 +3,7 @@ import logging from homeassistant.components.cover import ( ATTR_POSITION, + ATTR_TILT_POSITION, DOMAIN, SUPPORT_CLOSE, SUPPORT_OPEN, @@ -14,11 +15,13 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from . import ( CONF_INVERT_OPENCLOSE_BUTTONS, CONF_INVERT_PERCENT, + CONF_TILT_OPEN_POSITION, ZWaveDeviceEntity, workaround, ) from .const import ( COMMAND_CLASS_BARRIER_OPERATOR, + COMMAND_CLASS_MANUFACTURER_PROPRIETARY, COMMAND_CLASS_SWITCH_BINARY, COMMAND_CLASS_SWITCH_MULTILEVEL, DATA_NETWORK, @@ -29,6 +32,23 @@ _LOGGER = logging.getLogger(__name__) SUPPORT_GARAGE = SUPPORT_OPEN | SUPPORT_CLOSE +def _to_hex_str(id_in_bytes): + """Convert a two byte value to a hex string. + + Example: 0x1234 --> '0x1234' + """ + return "0x{:04x}".format(id_in_bytes) + + +# For some reason node.manufacturer_id is of type string. So we need to convert +# the values. +FIBARO = _to_hex_str(workaround.FIBARO) +FIBARO222_SHUTTERS = [ + _to_hex_str(workaround.FGR222_SHUTTER2), + _to_hex_str(workaround.FGRM222_SHUTTER2), +] + + async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Old method of setting up Z-Wave covers.""" pass @@ -53,6 +73,17 @@ def get_device(hass, values, node_config, **kwargs): values.primary.command_class == COMMAND_CLASS_SWITCH_MULTILEVEL and values.primary.index == 0 ): + if ( + values.primary.node.manufacturer_id == FIBARO + and values.primary.node.product_type in FIBARO222_SHUTTERS + ): + return FibaroFGRM222( + hass, + values, + invert_buttons, + invert_percent, + node_config.get(CONF_TILT_OPEN_POSITION), + ) return ZwaveRollershutter(hass, values, invert_buttons, invert_percent) if values.primary.command_class == COMMAND_CLASS_SWITCH_BINARY: return ZwaveGarageDoorSwitch(values) @@ -212,3 +243,116 @@ class ZwaveGarageDoorBarrier(ZwaveGarageDoorBase): def open_cover(self, **kwargs): """Open the garage door.""" self.values.primary.data = "Opened" + + +class FibaroFGRM222(ZwaveRollershutter): + """Implementation of proprietary features for Fibaro FGR-222 / FGRM-222. + + This adds support for the tilt feature for the ventian blind mode. + To enable this you need to configure the devices to use the venetian blind + mode and to enable the proprietary command class: + * Set "3: Reports type to Blind position reports sent" + to value "the main controller using Fibaro Command Class" + * Set "10: Roller Shutter operating modes" + to value "2 - Venetian Blind Mode, with positioning" + """ + + def __init__( + self, hass, values, invert_buttons, invert_percent, open_tilt_position: int + ): + """Initialize the FGRM-222.""" + self._value_blinds = None + self._value_tilt = None + self._has_tilt_mode = False # type: bool + self._open_tilt_position = 50 # type: int + if open_tilt_position is not None: + self._open_tilt_position = open_tilt_position + super().__init__(hass, values, invert_buttons, invert_percent) + + @property + def current_cover_tilt_position(self) -> int: + """Get the tilt of the blinds. + + Saturate values <5 and >94 so that it's easier to detect the end + positions in automations. + """ + if not self._has_tilt_mode: + return None + if self._value_tilt.data <= 5: + return 0 + if self._value_tilt.data >= 95: + return 100 + return self._value_tilt.data + + def set_cover_tilt_position(self, **kwargs): + """Move the cover tilt to a specific position.""" + if not self._has_tilt_mode: + _LOGGER.error("Can't set cover tilt as device is not yet set up.") + else: + # Limit the range to [0-99], as this what that the ZWave command + # accepts. + tilt_position = max(0, min(99, kwargs.get(ATTR_TILT_POSITION))) + _LOGGER.debug("setting tilt to %d", tilt_position) + self._value_tilt.data = tilt_position + + def open_cover_tilt(self, **kwargs): + """Set slats to horizontal position.""" + self.set_cover_tilt_position(tilt_position=self._open_tilt_position) + + def close_cover_tilt(self, **kwargs): + """Close the slats.""" + self.set_cover_tilt_position(tilt_position=0) + + def set_cover_position(self, **kwargs): + """Move the roller shutter to a specific position. + + If the venetian blinds mode is not activated, fall back to + the behavior of the parent class. + """ + if not self._has_tilt_mode: + super().set_cover_position(**kwargs) + else: + _LOGGER.debug("Setting cover position to %s", kwargs.get(ATTR_POSITION)) + self._value_blinds.data = kwargs.get(ATTR_POSITION) + + def _configure_values(self): + """Get the value objects from the node.""" + for value in self.node.get_values( + class_id=COMMAND_CLASS_MANUFACTURER_PROPRIETARY + ).values(): + if value is None: + continue + if value.index == 0: + self._value_blinds = value + elif value.index == 1: + self._value_tilt = value + else: + _LOGGER.warning( + "Undefined index %d for this command class", value.index + ) + + if self._value_tilt is not None: + # We reached here because the user has configured the Fibaro to + # report using the MANUFACTURER_PROPRIETARY command class. The only + # reason for the user to configure this way is if tilt support is + # needed (aka venetian blind mode). Therefore, turn it on. + # + # Note: This is safe to do even if the user has accidentally set + # this configuration parameter, or configuration parameter 10 to + # something other than venetian blind mode. The controller will just + # ignore potential tilt settings sent from home assistant in this + # case. + self._has_tilt_mode = True + _LOGGER.info( + "Zwave node %s is a Fibaro FGR-222/FGRM-222 with tilt support.", + self.node_id, + ) + + def update_properties(self): + """React on properties being updated.""" + if not self._has_tilt_mode: + self._configure_values() + if self._value_blinds is not None: + self._current_position = self._value_blinds.data + else: + super().update_properties() From 31cd0af47a2a6784eb197885931720ca860dfd0b Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 17 Dec 2019 16:26:36 +0100 Subject: [PATCH 328/677] Upgrade matrix-client to 0.3.2 (#30027) --- homeassistant/components/matrix/__init__.py | 41 +++++++------------ homeassistant/components/matrix/const.py | 4 ++ homeassistant/components/matrix/manifest.json | 2 +- homeassistant/components/matrix/notify.py | 13 +++--- homeassistant/components/matrix/services.yaml | 4 +- requirements_all.txt | 2 +- 6 files changed, 30 insertions(+), 36 deletions(-) create mode 100644 homeassistant/components/matrix/const.py diff --git a/homeassistant/components/matrix/__init__.py b/homeassistant/components/matrix/__init__.py index 71735bd7e51..f8a57572d04 100644 --- a/homeassistant/components/matrix/__init__.py +++ b/homeassistant/components/matrix/__init__.py @@ -1,4 +1,4 @@ -"""The matrix bot component.""" +"""The Matrix bot component.""" from functools import partial import logging import os @@ -19,6 +19,8 @@ from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv from homeassistant.util.json import load_json, save_json +from .const import DOMAIN, SERVICE_SEND_MESSAGE + _LOGGER = logging.getLogger(__name__) SESSION_FILE = ".matrix.conf" @@ -31,10 +33,7 @@ CONF_EXPRESSION = "expression" EVENT_MATRIX_COMMAND = "matrix_command" -DOMAIN = "matrix" - COMMAND_SCHEMA = vol.All( - # Basic Schema vol.Schema( { vol.Exclusive(CONF_WORD, "trigger"): cv.string, @@ -43,7 +42,6 @@ COMMAND_SCHEMA = vol.All( vol.Optional(CONF_ROOMS, default=[]): vol.All(cv.ensure_list, [cv.string]), } ), - # Make sure it's either a word or an expression command cv.has_at_least_one_key(CONF_WORD, CONF_EXPRESSION), ) @@ -65,7 +63,6 @@ CONFIG_SCHEMA = vol.Schema( extra=vol.ALLOW_EXTRA, ) -SERVICE_SEND_MESSAGE = "send_message" SERVICE_SCHEMA_SEND_MESSAGE = vol.Schema( { @@ -77,7 +74,6 @@ SERVICE_SCHEMA_SEND_MESSAGE = vol.Schema( def setup(hass, config): """Set up the Matrix bot component.""" - config = config[DOMAIN] try: @@ -138,11 +134,11 @@ class MatrixBot: # so we only do it once per room. self._aliases_fetched_for = set() - # word commands are stored dict-of-dict: First dict indexes by room ID + # Word commands are stored dict-of-dict: First dict indexes by room ID # / alias, second dict indexes by the word self._word_commands = {} - # regular expression commands are stored as a list of commands per + # Regular expression commands are stored as a list of commands per # room, i.e., a dict-of-list self._expression_commands = {} @@ -184,7 +180,7 @@ class MatrixBot: self.hass.bus.listen_once(EVENT_HOMEASSISTANT_START, handle_startup) def _handle_room_message(self, room_id, room, event): - """Handle a message sent to a room.""" + """Handle a message sent to a Matrix room.""" if event["content"]["msgtype"] != "m.text": return @@ -194,7 +190,7 @@ class MatrixBot: _LOGGER.debug("Handling message: %s", event["content"]["body"]) if event["content"]["body"][0] == "!": - # Could trigger a single-word command. + # Could trigger a single-word command pieces = event["content"]["body"].split(" ") cmd = pieces[0][1:] @@ -248,8 +244,7 @@ class MatrixBot: return room def _join_rooms(self): - """Join the rooms that we listen for commands in.""" - + """Join the Matrix rooms that we listen for commands in.""" for room_id in self._listening_rooms: try: room = self._join_or_get_room(room_id) @@ -285,8 +280,7 @@ class MatrixBot: save_json(self._session_filepath, self._auth_tokens) def _login(self): - """Login to the matrix homeserver and return the client instance.""" - + """Login to the Matrix homeserver and return the client instance.""" # Attempt to generate a valid client using either of the two possible # login methods: client = None @@ -299,13 +293,12 @@ class MatrixBot: except MatrixRequestError as ex: _LOGGER.warning( - "Login by token failed, falling back to password. " - "login_by_token raised: (%d) %s", + "Login by token failed, falling back to password: %d, %s", ex.code, ex.content, ) - # If we still don't have a client try password. + # If we still don't have a client try password if not client: try: client = self._login_by_password() @@ -313,20 +306,17 @@ class MatrixBot: except MatrixRequestError as ex: _LOGGER.error( - "Login failed, both token and username/password invalid " - "login_by_password raised: (%d) %s", + "Login failed, both token and username/password invalid: %d, %s", ex.code, ex.content, ) - - # re-raise the error so _setup can catch it. + # Re-raise the error so _setup can catch it raise return client def _login_by_token(self): """Login using authentication token and return the client.""" - return MatrixClient( base_url=self._homeserver, token=self._auth_tokens[self._mx_id], @@ -336,7 +326,6 @@ class MatrixBot: def _login_by_password(self): """Login using password authentication and return the client.""" - _client = MatrixClient( base_url=self._homeserver, valid_cert_check=self._verify_tls ) @@ -348,7 +337,7 @@ class MatrixBot: return _client def _send_message(self, message, target_rooms): - """Send the message to the matrix server.""" + """Send the message to the Matrix server.""" for target_room in target_rooms: try: @@ -356,7 +345,7 @@ class MatrixBot: _LOGGER.debug(room.send_text(message)) except MatrixRequestError as ex: _LOGGER.error( - "Unable to deliver message to room '%s': (%d): %s", + "Unable to deliver message to room '%s': %d, %s", target_room, ex.code, ex.content, diff --git a/homeassistant/components/matrix/const.py b/homeassistant/components/matrix/const.py new file mode 100644 index 00000000000..6b082bde121 --- /dev/null +++ b/homeassistant/components/matrix/const.py @@ -0,0 +1,4 @@ +"""Constants for the Matrix integration.""" +DOMAIN = "matrix" + +SERVICE_SEND_MESSAGE = "send_message" diff --git a/homeassistant/components/matrix/manifest.json b/homeassistant/components/matrix/manifest.json index a467518c04e..e7d1fab6874 100644 --- a/homeassistant/components/matrix/manifest.json +++ b/homeassistant/components/matrix/manifest.json @@ -3,7 +3,7 @@ "name": "Matrix", "documentation": "https://www.home-assistant.io/integrations/matrix", "requirements": [ - "matrix-client==0.2.0" + "matrix-client==0.3.2" ], "dependencies": [], "codeowners": [ diff --git a/homeassistant/components/matrix/notify.py b/homeassistant/components/matrix/notify.py index 06e9712becc..9f1f0eb992a 100644 --- a/homeassistant/components/matrix/notify.py +++ b/homeassistant/components/matrix/notify.py @@ -11,32 +11,33 @@ from homeassistant.components.notify import ( ) import homeassistant.helpers.config_validation as cv +from .const import DOMAIN, SERVICE_SEND_MESSAGE + _LOGGER = logging.getLogger(__name__) CONF_DEFAULT_ROOM = "default_room" -DOMAIN = "matrix" PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({vol.Required(CONF_DEFAULT_ROOM): cv.string}) def get_service(hass, config, discovery_info=None): """Get the Matrix notification service.""" - return MatrixNotificationService(config.get(CONF_DEFAULT_ROOM)) + return MatrixNotificationService(config[CONF_DEFAULT_ROOM]) class MatrixNotificationService(BaseNotificationService): - """Send Notifications to a Matrix Room.""" + """Send notifications to a Matrix room.""" def __init__(self, default_room): - """Set up the notification service.""" + """Set up the Matrix notification service.""" self._default_room = default_room def send_message(self, message="", **kwargs): - """Send the message to the matrix server.""" + """Send the message to the Matrix server.""" target_rooms = kwargs.get(ATTR_TARGET) or [self._default_room] service_data = {ATTR_TARGET: target_rooms, ATTR_MESSAGE: message} return self.hass.services.call( - DOMAIN, "send_message", service_data=service_data + DOMAIN, SERVICE_SEND_MESSAGE, service_data=service_data ) diff --git a/homeassistant/components/matrix/services.yaml b/homeassistant/components/matrix/services.yaml index 1cf83de2c33..03c441a39ec 100644 --- a/homeassistant/components/matrix/services.yaml +++ b/homeassistant/components/matrix/services.yaml @@ -2,8 +2,8 @@ send_message: description: Send message to target room(s) fields: message: - description: The message to be sent + description: The message to be sent. example: 'This is a message I am sending to matrix' target: - description: A list of room(s) to send the message to + description: A list of room(s) to send the message to. example: '#hasstest:matrix.org' \ No newline at end of file diff --git a/requirements_all.txt b/requirements_all.txt index 7984cd4a32a..d9e9a15998a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -819,7 +819,7 @@ lyft_rides==0.2 magicseaweed==1.0.3 # homeassistant.components.matrix -matrix-client==0.2.0 +matrix-client==0.3.2 # homeassistant.components.maxcube maxcube-api==0.1.0 From 876195a8a8c2e3fed27df0ffa88ef2206e272e56 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 17 Dec 2019 17:00:00 +0100 Subject: [PATCH 329/677] Upgrade zeroconf to 0.24.1 (#30028) --- homeassistant/components/zeroconf/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/zeroconf/manifest.json b/homeassistant/components/zeroconf/manifest.json index ba764300dae..c02ac425445 100644 --- a/homeassistant/components/zeroconf/manifest.json +++ b/homeassistant/components/zeroconf/manifest.json @@ -3,7 +3,7 @@ "name": "Zeroconf", "documentation": "https://www.home-assistant.io/integrations/zeroconf", "requirements": [ - "zeroconf==0.24.0" + "zeroconf==0.24.1" ], "dependencies": [ "api" diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index c0b7933fcca..bb212b3e0b4 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -24,7 +24,7 @@ ruamel.yaml==0.15.100 sqlalchemy==1.3.11 voluptuous-serialize==2.3.0 voluptuous==0.11.7 -zeroconf==0.24.0 +zeroconf==0.24.1 pycryptodome>=3.6.6 diff --git a/requirements_all.txt b/requirements_all.txt index d9e9a15998a..8515e78c5a1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2086,7 +2086,7 @@ youtube_dl==2019.11.28 zengge==0.2 # homeassistant.components.zeroconf -zeroconf==0.24.0 +zeroconf==0.24.1 # homeassistant.components.zha zha-quirks==0.0.28 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 20f8e8efc76..07f2b446e24 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -658,7 +658,7 @@ ya_ma==0.3.8 yahooweather==0.10 # homeassistant.components.zeroconf -zeroconf==0.24.0 +zeroconf==0.24.1 # homeassistant.components.zha zha-quirks==0.0.28 From 0135b988ff452f6ad49cc3a47b888a1ef1673efb Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Wed, 18 Dec 2019 00:32:19 +0000 Subject: [PATCH 330/677] [ci skip] Translation update --- .../components/elgato/.translations/sl.json | 27 +++++++++++++ .../components/icloud/.translations/sl.json | 38 +++++++++++++++++++ .../components/soma/.translations/sl.json | 4 +- 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/elgato/.translations/sl.json create mode 100644 homeassistant/components/icloud/.translations/sl.json diff --git a/homeassistant/components/elgato/.translations/sl.json b/homeassistant/components/elgato/.translations/sl.json new file mode 100644 index 00000000000..f05b0bcbd8f --- /dev/null +++ b/homeassistant/components/elgato/.translations/sl.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Ta naprava Elgato Key Light je \u017ee nastavljena.", + "connection_error": "Povezava z napravo Elgato Key Light ni uspela." + }, + "error": { + "connection_error": "Povezava z napravo Elgato Key Light ni uspela." + }, + "flow_title": "Elgato Key Light: {serial_number}", + "step": { + "user": { + "data": { + "host": "Gostitelj ali IP naslov", + "port": "\u0160tevilka vrat" + }, + "description": "Nastavite svojo Elgato Key Light tako, da se bo vklju\u010dila v Home Assistant.", + "title": "Pove\u017eite svojo Elgato Key Light" + }, + "zeroconf_confirm": { + "description": "Ali \u017eelite dodati Elgato Key Light s serijsko \u0161tevilko ' {serial_number} ' v Home Assistant-a?", + "title": "Odkrita naprava Elgato Key Light" + } + }, + "title": "Elgato Key Light" + } +} \ No newline at end of file diff --git a/homeassistant/components/icloud/.translations/sl.json b/homeassistant/components/icloud/.translations/sl.json new file mode 100644 index 00000000000..91cb4312cb3 --- /dev/null +++ b/homeassistant/components/icloud/.translations/sl.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "username_exists": "Ra\u010dun \u017ee nastavljen" + }, + "error": { + "login": "Napaka pri prijavi: preverite svoj e-po\u0161tni naslov in geslo", + "send_verification_code": "Kode za preverjanje ni bilo mogo\u010de poslati", + "username_exists": "Ra\u010dun \u017ee nastavljen", + "validate_verification_code": "Kode za preverjanje ni bilo mogo\u010de preveriti, izberi napravo za zaupanje in znova za\u017eeni preverjanje" + }, + "step": { + "trusted_device": { + "data": { + "trusted_device": "Zaupanja vredna naprava" + }, + "description": "Izberite svojo zaupanja vredno napravo", + "title": "iCloud zaupanja vredna naprava" + }, + "user": { + "data": { + "password": "Geslo", + "username": "E-po\u0161tni naslov" + }, + "description": "Vnesite svoje poverilnice", + "title": "iCloud poverilnice" + }, + "verification_code": { + "data": { + "verification_code": "Koda za preverjanje" + }, + "description": "Prosimo, vnesite kodo za preverjanje, ki ste jo pravkar prejeli od iCloud", + "title": "iCloud koda za preverjanje" + } + }, + "title": "Apple iCloud" + } +} \ No newline at end of file diff --git a/homeassistant/components/soma/.translations/sl.json b/homeassistant/components/soma/.translations/sl.json index b3075208d2c..01f7e50eb96 100644 --- a/homeassistant/components/soma/.translations/sl.json +++ b/homeassistant/components/soma/.translations/sl.json @@ -3,7 +3,9 @@ "abort": { "already_setup": "Nastavite lahko samo en ra\u010dun Soma.", "authorize_url_timeout": "\u010casovna omejitev za generiranje potrditvenega URL-ja je potekla.", - "missing_configuration": "Komponenta Soma ni konfigurirana. Upo\u0161tevajte dokumentacijo." + "connection_error": "Povezava s SOMA Connect ni uspela.", + "missing_configuration": "Komponenta Soma ni konfigurirana. Upo\u0161tevajte dokumentacijo.", + "result_error": "SOMA Connect se je odzvala s statusom napake." }, "create_entry": { "default": "Uspe\u0161no overjen s Soma." From feb39c39a3ac713a5ba27821a6f12debb153eb9c Mon Sep 17 00:00:00 2001 From: Greg <34967045+gtdiehl@users.noreply.github.com> Date: Tue, 17 Dec 2019 16:51:19 -0800 Subject: [PATCH 331/677] Update Envoy sensor to configure credentials and grab Inverter Date from updated API (#28837) * Update manifest.json Updated sensor to use the latest version of the API to be able to use the new features. * Updated sensor to use new API features. Configurable credentials and Inverter last reported date. * EnvoyReader is passed to Envoy uses async update() * Fixed pydocstyle warnings * Fixed merge issue. Had extra variable. * Added warning message when authentication for Inverter data fails * Added continue after exception for loop * Moved if statement outside of try/except * Removed unneeded boolean attribute --- .../components/enphase_envoy/manifest.json | 2 +- .../components/enphase_envoy/sensor.py | 55 +++++++++++++++---- requirements_all.txt | 2 +- 3 files changed, 46 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/enphase_envoy/manifest.json b/homeassistant/components/enphase_envoy/manifest.json index 5b5f94f7c8c..21d154be23a 100644 --- a/homeassistant/components/enphase_envoy/manifest.json +++ b/homeassistant/components/enphase_envoy/manifest.json @@ -3,7 +3,7 @@ "name": "Enphase envoy", "documentation": "https://www.home-assistant.io/integrations/enphase_envoy", "requirements": [ - "envoy_reader==0.8.6" + "envoy_reader==0.10.0" ], "dependencies": [], "codeowners": [] diff --git a/homeassistant/components/enphase_envoy/sensor.py b/homeassistant/components/enphase_envoy/sensor.py index 3977326c06d..a2b50f20eb6 100644 --- a/homeassistant/components/enphase_envoy/sensor.py +++ b/homeassistant/components/enphase_envoy/sensor.py @@ -2,6 +2,7 @@ import logging from envoy_reader.envoy_reader import EnvoyReader +import requests import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA @@ -9,6 +10,8 @@ from homeassistant.const import ( CONF_IP_ADDRESS, CONF_MONITORED_CONDITIONS, CONF_NAME, + CONF_PASSWORD, + CONF_USERNAME, ENERGY_WATT_HOUR, POWER_WATT, ) @@ -42,6 +45,8 @@ CONST_DEFAULT_HOST = "envoy" PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Optional(CONF_IP_ADDRESS, default=CONST_DEFAULT_HOST): cv.string, + vol.Optional(CONF_USERNAME, default="envoy"): cv.string, + vol.Optional(CONF_PASSWORD, default=""): cv.string, vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSORS)): vol.All( cv.ensure_list, [vol.In(list(SENSORS))] ), @@ -52,30 +57,42 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Enphase Envoy sensor.""" - ip_address = config[CONF_IP_ADDRESS] monitored_conditions = config[CONF_MONITORED_CONDITIONS] name = config[CONF_NAME] + username = config[CONF_USERNAME] + password = config[CONF_PASSWORD] + + envoy_reader = EnvoyReader(ip_address, username, password) entities = [] # Iterate through the list of sensors for condition in monitored_conditions: if condition == "inverters": - inverters = await EnvoyReader(ip_address).inverters_production() + try: + inverters = await envoy_reader.inverters_production() + except requests.exceptions.HTTPError: + _LOGGER.warning( + "Authentication for Inverter data failed during setup: %s", + ip_address, + ) + continue + if isinstance(inverters, dict): for inverter in inverters: entities.append( Envoy( - ip_address, + envoy_reader, condition, f"{name}{SENSORS[condition][0]} {inverter}", SENSORS[condition][1], ) ) + else: entities.append( Envoy( - ip_address, + envoy_reader, condition, f"{name}{SENSORS[condition][0]}", SENSORS[condition][1], @@ -87,13 +104,14 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= class Envoy(Entity): """Implementation of the Enphase Envoy sensors.""" - def __init__(self, ip_address, sensor_type, name, unit): + def __init__(self, envoy_reader, sensor_type, name, unit): """Initialize the sensor.""" - self._ip_address = ip_address + self._envoy_reader = envoy_reader + self._type = sensor_type self._name = name self._unit_of_measurement = unit - self._type = sensor_type self._state = None + self._last_reported = None @property def name(self): @@ -115,11 +133,18 @@ class Envoy(Entity): """Icon to use in the frontend, if any.""" return ICON + @property + def device_state_attributes(self): + """Return the state attributes.""" + if self._type == "inverters": + return {"last_reported": self._last_reported} + + return None + async def async_update(self): """Get the energy production data from the Enphase Envoy.""" - if self._type != "inverters": - _state = await getattr(EnvoyReader(self._ip_address), self._type)() + _state = await getattr(self._envoy_reader, self._type)() if isinstance(_state, int): self._state = _state else: @@ -127,9 +152,17 @@ class Envoy(Entity): self._state = None elif self._type == "inverters": - inverters = await (EnvoyReader(self._ip_address).inverters_production()) + try: + inverters = await (self._envoy_reader.inverters_production()) + except requests.exceptions.HTTPError: + _LOGGER.warning( + "Authentication for Inverter data failed during update: %s", + self._envoy_reader.host, + ) + if isinstance(inverters, dict): serial_number = self._name.split(" ")[2] - self._state = inverters[serial_number] + self._state = inverters[serial_number][0] + self._last_reported = inverters[serial_number][1] else: self._state = None diff --git a/requirements_all.txt b/requirements_all.txt index 8515e78c5a1..4c167459609 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -483,7 +483,7 @@ env_canada==0.0.30 # envirophat==0.0.6 # homeassistant.components.enphase_envoy -envoy_reader==0.8.6 +envoy_reader==0.10.0 # homeassistant.components.season ephem==3.7.7.0 From 9c7caaa142962a11eb06ff0fc7110c22e0bc5ff3 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 18 Dec 2019 07:41:01 +0100 Subject: [PATCH 332/677] Add option to ignore flows (#30008) --- .../components/config/config_entries.py | 35 ++++++++++++++++ homeassistant/components/hue/config_flow.py | 2 +- homeassistant/config_entries.py | 12 +++++- .../components/config/test_config_entries.py | 39 ++++++++++++++++++ tests/components/hue/test_config_flow.py | 7 +++- tests/test_config_entries.py | 40 +++++++++++++++++++ 6 files changed, 132 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/config/config_entries.py b/homeassistant/components/config/config_entries.py index 81065665e34..dbf0ee8f283 100644 --- a/homeassistant/components/config/config_entries.py +++ b/homeassistant/components/config/config_entries.py @@ -33,6 +33,7 @@ async def async_setup(hass): hass.components.websocket_api.async_register_command(config_entries_progress) hass.components.websocket_api.async_register_command(system_options_list) hass.components.websocket_api.async_register_command(system_options_update) + hass.components.websocket_api.async_register_command(ignore_config_flow) return True @@ -284,3 +285,37 @@ async def system_options_update(hass, connection, msg): hass.config_entries.async_update_entry(entry, system_options=changes) connection.send_result(msg["id"], entry.system_options.as_dict()) + + +@websocket_api.require_admin +@websocket_api.async_response +@websocket_api.websocket_command({"type": "config_entries/ignore_flow", "flow_id": str}) +async def ignore_config_flow(hass, connection, msg): + """Ignore a config flow.""" + flow = next( + ( + flw + for flw in hass.config_entries.flow.async_progress() + if flw["flow_id"] == msg["flow_id"] + ), + None, + ) + + if flow is None: + connection.send_error( + msg["id"], websocket_api.const.ERR_NOT_FOUND, "Config entry not found" + ) + return + + if "unique_id" not in flow["context"]: + connection.send_error( + msg["id"], "no_unique_id", "Specified flow has no unique ID." + ) + return + + await hass.config_entries.flow.async_init( + flow["handler"], + context={"source": config_entries.SOURCE_IGNORE}, + data={"unique_id": flow["context"]["unique_id"]}, + ) + connection.send_result(msg["id"]) diff --git a/homeassistant/components/hue/config_flow.py b/homeassistant/components/hue/config_flow.py index f2d7c6d1e8a..97cc1a8d66c 100644 --- a/homeassistant/components/hue/config_flow.py +++ b/homeassistant/components/hue/config_flow.py @@ -75,7 +75,7 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return self.async_abort(reason="no_bridges") # Find already configured hosts - already_configured = self._async_current_ids() + already_configured = self._async_current_ids(False) bridges = [bridge for bridge in bridges if bridge.id not in already_configured] if not bridges: diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index d39fc4803ea..1a010b38e70 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -24,6 +24,7 @@ SOURCE_IMPORT = "import" SOURCE_SSDP = "ssdp" SOURCE_USER = "user" SOURCE_ZEROCONF = "zeroconf" +SOURCE_IGNORE = "ignore" HANDLERS = Registry() @@ -157,6 +158,9 @@ class ConfigEntry: tries: int = 0, ) -> None: """Set up an entry.""" + if self.source == SOURCE_IGNORE: + return + if integration is None: integration = await loader.async_get_integration(hass, self.domain) @@ -792,12 +796,13 @@ class ConfigFlow(data_entry_flow.FlowHandler): return self.hass.config_entries.async_entries(self.handler) @callback - def _async_current_ids(self) -> Set[Optional[str]]: + def _async_current_ids(self, include_ignore: bool = True) -> Set[Optional[str]]: """Return current unique IDs.""" assert self.hass is not None return set( entry.unique_id for entry in self.hass.config_entries.async_entries(self.handler) + if include_ignore or entry.source != SOURCE_IGNORE ) @callback @@ -810,6 +815,11 @@ class ConfigFlow(data_entry_flow.FlowHandler): if flw["handler"] == self.handler and flw["flow_id"] != self.flow_id ] + async def async_step_ignore(self, user_input: Dict[str, Any]) -> Dict[str, Any]: + """Ignore this config flow.""" + await self.async_set_unique_id(user_input["unique_id"], raise_on_progress=False) + return self.async_create_entry(title="Ignored", data={}) + class OptionsFlowManager: """Flow to set options for a configuration entry.""" diff --git a/tests/components/config/test_config_entries.py b/tests/components/config/test_config_entries.py index 6176bd73c52..6631bbf8fbf 100644 --- a/tests/components/config/test_config_entries.py +++ b/tests/components/config/test_config_entries.py @@ -634,3 +634,42 @@ async def test_update_system_options(hass, hass_ws_client): assert response["success"] assert response["result"]["disable_new_entities"] assert entry.system_options.disable_new_entities + + +async def test_ignore_flow(hass, hass_ws_client): + """Test we can ignore a flow.""" + assert await async_setup_component(hass, "config", {}) + mock_integration(hass, MockModule("test", async_setup_entry=mock_coro_func(True))) + mock_entity_platform(hass, "config_flow.test", None) + + class TestFlow(core_ce.ConfigFlow): + VERSION = 1 + + async def async_step_user(self, user_input=None): + await self.async_set_unique_id("mock-unique-id") + return self.async_show_form(step_id="account", data_schema=vol.Schema({})) + + ws_client = await hass_ws_client(hass) + + with patch.dict(HANDLERS, {"test": TestFlow}): + result = await hass.config_entries.flow.async_init( + "test", context={"source": "user"} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + + await ws_client.send_json( + { + "id": 5, + "type": "config_entries/ignore_flow", + "flow_id": result["flow_id"], + } + ) + response = await ws_client.receive_json() + + assert response["success"] + + assert len(hass.config_entries.flow.async_progress()) == 0 + + entry = hass.config_entries.async_entries("test")[0] + assert entry.source == "ignore" + assert entry.unique_id == "mock-unique-id" diff --git a/tests/components/hue/test_config_flow.py b/tests/components/hue/test_config_flow.py index fe9a1f0e32c..64a9c81136a 100644 --- a/tests/components/hue/test_config_flow.py +++ b/tests/components/hue/test_config_flow.py @@ -6,7 +6,7 @@ import aiohue import pytest import voluptuous as vol -from homeassistant import data_entry_flow +from homeassistant import config_entries, data_entry_flow from homeassistant.components.hue import config_flow, const from tests.common import MockConfigEntry, mock_coro @@ -95,6 +95,11 @@ async def test_flow_one_bridge_discovered(hass, aioclient_mock): async def test_flow_two_bridges_discovered(hass, aioclient_mock): """Test config flow discovers two bridges.""" + # Add ignored config entry. Should still show up as option. + MockConfigEntry( + domain="hue", source=config_entries.SOURCE_IGNORE, unique_id="bla" + ).add_to_hass(hass) + aioclient_mock.get( const.API_NUPNP, json=[ diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index d83de450227..19f84e94570 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -1146,3 +1146,43 @@ async def test_finish_flow_aborts_progress(hass, manager): assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert len(hass.config_entries.flow.async_progress()) == 0 + + +async def test_unique_id_ignore(hass, manager): + """Test that we can ignore flows that are in progress and have a unique ID.""" + async_setup_entry = MagicMock(return_value=mock_coro(False)) + mock_integration(hass, MockModule("comp", async_setup_entry=async_setup_entry)) + mock_entity_platform(hass, "config_flow.comp", None) + + class TestFlow(config_entries.ConfigFlow): + + VERSION = 1 + + async def async_step_user(self, user_input=None): + await self.async_set_unique_id("mock-unique-id") + return self.async_show_form(step_id="discovery") + + with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}): + # Create one to be in progress + result = await manager.flow.async_init( + "comp", context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + + result2 = await manager.flow.async_init( + "comp", + context={"source": config_entries.SOURCE_IGNORE}, + data={"unique_id": "mock-unique-id"}, + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + + # assert len(hass.config_entries.flow.async_progress()) == 0 + + # We should never set up an ignored entry. + assert len(async_setup_entry.mock_calls) == 0 + + entry = hass.config_entries.async_entries("comp")[0] + + assert entry.source == "ignore" + assert entry.unique_id == "mock-unique-id" From d16011b849088d17af6ed54657eeec3c837d4fd1 Mon Sep 17 00:00:00 2001 From: Erik Kastelec <34520112+erikkastelec@users.noreply.github.com> Date: Wed, 18 Dec 2019 14:47:01 +0100 Subject: [PATCH 333/677] changed Venstar component temperature to half degree accuracy (#30034) --- homeassistant/components/venstar/climate.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/venstar/climate.py b/homeassistant/components/venstar/climate.py index df7dfcacb20..4ffe75acb9e 100644 --- a/homeassistant/components/venstar/climate.py +++ b/homeassistant/components/venstar/climate.py @@ -34,7 +34,7 @@ from homeassistant.const import ( CONF_SSL, CONF_TIMEOUT, CONF_USERNAME, - PRECISION_WHOLE, + PRECISION_HALVES, STATE_ON, TEMP_CELSIUS, TEMP_FAHRENHEIT, @@ -134,9 +134,9 @@ class VenstarThermostat(ClimateDevice): """Return the precision of the system. Venstar temperature values are passed back and forth in the - API as whole degrees C or F. + API in C or F, with half-degree accuracy. """ - return PRECISION_WHOLE + return PRECISION_HALVES @property def temperature_unit(self): From bdef54de0c10e512b5375c692c7e2749c18f2ea5 Mon Sep 17 00:00:00 2001 From: omriasta Date: Wed, 18 Dec 2019 15:04:54 -0500 Subject: [PATCH 334/677] Patch rachio (#30031) * fix binary sensor offline/online fixed self._handle_update on line 66 to produce args, kwargs. Updated the binary sensor to check the correct index in the tuple. * Fixed Standby switch Set standby switch to poll in order to get status when homeassistant starts up. Updated the index for the switch to get the status from the tuple. --- homeassistant/components/rachio/binary_sensor.py | 6 +++--- homeassistant/components/rachio/switch.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/rachio/binary_sensor.py b/homeassistant/components/rachio/binary_sensor.py index f74e3ca1802..f13eba59ac9 100644 --- a/homeassistant/components/rachio/binary_sensor.py +++ b/homeassistant/components/rachio/binary_sensor.py @@ -63,7 +63,7 @@ class RachioControllerBinarySensor(BinarySensorDevice): return # For this device - self._handle_update() + self._handle_update(args, kwargs) @abstractmethod def _poll_update(self, data=None) -> bool: @@ -119,9 +119,9 @@ class RachioControllerOnlineBinarySensor(RachioControllerBinarySensor): def _handle_update(self, *args, **kwargs) -> None: """Handle an update to the state of this sensor.""" - if args[0][KEY_SUBTYPE] == SUBTYPE_ONLINE: + if args[0][0][KEY_SUBTYPE] == SUBTYPE_ONLINE: self._state = True - elif args[0][KEY_SUBTYPE] == SUBTYPE_OFFLINE: + elif args[0][0][KEY_SUBTYPE] == SUBTYPE_OFFLINE: self._state = False self.schedule_update_ha_state() diff --git a/homeassistant/components/rachio/switch.py b/homeassistant/components/rachio/switch.py index 80c227a6df6..a3a4f6bcca1 100644 --- a/homeassistant/components/rachio/switch.py +++ b/homeassistant/components/rachio/switch.py @@ -107,7 +107,7 @@ class RachioStandbySwitch(RachioSwitch): dispatcher_connect( hass, SIGNAL_RACHIO_CONTROLLER_UPDATE, self._handle_any_update ) - super().__init__(controller, poll=False) + super().__init__(controller, poll=True) self._poll_update(controller.init_data) @property @@ -134,9 +134,9 @@ class RachioStandbySwitch(RachioSwitch): def _handle_update(self, *args, **kwargs) -> None: """Update the state using webhook data.""" - if args[0][KEY_SUBTYPE] == SUBTYPE_SLEEP_MODE_ON: + if args[0][0][KEY_SUBTYPE] == SUBTYPE_SLEEP_MODE_ON: self._state = True - elif args[0][KEY_SUBTYPE] == SUBTYPE_SLEEP_MODE_OFF: + elif args[0][0][KEY_SUBTYPE] == SUBTYPE_SLEEP_MODE_OFF: self._state = False self.schedule_update_ha_state() From 0adb88156d166d7e60998d431d517c791c044504 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Wed, 18 Dec 2019 13:06:57 -0700 Subject: [PATCH 335/677] Bump simplisafe-python to 5.3.6 (#30055) --- homeassistant/components/simplisafe/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/simplisafe/manifest.json b/homeassistant/components/simplisafe/manifest.json index 4115ce455b5..2df49bb5209 100644 --- a/homeassistant/components/simplisafe/manifest.json +++ b/homeassistant/components/simplisafe/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/simplisafe", "requirements": [ - "simplisafe-python==5.3.5" + "simplisafe-python==5.3.6" ], "dependencies": [], "codeowners": [ diff --git a/requirements_all.txt b/requirements_all.txt index 4c167459609..5cecb396649 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1801,7 +1801,7 @@ shodan==1.21.0 simplepush==1.1.4 # homeassistant.components.simplisafe -simplisafe-python==5.3.5 +simplisafe-python==5.3.6 # homeassistant.components.sisyphus sisyphus-control==2.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 07f2b446e24..538262919ff 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -567,7 +567,7 @@ rxv==0.6.0 samsungctl[websocket]==0.7.1 # homeassistant.components.simplisafe -simplisafe-python==5.3.5 +simplisafe-python==5.3.6 # homeassistant.components.sleepiq sleepyq==0.7 From 41bef4b919cc9768b3a027811487cf1719f2ca20 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Wed, 18 Dec 2019 15:15:11 -0500 Subject: [PATCH 336/677] Add timer reload service. (#30015) --- homeassistant/components/timer/__init__.py | 54 ++++++++--- tests/components/timer/test_init.py | 103 ++++++++++++++++++++- 2 files changed, 140 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/timer/__init__.py b/homeassistant/components/timer/__init__.py index de60752eada..0cc707f5a45 100644 --- a/homeassistant/components/timer/__init__.py +++ b/homeassistant/components/timer/__init__.py @@ -4,11 +4,12 @@ import logging import voluptuous as vol -from homeassistant.const import CONF_ICON, CONF_NAME +from homeassistant.const import CONF_ICON, CONF_NAME, SERVICE_RELOAD import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.helpers.restore_state import RestoreEntity +import homeassistant.helpers.service import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) @@ -54,26 +55,31 @@ CONFIG_SCHEMA = vol.Schema( extra=vol.ALLOW_EXTRA, ) +RELOAD_SERVICE_SCHEMA = vol.Schema({}) + async def async_setup(hass, config): """Set up a timer.""" component = EntityComponent(_LOGGER, DOMAIN, hass) - entities = [] + entities = await _async_process_config(hass, config) - for object_id, cfg in config[DOMAIN].items(): - if not cfg: - cfg = {} - - name = cfg.get(CONF_NAME) - icon = cfg.get(CONF_ICON) - duration = cfg.get(CONF_DURATION) - - entities.append(Timer(hass, object_id, name, icon, duration)) - - if not entities: - return False + async def reload_service_handler(service_call): + """Remove all input booleans and load new ones from config.""" + conf = await component.async_prepare_reload() + if conf is None: + return + new_entities = await _async_process_config(hass, conf) + if new_entities: + await component.async_add_entities(new_entities) + homeassistant.helpers.service.async_register_admin_service( + hass, + DOMAIN, + SERVICE_RELOAD, + reload_service_handler, + schema=RELOAD_SERVICE_SCHEMA, + ) component.async_register_entity_service( SERVICE_START, { @@ -87,10 +93,28 @@ async def async_setup(hass, config): component.async_register_entity_service(SERVICE_CANCEL, {}, "async_cancel") component.async_register_entity_service(SERVICE_FINISH, {}, "async_finish") - await component.async_add_entities(entities) + if entities: + await component.async_add_entities(entities) return True +async def _async_process_config(hass, config): + """Process config and create list of entities.""" + entities = [] + + for object_id, cfg in config[DOMAIN].items(): + if not cfg: + cfg = {} + + name = cfg.get(CONF_NAME) + icon = cfg.get(CONF_ICON) + duration = cfg.get(CONF_DURATION) + + entities.append(Timer(hass, object_id, name, icon, duration)) + + return entities + + class Timer(RestoreEntity): """Representation of a timer.""" diff --git a/tests/components/timer/test_init.py b/tests/components/timer/test_init.py index 39648b68fd7..547f7e4ab05 100644 --- a/tests/components/timer/test_init.py +++ b/tests/components/timer/test_init.py @@ -3,6 +3,9 @@ import asyncio from datetime import timedelta import logging +from unittest.mock import patch + +import pytest from homeassistant.components.timer import ( ATTR_DURATION, @@ -23,8 +26,14 @@ from homeassistant.components.timer import ( STATUS_IDLE, STATUS_PAUSED, ) -from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_ICON, CONF_ENTITY_ID -from homeassistant.core import CoreState +from homeassistant.const import ( + ATTR_FRIENDLY_NAME, + ATTR_ICON, + CONF_ENTITY_ID, + SERVICE_RELOAD, +) +from homeassistant.core import Context, CoreState +from homeassistant.exceptions import Unauthorized from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow @@ -195,3 +204,93 @@ def test_no_initial_state_and_no_restore_state(hass): state = hass.states.get("timer.test1") assert state assert state.state == STATUS_IDLE + + +async def test_config_reload(hass, hass_admin_user, hass_read_only_user): + """Test reload service.""" + count_start = len(hass.states.async_entity_ids()) + + _LOGGER.debug("ENTITIES @ start: %s", hass.states.async_entity_ids()) + + config = { + DOMAIN: { + "test_1": {}, + "test_2": { + CONF_NAME: "Hello World", + CONF_ICON: "mdi:work", + CONF_DURATION: 10, + }, + } + } + + assert await async_setup_component(hass, "timer", config) + await hass.async_block_till_done() + + assert count_start + 2 == len(hass.states.async_entity_ids()) + await hass.async_block_till_done() + + state_1 = hass.states.get("timer.test_1") + state_2 = hass.states.get("timer.test_2") + state_3 = hass.states.get("timer.test_3") + + assert state_1 is not None + assert state_2 is not None + assert state_3 is None + + assert STATUS_IDLE == state_1.state + assert ATTR_ICON not in state_1.attributes + assert ATTR_FRIENDLY_NAME not in state_1.attributes + + assert STATUS_IDLE == state_2.state + assert "Hello World" == state_2.attributes.get(ATTR_FRIENDLY_NAME) + assert "mdi:work" == state_2.attributes.get(ATTR_ICON) + assert "0:00:10" == state_2.attributes.get(ATTR_DURATION) + + with patch( + "homeassistant.config.load_yaml_config_file", + autospec=True, + return_value={ + DOMAIN: { + "test_2": { + CONF_NAME: "Hello World reloaded", + CONF_ICON: "mdi:work-reloaded", + CONF_DURATION: 20, + }, + "test_3": {}, + } + }, + ): + with patch("homeassistant.config.find_config_file", return_value=""): + with pytest.raises(Unauthorized): + await hass.services.async_call( + DOMAIN, + SERVICE_RELOAD, + blocking=True, + context=Context(user_id=hass_read_only_user.id), + ) + await hass.services.async_call( + DOMAIN, + SERVICE_RELOAD, + blocking=True, + context=Context(user_id=hass_admin_user.id), + ) + await hass.async_block_till_done() + + assert count_start + 2 == len(hass.states.async_entity_ids()) + + state_1 = hass.states.get("timer.test_1") + state_2 = hass.states.get("timer.test_2") + state_3 = hass.states.get("timer.test_3") + + assert state_1 is None + assert state_2 is not None + assert state_3 is not None + + assert STATUS_IDLE == state_2.state + assert "Hello World reloaded" == state_2.attributes.get(ATTR_FRIENDLY_NAME) + assert "mdi:work-reloaded" == state_2.attributes.get(ATTR_ICON) + assert "0:00:20" == state_2.attributes.get(ATTR_DURATION) + + assert STATUS_IDLE == state_3.state + assert ATTR_ICON not in state_3.attributes + assert ATTR_FRIENDLY_NAME not in state_3.attributes From 36f7096f0959b374b1c646493f22eb7aa9d0a771 Mon Sep 17 00:00:00 2001 From: Wim Haanstra Date: Wed, 18 Dec 2019 21:18:14 +0100 Subject: [PATCH 337/677] Fix failure in transform method (#30023) * Fix failure in transform method * Fix formatting issue --- homeassistant/components/dsmr_reader/definitions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/dsmr_reader/definitions.py b/homeassistant/components/dsmr_reader/definitions.py index f4a36ebc346..45bebfeda92 100644 --- a/homeassistant/components/dsmr_reader/definitions.py +++ b/homeassistant/components/dsmr_reader/definitions.py @@ -3,7 +3,9 @@ def dsmr_transform(value): """Transform DSMR version value to right format.""" - return float(value) / 10 + if value.isdigit(): + return float(value) / 10 + return value def tariff_transform(value): From 5ea4ba6a2e9f53ffa761a28176b08c59e022b403 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Thu, 19 Dec 2019 00:32:16 +0000 Subject: [PATCH 338/677] [ci skip] Translation update --- homeassistant/components/soma/.translations/es.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/soma/.translations/es.json b/homeassistant/components/soma/.translations/es.json index 86922622704..6df113b82c9 100644 --- a/homeassistant/components/soma/.translations/es.json +++ b/homeassistant/components/soma/.translations/es.json @@ -3,7 +3,9 @@ "abort": { "already_setup": "S\u00f3lo puede configurar una cuenta de Soma.", "authorize_url_timeout": "Tiempo de espera agotado para la autorizaci\u00f3n de la url.", - "missing_configuration": "El componente Soma no est\u00e1 configurado. Por favor, leer la documentaci\u00f3n." + "connection_error": "No se ha podido conectar a SOMA Connect.", + "missing_configuration": "El componente Soma no est\u00e1 configurado. Por favor, leer la documentaci\u00f3n.", + "result_error": "SOMA Connect respondi\u00f3 con un error." }, "create_entry": { "default": "Autenticado con \u00e9xito con Soma." From 9e5de1a106b934100c98aac1764f8a7a4b713fd5 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Wed, 18 Dec 2019 17:52:56 -0700 Subject: [PATCH 339/677] Guard against future unknown SimpliSafe entity types (#30059) * Guard against future unknown SimpliSafe entity types * Updated log message --- .../components/simplisafe/alarm_control_panel.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/simplisafe/alarm_control_panel.py b/homeassistant/components/simplisafe/alarm_control_panel.py index 9671d56c873..05dad43955c 100644 --- a/homeassistant/components/simplisafe/alarm_control_panel.py +++ b/homeassistant/components/simplisafe/alarm_control_panel.py @@ -177,11 +177,23 @@ class SimpliSafeAlarm(SimpliSafeEntity, AlarmControlPanel): self._state = None last_event = self._simplisafe.last_event_data[self._system.system_id] + + try: + last_event_sensor_type = EntityTypes(last_event["sensorType"]).name + except ValueError: + _LOGGER.warning( + 'Encountered unknown entity type: %s ("%s"). Please report it at' + "https://github.com/home-assistant/home-assistant/issues.", + last_event["sensorType"], + last_event["sensorName"], + ) + last_event_sensor_type = None + self._attrs.update( { ATTR_LAST_EVENT_INFO: last_event["info"], ATTR_LAST_EVENT_SENSOR_NAME: last_event["sensorName"], - ATTR_LAST_EVENT_SENSOR_TYPE: EntityTypes(last_event["sensorType"]).name, + ATTR_LAST_EVENT_SENSOR_TYPE: last_event_sensor_type, ATTR_LAST_EVENT_TIMESTAMP: utc_from_timestamp( last_event["eventTimestamp"] ), From c3144eddbbfe1d7b0949caeb3cd7a2f61841f71a Mon Sep 17 00:00:00 2001 From: Jc2k Date: Thu, 19 Dec 2019 08:45:23 +0000 Subject: [PATCH 340/677] Set unique id on homekit_controller config entries (#30035) * Set unique id on config entries * Changes from review --- .../components/homekit_controller/__init__.py | 9 +++- .../homekit_controller/config_flow.py | 54 ++++++++++--------- .../homekit_controller/test_config_flow.py | 28 +++++++--- 3 files changed, 55 insertions(+), 36 deletions(-) diff --git a/homeassistant/components/homekit_controller/__init__.py b/homeassistant/components/homekit_controller/__init__.py index c863da14a3a..444f64b6f38 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -9,8 +9,7 @@ from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry as dr from homeassistant.helpers.entity import Entity -# We need an import from .config_flow, without it .config_flow is never loaded. -from .config_flow import HomekitControllerFlowHandler # noqa: F401 +from .config_flow import normalize_hkid from .connection import HKDevice, get_accessory_information from .const import CONTROLLER, DOMAIN, ENTITY_MAP, KNOWN_DEVICES from .storage import EntityMapStorage @@ -181,6 +180,12 @@ async def async_setup_entry(hass, entry): conn = HKDevice(hass, entry, entry.data) hass.data[KNOWN_DEVICES][conn.unique_id] = conn + # For backwards compat + if entry.unique_id is None: + hass.config_entries.async_update_entry( + entry, unique_id=normalize_hkid(conn.unique_id) + ) + if not await conn.async_setup(): del hass.data[KNOWN_DEVICES][conn.unique_id] raise ConfigEntryNotReady diff --git a/homeassistant/components/homekit_controller/config_flow.py b/homeassistant/components/homekit_controller/config_flow.py index f4eb1190727..6cc724e9fe5 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -46,6 +46,11 @@ def load_old_pairings(hass): return old_pairings +def normalize_hkid(hkid): + """Normalize a hkid so that it is safe to compare with other normalized hkids.""" + return hkid.lower() + + @callback def find_existing_host(hass, serial): """Return a set of the configured hosts.""" @@ -77,6 +82,9 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow): key = user_input["device"] self.hkid = self.devices[key]["id"] self.model = self.devices[key]["md"] + await self.async_set_unique_id( + normalize_hkid(self.hkid), raise_on_progress=False + ) return await self.async_step_pair() all_hosts = await self.hass.async_add_executor_job(self.controller.discover, 5) @@ -120,18 +128,6 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow): status_flags = int(properties["sf"]) paired = not status_flags & 0x01 - _LOGGER.debug("Discovered device %s (%s - %s)", name, model, hkid) - - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 - self.context["hkid"] = hkid - self.context["title_placeholders"] = {"name": name} - - # If multiple HomekitControllerFlowHandler end up getting created - # for the same accessory dont let duplicates hang around - active_flows = self._async_in_progress() - if any(hkid == flow["context"]["hkid"] for flow in active_flows): - return self.async_abort(reason="already_in_progress") - # The configuration number increases every time the characteristic map # needs updating. Some devices use a slightly off-spec name so handle # both cases. @@ -143,21 +139,27 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow): ) config_num = None - if paired: - if hkid in self.hass.data.get(KNOWN_DEVICES, {}): - # The device is already paired and known to us - # According to spec we should monitor c# (config_num) for - # changes. If it changes, we check for new entities - conn = self.hass.data[KNOWN_DEVICES][hkid] - if conn.config_num != config_num: - _LOGGER.debug( - "HomeKit info %s: c# incremented, refreshing entities", hkid - ) - self.hass.async_create_task( - conn.async_refresh_entity_map(config_num) - ) - return self.async_abort(reason="already_configured") + # If the device is already paired and known to us we should monitor c# + # (config_num) for changes. If it changes, we check for new entities + if paired and hkid in self.hass.data.get(KNOWN_DEVICES, {}): + conn = self.hass.data[KNOWN_DEVICES][hkid] + if conn.config_num != config_num: + _LOGGER.debug( + "HomeKit info %s: c# incremented, refreshing entities", hkid + ) + self.hass.async_create_task(conn.async_refresh_entity_map(config_num)) + return self.async_abort(reason="already_configured") + _LOGGER.debug("Discovered device %s (%s - %s)", name, model, hkid) + + await self.async_set_unique_id(normalize_hkid(hkid)) + self._abort_if_unique_id_configured() + + # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 + self.context["hkid"] = hkid + self.context["title_placeholders"] = {"name": name} + + if paired: old_pairings = await self.hass.async_add_executor_job( load_old_pairings, self.hass ) diff --git a/tests/components/homekit_controller/test_config_flow.py b/tests/components/homekit_controller/test_config_flow.py index 22b486e1d51..4733581f136 100644 --- a/tests/components/homekit_controller/test_config_flow.py +++ b/tests/components/homekit_controller/test_config_flow.py @@ -74,6 +74,7 @@ async def test_discovery_works(hass): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } # User initiates pairing - device enters pairing mode and displays code @@ -122,6 +123,7 @@ async def test_discovery_works_upper_case(hass): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } # User initiates pairing - device enters pairing mode and displays code @@ -169,6 +171,7 @@ async def test_discovery_works_missing_csharp(hass): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } # User initiates pairing - device enters pairing mode and displays code @@ -234,6 +237,7 @@ async def test_pair_already_paired_1(hass): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } @@ -259,6 +263,7 @@ async def test_discovery_ignored_model(hass): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } @@ -286,6 +291,7 @@ async def test_discovery_invalid_config_entry(hass): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } # Discovery of a HKID that is in a pairable state but for which there is @@ -315,10 +321,7 @@ async def test_discovery_already_configured(hass): result = await flow.async_step_zeroconf(discovery_info) assert result["type"] == "abort" assert result["reason"] == "already_configured" - assert flow.context == { - "hkid": "00:00:00:00:00:00", - "title_placeholders": {"name": "TestDevice"}, - } + assert flow.context == {} assert conn.async_config_num_changed.call_count == 0 @@ -343,10 +346,7 @@ async def test_discovery_already_configured_config_change(hass): result = await flow.async_step_zeroconf(discovery_info) assert result["type"] == "abort" assert result["reason"] == "already_configured" - assert flow.context == { - "hkid": "00:00:00:00:00:00", - "title_placeholders": {"name": "TestDevice"}, - } + assert flow.context == {} assert conn.async_refresh_entity_map.call_args == mock.call(2) @@ -369,6 +369,7 @@ async def test_pair_unable_to_pair(hass): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } # User initiates pairing - device enters pairing mode and displays code @@ -402,6 +403,7 @@ async def test_pair_abort_errors_on_start(hass, exception, expected): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } # User initiates pairing - device refuses to enter pairing mode @@ -414,6 +416,7 @@ async def test_pair_abort_errors_on_start(hass, exception, expected): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } @@ -436,6 +439,7 @@ async def test_pair_form_errors_on_start(hass, exception, expected): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } # User initiates pairing - device refuses to enter pairing mode @@ -448,6 +452,7 @@ async def test_pair_form_errors_on_start(hass, exception, expected): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } @@ -470,6 +475,7 @@ async def test_pair_abort_errors_on_finish(hass, exception, expected): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } # User initiates pairing - device enters pairing mode and displays code @@ -486,6 +492,7 @@ async def test_pair_abort_errors_on_finish(hass, exception, expected): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } @@ -508,6 +515,7 @@ async def test_pair_form_errors_on_finish(hass, exception, expected): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } # User initiates pairing - device enters pairing mode and displays code @@ -524,6 +532,7 @@ async def test_pair_form_errors_on_finish(hass, exception, expected): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } @@ -712,6 +721,7 @@ async def test_parse_new_homekit_json(hass): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } @@ -763,6 +773,7 @@ async def test_parse_old_homekit_json(hass): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } @@ -823,4 +834,5 @@ async def test_parse_overlapping_homekit_json(hass): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } From e0d6810134418d8d3d7464998dcd31c355b614a0 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 19 Dec 2019 11:23:19 +0100 Subject: [PATCH 341/677] Remove stream from camera after deps (#30057) --- homeassistant/components/camera/manifest.json | 2 +- script/hassfest/dependencies.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/camera/manifest.json b/homeassistant/components/camera/manifest.json index 32cd7c3fe47..25d344d05ad 100644 --- a/homeassistant/components/camera/manifest.json +++ b/homeassistant/components/camera/manifest.json @@ -4,6 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/camera", "requirements": [], "dependencies": ["http"], - "after_dependencies": ["stream", "media_player"], + "after_dependencies": ["media_player"], "codeowners": [] } diff --git a/script/hassfest/dependencies.py b/script/hassfest/dependencies.py index 6ba228b5bc7..c67779d3c33 100644 --- a/script/hassfest/dependencies.py +++ b/script/hassfest/dependencies.py @@ -102,6 +102,7 @@ ALLOWED_USED_COMPONENTS = { "discovery", # Other "mjpeg", # base class, has no reqs or component to load. + "stream", # Stream cannot install on all systems, can be imported without reqs. } IGNORE_VIOLATIONS = [ From 9804fbb527fb053296533c5cd4604afcb7043a28 Mon Sep 17 00:00:00 2001 From: Yuchen Ying Date: Thu, 19 Dec 2019 02:23:46 -0800 Subject: [PATCH 342/677] Add unit_of_measurement to various Transmission sensors (#30037) * Add unit_of_measurement to various Transmission sensors Without unit_of_measurement, the history graph card will not show those sensors as line chart. * Change Counts to Torrents. --- homeassistant/components/transmission/const.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/transmission/const.py b/homeassistant/components/transmission/const.py index 5540f718ba1..9a9250dbed6 100644 --- a/homeassistant/components/transmission/const.py +++ b/homeassistant/components/transmission/const.py @@ -2,14 +2,14 @@ DOMAIN = "transmission" SENSOR_TYPES = { - "active_torrents": ["Active Torrents", None], + "active_torrents": ["Active Torrents", "Torrents"], "current_status": ["Status", None], "download_speed": ["Down Speed", "MB/s"], - "paused_torrents": ["Paused Torrents", None], - "total_torrents": ["Total Torrents", None], + "paused_torrents": ["Paused Torrents", "Torrents"], + "total_torrents": ["Total Torrents", "Torrents"], "upload_speed": ["Up Speed", "MB/s"], - "completed_torrents": ["Completed Torrents", None], - "started_torrents": ["Started Torrents", None], + "completed_torrents": ["Completed Torrents", "Torrents"], + "started_torrents": ["Started Torrents", "Torrents"], } SWITCH_TYPES = {"on_off": "Switch", "turtle_mode": "Turtle Mode"} From 5baaa852ddb6777de839bda12ff0b7022c74beb0 Mon Sep 17 00:00:00 2001 From: ochlocracy <5885236+ochlocracy@users.noreply.github.com> Date: Thu, 19 Dec 2019 06:44:17 -0500 Subject: [PATCH 343/677] Refactor Alexa capabilityResources object into class, Implement Alexa semantics object (#29917) * Refactor capabilityResources object into class. Implement semantics object to support open, close, raise, lower utterences. Replace covers PercentageController with RangeController. Add semantics for covers. Remove PowerController for covers. Add new display categories. Add new items to Alexa Global Catalog. Implement garage door voice PIN code support though Alexa app. Fixed bug with getting property for ModeController. Fixed bug were PercentageController AdjustPercentage would exceed 100. * Comment fixes in Tests. * Reorder imports. * Added additional tests for more code coverage. * Added and additional test for more code coverage. * Explicitly return None for configuration() if not instance of AlexaCapabilityResource. --- .../components/alexa/capabilities.py | 394 +++++++++--------- homeassistant/components/alexa/const.py | 157 ------- homeassistant/components/alexa/entities.py | 76 +++- homeassistant/components/alexa/handlers.py | 195 ++++++--- homeassistant/components/alexa/resources.py | 387 +++++++++++++++++ tests/components/alexa/test_capabilities.py | 14 +- tests/components/alexa/test_smart_home.py | 387 ++++++++++++++--- 7 files changed, 1123 insertions(+), 487 deletions(-) create mode 100644 homeassistant/components/alexa/resources.py diff --git a/homeassistant/components/alexa/capabilities.py b/homeassistant/components/alexa/capabilities.py index b5ffb1ef7e6..938101a7500 100644 --- a/homeassistant/components/alexa/capabilities.py +++ b/homeassistant/components/alexa/capabilities.py @@ -13,11 +13,9 @@ from homeassistant.const import ( STATE_ALARM_ARMED_CUSTOM_BYPASS, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, - STATE_CLOSED, STATE_LOCKED, STATE_OFF, STATE_ON, - STATE_OPEN, STATE_PAUSED, STATE_PLAYING, STATE_UNAVAILABLE, @@ -34,10 +32,16 @@ from .const import ( DATE_FORMAT, PERCENTAGE_FAN_MAP, RANGE_FAN_MAP, - Catalog, Inputs, ) from .errors import UnsupportedProperty +from .resources import ( + AlexaCapabilityResource, + AlexaGlobalCatalog, + AlexaModeResource, + AlexaPresetResource, + AlexaSemantics, +) _LOGGER = logging.getLogger(__name__) @@ -108,12 +112,15 @@ class AlexaCapability: @staticmethod def capability_resources(): - """Applicable to ToggleController, RangeController, and ModeController interfaces.""" + """Return the capability object. + + Applicable to ToggleController, RangeController, and ModeController interfaces. + """ return [] @staticmethod def configuration(): - """Return the Configuration object.""" + """Return the configuration object.""" return [] @staticmethod @@ -121,6 +128,14 @@ class AlexaCapability: """Applicable only to media players.""" return [] + @staticmethod + def semantics(): + """Return the semantics object. + + Applicable to ToggleController, RangeController, and ModeController interfaces. + """ + return [] + @staticmethod def supported_operations(): """Return the supportedOperations object.""" @@ -130,6 +145,10 @@ class AlexaCapability: """Serialize according to the Discovery API.""" result = {"type": "AlexaInterface", "interface": self.name(), "version": "3"} + instance = self.instance + if instance is not None: + result["instance"] = instance + properties_supported = self.properties_supported() if properties_supported: result["properties"] = { @@ -138,22 +157,19 @@ class AlexaCapability: "retrievable": self.properties_retrievable(), } - # pylint: disable=assignment-from-none proactively_reported = self.capability_proactively_reported() if proactively_reported is not None: result["proactivelyReported"] = proactively_reported - # pylint: disable=assignment-from-none non_controllable = self.properties_non_controllable() if non_controllable is not None: result["properties"]["nonControllable"] = non_controllable - # pylint: disable=assignment-from-none supports_deactivation = self.supports_deactivation() if supports_deactivation is not None: result["supportsDeactivation"] = supports_deactivation - capability_resources = self.serialize_capability_resources() + capability_resources = self.capability_resources() if capability_resources: result["capabilityResources"] = capability_resources @@ -161,10 +177,9 @@ class AlexaCapability: if configuration: result["configuration"] = configuration - # pylint: disable=assignment-from-none - instance = self.instance - if instance is not None: - result["instance"] = instance + semantics = self.semantics() + if semantics: + result["semantics"] = semantics supported_operations = self.supported_operations() if supported_operations: @@ -196,36 +211,6 @@ class AlexaCapability: yield result - def serialize_capability_resources(self): - """Return capabilityResources friendlyNames serialized for an API response.""" - resources = self.capability_resources() - if resources: - return {"friendlyNames": self.serialize_friendly_names(resources)} - - return None - - @staticmethod - def serialize_friendly_names(resources): - """Return capabilityResources, ModeResources, or presetResources friendlyNames serialized for an API response.""" - friendly_names = [] - for resource in resources: - if resource["type"] == Catalog.LABEL_ASSET: - friendly_names.append( - { - "@type": Catalog.LABEL_ASSET, - "value": {"assetId": resource["value"]}, - } - ) - else: - friendly_names.append( - { - "@type": Catalog.LABEL_TEXT, - "value": {"text": resource["value"], "locale": "en-US"}, - } - ) - - return friendly_names - class Alexa(AlexaCapability): """Implements Alexa Interface. @@ -906,6 +891,8 @@ class AlexaModeController(AlexaCapability): def __init__(self, entity, instance, non_controllable=False): """Initialize the entity.""" super().__init__(entity, instance) + self._resource = None + self._semantics = None self.properties_non_controllable = lambda: non_controllable def name(self): @@ -922,108 +909,102 @@ class AlexaModeController(AlexaCapability): def properties_retrievable(self): """Return True if properties can be retrieved.""" + return True def get_property(self, name): """Read and return a property.""" if name != "mode": raise UnsupportedProperty(name) + # Fan Direction if self.instance == f"{fan.DOMAIN}.{fan.ATTR_DIRECTION}": - return self.entity.attributes.get(fan.ATTR_DIRECTION) + mode = self.entity.attributes.get(fan.ATTR_DIRECTION, None) + if mode in (fan.DIRECTION_FORWARD, fan.DIRECTION_REVERSE, STATE_UNKNOWN): + return f"{fan.ATTR_DIRECTION}.{mode}" + # Cover Position if self.instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}": - return self.entity.attributes.get(cover.ATTR_POSITION) + # Return state instead of position when using ModeController. + mode = self.entity.state + if mode in ( + cover.STATE_OPEN, + cover.STATE_OPENING, + cover.STATE_CLOSED, + cover.STATE_CLOSING, + STATE_UNKNOWN, + ): + return f"{cover.ATTR_POSITION}.{mode}" return None def configuration(self): """Return configuration with modeResources.""" - return self.serialize_mode_resources() + if isinstance(self._resource, AlexaCapabilityResource): + return self._resource.serialize_configuration() + + return None def capability_resources(self): """Return capabilityResources object.""" - capability_resources = [] + # Fan Direction Resource if self.instance == f"{fan.DOMAIN}.{fan.ATTR_DIRECTION}": - capability_resources = [ - {"type": Catalog.LABEL_ASSET, "value": Catalog.SETTING_DIRECTION} - ] + self._resource = AlexaModeResource( + [AlexaGlobalCatalog.SETTING_DIRECTION], False + ) + self._resource.add_mode( + f"{fan.ATTR_DIRECTION}.{fan.DIRECTION_FORWARD}", [fan.DIRECTION_FORWARD] + ) + self._resource.add_mode( + f"{fan.ATTR_DIRECTION}.{fan.DIRECTION_REVERSE}", [fan.DIRECTION_REVERSE] + ) + return self._resource.serialize_capability_resources() + # Cover Position Resources if self.instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}": - capability_resources = [ - {"type": Catalog.LABEL_ASSET, "value": Catalog.SETTING_MODE}, - {"type": Catalog.LABEL_ASSET, "value": Catalog.SETTING_PRESET}, - ] + self._resource = AlexaModeResource( + ["Position", AlexaGlobalCatalog.SETTING_OPENING], False + ) + self._resource.add_mode( + f"{cover.ATTR_POSITION}.{cover.STATE_OPEN}", + [AlexaGlobalCatalog.VALUE_OPEN], + ) + self._resource.add_mode( + f"{cover.ATTR_POSITION}.{cover.STATE_CLOSED}", + [AlexaGlobalCatalog.VALUE_CLOSE], + ) + self._resource.add_mode(f"{cover.ATTR_POSITION}.custom", ["Custom"]) + return self._resource.serialize_capability_resources() - return capability_resources + return None - def mode_resources(self): - """Return modeResources object.""" - mode_resources = None - if self.instance == f"{fan.DOMAIN}.{fan.ATTR_DIRECTION}": - mode_resources = { - "ordered": False, - "resources": [ - { - "value": f"{fan.ATTR_DIRECTION}.{fan.DIRECTION_FORWARD}", - "friendly_names": [ - {"type": Catalog.LABEL_TEXT, "value": fan.DIRECTION_FORWARD} - ], - }, - { - "value": f"{fan.ATTR_DIRECTION}.{fan.DIRECTION_REVERSE}", - "friendly_names": [ - {"type": Catalog.LABEL_TEXT, "value": fan.DIRECTION_REVERSE} - ], - }, - ], - } + def semantics(self): + """Build and return semantics object.""" + # Cover Position if self.instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}": - mode_resources = { - "ordered": False, - "resources": [ - { - "value": f"{cover.ATTR_POSITION}.{STATE_OPEN}", - "friendly_names": [ - {"type": Catalog.LABEL_TEXT, "value": "open"}, - {"type": Catalog.LABEL_TEXT, "value": "opened"}, - {"type": Catalog.LABEL_TEXT, "value": "raise"}, - {"type": Catalog.LABEL_TEXT, "value": "raised"}, - ], - }, - { - "value": f"{cover.ATTR_POSITION}.{STATE_CLOSED}", - "friendly_names": [ - {"type": Catalog.LABEL_TEXT, "value": "close"}, - {"type": Catalog.LABEL_TEXT, "value": "closed"}, - {"type": Catalog.LABEL_TEXT, "value": "shut"}, - {"type": Catalog.LABEL_TEXT, "value": "lower"}, - {"type": Catalog.LABEL_TEXT, "value": "lowered"}, - ], - }, - ], - } + self._semantics = AlexaSemantics() + self._semantics.add_action_to_directive( + [AlexaSemantics.ACTION_CLOSE, AlexaSemantics.ACTION_LOWER], + "SetMode", + {"mode": f"{cover.ATTR_POSITION}.{cover.STATE_CLOSED}"}, + ) + self._semantics.add_action_to_directive( + [AlexaSemantics.ACTION_OPEN, AlexaSemantics.ACTION_RAISE], + "SetMode", + {"mode": f"{cover.ATTR_POSITION}.{cover.STATE_OPEN}"}, + ) + self._semantics.add_states_to_value( + [AlexaSemantics.STATES_CLOSED], + f"{cover.ATTR_POSITION}.{cover.STATE_CLOSED}", + ) + self._semantics.add_states_to_value( + [AlexaSemantics.STATES_OPEN], + f"{cover.ATTR_POSITION}.{cover.STATE_OPEN}", + ) + return self._semantics.serialize_semantics() - return mode_resources - - def serialize_mode_resources(self): - """Return ModeResources, friendlyNames serialized for an API response.""" - mode_resources = [] - resources = self.mode_resources() - ordered = resources["ordered"] - for resource in resources["resources"]: - mode_value = resource["value"] - friendly_names = resource["friendly_names"] - result = { - "value": mode_value, - "modeResources": { - "friendlyNames": self.serialize_friendly_names(friendly_names) - }, - } - mode_resources.append(result) - - return {"ordered": ordered, "supportedModes": mode_resources} + return None class AlexaRangeController(AlexaCapability): @@ -1035,6 +1016,8 @@ class AlexaRangeController(AlexaCapability): def __init__(self, entity, instance, non_controllable=False): """Initialize the entity.""" super().__init__(entity, instance) + self._resource = None + self._semantics = None self.properties_non_controllable = lambda: non_controllable def name(self): @@ -1058,88 +1041,111 @@ class AlexaRangeController(AlexaCapability): if name != "rangeValue": raise UnsupportedProperty(name) + # Fan Speed if self.instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}": speed = self.entity.attributes.get(fan.ATTR_SPEED) return RANGE_FAN_MAP.get(speed, 0) + # Cover Position + if self.instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}": + return self.entity.attributes.get(cover.ATTR_CURRENT_POSITION) + + # Cover Tilt Position + if self.instance == f"{cover.DOMAIN}.{cover.ATTR_TILT_POSITION}": + return self.entity.attributes.get(cover.ATTR_CURRENT_TILT_POSITION) + return None def configuration(self): """Return configuration with presetResources.""" - return self.serialize_preset_resources() + if isinstance(self._resource, AlexaCapabilityResource): + return self._resource.serialize_configuration() + + return None def capability_resources(self): """Return capabilityResources object.""" - capability_resources = [] + # Fan Speed Resources if self.instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}": - return [{"type": Catalog.LABEL_ASSET, "value": Catalog.SETTING_FANSPEED}] - - return capability_resources - - def preset_resources(self): - """Return presetResources object.""" - preset_resources = [] - - if self.instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}": - preset_resources = { - "minimumValue": 1, - "maximumValue": 3, - "precision": 1, - "presets": [ - { - "rangeValue": 1, - "names": [ - { - "type": Catalog.LABEL_ASSET, - "value": Catalog.VALUE_MINIMUM, - }, - {"type": Catalog.LABEL_ASSET, "value": Catalog.VALUE_LOW}, - ], - }, - { - "rangeValue": 2, - "names": [ - {"type": Catalog.LABEL_ASSET, "value": Catalog.VALUE_MEDIUM} - ], - }, - { - "rangeValue": 3, - "names": [ - { - "type": Catalog.LABEL_ASSET, - "value": Catalog.VALUE_MAXIMUM, - }, - {"type": Catalog.LABEL_ASSET, "value": Catalog.VALUE_HIGH}, - ], - }, - ], - } - - return preset_resources - - def serialize_preset_resources(self): - """Return PresetResources, friendlyNames serialized for an API response.""" - preset_resources = [] - resources = self.preset_resources() - for preset in resources["presets"]: - preset_resources.append( - { - "rangeValue": preset["rangeValue"], - "presetResources": { - "friendlyNames": self.serialize_friendly_names(preset["names"]) - }, - } + self._resource = AlexaPresetResource( + labels=[AlexaGlobalCatalog.SETTING_FAN_SPEED], + min_value=1, + max_value=3, + precision=1, ) + self._resource.add_preset( + value=1, + labels=[AlexaGlobalCatalog.VALUE_LOW, AlexaGlobalCatalog.VALUE_MINIMUM], + ) + self._resource.add_preset(value=2, labels=[AlexaGlobalCatalog.VALUE_MEDIUM]) + self._resource.add_preset( + value=3, + labels=[ + AlexaGlobalCatalog.VALUE_HIGH, + AlexaGlobalCatalog.VALUE_MAXIMUM, + ], + ) + return self._resource.serialize_capability_resources() - return { - "supportedRange": { - "minimumValue": resources["minimumValue"], - "maximumValue": resources["maximumValue"], - "precision": resources["precision"], - }, - "presets": preset_resources, - } + # Cover Position Resources + if self.instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}": + self._resource = AlexaPresetResource( + ["Position", AlexaGlobalCatalog.SETTING_OPENING], + min_value=0, + max_value=100, + precision=1, + unit=AlexaGlobalCatalog.UNIT_PERCENT, + ) + return self._resource.serialize_capability_resources() + + # Cover Tilt Position Resources + if self.instance == f"{cover.DOMAIN}.{cover.ATTR_TILT_POSITION}": + self._resource = AlexaPresetResource( + ["Tilt Position", AlexaGlobalCatalog.SETTING_OPENING], + min_value=0, + max_value=100, + precision=1, + unit=AlexaGlobalCatalog.UNIT_PERCENT, + ) + return self._resource.serialize_capability_resources() + + return None + + def semantics(self): + """Build and return semantics object.""" + + # Cover Position + if self.instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}": + self._semantics = AlexaSemantics() + self._semantics.add_action_to_directive( + [AlexaSemantics.ACTION_LOWER], "SetRangeValue", {"rangeValue": 0} + ) + self._semantics.add_action_to_directive( + [AlexaSemantics.ACTION_RAISE], "SetRangeValue", {"rangeValue": 100} + ) + self._semantics.add_states_to_value([AlexaSemantics.STATES_CLOSED], value=0) + self._semantics.add_states_to_range( + [AlexaSemantics.STATES_OPEN], min_value=1, max_value=100 + ) + return self._semantics.serialize_semantics() + + # Cover Tilt Position + if self.instance == f"{cover.DOMAIN}.{cover.ATTR_TILT_POSITION}": + self._semantics = AlexaSemantics() + self._semantics.add_action_to_directive( + [AlexaSemantics.ACTION_CLOSE], "SetRangeValue", {"rangeValue": 0} + ) + self._semantics.add_action_to_directive( + [AlexaSemantics.ACTION_OPEN], "SetRangeValue", {"rangeValue": 100} + ) + self._semantics.add_states_to_value([AlexaSemantics.STATES_CLOSED], value=0) + self._semantics.add_states_to_range( + [AlexaSemantics.STATES_OPEN], min_value=1, max_value=100 + ) + return self._semantics.serialize_semantics() + + return None class AlexaToggleController(AlexaCapability): @@ -1151,6 +1157,8 @@ class AlexaToggleController(AlexaCapability): def __init__(self, entity, instance, non_controllable=False): """Initialize the entity.""" super().__init__(entity, instance) + self._resource = None + self._semantics = None self.properties_non_controllable = lambda: non_controllable def name(self): @@ -1174,6 +1182,7 @@ class AlexaToggleController(AlexaCapability): if name != "toggleState": raise UnsupportedProperty(name) + # Fan Oscillating if self.instance == f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}": is_on = bool(self.entity.attributes.get(fan.ATTR_OSCILLATING)) return "ON" if is_on else "OFF" @@ -1182,16 +1191,15 @@ class AlexaToggleController(AlexaCapability): def capability_resources(self): """Return capabilityResources object.""" - capability_resources = [] + # Fan Oscillating Resource if self.instance == f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}": - capability_resources = [ - {"type": Catalog.LABEL_ASSET, "value": Catalog.SETTING_OSCILLATE}, - {"type": Catalog.LABEL_TEXT, "value": "Rotate"}, - {"type": Catalog.LABEL_TEXT, "value": "Rotation"}, - ] + self._resource = AlexaCapabilityResource( + [AlexaGlobalCatalog.SETTING_OSCILLATE, "Rotate", "Rotation"] + ) + return self._resource.serialize_capability_resources() - return capability_resources + return None class AlexaChannelController(AlexaCapability): diff --git a/homeassistant/components/alexa/const.py b/homeassistant/components/alexa/const.py index 2c62e1a485a..f1a86859da9 100644 --- a/homeassistant/components/alexa/const.py +++ b/homeassistant/components/alexa/const.py @@ -117,163 +117,6 @@ class Cause: VOICE_INTERACTION = "VOICE_INTERACTION" -class Catalog: - """The Global Alexa catalog. - - https://developer.amazon.com/docs/device-apis/resources-and-assets.html#global-alexa-catalog - - You can use the global Alexa catalog for pre-defined names of devices, settings, values, and units. - This catalog is localized into all the languages that Alexa supports. - - You can reference the following catalog of pre-defined friendly names. - Each item in the following list is an asset identifier followed by its supported friendly names. - The first friendly name for each identifier is the one displayed in the Alexa mobile app. - """ - - LABEL_ASSET = "asset" - LABEL_TEXT = "text" - - # Shower - DEVICENAME_SHOWER = "Alexa.DeviceName.Shower" - - # Washer, Washing Machine - DEVICENAME_WASHER = "Alexa.DeviceName.Washer" - - # Router, Internet Router, Network Router, Wifi Router, Net Router - DEVICENAME_ROUTER = "Alexa.DeviceName.Router" - - # Fan, Blower - DEVICENAME_FAN = "Alexa.DeviceName.Fan" - - # Air Purifier, Air Cleaner,Clean Air Machine - DEVICENAME_AIRPURIFIER = "Alexa.DeviceName.AirPurifier" - - # Space Heater, Portable Heater - DEVICENAME_SPACEHEATER = "Alexa.DeviceName.SpaceHeater" - - # Rain Head, Overhead shower, Rain Shower, Rain Spout, Rain Faucet - SHOWER_RAINHEAD = "Alexa.Shower.RainHead" - - # Handheld Shower, Shower Wand, Hand Shower - SHOWER_HANDHELD = "Alexa.Shower.HandHeld" - - # Water Temperature, Water Temp, Water Heat - SETTING_WATERTEMPERATURE = "Alexa.Setting.WaterTemperature" - - # Temperature, Temp - SETTING_TEMPERATURE = "Alexa.Setting.Temperature" - - # Wash Cycle, Wash Preset, Wash setting - SETTING_WASHCYCLE = "Alexa.Setting.WashCycle" - - # 2.4G Guest Wi-Fi, 2.4G Guest Network, Guest Network 2.4G, 2G Guest Wifi - SETTING_2GGUESTWIFI = "Alexa.Setting.2GGuestWiFi" - - # 5G Guest Wi-Fi, 5G Guest Network, Guest Network 5G, 5G Guest Wifi - SETTING_5GGUESTWIFI = "Alexa.Setting.5GGuestWiFi" - - # Guest Wi-fi, Guest Network, Guest Net - SETTING_GUESTWIFI = "Alexa.Setting.GuestWiFi" - - # Auto, Automatic, Automatic Mode, Auto Mode - SETTING_AUTO = "Alexa.Setting.Auto" - - # #Night, Night Mode - SETTING_NIGHT = "Alexa.Setting.Night" - - # Quiet, Quiet Mode, Noiseless, Silent - SETTING_QUIET = "Alexa.Setting.Quiet" - - # Oscillate, Swivel, Oscillation, Spin, Back and forth - SETTING_OSCILLATE = "Alexa.Setting.Oscillate" - - # Fan Speed, Airflow speed, Wind Speed, Air speed, Air velocity - SETTING_FANSPEED = "Alexa.Setting.FanSpeed" - - # Preset, Setting - SETTING_PRESET = "Alexa.Setting.Preset" - - # Mode - SETTING_MODE = "Alexa.Setting.Mode" - - # Direction - SETTING_DIRECTION = "Alexa.Setting.Direction" - - # Delicates, Delicate - VALUE_DELICATE = "Alexa.Value.Delicate" - - # Quick Wash, Fast Wash, Wash Quickly, Speed Wash - VALUE_QUICKWASH = "Alexa.Value.QuickWash" - - # Maximum, Max - VALUE_MAXIMUM = "Alexa.Value.Maximum" - - # Minimum, Min - VALUE_MINIMUM = "Alexa.Value.Minimum" - - # High - VALUE_HIGH = "Alexa.Value.High" - - # Low - VALUE_LOW = "Alexa.Value.Low" - - # Medium, Mid - VALUE_MEDIUM = "Alexa.Value.Medium" - - -class Unit: - """Alexa Units of Measure. - - https://developer.amazon.com/docs/device-apis/alexa-property-schemas.html#units-of-measure - """ - - ANGLE_DEGREES = "Alexa.Unit.Angle.Degrees" - - ANGLE_RADIANS = "Alexa.Unit.Angle.Radians" - - DISTANCE_FEET = "Alexa.Unit.Distance.Feet" - - DISTANCE_INCHES = "Alexa.Unit.Distance.Inches" - - DISTANCE_KILOMETERS = "Alexa.Unit.Distance.Kilometers" - - DISTANCE_METERS = "Alexa.Unit.Distance.Meters" - - DISTANCE_MILES = "Alexa.Unit.Distance.Miles" - - DISTANCE_YARDS = "Alexa.Unit.Distance.Yards" - - MASS_GRAMS = "Alexa.Unit.Mass.Grams" - - MASS_KILOGRAMS = "Alexa.Unit.Mass.Kilograms" - - PERCENT = "Alexa.Unit.Percent" - - TEMPERATURE_CELSIUS = "Alexa.Unit.Temperature.Celsius" - - TEMPERATURE_DEGREES = "Alexa.Unit.Temperature.Degrees" - - TEMPERATURE_FAHRENHEIT = "Alexa.Unit.Temperature.Fahrenheit" - - TEMPERATURE_KELVIN = "Alexa.Unit.Temperature.Kelvin" - - VOLUME_CUBICFEET = "Alexa.Unit.Volume.CubicFeet" - - VOLUME_CUBICMETERS = "Alexa.Unit.Volume.CubicMeters" - - VOLUME_GALLONS = "Alexa.Unit.Volume.Gallons" - - VOLUME_LITERS = "Alexa.Unit.Volume.Liters" - - VOLUME_PINTS = "Alexa.Unit.Volume.Pints" - - VOLUME_QUARTS = "Alexa.Unit.Volume.Quarts" - - WEIGHT_OUNCES = "Alexa.Unit.Weight.Ounces" - - WEIGHT_POUNDS = "Alexa.Unit.Weight.Pounds" - - class Inputs: """Valid names for the InputController. diff --git a/homeassistant/components/alexa/entities.py b/homeassistant/components/alexa/entities.py index 017686df607..2a3355434a3 100644 --- a/homeassistant/components/alexa/entities.py +++ b/homeassistant/components/alexa/entities.py @@ -83,6 +83,9 @@ class DisplayCategory: # Indicates media devices with video or photo capabilities. CAMERA = "CAMERA" + # Indicates a non-mobile computer, such as a desktop computer. + COMPUTER = "COMPUTER" + # Indicates an endpoint that detects and reports contact. CONTACT_SENSOR = "CONTACT_SENSOR" @@ -92,27 +95,60 @@ class DisplayCategory: # Indicates a doorbell. DOORBELL = "DOORBELL" + # Indicates a window covering on the outside of a structure. + EXTERIOR_BLIND = "EXTERIOR_BLIND" + # Indicates a fan. FAN = "FAN" + # Indicates a game console, such as Microsoft Xbox or Nintendo Switch + GAME_CONSOLE = "GAME_CONSOLE" + + # Indicates a garage door. Garage doors must implement the ModeController interface to open and close the door. + GARAGE_DOOR = "GARAGE_DOOR" + + # Indicates a window covering on the inside of a structure. + INTERIOR_BLIND = "INTERIOR_BLIND" + + # Indicates a laptop or other mobile computer. + LAPTOP = "LAPTOP" + # Indicates light sources or fixtures. LIGHT = "LIGHT" # Indicates a microwave oven. MICROWAVE = "MICROWAVE" + # Indicates a mobile phone. + MOBILE_PHONE = "MOBILE_PHONE" + # Indicates an endpoint that detects and reports motion. MOTION_SENSOR = "MOTION_SENSOR" + # Indicates a network-connected music system. + MUSIC_SYSTEM = "MUSIC_SYSTEM" + # An endpoint that cannot be described in on of the other categories. OTHER = "OTHER" + # Indicates a network router. + NETWORK_HARDWARE = "NETWORK_HARDWARE" + + # Indicates an oven cooking appliance. + OVEN = "OVEN" + + # Indicates a non-mobile phone, such as landline or an IP phone. + PHONE = "PHONE" + # Describes a combination of devices set to a specific state, when the # order of the state change is not important. For example a bedtime scene # might include turning off lights and lowering the thermostat, but the # order is unimportant. Applies to Scenes SCENE_TRIGGER = "SCENE_TRIGGER" + # Indicates a projector screen. + SCREEN = "SCREEN" + # Indicates a security panel. SECURITY_PANEL = "SECURITY_PANEL" @@ -126,10 +162,16 @@ class DisplayCategory: # Indicates the endpoint is a speaker or speaker system. SPEAKER = "SPEAKER" + # Indicates a streaming device such as Apple TV, Chromecast, or Roku. + STREAMING_DEVICE = "STREAMING_DEVICE" + # Indicates in-wall switches wired to the electrical system. Can control a # variety of devices. SWITCH = "SWITCH" + # Indicates a tablet computer. + TABLET = "TABLET" + # Indicates endpoints that report the temperature only. TEMPERATURE_SENSOR = "TEMPERATURE_SENSOR" @@ -140,6 +182,9 @@ class DisplayCategory: # Indicates the endpoint is a television. TV = "TV" + # Indicates a network-connected wearable device, such as an Apple Watch, Fitbit, or Samsung Gear. + WEARABLE = "WEARABLE" + class AlexaEntity: """An adaptation of an entity, expressed in Alexa's terms. @@ -318,20 +363,40 @@ class CoverCapabilities(AlexaEntity): def default_display_categories(self): """Return the display categories for this entity.""" device_class = self.entity.attributes.get(ATTR_DEVICE_CLASS) - if device_class in (cover.DEVICE_CLASS_GARAGE, cover.DEVICE_CLASS_DOOR): + if device_class == cover.DEVICE_CLASS_GARAGE: + return [DisplayCategory.GARAGE_DOOR] + if device_class == cover.DEVICE_CLASS_DOOR: return [DisplayCategory.DOOR] + if device_class in ( + cover.DEVICE_CLASS_BLIND, + cover.DEVICE_CLASS_SHADE, + cover.DEVICE_CLASS_CURTAIN, + ): + return [DisplayCategory.INTERIOR_BLIND] + if device_class in ( + cover.DEVICE_CLASS_WINDOW, + cover.DEVICE_CLASS_AWNING, + cover.DEVICE_CLASS_SHUTTER, + ): + return [DisplayCategory.EXTERIOR_BLIND] + return [DisplayCategory.OTHER] def interfaces(self): """Yield the supported interfaces.""" - yield AlexaPowerController(self.entity) supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) if supported & cover.SUPPORT_SET_POSITION: - yield AlexaPercentageController(self.entity) - if supported & (cover.SUPPORT_CLOSE | cover.SUPPORT_OPEN): + yield AlexaRangeController( + self.entity, instance=f"{cover.DOMAIN}.{cover.ATTR_POSITION}" + ) + elif supported & (cover.SUPPORT_CLOSE | cover.SUPPORT_OPEN): yield AlexaModeController( self.entity, instance=f"{cover.DOMAIN}.{cover.ATTR_POSITION}" ) + if supported & cover.SUPPORT_SET_TILT_POSITION: + yield AlexaRangeController( + self.entity, instance=f"{cover.DOMAIN}.{cover.ATTR_TILT_POSITION}" + ) yield AlexaEndpointHealth(self.hass, self.entity) yield Alexa(self.hass) @@ -355,6 +420,7 @@ class LightCapabilities(AlexaEntity): yield AlexaColorController(self.entity) if supported & light.SUPPORT_COLOR_TEMP: yield AlexaColorTemperatureController(self.entity) + yield AlexaEndpointHealth(self.hass, self.entity) yield Alexa(self.hass) @@ -370,6 +436,7 @@ class FanCapabilities(AlexaEntity): def interfaces(self): """Yield the supported interfaces.""" yield AlexaPowerController(self.entity) + supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) if supported & fan.SUPPORT_SET_SPEED: yield AlexaPercentageController(self.entity) @@ -377,7 +444,6 @@ class FanCapabilities(AlexaEntity): yield AlexaRangeController( self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_SPEED}" ) - if supported & fan.SUPPORT_OSCILLATE: yield AlexaToggleController( self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}" diff --git a/homeassistant/components/alexa/handlers.py b/homeassistant/components/alexa/handlers.py index efb4f59514d..b5603af7402 100644 --- a/homeassistant/components/alexa/handlers.py +++ b/homeassistant/components/alexa/handlers.py @@ -20,6 +20,7 @@ from homeassistant.const import ( SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_STOP, SERVICE_SET_COVER_POSITION, + SERVICE_SET_COVER_TILT_POSITION, SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_UNLOCK, @@ -28,8 +29,6 @@ from homeassistant.const import ( SERVICE_VOLUME_SET, SERVICE_VOLUME_UP, STATE_ALARM_DISARMED, - STATE_CLOSED, - STATE_OPEN, TEMP_CELSIUS, TEMP_FAHRENHEIT, ) @@ -113,9 +112,7 @@ async def async_api_turn_on(hass, config, directive, context): domain = ha.DOMAIN service = SERVICE_TURN_ON - if domain == cover.DOMAIN: - service = cover.SERVICE_OPEN_COVER - elif domain == media_player.DOMAIN: + if domain == media_player.DOMAIN: supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) power_features = media_player.SUPPORT_TURN_ON | media_player.SUPPORT_TURN_OFF if not supported & power_features: @@ -141,9 +138,7 @@ async def async_api_turn_off(hass, config, directive, context): domain = ha.DOMAIN service = SERVICE_TURN_OFF - if entity.domain == cover.DOMAIN: - service = cover.SERVICE_CLOSE_COVER - elif domain == media_player.DOMAIN: + if domain == media_player.DOMAIN: supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) power_features = media_player.SUPPORT_TURN_ON | media_player.SUPPORT_TURN_OFF if not supported & power_features: @@ -348,10 +343,6 @@ async def async_api_set_percentage(hass, config, directive, context): speed = "high" data[fan.ATTR_SPEED] = speed - elif entity.domain == cover.DOMAIN: - service = SERVICE_SET_COVER_POSITION - data[cover.ATTR_POSITION] = percentage - await hass.services.async_call( entity.domain, service, data, blocking=False, context=context ) @@ -385,13 +376,6 @@ async def async_api_adjust_percentage(hass, config, directive, context): data[fan.ATTR_SPEED] = speed - elif entity.domain == cover.DOMAIN: - service = SERVICE_SET_COVER_POSITION - - current = entity.attributes.get(cover.ATTR_POSITION) - - data[cover.ATTR_POSITION] = max(0, percentage_delta + current) - await hass.services.async_call( entity.domain, service, data, blocking=False, context=context ) @@ -960,32 +944,35 @@ async def async_api_disarm(hass, config, directive, context): @HANDLERS.register(("Alexa.ModeController", "SetMode")) async def async_api_set_mode(hass, config, directive, context): - """Process a next request.""" + """Process a SetMode directive.""" entity = directive.entity instance = directive.instance domain = entity.domain service = None data = {ATTR_ENTITY_ID: entity.entity_id} - capability_mode = directive.payload["mode"] - - if domain not in (fan.DOMAIN, cover.DOMAIN): - msg = "Entity does not support directive" - raise AlexaInvalidDirectiveError(msg) + mode = directive.payload["mode"] + # Fan Direction if instance == f"{fan.DOMAIN}.{fan.ATTR_DIRECTION}": - _, direction = capability_mode.split(".") + _, direction = mode.split(".") if direction in (fan.DIRECTION_REVERSE, fan.DIRECTION_FORWARD): service = fan.SERVICE_SET_DIRECTION data[fan.ATTR_DIRECTION] = direction - if instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}": - _, position = capability_mode.split(".") + # Cover Position + elif instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}": + _, position = mode.split(".") - if position == STATE_CLOSED: + if position == cover.STATE_CLOSED: service = cover.SERVICE_CLOSE_COVER - - if position == STATE_OPEN: + elif position == cover.STATE_OPEN: service = cover.SERVICE_OPEN_COVER + elif position == "custom": + service = cover.SERVICE_STOP_COVER + + else: + msg = "Entity does not support directive" + raise AlexaInvalidDirectiveError(msg) await hass.services.async_call( domain, service, data, blocking=False, context=context @@ -997,7 +984,7 @@ async def async_api_set_mode(hass, config, directive, context): "namespace": "Alexa.ModeController", "instance": instance, "name": "mode", - "value": capability_mode, + "value": mode, } ) @@ -1008,24 +995,13 @@ async def async_api_set_mode(hass, config, directive, context): async def async_api_adjust_mode(hass, config, directive, context): """Process a AdjustMode request. - Requires modeResources to be ordered. - Only modes that are ordered support the adjustMode directive. + Requires capabilityResources supportedModes to be ordered. + Only supportedModes with ordered=True support the adjustMode directive. """ - entity = directive.entity - instance = directive.instance - domain = entity.domain - if domain != fan.DOMAIN: - msg = "Entity does not support directive" - raise AlexaInvalidDirectiveError(msg) - - if instance is None: - msg = "Entity does not support directive" - raise AlexaInvalidDirectiveError(msg) - - # No modeResources are currently ordered to support this request. - - return directive.response() + # Currently no supportedModes are configured with ordered=True to support this request. + msg = "Entity does not support directive" + raise AlexaInvalidDirectiveError(msg) @HANDLERS.register(("Alexa.ToggleController", "TurnOn")) @@ -1037,19 +1013,29 @@ async def async_api_toggle_on(hass, config, directive, context): service = None data = {ATTR_ENTITY_ID: entity.entity_id} - if domain != fan.DOMAIN: - msg = "Entity does not support directive" - raise AlexaInvalidDirectiveError(msg) - + # Fan Oscillating if instance == f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}": service = fan.SERVICE_OSCILLATE data[fan.ATTR_OSCILLATING] = True + else: + msg = "Entity does not support directive" + raise AlexaInvalidDirectiveError(msg) await hass.services.async_call( domain, service, data, blocking=False, context=context ) - return directive.response() + response = directive.response() + response.add_context_property( + { + "namespace": "Alexa.ToggleController", + "instance": instance, + "name": "toggleState", + "value": "ON", + } + ) + + return response @HANDLERS.register(("Alexa.ToggleController", "TurnOff")) @@ -1061,19 +1047,29 @@ async def async_api_toggle_off(hass, config, directive, context): service = None data = {ATTR_ENTITY_ID: entity.entity_id} - if domain != fan.DOMAIN: - msg = "Entity does not support directive" - raise AlexaInvalidDirectiveError(msg) - + # Fan Oscillating if instance == f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}": service = fan.SERVICE_OSCILLATE data[fan.ATTR_OSCILLATING] = False + else: + msg = "Entity does not support directive" + raise AlexaInvalidDirectiveError(msg) await hass.services.async_call( domain, service, data, blocking=False, context=context ) - return directive.response() + response = directive.response() + response.add_context_property( + { + "namespace": "Alexa.ToggleController", + "instance": instance, + "name": "toggleState", + "value": "OFF", + } + ) + + return response @HANDLERS.register(("Alexa.RangeController", "SetRangeValue")) @@ -1086,10 +1082,7 @@ async def async_api_set_range(hass, config, directive, context): data = {ATTR_ENTITY_ID: entity.entity_id} range_value = int(directive.payload["rangeValue"]) - if domain != fan.DOMAIN: - msg = "Entity does not support directive" - raise AlexaInvalidDirectiveError(msg) - + # Fan Speed if instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}": service = fan.SERVICE_SET_SPEED speed = SPEED_FAN_MAP.get(range_value, None) @@ -1103,11 +1096,45 @@ async def async_api_set_range(hass, config, directive, context): data[fan.ATTR_SPEED] = speed + # Cover Position + elif instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}": + if range_value == 0: + service = cover.SERVICE_CLOSE_COVER + elif range_value == 100: + service = cover.SERVICE_OPEN_COVER + else: + service = cover.SERVICE_SET_COVER_POSITION + data[cover.ATTR_POSITION] = range_value + + # Cover Tilt Position + elif instance == f"{cover.DOMAIN}.{cover.ATTR_TILT_POSITION}": + if range_value == 0: + service = cover.SERVICE_CLOSE_COVER_TILT + elif range_value == 100: + service = cover.SERVICE_OPEN_COVER_TILT + else: + service = cover.SERVICE_SET_COVER_TILT_POSITION + data[cover.ATTR_POSITION] = range_value + + else: + msg = "Entity does not support directive" + raise AlexaInvalidDirectiveError(msg) + await hass.services.async_call( domain, service, data, blocking=False, context=context ) - return directive.response() + response = directive.response() + response.add_context_property( + { + "namespace": "Alexa.RangeController", + "instance": instance, + "name": "rangeValue", + "value": range_value, + } + ) + + return response @HANDLERS.register(("Alexa.RangeController", "AdjustRangeValue")) @@ -1119,24 +1146,56 @@ async def async_api_adjust_range(hass, config, directive, context): service = None data = {ATTR_ENTITY_ID: entity.entity_id} range_delta = int(directive.payload["rangeValueDelta"]) + response_value = 0 + # Fan Speed if instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}": service = fan.SERVICE_SET_SPEED - - # adjust range current_range = RANGE_FAN_MAP.get(entity.attributes.get(fan.ATTR_SPEED), 0) - speed = SPEED_FAN_MAP.get(max(0, range_delta + current_range), fan.SPEED_OFF) + speed = SPEED_FAN_MAP.get( + min(3, max(0, range_delta + current_range)), fan.SPEED_OFF + ) if speed == fan.SPEED_OFF: service = fan.SERVICE_TURN_OFF - data[fan.ATTR_SPEED] = speed + data[fan.ATTR_SPEED] = response_value = speed + + # Cover Position + elif instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}": + service = SERVICE_SET_COVER_POSITION + current = entity.attributes.get(cover.ATTR_POSITION) + data[cover.ATTR_POSITION] = response_value = min( + 100, max(0, range_delta + current) + ) + + # Cover Tilt Position + elif instance == f"{cover.DOMAIN}.{cover.ATTR_TILT_POSITION}": + service = SERVICE_SET_COVER_TILT_POSITION + current = entity.attributes.get(cover.ATTR_TILT_POSITION) + data[cover.ATTR_TILT_POSITION] = response_value = min( + 100, max(0, range_delta + current) + ) + + else: + msg = "Entity does not support directive" + raise AlexaInvalidDirectiveError(msg) await hass.services.async_call( domain, service, data, blocking=False, context=context ) - return directive.response() + response = directive.response() + response.add_context_property( + { + "namespace": "Alexa.RangeController", + "instance": instance, + "name": "rangeValue", + "value": response_value, + } + ) + + return response @HANDLERS.register(("Alexa.ChannelController", "ChangeChannel")) diff --git a/homeassistant/components/alexa/resources.py b/homeassistant/components/alexa/resources.py new file mode 100644 index 00000000000..061005252dc --- /dev/null +++ b/homeassistant/components/alexa/resources.py @@ -0,0 +1,387 @@ +"""Alexa Resources and Assets.""" + + +class AlexaGlobalCatalog: + """The Global Alexa catalog. + + https://developer.amazon.com/docs/device-apis/resources-and-assets.html#global-alexa-catalog + + You can use the global Alexa catalog for pre-defined names of devices, settings, values, and units. + This catalog is localized into all the languages that Alexa supports. + + You can reference the following catalog of pre-defined friendly names. + Each item in the following list is an asset identifier followed by its supported friendly names. + The first friendly name for each identifier is the one displayed in the Alexa mobile app. + """ + + # Air Purifier, Air Cleaner,Clean Air Machine + DEVICE_NAME_AIR_PURIFIER = "Alexa.DeviceName.AirPurifier" + + # Fan, Blower + DEVICE_NAME_FAN = "Alexa.DeviceName.Fan" + + # Router, Internet Router, Network Router, Wifi Router, Net Router + DEVICE_NAME_ROUTER = "Alexa.DeviceName.Router" + + # Shade, Blind, Curtain, Roller, Shutter, Drape, Awning, Window shade, Interior blind + DEVICE_NAME_SHADE = "Alexa.DeviceName.Shade" + + # Shower + DEVICE_NAME_SHOWER = "Alexa.DeviceName.Shower" + + # Space Heater, Portable Heater + DEVICE_NAME_SPACE_HEATER = "Alexa.DeviceName.SpaceHeater" + + # Washer, Washing Machine + DEVICE_NAME_WASHER = "Alexa.DeviceName.Washer" + + # 2.4G Guest Wi-Fi, 2.4G Guest Network, Guest Network 2.4G, 2G Guest Wifi + SETTING_2G_GUEST_WIFI = "Alexa.Setting.2GGuestWiFi" + + # 5G Guest Wi-Fi, 5G Guest Network, Guest Network 5G, 5G Guest Wifi + SETTING_5G_GUEST_WIFI = "Alexa.Setting.5GGuestWiFi" + + # Auto, Automatic, Automatic Mode, Auto Mode + SETTING_AUTO = "Alexa.Setting.Auto" + + # Direction + SETTING_DIRECTION = "Alexa.Setting.Direction" + + # Dry Cycle, Dry Preset, Dry Setting, Dryer Cycle, Dryer Preset, Dryer Setting + SETTING_DRY_CYCLE = "Alexa.Setting.DryCycle" + + # Fan Speed, Airflow speed, Wind Speed, Air speed, Air velocity + SETTING_FAN_SPEED = "Alexa.Setting.FanSpeed" + + # Guest Wi-fi, Guest Network, Guest Net + SETTING_GUEST_WIFI = "Alexa.Setting.GuestWiFi" + + # Heat + SETTING_HEAT = "Alexa.Setting.Heat" + + # Mode + SETTING_MODE = "Alexa.Setting.Mode" + + # Night, Night Mode + SETTING_NIGHT = "Alexa.Setting.Night" + + # Opening, Height, Lift, Width + SETTING_OPENING = "Alexa.Setting.Opening" + + # Oscillate, Swivel, Oscillation, Spin, Back and forth + SETTING_OSCILLATE = "Alexa.Setting.Oscillate" + + # Preset, Setting + SETTING_PRESET = "Alexa.Setting.Preset" + + # Quiet, Quiet Mode, Noiseless, Silent + SETTING_QUIET = "Alexa.Setting.Quiet" + + # Temperature, Temp + SETTING_TEMPERATURE = "Alexa.Setting.Temperature" + + # Wash Cycle, Wash Preset, Wash setting + SETTING_WASH_CYCLE = "Alexa.Setting.WashCycle" + + # Water Temperature, Water Temp, Water Heat + SETTING_WATER_TEMPERATURE = "Alexa.Setting.WaterTemperature" + + # Handheld Shower, Shower Wand, Hand Shower + SHOWER_HAND_HELD = "Alexa.Shower.HandHeld" + + # Rain Head, Overhead shower, Rain Shower, Rain Spout, Rain Faucet + SHOWER_RAIN_HEAD = "Alexa.Shower.RainHead" + + # Degrees, Degree + UNIT_ANGLE_DEGREES = "Alexa.Unit.Angle.Degrees" + + # Radians, Radian + UNIT_ANGLE_RADIANS = "Alexa.Unit.Angle.Radians" + + # Feet, Foot + UNIT_DISTANCE_FEET = "Alexa.Unit.Distance.Feet" + + # Inches, Inch + UNIT_DISTANCE_INCHES = "Alexa.Unit.Distance.Inches" + + # Kilometers + UNIT_DISTANCE_KILOMETERS = "Alexa.Unit.Distance.Kilometers" + + # Meters, Meter, m + UNIT_DISTANCE_METERS = "Alexa.Unit.Distance.Meters" + + # Miles, Mile + UNIT_DISTANCE_MILES = "Alexa.Unit.Distance.Miles" + + # Yards, Yard + UNIT_DISTANCE_YARDS = "Alexa.Unit.Distance.Yards" + + # Grams, Gram, g + UNIT_MASS_GRAMS = "Alexa.Unit.Mass.Grams" + + # Kilograms, Kilogram, kg + UNIT_MASS_KILOGRAMS = "Alexa.Unit.Mass.Kilograms" + + # Percent + UNIT_PERCENT = "Alexa.Unit.Percent" + + # Celsius, Degrees Celsius, Degrees, C, Centigrade, Degrees Centigrade + UNIT_TEMPERATURE_CELSIUS = "Alexa.Unit.Temperature.Celsius" + + # Degrees, Degree + UNIT_TEMPERATURE_DEGREES = "Alexa.Unit.Temperature.Degrees" + + # Fahrenheit, Degrees Fahrenheit, Degrees F, Degrees, F + UNIT_TEMPERATURE_FAHRENHEIT = "Alexa.Unit.Temperature.Fahrenheit" + + # Kelvin, Degrees Kelvin, Degrees K, Degrees, K + UNIT_TEMPERATURE_KELVIN = "Alexa.Unit.Temperature.Kelvin" + + # Cubic Feet, Cubic Foot + UNIT_VOLUME_CUBIC_FEET = "Alexa.Unit.Volume.CubicFeet" + + # Cubic Meters, Cubic Meter, Meters Cubed + UNIT_VOLUME_CUBIC_METERS = "Alexa.Unit.Volume.CubicMeters" + + # Gallons, Gallon + UNIT_VOLUME_GALLONS = "Alexa.Unit.Volume.Gallons" + + # Liters, Liter, L + UNIT_VOLUME_LITERS = "Alexa.Unit.Volume.Liters" + + # Pints, Pint + UNIT_VOLUME_PINTS = "Alexa.Unit.Volume.Pints" + + # Quarts, Quart + UNIT_VOLUME_QUARTS = "Alexa.Unit.Volume.Quarts" + + # Ounces, Ounce, oz + UNIT_WEIGHT_OUNCES = "Alexa.Unit.Weight.Ounces" + + # Pounds, Pound, lbs + UNIT_WEIGHT_POUNDS = "Alexa.Unit.Weight.Pounds" + + # Close + VALUE_CLOSE = "Alexa.Value.Close" + + # Delicates, Delicate + VALUE_DELICATE = "Alexa.Value.Delicate" + + # High + VALUE_HIGH = "Alexa.Value.High" + + # Low + VALUE_LOW = "Alexa.Value.Low" + + # Maximum, Max + VALUE_MAXIMUM = "Alexa.Value.Maximum" + + # Medium, Mid + VALUE_MEDIUM = "Alexa.Value.Medium" + + # Minimum, Min + VALUE_MINIMUM = "Alexa.Value.Minimum" + + # Open + VALUE_OPEN = "Alexa.Value.Open" + + # Quick Wash, Fast Wash, Wash Quickly, Speed Wash + VALUE_QUICK_WASH = "Alexa.Value.QuickWash" + + +class AlexaCapabilityResource: + """Base class for Alexa capabilityResources, ModeResources, and presetResources objects. + + https://developer.amazon.com/docs/device-apis/resources-and-assets.html#capability-resources + """ + + def __init__(self, labels): + """Initialize an Alexa resource.""" + self._resource_labels = [] + for label in labels: + self._resource_labels.append(label) + + def serialize_capability_resources(self): + """Return capabilityResources object serialized for an API response.""" + return self.serialize_labels(self._resource_labels) + + @staticmethod + def serialize_configuration(): + """Return ModeResources, PresetResources friendlyNames serialized for an API response.""" + return [] + + @staticmethod + def serialize_labels(resources): + """Return resource label objects for friendlyNames serialized for an API response.""" + labels = [] + for label in resources: + if label in AlexaGlobalCatalog.__dict__.values(): + label = {"@type": "asset", "value": {"assetId": label}} + else: + label = {"@type": "text", "value": {"text": label, "locale": "en-US"}} + + labels.append(label) + + return {"friendlyNames": labels} + + +class AlexaModeResource(AlexaCapabilityResource): + """Implements Alexa ModeResources. + + https://developer.amazon.com/docs/device-apis/resources-and-assets.html#capability-resources + """ + + def __init__(self, labels, ordered=False): + """Initialize an Alexa modeResource.""" + super().__init__(labels) + self._supported_modes = [] + self._mode_ordered = ordered + + def add_mode(self, value, labels): + """Add mode to the supportedModes object.""" + self._supported_modes.append({"value": value, "labels": labels}) + + def serialize_configuration(self): + """Return configuration for ModeResources friendlyNames serialized for an API response.""" + mode_resources = [] + for mode in self._supported_modes: + result = { + "value": mode["value"], + "modeResources": self.serialize_labels(mode["labels"]), + } + mode_resources.append(result) + + return {"ordered": self._mode_ordered, "supportedModes": mode_resources} + + +class AlexaPresetResource(AlexaCapabilityResource): + """Implements Alexa PresetResources. + + Use presetResources with RangeController to provide a set of friendlyNames for each RangeController preset. + + https://developer.amazon.com/docs/device-apis/resources-and-assets.html#presetresources + """ + + def __init__(self, labels, min_value, max_value, precision, unit=None): + """Initialize an Alexa presetResource.""" + super().__init__(labels) + self._presets = [] + self._minimum_value = int(min_value) + self._maximum_value = int(max_value) + self._precision = int(precision) + self._unit_of_measure = None + if unit in AlexaGlobalCatalog.__dict__.values(): + self._unit_of_measure = unit + + def add_preset(self, value, labels): + """Add preset to configuration presets array.""" + self._presets.append({"value": value, "labels": labels}) + + def serialize_configuration(self): + """Return configuration for PresetResources friendlyNames serialized for an API response.""" + configuration = { + "supportedRange": { + "minimumValue": self._minimum_value, + "maximumValue": self._maximum_value, + "precision": self._precision, + } + } + + if self._unit_of_measure: + configuration["unitOfMeasure"] = self._unit_of_measure + + if self._presets: + preset_resources = [] + for preset in self._presets: + preset_resources.append( + { + "rangeValue": preset["value"], + "presetResources": self.serialize_labels(preset["labels"]), + } + ) + configuration["presets"] = preset_resources + + return configuration + + +class AlexaSemantics: + """Class for Alexa Semantics Object. + + You can optionally enable additional utterances by using semantics. When you use semantics, + you manually map the phrases "open", "close", "raise", and "lower" to directives. + + Semantics is supported for the following interfaces only: ModeController, RangeController, and ToggleController. + + https://developer.amazon.com/docs/device-apis/alexa-discovery.html#semantics-object + """ + + MAPPINGS_ACTION = "actionMappings" + MAPPINGS_STATE = "stateMappings" + + ACTIONS_TO_DIRECTIVE = "ActionsToDirective" + STATES_TO_VALUE = "StatesToValue" + STATES_TO_RANGE = "StatesToRange" + + ACTION_CLOSE = "Alexa.Actions.Close" + ACTION_LOWER = "Alexa.Actions.Lower" + ACTION_OPEN = "Alexa.Actions.Open" + ACTION_RAISE = "Alexa.Actions.Raise" + + STATES_OPEN = "Alexa.States.Open" + STATES_CLOSED = "Alexa.States.Closed" + + DIRECTIVE_RANGE_SET_VALUE = "SetRangeValue" + DIRECTIVE_RANGE_ADJUST_VALUE = "AdjustRangeValue" + DIRECTIVE_TOGGLE_TURN_ON = "TurnOn" + DIRECTIVE_TOGGLE_TURN_OFF = "TurnOff" + DIRECTIVE_MODE_SET_MODE = "SetMode" + DIRECTIVE_MODE_ADJUST_MODE = "AdjustMode" + + def __init__(self): + """Initialize an Alexa modeResource.""" + self._action_mappings = [] + self._state_mappings = [] + + def _add_action_mapping(self, semantics): + """Add action mapping between actions and interface directives.""" + self._action_mappings.append(semantics) + + def _add_state_mapping(self, semantics): + """Add state mapping between states and interface directives.""" + self._state_mappings.append(semantics) + + def add_states_to_value(self, states, value): + """Add StatesToValue stateMappings.""" + self._add_state_mapping( + {"@type": self.STATES_TO_VALUE, "states": states, "value": value} + ) + + def add_states_to_range(self, states, min_value, max_value): + """Add StatesToRange stateMappings.""" + self._add_state_mapping( + { + "@type": self.STATES_TO_RANGE, + "states": states, + "range": {"minimumValue": min_value, "maximumValue": max_value}, + } + ) + + def add_action_to_directive(self, actions, directive, payload): + """Add ActionsToDirective actionMappings.""" + self._add_action_mapping( + { + "@type": self.ACTIONS_TO_DIRECTIVE, + "actions": actions, + "directive": {"name": directive, "payload": payload}, + } + ) + + def serialize_semantics(self): + """Return semantics object serialized for an API response.""" + semantics = {} + if self._action_mappings: + semantics[self.MAPPINGS_ACTION] = self._action_mappings + if self._state_mappings: + semantics[self.MAPPINGS_STATE] = self._state_mappings + + return semantics diff --git a/tests/components/alexa/test_capabilities.py b/tests/components/alexa/test_capabilities.py index ab9c375103a..9c086e1fc50 100644 --- a/tests/components/alexa/test_capabilities.py +++ b/tests/components/alexa/test_capabilities.py @@ -411,14 +411,14 @@ async def test_report_fan_direction(hass): properties.assert_not_has_property("Alexa.ModeController", "mode") properties = await reported_properties(hass, "fan.reverse") - properties.assert_equal("Alexa.ModeController", "mode", "reverse") + properties.assert_equal("Alexa.ModeController", "mode", "direction.reverse") properties = await reported_properties(hass, "fan.forward") - properties.assert_equal("Alexa.ModeController", "mode", "forward") + properties.assert_equal("Alexa.ModeController", "mode", "direction.forward") -async def test_report_cover_percentage_state(hass): - """Test PercentageController reports cover percentage correctly.""" +async def test_report_cover_range_value(hass): + """Test RangeController reports cover position correctly.""" hass.states.async_set( "cover.fully_open", "open", @@ -448,13 +448,13 @@ async def test_report_cover_percentage_state(hass): ) properties = await reported_properties(hass, "cover.fully_open") - properties.assert_equal("Alexa.PercentageController", "percentage", 100) + properties.assert_equal("Alexa.RangeController", "rangeValue", 100) properties = await reported_properties(hass, "cover.half_open") - properties.assert_equal("Alexa.PercentageController", "percentage", 50) + properties.assert_equal("Alexa.RangeController", "rangeValue", 50) properties = await reported_properties(hass, "cover.closed") - properties.assert_equal("Alexa.PercentageController", "percentage", 0) + properties.assert_equal("Alexa.RangeController", "rangeValue", 0) async def test_report_climate_state(hass): diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index 25c8f2a864f..4187c4a2c4f 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -126,10 +126,12 @@ async def discovery_test(device, hass, expected_endpoints=1): return None -def get_capability(capabilities, capability_name): +def get_capability(capabilities, capability_name, instance=None): """Search a set of capabilities for a specific one.""" for capability in capabilities: - if capability["interface"] == capability_name: + if instance and capability["instance"] == instance: + return capability + elif capability["interface"] == capability_name: return capability return None @@ -420,9 +422,29 @@ async def test_variable_fan(hass): ) assert call.data["speed"] == "medium" + call, _ = await assert_request_calls_service( + "Alexa.PercentageController", + "SetPercentage", + "fan#test_2", + "fan.set_speed", + hass, + payload={"percentage": "33"}, + ) + assert call.data["speed"] == "low" + + call, _ = await assert_request_calls_service( + "Alexa.PercentageController", + "SetPercentage", + "fan#test_2", + "fan.set_speed", + hass, + payload={"percentage": "100"}, + ) + assert call.data["speed"] == "high" + await assert_percentage_changes( hass, - [("high", "-5"), ("off", "5"), ("low", "-80")], + [("high", "-5"), ("off", "5"), ("low", "-80"), ("medium", "-34")], "Alexa.PercentageController", "AdjustPercentage", "fan#test_2", @@ -431,6 +453,16 @@ async def test_variable_fan(hass): "speed", ) + call, _ = await assert_request_calls_service( + "Alexa.PowerLevelController", + "SetPowerLevel", + "fan#test_2", + "fan.set_speed", + hass, + payload={"powerLevel": "20"}, + ) + assert call.data["speed"] == "low" + call, _ = await assert_request_calls_service( "Alexa.PowerLevelController", "SetPowerLevel", @@ -441,6 +473,16 @@ async def test_variable_fan(hass): ) assert call.data["speed"] == "medium" + call, _ = await assert_request_calls_service( + "Alexa.PowerLevelController", + "SetPowerLevel", + "fan#test_2", + "fan.set_speed", + hass, + payload={"powerLevel": "99"}, + ) + assert call.data["speed"] == "high" + await assert_percentage_changes( hass, [("high", "-5"), ("medium", "-50"), ("low", "-80")], @@ -1333,51 +1375,106 @@ async def test_group(hass): ) -async def test_cover(hass): - """Test cover discovery.""" +async def test_cover_position_range(hass): + """Test cover discovery and position using rangeController.""" device = ( - "cover.test", - "off", - {"friendly_name": "Test cover", "supported_features": 255, "position": 30}, + "cover.test_range", + "open", + { + "friendly_name": "Test cover range", + "device_class": "blind", + "supported_features": 7, + "position": 30, + }, ) appliance = await discovery_test(device, hass) - assert appliance["endpointId"] == "cover#test" - assert appliance["displayCategories"][0] == "OTHER" - assert appliance["friendlyName"] == "Test cover" + assert appliance["endpointId"] == "cover#test_range" + assert appliance["displayCategories"][0] == "INTERIOR_BLIND" + assert appliance["friendlyName"] == "Test cover range" - assert_endpoint_capabilities( - appliance, - "Alexa.ModeController", - "Alexa.PercentageController", - "Alexa.PowerController", - "Alexa.EndpointHealth", - "Alexa", + capabilities = assert_endpoint_capabilities( + appliance, "Alexa.RangeController", "Alexa.EndpointHealth", "Alexa" ) - await assert_power_controller_works( - "cover#test", "cover.open_cover", "cover.close_cover", hass - ) + range_capability = get_capability(capabilities, "Alexa.RangeController") + assert range_capability is not None + assert range_capability["instance"] == "cover.position" + + properties = range_capability["properties"] + assert properties["nonControllable"] is False + assert {"name": "rangeValue"} in properties["supported"] + + capability_resources = range_capability["capabilityResources"] + assert capability_resources is not None + assert { + "@type": "text", + "value": {"text": "Position", "locale": "en-US"}, + } in capability_resources["friendlyNames"] + + assert { + "@type": "asset", + "value": {"assetId": "Alexa.Setting.Opening"}, + } in capability_resources["friendlyNames"] + + configuration = range_capability["configuration"] + assert configuration is not None + assert configuration["unitOfMeasure"] == "Alexa.Unit.Percent" + + supported_range = configuration["supportedRange"] + assert supported_range["minimumValue"] == 0 + assert supported_range["maximumValue"] == 100 + assert supported_range["precision"] == 1 call, _ = await assert_request_calls_service( - "Alexa.PercentageController", - "SetPercentage", - "cover#test", + "Alexa.RangeController", + "SetRangeValue", + "cover#test_range", "cover.set_cover_position", hass, - payload={"percentage": "50"}, + payload={"rangeValue": "50"}, + instance="cover.position", ) assert call.data["position"] == 50 - await assert_percentage_changes( + call, msg = await assert_request_calls_service( + "Alexa.RangeController", + "SetRangeValue", + "cover#test_range", + "cover.close_cover", hass, - [(25, "-5"), (35, "5"), (0, "-80")], - "Alexa.PercentageController", - "AdjustPercentage", - "cover#test", - "percentageDelta", + payload={"rangeValue": "0"}, + instance="cover.position", + ) + properties = msg["context"]["properties"][0] + assert properties["name"] == "rangeValue" + assert properties["namespace"] == "Alexa.RangeController" + assert properties["value"] == 0 + + call, msg = await assert_request_calls_service( + "Alexa.RangeController", + "SetRangeValue", + "cover#test_range", + "cover.open_cover", + hass, + payload={"rangeValue": "100"}, + instance="cover.position", + ) + properties = msg["context"]["properties"][0] + assert properties["name"] == "rangeValue" + assert properties["namespace"] == "Alexa.RangeController" + assert properties["value"] == 100 + + await assert_range_changes( + hass, + [(25, "-5"), (35, "5"), (0, "-99"), (100, "99")], + "Alexa.RangeController", + "AdjustRangeValue", + "cover#test_range", + False, "cover.set_cover_position", "position", + instance="cover.position", ) @@ -2292,26 +2389,25 @@ async def test_mode_unsupported_domain(hass): assert msg["payload"]["type"] == "INVALID_DIRECTIVE" -async def test_cover_position(hass): - """Test cover position mode discovery.""" +async def test_cover_position_mode(hass): + """Test cover discovery and position using modeController.""" device = ( - "cover.test", - "off", - {"friendly_name": "Test cover", "supported_features": 255, "position": 30}, + "cover.test_mode", + "open", + { + "friendly_name": "Test cover mode", + "device_class": "blind", + "supported_features": 3, + }, ) appliance = await discovery_test(device, hass) - assert appliance["endpointId"] == "cover#test" - assert appliance["displayCategories"][0] == "OTHER" - assert appliance["friendlyName"] == "Test cover" + assert appliance["endpointId"] == "cover#test_mode" + assert appliance["displayCategories"][0] == "INTERIOR_BLIND" + assert appliance["friendlyName"] == "Test cover mode" capabilities = assert_endpoint_capabilities( - appliance, - "Alexa", - "Alexa.ModeController", - "Alexa.PercentageController", - "Alexa.PowerController", - "Alexa.EndpointHealth", + appliance, "Alexa", "Alexa.ModeController", "Alexa.EndpointHealth" ) mode_capability = get_capability(capabilities, "Alexa.ModeController") @@ -2324,9 +2420,14 @@ async def test_cover_position(hass): capability_resources = mode_capability["capabilityResources"] assert capability_resources is not None + assert { + "@type": "text", + "value": {"text": "Position", "locale": "en-US"}, + } in capability_resources["friendlyNames"] + assert { "@type": "asset", - "value": {"assetId": "Alexa.Setting.Mode"}, + "value": {"assetId": "Alexa.Setting.Opening"}, } in capability_resources["friendlyNames"] configuration = mode_capability["configuration"] @@ -2339,10 +2440,7 @@ async def test_cover_position(hass): "value": "position.open", "modeResources": { "friendlyNames": [ - {"@type": "text", "value": {"text": "open", "locale": "en-US"}}, - {"@type": "text", "value": {"text": "opened", "locale": "en-US"}}, - {"@type": "text", "value": {"text": "raise", "locale": "en-US"}}, - {"@type": "text", "value": {"text": "raised", "locale": "en-US"}}, + {"@type": "asset", "value": {"assetId": "Alexa.Value.Open"}} ] }, } in supported_modes @@ -2350,19 +2448,24 @@ async def test_cover_position(hass): "value": "position.closed", "modeResources": { "friendlyNames": [ - {"@type": "text", "value": {"text": "close", "locale": "en-US"}}, - {"@type": "text", "value": {"text": "closed", "locale": "en-US"}}, - {"@type": "text", "value": {"text": "shut", "locale": "en-US"}}, - {"@type": "text", "value": {"text": "lower", "locale": "en-US"}}, - {"@type": "text", "value": {"text": "lowered", "locale": "en-US"}}, + {"@type": "asset", "value": {"assetId": "Alexa.Value.Close"}} ] }, } in supported_modes + semantics = mode_capability["semantics"] + assert semantics is not None + + action_mappings = semantics["actionMappings"] + assert action_mappings is not None + + state_mappings = semantics["stateMappings"] + assert state_mappings is not None + call, msg = await assert_request_calls_service( "Alexa.ModeController", "SetMode", - "cover#test", + "cover#test_mode", "cover.close_cover", hass, payload={"mode": "position.closed"}, @@ -2376,7 +2479,7 @@ async def test_cover_position(hass): call, msg = await assert_request_calls_service( "Alexa.ModeController", "SetMode", - "cover#test", + "cover#test_mode", "cover.open_cover", hass, payload={"mode": "position.open"}, @@ -2387,6 +2490,20 @@ async def test_cover_position(hass): assert properties["namespace"] == "Alexa.ModeController" assert properties["value"] == "position.open" + call, msg = await assert_request_calls_service( + "Alexa.ModeController", + "SetMode", + "cover#test_mode", + "cover.stop_cover", + hass, + payload={"mode": "position.custom"}, + instance="cover.position", + ) + properties = msg["context"]["properties"][0] + assert properties["name"] == "mode" + assert properties["namespace"] == "Alexa.ModeController" + assert properties["value"] == "position.custom" + async def test_image_processing(hass): """Test image_processing discovery as event detection.""" @@ -2467,3 +2584,159 @@ async def test_presence_sensor(hass): assert properties["proactivelyReported"] is True assert not properties["retrievable"] assert {"name": "humanPresenceDetectionState"} in properties["supported"] + + +async def test_cover_tilt_position_range(hass): + """Test cover discovery and tilt position using rangeController.""" + device = ( + "cover.test_tilt_range", + "open", + { + "friendly_name": "Test cover tilt range", + "device_class": "blind", + "supported_features": 240, + "tilt_position": 30, + }, + ) + appliance = await discovery_test(device, hass) + + assert appliance["endpointId"] == "cover#test_tilt_range" + assert appliance["displayCategories"][0] == "INTERIOR_BLIND" + assert appliance["friendlyName"] == "Test cover tilt range" + + capabilities = assert_endpoint_capabilities( + appliance, "Alexa.RangeController", "Alexa.EndpointHealth", "Alexa" + ) + + range_capability = get_capability(capabilities, "Alexa.RangeController") + assert range_capability is not None + assert range_capability["instance"] == "cover.tilt_position" + + semantics = range_capability["semantics"] + assert semantics is not None + + action_mappings = semantics["actionMappings"] + assert action_mappings is not None + + state_mappings = semantics["stateMappings"] + assert state_mappings is not None + + call, _ = await assert_request_calls_service( + "Alexa.RangeController", + "SetRangeValue", + "cover#test_tilt_range", + "cover.set_cover_tilt_position", + hass, + payload={"rangeValue": "50"}, + instance="cover.tilt_position", + ) + assert call.data["position"] == 50 + + call, msg = await assert_request_calls_service( + "Alexa.RangeController", + "SetRangeValue", + "cover#test_tilt_range", + "cover.close_cover_tilt", + hass, + payload={"rangeValue": "0"}, + instance="cover.tilt_position", + ) + properties = msg["context"]["properties"][0] + assert properties["name"] == "rangeValue" + assert properties["namespace"] == "Alexa.RangeController" + assert properties["value"] == 0 + + call, msg = await assert_request_calls_service( + "Alexa.RangeController", + "SetRangeValue", + "cover#test_tilt_range", + "cover.open_cover_tilt", + hass, + payload={"rangeValue": "100"}, + instance="cover.tilt_position", + ) + properties = msg["context"]["properties"][0] + assert properties["name"] == "rangeValue" + assert properties["namespace"] == "Alexa.RangeController" + assert properties["value"] == 100 + + await assert_range_changes( + hass, + [(25, "-5"), (35, "5"), (0, "-99"), (100, "99")], + "Alexa.RangeController", + "AdjustRangeValue", + "cover#test_tilt_range", + False, + "cover.set_cover_tilt_position", + "tilt_position", + instance="cover.tilt_position", + ) + + +async def test_cover_semantics(hass): + """Test cover discovery and semantics.""" + device = ( + "cover.test_semantics", + "open", + { + "friendly_name": "Test cover semantics", + "device_class": "blind", + "supported_features": 255, + "position": 30, + "tilt_position": 30, + }, + ) + appliance = await discovery_test(device, hass) + + assert appliance["endpointId"] == "cover#test_semantics" + assert appliance["displayCategories"][0] == "INTERIOR_BLIND" + assert appliance["friendlyName"] == "Test cover semantics" + + capabilities = assert_endpoint_capabilities( + appliance, "Alexa.RangeController", "Alexa.EndpointHealth", "Alexa" + ) + + for range_instance in ("cover.position", "cover.tilt_position"): + range_capability = get_capability( + capabilities, "Alexa.RangeController", range_instance + ) + semantics = range_capability["semantics"] + assert semantics is not None + + action_mappings = semantics["actionMappings"] + assert action_mappings is not None + if range_instance == "cover.position": + assert { + "@type": "ActionsToDirective", + "actions": ["Alexa.Actions.Lower"], + "directive": {"name": "SetRangeValue", "payload": {"rangeValue": 0}}, + } in action_mappings + assert { + "@type": "ActionsToDirective", + "actions": ["Alexa.Actions.Raise"], + "directive": {"name": "SetRangeValue", "payload": {"rangeValue": 100}}, + } in action_mappings + elif range_instance == "cover.position": + assert { + "@type": "ActionsToDirective", + "actions": ["Alexa.Actions.Close"], + "directive": {"name": "SetRangeValue", "payload": {"rangeValue": 0}}, + } in action_mappings + assert { + "@type": "ActionsToDirective", + "actions": ["Alexa.Actions.Open"], + "directive": {"name": "SetRangeValue", "payload": {"rangeValue": 100}}, + } in action_mappings + + state_mappings = semantics["stateMappings"] + assert state_mappings is not None + assert { + "@type": "StatesToValue", + "states": ["Alexa.States.Closed"], + "value": 0, + } in state_mappings + assert { + "@type": "StatesToRange", + "states": ["Alexa.States.Open"], + "range": {"minimumValue": 1, "maximumValue": 100}, + } in state_mappings From 52818bdb891387e4b9eaa30ec0540e532c7cb78e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 19 Dec 2019 14:00:22 +0100 Subject: [PATCH 344/677] Make Hassfest stricter pt 2 (#30068) * Make Hassfest stricter * Fix if-condition * Small cleanup --- .../components/modbus/binary_sensor.py | 3 +- homeassistant/components/modbus/switch.py | 2 +- homeassistant/components/mqtt/climate.py | 10 +- homeassistant/components/tuya/climate.py | 6 +- homeassistant/components/zamg/sensor.py | 21 +-- script/hassfest/dependencies.py | 151 ++++++++++++------ tests/hassfest/test_dependencies.py | 17 +- 7 files changed, 130 insertions(+), 80 deletions(-) diff --git a/homeassistant/components/modbus/binary_sensor.py b/homeassistant/components/modbus/binary_sensor.py index cbefd1271d0..faf5160d7e5 100644 --- a/homeassistant/components/modbus/binary_sensor.py +++ b/homeassistant/components/modbus/binary_sensor.py @@ -3,8 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice from homeassistant.const import CONF_NAME, CONF_SLAVE from homeassistant.helpers import config_validation as cv diff --git a/homeassistant/components/modbus/switch.py b/homeassistant/components/modbus/switch.py index 43ef649f788..eba0c754f45 100644 --- a/homeassistant/components/modbus/switch.py +++ b/homeassistant/components/modbus/switch.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.switch import PLATFORM_SCHEMA from homeassistant.const import ( CONF_COMMAND_OFF, CONF_COMMAND_ON, diff --git a/homeassistant/components/mqtt/climate.py b/homeassistant/components/mqtt/climate.py index a51590e0b59..f9d7d8752f2 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -14,6 +14,10 @@ from homeassistant.components.climate.const import ( ATTR_TARGET_TEMP_LOW, DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, + FAN_AUTO, + FAN_HIGH, + FAN_LOW, + FAN_MEDIUM, HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_DRY, @@ -29,7 +33,6 @@ from homeassistant.components.climate.const import ( SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_RANGE, ) -from homeassistant.components.fan import SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM from homeassistant.const import ( ATTR_TEMPERATURE, CONF_DEVICE, @@ -165,8 +168,7 @@ PLATFORM_SCHEMA = ( vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Optional(CONF_FAN_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional( - CONF_FAN_MODE_LIST, - default=[HVAC_MODE_AUTO, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH], + CONF_FAN_MODE_LIST, default=[FAN_AUTO, FAN_LOW, FAN_MEDIUM, FAN_HIGH], ): cv.ensure_list, vol.Optional(CONF_FAN_MODE_STATE_TEMPLATE): cv.template, vol.Optional(CONF_FAN_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, @@ -339,7 +341,7 @@ class MqttClimate( self._target_temp_high = config[CONF_TEMP_INITIAL] if self._topic[CONF_FAN_MODE_STATE_TOPIC] is None: - self._current_fan_mode = SPEED_LOW + self._current_fan_mode = FAN_LOW if self._topic[CONF_SWING_MODE_STATE_TOPIC] is None: self._current_swing_mode = HVAC_MODE_OFF if self._topic[CONF_MODE_STATE_TOPIC] is None: diff --git a/homeassistant/components/tuya/climate.py b/homeassistant/components/tuya/climate.py index 6450920b806..eb0ef5eca2f 100644 --- a/homeassistant/components/tuya/climate.py +++ b/homeassistant/components/tuya/climate.py @@ -1,6 +1,9 @@ """Support for the Tuya climate devices.""" from homeassistant.components.climate import ENTITY_ID_FORMAT, ClimateDevice from homeassistant.components.climate.const import ( + FAN_HIGH, + FAN_LOW, + FAN_MEDIUM, HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_FAN_ONLY, @@ -9,7 +12,6 @@ from homeassistant.components.climate.const import ( SUPPORT_FAN_MODE, SUPPORT_TARGET_TEMPERATURE, ) -from homeassistant.components.fan import SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM from homeassistant.const import ( ATTR_TEMPERATURE, PRECISION_WHOLE, @@ -30,7 +32,7 @@ HA_STATE_TO_TUYA = { TUYA_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_TUYA.items()} -FAN_MODES = {SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH} +FAN_MODES = {FAN_LOW, FAN_MEDIUM, FAN_HIGH} def setup_platform(hass, config, add_entities, discovery_info=None): diff --git a/homeassistant/components/zamg/sensor.py b/homeassistant/components/zamg/sensor.py index c984b13ef4b..44c216eb1be 100644 --- a/homeassistant/components/zamg/sensor.py +++ b/homeassistant/components/zamg/sensor.py @@ -11,15 +11,8 @@ import pytz import requests import voluptuous as vol -from homeassistant.components.weather import ( - ATTR_WEATHER_ATTRIBUTION, - ATTR_WEATHER_HUMIDITY, - ATTR_WEATHER_PRESSURE, - ATTR_WEATHER_TEMPERATURE, - ATTR_WEATHER_WIND_BEARING, - ATTR_WEATHER_WIND_SPEED, -) from homeassistant.const import ( + ATTR_ATTRIBUTION, CONF_LATITUDE, CONF_LONGITUDE, CONF_MONITORED_CONDITIONS, @@ -43,15 +36,15 @@ DEFAULT_NAME = "zamg" MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=10) SENSOR_TYPES = { - ATTR_WEATHER_PRESSURE: ("Pressure", "hPa", "LDstat hPa", float), + "pressure": ("Pressure", "hPa", "LDstat hPa", float), "pressure_sealevel": ("Pressure at Sea Level", "hPa", "LDred hPa", float), - ATTR_WEATHER_HUMIDITY: ("Humidity", "%", "RF %", int), - ATTR_WEATHER_WIND_SPEED: ("Wind Speed", "km/h", "WG km/h", float), - ATTR_WEATHER_WIND_BEARING: ("Wind Bearing", "°", "WR °", int), + "humidity": ("Humidity", "%", "RF %", int), + "wind_speed": ("Wind Speed", "km/h", "WG km/h", float), + "wind_bearing": ("Wind Bearing", "°", "WR °", int), "wind_max_speed": ("Top Wind Speed", "km/h", "WSG km/h", float), "wind_max_bearing": ("Top Wind Bearing", "°", "WSR °", int), "sun_last_hour": ("Sun Last Hour", "%", "SO %", int), - ATTR_WEATHER_TEMPERATURE: ("Temperature", "°C", "T °C", float), + "temperature": ("Temperature", "°C", "T °C", float), "precipitation": ("Precipitation", "l/m²", "N l/m²", float), "dewpoint": ("Dew Point", "°C", "TP °C", float), # The following probably not useful for general consumption, @@ -140,7 +133,7 @@ class ZamgSensor(Entity): def device_state_attributes(self): """Return the state attributes.""" return { - ATTR_WEATHER_ATTRIBUTION: ATTRIBUTION, + ATTR_ATTRIBUTION: ATTRIBUTION, ATTR_STATION: self.probe.get_data("station_name"), ATTR_UPDATED: self.probe.last_update.isoformat(), } diff --git a/script/hassfest/dependencies.py b/script/hassfest/dependencies.py index c67779d3c33..cb58de3af76 100644 --- a/script/hassfest/dependencies.py +++ b/script/hassfest/dependencies.py @@ -1,5 +1,6 @@ """Validate dependencies.""" import ast +from pathlib import Path from typing import Dict, Set from homeassistant.requirements import DISCOVERY_INTEGRATIONS @@ -13,21 +14,25 @@ class ImportCollector(ast.NodeVisitor): def __init__(self, integration: Integration): """Initialize the import collector.""" self.integration = integration - self.referenced: Set[str] = set() + self.referenced: Dict[Path, Set[str]] = {} - def maybe_add_reference(self, reference_domain: str): + # Current file or dir we're inspecting + self._cur_fil_dir = None + + def collect(self) -> None: + """Collect imports from a source file.""" + for fil in self.integration.path.glob("**/*.py"): + if not fil.is_file(): + continue + + self._cur_fil_dir = fil.relative_to(self.integration.path) + self.referenced[self._cur_fil_dir] = set() + self.visit(ast.parse(fil.read_text())) + self._cur_fil_dir = None + + def _add_reference(self, reference_domain: str): """Add a reference.""" - if ( - # If it's importing something from itself - reference_domain == self.integration.path.name - # Platform file - or (self.integration.path / f"{reference_domain}.py").exists() - # Platform dir - or (self.integration.path / reference_domain).exists() - ): - return - - self.referenced.add(reference_domain) + self.referenced[self._cur_fil_dir].add(reference_domain) def visit_ImportFrom(self, node): """Visit ImportFrom node.""" @@ -37,19 +42,19 @@ class ImportCollector(ast.NodeVisitor): if node.module.startswith("homeassistant.components."): # from homeassistant.components.alexa.smart_home import EVENT_ALEXA_SMART_HOME # from homeassistant.components.logbook import bla - self.maybe_add_reference(node.module.split(".")[2]) + self._add_reference(node.module.split(".")[2]) elif node.module == "homeassistant.components": # from homeassistant.components import sun for name_node in node.names: - self.maybe_add_reference(name_node.name) + self._add_reference(name_node.name) def visit_Import(self, node): """Visit Import node.""" # import homeassistant.components.hue as hue for name_node in node.names: if name_node.name.startswith("homeassistant.components."): - self.maybe_add_reference(name_node.name.split(".")[2]) + self._add_reference(name_node.name.split(".")[2]) def visit_Attribute(self, node): """Visit Attribute node.""" @@ -77,7 +82,7 @@ class ImportCollector(ast.NodeVisitor): ) ) ): - self.maybe_add_reference(node.attr) + self._add_reference(node.attr) else: # Have it visit other kids self.generic_visit(node) @@ -133,45 +138,93 @@ IGNORE_VIOLATIONS = [ ] -def validate_dependencies(integration: Integration): - """Validate all dependencies.""" - # Find usage of hass.components - collector = ImportCollector(integration) - - for fil in integration.path.glob("**/*.py"): - if not fil.is_file(): - continue - - collector.visit(ast.parse(fil.read_text())) - - referenced = ( - collector.referenced - - ALLOWED_USED_COMPONENTS - - set(integration.manifest["dependencies"]) - - set(integration.manifest.get("after_dependencies", [])) +def calc_allowed_references(integration: Integration) -> Set[str]: + """Return a set of allowed references.""" + allowed_references = ( + ALLOWED_USED_COMPONENTS + | set(integration.manifest["dependencies"]) + | set(integration.manifest.get("after_dependencies", [])) ) # Discovery requirements are ok if referenced in manifest for check_domain, to_check in DISCOVERY_INTEGRATIONS.items(): - if check_domain in referenced and any( - check in integration.manifest for check in to_check - ): - referenced.remove(check_domain) + if any(check in integration.manifest for check in to_check): + allowed_references.add(check_domain) - if referenced: - for domain in sorted(referenced): - if ( - integration.domain in IGNORE_VIOLATIONS - or (integration.domain, domain) in IGNORE_VIOLATIONS + return allowed_references + + +def find_non_referenced_integrations( + integrations: Dict[str, Integration], + integration: Integration, + references: Dict[Path, Set[str]], +): + """Find intergrations that are not allowed to be referenced.""" + allowed_references = calc_allowed_references(integration) + referenced = set() + for path, refs in references.items(): + if len(path.parts) == 1: + # climate.py is stored as climate + cur_fil_dir = path.stem + else: + # climate/__init__.py is stored as climate + cur_fil_dir = path.parts[0] + + is_platform_other_integration = cur_fil_dir in integrations + + for ref in refs: + # We are always allowed to import from ourselves + if ref == integration.domain: + continue + + # These references are approved based on the manifest + if ref in allowed_references: + continue + + # Some violations are whitelisted + if (integration.domain, ref) in IGNORE_VIOLATIONS: + continue + + # If it's a platform for another integration, the other integration is ok + if is_platform_other_integration and cur_fil_dir == ref: + continue + + # These have a platform specified in this integration + if not is_platform_other_integration and ( + (integration.path / f"{ref}.py").is_file() + # Platform dir + or (integration.path / ref).is_dir() ): continue - integration.add_error( - "dependencies", - "Using component {} but it's not in 'dependencies' or 'after_dependencies'".format( - domain - ), - ) + referenced.add(ref) + + return referenced + + +def validate_dependencies( + integrations: Dict[str, Integration], integration: Integration +): + """Validate all dependencies.""" + # Some integrations are allowed to have violations. + if integration.domain in IGNORE_VIOLATIONS: + return + + # Find usage of hass.components + collector = ImportCollector(integration) + collector.collect() + + for domain in sorted( + find_non_referenced_integrations( + integrations, integration, collector.referenced + ) + ): + integration.add_error( + "dependencies", + "Using component {} but it's not in 'dependencies' or 'after_dependencies'".format( + domain + ), + ) def validate(integrations: Dict[str, Integration], config): @@ -181,7 +234,7 @@ def validate(integrations: Dict[str, Integration], config): if not integration.manifest: continue - validate_dependencies(integration) + validate_dependencies(integrations, integration) # check that all referenced dependencies exist for dep in integration.manifest["dependencies"]: diff --git a/tests/hassfest/test_dependencies.py b/tests/hassfest/test_dependencies.py index 0f07c45317e..b9690107619 100644 --- a/tests/hassfest/test_dependencies.py +++ b/tests/hassfest/test_dependencies.py @@ -9,7 +9,8 @@ from script.hassfest.dependencies import ImportCollector def mock_collector(): """Fixture with import collector that adds all referenced nodes.""" collector = ImportCollector(None) - collector.maybe_add_reference = collector.referenced.add + collector.unfiltered_referenced = set() + collector._add_reference = collector.unfiltered_referenced.add return collector @@ -22,7 +23,7 @@ from homeassistant.components import child_import """ ) ) - assert mock_collector.referenced == {"child_import"} + assert mock_collector.unfiltered_referenced == {"child_import"} def test_subimport(mock_collector): @@ -34,7 +35,7 @@ from homeassistant.components.subimport.smart_home import EVENT_ALEXA_SMART_HOME """ ) ) - assert mock_collector.referenced == {"subimport"} + assert mock_collector.unfiltered_referenced == {"subimport"} def test_child_import_field(mock_collector): @@ -46,7 +47,7 @@ from homeassistant.components.child_import_field import bla """ ) ) - assert mock_collector.referenced == {"child_import_field"} + assert mock_collector.unfiltered_referenced == {"child_import_field"} def test_renamed_absolute(mock_collector): @@ -58,7 +59,7 @@ import homeassistant.components.renamed_absolute as hue """ ) ) - assert mock_collector.referenced == {"renamed_absolute"} + assert mock_collector.unfiltered_referenced == {"renamed_absolute"} def test_hass_components_var(mock_collector): @@ -71,7 +72,7 @@ def bla(hass): """ ) ) - assert mock_collector.referenced == {"hass_components_var"} + assert mock_collector.unfiltered_referenced == {"hass_components_var"} def test_hass_components_class(mock_collector): @@ -85,7 +86,7 @@ class Hello: """ ) ) - assert mock_collector.referenced == {"hass_components_class"} + assert mock_collector.unfiltered_referenced == {"hass_components_class"} def test_all_imports(mock_collector): @@ -110,7 +111,7 @@ class Hello: """ ) ) - assert mock_collector.referenced == { + assert mock_collector.unfiltered_referenced == { "child_import", "subimport", "child_import_field", From 80be3b74a98a7dd29e0204f1acc22253316c1625 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Thu, 19 Dec 2019 14:10:27 +0100 Subject: [PATCH 345/677] Init entities as unavailable when offline (#29738) --- homeassistant/components/airly/__init__.py | 4 ---- homeassistant/components/airly/air_quality.py | 21 +++++++++++-------- homeassistant/components/airly/sensor.py | 15 +++++++++---- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/airly/__init__.py b/homeassistant/components/airly/__init__.py index ce165918ac2..17e1d27e571 100644 --- a/homeassistant/components/airly/__init__.py +++ b/homeassistant/components/airly/__init__.py @@ -10,7 +10,6 @@ import async_timeout from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE from homeassistant.core import Config, HomeAssistant -from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.util import Throttle @@ -48,9 +47,6 @@ async def async_setup_entry(hass, config_entry): await airly.async_update() - if not airly.data: - raise ConfigEntryNotReady() - hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = airly hass.async_create_task( diff --git a/homeassistant/components/airly/air_quality.py b/homeassistant/components/airly/air_quality.py index e22fa7939c2..b48a360da28 100644 --- a/homeassistant/components/airly/air_quality.py +++ b/homeassistant/components/airly/air_quality.py @@ -5,7 +5,7 @@ from homeassistant.components.air_quality import ( ATTR_PM_10, AirQualityEntity, ) -from homeassistant.const import CONF_NAME +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME from .const import ( ATTR_API_ADVICE, @@ -35,10 +35,13 @@ LABEL_PM_10_PERCENT = f"{ATTR_PM_10}_percent_of_limit" async def async_setup_entry(hass, config_entry, async_add_entities): """Set up Airly air_quality entity based on a config entry.""" name = config_entry.data[CONF_NAME] + latitude = config_entry.data[CONF_LATITUDE] + longitude = config_entry.data[CONF_LONGITUDE] + unique_id = f"{latitude}-{longitude}" data = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] - async_add_entities([AirlyAirQuality(data, name)], True) + async_add_entities([AirlyAirQuality(data, name, unique_id)], True) def round_state(func): @@ -56,11 +59,12 @@ def round_state(func): class AirlyAirQuality(AirQualityEntity): """Define an Airly air quality.""" - def __init__(self, airly, name): + def __init__(self, airly, name, unique_id): """Initialize.""" self.airly = airly self.data = airly.data self._name = name + self._unique_id = unique_id self._pm_2_5 = None self._pm_10 = None self._aqi = None @@ -108,12 +112,12 @@ class AirlyAirQuality(AirQualityEntity): @property def unique_id(self): """Return a unique_id for this entity.""" - return f"{self.airly.latitude}-{self.airly.longitude}" + return self._unique_id @property def available(self): """Return True if entity is available.""" - return bool(self.airly.data) + return bool(self.data) @property def device_state_attributes(self): @@ -132,7 +136,6 @@ class AirlyAirQuality(AirQualityEntity): if self.airly.data: self.data = self.airly.data - - self._pm_10 = self.data[ATTR_API_PM10] - self._pm_2_5 = self.data[ATTR_API_PM25] - self._aqi = self.data[ATTR_API_CAQI] + self._pm_10 = self.data[ATTR_API_PM10] + self._pm_2_5 = self.data[ATTR_API_PM25] + self._aqi = self.data[ATTR_API_CAQI] diff --git a/homeassistant/components/airly/sensor.py b/homeassistant/components/airly/sensor.py index bce32d64041..af0eac39cdc 100644 --- a/homeassistant/components/airly/sensor.py +++ b/homeassistant/components/airly/sensor.py @@ -2,6 +2,8 @@ from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_DEVICE_CLASS, + CONF_LATITUDE, + CONF_LONGITUDE, CONF_NAME, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_PRESSURE, @@ -60,12 +62,16 @@ SENSOR_TYPES = { async def async_setup_entry(hass, config_entry, async_add_entities): """Set up Airly sensor entities based on a config entry.""" name = config_entry.data[CONF_NAME] + latitude = config_entry.data[CONF_LATITUDE] + longitude = config_entry.data[CONF_LONGITUDE] data = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] sensors = [] for sensor in SENSOR_TYPES: - sensors.append(AirlySensor(data, name, sensor)) + unique_id = f"{latitude}-{longitude}-{sensor.lower()}" + sensors.append(AirlySensor(data, name, sensor, unique_id)) + async_add_entities(sensors, True) @@ -84,11 +90,12 @@ def round_state(func): class AirlySensor(Entity): """Define an Airly sensor.""" - def __init__(self, airly, name, kind): + def __init__(self, airly, name, kind, unique_id): """Initialize.""" self.airly = airly self.data = airly.data self._name = name + self._unique_id = unique_id self.kind = kind self._device_class = None self._state = None @@ -130,7 +137,7 @@ class AirlySensor(Entity): @property def unique_id(self): """Return a unique_id for this entity.""" - return f"{self.airly.latitude}-{self.airly.longitude}-{self.kind.lower()}" + return self._unique_id @property def unit_of_measurement(self): @@ -140,7 +147,7 @@ class AirlySensor(Entity): @property def available(self): """Return True if entity is available.""" - return bool(self.airly.data) + return bool(self.data) async def async_update(self): """Update the sensor.""" From 0cb468d7b0987733600bbc850a5e6a1538509784 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Thu, 19 Dec 2019 17:45:52 +0100 Subject: [PATCH 346/677] Make name of nmbs live sensor customizable via unique_id (#29662) * Make name customizable for nmbs live sensors * Change order of the live name * Added unique id to live nmbs sensor * Revert changing the name of the live sensor --- homeassistant/components/nmbs/sensor.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/nmbs/sensor.py b/homeassistant/components/nmbs/sensor.py index 26802808c0a..35c928deb37 100644 --- a/homeassistant/components/nmbs/sensor.py +++ b/homeassistant/components/nmbs/sensor.py @@ -92,7 +92,7 @@ class NMBSLiveBoard(Entity): """Initialize the sensor for getting liveboard data.""" self._station = live_station self._api_client = api_client - + self._unique_id = f"nmbs_live_{self._station}" self._attrs = {} self._state = None @@ -101,6 +101,11 @@ class NMBSLiveBoard(Entity): """Return the sensor default name.""" return "NMBS Live" + @property + def unique_id(self): + """Return a unique ID.""" + return self._unique_id + @property def icon(self): """Return the default icon or an alert icon if delays.""" From d236a19139311640610d165b16ed6c9acf1e45e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Thu, 19 Dec 2019 19:28:03 +0200 Subject: [PATCH 347/677] Include all SSDP data in discovery info (#28197) * Include all SSDP data in discovery info * Use UPnP device description as discovery info, inject some SSDP attrs * Clean up attribute names * Adapt existing SSDP flows to changed attribute names * Prefix all SSDP UPnP attribute name constants with ATTR_UPNP, tweak a bit --- .../components/deconz/config_flow.py | 20 ++++---- homeassistant/components/heos/config_flow.py | 10 ++-- .../components/huawei_lte/config_flow.py | 8 ++-- homeassistant/components/hue/config_flow.py | 18 ++++--- homeassistant/components/ssdp/__init__.py | 47 +++++++------------ tests/components/deconz/test_config_flow.py | 21 ++++----- tests/components/deconz/test_gateway.py | 9 ++-- tests/components/heos/conftest.py | 19 ++++---- tests/components/heos/test_config_flow.py | 10 ++-- .../components/huawei_lte/test_config_flow.py | 38 +++++---------- tests/components/hue/test_config_flow.py | 31 ++++++------ tests/components/ssdp/test_init.py | 22 +++++++-- 12 files changed, 128 insertions(+), 125 deletions(-) diff --git a/homeassistant/components/deconz/config_flow.py b/homeassistant/components/deconz/config_flow.py index 8c8ba318e83..05b7c18f448 100644 --- a/homeassistant/components/deconz/config_flow.py +++ b/homeassistant/components/deconz/config_flow.py @@ -1,5 +1,6 @@ """Config flow to configure deCONZ component.""" import asyncio +from urllib.parse import urlparse import async_timeout from pydeconz.errors import RequestError, ResponseError @@ -7,7 +8,7 @@ from pydeconz.utils import async_discovery, async_get_api_key, async_get_gateway import voluptuous as vol from homeassistant import config_entries -from homeassistant.components.ssdp import ATTR_MANUFACTURERURL, ATTR_SERIAL +from homeassistant.components import ssdp from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT from homeassistant.core import callback from homeassistant.helpers import aiohttp_client @@ -26,7 +27,6 @@ from .const import ( DECONZ_MANUFACTURERURL = "http://www.dresden-elektronik.de" CONF_SERIAL = "serial" -ATTR_UUID = "udn" @callback @@ -170,18 +170,20 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_ssdp(self, discovery_info): """Handle a discovered deCONZ bridge.""" - if discovery_info[ATTR_MANUFACTURERURL] != DECONZ_MANUFACTURERURL: + if discovery_info[ssdp.ATTR_UPNP_MANUFACTURER_URL] != DECONZ_MANUFACTURERURL: return self.async_abort(reason="not_deconz_bridge") - uuid = discovery_info[ATTR_UUID].replace("uuid:", "") + uuid = discovery_info[ssdp.ATTR_UPNP_UDN].replace("uuid:", "") _LOGGER.debug("deCONZ gateway discovered (%s)", uuid) + parsed_url = urlparse(discovery_info[ssdp.ATTR_SSDP_LOCATION]) + for entry in self.hass.config_entries.async_entries(DOMAIN): if uuid == entry.data.get(CONF_UUID): - return await self._update_entry(entry, discovery_info[CONF_HOST]) + return await self._update_entry(entry, parsed_url.hostname) - bridgeid = discovery_info[ATTR_SERIAL] + bridgeid = discovery_info[ssdp.ATTR_UPNP_SERIAL] if any( bridgeid == flow["context"][CONF_BRIDGEID] for flow in self._async_in_progress() @@ -190,11 +192,11 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context[CONF_BRIDGEID] = bridgeid - self.context["title_placeholders"] = {"host": discovery_info[CONF_HOST]} + self.context["title_placeholders"] = {"host": parsed_url.hostname} self.deconz_config = { - CONF_HOST: discovery_info[CONF_HOST], - CONF_PORT: discovery_info[CONF_PORT], + CONF_HOST: parsed_url.hostname, + CONF_PORT: parsed_url.port, } return await self.async_step_link() diff --git a/homeassistant/components/heos/config_flow.py b/homeassistant/components/heos/config_flow.py index 4380cb4d8ba..7e7fe067874 100644 --- a/homeassistant/components/heos/config_flow.py +++ b/homeassistant/components/heos/config_flow.py @@ -1,9 +1,12 @@ """Config flow to configure Heos.""" +from urllib.parse import urlparse + from pyheos import Heos, HeosError import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import CONF_HOST, CONF_NAME +from homeassistant.components import ssdp +from homeassistant.const import CONF_HOST from .const import DATA_DISCOVERED_HOSTS, DOMAIN @@ -23,11 +26,12 @@ class HeosFlowHandler(config_entries.ConfigFlow): async def async_step_ssdp(self, discovery_info): """Handle a discovered Heos device.""" # Store discovered host + hostname = urlparse(discovery_info[ssdp.ATTR_SSDP_LOCATION]).hostname friendly_name = "{} ({})".format( - discovery_info[CONF_NAME], discovery_info[CONF_HOST] + discovery_info[ssdp.ATTR_UPNP_FRIENDLY_NAME], hostname ) self.hass.data.setdefault(DATA_DISCOVERED_HOSTS, {}) - self.hass.data[DATA_DISCOVERED_HOSTS][friendly_name] = discovery_info[CONF_HOST] + self.hass.data[DATA_DISCOVERED_HOSTS][friendly_name] = hostname # Abort if other flows in progress or an entry already exists if self._async_in_progress() or self._async_current_entries(): return self.async_abort(reason="already_setup") diff --git a/homeassistant/components/huawei_lte/config_flow.py b/homeassistant/components/huawei_lte/config_flow.py index 9151e5eb999..b316472efaf 100644 --- a/homeassistant/components/huawei_lte/config_flow.py +++ b/homeassistant/components/huawei_lte/config_flow.py @@ -3,6 +3,7 @@ from collections import OrderedDict import logging from typing import Optional +from urllib.parse import urlparse from huawei_lte_api.AuthorizedConnection import AuthorizedConnection from huawei_lte_api.Client import Client @@ -19,7 +20,7 @@ from url_normalize import url_normalize import voluptuous as vol from homeassistant import config_entries -from homeassistant.components.ssdp import ATTR_HOST, ATTR_NAME, ATTR_PRESENTATIONURL +from homeassistant.components import ssdp from homeassistant.const import CONF_PASSWORD, CONF_RECIPIENT, CONF_URL, CONF_USERNAME from homeassistant.core import callback @@ -208,13 +209,14 @@ class ConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Handle SSDP initiated config flow.""" # Attempt to distinguish from other non-LTE Huawei router devices, at least # some ones we are interested in have "Mobile Wi-Fi" friendlyName. - if "mobile" not in discovery_info.get(ATTR_NAME, "").lower(): + if "mobile" not in discovery_info.get(ssdp.ATTR_UPNP_FRIENDLY_NAME, "").lower(): return self.async_abort(reason="not_huawei_lte") # https://github.com/PyCQA/pylint/issues/3167 url = self.context[CONF_URL] = url_normalize( # pylint: disable=no-member discovery_info.get( - ATTR_PRESENTATIONURL, f"http://{discovery_info[ATTR_HOST]}/" + ssdp.ATTR_UPNP_PRESENTATION_URL, + f"http://{urlparse(discovery_info[ssdp.ATTR_SSDP_LOCATION]).hostname}/", ) ) diff --git a/homeassistant/components/hue/config_flow.py b/homeassistant/components/hue/config_flow.py index 97cc1a8d66c..60000a68fb7 100644 --- a/homeassistant/components/hue/config_flow.py +++ b/homeassistant/components/hue/config_flow.py @@ -1,6 +1,7 @@ """Config flow to configure Philips Hue.""" import asyncio from typing import Dict, Optional +from urllib.parse import urlparse import aiohue from aiohue.discovery import discover_nupnp, normalize_bridge_id @@ -8,7 +9,7 @@ import async_timeout import voluptuous as vol from homeassistant import config_entries, core -from homeassistant.components.ssdp import ATTR_MANUFACTURERURL, ATTR_NAME +from homeassistant.components import ssdp from homeassistant.helpers import aiohttp_client from .bridge import authenticate_bridge @@ -147,22 +148,25 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): host is already configured and delegate to the import step if not. """ # Filter out non-Hue bridges #1 - if discovery_info[ATTR_MANUFACTURERURL] != HUE_MANUFACTURERURL: + if discovery_info[ssdp.ATTR_UPNP_MANUFACTURER_URL] != HUE_MANUFACTURERURL: return self.async_abort(reason="not_hue_bridge") # Filter out non-Hue bridges #2 if any( - name in discovery_info.get(ATTR_NAME, "") + name in discovery_info.get(ssdp.ATTR_UPNP_FRIENDLY_NAME, "") for name in HUE_IGNORED_BRIDGE_NAMES ): return self.async_abort(reason="not_hue_bridge") - if "host" not in discovery_info or "serial" not in discovery_info: + if ( + ssdp.ATTR_SSDP_LOCATION not in discovery_info + or ssdp.ATTR_UPNP_SERIAL not in discovery_info + ): return self.async_abort(reason="not_hue_bridge") - bridge = self._async_get_bridge( - discovery_info["host"], discovery_info["serial"] - ) + host = urlparse(discovery_info[ssdp.ATTR_SSDP_LOCATION]).hostname + + bridge = self._async_get_bridge(host, discovery_info[ssdp.ATTR_UPNP_SERIAL]) await self.async_set_unique_id(bridge.id) self._abort_if_unique_id_configured() diff --git a/homeassistant/components/ssdp/__init__.py b/homeassistant/components/ssdp/__init__.py index 1a97b1721fc..94e256f0523 100644 --- a/homeassistant/components/ssdp/__init__.py +++ b/homeassistant/components/ssdp/__init__.py @@ -2,7 +2,6 @@ import asyncio from datetime import timedelta import logging -from urllib.parse import urlparse import aiohttp from defusedxml import ElementTree @@ -14,19 +13,19 @@ from homeassistant.helpers.event import async_track_time_interval DOMAIN = "ssdp" SCAN_INTERVAL = timedelta(seconds=60) -ATTR_HOST = "host" -ATTR_PORT = "port" -ATTR_SSDP_DESCRIPTION = "ssdp_description" -ATTR_ST = "ssdp_st" -ATTR_NAME = "name" -ATTR_MODEL_NAME = "model_name" -ATTR_MODEL_NUMBER = "model_number" -ATTR_SERIAL = "serial_number" -ATTR_MANUFACTURER = "manufacturer" -ATTR_MANUFACTURERURL = "manufacturerURL" -ATTR_UDN = "udn" -ATTR_UPNP_DEVICE_TYPE = "upnp_device_type" -ATTR_PRESENTATIONURL = "presentation_url" +# Attributes for accessing info from SSDP response +ATTR_SSDP_LOCATION = "ssdp_location" +ATTR_SSDP_ST = "ssdp_st" +# Attributes for accessing info from retrieved UPnP device description +ATTR_UPNP_DEVICE_TYPE = "deviceType" +ATTR_UPNP_FRIENDLY_NAME = "friendlyName" +ATTR_UPNP_MANUFACTURER = "manufacturer" +ATTR_UPNP_MANUFACTURER_URL = "manufacturerURL" +ATTR_UPNP_MODEL_NAME = "modelName" +ATTR_UPNP_MODEL_NUMBER = "modelNumber" +ATTR_UPNP_PRESENTATION_URL = "presentationURL" +ATTR_UPNP_SERIAL = "serialNumber" +ATTR_UPNP_UDN = "UDN" _LOGGER = logging.getLogger(__name__) @@ -157,24 +156,12 @@ class Scanner: def info_from_entry(entry, device_info): - """Get most important info from an entry.""" - url = urlparse(entry.location) + """Get info from an entry.""" info = { - ATTR_HOST: url.hostname, - ATTR_PORT: url.port, - ATTR_SSDP_DESCRIPTION: entry.location, - ATTR_ST: entry.st, + ATTR_SSDP_LOCATION: entry.location, + ATTR_SSDP_ST: entry.st, } - if device_info: - info[ATTR_NAME] = device_info.get("friendlyName") - info[ATTR_MODEL_NAME] = device_info.get("modelName") - info[ATTR_MODEL_NUMBER] = device_info.get("modelNumber") - info[ATTR_SERIAL] = device_info.get("serialNumber") - info[ATTR_MANUFACTURER] = device_info.get("manufacturer") - info[ATTR_MANUFACTURERURL] = device_info.get("manufacturerURL") - info[ATTR_UDN] = device_info.get("UDN") - info[ATTR_UPNP_DEVICE_TYPE] = device_info.get("deviceType") - info[ATTR_PRESENTATIONURL] = device_info.get("presentationURL") + info.update(device_info) return info diff --git a/tests/components/deconz/test_config_flow.py b/tests/components/deconz/test_config_flow.py index b95743c95ad..2a1fb43333f 100644 --- a/tests/components/deconz/test_config_flow.py +++ b/tests/components/deconz/test_config_flow.py @@ -4,8 +4,8 @@ from unittest.mock import Mock, patch import pydeconz +from homeassistant.components import ssdp from homeassistant.components.deconz import config_flow -from homeassistant.components.ssdp import ATTR_MANUFACTURERURL, ATTR_SERIAL from tests.common import MockConfigEntry @@ -213,11 +213,10 @@ async def test_bridge_ssdp_discovery(hass): result = await hass.config_entries.flow.async_init( config_flow.DOMAIN, data={ - config_flow.CONF_HOST: "1.2.3.4", - config_flow.CONF_PORT: 80, - ATTR_SERIAL: "id", - ATTR_MANUFACTURERURL: config_flow.DECONZ_MANUFACTURERURL, - config_flow.ATTR_UUID: "uuid:1234", + ssdp.ATTR_SSDP_LOCATION: "http://1.2.3.4:80/", + ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.DECONZ_MANUFACTURERURL, + ssdp.ATTR_UPNP_SERIAL: "id", + ssdp.ATTR_UPNP_UDN: "uuid:1234", }, context={"source": "ssdp"}, ) @@ -230,7 +229,7 @@ async def test_bridge_ssdp_discovery_not_deconz_bridge(hass): """Test a non deconz bridge being discovered over ssdp.""" result = await hass.config_entries.flow.async_init( config_flow.DOMAIN, - data={ATTR_MANUFACTURERURL: "not deconz bridge"}, + data={ssdp.ATTR_UPNP_MANUFACTURER_URL: "not deconz bridge"}, context={"source": "ssdp"}, ) @@ -257,10 +256,10 @@ async def test_bridge_discovery_update_existing_entry(hass): result = await hass.config_entries.flow.async_init( config_flow.DOMAIN, data={ - config_flow.CONF_HOST: "mock-deconz", - ATTR_SERIAL: "123ABC", - ATTR_MANUFACTURERURL: config_flow.DECONZ_MANUFACTURERURL, - config_flow.ATTR_UUID: "uuid:456DEF", + ssdp.ATTR_SSDP_LOCATION: "http://mock-deconz/", + ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.DECONZ_MANUFACTURERURL, + ssdp.ATTR_UPNP_SERIAL: "123ABC", + ssdp.ATTR_UPNP_UDN: "uuid:456DEF", }, context={"source": "ssdp"}, ) diff --git a/tests/components/deconz/test_gateway.py b/tests/components/deconz/test_gateway.py index 109bb325dac..288868f1bec 100644 --- a/tests/components/deconz/test_gateway.py +++ b/tests/components/deconz/test_gateway.py @@ -145,11 +145,10 @@ async def test_update_address(hass): await hass.config_entries.flow.async_init( deconz.config_flow.DOMAIN, data={ - deconz.config_flow.CONF_HOST: "2.3.4.5", - deconz.config_flow.CONF_PORT: 80, - ssdp.ATTR_SERIAL: BRIDGEID, - ssdp.ATTR_MANUFACTURERURL: deconz.config_flow.DECONZ_MANUFACTURERURL, - deconz.config_flow.ATTR_UUID: "uuid:456DEF", + ssdp.ATTR_SSDP_LOCATION: "http://2.3.4.5:80/", + ssdp.ATTR_UPNP_MANUFACTURER_URL: deconz.config_flow.DECONZ_MANUFACTURERURL, + ssdp.ATTR_UPNP_SERIAL: BRIDGEID, + ssdp.ATTR_UPNP_UDN: "uuid:456DEF", }, context={"source": "ssdp"}, ) diff --git a/tests/components/heos/conftest.py b/tests/components/heos/conftest.py index c4610fdc1e7..5201b7f7b8a 100644 --- a/tests/components/heos/conftest.py +++ b/tests/components/heos/conftest.py @@ -5,6 +5,7 @@ from asynctest.mock import Mock, patch as patch from pyheos import Dispatcher, Heos, HeosPlayer, HeosSource, InputSource, const import pytest +from homeassistant.components import ssdp from homeassistant.components.heos import DOMAIN from homeassistant.const import CONF_HOST @@ -118,16 +119,14 @@ def dispatcher_fixture() -> Dispatcher: def discovery_data_fixture() -> dict: """Return mock discovery data for testing.""" return { - "host": "127.0.0.1", - "manufacturer": "Denon", - "model_name": "HEOS Drive", - "model_number": "DWSA-10 4.0", - "name": "Office", - "port": 60006, - "serial": None, - "ssdp_description": "http://127.0.0.1:60006/upnp/desc/aios_device/aios_device.xml", - "udn": "uuid:e61de70c-2250-1c22-0080-0005cdf512be", - "upnp_device_type": "urn:schemas-denon-com:device:AiosDevice:1", + ssdp.ATTR_SSDP_LOCATION: "http://127.0.0.1:60006/upnp/desc/aios_device/aios_device.xml", + ssdp.ATTR_UPNP_DEVICE_TYPE: "urn:schemas-denon-com:device:AiosDevice:1", + ssdp.ATTR_UPNP_FRIENDLY_NAME: "Office", + ssdp.ATTR_UPNP_MANUFACTURER: "Denon", + ssdp.ATTR_UPNP_MODEL_NAME: "HEOS Drive", + ssdp.ATTR_UPNP_MODEL_NUMBER: "DWSA-10 4.0", + ssdp.ATTR_UPNP_SERIAL: None, + ssdp.ATTR_UPNP_UDN: "uuid:e61de70c-2250-1c22-0080-0005cdf512be", } diff --git a/tests/components/heos/test_config_flow.py b/tests/components/heos/test_config_flow.py index df021fea55d..84e5dce1f1c 100644 --- a/tests/components/heos/test_config_flow.py +++ b/tests/components/heos/test_config_flow.py @@ -1,10 +1,13 @@ """Tests for the Heos config flow module.""" +from urllib.parse import urlparse + from pyheos import HeosError from homeassistant import data_entry_flow +from homeassistant.components import ssdp from homeassistant.components.heos.config_flow import HeosFlowHandler from homeassistant.components.heos.const import DATA_DISCOVERED_HOSTS, DOMAIN -from homeassistant.const import CONF_HOST, CONF_NAME +from homeassistant.const import CONF_HOST async def test_flow_aborts_already_setup(hass, config_entry): @@ -79,8 +82,9 @@ async def test_discovery_shows_create_form(hass, controller, discovery_data): assert len(hass.config_entries.flow.async_progress()) == 1 assert hass.data[DATA_DISCOVERED_HOSTS] == {"Office (127.0.0.1)": "127.0.0.1"} - discovery_data[CONF_HOST] = "127.0.0.2" - discovery_data[CONF_NAME] = "Bedroom" + port = urlparse(discovery_data[ssdp.ATTR_SSDP_LOCATION]).port + discovery_data[ssdp.ATTR_SSDP_LOCATION] = f"http://127.0.0.2:{port}/" + discovery_data[ssdp.ATTR_UPNP_FRIENDLY_NAME] = "Bedroom" await hass.config_entries.flow.async_init( DOMAIN, context={"source": "ssdp"}, data=discovery_data ) diff --git a/tests/components/huawei_lte/test_config_flow.py b/tests/components/huawei_lte/test_config_flow.py index b14583f13cd..29127ed964b 100644 --- a/tests/components/huawei_lte/test_config_flow.py +++ b/tests/components/huawei_lte/test_config_flow.py @@ -7,22 +7,9 @@ from requests.exceptions import ConnectionError from requests_mock import ANY from homeassistant import data_entry_flow +from homeassistant.components import ssdp from homeassistant.components.huawei_lte.config_flow import ConfigFlowHandler from homeassistant.components.huawei_lte.const import DOMAIN -from homeassistant.components.ssdp import ( - ATTR_HOST, - ATTR_MANUFACTURER, - ATTR_MANUFACTURERURL, - ATTR_MODEL_NAME, - ATTR_MODEL_NUMBER, - ATTR_NAME, - ATTR_PORT, - ATTR_PRESENTATIONURL, - ATTR_SERIAL, - ATTR_ST, - ATTR_UDN, - ATTR_UPNP_DEVICE_TYPE, -) from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME from tests.common import MockConfigEntry @@ -156,18 +143,17 @@ async def test_ssdp(flow): url = "http://192.168.100.1/" result = await flow.async_step_ssdp( discovery_info={ - ATTR_ST: "upnp:rootdevice", - ATTR_PORT: 60957, - ATTR_HOST: "192.168.100.1", - ATTR_MANUFACTURER: "Huawei", - ATTR_MANUFACTURERURL: "http://www.huawei.com/", - ATTR_MODEL_NAME: "Huawei router", - ATTR_MODEL_NUMBER: "12345678", - ATTR_NAME: "Mobile Wi-Fi", - ATTR_PRESENTATIONURL: url, - ATTR_SERIAL: "00000000", - ATTR_UDN: "uuid:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", - ATTR_UPNP_DEVICE_TYPE: "urn:schemas-upnp-org:device:InternetGatewayDevice:1", + ssdp.ATTR_SSDP_LOCATION: "http://192.168.100.1:60957/rootDesc.xml", + ssdp.ATTR_SSDP_ST: "upnp:rootdevice", + ssdp.ATTR_UPNP_DEVICE_TYPE: "urn:schemas-upnp-org:device:InternetGatewayDevice:1", + ssdp.ATTR_UPNP_FRIENDLY_NAME: "Mobile Wi-Fi", + ssdp.ATTR_UPNP_MANUFACTURER: "Huawei", + ssdp.ATTR_UPNP_MANUFACTURER_URL: "http://www.huawei.com/", + ssdp.ATTR_UPNP_MODEL_NAME: "Huawei router", + ssdp.ATTR_UPNP_MODEL_NUMBER: "12345678", + ssdp.ATTR_UPNP_PRESENTATION_URL: url, + ssdp.ATTR_UPNP_SERIAL: "00000000", + ssdp.ATTR_UPNP_UDN: "uuid:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", } ) diff --git a/tests/components/hue/test_config_flow.py b/tests/components/hue/test_config_flow.py index 64a9c81136a..2ad6474b9ac 100644 --- a/tests/components/hue/test_config_flow.py +++ b/tests/components/hue/test_config_flow.py @@ -7,6 +7,7 @@ import pytest import voluptuous as vol from homeassistant import config_entries, data_entry_flow +from homeassistant.components import ssdp from homeassistant.components.hue import config_flow, const from tests.common import MockConfigEntry, mock_coro @@ -208,9 +209,9 @@ async def test_bridge_ssdp(hass): result = await flow.async_step_ssdp( { - "host": "0.0.0.0", - "serial": "1234", - "manufacturerURL": config_flow.HUE_MANUFACTURERURL, + ssdp.ATTR_SSDP_LOCATION: "http://0.0.0.0/", + ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL, + ssdp.ATTR_UPNP_SERIAL: "1234", } ) @@ -224,7 +225,7 @@ async def test_bridge_ssdp_discover_other_bridge(hass): flow.hass = hass result = await flow.async_step_ssdp( - {"manufacturerURL": "http://www.notphilips.com"} + {ssdp.ATTR_UPNP_MANUFACTURER_URL: "http://www.notphilips.com"} ) assert result["type"] == "abort" @@ -238,10 +239,10 @@ async def test_bridge_ssdp_emulated_hue(hass): result = await flow.async_step_ssdp( { - "name": "HASS Bridge", - "host": "0.0.0.0", - "serial": "1234", - "manufacturerURL": config_flow.HUE_MANUFACTURERURL, + ssdp.ATTR_SSDP_LOCATION: "http://0.0.0.0/", + ssdp.ATTR_UPNP_FRIENDLY_NAME: "HASS Bridge", + ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL, + ssdp.ATTR_UPNP_SERIAL: "1234", } ) @@ -257,10 +258,10 @@ async def test_bridge_ssdp_espalexa(hass): result = await flow.async_step_ssdp( { - "name": "Espalexa (0.0.0.0)", - "host": "0.0.0.0", - "serial": "1234", - "manufacturerURL": config_flow.HUE_MANUFACTURERURL, + ssdp.ATTR_SSDP_LOCATION: "http://0.0.0.0/", + ssdp.ATTR_UPNP_FRIENDLY_NAME: "Espalexa (0.0.0.0)", + ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL, + ssdp.ATTR_UPNP_SERIAL: "1234", } ) @@ -281,9 +282,9 @@ async def test_bridge_ssdp_already_configured(hass): with pytest.raises(data_entry_flow.AbortFlow): await flow.async_step_ssdp( { - "host": "0.0.0.0", - "serial": "1234", - "manufacturerURL": config_flow.HUE_MANUFACTURERURL, + ssdp.ATTR_SSDP_LOCATION: "http://0.0.0.0/", + ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL, + ssdp.ATTR_UPNP_SERIAL: "1234", } ) diff --git a/tests/components/ssdp/test_init.py b/tests/components/ssdp/test_init.py index e224842b5ab..62b7c2bbde2 100644 --- a/tests/components/ssdp/test_init.py +++ b/tests/components/ssdp/test_init.py @@ -27,7 +27,9 @@ async def test_scan_match_st(hass): assert mock_init.mock_calls[0][2]["context"] == {"source": "ssdp"} -@pytest.mark.parametrize("key", ("manufacturer", "deviceType")) +@pytest.mark.parametrize( + "key", (ssdp.ATTR_UPNP_MANUFACTURER, ssdp.ATTR_UPNP_DEVICE_TYPE) +) async def test_scan_match_upnp_devicedesc(hass, aioclient_mock, key): """Test matching based on UPnP device description data.""" aioclient_mock.get( @@ -74,7 +76,14 @@ async def test_scan_not_all_present(hass, aioclient_mock): return_value=[Mock(st="mock-st", location="http://1.1.1.1")], ), patch.dict( gn_ssdp.SSDP, - {"mock-domain": [{"deviceType": "Paulus", "manufacturer": "Paulus"}]}, + { + "mock-domain": [ + { + ssdp.ATTR_UPNP_DEVICE_TYPE: "Paulus", + ssdp.ATTR_UPNP_MANUFACTURER: "Paulus", + } + ] + }, ), patch.object( hass.config_entries.flow, "async_init", return_value=mock_coro() ) as mock_init: @@ -103,7 +112,14 @@ async def test_scan_not_all_match(hass, aioclient_mock): return_value=[Mock(st="mock-st", location="http://1.1.1.1")], ), patch.dict( gn_ssdp.SSDP, - {"mock-domain": [{"deviceType": "Paulus", "manufacturer": "Not-Paulus"}]}, + { + "mock-domain": [ + { + ssdp.ATTR_UPNP_DEVICE_TYPE: "Paulus", + ssdp.ATTR_UPNP_MANUFACTURER: "Not-Paulus", + } + ] + }, ), patch.object( hass.config_entries.flow, "async_init", return_value=mock_coro() ) as mock_init: From 3a610edb78b15059b8edb663482e2087fb757507 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Fri, 20 Dec 2019 00:32:22 +0000 Subject: [PATCH 348/677] [ci skip] Translation update --- .../components/fan/.translations/hu.json | 16 ++++++++++++++++ .../components/lock/.translations/hu.json | 17 +++++++++++++++++ .../components/vacuum/.translations/hu.json | 16 ++++++++++++++++ 3 files changed, 49 insertions(+) create mode 100644 homeassistant/components/fan/.translations/hu.json create mode 100644 homeassistant/components/lock/.translations/hu.json create mode 100644 homeassistant/components/vacuum/.translations/hu.json diff --git a/homeassistant/components/fan/.translations/hu.json b/homeassistant/components/fan/.translations/hu.json new file mode 100644 index 00000000000..b559f29c581 --- /dev/null +++ b/homeassistant/components/fan/.translations/hu.json @@ -0,0 +1,16 @@ +{ + "device_automation": { + "action_type": { + "turn_off": "{entity_name} kikapcsol\u00e1sa", + "turn_on": "{entity_name} bekapcsol\u00e1sa" + }, + "condition_type": { + "is_off": "{entity_name} ki van kapcsolva", + "is_on": "{entity_name} be van kapcsolva" + }, + "trigger_type": { + "turned_off": "{entity_name} ki lett kapcsolva", + "turned_on": "{entity_name} be lett kapcsolva" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lock/.translations/hu.json b/homeassistant/components/lock/.translations/hu.json new file mode 100644 index 00000000000..20b1663558b --- /dev/null +++ b/homeassistant/components/lock/.translations/hu.json @@ -0,0 +1,17 @@ +{ + "device_automation": { + "action_type": { + "lock": "{entity_name} z\u00e1r\u00e1sa", + "open": "{entity_name} nyit\u00e1sa", + "unlock": "{entity_name} nyit\u00e1sa" + }, + "condition_type": { + "is_locked": "{entity_name} z\u00e1rva", + "is_unlocked": "{entity_name} nyitva" + }, + "trigger_type": { + "locked": "{entity_name} be lett z\u00e1rva", + "unlocked": "{entity_name} ki lett nyitva" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vacuum/.translations/hu.json b/homeassistant/components/vacuum/.translations/hu.json new file mode 100644 index 00000000000..81a39802c55 --- /dev/null +++ b/homeassistant/components/vacuum/.translations/hu.json @@ -0,0 +1,16 @@ +{ + "device_automation": { + "action_type": { + "clean": "{entity_name} takar\u00edt\u00e1s ind\u00edt\u00e1sa", + "dock": "{entity_name} visszak\u00fcld\u00e9se a dokkol\u00f3ra" + }, + "condition_type": { + "is_cleaning": "{entity_name} takar\u00edt", + "is_docked": "{entity_name} dokkolva van" + }, + "trigger_type": { + "cleaning": "{entity_name} elkezdett takar\u00edtani", + "docked": "{entity_name} dokkolt" + } + } +} \ No newline at end of file From eb0aed36536b7ae20308382dc8b755025857d735 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 20 Dec 2019 10:29:18 +0100 Subject: [PATCH 349/677] =?UTF-8?q?Fix=20update=20port=20and=20api=20key?= =?UTF-8?q?=20on=20deconz=20discovery=20config=20entry=20u=E2=80=A6=20(#30?= =?UTF-8?q?088)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix update port and api key on discovery config entry update * Remove coroutine from _update_entry --- .../components/deconz/config_flow.py | 16 ++++++++-- tests/components/deconz/test_config_flow.py | 31 ++++++++++++++++--- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/deconz/config_flow.py b/homeassistant/components/deconz/config_flow.py index 05b7c18f448..f44acdbd305 100644 --- a/homeassistant/components/deconz/config_flow.py +++ b/homeassistant/components/deconz/config_flow.py @@ -159,12 +159,17 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): title="deCONZ-" + self.deconz_config[CONF_BRIDGEID], data=self.deconz_config ) - async def _update_entry(self, entry, host): + def _update_entry(self, entry, host, port, api_key=None): """Update existing entry.""" if entry.data[CONF_HOST] == host: return self.async_abort(reason="already_configured") entry.data[CONF_HOST] = host + entry.data[CONF_PORT] = port + + if api_key is not None: + entry.data[CONF_API_KEY] = api_key + self.hass.config_entries.async_update_entry(entry) return self.async_abort(reason="updated_instance") @@ -181,7 +186,7 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): for entry in self.hass.config_entries.async_entries(DOMAIN): if uuid == entry.data.get(CONF_UUID): - return await self._update_entry(entry, parsed_url.hostname) + return self._update_entry(entry, parsed_url.hostname, parsed_url.port) bridgeid = discovery_info[ssdp.ATTR_UPNP_SERIAL] if any( @@ -211,7 +216,12 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): if bridgeid in gateway_entries: entry = gateway_entries[bridgeid] - return await self._update_entry(entry, user_input[CONF_HOST]) + return self._update_entry( + entry, + user_input[CONF_HOST], + user_input[CONF_PORT], + user_input[CONF_API_KEY], + ) self._hassio_discovery = user_input diff --git a/tests/components/deconz/test_config_flow.py b/tests/components/deconz/test_config_flow.py index 2a1fb43333f..ddcbc1a1d5f 100644 --- a/tests/components/deconz/test_config_flow.py +++ b/tests/components/deconz/test_config_flow.py @@ -322,32 +322,53 @@ async def test_hassio_update_instance(hass): """Test we can update an existing config entry.""" entry = MockConfigEntry( domain=config_flow.DOMAIN, - data={config_flow.CONF_BRIDGEID: "id", config_flow.CONF_HOST: "1.2.3.4"}, + data={ + config_flow.CONF_BRIDGEID: "id", + config_flow.CONF_HOST: "1.2.3.4", + config_flow.CONF_PORT: 40850, + config_flow.CONF_API_KEY: "secret", + }, ) entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( config_flow.DOMAIN, - data={config_flow.CONF_HOST: "mock-deconz", config_flow.CONF_SERIAL: "id"}, + data={ + config_flow.CONF_HOST: "mock-deconz", + config_flow.CONF_PORT: 8080, + config_flow.CONF_API_KEY: "updated", + config_flow.CONF_SERIAL: "id", + }, context={"source": "hassio"}, ) assert result["type"] == "abort" assert result["reason"] == "updated_instance" assert entry.data[config_flow.CONF_HOST] == "mock-deconz" + assert entry.data[config_flow.CONF_PORT] == 8080 + assert entry.data[config_flow.CONF_API_KEY] == "updated" async def test_hassio_dont_update_instance(hass): """Test we can update an existing config entry.""" entry = MockConfigEntry( domain=config_flow.DOMAIN, - data={config_flow.CONF_BRIDGEID: "id", config_flow.CONF_HOST: "1.2.3.4"}, + data={ + config_flow.CONF_BRIDGEID: "id", + config_flow.CONF_HOST: "1.2.3.4", + config_flow.CONF_PORT: 8080, + config_flow.CONF_API_KEY: "secret", + }, ) entry.add_to_hass(hass) - result = await hass.config_entries.flow.async_init( config_flow.DOMAIN, - data={config_flow.CONF_HOST: "1.2.3.4", config_flow.CONF_SERIAL: "id"}, + data={ + config_flow.CONF_HOST: "1.2.3.4", + config_flow.CONF_PORT: 8080, + config_flow.CONF_API_KEY: "secret", + config_flow.CONF_SERIAL: "id", + }, context={"source": "hassio"}, ) From 92fd3e3ad5eb611c9d0dbce92ab5d5c94b3317e8 Mon Sep 17 00:00:00 2001 From: Fazli Sapuan Date: Fri, 20 Dec 2019 18:00:21 +0800 Subject: [PATCH 350/677] Fix homekit handling of 0 light brightness and fan speed (#29962) * Fix homekit handling of 0 light brightness and fan speed * Update homekit tests for new initial brightness/speed value --- homeassistant/components/homekit/type_fans.py | 22 +++++++++++++++++-- .../components/homekit/type_lights.py | 21 ++++++++++++++++-- tests/components/homekit/test_type_fans.py | 5 ++++- tests/components/homekit/test_type_lights.py | 4 +++- 4 files changed, 46 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/homekit/type_fans.py b/homeassistant/components/homekit/type_fans.py index e3fa6c42c58..e6d128d1e28 100644 --- a/homeassistant/components/homekit/type_fans.py +++ b/homeassistant/components/homekit/type_fans.py @@ -88,8 +88,11 @@ class Fan(HomeAccessory): ) if CHAR_ROTATION_SPEED in chars: + # Initial value is set to 100 because 0 is a special value (off). 100 is + # an arbitrary non-zero value. It is updated immediately by update_state + # to set to the correct initial value. self.char_speed = serv_fan.configure_char( - CHAR_ROTATION_SPEED, value=0, setter_callback=self.set_speed + CHAR_ROTATION_SPEED, value=100, setter_callback=self.set_speed ) if CHAR_SWING_MODE in chars: @@ -156,7 +159,22 @@ class Fan(HomeAccessory): speed = new_state.attributes.get(ATTR_SPEED) hk_speed_value = self.speed_mapping.speed_to_homekit(speed) if hk_speed_value is not None and self.char_speed.value != hk_speed_value: - self.char_speed.set_value(hk_speed_value) + # If the homeassistant component reports its speed as the first entry + # in its speed list but is not off, the hk_speed_value is 0. But 0 + # is a special value in homekit. When you turn on a homekit accessory + # it will try to restore the last rotation speed state which will be + # the last value saved by char_speed.set_value. But if it is set to + # 0, HomeKit will update the rotation speed to 100 as it thinks 0 is + # off. + # + # Therefore, if the hk_speed_value is 0 and the device is still on, + # the rotation speed is mapped to 1 otherwise the update is ignored + # in order to avoid this incorrect behavior. + if hk_speed_value == 0: + if state == STATE_ON: + self.char_speed.set_value(1) + else: + self.char_speed.set_value(hk_speed_value) # Handle Oscillating if self.char_swing is not None: diff --git a/homeassistant/components/homekit/type_lights.py b/homeassistant/components/homekit/type_lights.py index 8e1b07fbbff..7f195b276d6 100644 --- a/homeassistant/components/homekit/type_lights.py +++ b/homeassistant/components/homekit/type_lights.py @@ -82,8 +82,11 @@ class Light(HomeAccessory): ) if CHAR_BRIGHTNESS in self.chars: + # Initial value is set to 100 because 0 is a special value (off). 100 is + # an arbitrary non-zero value. It is updated immediately by update_state + # to set to the correct initial value. self.char_brightness = serv_light.configure_char( - CHAR_BRIGHTNESS, value=0, setter_callback=self.set_brightness + CHAR_BRIGHTNESS, value=100, setter_callback=self.set_brightness ) if CHAR_COLOR_TEMPERATURE in self.chars: min_mireds = self.hass.states.get(self.entity_id).attributes.get( @@ -183,7 +186,21 @@ class Light(HomeAccessory): if not self._flag[CHAR_BRIGHTNESS] and isinstance(brightness, int): brightness = round(brightness / 255 * 100, 0) if self.char_brightness.value != brightness: - self.char_brightness.set_value(brightness) + # The homeassistant component might report its brightness as 0 but is + # not off. But 0 is a special value in homekit. When you turn on a + # homekit accessory it will try to restore the last brightness state + # which will be the last value saved by char_brightness.set_value. + # But if it is set to 0, HomeKit will update the brightness to 100 as + # it thinks 0 is off. + # + # Therefore, if the the brighness is 0 and the device is still on, + # the brightness is mapped to 1 otherwise the update is ignored in + # order to avoid this incorrect behavior. + if brightness == 0: + if state == STATE_ON: + self.char_brightness.set_value(1) + else: + self.char_brightness.set_value(brightness) self._flag[CHAR_BRIGHTNESS] = False # Handle color temperature diff --git a/tests/components/homekit/test_type_fans.py b/tests/components/homekit/test_type_fans.py index 781df6cfb7b..5631791d7a2 100644 --- a/tests/components/homekit/test_type_fans.py +++ b/tests/components/homekit/test_type_fans.py @@ -197,7 +197,10 @@ async def test_fan_speed(hass, hk_driver, cls, events): ) await hass.async_block_till_done() acc = cls.fan(hass, hk_driver, "Fan", entity_id, 2, None) - assert acc.char_speed.value == 0 + + # Initial value can be anything but 0. If it is 0, it might cause HomeKit to set the + # speed to 100 when turning on a fan on a freshly booted up server. + assert acc.char_speed.value != 0 await hass.async_add_job(acc.run) assert ( diff --git a/tests/components/homekit/test_type_lights.py b/tests/components/homekit/test_type_lights.py index 3444b2778f6..510cfa4f666 100644 --- a/tests/components/homekit/test_type_lights.py +++ b/tests/components/homekit/test_type_lights.py @@ -101,7 +101,9 @@ async def test_light_brightness(hass, hk_driver, cls, events): await hass.async_block_till_done() acc = cls.light(hass, hk_driver, "Light", entity_id, 2, None) - assert acc.char_brightness.value == 0 + # Initial value can be anything but 0. If it is 0, it might cause HomeKit to set the + # brightness to 100 when turning on a light on a freshly booted up server. + assert acc.char_brightness.value != 0 await hass.async_add_job(acc.run) await hass.async_block_till_done() From 04b5d6c6976333d88877a2760874febc2052041d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Arnauts?= Date: Fri, 20 Dec 2019 13:24:43 +0100 Subject: [PATCH 351/677] Rework tado component (#29246) * Fix imports so it works in custom_components * Rework tado component * Code cleanup * Remove water_heater * Address pylint warnings * Remove water_heater from components * Raise PlatformNotReady when we couldn't connect * Revert PlatformNotReady since we are not a platform * Add debugging information * Add fallback setting * Import with relative path * Address race condition * Cleanup * Catch 422 Errors and log the real error * Use async_schedule_update_ha_state to update the entities * Forgot the True --- homeassistant/components/tado/__init__.py | 184 ++++++---- homeassistant/components/tado/climate.py | 390 ++++++++-------------- homeassistant/components/tado/const.py | 18 + homeassistant/components/tado/sensor.py | 173 +++++----- 4 files changed, 345 insertions(+), 420 deletions(-) create mode 100644 homeassistant/components/tado/const.py diff --git a/homeassistant/components/tado/__init__.py b/homeassistant/components/tado/__init__.py index 1739cbb9254..ebf605bdc75 100644 --- a/homeassistant/components/tado/__init__.py +++ b/homeassistant/components/tado/__init__.py @@ -9,23 +9,29 @@ import voluptuous as vol from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers import config_validation as cv from homeassistant.helpers.discovery import load_platform +from homeassistant.helpers.dispatcher import dispatcher_send from homeassistant.util import Throttle +from .const import CONF_FALLBACK + _LOGGER = logging.getLogger(__name__) -DATA_TADO = "tado_data" DOMAIN = "tado" -MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=10) +SIGNAL_TADO_UPDATE_RECEIVED = "tado_update_received_{}_{}" TADO_COMPONENTS = ["sensor", "climate"] +MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=10) +SCAN_INTERVAL = timedelta(seconds=15) + CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.Schema( { vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_FALLBACK, default=True): cv.boolean, } ) }, @@ -38,91 +44,106 @@ def setup(hass, config): username = config[DOMAIN][CONF_USERNAME] password = config[DOMAIN][CONF_PASSWORD] - try: - tado = Tado(username, password) - tado.setDebugging(True) - except (RuntimeError, urllib.error.HTTPError): - _LOGGER.error("Unable to connect to mytado with username and password") + tadoconnector = TadoConnector(hass, username, password) + if not tadoconnector.setup(): return False - hass.data[DATA_TADO] = TadoDataStore(tado) + hass.data[DOMAIN] = tadoconnector + # Do first update + tadoconnector.update() + + # Load components for component in TADO_COMPONENTS: - load_platform(hass, component, DOMAIN, {}, config) + load_platform( + hass, + component, + DOMAIN, + {CONF_FALLBACK: config[DOMAIN][CONF_FALLBACK]}, + config, + ) + + # Poll for updates in the background + hass.helpers.event.track_time_interval( + lambda now: tadoconnector.update(), SCAN_INTERVAL + ) return True -class TadoDataStore: +class TadoConnector: """An object to store the Tado data.""" - def __init__(self, tado): - """Initialize Tado data store.""" - self.tado = tado + def __init__(self, hass, username, password): + """Initialize Tado Connector.""" + self.hass = hass + self._username = username + self._password = password - self.sensors = {} - self.data = {} + self.tado = None + self.zones = None + self.devices = None + self.data = { + "zone": {}, + "device": {}, + } + + def setup(self): + """Connect to Tado and fetch the zones.""" + try: + self.tado = Tado(self._username, self._password) + except (RuntimeError, urllib.error.HTTPError) as exc: + _LOGGER.error("Unable to connect: %s", exc) + return False + + self.tado.setDebugging(True) + + # Load zones and devices + self.zones = self.tado.getZones() + self.devices = self.tado.getMe()["homes"] + + return True @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): - """Update the internal data from mytado.com.""" - for data_id, sensor in list(self.sensors.items()): - data = None + """Update the registered zones.""" + for zone in self.zones: + self.update_sensor("zone", zone["id"]) + for device in self.devices: + self.update_sensor("device", device["id"]) - try: - if "zone" in sensor: - _LOGGER.debug( - "Querying mytado.com for zone %s %s", - sensor["id"], - sensor["name"], - ) - data = self.tado.getState(sensor["id"]) + def update_sensor(self, sensor_type, sensor): + """Update the internal data from Tado.""" + _LOGGER.debug("Updating %s %s", sensor_type, sensor) + try: + if sensor_type == "zone": + data = self.tado.getState(sensor) + elif sensor_type == "device": + data = self.tado.getDevices()[0] + else: + _LOGGER.debug("Unknown sensor: %s", sensor_type) + return + except RuntimeError: + _LOGGER.error( + "Unable to connect to Tado while updating %s %s", sensor_type, sensor, + ) + return - if "device" in sensor: - _LOGGER.debug( - "Querying mytado.com for device %s %s", - sensor["id"], - sensor["name"], - ) - data = self.tado.getDevices()[0] + self.data[sensor_type][sensor] = data - except RuntimeError: - _LOGGER.error( - "Unable to connect to myTado. %s %s", sensor["id"], sensor["id"] - ) + _LOGGER.debug("Dispatching update to %s %s: %s", sensor_type, sensor, data) + dispatcher_send( + self.hass, SIGNAL_TADO_UPDATE_RECEIVED.format(sensor_type, sensor) + ) - self.data[data_id] = data - - def add_sensor(self, data_id, sensor): - """Add a sensor to update in _update().""" - self.sensors[data_id] = sensor - self.data[data_id] = None - - def get_data(self, data_id): - """Get the cached data.""" - data = {"error": "no data"} - - if data_id in self.data: - data = self.data[data_id] - - return data - - def get_zones(self): - """Wrap for getZones().""" - return self.tado.getZones() - - def get_capabilities(self, tado_id): - """Wrap for getCapabilities(..).""" - return self.tado.getCapabilities(tado_id) - - def get_me(self): - """Wrap for getMe().""" - return self.tado.getMe() + def get_capabilities(self, zone_id): + """Return the capabilities of the devices.""" + return self.tado.getCapabilities(zone_id) def reset_zone_overlay(self, zone_id): - """Wrap for resetZoneOverlay(..).""" + """Reset the zone back to the default operation.""" self.tado.resetZoneOverlay(zone_id) - self.update(no_throttle=True) # pylint: disable=unexpected-keyword-arg + self.update_sensor("zone", zone_id) def set_zone_overlay( self, @@ -133,13 +154,32 @@ class TadoDataStore: device_type="HEATING", mode=None, ): - """Wrap for setZoneOverlay(..).""" - self.tado.setZoneOverlay( - zone_id, overlay_mode, temperature, duration, device_type, "ON", mode + """Set a zone overlay.""" + _LOGGER.debug( + "Set overlay for zone %s: mode=%s, temp=%s, duration=%s, type=%s, mode=%s", + zone_id, + overlay_mode, + temperature, + duration, + device_type, + mode, ) - self.update(no_throttle=True) # pylint: disable=unexpected-keyword-arg + try: + self.tado.setZoneOverlay( + zone_id, overlay_mode, temperature, duration, device_type, "ON", mode + ) + except urllib.error.HTTPError as exc: + _LOGGER.error("Could not set zone overlay: %s", exc.read()) + + self.update_sensor("zone", zone_id) def set_zone_off(self, zone_id, overlay_mode, device_type="HEATING"): """Set a zone to off.""" - self.tado.setZoneOverlay(zone_id, overlay_mode, None, None, device_type, "OFF") - self.update(no_throttle=True) # pylint: disable=unexpected-keyword-arg + try: + self.tado.setZoneOverlay( + zone_id, overlay_mode, None, None, device_type, "OFF" + ) + except urllib.error.HTTPError as exc: + _LOGGER.error("Could not set zone overlay: %s", exc.read()) + + self.update_sensor("zone", zone_id) diff --git a/homeassistant/components/tado/climate.py b/homeassistant/components/tado/climate.py index 2baf1f380b5..88433db0991 100644 --- a/homeassistant/components/tado/climate.py +++ b/homeassistant/components/tado/climate.py @@ -1,6 +1,5 @@ -"""Support for Tado to create a climate device for each zone.""" +"""Support for Tado thermostats.""" import logging -from typing import List, Optional from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( @@ -23,27 +22,20 @@ from homeassistant.components.climate.const import ( SUPPORT_TARGET_TEMPERATURE, ) from homeassistant.const import ATTR_TEMPERATURE, PRECISION_TENTHS, TEMP_CELSIUS -from homeassistant.util.temperature import convert as convert_temperature +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect -from . import DATA_TADO +from . import CONF_FALLBACK, DOMAIN, SIGNAL_TADO_UPDATE_RECEIVED +from .const import ( + CONST_MODE_OFF, + CONST_MODE_SMART_SCHEDULE, + CONST_OVERLAY_MANUAL, + CONST_OVERLAY_TADO_MODE, + TYPE_AIR_CONDITIONING, +) _LOGGER = logging.getLogger(__name__) -CONST_MODE_SMART_SCHEDULE = "SMART_SCHEDULE" # Default mytado mode -CONST_MODE_OFF = "OFF" # Switch off heating in a zone - -# When we change the temperature setting, we need an overlay mode -# wait until tado changes the mode automatic -CONST_OVERLAY_TADO_MODE = "TADO_MODE" -# the user has change the temperature or mode manually -CONST_OVERLAY_MANUAL = "MANUAL" -# the temperature will be reset after a timespan -CONST_OVERLAY_TIMER = "TIMER" - -CONST_MODE_FAN_HIGH = "HIGH" -CONST_MODE_FAN_MIDDLE = "MIDDLE" -CONST_MODE_FAN_LOW = "LOW" - FAN_MAP_TADO = {"HIGH": FAN_HIGH, "MIDDLE": FAN_MIDDLE, "LOW": FAN_LOW} HVAC_MAP_TADO_HEAT = { @@ -78,35 +70,29 @@ SUPPORT_PRESET = [PRESET_AWAY, PRESET_HOME] def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Tado climate platform.""" - tado = hass.data[DATA_TADO] + tado = hass.data[DOMAIN] - try: - zones = tado.get_zones() - except RuntimeError: - _LOGGER.error("Unable to get zone info from mytado") - return + entities = [] + for zone in tado.zones: + entity = create_climate_entity( + tado, zone["name"], zone["id"], discovery_info[CONF_FALLBACK] + ) + if entity: + entities.append(entity) - climate_devices = [] - for zone in zones: - device = create_climate_device(tado, hass, zone, zone["name"], zone["id"]) - if not device: - continue - climate_devices.append(device) - - if climate_devices: - add_entities(climate_devices, True) + if entities: + add_entities(entities, True) -def create_climate_device(tado, hass, zone, name, zone_id): - """Create a Tado climate device.""" +def create_climate_entity(tado, name: str, zone_id: int, fallback: bool): + """Create a Tado climate entity.""" capabilities = tado.get_capabilities(zone_id) + _LOGGER.debug("Capabilities for zone %s: %s", zone_id, capabilities) + + zone_type = capabilities["type"] - unit = TEMP_CELSIUS - ac_device = capabilities["type"] == "AIR_CONDITIONING" - hot_water_device = capabilities["type"] == "HOT_WATER" ac_support_heat = False - - if ac_device: + if zone_type == TYPE_AIR_CONDITIONING: # Only use heat if available # (you don't have to setup a heat mode, but cool is required) # Heat is preferred as it generally has a lower minimum temperature @@ -118,67 +104,56 @@ def create_climate_device(tado, hass, zone, name, zone_id): elif "temperatures" in capabilities: temperatures = capabilities["temperatures"] else: - _LOGGER.debug("Received zone %s has no temperature; not adding", name) - return + _LOGGER.debug("Not adding zone %s since it has no temperature", name) + return None min_temp = float(temperatures["celsius"]["min"]) max_temp = float(temperatures["celsius"]["max"]) step = temperatures["celsius"].get("step", PRECISION_TENTHS) - data_id = f"zone {name} {zone_id}" - device = TadoClimate( + entity = TadoClimate( tado, name, zone_id, - data_id, - hass.config.units.temperature(min_temp, unit), - hass.config.units.temperature(max_temp, unit), - step, - ac_device, - hot_water_device, - ac_support_heat, - ) - - tado.add_sensor( - data_id, {"id": zone_id, "zone": zone, "name": name, "climate": device} - ) - - return device - - -class TadoClimate(ClimateDevice): - """Representation of a Tado climate device.""" - - def __init__( - self, - store, - zone_name, - zone_id, - data_id, + zone_type, min_temp, max_temp, step, - ac_device, - hot_water_device, ac_support_heat, - tolerance=0.3, + fallback, + ) + return entity + + +class TadoClimate(ClimateDevice): + """Representation of a Tado climate entity.""" + + def __init__( + self, + tado, + zone_name, + zone_id, + zone_type, + min_temp, + max_temp, + step, + ac_support_heat, + fallback, ): - """Initialize of Tado climate device.""" - self._store = store - self._data_id = data_id + """Initialize of Tado climate entity.""" + self._tado = tado self.zone_name = zone_name self.zone_id = zone_id + self.zone_type = zone_type - self._ac_device = ac_device - self._hot_water_device = hot_water_device + self._ac_device = zone_type == TYPE_AIR_CONDITIONING self._ac_support_heat = ac_support_heat self._cooling = False self._active = False self._device_is_active = False - self._unit = TEMP_CELSIUS self._cur_temp = None self._cur_humidity = None self._is_away = False @@ -186,12 +161,34 @@ class TadoClimate(ClimateDevice): self._max_temp = max_temp self._step = step self._target_temp = None - self._tolerance = tolerance + + if fallback: + _LOGGER.debug("Default overlay is set to TADO MODE") + # Fallback to Smart Schedule at next Schedule switch + self._default_overlay = CONST_OVERLAY_TADO_MODE + else: + _LOGGER.debug("Default overlay is set to MANUAL MODE") + # Don't fallback to Smart Schedule, but keep in manual mode + self._default_overlay = CONST_OVERLAY_MANUAL self._current_fan = CONST_MODE_OFF self._current_operation = CONST_MODE_SMART_SCHEDULE self._overlay_mode = CONST_MODE_SMART_SCHEDULE + async def async_added_to_hass(self): + """Register for sensor updates.""" + + @callback + def async_update_callback(): + """Schedule an entity update.""" + self.async_schedule_update_ha_state(True) + + async_dispatcher_connect( + self.hass, + SIGNAL_TADO_UPDATE_RECEIVED.format("zone", self.zone_id), + async_update_callback, + ) + @property def supported_features(self): """Return the list of supported features.""" @@ -199,18 +196,19 @@ class TadoClimate(ClimateDevice): @property def name(self): - """Return the name of the device.""" + """Return the name of the entity.""" return self.zone_name + @property + def should_poll(self) -> bool: + """Do not poll.""" + return False + @property def current_humidity(self): """Return the current humidity.""" return self._cur_humidity - def set_humidity(self, humidity: int) -> None: - """Set new target humidity.""" - pass - @property def current_temperature(self): """Return the sensor temperature.""" @@ -234,9 +232,9 @@ class TadoClimate(ClimateDevice): Need to be a subset of HVAC_MODES. """ - if self._ac_device and self._ac_support_heat: - return SUPPORT_HVAC_HEAT_COOL - if self._ac_device and not self._ac_support_heat: + if self._ac_device: + if self._ac_support_heat: + return SUPPORT_HVAC_HEAT_COOL return SUPPORT_HVAC_COOL return SUPPORT_HVAC_HEAT @@ -248,16 +246,10 @@ class TadoClimate(ClimateDevice): """ if not self._device_is_active: return CURRENT_HVAC_OFF - if self._ac_device and self._ac_support_heat and self._cooling: - if self._active: - return CURRENT_HVAC_COOL - return CURRENT_HVAC_IDLE - if self._ac_device and self._ac_support_heat and not self._cooling: - if self._active: - return CURRENT_HVAC_HEAT - return CURRENT_HVAC_IDLE - if self._ac_device and not self._ac_support_heat: + if self._ac_device: if self._active: + if self._ac_support_heat and not self._cooling: + return CURRENT_HVAC_HEAT return CURRENT_HVAC_COOL return CURRENT_HVAC_IDLE if self._active: @@ -284,7 +276,7 @@ class TadoClimate(ClimateDevice): @property def preset_mode(self): - """Return the current preset mode, e.g., home, away, temp.""" + """Return the current preset mode (home, away).""" if self._is_away: return PRESET_AWAY return PRESET_HOME @@ -301,7 +293,7 @@ class TadoClimate(ClimateDevice): @property def temperature_unit(self): """Return the unit of measurement used by the platform.""" - return self._unit + return TEMP_CELSIUS @property def target_temperature_step(self): @@ -313,23 +305,13 @@ class TadoClimate(ClimateDevice): """Return the temperature we try to reach.""" return self._target_temp - @property - def target_temperature_high(self): - """Return the upper bound temperature we try to reach.""" - return None - - @property - def target_temperature_low(self): - """Return the lower bound temperature we try to reach.""" - return None - def set_temperature(self, **kwargs): """Set new target temperature.""" temperature = kwargs.get(ATTR_TEMPERATURE) if temperature is None: return - self._current_operation = CONST_OVERLAY_TADO_MODE + self._current_operation = self._default_overlay self._overlay_mode = None self._target_temp = temperature self._control_heating() @@ -343,50 +325,51 @@ class TadoClimate(ClimateDevice): elif hvac_mode == HVAC_MODE_AUTO: mode = CONST_MODE_SMART_SCHEDULE elif hvac_mode == HVAC_MODE_HEAT: - mode = CONST_OVERLAY_TADO_MODE + mode = self._default_overlay elif hvac_mode == HVAC_MODE_COOL: - mode = CONST_OVERLAY_TADO_MODE + mode = self._default_overlay elif hvac_mode == HVAC_MODE_HEAT_COOL: - mode = CONST_OVERLAY_TADO_MODE + mode = self._default_overlay self._current_operation = mode self._overlay_mode = None - if self._target_temp is None and self._ac_device: - self._target_temp = 27 + + # Set a target temperature if we don't have any + # This can happen when we switch from Off to On + if self._target_temp is None: + if self._ac_device: + self._target_temp = self.max_temp + else: + self._target_temp = self.min_temp + self.schedule_update_ha_state() + self._control_heating() @property def min_temp(self): """Return the minimum temperature.""" - return convert_temperature( - self._min_temp, self._unit, self.hass.config.units.temperature_unit - ) + return self._min_temp @property def max_temp(self): """Return the maximum temperature.""" - return convert_temperature( - self._max_temp, self._unit, self.hass.config.units.temperature_unit - ) + return self._max_temp def update(self): - """Update the state of this climate device.""" - self._store.update() - - data = self._store.get_data(self._data_id) - - if data is None: - _LOGGER.debug("Received no data for zone %s", self.zone_name) + """Handle update callbacks.""" + _LOGGER.debug("Updating climate platform for zone %d", self.zone_id) + try: + data = self._tado.data["zone"][self.zone_id] + except KeyError: + _LOGGER.debug("No data") return if "sensorDataPoints" in data: sensor_data = data["sensorDataPoints"] - unit = TEMP_CELSIUS - if "insideTemperature" in sensor_data: temperature = float(sensor_data["insideTemperature"]["celsius"]) - self._cur_temp = self.hass.config.units.temperature(temperature, unit) + self._cur_temp = temperature if "humidity" in sensor_data: humidity = float(sensor_data["humidity"]["percentage"]) @@ -398,7 +381,7 @@ class TadoClimate(ClimateDevice): and data["setting"]["temperature"] is not None ): setting = float(data["setting"]["temperature"]["celsius"]) - self._target_temp = self.hass.config.units.temperature(setting, unit) + self._target_temp = setting if "tadoMode" in data: mode = data["tadoMode"] @@ -468,135 +451,38 @@ class TadoClimate(ClimateDevice): self._current_fan = fan_speed def _control_heating(self): - """Send new target temperature to mytado.""" - if None not in (self._cur_temp, self._target_temp): - _LOGGER.info( - "Obtained current (%d) and target temperature (%d). " - "Tado thermostat active", - self._cur_temp, - self._target_temp, - ) - + """Send new target temperature to Tado.""" if self._current_operation == CONST_MODE_SMART_SCHEDULE: - _LOGGER.info( - "Switching mytado.com to SCHEDULE (default) for zone %s (%d)", + _LOGGER.debug( + "Switching to SMART_SCHEDULE for zone %s (%d)", self.zone_name, self.zone_id, ) - self._store.reset_zone_overlay(self.zone_id) + self._tado.reset_zone_overlay(self.zone_id) self._overlay_mode = self._current_operation return if self._current_operation == CONST_MODE_OFF: - if self._ac_device: - _LOGGER.info( - "Switching mytado.com to OFF for zone %s (%d) - AIR_CONDITIONING", - self.zone_name, - self.zone_id, - ) - self._store.set_zone_off( - self.zone_id, CONST_OVERLAY_MANUAL, "AIR_CONDITIONING" - ) - elif self._hot_water_device: - _LOGGER.info( - "Switching mytado.com to OFF for zone %s (%d) - HOT_WATER", - self.zone_name, - self.zone_id, - ) - self._store.set_zone_off( - self.zone_id, CONST_OVERLAY_MANUAL, "HOT_WATER" - ) - else: - _LOGGER.info( - "Switching mytado.com to OFF for zone %s (%d) - HEATING", - self.zone_name, - self.zone_id, - ) - self._store.set_zone_off(self.zone_id, CONST_OVERLAY_MANUAL, "HEATING") + _LOGGER.debug( + "Switching to OFF for zone %s (%d)", self.zone_name, self.zone_id + ) + self._tado.set_zone_off(self.zone_id, CONST_OVERLAY_MANUAL, self.zone_type) self._overlay_mode = self._current_operation return - if self._ac_device: - _LOGGER.info( - "Switching mytado.com to %s mode for zone %s (%d). Temp (%s) - AIR_CONDITIONING", - self._current_operation, - self.zone_name, - self.zone_id, - self._target_temp, - ) - self._store.set_zone_overlay( - self.zone_id, - self._current_operation, - self._target_temp, - None, - "AIR_CONDITIONING", - "COOL", - ) - elif self._hot_water_device: - _LOGGER.info( - "Switching mytado.com to %s mode for zone %s (%d). Temp (%s) - HOT_WATER", - self._current_operation, - self.zone_name, - self.zone_id, - self._target_temp, - ) - self._store.set_zone_overlay( - self.zone_id, - self._current_operation, - self._target_temp, - None, - "HOT_WATER", - ) - else: - _LOGGER.info( - "Switching mytado.com to %s mode for zone %s (%d). Temp (%s) - HEATING", - self._current_operation, - self.zone_name, - self.zone_id, - self._target_temp, - ) - self._store.set_zone_overlay( - self.zone_id, - self._current_operation, - self._target_temp, - None, - "HEATING", - ) - + _LOGGER.debug( + "Switching to %s for zone %s (%d) with temperature %s °C", + self._current_operation, + self.zone_name, + self.zone_id, + self._target_temp, + ) + self._tado.set_zone_overlay( + self.zone_id, + self._current_operation, + self._target_temp, + None, + self.zone_type, + "COOL" if self._ac_device else None, + ) self._overlay_mode = self._current_operation - - @property - def is_aux_heat(self) -> Optional[bool]: - """Return true if aux heater. - - Requires SUPPORT_AUX_HEAT. - """ - return None - - def turn_aux_heat_on(self) -> None: - """Turn auxiliary heater on.""" - pass - - def turn_aux_heat_off(self) -> None: - """Turn auxiliary heater off.""" - pass - - @property - def swing_mode(self) -> Optional[str]: - """Return the swing setting. - - Requires SUPPORT_SWING_MODE. - """ - return None - - @property - def swing_modes(self) -> Optional[List[str]]: - """Return the list of available swing modes. - - Requires SUPPORT_SWING_MODE. - """ - return None - - def set_swing_mode(self, swing_mode: str) -> None: - """Set new target swing operation.""" - pass diff --git a/homeassistant/components/tado/const.py b/homeassistant/components/tado/const.py new file mode 100644 index 00000000000..3c0232c8ba2 --- /dev/null +++ b/homeassistant/components/tado/const.py @@ -0,0 +1,18 @@ +"""Constant values for the Tado component.""" + +# Configuration +CONF_FALLBACK = "fallback" + +# Types +TYPE_AIR_CONDITIONING = "AIR_CONDITIONING" +TYPE_HEATING = "HEATING" +TYPE_HOT_WATER = "HOT_WATER" + +# Base modes +CONST_MODE_SMART_SCHEDULE = "SMART_SCHEDULE" # Use the schedule +CONST_MODE_OFF = "OFF" # Switch off heating in a zone + +# When we change the temperature setting, we need an overlay mode +CONST_OVERLAY_TADO_MODE = "TADO_MODE" # wait until tado changes the mode automatic +CONST_OVERLAY_MANUAL = "MANUAL" # the user has change the temperature or mode manually +CONST_OVERLAY_TIMER = "TIMER" # the temperature will be reset after a timespan diff --git a/homeassistant/components/tado/sensor.py b/homeassistant/components/tado/sensor.py index 346b27bec26..68b470aced7 100644 --- a/homeassistant/components/tado/sensor.py +++ b/homeassistant/components/tado/sensor.py @@ -1,130 +1,109 @@ """Support for Tado sensors for each zone.""" import logging -from homeassistant.const import ATTR_ID, ATTR_NAME, TEMP_CELSIUS +from homeassistant.const import TEMP_CELSIUS +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity -from . import DATA_TADO +from . import DOMAIN, SIGNAL_TADO_UPDATE_RECEIVED +from .const import TYPE_AIR_CONDITIONING, TYPE_HEATING, TYPE_HOT_WATER _LOGGER = logging.getLogger(__name__) -ATTR_DATA_ID = "data_id" -ATTR_DEVICE = "device" -ATTR_ZONE = "zone" +ZONE_SENSORS = { + TYPE_HEATING: [ + "temperature", + "humidity", + "power", + "link", + "heating", + "tado mode", + "overlay", + "early start", + ], + TYPE_AIR_CONDITIONING: [ + "temperature", + "humidity", + "power", + "link", + "ac", + "tado mode", + "overlay", + ], + TYPE_HOT_WATER: ["power", "link", "tado mode", "overlay"], +} -CLIMATE_HEAT_SENSOR_TYPES = [ - "temperature", - "humidity", - "power", - "link", - "heating", - "tado mode", - "overlay", - "early start", -] - -CLIMATE_COOL_SENSOR_TYPES = [ - "temperature", - "humidity", - "power", - "link", - "ac", - "tado mode", - "overlay", -] - -HOT_WATER_SENSOR_TYPES = ["power", "link", "tado mode", "overlay"] +DEVICE_SENSORS = ["tado bridge status"] def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the sensor platform.""" - tado = hass.data[DATA_TADO] + tado = hass.data[DOMAIN] - try: - zones = tado.get_zones() - except RuntimeError: - _LOGGER.error("Unable to get zone info from mytado") - return - - sensor_items = [] - for zone in zones: - if zone["type"] == "HEATING": - for variable in CLIMATE_HEAT_SENSOR_TYPES: - sensor_items.append( - create_zone_sensor(tado, zone, zone["name"], zone["id"], variable) - ) - elif zone["type"] == "HOT_WATER": - for variable in HOT_WATER_SENSOR_TYPES: - sensor_items.append( - create_zone_sensor(tado, zone, zone["name"], zone["id"], variable) - ) - elif zone["type"] == "AIR_CONDITIONING": - for variable in CLIMATE_COOL_SENSOR_TYPES: - sensor_items.append( - create_zone_sensor(tado, zone, zone["name"], zone["id"], variable) - ) - - me_data = tado.get_me() - sensor_items.append( - create_device_sensor( - tado, - me_data, - me_data["homes"][0]["name"], - me_data["homes"][0]["id"], - "tado bridge status", + # Create zone sensors + entities = [] + for zone in tado.zones: + entities.extend( + [ + create_zone_sensor(tado, zone["name"], zone["id"], variable) + for variable in ZONE_SENSORS.get(zone["type"]) + ] ) - ) - if sensor_items: - add_entities(sensor_items, True) + # Create device sensors + for home in tado.devices: + entities.extend( + [ + create_device_sensor(tado, home["name"], home["id"], variable) + for variable in DEVICE_SENSORS + ] + ) + + add_entities(entities, True) -def create_zone_sensor(tado, zone, name, zone_id, variable): +def create_zone_sensor(tado, name, zone_id, variable): """Create a zone sensor.""" - data_id = f"zone {name} {zone_id}" - - tado.add_sensor( - data_id, - {ATTR_ZONE: zone, ATTR_NAME: name, ATTR_ID: zone_id, ATTR_DATA_ID: data_id}, - ) - - return TadoSensor(tado, name, zone_id, variable, data_id) + return TadoSensor(tado, name, "zone", zone_id, variable) -def create_device_sensor(tado, device, name, device_id, variable): +def create_device_sensor(tado, name, device_id, variable): """Create a device sensor.""" - data_id = f"device {name} {device_id}" - - tado.add_sensor( - data_id, - { - ATTR_DEVICE: device, - ATTR_NAME: name, - ATTR_ID: device_id, - ATTR_DATA_ID: data_id, - }, - ) - - return TadoSensor(tado, name, device_id, variable, data_id) + return TadoSensor(tado, name, "device", device_id, variable) class TadoSensor(Entity): """Representation of a tado Sensor.""" - def __init__(self, store, zone_name, zone_id, zone_variable, data_id): + def __init__(self, tado, zone_name, sensor_type, zone_id, zone_variable): """Initialize of the Tado Sensor.""" - self._store = store + self._tado = tado self.zone_name = zone_name self.zone_id = zone_id self.zone_variable = zone_variable + self.sensor_type = sensor_type self._unique_id = f"{zone_variable} {zone_id}" - self._data_id = data_id self._state = None self._state_attributes = None + async def async_added_to_hass(self): + """Register for sensor updates.""" + + @callback + def async_update_callback(): + """Schedule an entity update.""" + self.async_schedule_update_ha_state(True) + + async_dispatcher_connect( + self.hass, + SIGNAL_TADO_UPDATE_RECEIVED.format(self.sensor_type, self.zone_id), + async_update_callback, + ) + @property def unique_id(self): """Return the unique id.""" @@ -165,14 +144,16 @@ class TadoSensor(Entity): if self.zone_variable == "humidity": return "mdi:water-percent" + @property + def should_poll(self) -> bool: + """Do not poll.""" + return False + def update(self): - """Update method called when should_poll is true.""" - self._store.update() - - data = self._store.get_data(self._data_id) - - if data is None: - _LOGGER.debug("Received no data for zone %s", self.zone_name) + """Handle update callbacks.""" + try: + data = self._tado.data[self.sensor_type][self.zone_id] + except KeyError: return unit = TEMP_CELSIUS From 84e1b3d07fb1bf3e65383286f5dd41f30498e653 Mon Sep 17 00:00:00 2001 From: gppanayotov <39181403+gppanayotov@users.noreply.github.com> Date: Fri, 20 Dec 2019 20:02:46 +0200 Subject: [PATCH 352/677] Add an open window sensor for heating zones (#30090) --- homeassistant/components/tado/sensor.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/homeassistant/components/tado/sensor.py b/homeassistant/components/tado/sensor.py index 68b470aced7..a928b61a508 100644 --- a/homeassistant/components/tado/sensor.py +++ b/homeassistant/components/tado/sensor.py @@ -21,6 +21,7 @@ ZONE_SENSORS = { "tado mode", "overlay", "early start", + "open window", ], TYPE_AIR_CONDITIONING: [ "temperature", @@ -240,3 +241,9 @@ class TadoSensor(Entity): self._state = True else: self._state = False + + elif self.zone_variable == "open window": + if "openWindowDetected" in data: + self._state = data["openWindowDetected"] + else: + self._state = False From 0faca57e8bb0b5f7b53de242a9b56d54a7eb9643 Mon Sep 17 00:00:00 2001 From: ochlocracy <5885236+ochlocracy@users.noreply.github.com> Date: Fri, 20 Dec 2019 14:28:23 -0500 Subject: [PATCH 353/677] Explicitly include Alexa interface for image_processing entities. (#30101) --- homeassistant/components/alexa/entities.py | 1 + tests/components/alexa/test_smart_home.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/alexa/entities.py b/homeassistant/components/alexa/entities.py index 2a3355434a3..89ca646890b 100644 --- a/homeassistant/components/alexa/entities.py +++ b/homeassistant/components/alexa/entities.py @@ -673,3 +673,4 @@ class ImageProcessingCapabilities(AlexaEntity): """Yield the supported interfaces.""" yield AlexaEventDetectionSensor(self.hass, self.entity) yield AlexaEndpointHealth(self.hass, self.entity) + yield Alexa(self.hass) diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index 4187c4a2c4f..468652bf6d2 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -2524,7 +2524,7 @@ async def test_image_processing(hass): assert appliance["friendlyName"] == "Test face" assert_endpoint_capabilities( - appliance, "Alexa.EventDetectionSensor", "Alexa.EndpointHealth" + appliance, "Alexa.EventDetectionSensor", "Alexa.EndpointHealth", "Alexa" ) From 27f35f86ad8883d6913ef161603201c829cb69a8 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Fri, 20 Dec 2019 20:29:12 +0100 Subject: [PATCH 354/677] Bump starlingbank to 3.2 (#30098) --- homeassistant/components/starlingbank/manifest.json | 4 +--- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/starlingbank/manifest.json b/homeassistant/components/starlingbank/manifest.json index 33dbb40f78a..d68b6ea125c 100644 --- a/homeassistant/components/starlingbank/manifest.json +++ b/homeassistant/components/starlingbank/manifest.json @@ -2,9 +2,7 @@ "domain": "starlingbank", "name": "Starlingbank", "documentation": "https://www.home-assistant.io/integrations/starlingbank", - "requirements": [ - "starlingbank==3.1" - ], + "requirements": ["starlingbank==3.2"], "dependencies": [], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index 5cecb396649..c7d10a8fc9d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1876,7 +1876,7 @@ sqlalchemy==1.3.11 starline==0.1.3 # homeassistant.components.starlingbank -starlingbank==3.1 +starlingbank==3.2 # homeassistant.components.statsd statsd==3.2.1 From b3098c9f2c55f0e5080ae3bea9457faa0192f743 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 20 Dec 2019 20:42:13 +0100 Subject: [PATCH 355/677] Bump ring to 0.2.5 (#30103) --- homeassistant/components/ring/manifest.json | 8 ++------ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/ring/manifest.json b/homeassistant/components/ring/manifest.json index 47fc2f3a6a8..5c23822fef9 100644 --- a/homeassistant/components/ring/manifest.json +++ b/homeassistant/components/ring/manifest.json @@ -2,11 +2,7 @@ "domain": "ring", "name": "Ring", "documentation": "https://www.home-assistant.io/integrations/ring", - "requirements": [ - "ring_doorbell==0.2.3" - ], - "dependencies": [ - "ffmpeg" - ], + "requirements": ["ring_doorbell==0.2.5"], + "dependencies": ["ffmpeg"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index c7d10a8fc9d..17a948f754b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1738,7 +1738,7 @@ rfk101py==0.0.1 rflink==0.0.46 # homeassistant.components.ring -ring_doorbell==0.2.3 +ring_doorbell==0.2.5 # homeassistant.components.fleetgo ritassist==0.9.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 538262919ff..be842e8e82f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -558,7 +558,7 @@ restrictedpython==5.0 rflink==0.0.46 # homeassistant.components.ring -ring_doorbell==0.2.3 +ring_doorbell==0.2.5 # homeassistant.components.yamaha rxv==0.6.0 From 4ef04840e91330ff932b051ee42ba8634ca0026b Mon Sep 17 00:00:00 2001 From: Jc2k Date: Fri, 20 Dec 2019 20:49:07 +0000 Subject: [PATCH 356/677] Don't error on removal of an ignored homekit_controller config entry (#30083) * Don't error on ignored entries * Don't ever call async_remove_entry or async_unload_entry for an unignored ignore config entry --- homeassistant/config_entries.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 1a010b38e70..8b314547d9c 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -246,6 +246,10 @@ class ConfigEntry: Returns if unload is possible and was successful. """ + if self.source == SOURCE_IGNORE: + self.state = ENTRY_STATE_NOT_LOADED + return True + if integration is None: integration = await loader.async_get_integration(hass, self.domain) @@ -292,6 +296,9 @@ class ConfigEntry: async def async_remove(self, hass: HomeAssistant) -> None: """Invoke remove callback on component.""" + if self.source == SOURCE_IGNORE: + return + integration = await loader.async_get_integration(hass, self.domain) component = integration.get_component() if not hasattr(component, "async_remove_entry"): From de94afd09094caaf413cb1af69ca53ec61d393a8 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Fri, 20 Dec 2019 22:00:31 +0100 Subject: [PATCH 357/677] add --show-diff-on-failure to pre-commit (#30097) This makes the traceback on a failing CI pipeline much more useful. --- azure-pipelines-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines-ci.yml b/azure-pipelines-ci.yml index 78de90c8552..546b63950fe 100644 --- a/azure-pipelines-ci.yml +++ b/azure-pipelines-ci.yml @@ -54,7 +54,7 @@ stages: displayName: 'Run bandit' - script: | . venv/bin/activate - pre-commit run isort --all-files + pre-commit run isort --all-files --show-diff-on-failure displayName: 'Run isort' - script: | . venv/bin/activate @@ -97,7 +97,7 @@ stages: pre-commit install-hooks --config .pre-commit-config-all.yaml - script: | . venv/bin/activate - pre-commit run black --all-files + pre-commit run black --all-files --show-diff-on-failure displayName: 'Check Black formatting' - stage: 'Tests' From ecdc1adf90ef1dc7033eae6362a666ec37224d95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Fri, 20 Dec 2019 23:35:02 +0200 Subject: [PATCH 358/677] Upgrade mypy to 0.761 (#30104) https://mypy-lang.blogspot.com/2019/12/mypy-0760-released.html https://github.com/python/mypy/releases/tag/v0.761 --- homeassistant/helpers/config_validation.py | 2 +- requirements_test.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 0f5b4e13dc2..5787db65102 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -10,7 +10,7 @@ import logging from numbers import Number import os import re -from socket import _GLOBAL_DEFAULT_TIMEOUT +from socket import _GLOBAL_DEFAULT_TIMEOUT # type: ignore # private, not in typeshed from typing import Any, Callable, Dict, List, Optional, TypeVar, Union from urllib.parse import urlparse from uuid import UUID diff --git a/requirements_test.txt b/requirements_test.txt index 328ad1d5b9a..37268e70726 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -6,7 +6,7 @@ asynctest==0.13.0 codecov==2.0.15 mock-open==1.3.1 -mypy==0.750 +mypy==0.761 pre-commit==1.20.0 pylint==2.4.4 astroid==2.3.3 From 660468079310ec7432e890436b314526fdd4ab7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sat, 21 Dec 2019 09:23:48 +0200 Subject: [PATCH 359/677] Helpers type hint improvements (#30106) --- homeassistant/helpers/device_registry.py | 12 ++++++------ homeassistant/helpers/discovery.py | 22 ++++++++++++++++------ homeassistant/helpers/entity_component.py | 13 +++++++------ homeassistant/helpers/entity_platform.py | 11 ++++++----- 4 files changed, 35 insertions(+), 23 deletions(-) diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index 2ff444da89f..4818de83cb9 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -2,7 +2,7 @@ from asyncio import Event from collections import OrderedDict import logging -from typing import List, Optional, cast +from typing import Any, Dict, List, Optional, cast import uuid import attr @@ -48,7 +48,7 @@ class DeviceEntry: is_new = attr.ib(type=bool, default=False) -def format_mac(mac): +def format_mac(mac: str) -> str: """Format the mac address string for entry into dev reg.""" to_test = mac @@ -260,7 +260,7 @@ class DeviceRegistry: return new - def async_remove_device(self, device_id): + def async_remove_device(self, device_id: str) -> None: """Remove a device from the device registry.""" del self.devices[device_id] self.hass.bus.async_fire( @@ -298,12 +298,12 @@ class DeviceRegistry: self.devices = devices @callback - def async_schedule_save(self): + def async_schedule_save(self) -> None: """Schedule saving the device registry.""" self._store.async_delay_save(self._data_to_save, SAVE_DELAY) @callback - def _data_to_save(self): + def _data_to_save(self) -> Dict[str, List[Dict[str, Any]]]: """Return data of device registry to store in a file.""" data = {} @@ -327,7 +327,7 @@ class DeviceRegistry: return data @callback - def async_clear_config_entry(self, config_entry_id): + def async_clear_config_entry(self, config_entry_id: str) -> None: """Clear config entry from registry entries.""" remove = [] for dev_id, device in self.devices.items(): diff --git a/homeassistant/helpers/discovery.py b/homeassistant/helpers/discovery.py index 8e4def77440..a6162dbde55 100644 --- a/homeassistant/helpers/discovery.py +++ b/homeassistant/helpers/discovery.py @@ -5,6 +5,8 @@ There are two different types of discoveries that can be fired/listened for. - listen_platform/discover_platform is for platforms. These are used by components to allow discovery of their platforms. """ +from typing import Callable, Collection, Union + from homeassistant import core, setup from homeassistant.const import ATTR_DISCOVERED, ATTR_SERVICE, EVENT_PLATFORM_DISCOVERED from homeassistant.exceptions import HomeAssistantError @@ -18,7 +20,9 @@ ATTR_PLATFORM = "platform" @bind_hass -def listen(hass, service, callback): +def listen( + hass: core.HomeAssistant, service: Union[str, Collection[str]], callback: Callable +) -> None: """Set up listener for discovery of specific service. Service can be a string or a list/tuple. @@ -28,7 +32,9 @@ def listen(hass, service, callback): @core.callback @bind_hass -def async_listen(hass, service, callback): +def async_listen( + hass: core.HomeAssistant, service: Union[str, Collection[str]], callback: Callable +) -> None: """Set up listener for discovery of specific service. Service can be a string or a list/tuple. @@ -39,7 +45,7 @@ def async_listen(hass, service, callback): service = tuple(service) @core.callback - def discovery_event_listener(event): + def discovery_event_listener(event: core.Event) -> None: """Listen for discovery events.""" if ATTR_SERVICE in event.data and event.data[ATTR_SERVICE] in service: hass.async_add_job( @@ -73,7 +79,9 @@ async def async_discover(hass, service, discovered, component, hass_config): @bind_hass -def listen_platform(hass, component, callback): +def listen_platform( + hass: core.HomeAssistant, component: str, callback: Callable +) -> None: """Register a platform loader listener.""" run_callback_threadsafe( hass.loop, async_listen_platform, hass, component, callback @@ -81,7 +89,9 @@ def listen_platform(hass, component, callback): @bind_hass -def async_listen_platform(hass, component, callback): +def async_listen_platform( + hass: core.HomeAssistant, component: str, callback: Callable +) -> None: """Register a platform loader listener. This method must be run in the event loop. @@ -89,7 +99,7 @@ def async_listen_platform(hass, component, callback): service = EVENT_LOAD_PLATFORM.format(component) @core.callback - def discovery_platform_listener(event): + def discovery_platform_listener(event: core.Event) -> None: """Listen for platform discovery events.""" if event.data.get(ATTR_SERVICE) != service: return diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index becd96bf5f3..84aa8becafd 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -5,13 +5,14 @@ from itertools import chain import logging from homeassistant import config as conf_util +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_ENTITY_ID, CONF_ENTITY_NAMESPACE, CONF_SCAN_INTERVAL, ENTITY_MATCH_ALL, ) -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_per_platform, discovery from homeassistant.helpers.config_validation import make_entity_service_schema @@ -29,7 +30,7 @@ DATA_INSTANCES = "entity_components" @bind_hass -async def async_update_entity(hass, entity_id): +async def async_update_entity(hass: HomeAssistant, entity_id: str) -> None: """Trigger an update for an entity.""" domain = entity_id.split(".", 1)[0] entity_comp = hass.data.get(DATA_INSTANCES, {}).get(domain) @@ -158,7 +159,7 @@ class EntityComponent: return await self._platforms[key].async_setup_entry(config_entry) - async def async_unload_entry(self, config_entry): + async def async_unload_entry(self, config_entry: ConfigEntry) -> bool: """Unload a config entry.""" key = config_entry.entry_id @@ -237,7 +238,7 @@ class EntityComponent: await self._platforms[key].async_setup(platform_config, discovery_info) @callback - def _async_update_group(self): + def _async_update_group(self) -> None: """Set up and/or update component group. This method must be run in the event loop. @@ -265,7 +266,7 @@ class EntityComponent: ) ) - async def _async_reset(self): + async def _async_reset(self) -> None: """Remove entities and reset the entity component to initial values. This method must be run in the event loop. @@ -283,7 +284,7 @@ class EntityComponent: "group", "remove", dict(object_id=slugify(self.group_name)) ) - async def async_remove_entity(self, entity_id): + async def async_remove_entity(self, entity_id: str) -> None: """Remove an entity managed by one of the platforms.""" for platform in self._platforms.values(): if entity_id in platform.entities: diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 133d1a5841f..e171a4cade8 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -1,6 +1,7 @@ """Class to manage the entities for a single platform.""" import asyncio from contextvars import ContextVar +from datetime import datetime from typing import Optional from homeassistant.const import DEVICE_DEFAULT_NAME @@ -64,14 +65,14 @@ class EntityPlatform: # which powers entity_component.add_entities if platform is None: self.parallel_updates = None - self.parallel_updates_semaphore = None + self.parallel_updates_semaphore: Optional[asyncio.Semaphore] = None return self.parallel_updates = getattr(platform, "PARALLEL_UPDATES", None) # semaphore will be created on demand self.parallel_updates_semaphore = None - def _get_parallel_updates_semaphore(self): + def _get_parallel_updates_semaphore(self) -> asyncio.Semaphore: """Get or create a semaphore for parallel updates.""" if self.parallel_updates_semaphore is None: self.parallel_updates_semaphore = asyncio.Semaphore( @@ -406,7 +407,7 @@ class EntityPlatform: await entity.async_update_ha_state() - async def async_reset(self): + async def async_reset(self) -> None: """Remove all entities and reset data. This method must be run in the event loop. @@ -426,7 +427,7 @@ class EntityPlatform: self._async_unsub_polling() self._async_unsub_polling = None - async def async_remove_entity(self, entity_id): + async def async_remove_entity(self, entity_id: str) -> None: """Remove entity id from platform.""" await self.entities[entity_id].async_remove() @@ -437,7 +438,7 @@ class EntityPlatform: self._async_unsub_polling() self._async_unsub_polling = None - async def _update_entity_states(self, now): + async def _update_entity_states(self, now: datetime) -> None: """Update the states of all the polling entities. To protect from flooding the executor, we will update async entities From 0fc92928a4544a3f4d3021f7a0923e7b950c4e84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Z=C3=A1hradn=C3=ADk?= Date: Sat, 21 Dec 2019 10:54:48 +0100 Subject: [PATCH 360/677] Add device class attribute to modbus sensors (#30030) --- .../components/modbus/binary_sensor.py | 24 +++++++++++++++---- homeassistant/components/modbus/sensor.py | 14 +++++++++-- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/modbus/binary_sensor.py b/homeassistant/components/modbus/binary_sensor.py index faf5160d7e5..9a431d24b0c 100644 --- a/homeassistant/components/modbus/binary_sensor.py +++ b/homeassistant/components/modbus/binary_sensor.py @@ -1,10 +1,15 @@ """Support for Modbus Coil sensors.""" import logging +from typing import Optional import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice -from homeassistant.const import CONF_NAME, CONF_SLAVE +from homeassistant.components.binary_sensor import ( + DEVICE_CLASSES_SCHEMA, + PLATFORM_SCHEMA, + BinarySensorDevice, +) +from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME, CONF_SLAVE from homeassistant.helpers import config_validation as cv from . import CONF_HUB, DEFAULT_HUB, DOMAIN as MODBUS_DOMAIN @@ -20,6 +25,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_COIL): cv.positive_int, vol.Required(CONF_NAME): cv.string, + vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, vol.Optional(CONF_HUB, default=DEFAULT_HUB): cv.string, vol.Optional(CONF_SLAVE): cv.positive_int, } @@ -35,7 +41,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): hub = hass.data[MODBUS_DOMAIN][coil.get(CONF_HUB)] sensors.append( ModbusCoilSensor( - hub, coil.get(CONF_NAME), coil.get(CONF_SLAVE), coil.get(CONF_COIL) + hub, + coil.get(CONF_NAME), + coil.get(CONF_SLAVE), + coil.get(CONF_COIL), + coil.get(CONF_DEVICE_CLASS), ) ) @@ -45,12 +55,13 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class ModbusCoilSensor(BinarySensorDevice): """Modbus coil sensor.""" - def __init__(self, hub, name, slave, coil): + def __init__(self, hub, name, slave, coil, device_class): """Initialize the Modbus coil sensor.""" self._hub = hub self._name = name self._slave = int(slave) if slave else None self._coil = int(coil) + self._device_class = device_class self._value = None @property @@ -63,6 +74,11 @@ class ModbusCoilSensor(BinarySensorDevice): """Return the state of the sensor.""" return self._value + @property + def device_class(self) -> Optional[str]: + """Return the device class of the sensor.""" + return self._device_class + def update(self): """Update the state of the sensor.""" result = self._hub.read_coils(self._slave, self._coil, 1) diff --git a/homeassistant/components/modbus/sensor.py b/homeassistant/components/modbus/sensor.py index 86f6445b8d6..5b04a898ab9 100644 --- a/homeassistant/components/modbus/sensor.py +++ b/homeassistant/components/modbus/sensor.py @@ -1,12 +1,13 @@ """Support for Modbus Register sensors.""" import logging import struct -from typing import Any, Union +from typing import Any, Optional, Union import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA from homeassistant.const import ( + CONF_DEVICE_CLASS, CONF_NAME, CONF_OFFSET, CONF_SLAVE, @@ -67,6 +68,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( vol.Optional(CONF_DATA_TYPE, default=DATA_TYPE_INT): vol.In( [DATA_TYPE_INT, DATA_TYPE_UINT, DATA_TYPE_FLOAT, DATA_TYPE_CUSTOM] ), + vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, vol.Optional(CONF_HUB, default=DEFAULT_HUB): cv.string, vol.Optional(CONF_OFFSET, default=0): number, vol.Optional(CONF_PRECISION, default=0): cv.positive_int, @@ -139,6 +141,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): register.get(CONF_OFFSET), structure, register.get(CONF_PRECISION), + register.get(CONF_DEVICE_CLASS), ) ) @@ -164,6 +167,7 @@ class ModbusRegisterSensor(RestoreEntity): offset, structure, precision, + device_class, ): """Initialize the modbus register sensor.""" self._hub = hub @@ -178,6 +182,7 @@ class ModbusRegisterSensor(RestoreEntity): self._offset = offset self._precision = precision self._structure = structure + self._device_class = device_class self._value = None async def async_added_to_hass(self): @@ -202,6 +207,11 @@ class ModbusRegisterSensor(RestoreEntity): """Return the unit of measurement.""" return self._unit_of_measurement + @property + def device_class(self) -> Optional[str]: + """Return the device class of the sensor.""" + return self._device_class + def update(self): """Update the state of the sensor.""" if self._register_type == REGISTER_TYPE_INPUT: From 3911f24f75d324f7f091126d17ee4e7c1634447c Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Sat, 21 Dec 2019 11:20:31 +0100 Subject: [PATCH 361/677] Upgrade python-velbus (#30110) --- homeassistant/components/velbus/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/velbus/manifest.json b/homeassistant/components/velbus/manifest.json index 1d9401f6cfe..71a2ac2b993 100644 --- a/homeassistant/components/velbus/manifest.json +++ b/homeassistant/components/velbus/manifest.json @@ -3,7 +3,7 @@ "name": "Velbus", "documentation": "https://www.home-assistant.io/integrations/velbus", "requirements": [ - "python-velbus==2.0.27" + "python-velbus==2.0.30" ], "config_flow": true, "dependencies": [], diff --git a/requirements_all.txt b/requirements_all.txt index 17a948f754b..822e92ef759 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1623,7 +1623,7 @@ python-telnet-vlc==1.0.4 python-twitch-client==0.6.0 # homeassistant.components.velbus -python-velbus==2.0.27 +python-velbus==2.0.30 # homeassistant.components.vlc python-vlc==1.1.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index be842e8e82f..a7f443e0ccf 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -528,7 +528,7 @@ python-miio==0.4.8 python-nest==4.1.0 # homeassistant.components.velbus -python-velbus==2.0.27 +python-velbus==2.0.30 # homeassistant.components.awair python_awair==0.0.4 From b41480ae466a99c977e138d335e552107e9865ac Mon Sep 17 00:00:00 2001 From: Jc2k Date: Sat, 21 Dec 2019 10:22:07 +0000 Subject: [PATCH 362/677] Add a config entry mechanism to rediscover a discovery that was ignored (#30099) * Mechanism to rediscover a discovery that was ignored * Add core config entry tests for new rediscover step * Add tests for homekit_controller implementation of async_step_rediscover * Rename rediscover to unignore * Comment the new ignore/unignore mechanisms --- .../homekit_controller/config_flow.py | 32 +++++ homeassistant/config_entries.py | 26 ++++ .../homekit_controller/test_config_flow.py | 82 ++++++++++++ tests/test_config_entries.py | 117 ++++++++++++++++++ 4 files changed, 257 insertions(+) diff --git a/homeassistant/components/homekit_controller/config_flow.py b/homeassistant/components/homekit_controller/config_flow.py index 6cc724e9fe5..3f230d923c7 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -108,6 +108,38 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow): ), ) + async def async_step_unignore(self, user_input): + """Rediscover a previously ignored discover.""" + unique_id = user_input["unique_id"] + await self.async_set_unique_id(unique_id) + + records = await self.hass.async_add_executor_job(self.controller.discover, 5) + for record in records: + if normalize_hkid(record["id"]) != unique_id: + continue + return await self.async_step_zeroconf( + { + "host": record["address"], + "port": record["port"], + "hostname": record["name"], + "type": "_hap._tcp.local.", + "name": record["name"], + "properties": { + "md": record["md"], + "pv": record["pv"], + "id": unique_id, + "c#": record["c#"], + "s#": record["s#"], + "ff": record["ff"], + "ci": record["ci"], + "sf": record["sf"], + "sh": "", + }, + } + ) + + return self.async_abort(reason="no_devices") + async def async_step_zeroconf(self, discovery_info): """Handle a discovered HomeKit accessory. diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 8b314547d9c..942998767a1 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -24,8 +24,17 @@ SOURCE_IMPORT = "import" SOURCE_SSDP = "ssdp" SOURCE_USER = "user" SOURCE_ZEROCONF = "zeroconf" + +# If a user wants to hide a discovery from the UI they can "Ignore" it. The config_entries/ignore_flow +# websocket command creates a config entry with this source and while it exists normal discoveries +# with the same unique id are ignored. SOURCE_IGNORE = "ignore" +# This is used when a user uses the "Stop Ignoring" button in the UI (the +# config_entries/ignore_flow websocket command). It's triggered after the "ignore" config entry has +# been removed and unloaded. +SOURCE_UNIGNORE = "unignore" + HANDLERS = Registry() STORAGE_KEY = "core.config_entries" @@ -461,6 +470,19 @@ class ConfigEntries: dev_reg.async_clear_config_entry(entry_id) ent_reg.async_clear_config_entry(entry_id) + # After we have fully removed an "ignore" config entry we can try and rediscover it so that a + # user is able to immediately start configuring it. We do this by starting a new flow with + # the 'unignore' step. If the integration doesn't implement async_step_unignore then + # this will be a no-op. + if entry.source == SOURCE_IGNORE: + self.hass.async_create_task( + self.hass.config_entries.flow.async_init( + entry.domain, + context={"source": SOURCE_UNIGNORE}, + data={"unique_id": entry.unique_id}, + ) + ) + return {"require_restart": not unload_success} async def async_initialize(self) -> None: @@ -827,6 +849,10 @@ class ConfigFlow(data_entry_flow.FlowHandler): await self.async_set_unique_id(user_input["unique_id"], raise_on_progress=False) return self.async_create_entry(title="Ignored", data={}) + async def async_step_unignore(self, user_input: Dict[str, Any]) -> Dict[str, Any]: + """Rediscover a config entry by it's unique_id.""" + return self.async_abort(reason="not_implemented") + class OptionsFlowManager: """Flow to set options for a configuration entry.""" diff --git a/tests/components/homekit_controller/test_config_flow.py b/tests/components/homekit_controller/test_config_flow.py index 4733581f136..56c1c30e8f3 100644 --- a/tests/components/homekit_controller/test_config_flow.py +++ b/tests/components/homekit_controller/test_config_flow.py @@ -836,3 +836,85 @@ async def test_parse_overlapping_homekit_json(hass): "title_placeholders": {"name": "TestDevice"}, "unique_id": "00:00:00:00:00:00", } + + +async def test_unignore_works(hass): + """Test rediscovery triggered disovers work.""" + discovery_info = { + "name": "TestDevice", + "address": "127.0.0.1", + "port": 8080, + "md": "TestDevice", + "pv": "1.0", + "id": "00:00:00:00:00:00", + "c#": 1, + "s#": 1, + "ff": 0, + "ci": 0, + "sf": 1, + } + + pairing = mock.Mock(pairing_data={"AccessoryPairingID": "00:00:00:00:00:00"}) + pairing.list_accessories_and_characteristics.return_value = [ + { + "aid": 1, + "services": [ + { + "characteristics": [{"type": "23", "value": "Koogeek-LS1-20833F"}], + "type": "3e", + } + ], + } + ] + + flow = _setup_flow_handler(hass) + + flow.controller.pairings = {"00:00:00:00:00:00": pairing} + flow.controller.discover.return_value = [discovery_info] + + result = await flow.async_step_unignore({"unique_id": "00:00:00:00:00:00"}) + assert result["type"] == "form" + assert result["step_id"] == "pair" + assert flow.context == { + "hkid": "00:00:00:00:00:00", + "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", + } + + # User initiates pairing by clicking on 'configure' - device enters pairing mode and displays code + result = await flow.async_step_pair({}) + assert result["type"] == "form" + assert result["step_id"] == "pair" + assert flow.controller.start_pairing.call_count == 1 + + # Pairing finalized + result = await flow.async_step_pair({"pairing_code": "111-22-33"}) + assert result["type"] == "create_entry" + assert result["title"] == "Koogeek-LS1-20833F" + assert result["data"] == pairing.pairing_data + + +async def test_unignore_ignores_missing_devices(hass): + """Test rediscovery triggered disovers handle devices that have gone away.""" + discovery_info = { + "name": "TestDevice", + "address": "127.0.0.1", + "port": 8080, + "md": "TestDevice", + "pv": "1.0", + "id": "00:00:00:00:00:00", + "c#": 1, + "s#": 1, + "ff": 0, + "ci": 0, + "sf": 1, + } + + flow = _setup_flow_handler(hass) + flow.controller.discover.return_value = [discovery_info] + + result = await flow.async_step_unignore({"unique_id": "00:00:00:00:00:01"}) + assert result["type"] == "abort" + assert flow.context == { + "unique_id": "00:00:00:00:00:01", + } diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 19f84e94570..5b694b2de87 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -1186,3 +1186,120 @@ async def test_unique_id_ignore(hass, manager): assert entry.source == "ignore" assert entry.unique_id == "mock-unique-id" + + +async def test_unignore_step_form(hass, manager): + """Test that we can ignore flows that are in progress and have a unique ID, then rediscover them.""" + async_setup_entry = MagicMock(return_value=mock_coro(True)) + mock_integration(hass, MockModule("comp", async_setup_entry=async_setup_entry)) + mock_entity_platform(hass, "config_flow.comp", None) + + class TestFlow(config_entries.ConfigFlow): + + VERSION = 1 + + async def async_step_unignore(self, user_input): + unique_id = user_input["unique_id"] + await self.async_set_unique_id(unique_id) + return self.async_show_form(step_id="discovery") + + with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}): + result = await manager.flow.async_init( + "comp", + context={"source": config_entries.SOURCE_IGNORE}, + data={"unique_id": "mock-unique-id"}, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + + entry = hass.config_entries.async_entries("comp")[0] + assert entry.source == "ignore" + assert entry.unique_id == "mock-unique-id" + assert entry.domain == "comp" + + await manager.async_remove(entry.entry_id) + + # Right after removal there shouldn't be an entry or active flows + assert len(hass.config_entries.async_entries("comp")) == 0 + assert len(hass.config_entries.flow.async_progress()) == 0 + + # But after a 'tick' the unignore step has run and we can see an active flow again. + await hass.async_block_till_done() + assert len(hass.config_entries.flow.async_progress()) == 1 + + # and still not config entries + assert len(hass.config_entries.async_entries("comp")) == 0 + + +async def test_unignore_create_entry(hass, manager): + """Test that we can ignore flows that are in progress and have a unique ID, then rediscover them.""" + async_setup_entry = MagicMock(return_value=mock_coro(True)) + mock_integration(hass, MockModule("comp", async_setup_entry=async_setup_entry)) + mock_entity_platform(hass, "config_flow.comp", None) + + class TestFlow(config_entries.ConfigFlow): + + VERSION = 1 + + async def async_step_unignore(self, user_input): + unique_id = user_input["unique_id"] + await self.async_set_unique_id(unique_id) + return self.async_create_entry(title="yo", data={}) + + with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}): + result = await manager.flow.async_init( + "comp", + context={"source": config_entries.SOURCE_IGNORE}, + data={"unique_id": "mock-unique-id"}, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + + entry = hass.config_entries.async_entries("comp")[0] + assert entry.source == "ignore" + assert entry.unique_id == "mock-unique-id" + assert entry.domain == "comp" + + await manager.async_remove(entry.entry_id) + + # Right after removal there shouldn't be an entry or flow + assert len(hass.config_entries.flow.async_progress()) == 0 + assert len(hass.config_entries.async_entries("comp")) == 0 + + # But after a 'tick' the unignore step has run and we can see a config entry. + await hass.async_block_till_done() + entry = hass.config_entries.async_entries("comp")[0] + assert entry.source == "unignore" + assert entry.unique_id == "mock-unique-id" + assert entry.title == "yo" + + # And still no active flow + assert len(hass.config_entries.flow.async_progress()) == 0 + + +async def test_unignore_default_impl(hass, manager): + """Test that resdicovery is a no-op by default.""" + async_setup_entry = MagicMock(return_value=mock_coro(True)) + mock_integration(hass, MockModule("comp", async_setup_entry=async_setup_entry)) + mock_entity_platform(hass, "config_flow.comp", None) + + class TestFlow(config_entries.ConfigFlow): + + VERSION = 1 + + with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}): + result = await manager.flow.async_init( + "comp", + context={"source": config_entries.SOURCE_IGNORE}, + data={"unique_id": "mock-unique-id"}, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + + entry = hass.config_entries.async_entries("comp")[0] + assert entry.source == "ignore" + assert entry.unique_id == "mock-unique-id" + assert entry.domain == "comp" + + await manager.async_remove(entry.entry_id) + await hass.async_block_till_done() + + assert len(hass.config_entries.async_entries("comp")) == 0 + assert len(hass.config_entries.flow.async_progress()) == 0 From fb3bb8220b99ad0b3a7ad23accf7c7a31478c52f Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Sat, 21 Dec 2019 16:26:58 -0500 Subject: [PATCH 363/677] Implement ZHA entity classes registry (#30108) * ZHA Entity registry. Match a zha_device and channels to a ZHA entity. * Refactor ZHA sensor to use registry. * Remove sensor_types registry. * Fix ZHA device tracker battery remaining. * Remove should_poll/force_update attributes. * Fix binary_sensor regression. * isort. * Pylint. * Don't access protected members. * Address comments and fix spelling. * Make pylint happy again. --- homeassistant/components/zha/binary_sensor.py | 4 +- .../components/zha/core/channels/__init__.py | 2 - homeassistant/components/zha/core/const.py | 18 +- .../components/zha/core/discovery.py | 7 - .../components/zha/core/registries.py | 118 +++++-- .../components/zha/device_tracker.py | 4 +- homeassistant/components/zha/sensor.py | 289 +++++++++--------- tests/components/zha/test_registries.py | 165 ++++++++++ 8 files changed, 415 insertions(+), 192 deletions(-) create mode 100644 tests/components/zha/test_registries.py diff --git a/homeassistant/components/zha/binary_sensor.py b/homeassistant/components/zha/binary_sensor.py index 24c2b92e739..e6176fe9da3 100644 --- a/homeassistant/components/zha/binary_sensor.py +++ b/homeassistant/components/zha/binary_sensor.py @@ -18,7 +18,7 @@ from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from .core.const import ( - CHANNEL_ATTRIBUTE, + CHANNEL_OCCUPANCY, CHANNEL_ON_OFF, CHANNEL_ZONE, DATA_ZHA, @@ -111,7 +111,7 @@ class BinarySensor(ZhaEntity, BinarySensorDevice): self._device_state_attributes = {} self._zone_channel = self.cluster_channels.get(CHANNEL_ZONE) self._on_off_channel = self.cluster_channels.get(CHANNEL_ON_OFF) - self._attr_channel = self.cluster_channels.get(CHANNEL_ATTRIBUTE) + self._attr_channel = self.cluster_channels.get(CHANNEL_OCCUPANCY) self._zha_sensor_type = kwargs[SENSOR_TYPE] async def _determine_device_class(self): diff --git a/homeassistant/components/zha/core/channels/__init__.py b/homeassistant/components/zha/core/channels/__init__.py index 715156c43ed..4013f05e0b6 100644 --- a/homeassistant/components/zha/core/channels/__init__.py +++ b/homeassistant/components/zha/core/channels/__init__.py @@ -17,7 +17,6 @@ from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_send from ..const import ( - CHANNEL_ATTRIBUTE, CHANNEL_EVENT_RELAY, CHANNEL_ZDO, REPORT_CONFIG_DEFAULT, @@ -280,7 +279,6 @@ class ZigbeeChannel(LogMixin): class AttributeListeningChannel(ZigbeeChannel): """Channel for attribute reports from the cluster.""" - CHANNEL_NAME = CHANNEL_ATTRIBUTE REPORT_CONFIG = [{"attr": 0, "config": REPORT_CONFIG_DEFAULT}] def __init__(self, cluster, device): diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index 24c0126ba60..6c991a319ac 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -50,10 +50,16 @@ CHANNEL_DOORLOCK = "door_lock" CHANNEL_ELECTRICAL_MEASUREMENT = "electrical_measurement" CHANNEL_EVENT_RELAY = "event_relay" CHANNEL_FAN = "fan" +CHANNEL_HUMIDITY = "humidity" CHANNEL_IAS_WD = "ias_wd" +CHANNEL_ILLUMINANCE = "illuminance" CHANNEL_LEVEL = ATTR_LEVEL +CHANNEL_OCCUPANCY = "occupancy" CHANNEL_ON_OFF = "on_off" CHANNEL_POWER_CONFIGURATION = "power" +CHANNEL_PRESSURE = "pressure" +CHANNEL_SMARTENERGY_METERING = "smartenergy_metering" +CHANNEL_TEMPERATURE = "temperature" CHANNEL_ZDO = "zdo" CHANNEL_ZONE = ZONE = "ias_zone" @@ -166,15 +172,15 @@ REPORT_CONFIG_OP = ( SENSOR_ACCELERATION = "acceleration" SENSOR_BATTERY = "battery" -SENSOR_ELECTRICAL_MEASUREMENT = "electrical_measurement" +SENSOR_ELECTRICAL_MEASUREMENT = CHANNEL_ELECTRICAL_MEASUREMENT SENSOR_GENERIC = "generic" -SENSOR_HUMIDITY = "humidity" -SENSOR_ILLUMINANCE = "illuminance" +SENSOR_HUMIDITY = CHANNEL_HUMIDITY +SENSOR_ILLUMINANCE = CHANNEL_ILLUMINANCE SENSOR_METERING = "metering" -SENSOR_OCCUPANCY = "occupancy" +SENSOR_OCCUPANCY = CHANNEL_OCCUPANCY SENSOR_OPENING = "opening" -SENSOR_PRESSURE = "pressure" -SENSOR_TEMPERATURE = "temperature" +SENSOR_PRESSURE = CHANNEL_PRESSURE +SENSOR_TEMPERATURE = CHANNEL_TEMPERATURE SENSOR_TYPE = "sensor_type" SIGNAL_ATTR_UPDATED = "attribute_updated" diff --git a/homeassistant/components/zha/core/discovery.py b/homeassistant/components/zha/core/discovery.py index e23862a7d3e..108bd841252 100644 --- a/homeassistant/components/zha/core/discovery.py +++ b/homeassistant/components/zha/core/discovery.py @@ -12,7 +12,6 @@ from zigpy.zcl.clusters.general import OnOff, PowerConfiguration from homeassistant import const as ha_const from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR -from homeassistant.components.sensor import DOMAIN as SENSOR from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_send @@ -21,7 +20,6 @@ from .const import ( COMPONENTS, CONF_DEVICE_CONFIG, DATA_ZHA, - SENSOR_GENERIC, SENSOR_TYPE, UNKNOWN, ZHA_DISCOVERY_NEW, @@ -34,7 +32,6 @@ from .registries import ( EVENT_RELAY_CLUSTERS, OUTPUT_CHANNEL_ONLY_CLUSTERS, REMOTE_DEVICE_TYPES, - SENSOR_TYPES, SINGLE_INPUT_CLUSTER_DEVICE_CLASS, SINGLE_OUTPUT_CLUSTER_DEVICE_CLASS, ZIGBEE_CHANNEL_REGISTRY, @@ -291,10 +288,6 @@ def _async_handle_single_cluster_match( "component": component, } - if component == SENSOR: - discovery_info.update( - {SENSOR_TYPE: SENSOR_TYPES.get(cluster.cluster_id, SENSOR_GENERIC)} - ) if component == BINARY_SENSOR: discovery_info.update( {SENSOR_TYPE: BINARY_SENSOR_TYPES.get(cluster.cluster_id, UNKNOWN)} diff --git a/homeassistant/components/zha/core/registries.py b/homeassistant/components/zha/core/registries.py index 13688a6c420..c2d3b13e375 100644 --- a/homeassistant/components/zha/core/registries.py +++ b/homeassistant/components/zha/core/registries.py @@ -5,7 +5,9 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/integrations/zha/ """ import collections +from typing import Callable, Set +import attr import bellows.ezsp import bellows.zigbee.application import zigpy.profiles.zha @@ -31,21 +33,14 @@ from . import channels # noqa: F401 pylint: disable=unused-import from .const import ( CONTROLLER, SENSOR_ACCELERATION, - SENSOR_BATTERY, - SENSOR_ELECTRICAL_MEASUREMENT, - SENSOR_HUMIDITY, - SENSOR_ILLUMINANCE, - SENSOR_METERING, SENSOR_OCCUPANCY, SENSOR_OPENING, - SENSOR_PRESSURE, - SENSOR_TEMPERATURE, ZHA_GW_RADIO, ZHA_GW_RADIO_DESCRIPTION, ZONE, RadioType, ) -from .decorators import DictRegistry, SetRegistry +from .decorators import CALLABLE_T, DictRegistry, SetRegistry BINARY_SENSOR_CLUSTERS = SetRegistry() BINARY_SENSOR_TYPES = {} @@ -60,7 +55,6 @@ LIGHT_CLUSTERS = SetRegistry() OUTPUT_CHANNEL_ONLY_CLUSTERS = SetRegistry() RADIO_TYPES = {} REMOTE_DEVICE_TYPES = collections.defaultdict(list) -SENSOR_TYPES = {} SINGLE_INPUT_CLUSTER_DEVICE_CLASS = {} SINGLE_OUTPUT_CLUSTER_DEVICE_CLASS = {} SWITCH_CLUSTERS = SetRegistry() @@ -176,19 +170,6 @@ def establish_device_mappings(): {zcl.clusters.general.OnOff: BINARY_SENSOR} ) - SENSOR_TYPES.update( - { - SMARTTHINGS_HUMIDITY_CLUSTER: SENSOR_HUMIDITY, - zcl.clusters.general.PowerConfiguration.cluster_id: SENSOR_BATTERY, - zcl.clusters.homeautomation.ElectricalMeasurement.cluster_id: SENSOR_ELECTRICAL_MEASUREMENT, - zcl.clusters.measurement.IlluminanceMeasurement.cluster_id: SENSOR_ILLUMINANCE, - zcl.clusters.measurement.PressureMeasurement.cluster_id: SENSOR_PRESSURE, - zcl.clusters.measurement.RelativeHumidity.cluster_id: SENSOR_HUMIDITY, - zcl.clusters.measurement.TemperatureMeasurement.cluster_id: SENSOR_TEMPERATURE, - zcl.clusters.smartenergy.Metering.cluster_id: SENSOR_METERING, - } - ) - zha = zigpy.profiles.zha REMOTE_DEVICE_TYPES[zha.PROFILE_ID].append(zha.DeviceType.COLOR_CONTROLLER) REMOTE_DEVICE_TYPES[zha.PROFILE_ID].append(zha.DeviceType.COLOR_DIMMER_SWITCH) @@ -207,3 +188,96 @@ def establish_device_mappings(): REMOTE_DEVICE_TYPES[zll.PROFILE_ID].append(zll.DeviceType.CONTROL_BRIDGE) REMOTE_DEVICE_TYPES[zll.PROFILE_ID].append(zll.DeviceType.CONTROLLER) REMOTE_DEVICE_TYPES[zll.PROFILE_ID].append(zll.DeviceType.SCENE_CONTROLLER) + + +@attr.s(frozen=True) +class MatchRule: + """Match a ZHA Entity to a channel name or generic id.""" + + channel_names: Set[str] = attr.ib(factory=frozenset, converter=frozenset) + generic_ids: Set[str] = attr.ib(factory=frozenset, converter=frozenset) + manufacturer: str = attr.ib(default=None) + model: str = attr.ib(default=None) + + +class ZHAEntityRegistry: + """Channel to ZHA Entity mapping.""" + + def __init__(self): + """Initialize Registry instance.""" + self._strict_registry = collections.defaultdict(dict) + self._loose_registry = collections.defaultdict(dict) + + def get_entity( + self, component: str, zha_device, chnls: list, default: CALLABLE_T = None + ) -> CALLABLE_T: + """Match a ZHA Channels to a ZHA Entity class.""" + for match in self._strict_registry[component]: + if self._strict_matched(zha_device, chnls, match): + return self._strict_registry[component][match] + + return default + + def strict_match( + self, component: str, rule: MatchRule + ) -> Callable[[CALLABLE_T], CALLABLE_T]: + """Decorate a strict match rule.""" + + def decorator(zha_ent: CALLABLE_T) -> CALLABLE_T: + """Register a strict match rule. + + All non empty fields of a match rule must match. + """ + self._strict_registry[component][rule] = zha_ent + return zha_ent + + return decorator + + def loose_match( + self, component: str, rule: MatchRule + ) -> Callable[[CALLABLE_T], CALLABLE_T]: + """Decorate a loose match rule.""" + + def decorator(zha_entity: CALLABLE_T) -> CALLABLE_T: + """Register a loose match rule. + + All non empty fields of a match rule must match. + """ + self._loose_registry[component][rule] = zha_entity + return zha_entity + + return decorator + + def _strict_matched(self, zha_device, chnls: dict, rule: MatchRule) -> bool: + """Return True if this device matches the criteria.""" + return all(self._matched(zha_device, chnls, rule)) + + def _loose_matched(self, zha_device, chnls: dict, rule: MatchRule) -> bool: + """Return True if this device matches the criteria.""" + return any(self._matched(zha_device, chnls, rule)) + + @staticmethod + def _matched(zha_device, chnls: list, rule: MatchRule) -> bool: + """Return a list of field matches.""" + if not any(attr.asdict(rule).values()): + return [False] + + matches = [] + if rule.channel_names: + channel_names = {ch.name for ch in chnls} + matches.append(rule.channel_names.issubset(channel_names)) + + if rule.generic_ids: + all_generic_ids = {ch.generic_id for ch in chnls} + matches.append(rule.generic_ids.issubset(all_generic_ids)) + + if rule.manufacturer: + matches.append(zha_device.manufacturer == rule.manufacturer) + + if rule.model: + matches.append(zha_device.model == rule.model) + + return matches + + +ZHA_ENTITIES = ZHAEntityRegistry() diff --git a/homeassistant/components/zha/device_tracker.py b/homeassistant/components/zha/device_tracker.py index 60a1f6c3c40..e7663b35686 100644 --- a/homeassistant/components/zha/device_tracker.py +++ b/homeassistant/components/zha/device_tracker.py @@ -15,7 +15,7 @@ from .core.const import ( ZHA_DISCOVERY_NEW, ) from .entity import ZhaEntity -from .sensor import battery_percentage_remaining_formatter +from .sensor import Battery _LOGGER = logging.getLogger(__name__) @@ -100,7 +100,7 @@ class ZHADeviceScannerEntity(ScannerEntity, ZhaEntity): """Handle tracking.""" self.debug("battery_percentage_remaining updated: %s", value) self._connected = True - self._battery_level = battery_percentage_remaining_formatter(value) + self._battery_level = Battery.formatter(value) self.async_schedule_update_ha_state() @property diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index b260dfc5459..133e82e6914 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -1,4 +1,5 @@ """Sensors on Zigbee Home Automation networks.""" +import functools import logging import numbers @@ -16,25 +17,20 @@ from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from .core.const import ( - CHANNEL_ATTRIBUTE, CHANNEL_ELECTRICAL_MEASUREMENT, + CHANNEL_HUMIDITY, + CHANNEL_ILLUMINANCE, CHANNEL_POWER_CONFIGURATION, + CHANNEL_PRESSURE, + CHANNEL_SMARTENERGY_METERING, + CHANNEL_TEMPERATURE, DATA_ZHA, DATA_ZHA_DISPATCHERS, - SENSOR_BATTERY, - SENSOR_ELECTRICAL_MEASUREMENT, - SENSOR_GENERIC, - SENSOR_HUMIDITY, - SENSOR_ILLUMINANCE, - SENSOR_METERING, - SENSOR_PRESSURE, - SENSOR_TEMPERATURE, - SENSOR_TYPE, SIGNAL_ATTR_UPDATED, SIGNAL_STATE_ATTR, - UNKNOWN, ZHA_DISCOVERY_NEW, ) +from .core.registries import SMARTTHINGS_HUMIDITY_CLUSTER, ZHA_ENTITIES, MatchRule from .entity import ZhaEntity PARALLEL_UPDATES = 5 @@ -56,115 +52,8 @@ BATTERY_SIZES = { 255: "Unknown", } - -# Formatter functions -def pass_through_formatter(value): - """No op update function.""" - return value - - -def illuminance_formatter(value): - """Convert Illimination data.""" - if value is None: - return None - return round(pow(10, ((value - 1) / 10000)), 1) - - -def temperature_formatter(value): - """Convert temperature data.""" - if value is None: - return None - return round(value / 100, 1) - - -def humidity_formatter(value): - """Return the state of the entity.""" - if value is None: - return None - return round(float(value) / 100, 1) - - -def active_power_formatter(value): - """Return the state of the entity.""" - if value is None: - return None - return round(float(value) / 10, 1) - - -def pressure_formatter(value): - """Return the state of the entity.""" - if value is None: - return None - - return round(float(value)) - - -def battery_percentage_remaining_formatter(value): - """Return the state of the entity.""" - # per zcl specs battery percent is reported at 200% ¯\_(ツ)_/¯ - if not isinstance(value, numbers.Number) or value == -1: - return value - value = value / 2 - value = int(round(value)) - return value - - -async def async_battery_device_state_attr_provider(channel): - """Return device statr attrs for battery sensors.""" - state_attrs = {} - battery_size = await channel.get_attribute_value("battery_size") - if battery_size is not None: - state_attrs["battery_size"] = BATTERY_SIZES.get(battery_size, "Unknown") - battery_quantity = await channel.get_attribute_value("battery_quantity") - if battery_quantity is not None: - state_attrs["battery_quantity"] = battery_quantity - return state_attrs - - -FORMATTER_FUNC_REGISTRY = { - SENSOR_HUMIDITY: humidity_formatter, - SENSOR_TEMPERATURE: temperature_formatter, - SENSOR_PRESSURE: pressure_formatter, - SENSOR_ELECTRICAL_MEASUREMENT: active_power_formatter, - SENSOR_ILLUMINANCE: illuminance_formatter, - SENSOR_GENERIC: pass_through_formatter, - SENSOR_BATTERY: battery_percentage_remaining_formatter, -} - -UNIT_REGISTRY = { - SENSOR_HUMIDITY: "%", - SENSOR_TEMPERATURE: TEMP_CELSIUS, - SENSOR_PRESSURE: "hPa", - SENSOR_ILLUMINANCE: "lx", - SENSOR_ELECTRICAL_MEASUREMENT: POWER_WATT, - SENSOR_GENERIC: None, - SENSOR_BATTERY: "%", -} - -CHANNEL_REGISTRY = { - SENSOR_ELECTRICAL_MEASUREMENT: CHANNEL_ELECTRICAL_MEASUREMENT, - SENSOR_BATTERY: CHANNEL_POWER_CONFIGURATION, -} - -POLLING_REGISTRY = {SENSOR_ELECTRICAL_MEASUREMENT: True} - -FORCE_UPDATE_REGISTRY = {SENSOR_ELECTRICAL_MEASUREMENT: False} - -DEVICE_CLASS_REGISTRY = { - UNKNOWN: None, - SENSOR_HUMIDITY: DEVICE_CLASS_HUMIDITY, - SENSOR_TEMPERATURE: DEVICE_CLASS_TEMPERATURE, - SENSOR_PRESSURE: DEVICE_CLASS_PRESSURE, - SENSOR_ILLUMINANCE: DEVICE_CLASS_ILLUMINANCE, - SENSOR_METERING: DEVICE_CLASS_POWER, - SENSOR_ELECTRICAL_MEASUREMENT: DEVICE_CLASS_POWER, - SENSOR_BATTERY: DEVICE_CLASS_BATTERY, -} - - -DEVICE_STATE_ATTR_PROVIDER_REGISTRY = { - SENSOR_BATTERY: async_battery_device_state_attr_provider -} +CHANNEL_ST_HUMIDITY_CLUSTER = f"channel_0x{SMARTTHINGS_HUMIDITY_CLUSTER:04x}" +STRICT_MATCH = functools.partial(ZHA_ENTITIES.strict_match, DOMAIN) async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): @@ -206,43 +95,34 @@ async def _async_setup_entities( async def make_sensor(discovery_info): """Create ZHA sensors factory.""" - return Sensor(**discovery_info) + + zha_dev = discovery_info["zha_device"] + channels = discovery_info["channels"] + + entity = ZHA_ENTITIES.get_entity(DOMAIN, zha_dev, channels, Sensor) + return entity(**discovery_info) class Sensor(ZhaEntity): """Base ZHA sensor.""" + _decimals = 1 + _device_class = None + _divisor = 1 _domain = DOMAIN + _multiplier = 1 + _unit = None def __init__(self, unique_id, zha_device, channels, **kwargs): """Init this sensor.""" super().__init__(unique_id, zha_device, channels, **kwargs) - self._sensor_type = kwargs.get(SENSOR_TYPE, SENSOR_GENERIC) - self._channel = self.cluster_channels.get( - CHANNEL_REGISTRY.get(self._sensor_type, CHANNEL_ATTRIBUTE) - ) - if self._sensor_type == SENSOR_METERING: - self._unit = self._channel.unit_of_measurement - self._formatter_function = self._channel.formatter_function - else: - self._unit = UNIT_REGISTRY.get(self._sensor_type) - self._formatter_function = FORMATTER_FUNC_REGISTRY.get( - self._sensor_type, pass_through_formatter - ) - self._force_update = FORCE_UPDATE_REGISTRY.get(self._sensor_type, False) - self._should_poll = POLLING_REGISTRY.get(self._sensor_type, False) - self._device_class = DEVICE_CLASS_REGISTRY.get(self._sensor_type, None) - self.state_attr_provider = DEVICE_STATE_ATTR_PROVIDER_REGISTRY.get( - self._sensor_type, None - ) + self._channel = channels[0] async def async_added_to_hass(self): """Run when about to be added to hass.""" await super().async_added_to_hass() - if self.state_attr_provider is not None: - self._device_state_attributes = await self.state_attr_provider( - self._channel - ) + self._device_state_attributes = await self.async_state_attr_provider() + await self.async_accept_signal( self._channel, SIGNAL_ATTR_UPDATED, self.async_set_state ) @@ -271,14 +151,9 @@ class Sensor(ZhaEntity): def async_set_state(self, state): """Handle state update from channel.""" - # this is necessary because HA saves the unit based on what shows in - # the UI and not based on what the sensor has configured so we need - # to flip it back after state restoration - if self._sensor_type == SENSOR_METERING: - self._unit = self._channel.unit_of_measurement - else: - self._unit = UNIT_REGISTRY.get(self._sensor_type) - self._state = self._formatter_function(state) + if state is not None: + state = self.formatter(state) + self._state = state self.async_schedule_update_ha_state() @callback @@ -286,3 +161,115 @@ class Sensor(ZhaEntity): """Restore previous state.""" self._state = last_state.state self._unit = last_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + + @callback + async def async_state_attr_provider(self): + """Initialize device state attributes.""" + return {} + + def formatter(self, value): + """Numeric pass-through formatter.""" + if self._decimals > 0: + return round( + float(value * self._multiplier) / self._divisor, self._decimals + ) + return round(float(value * self._multiplier) / self._divisor) + + +@STRICT_MATCH(MatchRule(channel_names={CHANNEL_POWER_CONFIGURATION})) +class Battery(Sensor): + """Battery sensor of power configuration cluster.""" + + _device_class = DEVICE_CLASS_BATTERY + _unit = "%" + + @staticmethod + def formatter(value): + """Return the state of the entity.""" + # per zcl specs battery percent is reported at 200% ¯\_(ツ)_/¯ + if not isinstance(value, numbers.Number) or value == -1: + return value + value = round(value / 2) + return value + + async def async_state_attr_provider(self): + """Return device state attrs for battery sensors.""" + state_attrs = {} + battery_size = await self._channel.get_attribute_value("battery_size") + if battery_size is not None: + state_attrs["battery_size"] = BATTERY_SIZES.get(battery_size, "Unknown") + battery_quantity = await self._channel.get_attribute_value("battery_quantity") + if battery_quantity is not None: + state_attrs["battery_quantity"] = battery_quantity + return state_attrs + + +@STRICT_MATCH(MatchRule(channel_names={CHANNEL_ELECTRICAL_MEASUREMENT})) +class ElectricalMeasurement(Sensor): + """Active power measurement.""" + + _device_class = DEVICE_CLASS_POWER + _divisor = 10 + _unit = POWER_WATT + + @property + def should_poll(self) -> bool: + """Return True if HA needs to poll for state changes.""" + return True + + +@STRICT_MATCH(MatchRule(generic_ids={CHANNEL_ST_HUMIDITY_CLUSTER})) +@STRICT_MATCH(MatchRule(channel_names={CHANNEL_HUMIDITY})) +class Humidity(Sensor): + """Humidity sensor.""" + + _device_class = DEVICE_CLASS_HUMIDITY + _divisor = 100 + _unit = "%" + + +@STRICT_MATCH(MatchRule(channel_names={CHANNEL_ILLUMINANCE})) +class Illuminance(Sensor): + """Illuminance Sensor.""" + + _device_class = DEVICE_CLASS_ILLUMINANCE + _unit = "lx" + + @staticmethod + def formatter(value): + """Convert illumination data.""" + return round(pow(10, ((value - 1) / 10000)), 1) + + +@STRICT_MATCH(MatchRule(channel_names={CHANNEL_SMARTENERGY_METERING})) +class SmartEnergyMetering(Sensor): + """Metering sensor.""" + + _device_class = DEVICE_CLASS_POWER + + def formatter(self, value): + """Pass through channel formatter.""" + return self._channel.formatter_function(value) + + @property + def unit_of_measurement(self): + """Return Unit of measurement.""" + return self._channel.unit_of_measurement + + +@STRICT_MATCH(MatchRule(channel_names={CHANNEL_PRESSURE})) +class Pressure(Sensor): + """Pressure sensor.""" + + _device_class = DEVICE_CLASS_PRESSURE + _decimals = 0 + _unit = "hPa" + + +@STRICT_MATCH(MatchRule(channel_names={CHANNEL_TEMPERATURE})) +class Temperature(Sensor): + """Temperature Sensor.""" + + _device_class = DEVICE_CLASS_TEMPERATURE + _divisor = 100 + _unit = TEMP_CELSIUS diff --git a/tests/components/zha/test_registries.py b/tests/components/zha/test_registries.py new file mode 100644 index 00000000000..a0eef355229 --- /dev/null +++ b/tests/components/zha/test_registries.py @@ -0,0 +1,165 @@ +"""Test ZHA registries.""" +from unittest import mock + +import pytest + +import homeassistant.components.zha.core.registries as registries + +MANUFACTURER = "mock manufacturer" +MODEL = "mock model" + + +@pytest.fixture +def zha_device(): + """Return a mock of ZHA device.""" + dev = mock.MagicMock() + dev.manufacturer = MANUFACTURER + dev.model = MODEL + return dev + + +@pytest.fixture +def channels(): + """Return a mock of channels.""" + + def channel(name, chan_id): + ch = mock.MagicMock() + ch.name = name + ch.generic_id = chan_id + return ch + + return [channel("level", "channel_0x0008"), channel("on_off", "channel_0x0006")] + + +@pytest.mark.parametrize( + "rule, matched", + [ + (registries.MatchRule(), False), + (registries.MatchRule(channel_names={"level"}), True), + (registries.MatchRule(channel_names={"level", "no match"}), False), + (registries.MatchRule(channel_names={"on_off"}), True), + (registries.MatchRule(channel_names={"on_off", "no match"}), False), + (registries.MatchRule(channel_names={"on_off", "level"}), True), + (registries.MatchRule(channel_names={"on_off", "level", "no match"}), False), + # test generic_id matching + (registries.MatchRule(generic_ids={"channel_0x0006"}), True), + (registries.MatchRule(generic_ids={"channel_0x0008"}), True), + (registries.MatchRule(generic_ids={"channel_0x0006", "channel_0x0008"}), True), + ( + registries.MatchRule( + generic_ids={"channel_0x0006", "channel_0x0008", "channel_0x0009"} + ), + False, + ), + ( + registries.MatchRule( + generic_ids={"channel_0x0006", "channel_0x0008"}, + channel_names={"on_off", "level"}, + ), + True, + ), + # manufacturer matching + (registries.MatchRule(manufacturer="no match"), False), + (registries.MatchRule(manufacturer=MANUFACTURER), True), + (registries.MatchRule(model=MODEL), True), + (registries.MatchRule(model="no match"), False), + # match everything + ( + registries.MatchRule( + generic_ids={"channel_0x0006", "channel_0x0008"}, + channel_names={"on_off", "level"}, + manufacturer=MANUFACTURER, + model=MODEL, + ), + True, + ), + ], +) +def test_registry_matching(rule, matched, zha_device, channels): + """Test empty rule matching.""" + reg = registries.ZHAEntityRegistry() + assert reg._strict_matched(zha_device, channels, rule) is matched + + +@pytest.mark.parametrize( + "rule, matched", + [ + (registries.MatchRule(), False), + (registries.MatchRule(channel_names={"level"}), True), + (registries.MatchRule(channel_names={"level", "no match"}), False), + (registries.MatchRule(channel_names={"on_off"}), True), + (registries.MatchRule(channel_names={"on_off", "no match"}), False), + (registries.MatchRule(channel_names={"on_off", "level"}), True), + (registries.MatchRule(channel_names={"on_off", "level", "no match"}), False), + ( + registries.MatchRule(channel_names={"on_off", "level"}, model="no match"), + True, + ), + ( + registries.MatchRule( + channel_names={"on_off", "level"}, + model="no match", + manufacturer="no match", + ), + True, + ), + ( + registries.MatchRule( + channel_names={"on_off", "level"}, + model="no match", + manufacturer=MANUFACTURER, + ), + True, + ), + # test generic_id matching + (registries.MatchRule(generic_ids={"channel_0x0006"}), True), + (registries.MatchRule(generic_ids={"channel_0x0008"}), True), + (registries.MatchRule(generic_ids={"channel_0x0006", "channel_0x0008"}), True), + ( + registries.MatchRule( + generic_ids={"channel_0x0006", "channel_0x0008", "channel_0x0009"} + ), + False, + ), + ( + registries.MatchRule( + generic_ids={"channel_0x0006", "channel_0x0008", "channel_0x0009"}, + model="mo match", + ), + False, + ), + ( + registries.MatchRule( + generic_ids={"channel_0x0006", "channel_0x0008", "channel_0x0009"}, + model=MODEL, + ), + True, + ), + ( + registries.MatchRule( + generic_ids={"channel_0x0006", "channel_0x0008"}, + channel_names={"on_off", "level"}, + ), + True, + ), + # manufacturer matching + (registries.MatchRule(manufacturer="no match"), False), + (registries.MatchRule(manufacturer=MANUFACTURER), True), + (registries.MatchRule(model=MODEL), True), + (registries.MatchRule(model="no match"), False), + # match everything + ( + registries.MatchRule( + generic_ids={"channel_0x0006", "channel_0x0008"}, + channel_names={"on_off", "level"}, + manufacturer=MANUFACTURER, + model=MODEL, + ), + True, + ), + ], +) +def test_registry_loose_matching(rule, matched, zha_device, channels): + """Test loose rule matching.""" + reg = registries.ZHAEntityRegistry() + assert reg._loose_matched(zha_device, channels, rule) is matched From 834929a14e4c0d288891a7ad9b8da94aa78b30c3 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 21 Dec 2019 22:45:06 +0100 Subject: [PATCH 364/677] Clean up mobile app webhooks (#30123) --- .../components/http/data_validator.py | 3 + homeassistant/components/mobile_app/const.py | 153 ------ .../components/mobile_app/device_tracker.py | 10 +- .../components/mobile_app/http_api.py | 27 +- .../components/mobile_app/webhook.py | 503 +++++++++++------- script/hassfest/dependencies.py | 1 + 6 files changed, 356 insertions(+), 341 deletions(-) diff --git a/homeassistant/components/http/data_validator.py b/homeassistant/components/http/data_validator.py index 017644a4d36..51b3b5617e4 100644 --- a/homeassistant/components/http/data_validator.py +++ b/homeassistant/components/http/data_validator.py @@ -20,6 +20,9 @@ class RequestDataValidator: def __init__(self, schema, allow_empty=False): """Initialize the decorator.""" + if isinstance(schema, dict): + schema = vol.Schema(schema) + self._schema = schema self._allow_empty = allow_empty diff --git a/homeassistant/components/mobile_app/const.py b/homeassistant/components/mobile_app/const.py index 318076d5fd9..720cf7106e7 100644 --- a/homeassistant/components/mobile_app/const.py +++ b/homeassistant/components/mobile_app/const.py @@ -1,19 +1,4 @@ """Constants for mobile_app.""" -import voluptuous as vol - -from homeassistant.components.binary_sensor import ( - DEVICE_CLASSES as BINARY_SENSOR_CLASSES, -) -from homeassistant.components.device_tracker import ( - ATTR_BATTERY, - ATTR_GPS, - ATTR_GPS_ACCURACY, - ATTR_LOCATION_NAME, -) -from homeassistant.components.sensor import DEVICE_CLASSES as SENSOR_CLASSES -from homeassistant.const import ATTR_DOMAIN, ATTR_SERVICE, ATTR_SERVICE_DATA -from homeassistant.helpers import config_validation as cv - DOMAIN = "mobile_app" STORAGE_KEY = DOMAIN @@ -71,100 +56,6 @@ ERR_ENCRYPTION_REQUIRED = "encryption_required" ERR_SENSOR_NOT_REGISTERED = "not_registered" ERR_SENSOR_DUPLICATE_UNIQUE_ID = "duplicate_unique_id" -WEBHOOK_TYPE_CALL_SERVICE = "call_service" -WEBHOOK_TYPE_FIRE_EVENT = "fire_event" -WEBHOOK_TYPE_GET_CONFIG = "get_config" -WEBHOOK_TYPE_GET_ZONES = "get_zones" -WEBHOOK_TYPE_REGISTER_SENSOR = "register_sensor" -WEBHOOK_TYPE_RENDER_TEMPLATE = "render_template" -WEBHOOK_TYPE_UPDATE_LOCATION = "update_location" -WEBHOOK_TYPE_UPDATE_REGISTRATION = "update_registration" -WEBHOOK_TYPE_UPDATE_SENSOR_STATES = "update_sensor_states" - -WEBHOOK_TYPES = [ - WEBHOOK_TYPE_CALL_SERVICE, - WEBHOOK_TYPE_FIRE_EVENT, - WEBHOOK_TYPE_GET_CONFIG, - WEBHOOK_TYPE_GET_ZONES, - WEBHOOK_TYPE_REGISTER_SENSOR, - WEBHOOK_TYPE_RENDER_TEMPLATE, - WEBHOOK_TYPE_UPDATE_LOCATION, - WEBHOOK_TYPE_UPDATE_REGISTRATION, - WEBHOOK_TYPE_UPDATE_SENSOR_STATES, -] - - -REGISTRATION_SCHEMA = vol.Schema( - { - vol.Optional(ATTR_APP_DATA, default={}): dict, - vol.Required(ATTR_APP_ID): cv.string, - vol.Required(ATTR_APP_NAME): cv.string, - vol.Required(ATTR_APP_VERSION): cv.string, - vol.Required(ATTR_DEVICE_NAME): cv.string, - vol.Required(ATTR_MANUFACTURER): cv.string, - vol.Required(ATTR_MODEL): cv.string, - vol.Required(ATTR_OS_NAME): cv.string, - vol.Optional(ATTR_OS_VERSION): cv.string, - vol.Required(ATTR_SUPPORTS_ENCRYPTION, default=False): cv.boolean, - } -) - -UPDATE_REGISTRATION_SCHEMA = vol.Schema( - { - vol.Optional(ATTR_APP_DATA, default={}): dict, - vol.Required(ATTR_APP_VERSION): cv.string, - vol.Required(ATTR_DEVICE_NAME): cv.string, - vol.Required(ATTR_MANUFACTURER): cv.string, - vol.Required(ATTR_MODEL): cv.string, - vol.Optional(ATTR_OS_VERSION): cv.string, - } -) - -WEBHOOK_PAYLOAD_SCHEMA = vol.Schema( - { - vol.Required(ATTR_WEBHOOK_TYPE): cv.string, # vol.In(WEBHOOK_TYPES) - vol.Required(ATTR_WEBHOOK_DATA, default={}): vol.Any(dict, list), - vol.Optional(ATTR_WEBHOOK_ENCRYPTED, default=False): cv.boolean, - vol.Optional(ATTR_WEBHOOK_ENCRYPTED_DATA): cv.string, - } -) - -CALL_SERVICE_SCHEMA = vol.Schema( - { - vol.Required(ATTR_DOMAIN): cv.string, - vol.Required(ATTR_SERVICE): cv.string, - vol.Optional(ATTR_SERVICE_DATA, default={}): dict, - } -) - -FIRE_EVENT_SCHEMA = vol.Schema( - { - vol.Required(ATTR_EVENT_TYPE): cv.string, - vol.Optional(ATTR_EVENT_DATA, default={}): dict, - } -) - -RENDER_TEMPLATE_SCHEMA = vol.Schema( - { - str: { - vol.Required(ATTR_TEMPLATE): cv.template, - vol.Optional(ATTR_TEMPLATE_VARIABLES, default={}): dict, - } - } -) - -UPDATE_LOCATION_SCHEMA = vol.Schema( - { - vol.Optional(ATTR_LOCATION_NAME): cv.string, - vol.Required(ATTR_GPS): cv.gps, - vol.Required(ATTR_GPS_ACCURACY): cv.positive_int, - vol.Optional(ATTR_BATTERY): cv.positive_int, - vol.Optional(ATTR_SPEED): cv.positive_int, - vol.Optional(ATTR_ALTITUDE): vol.Coerce(float), - vol.Optional(ATTR_COURSE): cv.positive_int, - vol.Optional(ATTR_VERTICAL_ACCURACY): cv.positive_int, - } -) ATTR_SENSOR_ATTRIBUTES = "attributes" ATTR_SENSOR_DEVICE_CLASS = "device_class" @@ -177,49 +68,5 @@ ATTR_SENSOR_TYPE_SENSOR = "sensor" ATTR_SENSOR_UNIQUE_ID = "unique_id" ATTR_SENSOR_UOM = "unit_of_measurement" -SENSOR_TYPES = [ATTR_SENSOR_TYPE_BINARY_SENSOR, ATTR_SENSOR_TYPE_SENSOR] - -COMBINED_CLASSES = sorted(set(BINARY_SENSOR_CLASSES + SENSOR_CLASSES)) - SIGNAL_SENSOR_UPDATE = DOMAIN + "_sensor_update" SIGNAL_LOCATION_UPDATE = DOMAIN + "_location_update_{}" - -REGISTER_SENSOR_SCHEMA = vol.Schema( - { - vol.Optional(ATTR_SENSOR_ATTRIBUTES, default={}): dict, - vol.Optional(ATTR_SENSOR_DEVICE_CLASS): vol.All( - vol.Lower, vol.In(COMBINED_CLASSES) - ), - vol.Required(ATTR_SENSOR_NAME): cv.string, - vol.Required(ATTR_SENSOR_TYPE): vol.In(SENSOR_TYPES), - vol.Required(ATTR_SENSOR_UNIQUE_ID): cv.string, - vol.Optional(ATTR_SENSOR_UOM): cv.string, - vol.Required(ATTR_SENSOR_STATE): vol.Any(bool, str, int, float), - vol.Optional(ATTR_SENSOR_ICON, default="mdi:cellphone"): cv.icon, - } -) - -UPDATE_SENSOR_STATE_SCHEMA = vol.All( - cv.ensure_list, - [ - vol.Schema( - { - vol.Optional(ATTR_SENSOR_ATTRIBUTES, default={}): dict, - vol.Optional(ATTR_SENSOR_ICON, default="mdi:cellphone"): cv.icon, - vol.Required(ATTR_SENSOR_STATE): vol.Any(bool, str, int, float), - vol.Required(ATTR_SENSOR_TYPE): vol.In(SENSOR_TYPES), - vol.Required(ATTR_SENSOR_UNIQUE_ID): cv.string, - } - ) - ], -) - -WEBHOOK_SCHEMAS = { - WEBHOOK_TYPE_CALL_SERVICE: CALL_SERVICE_SCHEMA, - WEBHOOK_TYPE_FIRE_EVENT: FIRE_EVENT_SCHEMA, - WEBHOOK_TYPE_REGISTER_SENSOR: REGISTER_SENSOR_SCHEMA, - WEBHOOK_TYPE_RENDER_TEMPLATE: RENDER_TEMPLATE_SCHEMA, - WEBHOOK_TYPE_UPDATE_LOCATION: UPDATE_LOCATION_SCHEMA, - WEBHOOK_TYPE_UPDATE_REGISTRATION: UPDATE_REGISTRATION_SCHEMA, - WEBHOOK_TYPE_UPDATE_SENSOR_STATES: UPDATE_SENSOR_STATE_SCHEMA, -} diff --git a/homeassistant/components/mobile_app/device_tracker.py b/homeassistant/components/mobile_app/device_tracker.py index f58f80aa5fc..480bfee512f 100644 --- a/homeassistant/components/mobile_app/device_tracker.py +++ b/homeassistant/components/mobile_app/device_tracker.py @@ -1,6 +1,12 @@ """Device tracker platform that adds support for OwnTracks over MQTT.""" import logging +from homeassistant.components.device_tracker import ( + ATTR_BATTERY, + ATTR_GPS, + ATTR_GPS_ACCURACY, + ATTR_LOCATION_NAME, +) from homeassistant.components.device_tracker.config_entry import TrackerEntity from homeassistant.components.device_tracker.const import SOURCE_TYPE_GPS from homeassistant.const import ATTR_BATTERY_LEVEL, ATTR_LATITUDE, ATTR_LONGITUDE @@ -9,13 +15,9 @@ from homeassistant.helpers.restore_state import RestoreEntity from .const import ( ATTR_ALTITUDE, - ATTR_BATTERY, ATTR_COURSE, ATTR_DEVICE_ID, ATTR_DEVICE_NAME, - ATTR_GPS, - ATTR_GPS_ACCURACY, - ATTR_LOCATION_NAME, ATTR_SPEED, ATTR_VERTICAL_ACCURACY, SIGNAL_LOCATION_UPDATE, diff --git a/homeassistant/components/mobile_app/http_api.py b/homeassistant/components/mobile_app/http_api.py index 9581a374384..717413f889a 100644 --- a/homeassistant/components/mobile_app/http_api.py +++ b/homeassistant/components/mobile_app/http_api.py @@ -5,20 +5,30 @@ import uuid from aiohttp.web import Request, Response from nacl.secret import SecretBox +import voluptuous as vol from homeassistant.components.http import HomeAssistantView from homeassistant.components.http.data_validator import RequestDataValidator from homeassistant.const import CONF_WEBHOOK_ID, HTTP_CREATED +from homeassistant.helpers import config_validation as cv from .const import ( + ATTR_APP_DATA, + ATTR_APP_ID, + ATTR_APP_NAME, + ATTR_APP_VERSION, ATTR_DEVICE_ID, + ATTR_DEVICE_NAME, + ATTR_MANUFACTURER, + ATTR_MODEL, + ATTR_OS_NAME, + ATTR_OS_VERSION, ATTR_SUPPORTS_ENCRYPTION, CONF_CLOUDHOOK_URL, CONF_REMOTE_UI_URL, CONF_SECRET, CONF_USER_ID, DOMAIN, - REGISTRATION_SCHEMA, ) from .helpers import supports_encryption @@ -29,7 +39,20 @@ class RegistrationsView(HomeAssistantView): url = "/api/mobile_app/registrations" name = "api:mobile_app:register" - @RequestDataValidator(REGISTRATION_SCHEMA) + @RequestDataValidator( + { + vol.Optional(ATTR_APP_DATA, default={}): dict, + vol.Required(ATTR_APP_ID): cv.string, + vol.Required(ATTR_APP_NAME): cv.string, + vol.Required(ATTR_APP_VERSION): cv.string, + vol.Required(ATTR_DEVICE_NAME): cv.string, + vol.Required(ATTR_MANUFACTURER): cv.string, + vol.Required(ATTR_MODEL): cv.string, + vol.Required(ATTR_OS_NAME): cv.string, + vol.Optional(ATTR_OS_VERSION): cv.string, + vol.Required(ATTR_SUPPORTS_ENCRYPTION, default=False): cv.boolean, + } + ) async def post(self, request: Request, data: Dict) -> Response: """Handle the POST request for registration.""" hass = request.app["hass"] diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index c2bc6c94112..3a477d89925 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -1,10 +1,21 @@ """Webhook handlers for mobile_app.""" +from functools import wraps import logging from aiohttp.web import HTTPBadRequest, Request, Response import voluptuous as vol +from homeassistant.components.binary_sensor import ( + DEVICE_CLASSES as BINARY_SENSOR_CLASSES, +) +from homeassistant.components.device_tracker import ( + ATTR_BATTERY, + ATTR_GPS, + ATTR_GPS_ACCURACY, + ATTR_LOCATION_NAME, +) from homeassistant.components.frontend import MANIFEST_JSON +from homeassistant.components.sensor import DEVICE_CLASSES as SENSOR_CLASSES from homeassistant.components.zone.const import DOMAIN as ZONE_DOMAIN from homeassistant.const import ( ATTR_DOMAIN, @@ -16,12 +27,17 @@ from homeassistant.const import ( ) from homeassistant.core import EventOrigin from homeassistant.exceptions import HomeAssistantError, ServiceNotFound, TemplateError -from homeassistant.helpers import device_registry as dr +from homeassistant.helpers import config_validation as cv, device_registry as dr from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.template import attach from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.util.decorator import Registry from .const import ( + ATTR_ALTITUDE, + ATTR_APP_DATA, + ATTR_APP_VERSION, + ATTR_COURSE, ATTR_DEVICE_ID, ATTR_DEVICE_NAME, ATTR_EVENT_DATA, @@ -29,11 +45,21 @@ from .const import ( ATTR_MANUFACTURER, ATTR_MODEL, ATTR_OS_VERSION, + ATTR_SENSOR_ATTRIBUTES, + ATTR_SENSOR_DEVICE_CLASS, + ATTR_SENSOR_ICON, + ATTR_SENSOR_NAME, + ATTR_SENSOR_STATE, ATTR_SENSOR_TYPE, + ATTR_SENSOR_TYPE_BINARY_SENSOR, + ATTR_SENSOR_TYPE_SENSOR, ATTR_SENSOR_UNIQUE_ID, + ATTR_SENSOR_UOM, + ATTR_SPEED, ATTR_SUPPORTS_ENCRYPTION, ATTR_TEMPLATE, ATTR_TEMPLATE_VARIABLES, + ATTR_VERTICAL_ACCURACY, ATTR_WEBHOOK_DATA, ATTR_WEBHOOK_ENCRYPTED, ATTR_WEBHOOK_ENCRYPTED_DATA, @@ -50,18 +76,6 @@ from .const import ( ERR_SENSOR_NOT_REGISTERED, SIGNAL_LOCATION_UPDATE, SIGNAL_SENSOR_UPDATE, - WEBHOOK_PAYLOAD_SCHEMA, - WEBHOOK_SCHEMAS, - WEBHOOK_TYPE_CALL_SERVICE, - WEBHOOK_TYPE_FIRE_EVENT, - WEBHOOK_TYPE_GET_CONFIG, - WEBHOOK_TYPE_GET_ZONES, - WEBHOOK_TYPE_REGISTER_SENSOR, - WEBHOOK_TYPE_RENDER_TEMPLATE, - WEBHOOK_TYPE_UPDATE_LOCATION, - WEBHOOK_TYPE_UPDATE_REGISTRATION, - WEBHOOK_TYPE_UPDATE_SENSOR_STATES, - WEBHOOK_TYPES, ) from .helpers import ( _decrypt_payload, @@ -76,6 +90,46 @@ from .helpers import ( _LOGGER = logging.getLogger(__name__) +WEBHOOK_COMMANDS = Registry() + +COMBINED_CLASSES = set(BINARY_SENSOR_CLASSES + SENSOR_CLASSES) +SENSOR_TYPES = [ATTR_SENSOR_TYPE_BINARY_SENSOR, ATTR_SENSOR_TYPE_SENSOR] + +WEBHOOK_PAYLOAD_SCHEMA = vol.Schema( + { + vol.Required(ATTR_WEBHOOK_TYPE): cv.string, + vol.Required(ATTR_WEBHOOK_DATA, default={}): vol.Any(dict, list), + vol.Optional(ATTR_WEBHOOK_ENCRYPTED, default=False): cv.boolean, + vol.Optional(ATTR_WEBHOOK_ENCRYPTED_DATA): cv.string, + } +) + + +def validate_schema(schema): + """Decorate a webhook function with a schema.""" + if isinstance(schema, dict): + schema = vol.Schema(schema) + + def wrapper(func): + """Wrap function so we validate schema.""" + + @wraps(func) + async def validate_and_run(hass, config_entry, data): + """Validate input and call handler.""" + try: + data = schema(data) + except vol.Invalid as ex: + err = vol.humanize.humanize_error(data, ex) + _LOGGER.error("Received invalid webhook payload: %s", err) + return empty_okay_response() + + return await func(hass, config_entry, data) + + return validate_and_run + + return wrapper + + async def handle_webhook( hass: HomeAssistantType, webhook_id: str, request: Request ) -> Response: @@ -83,12 +137,8 @@ async def handle_webhook( if webhook_id in hass.data[DOMAIN][DATA_DELETED_IDS]: return Response(status=410) - headers = {} - config_entry = hass.data[DOMAIN][DATA_CONFIG_ENTRIES][webhook_id] - registration = config_entry.data - try: req_data = await request.json() except ValueError: @@ -97,11 +147,11 @@ async def handle_webhook( if ( ATTR_WEBHOOK_ENCRYPTED not in req_data - and registration[ATTR_SUPPORTS_ENCRYPTION] + and config_entry.data[ATTR_SUPPORTS_ENCRYPTION] ): _LOGGER.warning( "Refusing to accept unencrypted webhook from %s", - registration[ATTR_DEVICE_NAME], + config_entry.data[ATTR_DEVICE_NAME], ) return error_response(ERR_ENCRYPTION_REQUIRED, "Encryption required") @@ -118,197 +168,286 @@ async def handle_webhook( if req_data[ATTR_WEBHOOK_ENCRYPTED]: enc_data = req_data[ATTR_WEBHOOK_ENCRYPTED_DATA] - webhook_payload = _decrypt_payload(registration[CONF_SECRET], enc_data) + webhook_payload = _decrypt_payload(config_entry.data[CONF_SECRET], enc_data) - if webhook_type not in WEBHOOK_TYPES: + if webhook_type not in WEBHOOK_COMMANDS: _LOGGER.error("Received invalid webhook type: %s", webhook_type) return empty_okay_response() - data = webhook_payload + _LOGGER.debug( + "Received webhook payload for type %s: %s", webhook_type, webhook_payload + ) - _LOGGER.debug("Received webhook payload for type %s: %s", webhook_type, data) + return await WEBHOOK_COMMANDS[webhook_type](hass, config_entry, webhook_payload) - if webhook_type in WEBHOOK_SCHEMAS: + +@WEBHOOK_COMMANDS.register("call_service") +@validate_schema( + { + vol.Required(ATTR_DOMAIN): cv.string, + vol.Required(ATTR_SERVICE): cv.string, + vol.Optional(ATTR_SERVICE_DATA, default={}): dict, + } +) +async def webhook_call_service(hass, config_entry, data): + """Handle a call service webhook.""" + try: + await hass.services.async_call( + data[ATTR_DOMAIN], + data[ATTR_SERVICE], + data[ATTR_SERVICE_DATA], + blocking=True, + context=registration_context(config_entry.data), + ) + except (vol.Invalid, ServiceNotFound, Exception) as ex: + _LOGGER.error( + "Error when calling service during mobile_app " + "webhook (device name: %s): %s", + config_entry.data[ATTR_DEVICE_NAME], + ex, + ) + raise HTTPBadRequest() + + return empty_okay_response() + + +@WEBHOOK_COMMANDS.register("fire_event") +@validate_schema( + { + vol.Required(ATTR_EVENT_TYPE): cv.string, + vol.Optional(ATTR_EVENT_DATA, default={}): dict, + } +) +async def webhook_fire_event(hass, config_entry, data): + """Handle a fire event webhook.""" + event_type = data[ATTR_EVENT_TYPE] + hass.bus.async_fire( + event_type, + data[ATTR_EVENT_DATA], + EventOrigin.remote, + context=registration_context(config_entry.data), + ) + return empty_okay_response() + + +@WEBHOOK_COMMANDS.register("render_template") +@validate_schema( + { + str: { + vol.Required(ATTR_TEMPLATE): cv.template, + vol.Optional(ATTR_TEMPLATE_VARIABLES, default={}): dict, + } + } +) +async def webhook_render_template(hass, config_entry, data): + """Handle a render template webhook.""" + resp = {} + for key, item in data.items(): try: - data = WEBHOOK_SCHEMAS[webhook_type](webhook_payload) - except vol.Invalid as ex: - err = vol.humanize.humanize_error(webhook_payload, ex) - _LOGGER.error("Received invalid webhook payload: %s", err) - return empty_okay_response(headers=headers) + tpl = item[ATTR_TEMPLATE] + attach(hass, tpl) + resp[key] = tpl.async_render(item.get(ATTR_TEMPLATE_VARIABLES)) + except TemplateError as ex: + resp[key] = {"error": str(ex)} - context = registration_context(registration) + return webhook_response(resp, registration=config_entry.data) - if webhook_type == WEBHOOK_TYPE_CALL_SERVICE: - try: - await hass.services.async_call( - data[ATTR_DOMAIN], - data[ATTR_SERVICE], - data[ATTR_SERVICE_DATA], - blocking=True, - context=context, + +@WEBHOOK_COMMANDS.register("update_location") +@validate_schema( + { + vol.Optional(ATTR_LOCATION_NAME): cv.string, + vol.Required(ATTR_GPS): cv.gps, + vol.Required(ATTR_GPS_ACCURACY): cv.positive_int, + vol.Optional(ATTR_BATTERY): cv.positive_int, + vol.Optional(ATTR_SPEED): cv.positive_int, + vol.Optional(ATTR_ALTITUDE): vol.Coerce(float), + vol.Optional(ATTR_COURSE): cv.positive_int, + vol.Optional(ATTR_VERTICAL_ACCURACY): cv.positive_int, + } +) +async def webhook_update_location(hass, config_entry, data): + """Handle an update location webhook.""" + hass.helpers.dispatcher.async_dispatcher_send( + SIGNAL_LOCATION_UPDATE.format(config_entry.entry_id), data + ) + return empty_okay_response() + + +@WEBHOOK_COMMANDS.register("update_registration") +@validate_schema( + { + vol.Optional(ATTR_APP_DATA, default={}): dict, + vol.Required(ATTR_APP_VERSION): cv.string, + vol.Required(ATTR_DEVICE_NAME): cv.string, + vol.Required(ATTR_MANUFACTURER): cv.string, + vol.Required(ATTR_MODEL): cv.string, + vol.Optional(ATTR_OS_VERSION): cv.string, + } +) +async def webhook_update_registration(hass, config_entry, data): + """Handle an update registration webhook.""" + new_registration = {**config_entry.data, **data} + + device_registry = await dr.async_get_registry(hass) + + device_registry.async_get_or_create( + config_entry_id=config_entry.entry_id, + identifiers={(DOMAIN, config_entry.data[ATTR_DEVICE_ID])}, + manufacturer=new_registration[ATTR_MANUFACTURER], + model=new_registration[ATTR_MODEL], + name=new_registration[ATTR_DEVICE_NAME], + sw_version=new_registration[ATTR_OS_VERSION], + ) + + hass.config_entries.async_update_entry(config_entry, data=new_registration) + + return webhook_response( + safe_registration(new_registration), registration=new_registration, + ) + + +@WEBHOOK_COMMANDS.register("register_sensor") +@validate_schema( + { + vol.Optional(ATTR_SENSOR_ATTRIBUTES, default={}): dict, + vol.Optional(ATTR_SENSOR_DEVICE_CLASS): vol.All( + vol.Lower, vol.In(COMBINED_CLASSES) + ), + vol.Required(ATTR_SENSOR_NAME): cv.string, + vol.Required(ATTR_SENSOR_TYPE): vol.In(SENSOR_TYPES), + vol.Required(ATTR_SENSOR_UNIQUE_ID): cv.string, + vol.Optional(ATTR_SENSOR_UOM): cv.string, + vol.Required(ATTR_SENSOR_STATE): vol.Any(bool, str, int, float), + vol.Optional(ATTR_SENSOR_ICON, default="mdi:cellphone"): cv.icon, + } +) +async def webhook_register_sensor(hass, config_entry, data): + """Handle a register sensor webhook.""" + entity_type = data[ATTR_SENSOR_TYPE] + + unique_id = data[ATTR_SENSOR_UNIQUE_ID] + + unique_store_key = f"{config_entry.data[CONF_WEBHOOK_ID]}_{unique_id}" + + if unique_store_key in hass.data[DOMAIN][entity_type]: + _LOGGER.error("Refusing to re-register existing sensor %s!", unique_id) + return error_response( + ERR_SENSOR_DUPLICATE_UNIQUE_ID, + f"{entity_type} {unique_id} already exists!", + status=409, + ) + + data[CONF_WEBHOOK_ID] = config_entry.data[CONF_WEBHOOK_ID] + + hass.data[DOMAIN][entity_type][unique_store_key] = data + + try: + await hass.data[DOMAIN][DATA_STORE].async_save(savable_state(hass)) + except HomeAssistantError as ex: + _LOGGER.error("Error registering sensor: %s", ex) + return empty_okay_response() + + register_signal = "{}_{}_register".format(DOMAIN, data[ATTR_SENSOR_TYPE]) + async_dispatcher_send(hass, register_signal, data) + + return webhook_response( + {"success": True}, registration=config_entry.data, status=HTTP_CREATED, + ) + + +@WEBHOOK_COMMANDS.register("update_sensor_states") +@validate_schema( + vol.All( + cv.ensure_list, + [ + vol.Schema( + { + vol.Optional(ATTR_SENSOR_ATTRIBUTES, default={}): dict, + vol.Optional(ATTR_SENSOR_ICON, default="mdi:cellphone"): cv.icon, + vol.Required(ATTR_SENSOR_STATE): vol.Any(bool, str, int, float), + vol.Required(ATTR_SENSOR_TYPE): vol.In(SENSOR_TYPES), + vol.Required(ATTR_SENSOR_UNIQUE_ID): cv.string, + } ) - except (vol.Invalid, ServiceNotFound, Exception) as ex: + ], + ) +) +async def webhook_update_sensor_states(hass, config_entry, data): + """Handle an update sensor states webhook.""" + resp = {} + for sensor in data: + entity_type = sensor[ATTR_SENSOR_TYPE] + + unique_id = sensor[ATTR_SENSOR_UNIQUE_ID] + + unique_store_key = f"{config_entry.data[CONF_WEBHOOK_ID]}_{unique_id}" + + if unique_store_key not in hass.data[DOMAIN][entity_type]: _LOGGER.error( - "Error when calling service during mobile_app " - "webhook (device name: %s): %s", - registration[ATTR_DEVICE_NAME], - ex, + "Refusing to update non-registered sensor: %s", unique_store_key ) - raise HTTPBadRequest() + err_msg = f"{entity_type} {unique_id} is not registered" + resp[unique_id] = { + "success": False, + "error": {"code": ERR_SENSOR_NOT_REGISTERED, "message": err_msg}, + } + continue - return empty_okay_response(headers=headers) + entry = hass.data[DOMAIN][entity_type][unique_store_key] - if webhook_type == WEBHOOK_TYPE_FIRE_EVENT: - event_type = data[ATTR_EVENT_TYPE] - hass.bus.async_fire( - event_type, data[ATTR_EVENT_DATA], EventOrigin.remote, context=context - ) - return empty_okay_response(headers=headers) + new_state = {**entry, **sensor} - if webhook_type == WEBHOOK_TYPE_RENDER_TEMPLATE: - resp = {} - for key, item in data.items(): - try: - tpl = item[ATTR_TEMPLATE] - attach(hass, tpl) - resp[key] = tpl.async_render(item.get(ATTR_TEMPLATE_VARIABLES)) - except TemplateError as ex: - resp[key] = {"error": str(ex)} + hass.data[DOMAIN][entity_type][unique_store_key] = new_state - return webhook_response(resp, registration=registration, headers=headers) - - if webhook_type == WEBHOOK_TYPE_UPDATE_LOCATION: - hass.helpers.dispatcher.async_dispatcher_send( - SIGNAL_LOCATION_UPDATE.format(config_entry.entry_id), data - ) - return empty_okay_response(headers=headers) - - if webhook_type == WEBHOOK_TYPE_UPDATE_REGISTRATION: - new_registration = {**registration, **data} - - device_registry = await dr.async_get_registry(hass) - - device_registry.async_get_or_create( - config_entry_id=config_entry.entry_id, - identifiers={(DOMAIN, registration[ATTR_DEVICE_ID])}, - manufacturer=new_registration[ATTR_MANUFACTURER], - model=new_registration[ATTR_MODEL], - name=new_registration[ATTR_DEVICE_NAME], - sw_version=new_registration[ATTR_OS_VERSION], - ) - - hass.config_entries.async_update_entry(config_entry, data=new_registration) - - return webhook_response( - safe_registration(new_registration), - registration=registration, - headers=headers, - ) - - if webhook_type == WEBHOOK_TYPE_REGISTER_SENSOR: - entity_type = data[ATTR_SENSOR_TYPE] - - unique_id = data[ATTR_SENSOR_UNIQUE_ID] - - unique_store_key = f"{webhook_id}_{unique_id}" - - if unique_store_key in hass.data[DOMAIN][entity_type]: - _LOGGER.error("Refusing to re-register existing sensor %s!", unique_id) - return error_response( - ERR_SENSOR_DUPLICATE_UNIQUE_ID, - f"{entity_type} {unique_id} already exists!", - status=409, - ) - - data[CONF_WEBHOOK_ID] = webhook_id - - hass.data[DOMAIN][entity_type][unique_store_key] = data + safe = savable_state(hass) try: - await hass.data[DOMAIN][DATA_STORE].async_save(savable_state(hass)) + await hass.data[DOMAIN][DATA_STORE].async_save(safe) except HomeAssistantError as ex: - _LOGGER.error("Error registering sensor: %s", ex) + _LOGGER.error("Error updating mobile_app registration: %s", ex) return empty_okay_response() - register_signal = "{}_{}_register".format(DOMAIN, data[ATTR_SENSOR_TYPE]) - async_dispatcher_send(hass, register_signal, data) + async_dispatcher_send(hass, SIGNAL_SENSOR_UPDATE, new_state) - return webhook_response( - {"success": True}, - registration=registration, - status=HTTP_CREATED, - headers=headers, - ) + resp[unique_id] = {"success": True} - if webhook_type == WEBHOOK_TYPE_UPDATE_SENSOR_STATES: - resp = {} - for sensor in data: - entity_type = sensor[ATTR_SENSOR_TYPE] + return webhook_response(resp, registration=config_entry.data) - unique_id = sensor[ATTR_SENSOR_UNIQUE_ID] - unique_store_key = f"{webhook_id}_{unique_id}" +@WEBHOOK_COMMANDS.register("get_zones") +async def webhook_get_zones(hass, config_entry, data): + """Handle a get zones webhook.""" + zones = [ + hass.states.get(entity_id) + for entity_id in sorted(hass.states.async_entity_ids(ZONE_DOMAIN)) + ] + return webhook_response(zones, registration=config_entry.data) - if unique_store_key not in hass.data[DOMAIN][entity_type]: - _LOGGER.error( - "Refusing to update non-registered sensor: %s", unique_store_key - ) - err_msg = f"{entity_type} {unique_id} is not registered" - resp[unique_id] = { - "success": False, - "error": {"code": ERR_SENSOR_NOT_REGISTERED, "message": err_msg}, - } - continue - entry = hass.data[DOMAIN][entity_type][unique_store_key] +@WEBHOOK_COMMANDS.register("get_config") +async def webhook_get_config(hass, config_entry, data): + """Handle a get config webhook.""" + hass_config = hass.config.as_dict() - new_state = {**entry, **sensor} + resp = { + "latitude": hass_config["latitude"], + "longitude": hass_config["longitude"], + "elevation": hass_config["elevation"], + "unit_system": hass_config["unit_system"], + "location_name": hass_config["location_name"], + "time_zone": hass_config["time_zone"], + "components": hass_config["components"], + "version": hass_config["version"], + "theme_color": MANIFEST_JSON["theme_color"], + } - hass.data[DOMAIN][entity_type][unique_store_key] = new_state + if CONF_CLOUDHOOK_URL in config_entry.data: + resp[CONF_CLOUDHOOK_URL] = config_entry.data[CONF_CLOUDHOOK_URL] - safe = savable_state(hass) + try: + resp[CONF_REMOTE_UI_URL] = hass.components.cloud.async_remote_ui_url() + except hass.components.cloud.CloudNotAvailable: + pass - try: - await hass.data[DOMAIN][DATA_STORE].async_save(safe) - except HomeAssistantError as ex: - _LOGGER.error("Error updating mobile_app registration: %s", ex) - return empty_okay_response() - - async_dispatcher_send(hass, SIGNAL_SENSOR_UPDATE, new_state) - - resp[unique_id] = {"success": True} - - return webhook_response(resp, registration=registration, headers=headers) - - if webhook_type == WEBHOOK_TYPE_GET_ZONES: - zones = ( - hass.states.get(entity_id) - for entity_id in sorted(hass.states.async_entity_ids(ZONE_DOMAIN)) - ) - return webhook_response(list(zones), registration=registration, headers=headers) - - if webhook_type == WEBHOOK_TYPE_GET_CONFIG: - - hass_config = hass.config.as_dict() - - resp = { - "latitude": hass_config["latitude"], - "longitude": hass_config["longitude"], - "elevation": hass_config["elevation"], - "unit_system": hass_config["unit_system"], - "location_name": hass_config["location_name"], - "time_zone": hass_config["time_zone"], - "components": hass_config["components"], - "version": hass_config["version"], - "theme_color": MANIFEST_JSON["theme_color"], - } - - if CONF_CLOUDHOOK_URL in registration: - resp[CONF_CLOUDHOOK_URL] = registration[CONF_CLOUDHOOK_URL] - - try: - resp[CONF_REMOTE_UI_URL] = hass.components.cloud.async_remote_ui_url() - except hass.components.cloud.CloudNotAvailable: - pass - - return webhook_response(resp, registration=registration, headers=headers) + return webhook_response(resp, registration=config_entry.data) diff --git a/script/hassfest/dependencies.py b/script/hassfest/dependencies.py index cb58de3af76..8500e9d897d 100644 --- a/script/hassfest/dependencies.py +++ b/script/hassfest/dependencies.py @@ -130,6 +130,7 @@ IGNORE_VIOLATIONS = [ "prometheus", "conversation", "logbook", + "mobile_app", # These should be extracted to external package "pvoutput", "dwd_weather_warnings", From 8e3dfbd5c98fe9d9644bc82734cab4177e236578 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Sat, 21 Dec 2019 17:15:50 -0500 Subject: [PATCH 365/677] Refactor ZHA electrical measurement sensor. (#30130) --- .../zha/core/channels/homeautomation.py | 38 +++++++++++++++++++ homeassistant/components/zha/sensor.py | 4 ++ tests/components/zha/test_sensor.py | 2 +- 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zha/core/channels/homeautomation.py b/homeassistant/components/zha/core/channels/homeautomation.py index dda6c1f4c13..d9d8f57eaaf 100644 --- a/homeassistant/components/zha/core/channels/homeautomation.py +++ b/homeassistant/components/zha/core/channels/homeautomation.py @@ -5,6 +5,7 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/integrations/zha/ """ import logging +from typing import Optional import zigpy.zcl.clusters.homeautomation as homeautomation @@ -65,6 +66,12 @@ class ElectricalMeasurementChannel(AttributeListeningChannel): REPORT_CONFIG = ({"attr": "active_power", "config": REPORT_CONFIG_DEFAULT},) + def __init__(self, cluster, device): + """Initialize Metering.""" + super().__init__(cluster, device) + self._divisor = None + self._multiplier = None + async def async_update(self): """Retrieve latest state.""" self.debug("async_update") @@ -78,8 +85,39 @@ class ElectricalMeasurementChannel(AttributeListeningChannel): async def async_initialize(self, from_cache): """Initialize channel.""" await self.get_attribute_value("active_power", from_cache=from_cache) + await self.fetch_config(from_cache) await super().async_initialize(from_cache) + async def fetch_config(self, from_cache): + """Fetch config from device and updates format specifier.""" + divisor = await self.get_attribute_value( + "ac_power_divisor", from_cache=from_cache + ) + if divisor is None: + divisor = await self.get_attribute_value( + "power_divisor", from_cache=from_cache + ) + self._divisor = divisor + + mult = await self.get_attribute_value( + "ac_power_multiplier", from_cache=from_cache + ) + if mult is None: + mult = await self.get_attribute_value( + "power_multiplier", from_cache=from_cache + ) + self._multiplier = mult + + @property + def divisor(self) -> Optional[int]: + """Return active power divisor.""" + return self._divisor or 1 + + @property + def multiplier(self) -> Optional[int]: + """Return active power divisor.""" + return self._multiplier or 1 + @registries.ZIGBEE_CHANNEL_REGISTRY.register( homeautomation.MeterIdentification.cluster_id diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index 133e82e6914..e9d21be6132 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -217,6 +217,10 @@ class ElectricalMeasurement(Sensor): """Return True if HA needs to poll for state changes.""" return True + def formatter(self, value) -> int: + """Return 'normalized' value.""" + return round(value * self._channel.multiplier / self._channel.divisor) + @STRICT_MATCH(MatchRule(generic_ids={CHANNEL_ST_HUMIDITY_CLUSTER})) @STRICT_MATCH(MatchRule(channel_names={CHANNEL_HUMIDITY})) diff --git a/tests/components/zha/test_sensor.py b/tests/components/zha/test_sensor.py index 7746c5d422e..4fa16f06b04 100644 --- a/tests/components/zha/test_sensor.py +++ b/tests/components/zha/test_sensor.py @@ -166,7 +166,7 @@ async def async_test_metering(hass, device_info): async def async_test_electrical_measurement(hass, device_info): """Test electrical measurement sensor.""" await send_attribute_report(hass, device_info["cluster"], 1291, 100) - assert_state(hass, device_info, "10.0", "W") + assert_state(hass, device_info, "100", "W") async def send_attribute_report(hass, cluster, attrid, value): From 9c23c4adf207bd66bebce3a55e141c6821507d28 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Sat, 21 Dec 2019 18:33:00 -0500 Subject: [PATCH 366/677] Cleanup ZHAEntity class (#30131) Remove `_domain` attribute since we're not using it anymore. --- homeassistant/components/zha/binary_sensor.py | 1 - homeassistant/components/zha/entity.py | 2 -- homeassistant/components/zha/fan.py | 2 -- homeassistant/components/zha/light.py | 2 -- homeassistant/components/zha/lock.py | 2 -- homeassistant/components/zha/sensor.py | 1 - homeassistant/components/zha/switch.py | 2 -- 7 files changed, 12 deletions(-) diff --git a/homeassistant/components/zha/binary_sensor.py b/homeassistant/components/zha/binary_sensor.py index e6176fe9da3..df95d408398 100644 --- a/homeassistant/components/zha/binary_sensor.py +++ b/homeassistant/components/zha/binary_sensor.py @@ -102,7 +102,6 @@ async def _async_setup_entities( class BinarySensor(ZhaEntity, BinarySensorDevice): """ZHA BinarySensor.""" - _domain = DOMAIN _device_class = None def __init__(self, **kwargs): diff --git a/homeassistant/components/zha/entity.py b/homeassistant/components/zha/entity.py index 108d8e27a9f..102472d25b0 100644 --- a/homeassistant/components/zha/entity.py +++ b/homeassistant/components/zha/entity.py @@ -30,8 +30,6 @@ RESTART_GRACE_PERIOD = 7200 # 2 hours class ZhaEntity(RestoreEntity, LogMixin, entity.Entity): """A base class for ZHA entities.""" - _domain = None # Must be overridden by subclasses - def __init__(self, unique_id, zha_device, channels, skip_entity_id=False, **kwargs): """Init ZHA entity.""" self._force_update = False diff --git a/homeassistant/components/zha/fan.py b/homeassistant/components/zha/fan.py index 43ad2291cb7..bccdf260a11 100644 --- a/homeassistant/components/zha/fan.py +++ b/homeassistant/components/zha/fan.py @@ -87,8 +87,6 @@ async def _async_setup_entities( class ZhaFan(ZhaEntity, FanEntity): """Representation of a ZHA fan.""" - _domain = DOMAIN - def __init__(self, unique_id, zha_device, channels, **kwargs): """Init this sensor.""" super().__init__(unique_id, zha_device, channels, **kwargs) diff --git a/homeassistant/components/zha/light.py b/homeassistant/components/zha/light.py index fb388afac0f..08d74f9fdb3 100644 --- a/homeassistant/components/zha/light.py +++ b/homeassistant/components/zha/light.py @@ -80,8 +80,6 @@ async def _async_setup_entities( class Light(ZhaEntity, light.Light): """Representation of a ZHA or ZLL light.""" - _domain = light.DOMAIN - def __init__(self, unique_id, zha_device, channels, **kwargs): """Initialize the ZHA light.""" super().__init__(unique_id, zha_device, channels, **kwargs) diff --git a/homeassistant/components/zha/lock.py b/homeassistant/components/zha/lock.py index a2151b4bdcb..2458bf4be5b 100644 --- a/homeassistant/components/zha/lock.py +++ b/homeassistant/components/zha/lock.py @@ -70,8 +70,6 @@ async def _async_setup_entities( class ZhaDoorLock(ZhaEntity, LockDevice): """Representation of a ZHA lock.""" - _domain = DOMAIN - def __init__(self, unique_id, zha_device, channels, **kwargs): """Init this sensor.""" super().__init__(unique_id, zha_device, channels, **kwargs) diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index e9d21be6132..26dc25c71dc 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -109,7 +109,6 @@ class Sensor(ZhaEntity): _decimals = 1 _device_class = None _divisor = 1 - _domain = DOMAIN _multiplier = 1 _unit = None diff --git a/homeassistant/components/zha/switch.py b/homeassistant/components/zha/switch.py index bfe816d614a..03296e8a553 100644 --- a/homeassistant/components/zha/switch.py +++ b/homeassistant/components/zha/switch.py @@ -60,8 +60,6 @@ async def _async_setup_entities( class Switch(ZhaEntity, SwitchDevice): """ZHA switch.""" - _domain = DOMAIN - def __init__(self, **kwargs): """Initialize the ZHA switch.""" super().__init__(**kwargs) From 251808874f015bcd96e9f747313e1f7657ca79fb Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Sun, 22 Dec 2019 10:23:44 +0100 Subject: [PATCH 367/677] Move imports into setup function in homekit __init__.py (#30137) --- homeassistant/components/homekit/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 9a2b2d5a851..ea2c466092e 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -29,7 +29,6 @@ from homeassistant.helpers.entityfilter import FILTER_SCHEMA from homeassistant.util import get_local_ip from homeassistant.util.decorator import Registry -from .accessories import HomeBridge, HomeDriver from .const import ( BRIDGE_NAME, CONF_ADVERTISE_IP, @@ -303,6 +302,8 @@ class HomeKit: def setup(self): """Set up bridge and accessory driver.""" + # pylint: disable=import-outside-toplevel + from .accessories import HomeBridge, HomeDriver self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.stop) From 63a843c19c9a536316d55f880225091ab54b082b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 22 Dec 2019 10:31:23 +0100 Subject: [PATCH 368/677] Fix test --- tests/components/homekit/test_homekit.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index c845370e278..de6aaf0f11e 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -126,7 +126,7 @@ async def test_homekit_setup(hass, hk_driver): homekit = HomeKit(hass, BRIDGE_NAME, DEFAULT_PORT, None, {}, {}, DEFAULT_SAFE_MODE) with patch( - PATH_HOMEKIT + ".HomeDriver", return_value=hk_driver + PATH_HOMEKIT + ".accessories.HomeDriver", return_value=hk_driver ) as mock_driver, patch("homeassistant.util.get_local_ip") as mock_ip: mock_ip.return_value = IP_ADDRESS await hass.async_add_job(homekit.setup) @@ -150,7 +150,9 @@ async def test_homekit_setup_ip_address(hass, hk_driver): """Test setup with given IP address.""" homekit = HomeKit(hass, BRIDGE_NAME, DEFAULT_PORT, "172.0.0.0", {}, {}, None) - with patch(PATH_HOMEKIT + ".HomeDriver", return_value=hk_driver) as mock_driver: + with patch( + PATH_HOMEKIT + ".accessories.HomeDriver", return_value=hk_driver + ) as mock_driver: await hass.async_add_job(homekit.setup) mock_driver.assert_called_with( hass, @@ -167,7 +169,9 @@ async def test_homekit_setup_advertise_ip(hass, hk_driver): hass, BRIDGE_NAME, DEFAULT_PORT, "0.0.0.0", {}, {}, None, "192.168.1.100" ) - with patch(PATH_HOMEKIT + ".HomeDriver", return_value=hk_driver) as mock_driver: + with patch( + PATH_HOMEKIT + ".accessories.HomeDriver", return_value=hk_driver + ) as mock_driver: await hass.async_add_job(homekit.setup) mock_driver.assert_called_with( hass, @@ -182,7 +186,7 @@ async def test_homekit_setup_safe_mode(hass, hk_driver): """Test if safe_mode flag is set.""" homekit = HomeKit(hass, BRIDGE_NAME, DEFAULT_PORT, None, {}, {}, True) - with patch(PATH_HOMEKIT + ".HomeDriver", return_value=hk_driver): + with patch(PATH_HOMEKIT + ".accessories.HomeDriver", return_value=hk_driver): await hass.async_add_job(homekit.setup) assert homekit.driver.safe_mode is True From 32aae7017ecafd58808a1740abaac955b199e92d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 22 Dec 2019 10:32:42 +0100 Subject: [PATCH 369/677] Remove requirement from entity integration (#30113) --- .../components/doods/image_processing.py | 2 +- homeassistant/components/doods/manifest.json | 14 +++--- .../components/image_processing/__init__.py | 42 ----------------- .../components/image_processing/manifest.json | 8 +--- .../components/seven_segments/manifest.json | 2 +- .../components/tensorflow/image_processing.py | 2 +- .../components/tensorflow/manifest.json | 7 ++- homeassistant/util/pil.py | 47 +++++++++++++++++++ requirements_all.txt | 4 +- requirements_test_all.txt | 5 -- 10 files changed, 67 insertions(+), 66 deletions(-) create mode 100644 homeassistant/util/pil.py diff --git a/homeassistant/components/doods/image_processing.py b/homeassistant/components/doods/image_processing.py index 5e4d211d497..9525f9e8ddf 100644 --- a/homeassistant/components/doods/image_processing.py +++ b/homeassistant/components/doods/image_processing.py @@ -14,12 +14,12 @@ from homeassistant.components.image_processing import ( CONF_SOURCE, PLATFORM_SCHEMA, ImageProcessingEntity, - draw_box, ) from homeassistant.const import CONF_TIMEOUT from homeassistant.core import split_entity_id from homeassistant.helpers import template import homeassistant.helpers.config_validation as cv +from homeassistant.util.pil import draw_box _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/doods/manifest.json b/homeassistant/components/doods/manifest.json index e0dcb48527f..551af839b5c 100644 --- a/homeassistant/components/doods/manifest.json +++ b/homeassistant/components/doods/manifest.json @@ -1,10 +1,8 @@ { - "domain": "doods", - "name": "DOODS - Distributed Outside Object Detection Service", - "documentation": "https://www.home-assistant.io/integrations/doods", - "requirements": [ - "pydoods==1.0.2" - ], - "dependencies": [], - "codeowners": [] + "domain": "doods", + "name": "DOODS - Distributed Outside Object Detection Service", + "documentation": "https://www.home-assistant.io/integrations/doods", + "requirements": ["pydoods==1.0.2", "pillow==6.2.1"], + "dependencies": [], + "codeowners": [] } diff --git a/homeassistant/components/image_processing/__init__.py b/homeassistant/components/image_processing/__init__.py index 78ae15eb537..a8f5f0f097e 100644 --- a/homeassistant/components/image_processing/__init__.py +++ b/homeassistant/components/image_processing/__init__.py @@ -2,9 +2,7 @@ import asyncio from datetime import timedelta import logging -from typing import Tuple -from PIL import ImageDraw import voluptuous as vol from homeassistant.const import ATTR_ENTITY_ID, ATTR_NAME, CONF_ENTITY_ID, CONF_NAME @@ -65,46 +63,6 @@ PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend( PLATFORM_SCHEMA_BASE = cv.PLATFORM_SCHEMA_BASE.extend(PLATFORM_SCHEMA.schema) -def draw_box( - draw: ImageDraw, - box: Tuple[float, float, float, float], - img_width: int, - img_height: int, - text: str = "", - color: Tuple[int, int, int] = (255, 255, 0), -) -> None: - """ - Draw a bounding box on and image. - - The bounding box is defined by the tuple (y_min, x_min, y_max, x_max) - where the coordinates are floats in the range [0.0, 1.0] and - relative to the width and height of the image. - - For example, if an image is 100 x 200 pixels (height x width) and the bounding - box is `(0.1, 0.2, 0.5, 0.9)`, the upper-left and bottom-right coordinates of - the bounding box will be `(40, 10)` to `(180, 50)` (in (x,y) coordinates). - """ - - line_width = 3 - font_height = 8 - y_min, x_min, y_max, x_max = box - (left, right, top, bottom) = ( - x_min * img_width, - x_max * img_width, - y_min * img_height, - y_max * img_height, - ) - draw.line( - [(left, top), (left, bottom), (right, bottom), (right, top), (left, top)], - width=line_width, - fill=color, - ) - if text: - draw.text( - (left + line_width, abs(top - line_width - font_height)), text, fill=color - ) - - async def async_setup(hass, config): """Set up the image processing.""" component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL) diff --git a/homeassistant/components/image_processing/manifest.json b/homeassistant/components/image_processing/manifest.json index e986ac6f4ca..1e9f2963a38 100644 --- a/homeassistant/components/image_processing/manifest.json +++ b/homeassistant/components/image_processing/manifest.json @@ -2,11 +2,7 @@ "domain": "image_processing", "name": "Image processing", "documentation": "https://www.home-assistant.io/integrations/image_processing", - "requirements": [ - "pillow==6.2.1" - ], - "dependencies": [ - "camera" - ], + "requirements": [], + "dependencies": ["camera"], "codeowners": [] } diff --git a/homeassistant/components/seven_segments/manifest.json b/homeassistant/components/seven_segments/manifest.json index 0bf49d4b906..6e375da0322 100644 --- a/homeassistant/components/seven_segments/manifest.json +++ b/homeassistant/components/seven_segments/manifest.json @@ -2,7 +2,7 @@ "domain": "seven_segments", "name": "Seven segments", "documentation": "https://www.home-assistant.io/integrations/seven_segments", - "requirements": [], + "requirements": ["pillow==6.2.1"], "dependencies": [], "codeowners": [] } diff --git a/homeassistant/components/tensorflow/image_processing.py b/homeassistant/components/tensorflow/image_processing.py index 5f576f176e8..dee2a021829 100644 --- a/homeassistant/components/tensorflow/image_processing.py +++ b/homeassistant/components/tensorflow/image_processing.py @@ -15,11 +15,11 @@ from homeassistant.components.image_processing import ( CONF_SOURCE, PLATFORM_SCHEMA, ImageProcessingEntity, - draw_box, ) from homeassistant.core import split_entity_id from homeassistant.helpers import template import homeassistant.helpers.config_validation as cv +from homeassistant.util.pil import draw_box _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/tensorflow/manifest.json b/homeassistant/components/tensorflow/manifest.json index 278a92a481f..627fa8d6bd6 100644 --- a/homeassistant/components/tensorflow/manifest.json +++ b/homeassistant/components/tensorflow/manifest.json @@ -2,7 +2,12 @@ "domain": "tensorflow", "name": "Tensorflow", "documentation": "https://www.home-assistant.io/integrations/tensorflow", - "requirements": ["tensorflow==1.13.2", "numpy==1.17.4", "protobuf==3.6.1"], + "requirements": [ + "tensorflow==1.13.2", + "numpy==1.17.4", + "protobuf==3.6.1", + "pillow==6.2.1" + ], "dependencies": [], "codeowners": [] } diff --git a/homeassistant/util/pil.py b/homeassistant/util/pil.py new file mode 100644 index 00000000000..80c11c9c410 --- /dev/null +++ b/homeassistant/util/pil.py @@ -0,0 +1,47 @@ +"""PIL utilities. + +Can only be used by integrations that have pillow in their requirements. +""" +from typing import Tuple + +from PIL import ImageDraw + + +def draw_box( + draw: ImageDraw, + box: Tuple[float, float, float, float], + img_width: int, + img_height: int, + text: str = "", + color: Tuple[int, int, int] = (255, 255, 0), +) -> None: + """ + Draw a bounding box on and image. + + The bounding box is defined by the tuple (y_min, x_min, y_max, x_max) + where the coordinates are floats in the range [0.0, 1.0] and + relative to the width and height of the image. + + For example, if an image is 100 x 200 pixels (height x width) and the bounding + box is `(0.1, 0.2, 0.5, 0.9)`, the upper-left and bottom-right coordinates of + the bounding box will be `(40, 10)` to `(180, 50)` (in (x,y) coordinates). + """ + + line_width = 3 + font_height = 8 + y_min, x_min, y_max, x_max = box + (left, right, top, bottom) = ( + x_min * img_width, + x_max * img_width, + y_min * img_height, + y_max * img_height, + ) + draw.line( + [(left, top), (left, bottom), (right, bottom), (right, top), (left, top)], + width=line_width, + fill=color, + ) + if text: + draw.text( + (left + line_width, abs(top - line_width - font_height)), text, fill=color + ) diff --git a/requirements_all.txt b/requirements_all.txt index 822e92ef759..19b97e2521f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -982,9 +982,11 @@ piglow==1.2.4 # homeassistant.components.pilight pilight==0.1.1 -# homeassistant.components.image_processing +# homeassistant.components.doods # homeassistant.components.proxy # homeassistant.components.qrcode +# homeassistant.components.seven_segments +# homeassistant.components.tensorflow pillow==6.2.1 # homeassistant.components.dominos diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a7f443e0ccf..ab84629f12e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -323,11 +323,6 @@ pexpect==4.6.0 # homeassistant.components.pilight pilight==0.1.1 -# homeassistant.components.image_processing -# homeassistant.components.proxy -# homeassistant.components.qrcode -pillow==6.2.1 - # homeassistant.components.plex plexapi==3.3.0 From ed0ee3100d56c54f4e93f94e4e3f6bdfbb62e20c Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 22 Dec 2019 12:01:22 +0100 Subject: [PATCH 370/677] Upgrade zeroconf to 0.24.2 (#30140) --- homeassistant/components/zeroconf/__init__.py | 3 +-- homeassistant/components/zeroconf/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index 18ea1fdfca7..d6be4cdf6a0 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -1,5 +1,4 @@ """Support for exposing Home Assistant via Zeroconf.""" - import ipaddress import logging import socket @@ -15,6 +14,7 @@ from zeroconf import ( from homeassistant import util from homeassistant.const import ( + ATTR_NAME, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, __version__, @@ -29,7 +29,6 @@ ATTR_HOST = "host" ATTR_PORT = "port" ATTR_HOSTNAME = "hostname" ATTR_TYPE = "type" -ATTR_NAME = "name" ATTR_PROPERTIES = "properties" ZEROCONF_TYPE = "_home-assistant._tcp.local." diff --git a/homeassistant/components/zeroconf/manifest.json b/homeassistant/components/zeroconf/manifest.json index c02ac425445..1dc1618842d 100644 --- a/homeassistant/components/zeroconf/manifest.json +++ b/homeassistant/components/zeroconf/manifest.json @@ -3,7 +3,7 @@ "name": "Zeroconf", "documentation": "https://www.home-assistant.io/integrations/zeroconf", "requirements": [ - "zeroconf==0.24.1" + "zeroconf==0.24.2" ], "dependencies": [ "api" diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index bb212b3e0b4..ce6ad36dbf0 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -24,7 +24,7 @@ ruamel.yaml==0.15.100 sqlalchemy==1.3.11 voluptuous-serialize==2.3.0 voluptuous==0.11.7 -zeroconf==0.24.1 +zeroconf==0.24.2 pycryptodome>=3.6.6 diff --git a/requirements_all.txt b/requirements_all.txt index 19b97e2521f..6919b103117 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2088,7 +2088,7 @@ youtube_dl==2019.11.28 zengge==0.2 # homeassistant.components.zeroconf -zeroconf==0.24.1 +zeroconf==0.24.2 # homeassistant.components.zha zha-quirks==0.0.28 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ab84629f12e..0cc01b5f47f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -653,7 +653,7 @@ ya_ma==0.3.8 yahooweather==0.10 # homeassistant.components.zeroconf -zeroconf==0.24.1 +zeroconf==0.24.2 # homeassistant.components.zha zha-quirks==0.0.28 From 83768be8147139b43b5f43665f17a6f1ed301a94 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Sun, 22 Dec 2019 13:24:57 -0500 Subject: [PATCH 371/677] Refactor ZHA binary_sensor (#30138) * Refactor ZHA binary_sensor. Use ZHA entity class registry for channel specific implementations. * Remove registries.BINARY_SENSOR_TYPES dict. * Address PR comments. --- homeassistant/components/zha/binary_sensor.py | 124 ++++++++++-------- homeassistant/components/zha/core/const.py | 1 + .../components/zha/core/discovery.py | 25 +--- .../components/zha/core/registries.py | 21 +-- 4 files changed, 69 insertions(+), 102 deletions(-) diff --git a/homeassistant/components/zha/binary_sensor.py b/homeassistant/components/zha/binary_sensor.py index df95d408398..ed09a190adb 100644 --- a/homeassistant/components/zha/binary_sensor.py +++ b/homeassistant/components/zha/binary_sensor.py @@ -1,4 +1,5 @@ """Binary sensors on Zigbee Home Automation networks.""" +import functools import logging from homeassistant.components.binary_sensor import ( @@ -18,20 +19,16 @@ from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from .core.const import ( + CHANNEL_ACCELEROMETER, CHANNEL_OCCUPANCY, CHANNEL_ON_OFF, CHANNEL_ZONE, DATA_ZHA, DATA_ZHA_DISPATCHERS, - SENSOR_ACCELERATION, - SENSOR_OCCUPANCY, - SENSOR_OPENING, - SENSOR_TYPE, SIGNAL_ATTR_UPDATED, - UNKNOWN, ZHA_DISCOVERY_NEW, - ZONE, ) +from .core.registries import ZHA_ENTITIES, MatchRule from .entity import ZhaEntity _LOGGER = logging.getLogger(__name__) @@ -46,19 +43,13 @@ CLASS_MAPPING = { 0x002D: DEVICE_CLASS_VIBRATION, } - -async def get_ias_device_class(channel): - """Get the HA device class from the channel.""" - zone_type = await channel.get_attribute_value("zone_type") - return CLASS_MAPPING.get(zone_type) +STRICT_MATCH = functools.partial(ZHA_ENTITIES.strict_match, DOMAIN) DEVICE_CLASS_REGISTRY = { - UNKNOWN: None, - SENSOR_OPENING: DEVICE_CLASS_OPENING, - ZONE: get_ias_device_class, - SENSOR_OCCUPANCY: DEVICE_CLASS_OCCUPANCY, - SENSOR_ACCELERATION: DEVICE_CLASS_MOVING, + CHANNEL_ACCELEROMETER: DEVICE_CLASS_MOVING, + CHANNEL_OCCUPANCY: DEVICE_CLASS_OCCUPANCY, + CHANNEL_ON_OFF: DEVICE_CLASS_OPENING, } @@ -94,7 +85,12 @@ async def _async_setup_entities( """Set up the ZHA binary sensors.""" entities = [] for discovery_info in discovery_infos: - entities.append(BinarySensor(**discovery_info)) + zha_dev = discovery_info["zha_device"] + channels = discovery_info["channels"] + + entity = ZHA_ENTITIES.get_entity(DOMAIN, zha_dev, channels, BinarySensor) + if entity: + entities.append(entity(**discovery_info)) async_add_entities(entities, update_before_add=True) @@ -102,43 +98,25 @@ async def _async_setup_entities( class BinarySensor(ZhaEntity, BinarySensorDevice): """ZHA BinarySensor.""" - _device_class = None + DEVICE_CLASS = None - def __init__(self, **kwargs): + def __init__(self, unique_id, zha_device, channels, **kwargs): """Initialize the ZHA binary sensor.""" - super().__init__(**kwargs) - self._device_state_attributes = {} - self._zone_channel = self.cluster_channels.get(CHANNEL_ZONE) - self._on_off_channel = self.cluster_channels.get(CHANNEL_ON_OFF) - self._attr_channel = self.cluster_channels.get(CHANNEL_OCCUPANCY) - self._zha_sensor_type = kwargs[SENSOR_TYPE] + super().__init__(unique_id, zha_device, channels, **kwargs) + self._channel = channels[0] + self._device_class = self.DEVICE_CLASS - async def _determine_device_class(self): - """Determine the device class for this binary sensor.""" - device_class_supplier = DEVICE_CLASS_REGISTRY.get(self._zha_sensor_type) - if callable(device_class_supplier): - channel = self.cluster_channels.get(self._zha_sensor_type) - if channel is None: - return None - return await device_class_supplier(channel) - return device_class_supplier + async def get_device_class(self): + """Get the HA device class from the channel.""" + pass async def async_added_to_hass(self): """Run when about to be added to hass.""" - self._device_class = await self._determine_device_class() await super().async_added_to_hass() - if self._on_off_channel: - await self.async_accept_signal( - self._on_off_channel, SIGNAL_ATTR_UPDATED, self.async_set_state - ) - if self._zone_channel: - await self.async_accept_signal( - self._zone_channel, SIGNAL_ATTR_UPDATED, self.async_set_state - ) - if self._attr_channel: - await self.async_accept_signal( - self._attr_channel, SIGNAL_ATTR_UPDATED, self.async_set_state - ) + await self.get_device_class() + await self.async_accept_signal( + self._channel, SIGNAL_ATTR_UPDATED, self.async_set_state + ) @callback def async_restore_last_state(self, last_state): @@ -148,7 +126,7 @@ class BinarySensor(ZhaEntity, BinarySensorDevice): @property def is_on(self) -> bool: - """Return if the switch is on based on the statemachine.""" + """Return True if the switch is on based on the state machine.""" if self._state is None: return False return self._state @@ -166,13 +144,43 @@ class BinarySensor(ZhaEntity, BinarySensorDevice): async def async_update(self): """Attempt to retrieve on off state from the binary sensor.""" await super().async_update() - if self._on_off_channel: - self._state = await self._on_off_channel.get_attribute_value("on_off") - if self._zone_channel: - value = await self._zone_channel.get_attribute_value("zone_status") - if value is not None: - self._state = value & 3 - if self._attr_channel: - self._state = await self._attr_channel.get_attribute_value( - self._attr_channel.value_attribute - ) + attribute = getattr(self._channel, "value_attribute", "on_off") + self._state = await self._channel.get_attribute_value(attribute) + + +@STRICT_MATCH(MatchRule(channel_names={CHANNEL_ACCELEROMETER})) +class Accelerometer(BinarySensor): + """ZHA BinarySensor.""" + + DEVICE_CLASS = DEVICE_CLASS_MOTION + + +@STRICT_MATCH(MatchRule(channel_names={CHANNEL_OCCUPANCY})) +class Occupancy(BinarySensor): + """ZHA BinarySensor.""" + + DEVICE_CLASS = DEVICE_CLASS_OCCUPANCY + + +@STRICT_MATCH(MatchRule(channel_names={CHANNEL_ON_OFF})) +class Opening(BinarySensor): + """ZHA BinarySensor.""" + + DEVICE_CLASS = DEVICE_CLASS_OPENING + + +@STRICT_MATCH(MatchRule(channel_names={CHANNEL_ZONE})) +class IASZone(BinarySensor): + """ZHA IAS BinarySensor.""" + + async def get_device_class(self) -> None: + """Get the HA device class from the channel.""" + zone_type = await self._channel.get_attribute_value("zone_type") + self._device_class = CLASS_MAPPING.get(zone_type) + + async def async_update(self): + """Attempt to retrieve on off state from the binary sensor.""" + await super().async_update() + value = await self._channel.get_attribute_value("zone_status") + if value is not None: + self._state = value & 3 diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index 6c991a319ac..c658febfd2d 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -43,6 +43,7 @@ ATTR_WARNING_DEVICE_STROBE_INTENSITY = "intensity" BAUD_RATES = [2400, 4800, 9600, 14400, 19200, 38400, 57600, 115200, 128000, 256000] +CHANNEL_ACCELEROMETER = "accelerometer" CHANNEL_ATTRIBUTE = "attribute" CHANNEL_BASIC = "basic" CHANNEL_COLOR = "light_color" diff --git a/homeassistant/components/zha/core/discovery.py b/homeassistant/components/zha/core/discovery.py index 108bd841252..d128ed274c0 100644 --- a/homeassistant/components/zha/core/discovery.py +++ b/homeassistant/components/zha/core/discovery.py @@ -11,21 +11,12 @@ import zigpy.profiles from zigpy.zcl.clusters.general import OnOff, PowerConfiguration from homeassistant import const as ha_const -from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_send from .channels import AttributeListeningChannel, EventRelayChannel, ZDOChannel -from .const import ( - COMPONENTS, - CONF_DEVICE_CONFIG, - DATA_ZHA, - SENSOR_TYPE, - UNKNOWN, - ZHA_DISCOVERY_NEW, -) +from .const import COMPONENTS, CONF_DEVICE_CONFIG, DATA_ZHA, ZHA_DISCOVERY_NEW from .registries import ( - BINARY_SENSOR_TYPES, CHANNEL_ONLY_CLUSTERS, COMPONENT_CLUSTERS, DEVICE_CLASS, @@ -160,15 +151,6 @@ def _async_handle_profile_match( "component": component, } - if component == BINARY_SENSOR: - discovery_info.update({SENSOR_TYPE: UNKNOWN}) - for cluster_id in profile_clusters: - if cluster_id in BINARY_SENSOR_TYPES: - discovery_info.update( - {SENSOR_TYPE: BINARY_SENSOR_TYPES.get(cluster_id, UNKNOWN)} - ) - break - return discovery_info @@ -288,9 +270,4 @@ def _async_handle_single_cluster_match( "component": component, } - if component == BINARY_SENSOR: - discovery_info.update( - {SENSOR_TYPE: BINARY_SENSOR_TYPES.get(cluster.cluster_id, UNKNOWN)} - ) - return discovery_info diff --git a/homeassistant/components/zha/core/registries.py b/homeassistant/components/zha/core/registries.py index c2d3b13e375..f235b459b87 100644 --- a/homeassistant/components/zha/core/registries.py +++ b/homeassistant/components/zha/core/registries.py @@ -30,20 +30,10 @@ from homeassistant.components.switch import DOMAIN as SWITCH # importing channels updates registries from . import channels # noqa: F401 pylint: disable=unused-import -from .const import ( - CONTROLLER, - SENSOR_ACCELERATION, - SENSOR_OCCUPANCY, - SENSOR_OPENING, - ZHA_GW_RADIO, - ZHA_GW_RADIO_DESCRIPTION, - ZONE, - RadioType, -) +from .const import CONTROLLER, ZHA_GW_RADIO, ZHA_GW_RADIO_DESCRIPTION, RadioType from .decorators import CALLABLE_T, DictRegistry, SetRegistry BINARY_SENSOR_CLUSTERS = SetRegistry() -BINARY_SENSOR_TYPES = {} BINDABLE_CLUSTERS = SetRegistry() CHANNEL_ONLY_CLUSTERS = SetRegistry() CLUSTER_REPORT_CONFIGS = {} @@ -104,15 +94,6 @@ def establish_device_mappings(): BINARY_SENSOR_CLUSTERS.add(SMARTTHINGS_ACCELERATION_CLUSTER) - BINARY_SENSOR_TYPES.update( - { - SMARTTHINGS_ACCELERATION_CLUSTER: SENSOR_ACCELERATION, - zcl.clusters.general.OnOff.cluster_id: SENSOR_OPENING, - zcl.clusters.measurement.OccupancySensing.cluster_id: SENSOR_OCCUPANCY, - zcl.clusters.security.IasZone.cluster_id: ZONE, - } - ) - DEVICE_CLASS[zigpy.profiles.zha.PROFILE_ID].update( { SMARTTHINGS_ARRIVAL_SENSOR_DEVICE_TYPE: DEVICE_TRACKER, From 70f8bfbd4f569d58b7959179a5fd3239c59f7cb9 Mon Sep 17 00:00:00 2001 From: Philipp Danner Date: Sun, 22 Dec 2019 19:46:53 +0100 Subject: [PATCH 372/677] Update Integration of Keba charging station (#30125) * fixed parsing of current to float in service set_current * Added optional name in the config file in order to get a better entety naming (easier to find) * fix parsing of all parameters to service calls * addressed code review comments + updated pypi dependency * config name imported from cont.py + minor naming changes to be more clear about the meaning of a sensor * removed name in config again, use product name gathered from the charging station instead * implemented suggested changes * changed variable naming as requested --- homeassistant/components/keba/__init__.py | 19 +++++--- .../components/keba/binary_sensor.py | 23 +++++---- homeassistant/components/keba/lock.py | 11 +++-- homeassistant/components/keba/manifest.json | 2 +- homeassistant/components/keba/sensor.py | 47 +++++++++++++++---- requirements_all.txt | 2 +- 6 files changed, 72 insertions(+), 32 deletions(-) diff --git a/homeassistant/components/keba/__init__.py b/homeassistant/components/keba/__init__.py index 830ebe7ffab..a261311be76 100644 --- a/homeassistant/components/keba/__init__.py +++ b/homeassistant/components/keba/__init__.py @@ -112,7 +112,8 @@ class KebaHandler(KebaKeContact): self._update_listeners = [] self._hass = hass self.rfid = rfid - self.device_name = "keba_wallbox_" + self.device_name = "keba" # correct device name will be set in setup() + self.device_id = "keba_wallbox_" # correct device id will be set in setup() # Ensure at least MAX_POLLING_INTERVAL seconds delay self._refresh_interval = max(MAX_POLLING_INTERVAL, refresh_interval) @@ -147,8 +148,12 @@ class KebaHandler(KebaKeContact): # Request initial values and extract serial number await self.request_data() - if self.get_value("Serial") is not None: - self.device_name = f"keba_wallbox_{self.get_value('Serial')}" + if ( + self.get_value("Serial") is not None + and self.get_value("Product") is not None + ): + self.device_id = f"keba_wallbox_{self.get_value('Serial')}" + self.device_name = self.get_value("Product") return True return False @@ -179,7 +184,7 @@ class KebaHandler(KebaKeContact): """Set energy target in async way.""" try: energy = param["energy"] - await self.set_energy(energy) + await self.set_energy(float(energy)) self._set_fast_polling() except (KeyError, ValueError) as ex: _LOGGER.warning("Energy value is not correct. %s", ex) @@ -188,7 +193,7 @@ class KebaHandler(KebaKeContact): """Set current maximum in async way.""" try: current = param["current"] - await self.set_current(current) + await self.set_current(float(current)) # No fast polling as this function might be called regularly except (KeyError, ValueError) as ex: _LOGGER.warning("Current value is not correct. %s", ex) @@ -216,10 +221,10 @@ class KebaHandler(KebaKeContact): async def async_set_failsafe(self, param=None): """Set failsafe mode in async way.""" try: - timout = param[CONF_FS_TIMEOUT] + timeout = param[CONF_FS_TIMEOUT] fallback = param[CONF_FS_FALLBACK] persist = param[CONF_FS_PERSIST] - await self.set_failsafe(timout, fallback, persist) + await self.set_failsafe(int(timeout), float(fallback), bool(persist)) self._set_fast_polling() except (KeyError, ValueError) as ex: _LOGGER.warning( diff --git a/homeassistant/components/keba/binary_sensor.py b/homeassistant/components/keba/binary_sensor.py index ac7326cc92e..5cced416bc3 100644 --- a/homeassistant/components/keba/binary_sensor.py +++ b/homeassistant/components/keba/binary_sensor.py @@ -22,10 +22,16 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= keba = hass.data[DOMAIN] sensors = [ - KebaBinarySensor(keba, "Online", "Wallbox", DEVICE_CLASS_CONNECTIVITY), - KebaBinarySensor(keba, "Plug", "Plug", DEVICE_CLASS_PLUG), - KebaBinarySensor(keba, "State", "Charging state", DEVICE_CLASS_POWER), - KebaBinarySensor(keba, "Tmo FS", "Failsafe Mode", DEVICE_CLASS_SAFETY), + KebaBinarySensor( + keba, "Online", "Status", "device_state", DEVICE_CLASS_CONNECTIVITY + ), + KebaBinarySensor(keba, "Plug", "Plug", "plug_state", DEVICE_CLASS_PLUG), + KebaBinarySensor( + keba, "State", "Charging State", "charging_state", DEVICE_CLASS_POWER + ), + KebaBinarySensor( + keba, "Tmo FS", "Failsafe Mode", "failsafe_mode_state", DEVICE_CLASS_SAFETY + ), ] async_add_entities(sensors) @@ -33,11 +39,12 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= class KebaBinarySensor(BinarySensorDevice): """Representation of a binary sensor of a KEBA charging station.""" - def __init__(self, keba, key, sensor_name, device_class): + def __init__(self, keba, key, name, entity_type, device_class): """Initialize the KEBA Sensor.""" self._key = key self._keba = keba - self._name = sensor_name + self._name = name + self._entity_type = entity_type self._device_class = device_class self._is_on = None self._attributes = {} @@ -50,12 +57,12 @@ class KebaBinarySensor(BinarySensorDevice): @property def unique_id(self): """Return the unique ID of the binary sensor.""" - return f"{self._keba.device_name}_{self._name}" + return f"{self._keba.device_id}_{self._entity_type}" @property def name(self): """Return the name of the device.""" - return self._name + return f"{self._keba.device_name} {self._name}" @property def device_class(self): diff --git a/homeassistant/components/keba/lock.py b/homeassistant/components/keba/lock.py index 3a65e44cd6f..f69fbdddf20 100644 --- a/homeassistant/components/keba/lock.py +++ b/homeassistant/components/keba/lock.py @@ -15,17 +15,18 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= keba = hass.data[DOMAIN] - sensors = [KebaLock(keba, "Authentication")] + sensors = [KebaLock(keba, "Authentication", "authentication")] async_add_entities(sensors) class KebaLock(LockDevice): """The entity class for KEBA charging stations switch.""" - def __init__(self, keba, name): + def __init__(self, keba, name, entity_type): """Initialize the KEBA switch.""" self._keba = keba self._name = name + self._entity_type = entity_type self._state = True @property @@ -35,13 +36,13 @@ class KebaLock(LockDevice): @property def unique_id(self): - """Return the unique ID of the binary sensor.""" - return f"{self._keba.device_name}_{self._name}" + """Return the unique ID of the lock.""" + return f"{self._keba.device_id}_{self._entity_type}" @property def name(self): """Return the name of the device.""" - return self._name + return f"{self._keba.device_name} {self._name}" @property def is_locked(self): diff --git a/homeassistant/components/keba/manifest.json b/homeassistant/components/keba/manifest.json index 422a79cd0be..0f3d21fc783 100644 --- a/homeassistant/components/keba/manifest.json +++ b/homeassistant/components/keba/manifest.json @@ -2,7 +2,7 @@ "domain": "keba", "name": "Keba Charging Station", "documentation": "https://www.home-assistant.io/integrations/keba", - "requirements": ["keba-kecontact==0.2.0"], + "requirements": ["keba-kecontact==1.0.0"], "dependencies": [], "codeowners": [ "@dannerph" diff --git a/homeassistant/components/keba/sensor.py b/homeassistant/components/keba/sensor.py index dfa04f95c65..d9e6118ff32 100644 --- a/homeassistant/components/keba/sensor.py +++ b/homeassistant/components/keba/sensor.py @@ -17,15 +17,40 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= keba = hass.data[DOMAIN] sensors = [ - KebaSensor(keba, "Curr user", "Max current", "mdi:flash", "A"), + KebaSensor(keba, "Curr user", "Max Current", "max_current", "mdi:flash", "A"), KebaSensor( - keba, "Setenergy", "Energy target", "mdi:gauge", ENERGY_KILO_WATT_HOUR + keba, + "Setenergy", + "Energy Target", + "energy_target", + "mdi:gauge", + ENERGY_KILO_WATT_HOUR, ), - KebaSensor(keba, "P", "Charging power", "mdi:flash", "kW", DEVICE_CLASS_POWER), KebaSensor( - keba, "E pres", "Session energy", "mdi:gauge", ENERGY_KILO_WATT_HOUR + keba, + "P", + "Charging Power", + "charging_power", + "mdi:flash", + "kW", + DEVICE_CLASS_POWER, + ), + KebaSensor( + keba, + "E pres", + "Session Energy", + "session_energy", + "mdi:gauge", + ENERGY_KILO_WATT_HOUR, + ), + KebaSensor( + keba, + "E total", + "Total Energy", + "total_energy", + "mdi:gauge", + ENERGY_KILO_WATT_HOUR, ), - KebaSensor(keba, "E total", "Total Energy", "mdi:gauge", ENERGY_KILO_WATT_HOUR), ] async_add_entities(sensors) @@ -33,14 +58,16 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= class KebaSensor(Entity): """The entity class for KEBA charging stations sensors.""" - def __init__(self, keba, key, name, icon, unit, device_class=None): + def __init__(self, keba, key, name, entity_type, icon, unit, device_class=None): """Initialize the KEBA Sensor.""" - self._key = key self._keba = keba + self._key = key self._name = name - self._device_class = device_class + self._entity_type = entity_type self._icon = icon self._unit = unit + self._device_class = device_class + self._state = None self._attributes = {} @@ -52,12 +79,12 @@ class KebaSensor(Entity): @property def unique_id(self): """Return the unique ID of the binary sensor.""" - return f"{self._keba.device_name}_{self._name}" + return f"{self._keba.device_id}_{self._entity_type}" @property def name(self): """Return the name of the device.""" - return self._name + return f"{self._keba.device_name} {self._name}" @property def device_class(self): diff --git a/requirements_all.txt b/requirements_all.txt index 6919b103117..831933c70b1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -738,7 +738,7 @@ jsonrpc-websocket==0.6 kaiterra-async-client==0.0.2 # homeassistant.components.keba -keba-kecontact==0.2.0 +keba-kecontact==1.0.0 # homeassistant.scripts.keyring keyring==20.0.0 From 868eb3c735c2136cf81a5f18a19cd08a7d0522a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sun, 22 Dec 2019 20:51:39 +0200 Subject: [PATCH 373/677] More helpers type improvements (#30145) --- homeassistant/helpers/check_config.py | 26 +++-- homeassistant/helpers/config_validation.py | 115 ++++++++++++--------- homeassistant/helpers/device_registry.py | 8 +- homeassistant/helpers/entity_registry.py | 42 ++++---- homeassistant/helpers/logging.py | 23 +++-- 5 files changed, 124 insertions(+), 90 deletions(-) diff --git a/homeassistant/helpers/check_config.py b/homeassistant/helpers/check_config.py index 81e654247b7..1b1e136ed89 100644 --- a/homeassistant/helpers/check_config.py +++ b/homeassistant/helpers/check_config.py @@ -1,6 +1,6 @@ """Helper to check the configuration file.""" -from collections import OrderedDict, namedtuple -from typing import List +from collections import OrderedDict +from typing import List, NamedTuple, Optional import attr import voluptuous as vol @@ -19,15 +19,20 @@ from homeassistant.config import ( ) from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.typing import ConfigType from homeassistant.requirements import ( RequirementsNotFound, async_get_integration_with_requirements, ) import homeassistant.util.yaml.loader as yaml_loader -# mypy: allow-untyped-calls, allow-untyped-defs, no-warn-return-any -CheckConfigError = namedtuple("CheckConfigError", "message domain config") +class CheckConfigError(NamedTuple): + """Configuration check error.""" + + message: str + domain: Optional[str] + config: Optional[ConfigType] @attr.s @@ -36,7 +41,12 @@ class HomeAssistantConfig(OrderedDict): errors: List[CheckConfigError] = attr.ib(default=attr.Factory(list)) - def add_error(self, message, domain=None, config=None): + def add_error( + self, + message: str, + domain: Optional[str] = None, + config: Optional[ConfigType] = None, + ) -> "HomeAssistantConfig": """Add a single error.""" self.errors.append(CheckConfigError(str(message), domain, config)) return self @@ -55,7 +65,9 @@ async def async_check_ha_config_file(hass: HomeAssistant) -> HomeAssistantConfig config_dir = hass.config.config_dir result = HomeAssistantConfig() - def _pack_error(package, component, config, message): + def _pack_error( + package: str, component: str, config: ConfigType, message: str + ) -> None: """Handle errors from packages: _log_pkg_error.""" message = "Package {} setup failed. Component {} {}".format( package, component, message @@ -64,7 +76,7 @@ async def async_check_ha_config_file(hass: HomeAssistant) -> HomeAssistantConfig pack_config = core_config[CONF_PACKAGES].get(package, config) result.add_error(message, domain, pack_config) - def _comp_error(ex, domain, config): + def _comp_error(ex: Exception, domain: str, config: ConfigType) -> None: """Handle errors from components: async_log_exception.""" result.add_error(_format_config_error(ex, domain, config), domain, config) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 5787db65102..035e1f678bf 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -5,13 +5,26 @@ from datetime import ( time as time_sys, timedelta, ) +from enum import Enum import inspect import logging from numbers import Number import os import re from socket import _GLOBAL_DEFAULT_TIMEOUT # type: ignore # private, not in typeshed -from typing import Any, Callable, Dict, List, Optional, TypeVar, Union +from typing import ( + Any, + Callable, + Dict, + Hashable, + List, + Optional, + Pattern, + Type, + TypeVar, + Union, + cast, +) from urllib.parse import urlparse from uuid import UUID @@ -48,12 +61,11 @@ from homeassistant.const import ( ) from homeassistant.core import split_entity_id, valid_entity_id from homeassistant.exceptions import TemplateError +from homeassistant.helpers import template as template_helper from homeassistant.helpers.logging import KeywordStyleAdapter from homeassistant.util import slugify as util_slugify import homeassistant.util.dt as dt_util -# mypy: allow-untyped-calls, allow-untyped-defs -# mypy: no-check-untyped-defs, no-warn-return-any # pylint: disable=invalid-name TIME_PERIOD_ERROR = "offset {} should be format 'HH:MM' or 'HH:MM:SS'" @@ -126,7 +138,7 @@ def boolean(value: Any) -> bool: raise vol.Invalid("invalid boolean value {}".format(value)) -def isdevice(value): +def isdevice(value: Any) -> str: """Validate that value is a real device.""" try: os.stat(value) @@ -135,19 +147,19 @@ def isdevice(value): raise vol.Invalid("No device at {} found".format(value)) -def matches_regex(regex): +def matches_regex(regex: str) -> Callable[[Any], str]: """Validate that the value is a string that matches a regex.""" - regex = re.compile(regex) + compiled = re.compile(regex) def validator(value: Any) -> str: """Validate that value matches the given regex.""" if not isinstance(value, str): raise vol.Invalid("not a string value: {}".format(value)) - if not regex.match(value): + if not compiled.match(value): raise vol.Invalid( "value {} does not match regular expression {}".format( - value, regex.pattern + value, compiled.pattern ) ) @@ -156,14 +168,14 @@ def matches_regex(regex): return validator -def is_regex(value): +def is_regex(value: Any) -> Pattern[Any]: """Validate that a string is a valid regular expression.""" try: r = re.compile(value) return r except TypeError: raise vol.Invalid( - "value {} is of the wrong type for a regular " "expression".format(value) + "value {} is of the wrong type for a regular expression".format(value) ) except re.error: raise vol.Invalid("value {} is not a valid regular expression".format(value)) @@ -204,9 +216,9 @@ def ensure_list(value: Union[T, List[T], None]) -> List[T]: def entity_id(value: Any) -> str: """Validate Entity ID.""" - value = string(value).lower() - if valid_entity_id(value): - return value + str_value = string(value).lower() + if valid_entity_id(str_value): + return str_value raise vol.Invalid("Entity ID {} is an invalid entity id".format(value)) @@ -253,17 +265,17 @@ def entities_domain(domain: str) -> Callable[[Union[str, List]], List[str]]: return validate -def enum(enumClass): +def enum(enumClass: Type[Enum]) -> vol.All: """Create validator for specified enum.""" return vol.All(vol.In(enumClass.__members__), enumClass.__getitem__) -def icon(value): +def icon(value: Any) -> str: """Validate icon.""" - value = str(value) + str_value = str(value) - if ":" in value: - return value + if ":" in str_value: + return str_value raise vol.Invalid('Icons should be specified in the form "prefix:name"') @@ -362,7 +374,7 @@ def time_period_seconds(value: Union[int, str]) -> timedelta: time_period = vol.Any(time_period_str, time_period_seconds, timedelta, time_period_dict) -def match_all(value): +def match_all(value: T) -> T: """Validate that matches all values.""" return value @@ -382,12 +394,12 @@ def remove_falsy(value: List[T]) -> List[T]: return [v for v in value if v] -def service(value): +def service(value: Any) -> str: """Validate service.""" # Services use same format as entities so we can use same helper. - value = string(value).lower() - if valid_entity_id(value): - return value + str_value = string(value).lower() + if valid_entity_id(str_value): + return str_value raise vol.Invalid("Service {} does not match format .".format(value)) @@ -407,7 +419,7 @@ def schema_with_slug_keys(value_schema: Union[T, Callable]) -> Callable: for key in value.keys(): slug(key) - return schema(value) + return cast(Dict, schema(value)) return verify @@ -416,10 +428,10 @@ def slug(value: Any) -> str: """Validate value is a valid slug.""" if value is None: raise vol.Invalid("Slug should not be None") - value = str(value) - slg = util_slugify(value) - if value == slg: - return value + str_value = str(value) + slg = util_slugify(str_value) + if str_value == slg: + return str_value raise vol.Invalid("invalid slug {} (try {})".format(value, slg)) @@ -458,42 +470,41 @@ unit_system = vol.All( ) -def template(value): +def template(value: Optional[Any]) -> template_helper.Template: """Validate a jinja2 template.""" - from homeassistant.helpers import template as template_helper if value is None: raise vol.Invalid("template value is None") if isinstance(value, (list, dict, template_helper.Template)): raise vol.Invalid("template value should be a string") - value = template_helper.Template(str(value)) + template_value = template_helper.Template(str(value)) # type: ignore try: - value.ensure_valid() - return value + template_value.ensure_valid() + return cast(template_helper.Template, template_value) except TemplateError as ex: raise vol.Invalid("invalid template ({})".format(ex)) -def template_complex(value): +def template_complex(value: Any) -> Any: """Validate a complex jinja2 template.""" if isinstance(value, list): - return_value = value.copy() - for idx, element in enumerate(return_value): - return_value[idx] = template_complex(element) - return return_value + return_list = value.copy() + for idx, element in enumerate(return_list): + return_list[idx] = template_complex(element) + return return_list if isinstance(value, dict): - return_value = value.copy() - for key, element in return_value.items(): - return_value[key] = template_complex(element) - return return_value + return_dict = value.copy() + for key, element in return_dict.items(): + return_dict[key] = template_complex(element) + return return_dict if isinstance(value, str): return template(value) return value -def datetime(value): +def datetime(value: Any) -> datetime_sys: """Validate datetime.""" if isinstance(value, datetime_sys): return value @@ -509,7 +520,7 @@ def datetime(value): return date_val -def time_zone(value): +def time_zone(value: str) -> str: """Validate timezone.""" if dt_util.get_time_zone(value) is not None: return value @@ -522,7 +533,7 @@ def time_zone(value): weekdays = vol.All(ensure_list, [vol.In(WEEKDAYS)]) -def socket_timeout(value): +def socket_timeout(value: Optional[Any]) -> object: """Validate timeout float > 0.0. None coerced to socket._GLOBAL_DEFAULT_TIMEOUT bare object. @@ -544,12 +555,12 @@ def url(value: Any) -> str: url_in = str(value) if urlparse(url_in).scheme in ["http", "https"]: - return vol.Schema(vol.Url())(url_in) + return cast(str, vol.Schema(vol.Url())(url_in)) raise vol.Invalid("invalid url") -def x10_address(value): +def x10_address(value: str) -> str: """Validate an x10 address.""" regex = re.compile(r"([A-Pa-p]{1})(?:[2-9]|1[0-6]?)$") if not regex.match(value): @@ -557,7 +568,7 @@ def x10_address(value): return str(value).lower() -def uuid4_hex(value): +def uuid4_hex(value: Any) -> str: """Validate a v4 UUID in hex format.""" try: result = UUID(value, version=4) @@ -678,10 +689,12 @@ def deprecated( # Validator helpers -def key_dependency(key, dependency): +def key_dependency( + key: Hashable, dependency: Hashable +) -> Callable[[Dict[Hashable, Any]], Dict[Hashable, Any]]: """Validate that all dependencies exist for key.""" - def validator(value): + def validator(value: Dict[Hashable, Any]) -> Dict[Hashable, Any]: """Test dependencies.""" if not isinstance(value, dict): raise vol.Invalid("key dependencies require a dict") @@ -696,7 +709,7 @@ def key_dependency(key, dependency): return validator -def custom_serializer(schema): +def custom_serializer(schema: Any) -> Any: """Serialize additional types for voluptuous_serialize.""" if schema is positive_time_period_dict: return {"type": "positive_time_period_dict"} diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index 4818de83cb9..512334c8d3c 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -12,8 +12,7 @@ from homeassistant.loader import bind_hass from .typing import HomeAssistantType -# mypy: allow-untyped-calls, allow-untyped-defs -# mypy: no-check-untyped-defs, no-warn-return-any +# mypy: allow-untyped-calls, allow-untyped-defs, no-check-untyped-defs _LOGGER = logging.getLogger(__name__) _UNDEF = object() @@ -71,10 +70,11 @@ def format_mac(mac: str) -> str: class DeviceRegistry: """Class to hold a registry of devices.""" - def __init__(self, hass): + devices: Dict[str, DeviceEntry] + + def __init__(self, hass: HomeAssistantType) -> None: """Initialize the device registry.""" self.hass = hass - self.devices = None self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) @callback diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index a5bd62d973c..5eb79965880 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -11,7 +11,7 @@ import asyncio from collections import OrderedDict from itertools import chain import logging -from typing import Any, Dict, Iterable, List, Optional, cast +from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, cast import attr @@ -23,6 +23,9 @@ from homeassistant.util.yaml import load_yaml from .typing import HomeAssistantType +if TYPE_CHECKING: + from homeassistant.config_entries import ConfigEntry # noqa: F401 + # mypy: allow-untyped-defs, no-check-untyped-defs PATH_REGISTRY = "entity_registry.yaml" @@ -48,7 +51,7 @@ class RegistryEntry: unique_id = attr.ib(type=str) platform = attr.ib(type=str) name = attr.ib(type=str, default=None) - device_id = attr.ib(type=str, default=None) + device_id: Optional[str] = attr.ib(default=None) config_entry_id: Optional[str] = attr.ib(default=None) disabled_by = attr.ib( type=Optional[str], @@ -135,16 +138,16 @@ class EntityRegistry: @callback def async_get_or_create( self, - domain, - platform, - unique_id, + domain: str, + platform: str, + unique_id: str, *, - suggested_object_id=None, - config_entry=None, - device_id=None, - known_object_ids=None, - disabled_by=None, - ): + suggested_object_id: Optional[str] = None, + config_entry: Optional["ConfigEntry"] = None, + device_id: Optional[str] = None, + known_object_ids: Optional[Iterable[str]] = None, + disabled_by: Optional[str] = None, + ) -> RegistryEntry: """Get entity. Create if it doesn't exist.""" config_entry_id = None if config_entry: @@ -153,7 +156,7 @@ class EntityRegistry: entity_id = self.async_get_entity_id(domain, platform, unique_id) if entity_id: - return self._async_update_entity( + return self._async_update_entity( # type: ignore entity_id, config_entry_id=config_entry_id or _UNDEF, device_id=device_id or _UNDEF, @@ -228,12 +231,15 @@ class EntityRegistry: disabled_by=_UNDEF, ): """Update properties of an entity.""" - return self._async_update_entity( - entity_id, - name=name, - new_entity_id=new_entity_id, - new_unique_id=new_unique_id, - disabled_by=disabled_by, + return cast( # cast until we have _async_update_entity type hinted + RegistryEntry, + self._async_update_entity( + entity_id, + name=name, + new_entity_id=new_entity_id, + new_unique_id=new_unique_id, + disabled_by=disabled_by, + ), ) @callback diff --git a/homeassistant/helpers/logging.py b/homeassistant/helpers/logging.py index 7b2507d9e05..0b274458045 100644 --- a/homeassistant/helpers/logging.py +++ b/homeassistant/helpers/logging.py @@ -1,8 +1,7 @@ """Helpers for logging allowing more advanced logging styles to be used.""" import inspect import logging - -# mypy: allow-untyped-defs, no-check-untyped-defs +from typing import Any, Mapping, MutableMapping, Optional, Tuple class KeywordMessage: @@ -12,13 +11,13 @@ class KeywordMessage: Adapted from: https://stackoverflow.com/a/24683360/2267718 """ - def __init__(self, fmt, args, kwargs): - """Initialize a new BraceMessage object.""" + def __init__(self, fmt: Any, args: Any, kwargs: Mapping[str, Any]) -> None: + """Initialize a new KeywordMessage object.""" self._fmt = fmt self._args = args self._kwargs = kwargs - def __str__(self): + def __str__(self) -> str: """Convert the object to a string for logging.""" return str(self._fmt).format(*self._args, **self._kwargs) @@ -26,26 +25,30 @@ class KeywordMessage: class KeywordStyleAdapter(logging.LoggerAdapter): """Represents an adapter wrapping the logger allowing KeywordMessages.""" - def __init__(self, logger, extra=None): + def __init__( + self, logger: logging.Logger, extra: Optional[Mapping[str, Any]] = None + ) -> None: """Initialize a new StyleAdapter for the provided logger.""" super().__init__(logger, extra or {}) - def log(self, level, msg, *args, **kwargs): + def log(self, level: int, msg: Any, *args: Any, **kwargs: Any) -> None: """Log the message provided at the appropriate level.""" if self.isEnabledFor(level): msg, log_kwargs = self.process(msg, kwargs) - self.logger._log( # pylint: disable=protected-access + self.logger._log( # type: ignore # pylint: disable=protected-access level, KeywordMessage(msg, args, kwargs), (), **log_kwargs ) - def process(self, msg, kwargs): + def process( + self, msg: Any, kwargs: MutableMapping[str, Any] + ) -> Tuple[Any, MutableMapping[str, Any]]: """Process the keyward args in preparation for logging.""" return ( msg, { k: kwargs[k] for k in inspect.getfullargspec( - self.logger._log # pylint: disable=protected-access + self.logger._log # type: ignore # pylint: disable=protected-access ).args[1:] if k in kwargs }, From b1bb2298e0c57f6a40119bce88195722fb4ed0ac Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sun, 22 Dec 2019 19:52:39 +0100 Subject: [PATCH 374/677] Bump sqlalchemy to 1.3.12 (#30142) --- homeassistant/components/recorder/manifest.json | 6 ++---- homeassistant/components/sql/manifest.json | 10 +++------- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 8 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/recorder/manifest.json b/homeassistant/components/recorder/manifest.json index 4ad000866db..ad0ac979e03 100644 --- a/homeassistant/components/recorder/manifest.json +++ b/homeassistant/components/recorder/manifest.json @@ -2,9 +2,7 @@ "domain": "recorder", "name": "Recorder", "documentation": "https://www.home-assistant.io/integrations/recorder", - "requirements": [ - "sqlalchemy==1.3.11" - ], + "requirements": ["sqlalchemy==1.3.12"], "dependencies": [], "codeowners": [] -} \ No newline at end of file +} diff --git a/homeassistant/components/sql/manifest.json b/homeassistant/components/sql/manifest.json index 39435524c20..3434b154e29 100644 --- a/homeassistant/components/sql/manifest.json +++ b/homeassistant/components/sql/manifest.json @@ -2,11 +2,7 @@ "domain": "sql", "name": "Sql", "documentation": "https://www.home-assistant.io/integrations/sql", - "requirements": [ - "sqlalchemy==1.3.11" - ], + "requirements": ["sqlalchemy==1.3.12"], "dependencies": [], - "codeowners": [ - "@dgomes" - ] -} \ No newline at end of file + "codeowners": ["@dgomes"] +} diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index ce6ad36dbf0..5d14a6e30d6 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -21,7 +21,7 @@ pytz>=2019.03 pyyaml==5.2.0 requests==2.22.0 ruamel.yaml==0.15.100 -sqlalchemy==1.3.11 +sqlalchemy==1.3.12 voluptuous-serialize==2.3.0 voluptuous==0.11.7 zeroconf==0.24.2 diff --git a/requirements_all.txt b/requirements_all.txt index 831933c70b1..dddbcf7a3fd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1872,7 +1872,7 @@ spotipy-homeassistant==2.4.4.dev1 # homeassistant.components.recorder # homeassistant.components.sql -sqlalchemy==1.3.11 +sqlalchemy==1.3.12 # homeassistant.components.starline starline==0.1.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0cc01b5f47f..e5361e7464e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -578,7 +578,7 @@ somecomfort==0.5.2 # homeassistant.components.recorder # homeassistant.components.sql -sqlalchemy==1.3.11 +sqlalchemy==1.3.12 # homeassistant.components.starline starline==0.1.3 From d4ff214fce5680dcf753e0d16d4f51ccb8bf9d95 Mon Sep 17 00:00:00 2001 From: cgtobi Date: Sun, 22 Dec 2019 19:53:03 +0100 Subject: [PATCH 375/677] Clean up scaffold (#30135) --- .../config_flow_oauth2/tests/test_config_flow.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/script/scaffold/templates/config_flow_oauth2/tests/test_config_flow.py b/script/scaffold/templates/config_flow_oauth2/tests/test_config_flow.py index 50540594a34..ec332de13e2 100644 --- a/script/scaffold/templates/config_flow_oauth2/tests/test_config_flow.py +++ b/script/scaffold/templates/config_flow_oauth2/tests/test_config_flow.py @@ -1,5 +1,5 @@ """Test the NEW_NAME config flow.""" -from homeassistant import config_entries, data_entry_flow, setup +from homeassistant import config_entries, setup from homeassistant.components.NEW_DOMAIN.const import ( DOMAIN, OAUTH2_AUTHORIZE, @@ -17,11 +17,7 @@ async def test_full_flow(hass, aiohttp_client, aioclient_mock): hass, "NEW_DOMAIN", { - "NEW_DOMAIN": { - "type": "oauth2", - "client_id": CLIENT_ID, - "client_secret": CLIENT_SECRET, - }, + "NEW_DOMAIN": {"client_id": CLIENT_ID, "client_secret": CLIENT_SECRET}, "http": {"base_url": "https://example.com"}, }, ) @@ -31,7 +27,6 @@ async def test_full_flow(hass, aiohttp_client, aioclient_mock): ) state = config_entry_oauth2_flow._encode_jwt(hass, {"flow_id": result["flow_id"]}) - assert result["type"] == data_entry_flow.RESULT_TYPE_EXTERNAL_STEP assert result["url"] == ( f"{OAUTH2_AUTHORIZE}?response_type=code&client_id={CLIENT_ID}" "&redirect_uri=https://example.com/auth/external/callback" @@ -56,5 +51,3 @@ async def test_full_flow(hass, aiohttp_client, aioclient_mock): result = await hass.config_entries.flow.async_configure(result["flow_id"]) assert len(hass.config_entries.async_entries(DOMAIN)) == 1 - entry = hass.config_entries.async_entries(DOMAIN)[0] - assert entry.data["type"] == "oauth2" From fdaaabf07015a88c33203d2724e54966ddbaf0c3 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Sun, 22 Dec 2019 14:07:49 -0500 Subject: [PATCH 376/677] ZHA binary_sensor cleanup. (#30149) --- homeassistant/components/zha/binary_sensor.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/homeassistant/components/zha/binary_sensor.py b/homeassistant/components/zha/binary_sensor.py index ed09a190adb..954fa8b29aa 100644 --- a/homeassistant/components/zha/binary_sensor.py +++ b/homeassistant/components/zha/binary_sensor.py @@ -46,13 +46,6 @@ CLASS_MAPPING = { STRICT_MATCH = functools.partial(ZHA_ENTITIES.strict_match, DOMAIN) -DEVICE_CLASS_REGISTRY = { - CHANNEL_ACCELEROMETER: DEVICE_CLASS_MOVING, - CHANNEL_OCCUPANCY: DEVICE_CLASS_OCCUPANCY, - CHANNEL_ON_OFF: DEVICE_CLASS_OPENING, -} - - async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Old way of setting up Zigbee Home Automation binary sensors.""" pass @@ -152,7 +145,7 @@ class BinarySensor(ZhaEntity, BinarySensorDevice): class Accelerometer(BinarySensor): """ZHA BinarySensor.""" - DEVICE_CLASS = DEVICE_CLASS_MOTION + DEVICE_CLASS = DEVICE_CLASS_MOVING @STRICT_MATCH(MatchRule(channel_names={CHANNEL_OCCUPANCY})) From d101d4449fa056548bf787645eb0b9a0aa8ee827 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sun, 22 Dec 2019 22:24:18 +0100 Subject: [PATCH 377/677] Fix deconz SSDP updating Hassio discovery (#30153) --- .../components/deconz/config_flow.py | 8 ++++- tests/components/deconz/test_config_flow.py | 33 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/deconz/config_flow.py b/homeassistant/components/deconz/config_flow.py index f44acdbd305..c84192456d1 100644 --- a/homeassistant/components/deconz/config_flow.py +++ b/homeassistant/components/deconz/config_flow.py @@ -161,7 +161,11 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): def _update_entry(self, entry, host, port, api_key=None): """Update existing entry.""" - if entry.data[CONF_HOST] == host: + if ( + entry.data[CONF_HOST] == host + and entry.data[CONF_PORT] == port + and (api_key is None or entry.data[CONF_API_KEY] == api_key) + ): return self.async_abort(reason="already_configured") entry.data[CONF_HOST] = host @@ -186,6 +190,8 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): for entry in self.hass.config_entries.async_entries(DOMAIN): if uuid == entry.data.get(CONF_UUID): + if entry.source == "hassio": + return self.async_abort(reason="already_configured") return self._update_entry(entry, parsed_url.hostname, parsed_url.port) bridgeid = discovery_info[ssdp.ATTR_UPNP_SERIAL] diff --git a/tests/components/deconz/test_config_flow.py b/tests/components/deconz/test_config_flow.py index ddcbc1a1d5f..da8b0a8a7f4 100644 --- a/tests/components/deconz/test_config_flow.py +++ b/tests/components/deconz/test_config_flow.py @@ -269,6 +269,39 @@ async def test_bridge_discovery_update_existing_entry(hass): assert entry.data[config_flow.CONF_HOST] == "mock-deconz" +async def test_bridge_discovery_dont_update_existing_hassio_entry(hass): + """Test to ensure the SSDP discovery does not update an Hass.io entry.""" + entry = MockConfigEntry( + domain=config_flow.DOMAIN, + source="hassio", + data={ + config_flow.CONF_HOST: "core-deconz", + config_flow.CONF_BRIDGEID: "123ABC", + config_flow.CONF_UUID: "456DEF", + }, + ) + entry.add_to_hass(hass) + + gateway = Mock() + gateway.config_entry = entry + hass.data[config_flow.DOMAIN] = {"123ABC": gateway} + + result = await hass.config_entries.flow.async_init( + config_flow.DOMAIN, + data={ + ssdp.ATTR_SSDP_LOCATION: "http://mock-deconz/", + ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.DECONZ_MANUFACTURERURL, + ssdp.ATTR_UPNP_SERIAL: "123ABC", + ssdp.ATTR_UPNP_UDN: "uuid:456DEF", + }, + context={"source": "ssdp"}, + ) + + assert result["type"] == "abort" + assert result["reason"] == "already_configured" + assert entry.data[config_flow.CONF_HOST] == "core-deconz" + + async def test_create_entry(hass, aioclient_mock): """Test that _create_entry work and that bridgeid can be requested.""" aioclient_mock.get( From e9dc404de15f32eeff9a54a0e541381e6a8a57a8 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sun, 22 Dec 2019 22:58:22 +0100 Subject: [PATCH 378/677] Allow battery value of 0 as well as make sure to not create a battery tracker if one already exist (#30155) --- homeassistant/components/deconz/sensor.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/deconz/sensor.py b/homeassistant/components/deconz/sensor.py index 4c854a0ec11..4ffaba9b499 100644 --- a/homeassistant/components/deconz/sensor.py +++ b/homeassistant/components/deconz/sensor.py @@ -59,7 +59,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entity_handler.add_entity(new_sensor) entities.append(new_sensor) - if sensor.battery: + if sensor.battery is not None: new_battery = DeconzBattery(sensor, gateway) if new_battery.unique_id not in batteries: batteries.add(new_battery.unique_id) @@ -225,6 +225,9 @@ class DeconzBatteryHandler: @callback def create_tracker(self, sensor): """Create new tracker for battery state.""" + for tracker in self._trackers: + if sensor == tracker.sensor: + return self._trackers.add(DeconzSensorStateTracker(sensor, self.gateway)) @callback From a2678b2affea5bc11da7f470a5b554803cc3b5ea Mon Sep 17 00:00:00 2001 From: ochlocracy <5885236+ochlocracy@users.noreply.github.com> Date: Mon, 23 Dec 2019 09:17:37 -0500 Subject: [PATCH 379/677] Add support for input_number entities in Alexa integration (#30139) * Add support for input_number entities * Update homeassistant/components/alexa/capabilities.py Co-Authored-By: Paulus Schoutsen * Removed get methods to directly access required attributes dicts. Co-authored-by: Paulus Schoutsen --- .../components/alexa/capabilities.py | 28 ++- homeassistant/components/alexa/entities.py | 19 ++ homeassistant/components/alexa/handlers.py | 39 +++- homeassistant/components/alexa/resources.py | 6 +- tests/components/alexa/test_smart_home.py | 172 ++++++++++++++++++ 5 files changed, 256 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/alexa/capabilities.py b/homeassistant/components/alexa/capabilities.py index 938101a7500..513a0c157d3 100644 --- a/homeassistant/components/alexa/capabilities.py +++ b/homeassistant/components/alexa/capabilities.py @@ -1,7 +1,7 @@ """Alexa capabilities.""" import logging -from homeassistant.components import cover, fan, image_processing, light +from homeassistant.components import cover, fan, image_processing, input_number, light from homeassistant.components.alarm_control_panel import ATTR_CODE_FORMAT, FORMAT_NUMBER import homeassistant.components.climate.const as climate import homeassistant.components.media_player.const as media_player @@ -1054,6 +1054,10 @@ class AlexaRangeController(AlexaCapability): if self.instance == f"{cover.DOMAIN}.{cover.ATTR_TILT_POSITION}": return self.entity.attributes.get(cover.ATTR_CURRENT_TILT_POSITION) + # Input Number Value + if self.instance == f"{input_number.DOMAIN}.{input_number.ATTR_VALUE}": + return float(self.entity.state) + return None def configuration(self): @@ -1110,6 +1114,28 @@ class AlexaRangeController(AlexaCapability): ) return self._resource.serialize_capability_resources() + # Input Number Value + if self.instance == f"{input_number.DOMAIN}.{input_number.ATTR_VALUE}": + min_value = float(self.entity.attributes[input_number.ATTR_MIN]) + max_value = float(self.entity.attributes[input_number.ATTR_MAX]) + precision = float(self.entity.attributes.get(input_number.ATTR_STEP, 1)) + unit = self.entity.attributes.get(input_number.ATTR_UNIT_OF_MEASUREMENT) + + self._resource = AlexaPresetResource( + ["Value"], + min_value=min_value, + max_value=max_value, + precision=precision, + unit=unit, + ) + self._resource.add_preset( + value=min_value, labels=[AlexaGlobalCatalog.VALUE_MINIMUM] + ) + self._resource.add_preset( + value=max_value, labels=[AlexaGlobalCatalog.VALUE_MAXIMUM] + ) + return self._resource.serialize_capability_resources() + return None def semantics(self): diff --git a/homeassistant/components/alexa/entities.py b/homeassistant/components/alexa/entities.py index 89ca646890b..ab3dc75bd2c 100644 --- a/homeassistant/components/alexa/entities.py +++ b/homeassistant/components/alexa/entities.py @@ -11,6 +11,7 @@ from homeassistant.components import ( group, image_processing, input_boolean, + input_number, light, lock, media_player, @@ -674,3 +675,21 @@ class ImageProcessingCapabilities(AlexaEntity): yield AlexaEventDetectionSensor(self.hass, self.entity) yield AlexaEndpointHealth(self.hass, self.entity) yield Alexa(self.hass) + + +@ENTITY_ADAPTERS.register(input_number.DOMAIN) +class InputNumberCapabilities(AlexaEntity): + """Class to represent input_number capabilities.""" + + def default_display_categories(self): + """Return the display categories for this entity.""" + return [DisplayCategory.OTHER] + + def interfaces(self): + """Yield the supported interfaces.""" + + yield AlexaRangeController( + self.entity, instance=f"{input_number.DOMAIN}.{input_number.ATTR_VALUE}" + ) + yield AlexaEndpointHealth(self.hass, self.entity) + yield Alexa(self.hass) diff --git a/homeassistant/components/alexa/handlers.py b/homeassistant/components/alexa/handlers.py index b5603af7402..133919be84d 100644 --- a/homeassistant/components/alexa/handlers.py +++ b/homeassistant/components/alexa/handlers.py @@ -3,7 +3,14 @@ import logging import math from homeassistant import core as ha -from homeassistant.components import cover, fan, group, light, media_player +from homeassistant.components import ( + cover, + fan, + group, + input_number, + light, + media_player, +) from homeassistant.components.climate import const as climate from homeassistant.const import ( ATTR_ENTITY_ID, @@ -1080,12 +1087,12 @@ async def async_api_set_range(hass, config, directive, context): domain = entity.domain service = None data = {ATTR_ENTITY_ID: entity.entity_id} - range_value = int(directive.payload["rangeValue"]) + range_value = directive.payload["rangeValue"] # Fan Speed if instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}": service = fan.SERVICE_SET_SPEED - speed = SPEED_FAN_MAP.get(range_value, None) + speed = SPEED_FAN_MAP.get(int(range_value)) if not speed: msg = "Entity does not support value" @@ -1098,6 +1105,7 @@ async def async_api_set_range(hass, config, directive, context): # Cover Position elif instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}": + range_value = int(range_value) if range_value == 0: service = cover.SERVICE_CLOSE_COVER elif range_value == 100: @@ -1108,6 +1116,7 @@ async def async_api_set_range(hass, config, directive, context): # Cover Tilt Position elif instance == f"{cover.DOMAIN}.{cover.ATTR_TILT_POSITION}": + range_value = int(range_value) if range_value == 0: service = cover.SERVICE_CLOSE_COVER_TILT elif range_value == 100: @@ -1116,6 +1125,14 @@ async def async_api_set_range(hass, config, directive, context): service = cover.SERVICE_SET_COVER_TILT_POSITION data[cover.ATTR_POSITION] = range_value + # Input Number Value + elif instance == f"{input_number.DOMAIN}.{input_number.ATTR_VALUE}": + range_value = float(range_value) + service = input_number.SERVICE_SET_VALUE + min_value = float(entity.attributes[input_number.ATTR_MIN]) + max_value = float(entity.attributes[input_number.ATTR_MAX]) + data[input_number.ATTR_VALUE] = min(max_value, max(min_value, range_value)) + else: msg = "Entity does not support directive" raise AlexaInvalidDirectiveError(msg) @@ -1145,11 +1162,12 @@ async def async_api_adjust_range(hass, config, directive, context): domain = entity.domain service = None data = {ATTR_ENTITY_ID: entity.entity_id} - range_delta = int(directive.payload["rangeValueDelta"]) + range_delta = directive.payload["rangeValueDelta"] response_value = 0 # Fan Speed if instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}": + range_delta = int(range_delta) service = fan.SERVICE_SET_SPEED current_range = RANGE_FAN_MAP.get(entity.attributes.get(fan.ATTR_SPEED), 0) speed = SPEED_FAN_MAP.get( @@ -1163,6 +1181,7 @@ async def async_api_adjust_range(hass, config, directive, context): # Cover Position elif instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}": + range_delta = int(range_delta) service = SERVICE_SET_COVER_POSITION current = entity.attributes.get(cover.ATTR_POSITION) data[cover.ATTR_POSITION] = response_value = min( @@ -1171,12 +1190,24 @@ async def async_api_adjust_range(hass, config, directive, context): # Cover Tilt Position elif instance == f"{cover.DOMAIN}.{cover.ATTR_TILT_POSITION}": + range_delta = int(range_delta) service = SERVICE_SET_COVER_TILT_POSITION current = entity.attributes.get(cover.ATTR_TILT_POSITION) data[cover.ATTR_TILT_POSITION] = response_value = min( 100, max(0, range_delta + current) ) + # Input Number Value + elif instance == f"{input_number.DOMAIN}.{input_number.ATTR_VALUE}": + range_delta = float(range_delta) + service = input_number.SERVICE_SET_VALUE + min_value = float(entity.attributes[input_number.ATTR_MIN]) + max_value = float(entity.attributes[input_number.ATTR_MAX]) + current = float(entity.state) + data[input_number.ATTR_VALUE] = response_value = min( + max_value, max(min_value, range_delta + current) + ) + else: msg = "Entity does not support directive" raise AlexaInvalidDirectiveError(msg) diff --git a/homeassistant/components/alexa/resources.py b/homeassistant/components/alexa/resources.py index 061005252dc..09927321c36 100644 --- a/homeassistant/components/alexa/resources.py +++ b/homeassistant/components/alexa/resources.py @@ -266,9 +266,9 @@ class AlexaPresetResource(AlexaCapabilityResource): """Initialize an Alexa presetResource.""" super().__init__(labels) self._presets = [] - self._minimum_value = int(min_value) - self._maximum_value = int(max_value) - self._precision = int(precision) + self._minimum_value = min_value + self._maximum_value = max_value + self._precision = precision self._unit_of_measure = None if unit in AlexaGlobalCatalog.__dict__.values(): self._unit_of_measure = unit diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index 468652bf6d2..3d7c2b118e7 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -2740,3 +2740,175 @@ async def test_cover_semantics(hass): "states": ["Alexa.States.Open"], "range": {"minimumValue": 1, "maximumValue": 100}, } in state_mappings + + +async def test_input_number(hass): + """Test input_number discovery.""" + device = ( + "input_number.test_slider", + 30, + { + "initial": 30, + "min": -20, + "max": 35, + "step": 1, + "mode": "slider", + "friendly_name": "Test Slider", + }, + ) + appliance = await discovery_test(device, hass) + + assert appliance["endpointId"] == "input_number#test_slider" + assert appliance["displayCategories"][0] == "OTHER" + assert appliance["friendlyName"] == "Test Slider" + + capabilities = assert_endpoint_capabilities( + appliance, "Alexa.RangeController", "Alexa.EndpointHealth", "Alexa" + ) + + range_capability = get_capability( + capabilities, "Alexa.RangeController", "input_number.value" + ) + + capability_resources = range_capability["capabilityResources"] + assert capability_resources is not None + assert { + "@type": "text", + "value": {"text": "Value", "locale": "en-US"}, + } in capability_resources["friendlyNames"] + + configuration = range_capability["configuration"] + assert configuration is not None + + supported_range = configuration["supportedRange"] + assert supported_range["minimumValue"] == -20 + assert supported_range["maximumValue"] == 35 + assert supported_range["precision"] == 1 + + presets = configuration["presets"] + assert { + "rangeValue": 35, + "presetResources": { + "friendlyNames": [ + {"@type": "asset", "value": {"assetId": "Alexa.Value.Maximum"}} + ] + }, + } in presets + + assert { + "rangeValue": -20, + "presetResources": { + "friendlyNames": [ + {"@type": "asset", "value": {"assetId": "Alexa.Value.Minimum"}} + ] + }, + } in presets + + call, _ = await assert_request_calls_service( + "Alexa.RangeController", + "SetRangeValue", + "input_number#test_slider", + "input_number.set_value", + hass, + payload={"rangeValue": "10"}, + instance="input_number.value", + ) + assert call.data["value"] == 10 + + await assert_range_changes( + hass, + [(25, "-5"), (35, "5"), (-20, "-100"), (35, "100")], + "Alexa.RangeController", + "AdjustRangeValue", + "input_number#test_slider", + False, + "input_number.set_value", + "value", + instance="input_number.value", + ) + + +async def test_input_number_float(hass): + """Test input_number discovery.""" + device = ( + "input_number.test_slider_float", + 0.5, + { + "initial": 0.5, + "min": 0, + "max": 1, + "step": 0.01, + "mode": "slider", + "friendly_name": "Test Slider Float", + }, + ) + appliance = await discovery_test(device, hass) + + assert appliance["endpointId"] == "input_number#test_slider_float" + assert appliance["displayCategories"][0] == "OTHER" + assert appliance["friendlyName"] == "Test Slider Float" + + capabilities = assert_endpoint_capabilities( + appliance, "Alexa.RangeController", "Alexa.EndpointHealth", "Alexa" + ) + + range_capability = get_capability( + capabilities, "Alexa.RangeController", "input_number.value" + ) + + capability_resources = range_capability["capabilityResources"] + assert capability_resources is not None + assert { + "@type": "text", + "value": {"text": "Value", "locale": "en-US"}, + } in capability_resources["friendlyNames"] + + configuration = range_capability["configuration"] + assert configuration is not None + + supported_range = configuration["supportedRange"] + assert supported_range["minimumValue"] == 0 + assert supported_range["maximumValue"] == 1 + assert supported_range["precision"] == 0.01 + + presets = configuration["presets"] + assert { + "rangeValue": 1, + "presetResources": { + "friendlyNames": [ + {"@type": "asset", "value": {"assetId": "Alexa.Value.Maximum"}} + ] + }, + } in presets + + assert { + "rangeValue": 0, + "presetResources": { + "friendlyNames": [ + {"@type": "asset", "value": {"assetId": "Alexa.Value.Minimum"}} + ] + }, + } in presets + + call, _ = await assert_request_calls_service( + "Alexa.RangeController", + "SetRangeValue", + "input_number#test_slider_float", + "input_number.set_value", + hass, + payload={"rangeValue": "0.333"}, + instance="input_number.value", + ) + assert call.data["value"] == 0.333 + + await assert_range_changes( + hass, + [(0.4, "-0.1"), (0.6, "0.1"), (0, "-100"), (1, "100"), (0.51, "0.01")], + "Alexa.RangeController", + "AdjustRangeValue", + "input_number#test_slider_float", + False, + "input_number.set_value", + "value", + instance="input_number.value", + ) From e3a396491119fc8f436415ede7ea3543e6b5e8c7 Mon Sep 17 00:00:00 2001 From: Quentame Date: Mon, 23 Dec 2019 17:25:57 +0100 Subject: [PATCH 380/677] Add icon to Plex sensor (#30172) --- homeassistant/components/plex/sensor.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/homeassistant/components/plex/sensor.py b/homeassistant/components/plex/sensor.py index 2a994b08a7b..2aed57946eb 100644 --- a/homeassistant/components/plex/sensor.py +++ b/homeassistant/components/plex/sensor.py @@ -86,6 +86,11 @@ class PlexSensor(Entity): """Return the unit this state is expressed in.""" return "Watching" + @property + def icon(self): + """Return the icon of the sensor.""" + return "mdi:plex" + @property def device_state_attributes(self): """Return the state attributes.""" From 4c5801ee8c856c58ac74de9634857649336f299f Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Mon, 23 Dec 2019 17:34:57 +0100 Subject: [PATCH 381/677] Add cast to state of Dyson Air Quality Sensor (#30100) --- homeassistant/components/dyson/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/dyson/sensor.py b/homeassistant/components/dyson/sensor.py index c51f46c7790..2fdd3cd6c1f 100644 --- a/homeassistant/components/dyson/sensor.py +++ b/homeassistant/components/dyson/sensor.py @@ -195,5 +195,5 @@ class DysonAirQualitySensor(DysonSensor): def state(self): """Return Air Quality value.""" if self._device.environmental_state: - return self._device.environmental_state.volatil_organic_compounds + return int(self._device.environmental_state.volatil_organic_compounds) return None From f72ba0c716b945ad30ff80f52fb986bd60aaf3e9 Mon Sep 17 00:00:00 2001 From: Quentame Date: Mon, 23 Dec 2019 17:37:41 +0100 Subject: [PATCH 382/677] Add icons to Freebox sensors (#30132) --- homeassistant/components/freebox/sensor.py | 24 +++++++++++++--------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/freebox/sensor.py b/homeassistant/components/freebox/sensor.py index e8f96586300..61ec670d217 100644 --- a/homeassistant/components/freebox/sensor.py +++ b/homeassistant/components/freebox/sensor.py @@ -18,6 +18,8 @@ class FbxSensor(Entity): """Representation of a freebox sensor.""" _name = "generic" + _unit = None + _icon = None def __init__(self, fbx): """Initialize the sensor.""" @@ -30,6 +32,16 @@ class FbxSensor(Entity): """Return the name of the sensor.""" return self._name + @property + def unit_of_measurement(self): + """Return the unit of the sensor.""" + return self._unit + + @property + def icon(self): + """Return the icon of the sensor.""" + return self._icon + @property def state(self): """Return the state of the sensor.""" @@ -45,11 +57,7 @@ class FbxRXSensor(FbxSensor): _name = "Freebox download speed" _unit = "KB/s" - - @property - def unit_of_measurement(self): - """Define the unit.""" - return self._unit + _icon = "mdi:download-network" async def async_update(self): """Get the value from fetched datas.""" @@ -63,11 +71,7 @@ class FbxTXSensor(FbxSensor): _name = "Freebox upload speed" _unit = "KB/s" - - @property - def unit_of_measurement(self): - """Define the unit.""" - return self._unit + _icon = "mdi:upload-network" async def async_update(self): """Get the value from fetched datas.""" From f23cc16660c27b041fa034f9e06310dfd0caa882 Mon Sep 17 00:00:00 2001 From: Greg <34967045+gtdiehl@users.noreply.github.com> Date: Mon, 23 Dec 2019 12:35:54 -0800 Subject: [PATCH 383/677] Upgrade envoy_reader to 0.11.0 (#30179) --- homeassistant/components/enphase_envoy/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/enphase_envoy/manifest.json b/homeassistant/components/enphase_envoy/manifest.json index 21d154be23a..fc79659ff76 100644 --- a/homeassistant/components/enphase_envoy/manifest.json +++ b/homeassistant/components/enphase_envoy/manifest.json @@ -3,7 +3,7 @@ "name": "Enphase envoy", "documentation": "https://www.home-assistant.io/integrations/enphase_envoy", "requirements": [ - "envoy_reader==0.10.0" + "envoy_reader==0.11.0" ], "dependencies": [], "codeowners": [] diff --git a/requirements_all.txt b/requirements_all.txt index dddbcf7a3fd..252665f527b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -483,7 +483,7 @@ env_canada==0.0.30 # envirophat==0.0.6 # homeassistant.components.enphase_envoy -envoy_reader==0.10.0 +envoy_reader==0.11.0 # homeassistant.components.season ephem==3.7.7.0 From 059e8722b6af0c289d5455a2a212c56de16f1409 Mon Sep 17 00:00:00 2001 From: P-Verbrugge <41943098+P-Verbrugge@users.noreply.github.com> Date: Mon, 23 Dec 2019 21:42:44 +0100 Subject: [PATCH 384/677] Updated formatting of total_blocks value (#30170) The number of total blocks is always a round number. There can't be .1 or .11 blocks for example. The output is now always formatted with two decimals that are always 00. --- homeassistant/components/bitcoin/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/bitcoin/sensor.py b/homeassistant/components/bitcoin/sensor.py index b62bb434e85..bc8394d51a5 100644 --- a/homeassistant/components/bitcoin/sensor.py +++ b/homeassistant/components/bitcoin/sensor.py @@ -148,7 +148,7 @@ class BitcoinSensor(Entity): elif self.type == "total_btc": self._state = "{0:.2f}".format(stats.total_btc * 0.00000001) elif self.type == "total_blocks": - self._state = "{0:.2f}".format(stats.total_blocks) + self._state = "{0:.0f}".format(stats.total_blocks) elif self.type == "next_retarget": self._state = "{0:.2f}".format(stats.next_retarget) elif self.type == "estimated_transaction_volume_usd": From edce497a0d48ba25a8b5a5373c4eec2a85b4a4a7 Mon Sep 17 00:00:00 2001 From: Claudio Heckler Date: Mon, 23 Dec 2019 17:48:24 -0300 Subject: [PATCH 385/677] New date_time_utc display option added to the time_date sensor platform (#30158) --- homeassistant/components/time_date/sensor.py | 6 ++- tests/components/time_date/test_sensor.py | 49 +++++++++++++++++++- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/time_date/sensor.py b/homeassistant/components/time_date/sensor.py index 9edbdd3a9a1..1deb564133e 100644 --- a/homeassistant/components/time_date/sensor.py +++ b/homeassistant/components/time_date/sensor.py @@ -20,7 +20,8 @@ OPTION_TYPES = { "time": "Time", "date": "Date", "date_time": "Date & Time", - "date_time_iso": "Date & Time ISO", + "date_time_utc": "Date & Time (UTC)", + "date_time_iso": "Date & Time (ISO)", "time_date": "Time & Date", "beat": "Internet Time", "time_utc": "Time (UTC)", @@ -102,6 +103,7 @@ class TimeDateSensor(Entity): time = dt_util.as_local(time_date).strftime(TIME_STR_FORMAT) time_utc = time_date.strftime(TIME_STR_FORMAT) date = dt_util.as_local(time_date).date().isoformat() + date_utc = time_date.date().isoformat() # Calculate Swatch Internet Time. time_bmt = time_date + timedelta(hours=1) @@ -119,6 +121,8 @@ class TimeDateSensor(Entity): self._state = date elif self.type == "date_time": self._state = f"{date}, {time}" + elif self.type == "date_time_utc": + self._state = f"{date_utc}, {time_utc}" elif self.type == "time_date": self._state = f"{time}, {date}" elif self.type == "time_utc": diff --git a/tests/components/time_date/test_sensor.py b/tests/components/time_date/test_sensor.py index cdd3bd7e9f3..2aae99f93a5 100644 --- a/tests/components/time_date/test_sensor.py +++ b/tests/components/time_date/test_sensor.py @@ -1,4 +1,4 @@ -"""The tests for Kira sensor platform.""" +"""The tests for time_date sensor platform.""" import unittest from unittest.mock import patch @@ -9,7 +9,7 @@ from tests.common import get_test_home_assistant class TestTimeDateSensor(unittest.TestCase): - """Tests the Kira Sensor platform.""" + """Tests the time_date Sensor platform.""" # pylint: disable=invalid-name DEVICES = [] @@ -67,6 +67,14 @@ class TestTimeDateSensor(unittest.TestCase): device._update_internal_state(now) assert device.state == "00:54" + device = time_date.TimeDateSensor(self.hass, "date_time") + device._update_internal_state(now) + assert device.state == "2017-05-18, 00:54" + + device = time_date.TimeDateSensor(self.hass, "date_time_utc") + device._update_internal_state(now) + assert device.state == "2017-05-18, 00:54" + device = time_date.TimeDateSensor(self.hass, "beat") device._update_internal_state(now) assert device.state == "@079" @@ -75,6 +83,41 @@ class TestTimeDateSensor(unittest.TestCase): device._update_internal_state(now) assert device.state == "2017-05-18T00:54:00" + def test_states_non_default_timezone(self): + """Test states of sensors in a timezone other than UTC.""" + new_tz = dt_util.get_time_zone("America/New_York") + assert new_tz is not None + dt_util.set_default_time_zone(new_tz) + + now = dt_util.utc_from_timestamp(1495068856) + device = time_date.TimeDateSensor(self.hass, "time") + device._update_internal_state(now) + assert device.state == "20:54" + + device = time_date.TimeDateSensor(self.hass, "date") + device._update_internal_state(now) + assert device.state == "2017-05-17" + + device = time_date.TimeDateSensor(self.hass, "time_utc") + device._update_internal_state(now) + assert device.state == "00:54" + + device = time_date.TimeDateSensor(self.hass, "date_time") + device._update_internal_state(now) + assert device.state == "2017-05-17, 20:54" + + device = time_date.TimeDateSensor(self.hass, "date_time_utc") + device._update_internal_state(now) + assert device.state == "2017-05-18, 00:54" + + device = time_date.TimeDateSensor(self.hass, "beat") + device._update_internal_state(now) + assert device.state == "@079" + + device = time_date.TimeDateSensor(self.hass, "date_time_iso") + device._update_internal_state(now) + assert device.state == "2017-05-17T20:54:00" + # pylint: disable=no-member def test_timezone_intervals(self): """Test date sensor behavior in a timezone besides UTC.""" @@ -122,5 +165,7 @@ class TestTimeDateSensor(unittest.TestCase): assert device.icon == "mdi:calendar" device = time_date.TimeDateSensor(self.hass, "date_time") assert device.icon == "mdi:calendar-clock" + device = time_date.TimeDateSensor(self.hass, "date_time_utc") + assert device.icon == "mdi:calendar-clock" device = time_date.TimeDateSensor(self.hass, "date_time_iso") assert device.icon == "mdi:calendar-clock" From 3aa2ae170010cb5d9bcfdbed4f68a0a4072a99ce Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Mon, 23 Dec 2019 12:54:25 -0800 Subject: [PATCH 386/677] Enable config flow for Tesla (#28744) * build: bump teslajsonpy to 0.2.0 * Remove tests * feat: add config flow * feat: add async * perf: convert unnecessary async calls to sync * feat: add charger voltage and current sensor * feat: add options flow * build: bump teslajsonpy to 0.2.0 * Remove icon property * Revert climate mode change * Remove charger sensor * Simplify async_setup_platform * Update homeassistant/components/tesla/sensor.py Co-Authored-By: Paulus Schoutsen * Update homeassistant/components/tesla/binary_sensor.py Co-Authored-By: Paulus Schoutsen * Address requested changes * Fix pylint error * Address requested changes * Update codeowners * Fix pylint error * Address requested changes * Address requested change * Remove unnecessary check for existing config entry * Load scan_interval in async_setup_entry * Include coverage of config_flow * Add tests for full coverage * Address requested test changes * Remove unnecessary init lines * Remove unnecessary init Co-authored-by: Paulus Schoutsen --- .coveragerc | 9 +- CODEOWNERS | 2 +- .../components/tesla/.translations/en.json | 30 +++ homeassistant/components/tesla/__init__.py | 187 ++++++++++++++---- .../components/tesla/binary_sensor.py | 30 ++- homeassistant/components/tesla/climate.py | 29 ++- homeassistant/components/tesla/config_flow.py | 143 ++++++++++++++ .../components/tesla/device_tracker.py | 85 +++++--- homeassistant/components/tesla/lock.py | 23 ++- homeassistant/components/tesla/manifest.json | 3 +- homeassistant/components/tesla/sensor.py | 24 ++- homeassistant/components/tesla/strings.json | 30 +++ homeassistant/components/tesla/switch.py | 33 ++-- homeassistant/generated/config_flows.py | 1 + requirements_test_all.txt | 3 + tests/components/tesla/__init__.py | 1 + tests/components/tesla/test_config_flow.py | 160 +++++++++++++++ 17 files changed, 671 insertions(+), 122 deletions(-) create mode 100644 homeassistant/components/tesla/.translations/en.json create mode 100644 homeassistant/components/tesla/config_flow.py create mode 100644 homeassistant/components/tesla/strings.json create mode 100644 tests/components/tesla/__init__.py create mode 100644 tests/components/tesla/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index fc7a4691ef2..70d8f867e0e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -688,7 +688,14 @@ omit = homeassistant/components/telnet/switch.py homeassistant/components/temper/sensor.py homeassistant/components/tensorflow/image_processing.py - homeassistant/components/tesla/* + homeassistant/components/tesla/__init__.py + homeassistant/components/tesla/binary_sensor.py + homeassistant/components/tesla/climate.py + homeassistant/components/tesla/const.py + homeassistant/components/tesla/device_tracker.py + homeassistant/components/tesla/lock.py + homeassistant/components/tesla/sensor.py + homeassistant/components/tesla/switch.py homeassistant/components/tfiac/climate.py homeassistant/components/thermoworks_smoke/sensor.py homeassistant/components/thethingsnetwork/* diff --git a/CODEOWNERS b/CODEOWNERS index 4fbdca20686..a7d1d346c5f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -322,7 +322,7 @@ homeassistant/components/tahoma/* @philklei homeassistant/components/tautulli/* @ludeeus homeassistant/components/tellduslive/* @fredrike homeassistant/components/template/* @PhracturedBlue -homeassistant/components/tesla/* @zabuldon +homeassistant/components/tesla/* @zabuldon @alandtse homeassistant/components/tfiac/* @fredrike @mellado homeassistant/components/thethingsnetwork/* @fabaff homeassistant/components/threshold/* @fabaff diff --git a/homeassistant/components/tesla/.translations/en.json b/homeassistant/components/tesla/.translations/en.json new file mode 100644 index 00000000000..831406a0d63 --- /dev/null +++ b/homeassistant/components/tesla/.translations/en.json @@ -0,0 +1,30 @@ +{ + "config": { + "error": { + "connection_error": "Error connecting; check network and retry", + "identifier_exists": "Email already registered", + "invalid_credentials": "Invalid credentials", + "unknown_error": "Unknown error, please report log info" + }, + "step": { + "user": { + "data": { + "username": "Email Address", + "password": "Password" + }, + "description": "Please enter your information.", + "title": "Tesla - Configuration" + } + }, + "title": "Tesla" + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Seconds between scans" + } + } + } + } +} diff --git a/homeassistant/components/tesla/__init__.py b/homeassistant/components/tesla/__init__.py index a3d45eed01c..dbfe07271ee 100644 --- a/homeassistant/components/tesla/__init__.py +++ b/homeassistant/components/tesla/__init__.py @@ -1,21 +1,32 @@ """Support for Tesla cars.""" +import asyncio from collections import defaultdict import logging -from teslajsonpy import Controller as teslaAPI, TeslaException +from teslajsonpy import Controller as TeslaAPI, TeslaException import voluptuous as vol +from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( ATTR_BATTERY_LEVEL, + CONF_ACCESS_TOKEN, CONF_PASSWORD, CONF_SCAN_INTERVAL, + CONF_TOKEN, CONF_USERNAME, ) -from homeassistant.helpers import aiohttp_client, config_validation as cv, discovery +from homeassistant.core import callback +from homeassistant.helpers import aiohttp_client, config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import slugify -from .const import DOMAIN, TESLA_COMPONENTS +from .config_flow import ( + CannotConnect, + InvalidAuth, + configured_instances, + validate_input, +) +from .const import DATA_LISTENER, DOMAIN, TESLA_COMPONENTS _LOGGER = logging.getLogger(__name__) @@ -34,69 +45,144 @@ CONFIG_SCHEMA = vol.Schema( extra=vol.ALLOW_EXTRA, ) -NOTIFICATION_ID = "tesla_integration_notification" -NOTIFICATION_TITLE = "Tesla integration setup" + +@callback +def _async_save_tokens(hass, config_entry, access_token, refresh_token): + hass.config_entries.async_update_entry( + config_entry, + data={ + **config_entry.data, + CONF_ACCESS_TOKEN: access_token, + CONF_TOKEN: refresh_token, + }, + ) async def async_setup(hass, base_config): """Set up of Tesla component.""" - config = base_config.get(DOMAIN) - email = config.get(CONF_USERNAME) - password = config.get(CONF_PASSWORD) - update_interval = config.get(CONF_SCAN_INTERVAL) - if hass.data.get(DOMAIN) is None: + def _update_entry(email, data=None, options=None): + data = data or {} + options = options or {CONF_SCAN_INTERVAL: 300} + for entry in hass.config_entries.async_entries(DOMAIN): + if email != entry.title: + continue + hass.config_entries.async_update_entry(entry, data=data, options=options) + + config = base_config.get(DOMAIN) + if not config: + return True + email = config[CONF_USERNAME] + password = config[CONF_PASSWORD] + scan_interval = config[CONF_SCAN_INTERVAL] + if email in configured_instances(hass): try: - websession = aiohttp_client.async_get_clientsession(hass) - controller = teslaAPI( - websession, - email=email, - password=password, - update_interval=update_interval, - ) - await controller.connect(test_login=False) - hass.data[DOMAIN] = {"controller": controller, "devices": defaultdict(list)} - _LOGGER.debug("Connected to the Tesla API.") - except TeslaException as ex: - if ex.code == 401: - hass.components.persistent_notification.create( - "Error:
Please check username and password." - "You will need to restart Home Assistant after fixing.", - title=NOTIFICATION_TITLE, - notification_id=NOTIFICATION_ID, - ) - else: - hass.components.persistent_notification.create( - "Error:
Can't communicate with Tesla API.
" - "Error code: {} Reason: {}" - "You will need to restart Home Assistant after fixing." - "".format(ex.code, ex.message), - title=NOTIFICATION_TITLE, - notification_id=NOTIFICATION_ID, - ) - _LOGGER.error("Unable to communicate with Tesla API: %s", ex.message) + info = await validate_input(hass, config) + except (CannotConnect, InvalidAuth): return False - all_devices = controller.get_homeassistant_components() + _update_entry( + email, + data={ + CONF_ACCESS_TOKEN: info[CONF_ACCESS_TOKEN], + CONF_TOKEN: info[CONF_TOKEN], + }, + options={CONF_SCAN_INTERVAL: scan_interval}, + ) + else: + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data={CONF_USERNAME: email, CONF_PASSWORD: password}, + ) + ) + hass.data.setdefault(DOMAIN, {}) + hass.data[DOMAIN][email] = {CONF_SCAN_INTERVAL: scan_interval} + return True + + +async def async_setup_entry(hass, config_entry): + """Set up Tesla as config entry.""" + + hass.data.setdefault(DOMAIN, {}) + config = config_entry.data + websession = aiohttp_client.async_get_clientsession(hass) + email = config_entry.title + if email in hass.data[DOMAIN] and CONF_SCAN_INTERVAL in hass.data[DOMAIN][email]: + scan_interval = hass.data[DOMAIN][email][CONF_SCAN_INTERVAL] + hass.config_entries.async_update_entry( + config_entry, options={CONF_SCAN_INTERVAL: scan_interval} + ) + hass.data[DOMAIN].pop(email) + try: + controller = TeslaAPI( + websession, + refresh_token=config[CONF_TOKEN], + update_interval=config_entry.options.get(CONF_SCAN_INTERVAL, 300), + ) + (refresh_token, access_token) = await controller.connect() + except TeslaException as ex: + _LOGGER.error("Unable to communicate with Tesla API: %s", ex.message) + return False + _async_save_tokens(hass, config_entry, access_token, refresh_token) + entry_data = hass.data[DOMAIN][config_entry.entry_id] = { + "controller": controller, + "devices": defaultdict(list), + DATA_LISTENER: [config_entry.add_update_listener(update_listener)], + } + _LOGGER.debug("Connected to the Tesla API.") + all_devices = entry_data["controller"].get_homeassistant_components() + if not all_devices: return False for device in all_devices: - hass.data[DOMAIN]["devices"][device.hass_type].append(device) + entry_data["devices"][device.hass_type].append(device) for component in TESLA_COMPONENTS: + _LOGGER.debug("Loading %s", component) hass.async_create_task( - discovery.async_load_platform(hass, component, DOMAIN, {}, base_config) + hass.config_entries.async_forward_entry_setup(config_entry, component) ) return True +async def async_unload_entry(hass, config_entry) -> bool: + """Unload a config entry.""" + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(config_entry, component) + for component in TESLA_COMPONENTS + ] + ) + for listener in hass.data[DOMAIN][config_entry.entry_id][DATA_LISTENER]: + listener() + username = config_entry.title + hass.data[DOMAIN].pop(config_entry.entry_id) + _LOGGER.debug("Unloaded entry for %s", username) + return True + + +async def update_listener(hass, config_entry): + """Update when config_entry options update.""" + controller = hass.data[DOMAIN][config_entry.entry_id]["controller"] + old_update_interval = controller.update_interval + controller.update_interval = config_entry.options.get(CONF_SCAN_INTERVAL) + _LOGGER.debug( + "Changing scan_interval from %s to %s", + old_update_interval, + controller.update_interval, + ) + + class TeslaDevice(Entity): """Representation of a Tesla device.""" - def __init__(self, tesla_device, controller): + def __init__(self, tesla_device, controller, config_entry): """Initialise the Tesla device.""" self.tesla_device = tesla_device self.controller = controller + self.config_entry = config_entry self._name = self.tesla_device.name self.tesla_id = slugify(self.tesla_device.uniq_name) self._attributes = {} @@ -124,6 +210,17 @@ class TeslaDevice(Entity): attr[ATTR_BATTERY_LEVEL] = self.tesla_device.battery_level() return attr + @property + def device_info(self): + """Return the device_info of the device.""" + return { + "identifiers": {(DOMAIN, self.tesla_device.id())}, + "name": self.tesla_device.car_name(), + "manufacturer": "Tesla", + "model": self.tesla_device.car_type, + "sw_version": self.tesla_device.car_version, + } + async def async_added_to_hass(self): """Register state update callback.""" pass @@ -134,4 +231,10 @@ class TeslaDevice(Entity): async def async_update(self): """Update the state of the device.""" + if self.controller.is_token_refreshed(): + (refresh_token, access_token) = self.controller.get_tokens() + _async_save_tokens( + self.hass, self.config_entry, access_token, refresh_token + ) + _LOGGER.debug("Saving new tokens in config_entry") await self.tesla_device.async_update() diff --git a/homeassistant/components/tesla/binary_sensor.py b/homeassistant/components/tesla/binary_sensor.py index 738533a9b56..8f610d960b3 100644 --- a/homeassistant/components/tesla/binary_sensor.py +++ b/homeassistant/components/tesla/binary_sensor.py @@ -8,21 +8,35 @@ from . import DOMAIN as TESLA_DOMAIN, TeslaDevice _LOGGER = logging.getLogger(__name__) -async def async_setup_platform(hass, config, add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Tesla binary sensor.""" - devices = [ - TeslaBinarySensor(device, hass.data[TESLA_DOMAIN]["controller"], "connectivity") - for device in hass.data[TESLA_DOMAIN]["devices"]["binary_sensor"] - ] - add_entities(devices, True) + pass + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the Tesla binary_sensors by config_entry.""" + async_add_entities( + [ + TeslaBinarySensor( + device, + hass.data[TESLA_DOMAIN][config_entry.entry_id]["controller"], + "connectivity", + config_entry, + ) + for device in hass.data[TESLA_DOMAIN][config_entry.entry_id]["devices"][ + "binary_sensor" + ] + ], + True, + ) class TeslaBinarySensor(TeslaDevice, BinarySensorDevice): """Implement an Tesla binary sensor for parking and charger.""" - def __init__(self, tesla_device, controller, sensor_type): + def __init__(self, tesla_device, controller, sensor_type, config_entry): """Initialise of a Tesla binary sensor.""" - super().__init__(tesla_device, controller) + super().__init__(tesla_device, controller, config_entry) self._state = False self._sensor_type = sensor_type diff --git a/homeassistant/components/tesla/climate.py b/homeassistant/components/tesla/climate.py index 85fd8a8e258..d3c87035c9c 100644 --- a/homeassistant/components/tesla/climate.py +++ b/homeassistant/components/tesla/climate.py @@ -16,21 +16,34 @@ _LOGGER = logging.getLogger(__name__) SUPPORT_HVAC = [HVAC_MODE_HEAT, HVAC_MODE_OFF] -async def async_setup_platform(hass, config, add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Tesla climate platform.""" - devices = [ - TeslaThermostat(device, hass.data[TESLA_DOMAIN]["controller"]) - for device in hass.data[TESLA_DOMAIN]["devices"]["climate"] - ] - add_entities(devices, True) + pass + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the Tesla binary_sensors by config_entry.""" + async_add_entities( + [ + TeslaThermostat( + device, + hass.data[TESLA_DOMAIN][config_entry.entry_id]["controller"], + config_entry, + ) + for device in hass.data[TESLA_DOMAIN][config_entry.entry_id]["devices"][ + "climate" + ] + ], + True, + ) class TeslaThermostat(TeslaDevice, ClimateDevice): """Representation of a Tesla climate.""" - def __init__(self, tesla_device, controller): + def __init__(self, tesla_device, controller, config_entry): """Initialize the Tesla device.""" - super().__init__(tesla_device, controller) + super().__init__(tesla_device, controller, config_entry) self._target_temperature = None self._temperature = None diff --git a/homeassistant/components/tesla/config_flow.py b/homeassistant/components/tesla/config_flow.py new file mode 100644 index 00000000000..2d2bc0158d2 --- /dev/null +++ b/homeassistant/components/tesla/config_flow.py @@ -0,0 +1,143 @@ +"""Tesla Config Flow.""" +import logging + +from teslajsonpy import Controller as TeslaAPI, TeslaException +import voluptuous as vol + +from homeassistant import config_entries, core, exceptions +from homeassistant.const import ( + CONF_ACCESS_TOKEN, + CONF_PASSWORD, + CONF_SCAN_INTERVAL, + CONF_TOKEN, + CONF_USERNAME, +) +from homeassistant.core import callback +from homeassistant.helpers import aiohttp_client, config_validation as cv + +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + +DATA_SCHEMA = vol.Schema( + {vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str} +) + + +@callback +def configured_instances(hass): + """Return a set of configured Tesla instances.""" + return set(entry.title for entry in hass.config_entries.async_entries(DOMAIN)) + + +class TeslaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Tesla.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + + async def async_step_import(self, import_config): + """Import a config entry from configuration.yaml.""" + return await self.async_step_user(import_config) + + async def async_step_user(self, user_input=None): + """Handle the start of the config flow.""" + + if not user_input: + return self.async_show_form( + step_id="user", + data_schema=DATA_SCHEMA, + errors={}, + description_placeholders={}, + ) + + if user_input[CONF_USERNAME] in configured_instances(self.hass): + return self.async_show_form( + step_id="user", + data_schema=DATA_SCHEMA, + errors={CONF_USERNAME: "identifier_exists"}, + description_placeholders={}, + ) + + try: + info = await validate_input(self.hass, user_input) + except CannotConnect: + return self.async_show_form( + step_id="user", + data_schema=DATA_SCHEMA, + errors={"base": "connection_error"}, + description_placeholders={}, + ) + except InvalidAuth: + return self.async_show_form( + step_id="user", + data_schema=DATA_SCHEMA, + errors={"base": "invalid_credentials"}, + description_placeholders={}, + ) + return self.async_create_entry(title=user_input[CONF_USERNAME], data=info) + + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Get the options flow for this handler.""" + return OptionsFlowHandler(config_entry) + + +class OptionsFlowHandler(config_entries.OptionsFlow): + """Handle a option flow for Tesla.""" + + def __init__(self, config_entry: config_entries.ConfigEntry): + """Initialize options flow.""" + self.config_entry = config_entry + + async def async_step_init(self, user_input=None): + """Handle options flow.""" + if user_input is not None: + return self.async_create_entry(title="", data=user_input) + + data_schema = vol.Schema( + { + vol.Optional( + CONF_SCAN_INTERVAL, + default=self.config_entry.options.get(CONF_SCAN_INTERVAL, 300), + ): vol.All(cv.positive_int, vol.Clamp(min=300)) + } + ) + return self.async_show_form(step_id="init", data_schema=data_schema) + + +async def validate_input(hass: core.HomeAssistant, data): + """Validate the user input allows us to connect. + + Data has the keys from DATA_SCHEMA with values provided by the user. + """ + + config = {} + websession = aiohttp_client.async_get_clientsession(hass) + try: + controller = TeslaAPI( + websession, + email=data[CONF_USERNAME], + password=data[CONF_PASSWORD], + update_interval=300, + ) + (config[CONF_TOKEN], config[CONF_ACCESS_TOKEN]) = await controller.connect( + test_login=True + ) + except TeslaException as ex: + if ex.code == 401: + _LOGGER.error("Invalid credentials: %s", ex) + raise InvalidAuth() + _LOGGER.error("Unable to communicate with Tesla API: %s", ex) + raise CannotConnect() + _LOGGER.debug("Credentials successfully connected to the Tesla API") + return config + + +class CannotConnect(exceptions.HomeAssistantError): + """Error to indicate we cannot connect.""" + + +class InvalidAuth(exceptions.HomeAssistantError): + """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/tesla/device_tracker.py b/homeassistant/components/tesla/device_tracker.py index c205cc587eb..08e5d58ba6e 100644 --- a/homeassistant/components/tesla/device_tracker.py +++ b/homeassistant/components/tesla/device_tracker.py @@ -1,45 +1,70 @@ """Support for tracking Tesla cars.""" import logging -from homeassistant.helpers.event import async_track_utc_time_change -from homeassistant.util import slugify +from homeassistant.components.device_tracker import SOURCE_TYPE_GPS +from homeassistant.components.device_tracker.config_entry import TrackerEntity -from . import DOMAIN as TESLA_DOMAIN +from . import DOMAIN as TESLA_DOMAIN, TeslaDevice _LOGGER = logging.getLogger(__name__) -async def async_setup_scanner(hass, config, async_see, discovery_info=None): - """Set up the Tesla tracker.""" - tracker = TeslaDeviceTracker( - hass, config, async_see, hass.data[TESLA_DOMAIN]["devices"]["devices_tracker"] - ) - await tracker.update_info() - async_track_utc_time_change(hass, tracker.update_info, second=range(0, 60, 30)) - return True +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the Tesla binary_sensors by config_entry.""" + entities = [ + TeslaDeviceEntity( + device, + hass.data[TESLA_DOMAIN][config_entry.entry_id]["controller"], + config_entry, + ) + for device in hass.data[TESLA_DOMAIN][config_entry.entry_id]["devices"][ + "devices_tracker" + ] + ] + async_add_entities(entities, True) -class TeslaDeviceTracker: +class TeslaDeviceEntity(TeslaDevice, TrackerEntity): """A class representing a Tesla device.""" - def __init__(self, hass, config, see, tesla_devices): + def __init__(self, tesla_device, controller, config_entry): """Initialize the Tesla device scanner.""" - self.hass = hass - self.see = see - self.devices = tesla_devices + super().__init__(tesla_device, controller, config_entry) + self._latitude = None + self._longitude = None + self._attributes = {"trackr_id": self.unique_id} + self._listener = None - async def update_info(self, now=None): + async def async_update(self): """Update the device info.""" - for device in self.devices: - await device.async_update() - name = device.name - _LOGGER.debug("Updating device position: %s", name) - dev_id = slugify(device.uniq_name) - location = device.get_location() - if location: - lat = location["latitude"] - lon = location["longitude"] - attrs = {"trackr_id": dev_id, "id": dev_id, "name": name} - await self.see( - dev_id=dev_id, host_name=name, gps=(lat, lon), attributes=attrs - ) + _LOGGER.debug("Updating device position: %s", self.name) + await super().async_update() + location = self.tesla_device.get_location() + if location: + self._latitude = location["latitude"] + self._longitude = location["longitude"] + self._attributes = { + "trackr_id": self.unique_id, + "heading": location["heading"], + "speed": location["speed"], + } + + @property + def latitude(self) -> float: + """Return latitude value of the device.""" + return self._latitude + + @property + def longitude(self) -> float: + """Return longitude value of the device.""" + return self._longitude + + @property + def should_poll(self): + """Return whether polling is needed.""" + return True + + @property + def source_type(self): + """Return the source type, eg gps or router, of the device.""" + return SOURCE_TYPE_GPS diff --git a/homeassistant/components/tesla/lock.py b/homeassistant/components/tesla/lock.py index 5e97602357d..33eed8cf7c1 100644 --- a/homeassistant/components/tesla/lock.py +++ b/homeassistant/components/tesla/lock.py @@ -9,22 +9,31 @@ from . import DOMAIN as TESLA_DOMAIN, TeslaDevice _LOGGER = logging.getLogger(__name__) -async def async_setup_platform(hass, config, add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Tesla lock platform.""" - devices = [ - TeslaLock(device, hass.data[TESLA_DOMAIN]["controller"]) - for device in hass.data[TESLA_DOMAIN]["devices"]["lock"] + pass + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the Tesla binary_sensors by config_entry.""" + entities = [ + TeslaLock( + device, + hass.data[TESLA_DOMAIN][config_entry.entry_id]["controller"], + config_entry, + ) + for device in hass.data[TESLA_DOMAIN][config_entry.entry_id]["devices"]["lock"] ] - add_entities(devices, True) + async_add_entities(entities, True) class TeslaLock(TeslaDevice, LockDevice): """Representation of a Tesla door lock.""" - def __init__(self, tesla_device, controller): + def __init__(self, tesla_device, controller, config_entry): """Initialise of the lock.""" self._state = None - super().__init__(tesla_device, controller) + super().__init__(tesla_device, controller, config_entry) async def async_lock(self, **kwargs): """Send the lock command.""" diff --git a/homeassistant/components/tesla/manifest.json b/homeassistant/components/tesla/manifest.json index a2021092413..4a869ab0a41 100644 --- a/homeassistant/components/tesla/manifest.json +++ b/homeassistant/components/tesla/manifest.json @@ -1,8 +1,9 @@ { "domain": "tesla", "name": "Tesla", + "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/tesla", "requirements": ["teslajsonpy==0.2.0"], "dependencies": [], - "codeowners": ["@zabuldon"] + "codeowners": ["@zabuldon", "@alandtse"] } diff --git a/homeassistant/components/tesla/sensor.py b/homeassistant/components/tesla/sensor.py index 1cce37f232a..d93d3fa45d6 100644 --- a/homeassistant/components/tesla/sensor.py +++ b/homeassistant/components/tesla/sensor.py @@ -14,30 +14,34 @@ from . import DOMAIN as TESLA_DOMAIN, TeslaDevice _LOGGER = logging.getLogger(__name__) -async def async_setup_platform(hass, config, add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Tesla sensor platform.""" - controller = hass.data[TESLA_DOMAIN]["devices"]["controller"] - devices = [] + pass - for device in hass.data[TESLA_DOMAIN]["devices"]["sensor"]: + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the Tesla binary_sensors by config_entry.""" + controller = hass.data[TESLA_DOMAIN][config_entry.entry_id]["controller"] + entities = [] + for device in hass.data[TESLA_DOMAIN][config_entry.entry_id]["devices"]["sensor"]: if device.bin_type == 0x4: - devices.append(TeslaSensor(device, controller, "inside")) - devices.append(TeslaSensor(device, controller, "outside")) + entities.append(TeslaSensor(device, controller, config_entry, "inside")) + entities.append(TeslaSensor(device, controller, config_entry, "outside")) elif device.bin_type in [0xA, 0xB, 0x5]: - devices.append(TeslaSensor(device, controller)) - add_entities(devices, True) + entities.append(TeslaSensor(device, controller, config_entry)) + async_add_entities(entities, True) class TeslaSensor(TeslaDevice, Entity): """Representation of Tesla sensors.""" - def __init__(self, tesla_device, controller, sensor_type=None): + def __init__(self, tesla_device, controller, config_entry, sensor_type=None): """Initialize of the sensor.""" self.current_value = None self._unit = None self.last_changed_time = None self.type = sensor_type - super().__init__(tesla_device, controller) + super().__init__(tesla_device, controller, config_entry) if self.type: self._name = f"{self.tesla_device.name} ({self.type})" diff --git a/homeassistant/components/tesla/strings.json b/homeassistant/components/tesla/strings.json new file mode 100644 index 00000000000..831406a0d63 --- /dev/null +++ b/homeassistant/components/tesla/strings.json @@ -0,0 +1,30 @@ +{ + "config": { + "error": { + "connection_error": "Error connecting; check network and retry", + "identifier_exists": "Email already registered", + "invalid_credentials": "Invalid credentials", + "unknown_error": "Unknown error, please report log info" + }, + "step": { + "user": { + "data": { + "username": "Email Address", + "password": "Password" + }, + "description": "Please enter your information.", + "title": "Tesla - Configuration" + } + }, + "title": "Tesla" + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Seconds between scans" + } + } + } + } +} diff --git a/homeassistant/components/tesla/switch.py b/homeassistant/components/tesla/switch.py index 5f432875aeb..3fc424e390d 100644 --- a/homeassistant/components/tesla/switch.py +++ b/homeassistant/components/tesla/switch.py @@ -9,26 +9,31 @@ from . import DOMAIN as TESLA_DOMAIN, TeslaDevice _LOGGER = logging.getLogger(__name__) -async def async_setup_platform(hass, config, add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Tesla switch platform.""" - controller = hass.data[TESLA_DOMAIN]["controller"] - devices = [] - for device in hass.data[TESLA_DOMAIN]["devices"]["switch"]: + pass + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the Tesla binary_sensors by config_entry.""" + controller = hass.data[TESLA_DOMAIN][config_entry.entry_id]["controller"] + entities = [] + for device in hass.data[TESLA_DOMAIN][config_entry.entry_id]["devices"]["switch"]: if device.bin_type == 0x8: - devices.append(ChargerSwitch(device, controller)) - devices.append(UpdateSwitch(device, controller)) + entities.append(ChargerSwitch(device, controller, config_entry)) + entities.append(UpdateSwitch(device, controller, config_entry)) elif device.bin_type == 0x9: - devices.append(RangeSwitch(device, controller)) - add_entities(devices, True) + entities.append(RangeSwitch(device, controller, config_entry)) + async_add_entities(entities, True) class ChargerSwitch(TeslaDevice, SwitchDevice): """Representation of a Tesla charger switch.""" - def __init__(self, tesla_device, controller): + def __init__(self, tesla_device, controller, config_entry): """Initialise of the switch.""" self._state = None - super().__init__(tesla_device, controller) + super().__init__(tesla_device, controller, config_entry) async def async_turn_on(self, **kwargs): """Send the on command.""" @@ -55,10 +60,10 @@ class ChargerSwitch(TeslaDevice, SwitchDevice): class RangeSwitch(TeslaDevice, SwitchDevice): """Representation of a Tesla max range charging switch.""" - def __init__(self, tesla_device, controller): + def __init__(self, tesla_device, controller, config_entry): """Initialise the switch.""" self._state = None - super().__init__(tesla_device, controller) + super().__init__(tesla_device, controller, config_entry) async def async_turn_on(self, **kwargs): """Send the on command.""" @@ -85,11 +90,11 @@ class RangeSwitch(TeslaDevice, SwitchDevice): class UpdateSwitch(TeslaDevice, SwitchDevice): """Representation of a Tesla update switch.""" - def __init__(self, tesla_device, controller): + def __init__(self, tesla_device, controller, config_entry): """Initialise the switch.""" self._state = None tesla_device.type = "update switch" - super().__init__(tesla_device, controller) + super().__init__(tesla_device, controller, config_entry) self._name = self._name.replace("charger", "update") self.tesla_id = self.tesla_id.replace("charger", "update") diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 2b3940000e7..88ff92a57b0 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -73,6 +73,7 @@ FLOWS = [ "sonos", "starline", "tellduslive", + "tesla", "toon", "tplink", "traccar", diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e5361e7464e..2b7101dfa25 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -598,6 +598,9 @@ sunwatcher==0.2.1 # homeassistant.components.tellduslive tellduslive==0.10.10 +# homeassistant.components.tesla +teslajsonpy==0.2.0 + # homeassistant.components.toon toonapilib==3.2.4 diff --git a/tests/components/tesla/__init__.py b/tests/components/tesla/__init__.py new file mode 100644 index 00000000000..89b1e1c0c54 --- /dev/null +++ b/tests/components/tesla/__init__.py @@ -0,0 +1 @@ +"""Tests for the Tesla integration.""" diff --git a/tests/components/tesla/test_config_flow.py b/tests/components/tesla/test_config_flow.py new file mode 100644 index 00000000000..b6eeff54a50 --- /dev/null +++ b/tests/components/tesla/test_config_flow.py @@ -0,0 +1,160 @@ +"""Test the Tesla config flow.""" +from unittest.mock import patch + +from teslajsonpy import TeslaException + +from homeassistant import config_entries, data_entry_flow, setup +from homeassistant.components.tesla.const import DOMAIN +from homeassistant.const import ( + CONF_ACCESS_TOKEN, + CONF_PASSWORD, + CONF_SCAN_INTERVAL, + CONF_TOKEN, + CONF_USERNAME, +) + +from tests.common import MockConfigEntry, mock_coro + + +async def test_form(hass): + """Test we get the form.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + with patch( + "homeassistant.components.tesla.config_flow.TeslaAPI.connect", + return_value=mock_coro(("test-refresh-token", "test-access-token")), + ), patch( + "homeassistant.components.tesla.async_setup", return_value=mock_coro(True) + ) as mock_setup, patch( + "homeassistant.components.tesla.async_setup_entry", return_value=mock_coro(True) + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_PASSWORD: "test", CONF_USERNAME: "test@email.com"} + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result2["title"] == "test@email.com" + assert result2["data"] == { + "token": "test-refresh-token", + "access_token": "test-access-token", + } + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_invalid_auth(hass): + """Test we handle invalid auth.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.tesla.config_flow.TeslaAPI.connect", + side_effect=TeslaException(401), + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_USERNAME: "test-username", CONF_PASSWORD: "test-password"}, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "invalid_credentials"} + + +async def test_form_cannot_connect(hass): + """Test we handle cannot connect error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.tesla.config_flow.TeslaAPI.connect", + side_effect=TeslaException(code=404), + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_PASSWORD: "test-password", CONF_USERNAME: "test-username"}, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "connection_error"} + + +async def test_form_repeat_identifier(hass): + """Test we handle repeat identifiers.""" + entry = MockConfigEntry(domain=DOMAIN, title="test-username", data={}, options=None) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + with patch( + "homeassistant.components.tesla.config_flow.TeslaAPI.connect", + return_value=mock_coro(("test-refresh-token", "test-access-token")), + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_USERNAME: "test-username", CONF_PASSWORD: "test-password"}, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {CONF_USERNAME: "identifier_exists"} + + +async def test_import(hass): + """Test import step.""" + + with patch( + "homeassistant.components.tesla.config_flow.TeslaAPI.connect", + return_value=mock_coro(("test-refresh-token", "test-access-token")), + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={CONF_PASSWORD: "test-password", CONF_USERNAME: "test-username"}, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "test-username" + assert result["data"][CONF_ACCESS_TOKEN] == "test-access-token" + assert result["data"][CONF_TOKEN] == "test-refresh-token" + assert result["description_placeholders"] is None + + +async def test_option_flow(hass): + """Test config flow options.""" + entry = MockConfigEntry(domain=DOMAIN, data={}, options=None) + entry.add_to_hass(hass) + + result = await hass.config_entries.options.flow.async_init(entry.entry_id) + + assert result["type"] == "form" + assert result["step_id"] == "init" + + result = await hass.config_entries.options.flow.async_configure( + result["flow_id"], user_input={CONF_SCAN_INTERVAL: 350} + ) + assert result["type"] == "create_entry" + assert result["data"] == {CONF_SCAN_INTERVAL: 350} + + +async def test_option_flow_input_floor(hass): + """Test config flow options.""" + entry = MockConfigEntry(domain=DOMAIN, data={}, options=None) + entry.add_to_hass(hass) + + result = await hass.config_entries.options.flow.async_init(entry.entry_id) + + assert result["type"] == "form" + assert result["step_id"] == "init" + + result = await hass.config_entries.options.flow.async_configure( + result["flow_id"], user_input={CONF_SCAN_INTERVAL: 1} + ) + assert result["type"] == "create_entry" + assert result["data"] == {CONF_SCAN_INTERVAL: 300} From 85d2ba047b9ec16fa54e0b5e84cb2a94b1f98297 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Mon, 23 Dec 2019 19:11:35 -0500 Subject: [PATCH 387/677] Protect against bad data stored in ZHA (#30183) --- homeassistant/components/zha/api.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zha/api.py b/homeassistant/components/zha/api.py index d9c1db2ae5d..1294fcaedbd 100644 --- a/homeassistant/components/zha/api.py +++ b/homeassistant/components/zha/api.py @@ -449,7 +449,11 @@ async def remove_group(group, zha_gateway): group.group_id ) ) - await asyncio.gather(*tasks) + if tasks: + await asyncio.gather(*tasks) + else: + # we have members but none are tracked by ZHA for whatever reason + zha_gateway.application_controller.groups.pop(group.group_id) else: zha_gateway.application_controller.groups.pop(group.group_id) From 8add6c5f2e968b6fea2814bd5fecb1f496a67f0d Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Tue, 24 Dec 2019 00:32:12 +0000 Subject: [PATCH 388/677] [ci skip] Translation update --- .../components/tesla/.translations/en.json | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/tesla/.translations/en.json b/homeassistant/components/tesla/.translations/en.json index 831406a0d63..8c43f28e04e 100644 --- a/homeassistant/components/tesla/.translations/en.json +++ b/homeassistant/components/tesla/.translations/en.json @@ -1,30 +1,30 @@ { - "config": { - "error": { - "connection_error": "Error connecting; check network and retry", - "identifier_exists": "Email already registered", - "invalid_credentials": "Invalid credentials", - "unknown_error": "Unknown error, please report log info" - }, - "step": { - "user": { - "data": { - "username": "Email Address", - "password": "Password" + "config": { + "error": { + "connection_error": "Error connecting; check network and retry", + "identifier_exists": "Email already registered", + "invalid_credentials": "Invalid credentials", + "unknown_error": "Unknown error, please report log info" }, - "description": "Please enter your information.", - "title": "Tesla - Configuration" - } + "step": { + "user": { + "data": { + "password": "Password", + "username": "Email Address" + }, + "description": "Please enter your information.", + "title": "Tesla - Configuration" + } + }, + "title": "Tesla" }, - "title": "Tesla" - }, - "options": { - "step": { - "init": { - "data": { - "scan_interval": "Seconds between scans" + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Seconds between scans" + } + } } - } } - } -} +} \ No newline at end of file From 783672d305b8390d6e3ea2f49622b644de6c46ef Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 24 Dec 2019 14:11:01 +0100 Subject: [PATCH 389/677] Upgrade zeroconf to 0.24.3 (#30187) --- homeassistant/components/zeroconf/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/zeroconf/manifest.json b/homeassistant/components/zeroconf/manifest.json index 1dc1618842d..720ffc3e69e 100644 --- a/homeassistant/components/zeroconf/manifest.json +++ b/homeassistant/components/zeroconf/manifest.json @@ -3,7 +3,7 @@ "name": "Zeroconf", "documentation": "https://www.home-assistant.io/integrations/zeroconf", "requirements": [ - "zeroconf==0.24.2" + "zeroconf==0.24.3" ], "dependencies": [ "api" diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 5d14a6e30d6..363e175ec5a 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -24,7 +24,7 @@ ruamel.yaml==0.15.100 sqlalchemy==1.3.12 voluptuous-serialize==2.3.0 voluptuous==0.11.7 -zeroconf==0.24.2 +zeroconf==0.24.3 pycryptodome>=3.6.6 diff --git a/requirements_all.txt b/requirements_all.txt index 252665f527b..49a9fa49e2e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2088,7 +2088,7 @@ youtube_dl==2019.11.28 zengge==0.2 # homeassistant.components.zeroconf -zeroconf==0.24.2 +zeroconf==0.24.3 # homeassistant.components.zha zha-quirks==0.0.28 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2b7101dfa25..422af61d9df 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -656,7 +656,7 @@ ya_ma==0.3.8 yahooweather==0.10 # homeassistant.components.zeroconf -zeroconf==0.24.2 +zeroconf==0.24.3 # homeassistant.components.zha zha-quirks==0.0.28 From 2113d65c06a1b4bdae4f7ebc80691610c998dfbd Mon Sep 17 00:00:00 2001 From: Quentame Date: Tue, 24 Dec 2019 14:14:58 +0100 Subject: [PATCH 390/677] Fix AdGuard Home safe search sensor name (#30171) * Fix AdGuard Home safe search sensor name * Fix typo --- homeassistant/components/adguard/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/adguard/sensor.py b/homeassistant/components/adguard/sensor.py index e0c86e42d26..c818752ad2f 100644 --- a/homeassistant/components/adguard/sensor.py +++ b/homeassistant/components/adguard/sensor.py @@ -178,7 +178,7 @@ class AdGuardHomeReplacedSafeSearchSensor(AdGuardHomeSensor): """Initialize AdGuard Home sensor.""" super().__init__( adguard, - "Searches Safe Search Enforced", + "AdGuard Safe Searches Enforced", "mdi:shield-search", "enforced_safesearch", "requests", From f32eaa2fdd4a68fc53a0b1e30d509f09f8ae675f Mon Sep 17 00:00:00 2001 From: Tim Rightnour <6556271+garbled1@users.noreply.github.com> Date: Tue, 24 Dec 2019 06:41:29 -0700 Subject: [PATCH 391/677] Add onewire devices and owserver remote host support (#29948) * Add support for HobbyBoards sensors: Hub, Humidity, Moisture, PulseCounter Add support for an owserver running on a remote host * Cleanup and fixes for style/etc * Forgot to convert to f-strings. * Update with changes requested by @MartinHjelmare * Pylint * Fix to None * Reverse logic for hobbyboard test to eliminate pylint complaint * Modified manifest to list myself as codeowner * Move some functions down into OneWireProxy instead of the top OneWire * Oops, missed some decode()'s * And another! --- CODEOWNERS | 1 + .../components/onewire/manifest.json | 8 +- homeassistant/components/onewire/sensor.py | 130 +++++++++++++++++- requirements_all.txt | 3 + 4 files changed, 137 insertions(+), 5 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index a7d1d346c5f..52f13748303 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -235,6 +235,7 @@ homeassistant/components/obihai/* @dshokouhi homeassistant/components/ohmconnect/* @robbiet480 homeassistant/components/ombi/* @larssont homeassistant/components/onboarding/* @home-assistant/core +homeassistant/components/onewire/* @garbled1 homeassistant/components/opentherm_gw/* @mvn23 homeassistant/components/openuv/* @bachya homeassistant/components/openweathermap/* @fabaff diff --git a/homeassistant/components/onewire/manifest.json b/homeassistant/components/onewire/manifest.json index 2d8c6c71071..6687a10e3d7 100644 --- a/homeassistant/components/onewire/manifest.json +++ b/homeassistant/components/onewire/manifest.json @@ -2,7 +2,11 @@ "domain": "onewire", "name": "Onewire", "documentation": "https://www.home-assistant.io/integrations/onewire", - "requirements": [], + "requirements": [ + "pyownet==0.10.0.post1" + ], "dependencies": [], - "codeowners": [] + "codeowners": [ + "@garbled1" + ] } diff --git a/homeassistant/components/onewire/sensor.py b/homeassistant/components/onewire/sensor.py index 6e90178c5d3..6405cb05adc 100644 --- a/homeassistant/components/onewire/sensor.py +++ b/homeassistant/components/onewire/sensor.py @@ -4,10 +4,11 @@ import logging import os import time +from pyownet import protocol import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import TEMP_CELSIUS +from homeassistant.const import CONF_HOST, CONF_PORT, TEMP_CELSIUS import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -30,33 +31,122 @@ DEVICE_SENSORS = { "28": {"temperature": "temperature"}, "3B": {"temperature": "temperature"}, "42": {"temperature": "temperature"}, + "1D": {"counter_a": "counter.A", "counter_b": "counter.B"}, + "EF": {"HobbyBoard": "special"}, +} + +# EF sensors are usually hobbyboards specialized sensors. +# These can only be read by OWFS. Currently this driver only supports them +# via owserver (network protocol) + +HOBBYBOARD_EF = { + "HobbyBoards_EF": { + "humidity": "humidity/humidity_corrected", + "humidity_raw": "humidity/humidity_raw", + "temperature": "humidity/temperature", + }, + "HB_MOISTURE_METER": { + "moisture_0": "moisture/sensor.0", + "moisture_1": "moisture/sensor.1", + "moisture_2": "moisture/sensor.2", + "moisture_3": "moisture/sensor.3", + }, } SENSOR_TYPES = { "temperature": ["temperature", TEMP_CELSIUS], "humidity": ["humidity", "%"], + "humidity_raw": ["humidity", "%"], "pressure": ["pressure", "mb"], "illuminance": ["illuminance", "lux"], + "wetness_0": ["wetness", "%"], + "wetness_1": ["wetness", "%"], + "wetness_2": ["wetness", "%"], + "wetness_3": ["wetness", "%"], + "moisture_0": ["moisture", "cb"], + "moisture_1": ["moisture", "cb"], + "moisture_2": ["moisture", "cb"], + "moisture_3": ["moisture", "cb"], + "counter_a": ["counter", "count"], + "counter_b": ["counter", "count"], + "HobbyBoard": ["none", "none"], } PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Optional(CONF_NAMES): {cv.string: cv.string}, vol.Optional(CONF_MOUNT_DIR, default=DEFAULT_MOUNT_DIR): cv.string, + vol.Optional(CONF_HOST): cv.string, + vol.Optional(CONF_PORT, default=4304): cv.port, } ) +def hb_info_from_type(dev_type="std"): + """Return the proper info array for the device type.""" + if "std" in dev_type: + return DEVICE_SENSORS + if "HobbyBoard" in dev_type: + return HOBBYBOARD_EF + + def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the one wire Sensors.""" - base_dir = config.get(CONF_MOUNT_DIR) + base_dir = config[CONF_MOUNT_DIR] + owport = config[CONF_PORT] + owhost = config.get(CONF_HOST) devs = [] device_names = {} if "names" in config: if isinstance(config["names"], dict): device_names = config["names"] - if base_dir == DEFAULT_MOUNT_DIR: + # We have an owserver on a remote(or local) host/port + if owhost: + try: + owproxy = protocol.proxy(host=owhost, port=owport) + devices = owproxy.dir() + except protocol.Error as exc: + _LOGGER.error( + "Cannot connect to owserver on %s:%d, got: %s", owhost, owport, exc + ) + devices = [] + for device in devices: + _LOGGER.debug("found device: %s", device) + family = owproxy.read(f"{device}family").decode() + dev_type = "std" + if "EF" in family: + dev_type = "HobbyBoard" + family = owproxy.read(f"{device}type").decode() + + if family not in hb_info_from_type(dev_type): + _LOGGER.warning( + "Ignoring unknown family (%s) of sensor found for device: %s", + family, + device, + ) + continue + for sensor_key, sensor_value in hb_info_from_type(dev_type)[family].items(): + if "moisture" in sensor_key: + s_id = sensor_key.split("_")[1] + is_leaf = int( + owproxy.read(f"{device}moisture/is_leaf.{s_id}").decode() + ) + if is_leaf: + sensor_key = f"wetness_{id}" + sensor_id = os.path.split(os.path.split(device)[0])[1] + device_file = os.path.join(os.path.split(device)[0], sensor_value) + devs.append( + OneWireProxy( + device_names.get(sensor_id, sensor_id), + device_file, + sensor_key, + owproxy, + ) + ) + + # We have a raw GPIO ow sensor on a Pi + elif base_dir == DEFAULT_MOUNT_DIR: for device_family in DEVICE_SENSORS: for device_folder in glob(os.path.join(base_dir, device_family + "[.-]*")): sensor_id = os.path.split(device_folder)[1] @@ -68,10 +158,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None): "temperature", ) ) + + # We have an owfs mounted else: for family_file_path in glob(os.path.join(base_dir, "*", "family")): with open(family_file_path, "r") as family_file: family = family_file.read() + if "EF" in family: + continue if family in DEVICE_SENSORS: for sensor_key, sensor_value in DEVICE_SENSORS[family].items(): sensor_id = os.path.split(os.path.split(family_file_path)[0])[1] @@ -121,6 +215,8 @@ class OneWire(Entity): @property def state(self): """Return the state of the sensor.""" + if "count" in self._unit_of_measurement: + return int(self._state) return self._state @property @@ -129,6 +225,34 @@ class OneWire(Entity): return self._unit_of_measurement +class OneWireProxy(OneWire): + """Implementation of a One wire Sensor through owserver.""" + + def __init__(self, name, device_file, sensor_type, owproxy): + """Initialize the onewire sensor via owserver.""" + super().__init__(name, device_file, sensor_type) + self._owproxy = owproxy + + def _read_value_ownet(self): + """Read a value from the owserver.""" + if self._owproxy: + return self._owproxy.read(self._device_file).decode().lstrip() + return None + + def update(self): + """Get the latest data from the device.""" + value = None + value_read = False + try: + value_read = self._read_value_ownet() + except protocol.Error as exc: + _LOGGER.error("Owserver failure in read(), got: %s", exc) + if value_read: + value = round(float(value_read), 1) + + self._state = value + + class OneWireDirect(OneWire): """Implementation of an One wire Sensor directly connected to RPI GPIO.""" diff --git a/requirements_all.txt b/requirements_all.txt index 49a9fa49e2e..5a95d3f28b8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1429,6 +1429,9 @@ pyowlet==1.0.3 # homeassistant.components.openweathermap pyowm==2.10.0 +# homeassistant.components.onewire +pyownet==0.10.0.post1 + # homeassistant.components.elv pypca==0.0.7 From 482ff4061e043eb8134d188a2aad9a4d65b99cb0 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Tue, 24 Dec 2019 20:00:05 +0100 Subject: [PATCH 392/677] Bump importlib-metadata to 1.3.0 (#30196) * Bump importlib-metadata to 1.3.0 * Include setup.py and package_constraints.txt --- 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 363e175ec5a..4bbbad9ad72 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -12,7 +12,7 @@ defusedxml==0.6.0 distro==1.4.0 hass-nabucasa==0.30 home-assistant-frontend==20191204.1 -importlib-metadata==0.23 +importlib-metadata==1.3.0 jinja2>=2.10.3 netdisco==2.6.0 pip>=8.0.3 diff --git a/requirements_all.txt b/requirements_all.txt index 5a95d3f28b8..dd1e7ea3705 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -5,7 +5,7 @@ async_timeout==3.0.1 attrs==19.3.0 bcrypt==3.1.7 certifi>=2019.11.28 -importlib-metadata==0.23 +importlib-metadata==1.3.0 jinja2>=2.10.3 PyJWT==1.7.1 cryptography==2.8 diff --git a/setup.py b/setup.py index 720406ab91b..cf84577b558 100755 --- a/setup.py +++ b/setup.py @@ -38,7 +38,7 @@ REQUIRES = [ "attrs==19.3.0", "bcrypt==3.1.7", "certifi>=2019.11.28", - "importlib-metadata==0.23", + "importlib-metadata==1.3.0", "jinja2>=2.10.3", "PyJWT==1.7.1", # PyJWT has loose dependency. We want the latest one. From 4ea42c2479033ec45e2239c93645dc9bd5daaca5 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Tue, 24 Dec 2019 20:00:32 +0100 Subject: [PATCH 393/677] Bump datapoint to 0.9.5 (#30185) --- homeassistant/components/metoffice/manifest.json | 4 +--- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/metoffice/manifest.json b/homeassistant/components/metoffice/manifest.json index f5624e33edb..572b3a7bcb8 100644 --- a/homeassistant/components/metoffice/manifest.json +++ b/homeassistant/components/metoffice/manifest.json @@ -2,9 +2,7 @@ "domain": "metoffice", "name": "Metoffice", "documentation": "https://www.home-assistant.io/integrations/metoffice", - "requirements": [ - "datapoint==0.4.3" - ], + "requirements": ["datapoint==0.9.5"], "dependencies": [], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index dd1e7ea3705..7cfaff94ef8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -399,7 +399,7 @@ crimereports==1.0.1 datadog==0.15.0 # homeassistant.components.metoffice -datapoint==0.4.3 +datapoint==0.9.5 # homeassistant.components.decora # decora==0.6 From 25f78dd1a9d2deab026039ef35ab98e533cd70cc Mon Sep 17 00:00:00 2001 From: ochlocracy <5885236+ochlocracy@users.noreply.github.com> Date: Tue, 24 Dec 2019 17:06:39 -0500 Subject: [PATCH 394/677] Implement EqualizerController in Alexa for media_player. (#30159) --- .../components/alexa/capabilities.py | 72 +++++++++- homeassistant/components/alexa/const.py | 8 ++ homeassistant/components/alexa/entities.py | 4 + homeassistant/components/alexa/handlers.py | 40 ++++++ tests/components/alexa/test_smart_home.py | 125 ++++++++++++++++++ 5 files changed, 248 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/alexa/capabilities.py b/homeassistant/components/alexa/capabilities.py index 513a0c157d3..3fdad2b3c92 100644 --- a/homeassistant/components/alexa/capabilities.py +++ b/homeassistant/components/alexa/capabilities.py @@ -120,7 +120,20 @@ class AlexaCapability: @staticmethod def configuration(): - """Return the configuration object.""" + """Return the configuration object. + + Applicable to the ThermostatController, SecurityControlPanel, ModeController, RangeController, + and EventDetectionSensor. + """ + return [] + + @staticmethod + def configurations(): + """Return the configurations object. + + The plural configurations object is different that the singular configuration object. + Applicable to EqualizerController interface. + """ return [] @staticmethod @@ -177,6 +190,11 @@ class AlexaCapability: if configuration: result["configuration"] = configuration + # The plural configurations object is different than the singular configuration object above. + configurations = self.configurations() + if configurations: + result["configurations"] = configurations + semantics = self.semantics() if semantics: result["semantics"] = semantics @@ -1356,3 +1374,55 @@ class AlexaEventDetectionSensor(AlexaCapability): } }, } + + +class AlexaEqualizerController(AlexaCapability): + """Implements Alexa.EqualizerController. + + https://developer.amazon.com/en-US/docs/alexa/device-apis/alexa-equalizercontroller.html + """ + + def name(self): + """Return the Alexa API name of this interface.""" + return "Alexa.EqualizerController" + + def properties_supported(self): + """Return what properties this entity supports. + + Either bands, mode or both can be specified. Only mode is supported at this time. + """ + return [{"name": "mode"}] + + def get_property(self, name): + """Read and return a property.""" + if name != "mode": + raise UnsupportedProperty(name) + + sound_mode = self.entity.attributes.get(media_player.ATTR_SOUND_MODE) + if sound_mode and sound_mode.upper() in ( + "MOVIE", + "MUSIC", + "NIGHT", + "SPORT", + "TV", + ): + return sound_mode.upper() + + return None + + def configurations(self): + """Return the sound modes supported in the configurations object. + + Valid Values for modes are: MOVIE, MUSIC, NIGHT, SPORT, TV. + """ + configurations = None + sound_mode_list = self.entity.attributes.get(media_player.ATTR_SOUND_MODE_LIST) + if sound_mode_list: + supported_sound_modes = [] + for sound_mode in sound_mode_list: + if sound_mode.upper() in ("MOVIE", "MUSIC", "NIGHT", "SPORT", "TV"): + supported_sound_modes.append({"name": sound_mode.upper()}) + + configurations = {"modes": {"supported": supported_sound_modes}} + + return configurations diff --git a/homeassistant/components/alexa/const.py b/homeassistant/components/alexa/const.py index f1a86859da9..6968ab3a691 100644 --- a/homeassistant/components/alexa/const.py +++ b/homeassistant/components/alexa/const.py @@ -196,3 +196,11 @@ class Inputs: "video3": "VIDEO 3", "xbox": "XBOX", } + + VALID_SOUND_MODE_MAP = { + "movie": "MOVIE", + "music": "MUSIC", + "night": "NIGHT", + "sport": "SPORT", + "tv": "TV", + } diff --git a/homeassistant/components/alexa/entities.py b/homeassistant/components/alexa/entities.py index ab3dc75bd2c..4321d289cec 100644 --- a/homeassistant/components/alexa/entities.py +++ b/homeassistant/components/alexa/entities.py @@ -42,6 +42,7 @@ from .capabilities import ( AlexaContactSensor, AlexaDoorbellEventSource, AlexaEndpointHealth, + AlexaEqualizerController, AlexaEventDetectionSensor, AlexaInputController, AlexaLockController, @@ -522,6 +523,9 @@ class MediaPlayerCapabilities(AlexaEntity): if supported & media_player.const.SUPPORT_PLAY_MEDIA: yield AlexaChannelController(self.entity) + if supported & media_player.const.SUPPORT_SELECT_SOUND_MODE: + yield AlexaEqualizerController(self.entity) + yield AlexaEndpointHealth(self.hass, self.entity) yield Alexa(self.hass) diff --git a/homeassistant/components/alexa/handlers.py b/homeassistant/components/alexa/handlers.py index 133919be84d..ce6c37a2b39 100644 --- a/homeassistant/components/alexa/handlers.py +++ b/homeassistant/components/alexa/handlers.py @@ -1352,3 +1352,43 @@ async def async_api_seek(hass, config, directive, context): return directive.response( name="StateReport", namespace="Alexa.SeekController", payload=payload ) + + +@HANDLERS.register(("Alexa.EqualizerController", "SetMode")) +async def async_api_set_eq_mode(hass, config, directive, context): + """Process a SetMode request for EqualizerController.""" + mode = directive.payload["mode"] + entity = directive.entity + data = {ATTR_ENTITY_ID: entity.entity_id} + + sound_mode_list = entity.attributes.get(media_player.const.ATTR_SOUND_MODE_LIST) + if sound_mode_list and mode.lower() in sound_mode_list: + data[media_player.const.ATTR_SOUND_MODE] = mode.lower() + else: + msg = "failed to map sound mode {} to a mode on {}".format( + mode, entity.entity_id + ) + raise AlexaInvalidValueError(msg) + + await hass.services.async_call( + entity.domain, + media_player.SERVICE_SELECT_SOUND_MODE, + data, + blocking=False, + context=context, + ) + + return directive.response() + + +@HANDLERS.register(("Alexa.EqualizerController", "AdjustBands")) +@HANDLERS.register(("Alexa.EqualizerController", "ResetBands")) +@HANDLERS.register(("Alexa.EqualizerController", "SetBands")) +async def async_api_bands_directive(hass, config, directive, context): + """Handle an AdjustBands, ResetBands, SetBands request. + + Only mode directives are currently supported for the EqualizerController. + """ + # Currently bands directives are not supported. + msg = "Entity does not support directive" + raise AlexaInvalidDirectiveError(msg) diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index 3d7c2b118e7..37301c3555e 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -9,6 +9,7 @@ from homeassistant.components.media_player.const import ( SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, + SUPPORT_SELECT_SOUND_MODE, SUPPORT_SELECT_SOURCE, SUPPORT_STOP, SUPPORT_TURN_OFF, @@ -2912,3 +2913,127 @@ async def test_input_number_float(hass): "value", instance="input_number.value", ) + + +async def test_media_player_eq_modes(hass): + """Test media player discovery with sound mode list.""" + device = ( + "media_player.test", + "on", + { + "friendly_name": "Test media player", + "supported_features": SUPPORT_SELECT_SOUND_MODE, + "sound_mode": "tv", + "sound_mode_list": ["movie", "music", "night", "sport", "tv", "rocknroll"], + }, + ) + appliance = await discovery_test(device, hass) + + assert appliance["endpointId"] == "media_player#test" + assert appliance["friendlyName"] == "Test media player" + + capabilities = assert_endpoint_capabilities( + appliance, + "Alexa", + "Alexa.EqualizerController", + "Alexa.PowerController", + "Alexa.EndpointHealth", + ) + + eq_capability = get_capability(capabilities, "Alexa.EqualizerController") + assert eq_capability is not None + assert "modes" in eq_capability["configurations"] + + eq_modes = eq_capability["configurations"]["modes"] + assert {"name": "rocknroll"} not in eq_modes["supported"] + assert {"name": "ROCKNROLL"} not in eq_modes["supported"] + + for mode in ("MOVIE", "MUSIC", "NIGHT", "SPORT", "TV"): + assert {"name": mode} in eq_modes["supported"] + + call, _ = await assert_request_calls_service( + "Alexa.EqualizerController", + "SetMode", + "media_player#test", + "media_player.select_sound_mode", + hass, + payload={"mode": mode}, + ) + assert call.data["sound_mode"] == mode.lower() + + +async def test_media_player_sound_mode_list_none(hass): + """Test EqualizerController bands directive not supported.""" + device = ( + "media_player.test", + "on", + { + "friendly_name": "Test media player", + "supported_features": SUPPORT_SELECT_SOUND_MODE, + "sound_mode": "unknown", + "sound_mode_list": None, + }, + ) + appliance = await discovery_test(device, hass) + assert appliance["endpointId"] == "media_player#test" + assert appliance["friendlyName"] == "Test media player" + + +async def test_media_player_eq_bands_not_supported(hass): + """Test EqualizerController bands directive not supported.""" + device = ( + "media_player.test_bands", + "on", + { + "friendly_name": "Test media player", + "supported_features": SUPPORT_SELECT_SOUND_MODE, + "sound_mode": "tv", + "sound_mode_list": ["movie", "music", "night", "sport", "tv", "rocknroll"], + }, + ) + await discovery_test(device, hass) + + context = Context() + + # Test for SetBands Error + request = get_new_request( + "Alexa.EqualizerController", "SetBands", "media_player#test_bands" + ) + request["directive"]["payload"] = {"bands": [{"name": "BASS", "value": -2}]} + msg = await smart_home.async_handle_message(hass, DEFAULT_CONFIG, request, context) + + assert "event" in msg + msg = msg["event"] + assert msg["header"]["name"] == "ErrorResponse" + assert msg["header"]["namespace"] == "Alexa" + assert msg["payload"]["type"] == "INVALID_DIRECTIVE" + + # Test for AdjustBands Error + request = get_new_request( + "Alexa.EqualizerController", "AdjustBands", "media_player#test_bands" + ) + request["directive"]["payload"] = { + "bands": [{"name": "BASS", "levelDelta": 3, "levelDirection": "UP"}] + } + msg = await smart_home.async_handle_message(hass, DEFAULT_CONFIG, request, context) + + assert "event" in msg + msg = msg["event"] + assert msg["header"]["name"] == "ErrorResponse" + assert msg["header"]["namespace"] == "Alexa" + assert msg["payload"]["type"] == "INVALID_DIRECTIVE" + + # Test for ResetBands Error + request = get_new_request( + "Alexa.EqualizerController", "ResetBands", "media_player#test_bands" + ) + request["directive"]["payload"] = { + "bands": [{"name": "BASS", "levelDelta": 3, "levelDirection": "UP"}] + } + msg = await smart_home.async_handle_message(hass, DEFAULT_CONFIG, request, context) + + assert "event" in msg + msg = msg["event"] + assert msg["header"]["name"] == "ErrorResponse" + assert msg["header"]["namespace"] == "Alexa" + assert msg["payload"]["type"] == "INVALID_DIRECTIVE" From bb35d93fde2b775564d1a054a3c45ba9785b8d4b Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Wed, 25 Dec 2019 00:32:06 +0000 Subject: [PATCH 395/677] [ci skip] Translation update --- .../alarm_control_panel/.translations/ko.json | 7 + .../components/almond/.translations/ko.json | 5 + .../ambient_station/.translations/ko.json | 6 +- .../binary_sensor/.translations/ko.json | 172 +++++++++--------- .../components/cast/.translations/ko.json | 2 +- .../components/climate/.translations/ko.json | 17 ++ .../coolmaster/.translations/ko.json | 2 +- .../components/cover/.translations/ko.json | 24 +-- .../components/deconz/.translations/ko.json | 42 ++--- .../components/demo/.translations/ko.json | 5 + .../device_tracker/.translations/ko.json | 4 +- .../dialogflow/.translations/ko.json | 2 +- .../components/elgato/.translations/ko.json | 27 +++ .../components/esphome/.translations/ko.json | 4 +- .../components/fan/.translations/ko.json | 16 ++ .../components/geofency/.translations/ko.json | 2 +- .../geonetnz_volcano/.translations/ko.json | 16 ++ .../gpslogger/.translations/ko.json | 2 +- .../hisense_aehw4a1/.translations/ko.json | 15 ++ .../components/icloud/.translations/ko.json | 38 ++++ .../components/ifttt/.translations/ko.json | 2 +- .../components/ios/.translations/ko.json | 2 +- .../components/izone/.translations/ko.json | 2 +- .../components/light/.translations/ko.json | 8 +- .../components/locative/.translations/ko.json | 2 +- .../components/lock/.translations/ko.json | 8 +- .../lutron_caseta/.translations/ko.json | 5 + .../components/mailgun/.translations/ko.json | 2 +- .../media_player/.translations/ko.json | 10 +- .../owntracks/.translations/ko.json | 2 +- .../components/plaato/.translations/ko.json | 2 +- .../components/plex/.translations/ko.json | 1 + .../components/ps4/.translations/ko.json | 2 +- .../components/sensor/.translations/ko.json | 36 ++-- .../components/soma/.translations/ko.json | 4 +- .../components/sonos/.translations/ko.json | 2 +- .../components/starline/.translations/ko.json | 42 +++++ .../components/switch/.translations/ko.json | 12 +- .../components/tesla/.translations/fr.json | 30 +++ .../components/tesla/.translations/ko.json | 30 +++ .../components/tesla/.translations/ru.json | 30 +++ .../components/tplink/.translations/ko.json | 2 +- .../components/traccar/.translations/ko.json | 2 +- .../components/twilio/.translations/ko.json | 2 +- .../components/upnp/.translations/ko.json | 2 +- .../components/vacuum/.translations/ko.json | 16 ++ .../components/wemo/.translations/ko.json | 2 +- .../components/wled/.translations/ko.json | 11 +- .../components/zha/.translations/ko.json | 30 +-- 49 files changed, 511 insertions(+), 198 deletions(-) create mode 100644 homeassistant/components/climate/.translations/ko.json create mode 100644 homeassistant/components/demo/.translations/ko.json create mode 100644 homeassistant/components/elgato/.translations/ko.json create mode 100644 homeassistant/components/fan/.translations/ko.json create mode 100644 homeassistant/components/geonetnz_volcano/.translations/ko.json create mode 100644 homeassistant/components/hisense_aehw4a1/.translations/ko.json create mode 100644 homeassistant/components/icloud/.translations/ko.json create mode 100644 homeassistant/components/lutron_caseta/.translations/ko.json create mode 100644 homeassistant/components/starline/.translations/ko.json create mode 100644 homeassistant/components/tesla/.translations/fr.json create mode 100644 homeassistant/components/tesla/.translations/ko.json create mode 100644 homeassistant/components/tesla/.translations/ru.json create mode 100644 homeassistant/components/vacuum/.translations/ko.json diff --git a/homeassistant/components/alarm_control_panel/.translations/ko.json b/homeassistant/components/alarm_control_panel/.translations/ko.json index 5d6caa5fe12..b70ae8dc025 100644 --- a/homeassistant/components/alarm_control_panel/.translations/ko.json +++ b/homeassistant/components/alarm_control_panel/.translations/ko.json @@ -6,6 +6,13 @@ "arm_night": "{entity_name} \uc57c\uac04\uacbd\ube44", "disarm": "{entity_name} \uacbd\ube44\ud574\uc81c", "trigger": "{entity_name} \ud2b8\ub9ac\uac70" + }, + "trigger_type": { + "armed_away": "{entity_name} \uc774(\uac00) \uc678\ucd9c \uacbd\ube44\ubaa8\ub4dc\ub85c \uc124\uc815\ub420 \ub54c", + "armed_home": "{entity_name} \uc774(\uac00) \uc7ac\uc2e4 \uacbd\ube44\ubaa8\ub4dc\ub85c \uc124\uc815\ub420 \ub54c", + "armed_night": "{entity_name} \uc774(\uac00) \uc57c\uac04 \uacbd\ube44\ubaa8\ub4dc\ub85c \uc124\uc815\ub420 \ub54c", + "disarmed": "{entity_name} \uc774(\uac00) \ud574\uc81c\ub420 \ub54c", + "triggered": "{entity_name} \uc774(\uac00) \ud2b8\ub9ac\uac70\ub420 \ub54c" } } } \ No newline at end of file diff --git a/homeassistant/components/almond/.translations/ko.json b/homeassistant/components/almond/.translations/ko.json index 2a2346aaf50..9f1e71163d6 100644 --- a/homeassistant/components/almond/.translations/ko.json +++ b/homeassistant/components/almond/.translations/ko.json @@ -5,6 +5,11 @@ "cannot_connect": "Almond \uc11c\ubc84\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", "missing_configuration": "Almond \uc124\uc815 \ubc29\ubc95\uc5d0 \ub300\ud55c \uc124\uba85\uc11c\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694." }, + "step": { + "pick_implementation": { + "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd" + } + }, "title": "Almond" } } \ No newline at end of file diff --git a/homeassistant/components/ambient_station/.translations/ko.json b/homeassistant/components/ambient_station/.translations/ko.json index 541b8699dc8..eb9209a6c37 100644 --- a/homeassistant/components/ambient_station/.translations/ko.json +++ b/homeassistant/components/ambient_station/.translations/ko.json @@ -1,15 +1,15 @@ { "config": { "error": { - "identifier_exists": "Application \ud0a4 \ud639\uc740 API \ud0a4\uac00 \uc774\ubbf8 \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "invalid_key": "Application \ud0a4 \ud639\uc740 API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "identifier_exists": "\uc560\ud50c\ub9ac\ucf00\uc774\uc158 \ud0a4 \ud639\uc740 API \ud0a4\uac00 \uc774\ubbf8 \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_key": "\uc560\ud50c\ub9ac\ucf00\uc774\uc158 \ud0a4 \ud639\uc740 API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "no_devices": "\uacc4\uc815\uc5d0 \uae30\uae30\uac00 \uc874\uc7ac\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4" }, "step": { "user": { "data": { "api_key": "API \ud0a4", - "app_key": "Application \ud0a4" + "app_key": "\uc560\ud50c\ub9ac\ucf00\uc774\uc158 \ud0a4" }, "title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4 \uc785\ub825" } diff --git a/homeassistant/components/binary_sensor/.translations/ko.json b/homeassistant/components/binary_sensor/.translations/ko.json index 167708c2cf1..4c1cba2bec5 100644 --- a/homeassistant/components/binary_sensor/.translations/ko.json +++ b/homeassistant/components/binary_sensor/.translations/ko.json @@ -1,94 +1,94 @@ { "device_automation": { "condition_type": { - "is_bat_low": "{entity_name} \uc758 \ubc30\ud130\ub9ac \uc794\ub7c9\uc774 \ubd80\uc871\ud569\ub2c8\ub2e4", - "is_cold": "{entity_name} \uc774(\uac00) \ucc28\uac11\uc2b5\ub2c8\ub2e4", - "is_connected": "{entity_name} \uc774(\uac00) \uc5f0\uacb0\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "is_gas": "{entity_name} \uc774(\uac00) \uac00\uc2a4\ub97c \uac10\uc9c0\ud588\uc2b5\ub2c8\ub2e4", - "is_hot": "{entity_name} \uc774(\uac00) \ub728\uac81\uc2b5\ub2c8\ub2e4", - "is_light": "{entity_name} \uc774(\uac00) \ube5b\uc744 \uac10\uc9c0\ud588\uc2b5\ub2c8\ub2e4", - "is_locked": "{entity_name} \uc774(\uac00) \uc7a0\uacbc\uc2b5\ub2c8\ub2e4", - "is_moist": "{entity_name} \uc774(\uac00) \uc2b5\ud569\ub2c8\ub2e4", - "is_motion": "{entity_name} \uc774(\uac00) \uc6c0\uc9c1\uc784\uc744 \uac10\uc9c0\ud588\uc2b5\ub2c8\ub2e4", - "is_moving": "{entity_name} \uc774(\uac00) \uc6c0\uc9c1\uc600\uc2b5\ub2c8\ub2e4", - "is_no_gas": "{entity_name} \uc774(\uac00) \uac00\uc2a4\ub97c \uac10\uc9c0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "is_no_light": "{entity_name} \uc774(\uac00) \ube5b\uc744 \uac10\uc9c0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "is_no_motion": "{entity_name} \uc774(\uac00) \uc6c0\uc9c1\uc784\uc744 \uac10\uc9c0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "is_no_problem": "{entity_name} \uc774(\uac00) \ubb38\uc81c\ub97c \uac10\uc9c0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "is_no_smoke": "{entity_name} \uc774(\uac00) \uc5f0\uae30\ub97c \uac10\uc9c0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "is_no_sound": "{entity_name} \uc774(\uac00) \uc18c\ub9ac\ub97c \uac10\uc9c0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "is_no_vibration": "{entity_name} \uc774(\uac00) \uc9c4\ub3d9\uc744 \uac10\uc9c0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "is_not_bat_low": "{entity_name} \uc758 \ubc30\ud130\ub9ac\uac00 \uc815\uc0c1\uc785\ub2c8\ub2e4", - "is_not_cold": "{entity_name} \uc774(\uac00) \ucc28\uac11\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.", - "is_not_connected": "{entity_name} \uc758 \uc5f0\uacb0\uc774 \ub04a\uc5b4\uc84c\uc2b5\ub2c8\ub2e4", - "is_not_hot": "{entity_name} \uc774(\uac00) \ub728\uac81\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.", - "is_not_locked": "{entity_name} \uc758 \uc7a0\uae08\uc774 \ud574\uc81c\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "is_not_moist": "{entity_name} \uc774(\uac00) \uac74\uc870\ud569\ub2c8\ub2e4", - "is_not_moving": "{entity_name} \uc774(\uac00) \uc6c0\uc9c1\uc774\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4", - "is_not_occupied": "{entity_name} \uc774(\uac00) \uc0ac\uc6a9\uc911\uc774\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4", - "is_not_open": "{entity_name} \uc774(\uac00) \ub2eb\ud614\uc2b5\ub2c8\ub2e4", - "is_not_plugged_in": "{entity_name} \uc774(\uac00) \ubf51\ud614\uc2b5\ub2c8\ub2e4", - "is_not_powered": "{entity_name} \uc5d0 \uc804\uc6d0\uc774 \uacf5\uae09\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4", - "is_not_present": "{entity_name} \uc774(\uac00) \uc5c6\uc2b5\ub2c8\ub2e4", - "is_not_unsafe": "{entity_name} \uc740(\ub294) \uc548\uc804\ud569\ub2c8\ub2e4", - "is_occupied": "{entity_name} \uc774(\uac00) \uc0ac\uc6a9\uc911\uc785\ub2c8\ub2e4", - "is_off": "{entity_name} \uc774(\uac00) \uaebc\uc84c\uc2b5\ub2c8\ub2e4", - "is_on": "{entity_name} \uc774(\uac00) \ucf1c\uc84c\uc2b5\ub2c8\ub2e4", - "is_open": "{entity_name} \uc774(\uac00) \uc5f4\ub838\uc2b5\ub2c8\ub2e4", - "is_plugged_in": "{entity_name} \uc774(\uac00) \uaf3d\ud614\uc2b5\ub2c8\ub2e4", - "is_powered": "{entity_name} \uc5d0 \uc804\uc6d0\uc774 \uacf5\uae09\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "is_present": "{entity_name} \uc774(\uac00) \uc788\uc2b5\ub2c8\ub2e4", - "is_problem": "{entity_name} \uc774(\uac00) \ubb38\uc81c\ub97c \uac10\uc9c0\ud588\uc2b5\ub2c8\ub2e4", - "is_smoke": "{entity_name} \uc774(\uac00) \uc5f0\uae30\ub97c \uac10\uc9c0\ud588\uc2b5\ub2c8\ub2e4", - "is_sound": "{entity_name} \uc774(\uac00) \uc18c\ub9ac\ub97c \uac10\uc9c0\ud588\uc2b5\ub2c8\ub2e4", - "is_unsafe": "{entity_name} \uc740(\ub294) \uc548\uc804\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4", - "is_vibration": "{entity_name} \uc774(\uac00) \uc9c4\ub3d9\uc744 \uac10\uc9c0\ud588\uc2b5\ub2c8\ub2e4" + "is_bat_low": "{entity_name} \uc758 \ubc30\ud130\ub9ac \uc794\ub7c9\uc774 \ubd80\uc871\ud558\uba74", + "is_cold": "{entity_name} \uc774(\uac00) \ucc28\uac00\uc6b0\uba74", + "is_connected": "{entity_name} \uc774(\uac00) \uc5f0\uacb0\ub418\uc5b4 \uc788\uc73c\uba74", + "is_gas": "{entity_name} \uc774(\uac00) \uac00\uc2a4\ub97c \uac10\uc9c0\ud558\uba74", + "is_hot": "{entity_name} \uc774(\uac00) \ub728\uac70\uc6b0\uba74", + "is_light": "{entity_name} \uc774(\uac00) \ube5b\uc744 \uac10\uc9c0\ud558\uba74", + "is_locked": "{entity_name} \uc774(\uac00) \uc7a0\uaca8\uc788\uc73c\uba74", + "is_moist": "{entity_name} \uc774(\uac00) \uc2b5\ud558\uba74", + "is_motion": "{entity_name} \uc774(\uac00) \uc6c0\uc9c1\uc784\uc744 \uac10\uc9c0\ud558\uba74", + "is_moving": "{entity_name} \uc774(\uac00) \uc6c0\uc9c1\uc774\uba74", + "is_no_gas": "{entity_name} \uc774(\uac00) \uac00\uc2a4\ub97c \uac10\uc9c0\ud558\uc9c0 \uc54a\uc73c\uba74", + "is_no_light": "{entity_name} \uc774(\uac00) \ube5b\uc744 \uac10\uc9c0\ud558\uc9c0 \uc54a\uc73c\uba74", + "is_no_motion": "{entity_name} \uc774(\uac00) \uc6c0\uc9c1\uc784\uc744 \uac10\uc9c0\ud558\uc9c0 \uc54a\uc73c\uba74", + "is_no_problem": "{entity_name} \uc774(\uac00) \ubb38\uc81c\ub97c \uac10\uc9c0\ud558\uc9c0 \uc54a\uc73c\uba74", + "is_no_smoke": "{entity_name} \uc774(\uac00) \uc5f0\uae30\ub97c \uac10\uc9c0\ud558\uc9c0 \uc54a\uc73c\uba74", + "is_no_sound": "{entity_name} \uc774(\uac00) \uc18c\ub9ac\ub97c \uac10\uc9c0\ud558\uc9c0 \uc54a\uc73c\uba74", + "is_no_vibration": "{entity_name} \uc774(\uac00) \uc9c4\ub3d9\uc744 \uac10\uc9c0\ud558\uc9c0 \uc54a\uc73c\uba74", + "is_not_bat_low": "{entity_name} \uc758 \ubc30\ud130\ub9ac\uac00 \uc815\uc0c1\uc774\uba74", + "is_not_cold": "{entity_name} \uc774(\uac00) \ucc28\uac11\uc9c0 \uc54a\ub2e4\uba74", + "is_not_connected": "{entity_name} \uc758 \uc5f0\uacb0\uc774 \ub04a\uc5b4\uc838 \uc788\ub2e4\uba74", + "is_not_hot": "{entity_name} \uc774(\uac00) \ub728\uac81\uc9c0 \uc54a\ub2e4\uba74", + "is_not_locked": "{entity_name} \uc774(\uac00) \uc7a0\uaca8\uc788\uc9c0 \uc54a\uc73c\uba74", + "is_not_moist": "{entity_name} \uc774(\uac00) \uac74\uc870\ud558\uba74", + "is_not_moving": "{entity_name} \uc774(\uac00) \uc6c0\uc9c1\uc774\uc9c0 \uc54a\uc73c\uba74", + "is_not_occupied": "{entity_name} \uc774(\uac00) \uc0ac\uc6a9 \uc911\uc774\uc9c0 \uc54a\uc73c\uba74", + "is_not_open": "{entity_name} \uc774(\uac00) \ub2eb\ud600 \uc788\uc73c\uba74", + "is_not_plugged_in": "{entity_name} \ud50c\ub7ec\uadf8\uac00 \ubf51\ud600 \uc788\uc73c\uba74", + "is_not_powered": "{entity_name} \uc5d0 \uc804\uc6d0\uc774 \uacf5\uae09\ub418\uc9c0 \uc54a\uc73c\uba74", + "is_not_present": "{entity_name} \uc774(\uac00) \uc678\ucd9c \uc911\uc774\uba74", + "is_not_unsafe": "{entity_name} \uc774(\uac00) \uc548\uc804\ud558\uba74", + "is_occupied": "{entity_name} \uc774(\uac00) \uc0ac\uc6a9 \uc911\uc774\uba74", + "is_off": "{entity_name} \uc774(\uac00) \uaebc\uc838 \uc788\uc73c\uba74", + "is_on": "{entity_name} \uc774(\uac00) \ucf1c\uc838 \uc788\uc73c\uba74", + "is_open": "{entity_name} \uc774(\uac00) \uc5f4\ub824 \uc788\uc73c\uba74", + "is_plugged_in": "{entity_name} \ud50c\ub7ec\uadf8\uac00 \uaf3d\ud600 \uc788\uc73c\uba74", + "is_powered": "{entity_name} \uc5d0 \uc804\uc6d0\uc774 \uacf5\uae09\ub418\uace0 \uc788\uc73c\uba74", + "is_present": "{entity_name} \uc774(\uac00) \uc7ac\uc2e4 \uc911\uc774\uba74", + "is_problem": "{entity_name} \uc774(\uac00) \ubb38\uc81c\ub97c \uac10\uc9c0\ud558\uba74", + "is_smoke": "{entity_name} \uc774(\uac00) \uc5f0\uae30\ub97c \uac10\uc9c0\ud558\uba74", + "is_sound": "{entity_name} \uc774(\uac00) \uc18c\ub9ac\ub97c \uac10\uc9c0\ud558\uba74", + "is_unsafe": "{entity_name} \uc740(\ub294) \uc548\uc804\ud558\uc9c0 \uc54a\uc73c\uba74", + "is_vibration": "{entity_name} \uc774(\uac00) \uc9c4\ub3d9\uc744 \uac10\uc9c0\ud558\uba74" }, "trigger_type": { - "bat_low": "{entity_name} \uc758 \ubc30\ud130\ub9ac \uc794\ub7c9 \ubd80\uc871", - "closed": "{entity_name} \uc774(\uac00) \ub2eb\ud798", - "cold": "{entity_name} \uc774(\uac00) \ucc28\uac00\uc6cc\uc9d0", - "connected": "{entity_name} \uc774(\uac00) \uc5f0\uacb0\ub428", - "gas": "{entity_name} \uc774(\uac00) \uac00\uc2a4\ub97c \uac10\uc9c0\ud568", - "hot": "{entity_name} \uc774(\uac00) \ub728\uac70\uc6cc\uc9d0", - "light": "{entity_name} \uc774(\uac00) \ube5b\uc744 \uac10\uc9c0\ud568", - "locked": "{entity_name} \uc774(\uac00) \uc7a0\uae40", - "moist": "{entity_name} \uc774(\uac00) \uc2b5\ud574\uc9d0", - "moist\u00a7": "{entity_name} \uc774(\uac00) \uc2b5\ud574\uc9d0", - "motion": "{entity_name} \uc774(\uac00) \uc6c0\uc9c1\uc784\uc744 \uac10\uc9c0\ud568", - "moving": "{entity_name} \uc774(\uac00) \uc6c0\uc9c1\uc784", - "no_gas": "{entity_name} \uc774(\uac00) \uac00\uc2a4\ub97c \uac10\uc9c0 \ubabb\ud568", - "no_light": "{entity_name} \uc774(\uac00) \ube5b\uc744 \uac10\uc9c0 \ubabb\ud568", - "no_motion": "{entity_name} \uc774(\uac00) \uc6c0\uc9c1\uc784\uc744 \uac10\uc9c0 \ubabb\ud568", - "no_problem": "{entity_name} \uc774(\uac00) \ubb38\uc81c\ub97c \uac10\uc9c0 \ubabb\ud568", - "no_smoke": "{entity_name} \uc774(\uac00) \uc5f0\uae30\ub97c \uac10\uc9c0 \ubabb\ud568", - "no_sound": "{entity_name} \uc774(\uac00) \uc18c\ub9ac\ub97c \uac10\uc9c0 \ubabb\ud568", - "no_vibration": "{entity_name} \uc774(\uac00) \uc9c4\ub3d9\uc744 \uac10\uc9c0 \ubabb\ud568", - "not_bat_low": "{entity_name} \uc758 \ubc30\ud130\ub9ac \uc815\uc0c1", - "not_cold": "{entity_name} \uc774(\uac00) \ucc28\uac11\uc9c0 \uc54a\uc74c", - "not_connected": "{entity_name} \uc758 \uc5f0\uacb0\uc774 \ub04a\uc5b4\uc9d0", - "not_hot": "{entity_name} \uc774(\uac00) \ub728\uac81\uc9c0 \uc54a\uc74c", - "not_locked": "{entity_name} \uc758 \uc7a0\uae08\uc774 \ud574\uc81c\ub428", - "not_moist": "{entity_name} \uc774(\uac00) \uac74\uc870\ud574\uc9d0", - "not_moving": "{entity_name} \uc774(\uac00) \uc6c0\uc9c1\uc774\uc9c0 \uc54a\uc74c", - "not_occupied": "{entity_name} \uc774(\uac00) \uc0ac\uc6a9\uc911\uc774\uc9c0 \uc54a\uc74c", - "not_opened": "{entity_name} \uc774(\uac00) \ub2eb\ud798", - "not_plugged_in": "{entity_name} \uc774(\uac00) \ubf51\ud798", - "not_powered": "{entity_name} \uc5d0 \uc804\uc6d0\uc774 \uacf5\uae09\ub418\uc9c0 \uc54a\uc74c", - "not_present": "{entity_name} \uc774(\uac00) \uc5c6\uc74c", - "not_unsafe": "{entity_name} \uc740(\ub294) \uc548\uc804\ud574\uc9d0", - "occupied": "{entity_name} \uc774(\uac00) \uc0ac\uc6a9\uc911", - "opened": "{entity_name} \uc774(\uac00) \uc5f4\ub9bc", - "plugged_in": "{entity_name} \uc774(\uac00) \uaf3d\ud798", - "powered": "{entity_name} \uc5d0 \uc804\uc6d0\uc774 \uacf5\uae09\ub428", - "present": "{entity_name} \uc774(\uac00) \uc788\uc74c", - "problem": "{entity_name} \uc774(\uac00) \ubb38\uc81c\ub97c \uac10\uc9c0\ud568", - "smoke": "{entity_name} \uc774(\uac00) \uc5f0\uae30\ub97c \uac10\uc9c0\ud568", - "sound": "{entity_name} \uc774(\uac00) \uc18c\ub9ac\ub97c \uac10\uc9c0\ud568", - "turned_off": "{entity_name} \uc774(\uac00) \uaebc\uc9d0", - "turned_on": "{entity_name} \uc774(\uac00) \ucf1c\uc9d0", - "unsafe": "{entity_name} \uc740(\ub294) \uc548\uc804\ud558\uc9c0 \uc54a\uc74c", - "vibration": "{entity_name} \uc774(\uac00) \uc9c4\ub3d9\uc744 \uac10\uc9c0\ud568" + "bat_low": "{entity_name} \ubc30\ud130\ub9ac \uc794\ub7c9\uc774 \ubd80\uc871\ud574\uc9c8 \ub54c", + "closed": "{entity_name} \uc774(\uac00) \ub2eb\ud790 \ub54c", + "cold": "{entity_name} \uc774(\uac00) \ucc28\uac00\uc6cc\uc9c8 \ub54c", + "connected": "{entity_name} \uc774(\uac00) \uc5f0\uacb0\ub420 \ub54c", + "gas": "{entity_name} \uc774(\uac00) \uac00\uc2a4\ub97c \uac10\uc9c0\ud560 \ub54c", + "hot": "{entity_name} \uc774(\uac00) \ub728\uac70\uc6cc\uc9c8 \ub54c", + "light": "{entity_name} \uc774(\uac00) \ube5b\uc744 \uac10\uc9c0\ud560 \ub54c", + "locked": "{entity_name} \uc774(\uac00) \uc7a0\uae38 \ub54c", + "moist": "{entity_name} \uc774(\uac00) \uc2b5\ud574\uc9c8 \ub54c", + "moist\u00a7": "{entity_name} \uc774(\uac00) \uc2b5\ud574\uc9c8 \ub54c", + "motion": "{entity_name} \uc774(\uac00) \uc6c0\uc9c1\uc784\uc744 \uac10\uc9c0\ud560 \ub54c", + "moving": "{entity_name} \uc774(\uac00) \uc6c0\uc9c1\uc77c \ub54c", + "no_gas": "{entity_name} \uc774(\uac00) \uac00\uc2a4\ub97c \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub420 \ub54c", + "no_light": "{entity_name} \uc774(\uac00) \ube5b\uc744 \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub420 \ub54c", + "no_motion": "{entity_name} \uc774(\uac00) \uc6c0\uc9c1\uc784\uc744 \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub420 \ub54c", + "no_problem": "{entity_name} \uc774(\uac00) \ubb38\uc81c\ub97c \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub420 \ub54c", + "no_smoke": "{entity_name} \uc774(\uac00) \uc5f0\uae30\ub97c \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub420 \ub54c", + "no_sound": "{entity_name} \uc774(\uac00) \uc18c\ub9ac\ub97c \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub420 \ub54c", + "no_vibration": "{entity_name} \uc774(\uac00) \uc9c4\ub3d9\uc744 \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub420 \ub54c", + "not_bat_low": "{entity_name} \ubc30\ud130\ub9ac\uac00 \uc815\uc0c1\uc774 \ub420 \ub54c", + "not_cold": "{entity_name} \uc774(\uac00) \ucc28\uac11\uc9c0 \uc54a\uac8c \ub420 \ub54c", + "not_connected": "{entity_name} \uc758 \uc5f0\uacb0\uc774 \ub04a\uc5b4\uc9c8 \ub54c", + "not_hot": "{entity_name} \uc774(\uac00) \ub728\uac81\uc9c0 \uc54a\uac8c \ub420 \ub54c", + "not_locked": "{entity_name} \uc758 \uc7a0\uae08\uc774 \ud574\uc81c\ub420 \ub54c", + "not_moist": "{entity_name} \uc774(\uac00) \uac74\uc870\ud574\uc9c8 \ub54c", + "not_moving": "{entity_name} \uc774(\uac00) \uc6c0\uc9c1\uc774\uc9c0 \uc54a\uc744 \ub54c", + "not_occupied": "{entity_name} \uc774(\uac00) \uc0ac\uc6a9 \uc911\uc774\uc9c0 \uc54a\uac8c \ub420 \ub54c", + "not_opened": "{entity_name} \uc774(\uac00) \ub2eb\ud790 \ub54c", + "not_plugged_in": "{entity_name} \ud50c\ub7ec\uadf8\uac00 \ubf51\ud790 \ub54c", + "not_powered": "{entity_name} \uc5d0 \uc804\uc6d0\uc774 \uacf5\uae09\ub418\uc9c0 \uc54a\uc744 \ub54c", + "not_present": "{entity_name} \uc774(\uac00) \uc678\ucd9c \uc0c1\ud0dc\uac00 \ub420 \ub54c", + "not_unsafe": "{entity_name} \uc740(\ub294) \uc548\uc804\ud574\uc9c8 \ub54c", + "occupied": "{entity_name} \uc774(\uac00) \uc0ac\uc6a9 \uc911\uc774 \ub420 \ub54c", + "opened": "{entity_name} \uc774(\uac00) \uc5f4\ub9b4 \ub54c", + "plugged_in": "{entity_name} \ud50c\ub7ec\uadf8\uac00 \uaf3d\ud790 \ub54c", + "powered": "{entity_name} \uc5d0 \uc804\uc6d0\uc774 \uacf5\uae09\ub420 \ub54c", + "present": "{entity_name} \uc774(\uac00) \uc7ac\uc2e4 \uc0c1\ud0dc\uac00 \ub420 \ub54c", + "problem": "{entity_name} \uc774(\uac00) \ubb38\uc81c\ub97c \uac10\uc9c0\ud560 \ub54c", + "smoke": "{entity_name} \uc774(\uac00) \uc5f0\uae30\ub97c \uac10\uc9c0\ud560 \ub54c", + "sound": "{entity_name} \uc774(\uac00) \uc18c\ub9ac\ub97c \uac10\uc9c0\ud560 \ub54c", + "turned_off": "{entity_name} \uc774(\uac00) \uaebc\uc9c8 \ub54c", + "turned_on": "{entity_name} \uc774(\uac00) \ucf1c\uc9c8 \ub54c", + "unsafe": "{entity_name} \uc774(\uac00) \uc548\uc804\ud558\uc9c0 \uc54a\uc744 \ub54c", + "vibration": "{entity_name} \uc774(\uac00) \uc9c4\ub3d9\uc744 \uac10\uc9c0\ud560 \ub54c" } } } \ No newline at end of file diff --git a/homeassistant/components/cast/.translations/ko.json b/homeassistant/components/cast/.translations/ko.json index 1374372aa24..f0eebf4b7b9 100644 --- a/homeassistant/components/cast/.translations/ko.json +++ b/homeassistant/components/cast/.translations/ko.json @@ -6,7 +6,7 @@ }, "step": { "confirm": { - "description": "Google \uce90\uc2a4\ud2b8\ub97c \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "Google \uce90\uc2a4\ud2b8\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "Google \uce90\uc2a4\ud2b8" } }, diff --git a/homeassistant/components/climate/.translations/ko.json b/homeassistant/components/climate/.translations/ko.json new file mode 100644 index 00000000000..299172958e8 --- /dev/null +++ b/homeassistant/components/climate/.translations/ko.json @@ -0,0 +1,17 @@ +{ + "device_automation": { + "action_type": { + "set_hvac_mode": "{entity_name} \uc758 HVAC \ubaa8\ub4dc \ubcc0\uacbd", + "set_preset_mode": "{entity_name} \uc758 \uc0ac\uc804 \uc124\uc815 \ubcc0\uacbd" + }, + "condition_type": { + "is_hvac_mode": "{entity_name} \uc774(\uac00) \ud2b9\uc815 HVAC \ubaa8\ub4dc\ub85c \uc124\uc815\ub418\uc5b4\uc788\uc73c\uba74", + "is_preset_mode": "{entity_name} \uc774(\uac00) \ud2b9\uc815 \uc0ac\uc804 \uc124\uc815 \ubaa8\ub4dc\ub85c \uc124\uc815\ub418\uc5b4\uc788\uc73c\uba74" + }, + "trigger_type": { + "current_humidity_changed": "{entity_name} \uc774(\uac00) \uc2b5\ub3c4 \ubcc0\ud654\ub97c \uac10\uc9c0\ud560 \ub54c", + "current_temperature_changed": "{entity_name} \uc774(\uac00) \uc628\ub3c4 \ubcc0\ud654\ub97c \uac10\uc9c0\ud560 \ub54c", + "hvac_mode_changed": "{entity_name} HVAC \ubaa8\ub4dc\uac00 \ubcc0\uacbd\ub420 \ub54c" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/coolmaster/.translations/ko.json b/homeassistant/components/coolmaster/.translations/ko.json index ff6ddf0acfe..4d96e606c7b 100644 --- a/homeassistant/components/coolmaster/.translations/ko.json +++ b/homeassistant/components/coolmaster/.translations/ko.json @@ -13,7 +13,7 @@ "heat": "\ub09c\ubc29 \ubaa8\ub4dc \uc9c0\uc6d0", "heat_cool": "\uc790\ub3d9 \ub0c9/\ub09c\ubc29 \ubaa8\ub4dc \uc9c0\uc6d0", "host": "\ud638\uc2a4\ud2b8", - "off": "\uc804\uc6d0\uc744 \ub04c \uc218 \uc788\uc2b4" + "off": "\uc804\uc6d0 \ub044\uae30 \ud5c8\uc6a9" }, "title": "CoolMasterNet \uc5f0\uacb0 \uc0c1\uc138\uc815\ubcf4\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694." } diff --git a/homeassistant/components/cover/.translations/ko.json b/homeassistant/components/cover/.translations/ko.json index 6a59bb9f6ae..145938b6f24 100644 --- a/homeassistant/components/cover/.translations/ko.json +++ b/homeassistant/components/cover/.translations/ko.json @@ -1,20 +1,20 @@ { "device_automation": { "condition_type": { - "is_closed": "{entity_name} \uc774(\uac00) \ub2eb\ud614\uc2b5\ub2c8\ub2e4", - "is_closing": "{entity_name} \uc774(\uac00) \ub2eb\ud799\ub2c8\ub2e4", - "is_open": "{entity_name} \uc774(\uac00) \uc5f4\ub838\uc2b5\ub2c8\ub2e4", - "is_opening": "{entity_name} \uc774(\uac00) \uc5f4\ub9bd\ub2c8\ub2e4", - "is_position": "\ud604\uc7ac {entity_name} \uac1c\ud3d0 \uc704\uce58", - "is_tilt_position": "\ud604\uc7ac {entity_name} \uac1c\ud3d0 \uae30\uc6b8\uae30" + "is_closed": "{entity_name} \uc774(\uac00) \ub2eb\ud600 \uc788\uc73c\uba74", + "is_closing": "{entity_name} \uc774(\uac00) \ub2eb\ud788\ub294 \uc911\uc774\uba74", + "is_open": "{entity_name} \uc774(\uac00) \uc5f4\ub824 \uc788\uc73c\uba74", + "is_opening": "{entity_name} \uc774(\uac00) \uc5f4\ub9ac\ub294 \uc911\uc774\uba74", + "is_position": "\ud604\uc7ac {entity_name} \uac1c\ud3d0 \uc704\uce58\uac00 ~ \uc774\uba74", + "is_tilt_position": "\ud604\uc7ac {entity_name} \uac1c\ud3d0 \uae30\uc6b8\uae30\uac00 ~ \uc774\uba74" }, "trigger_type": { - "closed": "{entity_name} \uc774(\uac00) \ub2eb\ud798", - "closing": "{entity_name} \uc774(\uac00) \ub2eb\ud788\ub294 \uc911", - "opened": "{entity_name} \uc774(\uac00) \uc5f4\ub9bc", - "opening": "{entity_name} \uc774(\uac00) \uc5f4\ub9ac\ub294 \uc911", - "position": "{entity_name} \uac1c\ud3d0 \uc704\uce58 \ubcc0\ud654", - "tilt_position": "{entity_name} \uac1c\ud3d0 \uae30\uc6b8\uae30 \ubcc0\ud654" + "closed": "{entity_name} \uc774(\uac00) \ub2eb\ud790 \ub54c", + "closing": "{entity_name} \uc774(\uac00) \ub2eb\ud788\ub294 \uc911\uc77c \ub54c", + "opened": "{entity_name} \uc774(\uac00) \uc5f4\ub9b4 \ub54c", + "opening": "{entity_name} \uc774(\uac00) \uc5f4\ub9ac\ub294 \uc911\uc77c \ub54c", + "position": "{entity_name} \uac1c\ud3d0 \uc704\uce58\uac00 \ubcc0\ud560 \ub54c", + "tilt_position": "{entity_name} \uac1c\ud3d0 \uae30\uc6b8\uae30\uac00 \ubcc0\ud560 \ub54c" } } } \ No newline at end of file diff --git a/homeassistant/components/deconz/.translations/ko.json b/homeassistant/components/deconz/.translations/ko.json index fede936b964..2c9b864dfc1 100644 --- a/homeassistant/components/deconz/.translations/ko.json +++ b/homeassistant/components/deconz/.translations/ko.json @@ -65,27 +65,27 @@ "turn_on": "\ucf1c\uae30" }, "trigger_type": { - "remote_awakened": "\uae30\uae30 \uc808\uc804 \ubaa8\ub4dc \ud574\uc81c\ub428", - "remote_button_double_press": "\"{subtype}\" \ubc84\ud2bc\uc744 \ub450 \ubc88 \ub204\ub984", - "remote_button_long_press": "\"{subtype}\" \ubc84\ud2bc\uc744 \uacc4\uc18d \ub204\ub984", - "remote_button_long_release": "\"{subtype}\" \ubc84\ud2bc\uc744 \uae38\uac8c \ub20c\ub800\ub2e4\uac00 \ub5cc", - "remote_button_quadruple_press": "\"{subtype}\" \ubc84\ud2bc\uc744 \ub124 \ubc88 \ub204\ub984", - "remote_button_quintuple_press": "\"{subtype}\" \ubc84\ud2bc\uc744 \ub2e4\uc12f \ubc88 \ub204\ub984", - "remote_button_rotated": "\"{subtype}\" \ubc84\ud2bc\uc744 \ud68c\uc804", - "remote_button_rotation_stopped": "\"{subtype}\" \ubc84\ud2bc\uc744 \ud68c\uc804 \uc815\uc9c0", - "remote_button_short_press": "\"{subtype}\" \ubc84\ud2bc\uc744 \ub204\ub984", - "remote_button_short_release": "\"{subtype}\" \ubc84\ud2bc\uc744 \ub5cc", - "remote_button_triple_press": "\"{subtype}\" \ubc84\ud2bc\uc744 \uc138 \ubc88 \ub204\ub984", - "remote_double_tap": "\uae30\uae30\uc758 \"{subtype}\" \uac00 \ub354\ube14\ud0ed \ub428", - "remote_falling": "\uae30\uae30\uac00 \ub5a8\uc5b4\uc9d0", - "remote_gyro_activated": "\uae30\uae30 \ud754\ub4e6", - "remote_moved": "\uae30\uae30\uc758 \"{subtype}\" \uac00 \uc704\ub85c \ud5a5\ud55c\ucc44\ub85c \uc6c0\uc9c1\uc784", - "remote_rotate_from_side_1": "\"\uba74 1\" \uc5d0\uc11c \"{subtype}\" \ub85c \uae30\uae30\uac00 \ud68c\uc804\ub428", - "remote_rotate_from_side_2": "\"\uba74 2\" \uc5d0\uc11c \"{subtype}\" \ub85c \uae30\uae30\uac00 \ud68c\uc804\ub428", - "remote_rotate_from_side_3": "\"\uba74 3\" \uc5d0\uc11c \"{subtype}\" \ub85c \uae30\uae30\uac00 \ud68c\uc804\ub428", - "remote_rotate_from_side_4": "\"\uba74 4\" \uc5d0\uc11c \"{subtype}\" \ub85c \uae30\uae30\uac00 \ud68c\uc804\ub428", - "remote_rotate_from_side_5": "\"\uba74 5\" \uc5d0\uc11c \"{subtype}\" \ub85c \uae30\uae30\uac00 \ud68c\uc804\ub428", - "remote_rotate_from_side_6": "\"\uba74 6\" \uc5d0\uc11c \"{subtype}\" \ub85c \uae30\uae30\uac00 \ud68c\uc804\ub428" + "remote_awakened": "\uae30\uae30 \uc808\uc804 \ubaa8\ub4dc \ud574\uc81c\ub420 \ub54c", + "remote_button_double_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub450 \ubc88 \ub20c\ub9b4 \ub54c", + "remote_button_long_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \uacc4\uc18d \ub20c\ub824\uc9c8 \ub54c", + "remote_button_long_release": "\"{subtype}\" \ubc84\ud2bc\uc774 \uae38\uac8c \ub20c\ub838\ub2e4\uac00 \uc190\uc744 \ub5c4 \ub54c", + "remote_button_quadruple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub124 \ubc88 \ub20c\ub9b4 \ub54c", + "remote_button_quintuple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub2e4\uc12f \ubc88 \ub20c\ub9b4 \ub54c", + "remote_button_rotated": "\"{subtype}\" \ub85c \ubc84\ud2bc\uc774 \ud68c\uc804\ub420 \ub54c", + "remote_button_rotation_stopped": "\"{subtype}\" \ub85c \ubc84\ud2bc\uc774 \ud68c\uc804\uc744 \uba48\ucd9c \ub54c", + "remote_button_short_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub20c\ub9b4 \ub54c", + "remote_button_short_release": "\"{subtype}\" \ubc84\ud2bc\uc5d0\uc11c \uc190\uc744 \ub5c4 \ub54c", + "remote_button_triple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \uc138 \ubc88 \ub20c\ub9b4 \ub54c", + "remote_double_tap": "\uae30\uae30\uc758 \"{subtype}\" \uac00 \ub354\ube14 \ud0ed \ub420 \ub54c", + "remote_falling": "\uae30\uae30\uac00 \ub5a8\uc5b4\uc9c8 \ub54c", + "remote_gyro_activated": "\uae30\uae30\uac00 \ud754\ub4e4\ub9b4 \ub54c", + "remote_moved": "\uae30\uae30\uc758 \"{subtype}\" \uac00 \uc704\ub85c \ud5a5\ud55c\ucc44\ub85c \uc6c0\uc9c1\uc77c \ub54c", + "remote_rotate_from_side_1": "\"\uba74 1\" \uc5d0\uc11c \"{subtype}\" \ub85c \uae30\uae30\uac00 \ud68c\uc804\ub420 \ub54c", + "remote_rotate_from_side_2": "\"\uba74 2\" \uc5d0\uc11c \"{subtype}\" \ub85c \uae30\uae30\uac00 \ud68c\uc804\ub420 \ub54c", + "remote_rotate_from_side_3": "\"\uba74 3\" \uc5d0\uc11c \"{subtype}\" \ub85c \uae30\uae30\uac00 \ud68c\uc804\ub420 \ub54c", + "remote_rotate_from_side_4": "\"\uba74 4\" \uc5d0\uc11c \"{subtype}\" \ub85c \uae30\uae30\uac00 \ud68c\uc804\ub420 \ub54c", + "remote_rotate_from_side_5": "\"\uba74 5\" \uc5d0\uc11c \"{subtype}\" \ub85c \uae30\uae30\uac00 \ud68c\uc804\ub420 \ub54c", + "remote_rotate_from_side_6": "\"\uba74 6\" \uc5d0\uc11c \"{subtype}\" \ub85c \uae30\uae30\uac00 \ud68c\uc804\ub420 \ub54c" } }, "options": { diff --git a/homeassistant/components/demo/.translations/ko.json b/homeassistant/components/demo/.translations/ko.json new file mode 100644 index 00000000000..d20943c7b36 --- /dev/null +++ b/homeassistant/components/demo/.translations/ko.json @@ -0,0 +1,5 @@ +{ + "config": { + "title": "\ub370\ubaa8" + } +} \ No newline at end of file diff --git a/homeassistant/components/device_tracker/.translations/ko.json b/homeassistant/components/device_tracker/.translations/ko.json index 92137ea2768..1834767222a 100644 --- a/homeassistant/components/device_tracker/.translations/ko.json +++ b/homeassistant/components/device_tracker/.translations/ko.json @@ -1,8 +1,8 @@ { "device_automation": { "condition_type": { - "is_home": "{entity_name} \uc774(\uac00) \uc9d1\uc5d0 \uc788\uc2b5\ub2c8\ub2e4", - "is_not_home": "{entity_name} \uc774(\uac00) \uc678\ucd9c\uc911\uc785\ub2c8\ub2e4" + "is_home": "{entity_name} \uc774(\uac00) \uc9d1\uc5d0 \uc788\uc73c\uba74", + "is_not_home": "{entity_name} \uc774(\uac00) \uc678\ucd9c \uc911\uc774\uba74" } } } \ No newline at end of file diff --git a/homeassistant/components/dialogflow/.translations/ko.json b/homeassistant/components/dialogflow/.translations/ko.json index 91f15f1fb77..2010495d959 100644 --- a/homeassistant/components/dialogflow/.translations/ko.json +++ b/homeassistant/components/dialogflow/.translations/ko.json @@ -9,7 +9,7 @@ }, "step": { "user": { - "description": "Dialogflow \uc744 \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "Dialogflow \uc744 \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "Dialogflow Webhook \uc124\uc815" } }, diff --git a/homeassistant/components/elgato/.translations/ko.json b/homeassistant/components/elgato/.translations/ko.json new file mode 100644 index 00000000000..9d7ab4ef2b0 --- /dev/null +++ b/homeassistant/components/elgato/.translations/ko.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Elgato Key Light \uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "connection_error": "Elgato Key Light \uae30\uae30\uc5d0 \uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4." + }, + "error": { + "connection_error": "Elgato Key Light \uae30\uae30\uc5d0 \uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4." + }, + "flow_title": "Elgato Key Light: {serial_number}", + "step": { + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8 \ub610\ub294 IP \uc8fc\uc18c", + "port": "\ud3ec\ud2b8 \ubc88\ud638" + }, + "description": "Home Assistant \uc5d0 Elgato Key Light \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4.", + "title": "Elgato Key Light \uc5f0\uacb0" + }, + "zeroconf_confirm": { + "description": "Elgato Key Light \uc2dc\ub9ac\uc5bc \ubc88\ud638 `{serial_number}` \uc744(\ub97c) Home Assistant \uc5d0 \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "\ubc1c\uacac\ub41c Elgato Key Light \uae30\uae30" + } + }, + "title": "Elgato Key Light" + } +} \ No newline at end of file diff --git a/homeassistant/components/esphome/.translations/ko.json b/homeassistant/components/esphome/.translations/ko.json index b6bcf3cd1b3..4d8068c801b 100644 --- a/homeassistant/components/esphome/.translations/ko.json +++ b/homeassistant/components/esphome/.translations/ko.json @@ -18,8 +18,8 @@ "title": "\ube44\ubc00\ubc88\ud638 \uc785\ub825" }, "discovery_confirm": { - "description": "Home Assistant \uc5d0 ESPHome node `{name}` \uc744(\ub97c) \ucd94\uac00 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "\ubc1c\uacac \ub41c ESPHome node" + "description": "Home Assistant \uc5d0 ESPHome node `{name}` \uc744(\ub97c) \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "\ubc1c\uacac\ub41c ESPHome node" }, "user": { "data": { diff --git a/homeassistant/components/fan/.translations/ko.json b/homeassistant/components/fan/.translations/ko.json new file mode 100644 index 00000000000..dec2a711e57 --- /dev/null +++ b/homeassistant/components/fan/.translations/ko.json @@ -0,0 +1,16 @@ +{ + "device_automation": { + "action_type": { + "turn_off": "{entity_name} \ub044\uae30", + "turn_on": "{entity_name} \ucf1c\uae30" + }, + "condition_type": { + "is_off": "{entity_name} \uc774(\uac00) \uaebc\uc838 \uc788\uc73c\uba74", + "is_on": "{entity_name} \uc774(\uac00) \ucf1c\uc838 \uc788\uc73c\uba74" + }, + "trigger_type": { + "turned_off": "{entity_name} \uc774(\uac00) \uaebc\uc9c8 \ub54c", + "turned_on": "{entity_name} \uc774(\uac00) \ucf1c\uc9c8 \ub54c" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geofency/.translations/ko.json b/homeassistant/components/geofency/.translations/ko.json index 42ff061a151..37f5ef0e76a 100644 --- a/homeassistant/components/geofency/.translations/ko.json +++ b/homeassistant/components/geofency/.translations/ko.json @@ -9,7 +9,7 @@ }, "step": { "user": { - "description": "Geofency Webhook \uc744 \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "Geofency Webhook \uc744 \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "Geofency Webhook \uc124\uc815" } }, diff --git a/homeassistant/components/geonetnz_volcano/.translations/ko.json b/homeassistant/components/geonetnz_volcano/.translations/ko.json new file mode 100644 index 00000000000..5d393fef4c4 --- /dev/null +++ b/homeassistant/components/geonetnz_volcano/.translations/ko.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "identifier_exists": "\uc704\uce58\uac00 \uc774\ubbf8 \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "radius": "\ubc18\uacbd" + }, + "title": "\ud544\ud130 \uc138\ubd80 \uc0ac\ud56d\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694" + } + }, + "title": "GeoNet NZ Volcano" + } +} \ No newline at end of file diff --git a/homeassistant/components/gpslogger/.translations/ko.json b/homeassistant/components/gpslogger/.translations/ko.json index 786a67b0b19..19bfc36e424 100644 --- a/homeassistant/components/gpslogger/.translations/ko.json +++ b/homeassistant/components/gpslogger/.translations/ko.json @@ -9,7 +9,7 @@ }, "step": { "user": { - "description": "GPSLogger Webhook \uc744 \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "GPSLogger Webhook \uc744 \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "GPSLogger Webhook \uc124\uc815" } }, diff --git a/homeassistant/components/hisense_aehw4a1/.translations/ko.json b/homeassistant/components/hisense_aehw4a1/.translations/ko.json new file mode 100644 index 00000000000..6d8b6b4b44c --- /dev/null +++ b/homeassistant/components/hisense_aehw4a1/.translations/ko.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "no_devices_found": "Hisense AEH-W4A1 \uae30\uae30\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.", + "single_instance_allowed": "\ud558\ub098\uc758 Hisense AEH-W4A1 \ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + }, + "step": { + "confirm": { + "description": "Hisense AEH-W4A1 \uc744 \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "Hisense AEH-W4A1" + } + }, + "title": "Hisense AEH-W4A1" + } +} \ No newline at end of file diff --git a/homeassistant/components/icloud/.translations/ko.json b/homeassistant/components/icloud/.translations/ko.json new file mode 100644 index 00000000000..a689a895278 --- /dev/null +++ b/homeassistant/components/icloud/.translations/ko.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "username_exists": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + }, + "error": { + "login": "\ub85c\uadf8\uc778 \uc624\ub958: \uc774\uba54\uc77c \ubc0f \ube44\ubc00\ubc88\ud638\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694", + "send_verification_code": "\uc778\uc99d \ucf54\ub4dc\ub97c \ubcf4\ub0b4\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "username_exists": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "validate_verification_code": "\uc778\uc99d \ucf54\ub4dc\ub97c \ud655\uc778\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \uc2e0\ub8b0\ud560 \uc218 \uc788\ub294 \uae30\uae30\ub97c \uc120\ud0dd\ud558\uace0 \uc778\uc99d\uc744 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694" + }, + "step": { + "trusted_device": { + "data": { + "trusted_device": "\uc2e0\ub8b0\ud560 \uc218 \uc788\ub294 \uae30\uae30" + }, + "description": "\uc2e0\ub8b0\ud560 \uc218 \uc788\ub294 \uae30\uae30\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694", + "title": "iCloud \uac00 \uc2e0\ub8b0\ud560 \uc218 \uc788\ub294 \uae30\uae30" + }, + "user": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc774\uba54\uc77c" + }, + "description": "\uc790\uaca9 \uc99d\uba85\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694", + "title": "iCloud \uc790\uaca9 \uc99d\uba85" + }, + "verification_code": { + "data": { + "verification_code": "\uc778\uc99d \ucf54\ub4dc" + }, + "description": "iCloud \uc5d0\uc11c \ubc1b\uc740 \uc778\uc99d \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694", + "title": "iCloud \uc778\uc99d \ucf54\ub4dc" + } + }, + "title": "Apple iCloud" + } +} \ No newline at end of file diff --git a/homeassistant/components/ifttt/.translations/ko.json b/homeassistant/components/ifttt/.translations/ko.json index 75bdd0d99c8..9c8083a1d94 100644 --- a/homeassistant/components/ifttt/.translations/ko.json +++ b/homeassistant/components/ifttt/.translations/ko.json @@ -9,7 +9,7 @@ }, "step": { "user": { - "description": "IFTTT \ub97c \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "IFTTT \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "IFTTT Webhook \uc560\ud50c\ub9bf \uc124\uc815" } }, diff --git a/homeassistant/components/ios/.translations/ko.json b/homeassistant/components/ios/.translations/ko.json index 1496dab0555..283594a45b5 100644 --- a/homeassistant/components/ios/.translations/ko.json +++ b/homeassistant/components/ios/.translations/ko.json @@ -5,7 +5,7 @@ }, "step": { "confirm": { - "description": "Home Assistant iOS \ucef4\ud3ec\ub10c\ud2b8\ub97c \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "Home Assistant iOS \ucef4\ud3ec\ub10c\ud2b8\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "Home Assistant iOS" } }, diff --git a/homeassistant/components/izone/.translations/ko.json b/homeassistant/components/izone/.translations/ko.json index 69b8ce8a31e..91593b26511 100644 --- a/homeassistant/components/izone/.translations/ko.json +++ b/homeassistant/components/izone/.translations/ko.json @@ -6,7 +6,7 @@ }, "step": { "confirm": { - "description": "iZone \uc744 \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "iZone \uc744 \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "iZone" } }, diff --git a/homeassistant/components/light/.translations/ko.json b/homeassistant/components/light/.translations/ko.json index e055f67421e..b923fdb210e 100644 --- a/homeassistant/components/light/.translations/ko.json +++ b/homeassistant/components/light/.translations/ko.json @@ -6,12 +6,12 @@ "turn_on": "{entity_name} \ucf1c\uae30" }, "condition_type": { - "is_off": "{entity_name} \uc774(\uac00) \uaebc\uc84c\uc2b5\ub2c8\ub2e4", - "is_on": "{entity_name} \uc774(\uac00) \ucf1c\uc84c\uc2b5\ub2c8\ub2e4" + "is_off": "{entity_name} \uc774(\uac00) \uaebc\uc838 \uc788\uc73c\uba74", + "is_on": "{entity_name} \uc774(\uac00) \ucf1c\uc838 \uc788\uc73c\uba74" }, "trigger_type": { - "turned_off": "{entity_name} \uc774(\uac00) \uaebc\uc84c\uc2b5\ub2c8\ub2e4", - "turned_on": "{entity_name} \uc774(\uac00) \ucf1c\uc84c\uc2b5\ub2c8\ub2e4" + "turned_off": "{entity_name} \uc774(\uac00) \uaebc\uc9c8 \ub54c", + "turned_on": "{entity_name} \uc774(\uac00) \ucf1c\uc9c8 \ub54c" } } } \ No newline at end of file diff --git a/homeassistant/components/locative/.translations/ko.json b/homeassistant/components/locative/.translations/ko.json index 0649ed557c4..c53f538799f 100644 --- a/homeassistant/components/locative/.translations/ko.json +++ b/homeassistant/components/locative/.translations/ko.json @@ -9,7 +9,7 @@ }, "step": { "user": { - "description": "Locative Webhook \uc744 \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "Locative Webhook \uc744 \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "Locative Webhook \uc124\uc815" } }, diff --git a/homeassistant/components/lock/.translations/ko.json b/homeassistant/components/lock/.translations/ko.json index 6abd9cd60e6..fb202f73b37 100644 --- a/homeassistant/components/lock/.translations/ko.json +++ b/homeassistant/components/lock/.translations/ko.json @@ -6,8 +6,12 @@ "unlock": "{entity_name} \uc7a0\uae08 \ud574\uc81c" }, "condition_type": { - "is_locked": "{entity_name} \uc774(\uac00) \uc7a0\uacbc\uc2b5\ub2c8\ub2e4", - "is_unlocked": "{entity_name} \uc758 \uc7a0\uae08\uc774 \ud574\uc81c\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "is_locked": "{entity_name} \uc774(\uac00) \uc7a0\uaca8\uc788\uc73c\uba74", + "is_unlocked": "{entity_name} \uc774(\uac00) \uc7a0\uaca8\uc788\uc9c0 \uc54a\uc73c\uba74" + }, + "trigger_type": { + "locked": "{entity_name} \uc774(\uac00) \uc7a0\uae38 \ub54c", + "unlocked": "{entity_name} \uc774(\uac00) \uc7a0\uae08\uc774 \ud574\uc81c\ub420 \ub54c" } } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/.translations/ko.json b/homeassistant/components/lutron_caseta/.translations/ko.json new file mode 100644 index 00000000000..cfc3c290afe --- /dev/null +++ b/homeassistant/components/lutron_caseta/.translations/ko.json @@ -0,0 +1,5 @@ +{ + "config": { + "title": "Lutron Cas\u00e9ta" + } +} \ No newline at end of file diff --git a/homeassistant/components/mailgun/.translations/ko.json b/homeassistant/components/mailgun/.translations/ko.json index 4ca5b155e73..8f1f021caf6 100644 --- a/homeassistant/components/mailgun/.translations/ko.json +++ b/homeassistant/components/mailgun/.translations/ko.json @@ -9,7 +9,7 @@ }, "step": { "user": { - "description": "Mailgun \uc744 \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "Mailgun \uc744 \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "Mailgun Webhook \uc124\uc815" } }, diff --git a/homeassistant/components/media_player/.translations/ko.json b/homeassistant/components/media_player/.translations/ko.json index 7542154448f..49367eaf617 100644 --- a/homeassistant/components/media_player/.translations/ko.json +++ b/homeassistant/components/media_player/.translations/ko.json @@ -1,11 +1,11 @@ { "device_automation": { "condition_type": { - "is_idle": "{entity_name} \uc774(\uac00) \uc720\ud734\uc0c1\ud0dc\uc785\ub2c8\ub2e4", - "is_off": "{entity_name} \uc774(\uac00) \uaebc\uc84c\uc2b5\ub2c8\ub2e4", - "is_on": "{entity_name} \uc774(\uac00) \ucf1c\uc84c\uc2b5\ub2c8\ub2e4", - "is_paused": "{entity_name} \uc774(\uac00) \uc77c\uc2dc\uc911\uc9c0\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "is_playing": "{entity_name} \uc774(\uac00) \uc7ac\uc0dd\uc911\uc785\ub2c8\ub2e4" + "is_idle": "{entity_name} \uc774(\uac00) \uc720\ud734\uc0c1\ud0dc\uc774\uba74", + "is_off": "{entity_name} \uc774(\uac00) \uaebc\uc838 \uc788\uc73c\uba74", + "is_on": "{entity_name} \uc774(\uac00) \ucf1c\uc838 \uc788\uc73c\uba74", + "is_paused": "{entity_name} \uc774(\uac00) \uc77c\uc2dc\uc911\uc9c0\ub418\uc5b4 \uc788\uc73c\uba74", + "is_playing": "{entity_name} \uc774(\uac00) \uc7ac\uc0dd \uc911\uc774\uba74" } } } \ No newline at end of file diff --git a/homeassistant/components/owntracks/.translations/ko.json b/homeassistant/components/owntracks/.translations/ko.json index d70ca8b114e..ee1507d9e0a 100644 --- a/homeassistant/components/owntracks/.translations/ko.json +++ b/homeassistant/components/owntracks/.translations/ko.json @@ -8,7 +8,7 @@ }, "step": { "user": { - "description": "OwnTracks \ub97c \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "OwnTracks \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "OwnTracks \uc124\uc815" } }, diff --git a/homeassistant/components/plaato/.translations/ko.json b/homeassistant/components/plaato/.translations/ko.json index 50a51dff873..619fdcf736f 100644 --- a/homeassistant/components/plaato/.translations/ko.json +++ b/homeassistant/components/plaato/.translations/ko.json @@ -9,7 +9,7 @@ }, "step": { "user": { - "description": "Plaato Airlock \uc744 \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "Plaato Airlock \uc744 \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "Plaato Webhook \uc124\uc815" } }, diff --git a/homeassistant/components/plex/.translations/ko.json b/homeassistant/components/plex/.translations/ko.json index f8e78945802..cf5a7946b9d 100644 --- a/homeassistant/components/plex/.translations/ko.json +++ b/homeassistant/components/plex/.translations/ko.json @@ -6,6 +6,7 @@ "already_in_progress": "Plex \ub97c \uad6c\uc131 \uc911\uc785\ub2c8\ub2e4", "discovery_no_file": "\ub808\uac70\uc2dc \uad6c\uc131 \ud30c\uc77c\uc744 \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", "invalid_import": "\uac00\uc838\uc628 \uad6c\uc131 \ub0b4\uc6a9\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "non-interactive": "\ube44 \ub300\ud654\ud615 \uac00\uc838\uc624\uae30", "token_request_timeout": "\ud1a0\ud070 \ud68d\ub4dd \uc2dc\uac04\uc774 \ucd08\uacfc\ud588\uc2b5\ub2c8\ub2e4", "unknown": "\uc54c \uc218 \uc5c6\ub294 \uc774\uc720\ub85c \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4" }, diff --git a/homeassistant/components/ps4/.translations/ko.json b/homeassistant/components/ps4/.translations/ko.json index 25f64cd21e9..46bbd6b309c 100644 --- a/homeassistant/components/ps4/.translations/ko.json +++ b/homeassistant/components/ps4/.translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "credential_error": "\uc790\uaca9 \uc99d\uba85\uc744 \uac00\uc838\uc624\ub294 \uc911 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", - "devices_configured": "\ubc1c\uacac \ub41c \ubaa8\ub4e0 \uae30\uae30\ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "devices_configured": "\ubc1c\uacac\ub41c \ubaa8\ub4e0 \uae30\uae30\ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "no_devices_found": "PlayStation 4 \uae30\uae30\ub97c \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", "port_987_bind_error": "\ud3ec\ud2b8 987 \uc5d0 \ubc14\uc778\ub529 \ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ucd94\uac00 \uc815\ubcf4\ub294 [\uc548\ub0b4](https://www.home-assistant.io/components/ps4/) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.", "port_997_bind_error": "\ud3ec\ud2b8 997 \uc5d0 \ubc14\uc778\ub529 \ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ucd94\uac00 \uc815\ubcf4\ub294 [\uc548\ub0b4](https://www.home-assistant.io/components/ps4/) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." diff --git a/homeassistant/components/sensor/.translations/ko.json b/homeassistant/components/sensor/.translations/ko.json index 0e74f3f4f89..7716cc016c3 100644 --- a/homeassistant/components/sensor/.translations/ko.json +++ b/homeassistant/components/sensor/.translations/ko.json @@ -1,26 +1,26 @@ { "device_automation": { "condition_type": { - "is_battery_level": "\ud604\uc7ac {entity_name} \ubc30\ud130\ub9ac \uc794\ub7c9", - "is_humidity": "\ud604\uc7ac {entity_name} \uc2b5\ub3c4", - "is_illuminance": "\ud604\uc7ac {entity_name} \uc870\ub3c4", - "is_power": "\ud604\uc7ac {entity_name} \uc18c\ube44 \uc804\ub825", - "is_pressure": "\ud604\uc7ac {entity_name} \uc555\ub825", - "is_signal_strength": "\ud604\uc7ac {entity_name} \uc2e0\ud638 \uac15\ub3c4", - "is_temperature": "\ud604\uc7ac {entity_name} \uc628\ub3c4", - "is_timestamp": "\ud604\uc7ac {entity_name} \uc2dc\uac01", - "is_value": "\ud604\uc7ac {entity_name} \uac12" + "is_battery_level": "\ud604\uc7ac {entity_name} \ubc30\ud130\ub9ac \uc794\ub7c9\uc774 ~ \uc774\uba74", + "is_humidity": "\ud604\uc7ac {entity_name} \uc2b5\ub3c4\uac00 ~ \uc774\uba74", + "is_illuminance": "\ud604\uc7ac {entity_name} \uc870\ub3c4\uac00 ~ \uc774\uba74", + "is_power": "\ud604\uc7ac {entity_name} \uc18c\ube44 \uc804\ub825\uc774 ~ \uc774\uba74", + "is_pressure": "\ud604\uc7ac {entity_name} \uc555\ub825\uc774 ~ \uc774\uba74", + "is_signal_strength": "\ud604\uc7ac {entity_name} \uc2e0\ud638 \uac15\ub3c4\uac00 ~ \uc774\uba74", + "is_temperature": "\ud604\uc7ac {entity_name} \uc628\ub3c4\uac00 ~ \uc774\uba74", + "is_timestamp": "\ud604\uc7ac {entity_name} \uc2dc\uac01\uc774 ~ \uc774\uba74", + "is_value": "\ud604\uc7ac {entity_name} \uac12\uc774 ~ \uc774\uba74" }, "trigger_type": { - "battery_level": "{entity_name} \ubc30\ud130\ub9ac \uc794\ub7c9 \ubcc0\ud654", - "humidity": "{entity_name} \uc2b5\ub3c4 \ubcc0\ud654", - "illuminance": "{entity_name} \uc870\ub3c4 \ubcc0\ud654", - "power": "{entity_name} \uc18c\ube44 \uc804\ub825 \ubcc0\ud654", - "pressure": "{entity_name} \uc555\ub825 \ubcc0\ud654", - "signal_strength": "{entity_name} \uc2e0\ud638 \uac15\ub3c4 \ubcc0\ud654", - "temperature": "{entity_name} \uc628\ub3c4 \ubcc0\ud654", - "timestamp": "{entity_name} \uc2dc\uac01 \ubcc0\ud654", - "value": "{entity_name} \uac12 \ubcc0\ud654" + "battery_level": "{entity_name} \ubc30\ud130\ub9ac \uc794\ub7c9\uc774 \ubc14\ub014 \ub54c", + "humidity": "{entity_name} \uc2b5\ub3c4\uac00 \ubc14\ub014 \ub54c", + "illuminance": "{entity_name} \uc870\ub3c4\uac00 \ubc14\ub014 \ub54c", + "power": "{entity_name} \uc18c\ube44 \uc804\ub825\uc774 \ubc14\ub014 \ub54c", + "pressure": "{entity_name} \uc555\ub825\uc774 \ubc14\ub014 \ub54c", + "signal_strength": "{entity_name} \uc2e0\ud638 \uac15\ub3c4\uac00 \ubc14\ub014 \ub54c", + "temperature": "{entity_name} \uc628\ub3c4\uac00 \ubc14\ub014 \ub54c", + "timestamp": "{entity_name} \uc2dc\uac01\uc774 \ubc14\ub014 \ub54c", + "value": "{entity_name} \uac12\uc774 \ubc14\ub014 \ub54c" } } } \ No newline at end of file diff --git a/homeassistant/components/soma/.translations/ko.json b/homeassistant/components/soma/.translations/ko.json index 90995ebc9f2..ae4d84671a3 100644 --- a/homeassistant/components/soma/.translations/ko.json +++ b/homeassistant/components/soma/.translations/ko.json @@ -3,7 +3,9 @@ "abort": { "already_setup": "\ud558\ub098\uc758 Soma \uacc4\uc815\ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "authorize_url_timeout": "\uc778\uc99d url \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "missing_configuration": "Soma \uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694." + "connection_error": "SOMA Connect \uc5d0 \uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", + "missing_configuration": "Soma \uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", + "result_error": "SOMA Connect \uac00 \uc624\ub958 \uc0c1\ud0dc\ub85c \uc751\ub2f5\ud588\uc2b5\ub2c8\ub2e4." }, "create_entry": { "default": "Soma \ub85c \uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4." diff --git a/homeassistant/components/sonos/.translations/ko.json b/homeassistant/components/sonos/.translations/ko.json index 4ca3d621599..931a0beadfc 100644 --- a/homeassistant/components/sonos/.translations/ko.json +++ b/homeassistant/components/sonos/.translations/ko.json @@ -6,7 +6,7 @@ }, "step": { "confirm": { - "description": "Sonos \ub97c \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "Sonos \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "Sonos" } }, diff --git a/homeassistant/components/starline/.translations/ko.json b/homeassistant/components/starline/.translations/ko.json new file mode 100644 index 00000000000..4d7ecf427f8 --- /dev/null +++ b/homeassistant/components/starline/.translations/ko.json @@ -0,0 +1,42 @@ +{ + "config": { + "error": { + "error_auth_app": "\uc560\ud50c\ub9ac\ucf00\uc774\uc158 ID \ud639\uc740 \ubcf4\uc548\ud0a4\uac00 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4", + "error_auth_mfa": "\ube44\ubc00\ubc88\ud638\uac00 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4", + "error_auth_user": "\uc0ac\uc6a9\uc790 \uc774\ub984 \ub610\ub294 \ube44\ubc00\ubc88\ud638\uac00 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4" + }, + "step": { + "auth_app": { + "data": { + "app_id": "\uc560\ud50c\ub9ac\ucf00\uc774\uc158 ID", + "app_secret": "\ubcf4\uc548\ud0a4" + }, + "description": "StarLine \uac1c\ubc1c\uc790 \uacc4\uc815\uc758 \uc560\ud50c\ub9ac\ucf00\uc774\uc158 ID \ubc0f \ube44\ubc00\ubc88\ud638", + "title": "\uc560\ud50c\ub9ac\ucf00\uc774\uc158 \uc790\uaca9 \uc99d\uba85" + }, + "auth_captcha": { + "data": { + "captcha_code": "\uc774\ubbf8\uc9c0\uc758 \ucf54\ub4dc" + }, + "description": "{captcha_img}", + "title": "\ubcf4\uc548 \ubb38\uc790" + }, + "auth_mfa": { + "data": { + "mfa_code": "SMS \ucf54\ub4dc" + }, + "description": "{phone_number} \uc804\ud654\ub85c \uc804\uc1a1\ub41c \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694", + "title": "2\ub2e8\uacc4 \uc778\uc99d" + }, + "auth_user": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + }, + "description": "StarLine \uacc4\uc815 \uc774\uba54\uc77c \ubc0f \ube44\ubc00\ubc88\ud638", + "title": "\uc0ac\uc6a9\uc790 \uc790\uaca9 \uc99d\uba85" + } + }, + "title": "StarLine" + } +} \ No newline at end of file diff --git a/homeassistant/components/switch/.translations/ko.json b/homeassistant/components/switch/.translations/ko.json index 02c303f9329..d3b9b1dd169 100644 --- a/homeassistant/components/switch/.translations/ko.json +++ b/homeassistant/components/switch/.translations/ko.json @@ -6,14 +6,14 @@ "turn_on": "{entity_name} \ucf1c\uae30" }, "condition_type": { - "is_off": "{entity_name} \uc774(\uac00) \uaebc\uc84c\uc2b5\ub2c8\ub2e4", - "is_on": "{entity_name} \uc774(\uac00) \ucf1c\uc84c\uc2b5\ub2c8\ub2e4", - "turn_off": "{entity_name} \uc774(\uac00) \uaebc\uc84c\uc2b5\ub2c8\ub2e4", - "turn_on": "{entity_name} \uc774(\uac00) \ucf1c\uc84c\uc2b5\ub2c8\ub2e4" + "is_off": "{entity_name} \uc774(\uac00) \uaebc\uc838 \uc788\uc73c\uba74", + "is_on": "{entity_name} \uc774(\uac00) \ucf1c\uc838 \uc788\uc73c\uba74", + "turn_off": "{entity_name} \uc774(\uac00) \uaebc\uc838 \uc788\uc73c\uba74", + "turn_on": "{entity_name} \uc774(\uac00) \ucf1c\uc838 \uc788\uc73c\uba74" }, "trigger_type": { - "turned_off": "{entity_name} \uc774(\uac00) \uaebc\uc84c\uc2b5\ub2c8\ub2e4", - "turned_on": "{entity_name} \uc774(\uac00) \ucf1c\uc84c\uc2b5\ub2c8\ub2e4" + "turned_off": "{entity_name} \uc774(\uac00) \uaebc\uc9c8 \ub54c", + "turned_on": "{entity_name} \uc774(\uac00) \ucf1c\uc9c8 \ub54c" } } } \ No newline at end of file diff --git a/homeassistant/components/tesla/.translations/fr.json b/homeassistant/components/tesla/.translations/fr.json new file mode 100644 index 00000000000..69742d3370c --- /dev/null +++ b/homeassistant/components/tesla/.translations/fr.json @@ -0,0 +1,30 @@ +{ + "config": { + "error": { + "connection_error": "Erreur de connexion; v\u00e9rifier le r\u00e9seau et r\u00e9essayer", + "identifier_exists": "Email d\u00e9j\u00e0 enregistr\u00e9", + "invalid_credentials": "Informations d'identification invalides", + "unknown_error": "Erreur inconnue, veuillez signaler les informations du journal" + }, + "step": { + "user": { + "data": { + "password": "Mot de passe", + "username": "Adresse e-mail" + }, + "description": "Veuillez saisir vos informations.", + "title": "Tesla - Configuration" + } + }, + "title": "Tesla" + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Secondes entre les scans" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tesla/.translations/ko.json b/homeassistant/components/tesla/.translations/ko.json new file mode 100644 index 00000000000..8b7dc9ce93c --- /dev/null +++ b/homeassistant/components/tesla/.translations/ko.json @@ -0,0 +1,30 @@ +{ + "config": { + "error": { + "connection_error": "\uc5f0\uacb0 \uc624\ub958; \ub124\ud2b8\uc6cc\ud06c\ub97c \ud655\uc778\ud558\uace0 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694", + "identifier_exists": "\uc774\uba54\uc77c \uc8fc\uc18c\uac00 \uc774\ubbf8 \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_credentials": "\uc774\uba54\uc77c \uc8fc\uc18c \ud639\uc740 \ube44\ubc00\ubc88\ud638\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown_error": "\uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uc785\ub2c8\ub2e4. \ub85c\uadf8 \ub0b4\uc6a9\uc744 \uc54c\ub824\uc8fc\uc138\uc694" + }, + "step": { + "user": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc774\uba54\uc77c \uc8fc\uc18c" + }, + "description": "\uc0ac\uc6a9\uc790 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694", + "title": "Tesla - \uad6c\uc131" + } + }, + "title": "Tesla" + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\uc2a4\uce94 \uac04\uaca9(\ucd08)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tesla/.translations/ru.json b/homeassistant/components/tesla/.translations/ru.json new file mode 100644 index 00000000000..15eeabf6136 --- /dev/null +++ b/homeassistant/components/tesla/.translations/ru.json @@ -0,0 +1,30 @@ +{ + "config": { + "error": { + "connection_error": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0441\u0435\u0442\u044c \u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443.", + "identifier_exists": "\u0423\u0447\u0451\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u0443\u0436\u0435 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0430.", + "invalid_credentials": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0435 \u0443\u0447\u0451\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435.", + "unknown_error": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0410\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b" + }, + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0443\u0447\u0451\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438.", + "title": "Tesla" + } + }, + "title": "Tesla" + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043c\u0435\u0436\u0434\u0443 \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f\u043c\u0438 (\u0441\u0435\u043a.)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tplink/.translations/ko.json b/homeassistant/components/tplink/.translations/ko.json index 05bebdd1455..89255d78518 100644 --- a/homeassistant/components/tplink/.translations/ko.json +++ b/homeassistant/components/tplink/.translations/ko.json @@ -6,7 +6,7 @@ }, "step": { "confirm": { - "description": "TP-Link \uc2a4\ub9c8\ud2b8 \uae30\uae30\ub97c \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "TP-Link \uc2a4\ub9c8\ud2b8 \uae30\uae30\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "TP-Link Smart Home" } }, diff --git a/homeassistant/components/traccar/.translations/ko.json b/homeassistant/components/traccar/.translations/ko.json index d9f31967e68..40e1aaf4d6b 100644 --- a/homeassistant/components/traccar/.translations/ko.json +++ b/homeassistant/components/traccar/.translations/ko.json @@ -9,7 +9,7 @@ }, "step": { "user": { - "description": "Traccar \ub97c \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "Traccar \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "Traccar \uc124\uc815" } }, diff --git a/homeassistant/components/twilio/.translations/ko.json b/homeassistant/components/twilio/.translations/ko.json index 4e4c80801d4..b8e88820590 100644 --- a/homeassistant/components/twilio/.translations/ko.json +++ b/homeassistant/components/twilio/.translations/ko.json @@ -9,7 +9,7 @@ }, "step": { "user": { - "description": "Twilio \ub97c \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "Twilio \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "Twilio Webhook \uc124\uc815" } }, diff --git a/homeassistant/components/upnp/.translations/ko.json b/homeassistant/components/upnp/.translations/ko.json index d846a5e38ce..bd6aaeef4e2 100644 --- a/homeassistant/components/upnp/.translations/ko.json +++ b/homeassistant/components/upnp/.translations/ko.json @@ -10,7 +10,7 @@ }, "step": { "confirm": { - "description": "UPnP/IGD \ub97c \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "UPnP/IGD \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "UPnP/IGD" }, "init": { diff --git a/homeassistant/components/vacuum/.translations/ko.json b/homeassistant/components/vacuum/.translations/ko.json new file mode 100644 index 00000000000..0197329abda --- /dev/null +++ b/homeassistant/components/vacuum/.translations/ko.json @@ -0,0 +1,16 @@ +{ + "device_automation": { + "action_type": { + "clean": "{entity_name} \uc744(\ub97c) \uccad\uc18c\uc2dc\ud0a4\uae30", + "dock": "{entity_name} \uc744(\ub97c) \ucda9\uc804\uc2a4\ud14c\uc774\uc158\uc73c\ub85c \ubcf5\uadc0\uc2dc\ud0a4\uae30" + }, + "condition_type": { + "is_cleaning": "{entity_name} \uc774(\uac00) \uccad\uc18c \uc911\uc774\uba74", + "is_docked": "{entity_name} \uc774(\uac00) \ub3c4\ud0b9\ub418\uc5b4\uc788\uc73c\uba74" + }, + "trigger_type": { + "cleaning": "{entity_name} \uc774(\uac00) \uccad\uc18c\ub97c \uc2dc\uc791\ud560 \ub54c", + "docked": "{entity_name} \uc774(\uac00) \ub3c4\ud0b9\ub420 \ub54c" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wemo/.translations/ko.json b/homeassistant/components/wemo/.translations/ko.json index 57515f2c970..cc3a70a0bc6 100644 --- a/homeassistant/components/wemo/.translations/ko.json +++ b/homeassistant/components/wemo/.translations/ko.json @@ -6,7 +6,7 @@ }, "step": { "confirm": { - "description": "Wemo \ub97c \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "Wemo \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "Wemo" } }, diff --git a/homeassistant/components/wled/.translations/ko.json b/homeassistant/components/wled/.translations/ko.json index bee9c2a6204..38496c01ee8 100644 --- a/homeassistant/components/wled/.translations/ko.json +++ b/homeassistant/components/wled/.translations/ko.json @@ -12,8 +12,15 @@ "user": { "data": { "host": "\ud638\uc2a4\ud2b8 \ub610\ub294 IP \uc8fc\uc18c" - } + }, + "description": "Home Assistant \uc5d0 WLED \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4.", + "title": "WLED \uc5f0\uacb0" + }, + "zeroconf_confirm": { + "description": "Home Assistant \uc5d0 WLED `{name}` \uc744(\ub97c) \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "\ubc1c\uacac\ub41c WLED \uae30\uae30" } - } + }, + "title": "WLED" } } \ No newline at end of file diff --git a/homeassistant/components/zha/.translations/ko.json b/homeassistant/components/zha/.translations/ko.json index 3a62f5d7ebe..69b8f9ad9a4 100644 --- a/homeassistant/components/zha/.translations/ko.json +++ b/homeassistant/components/zha/.translations/ko.json @@ -47,21 +47,21 @@ "turn_on": "\ucf1c\uae30" }, "trigger_type": { - "device_dropped": "\uae30\uae30\ub97c \ub5a8\uad7c", - "device_flipped": "\"{subtype}\" \uae30\uae30\ub97c \ub4a4\uc9d1\uc74c", - "device_knocked": "\"{subtype}\" \uae30\uae30\ub97c \ub450\ub4dc\ub9bc", - "device_rotated": "\"{subtype}\" \uae30\uae30\ub97c \ud68c\uc804", - "device_shaken": "\uae30\uae30\ub97c \ud754\ub4e6", - "device_slid": "\"{subtype}\" \uae30\uae30\ub97c \uc2ac\ub77c\uc774\ub4dc", - "device_tilted": "\uae30\uae30\ub97c \uae30\uc6b8\uc784", - "remote_button_double_press": "\"{subtype}\" \ubc84\ud2bc\uc744 \ub450 \ubc88 \ub204\ub984", - "remote_button_long_press": "\"{subtype}\" \ubc84\ud2bc\uc744 \uacc4\uc18d \ub204\ub984", - "remote_button_long_release": "\"{subtype}\" \ubc84\ud2bc\uc744 \uae38\uac8c \ub20c\ub800\ub2e4\uac00 \ub5cc", - "remote_button_quadruple_press": "\"{subtype}\" \ubc84\ud2bc\uc744 \ub124 \ubc88 \ub204\ub984", - "remote_button_quintuple_press": "\"{subtype}\" \ubc84\ud2bc\uc744 \ub2e4\uc12f \ubc88 \ub204\ub984", - "remote_button_short_press": "\"{subtype}\" \ubc84\ud2bc\uc744 \ub204\ub984", - "remote_button_short_release": "\"{subtype}\" \ubc84\ud2bc\uc744 \ub5cc", - "remote_button_triple_press": "\"{subtype}\" \ubc84\ud2bc\uc744 \uc138 \ubc88 \ub204\ub984" + "device_dropped": "\uae30\uae30\uac00 \ub5a8\uc5b4\uc84c\uc744 \ub54c", + "device_flipped": "\"{subtype}\" \uae30\uae30\uac00 \ub4a4\uc9d1\uc5b4\uc9c8 \ub54c", + "device_knocked": "\"{subtype}\" \uae30\uae30\uac00 \ub450\ub4dc\ub824\uc9c8 \ub54c", + "device_rotated": "\"{subtype}\" \uae30\uae30\uac00 \ud68c\uc804\ub420 \ub54c", + "device_shaken": "\uae30\uae30\uac00 \ud754\ub4e4\ub9b4 \ub54c", + "device_slid": "\"{subtype}\" \uae30\uae30\uac00 \ubbf8\ub044\ub7ec\uc9c8 \ub54c", + "device_tilted": "\uae30\uae30\uac00 \uae30\uc6b8\uc5b4\uc9c8 \ub54c", + "remote_button_double_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub450 \ubc88 \ub20c\ub9b4 \ub54c", + "remote_button_long_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \uacc4\uc18d \ub20c\ub824\uc9c8 \ub54c", + "remote_button_long_release": "\"{subtype}\" \ubc84\ud2bc\uc774 \uae38\uac8c \ub20c\ub838\ub2e4\uac00 \uc190\uc744 \ub5c4 \ub54c", + "remote_button_quadruple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub124 \ubc88 \ub20c\ub9b4 \ub54c", + "remote_button_quintuple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub2e4\uc12f \ubc88 \ub20c\ub9b4 \ub54c", + "remote_button_short_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub20c\ub9b4 \ub54c", + "remote_button_short_release": "\"{subtype}\" \ubc84\ud2bc\uc5d0\uc11c \uc190\uc744 \ub5c4 \ub54c", + "remote_button_triple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \uc138 \ubc88 \ub20c\ub9b4 \ub54c" } } } \ No newline at end of file From 217b974cefaae4dbbee4f4ba916937b8a0e0dba9 Mon Sep 17 00:00:00 2001 From: Josef Schlehofer Date: Wed, 25 Dec 2019 10:18:10 +0100 Subject: [PATCH 396/677] Upgrade youtube_dl to 2019.12.25 (#30203) --- homeassistant/components/media_extractor/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_extractor/manifest.json b/homeassistant/components/media_extractor/manifest.json index 16f491f0cae..b57b4396869 100644 --- a/homeassistant/components/media_extractor/manifest.json +++ b/homeassistant/components/media_extractor/manifest.json @@ -3,7 +3,7 @@ "name": "Media extractor", "documentation": "https://www.home-assistant.io/integrations/media_extractor", "requirements": [ - "youtube_dl==2019.11.28" + "youtube_dl==2019.12.25" ], "dependencies": [ "media_player" diff --git a/requirements_all.txt b/requirements_all.txt index 7cfaff94ef8..fedfa2f61ec 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2085,7 +2085,7 @@ yeelight==0.5.0 yeelightsunflower==0.0.10 # homeassistant.components.media_extractor -youtube_dl==2019.11.28 +youtube_dl==2019.12.25 # homeassistant.components.zengge zengge==0.2 From 5e3102b2d6c3aece0009bfbb0894ccb7b478bff5 Mon Sep 17 00:00:00 2001 From: Niall Donegan Date: Wed, 25 Dec 2019 10:09:03 +0000 Subject: [PATCH 397/677] Pull track position from MPD status (#28579) * Pull track position from MPD status() This allows the progress bar to work when using the media-control card in lovelace. * Actually commit flake8 fix! * Extra documentation. Mainly to trigger CI rerun. * Updated to use self._media_position --- homeassistant/components/mpd/media_player.py | 22 ++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/homeassistant/components/mpd/media_player.py b/homeassistant/components/mpd/media_player.py index 2628815727c..6460becbb3e 100644 --- a/homeassistant/components/mpd/media_player.py +++ b/homeassistant/components/mpd/media_player.py @@ -37,6 +37,7 @@ from homeassistant.const import ( ) import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle +import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) @@ -98,6 +99,8 @@ class MpdDevice(MediaPlayerDevice): self._is_connected = False self._muted = False self._muted_volume = 0 + self._media_position_updated_at = None + self._media_position = None # set up MPD client self._client = mpd.MPDClient() @@ -130,6 +133,11 @@ class MpdDevice(MediaPlayerDevice): self._status = self._client.status() self._currentsong = self._client.currentsong() + position = self._status["time"] + if self._media_position != position: + self._media_position_updated_at = dt_util.utcnow() + self._media_position = position + self._update_playlists() @property @@ -188,6 +196,20 @@ class MpdDevice(MediaPlayerDevice): # Time does not exist for streams return self._currentsong.get("time") + @property + def media_position(self): + """Position of current playing media in seconds. + + This is returned as part of the mpd status rather than in the details + of the current song. + """ + return self._media_position + + @property + def media_position_updated_at(self): + """Last valid time of media position.""" + return self._media_position_updated_at + @property def media_title(self): """Return the title of current playing media.""" From 89450f405c76dd6d7fd166f59f9e30ee5a3bad17 Mon Sep 17 00:00:00 2001 From: rhadamantys <46837767+rhadamantys@users.noreply.github.com> Date: Wed, 25 Dec 2019 11:41:00 +0100 Subject: [PATCH 398/677] Add support for enocean window handle FA 10 00 (Hoppe) (#29968) * Added support for Somfy RTS wireless power socket and Somfy Temperature Sensore Thermos Wirefree io * Added code formatting fixes for commit 5faaf9c * added support for RollerShutterRTSComponent from Somfy * Added support for RTS roller shutter in set_cover_position * added support for enocean window handle FA 10 00 (Hoppe) * removed spaces in empty lines * removal of rawdata attribute / code style * isort fix * PyLint fixes * Improvements after review suggestions: rename device class to sensor type to avoid ambiguousness * added import for DEVICE_CLASS_POWER from const.py * removed window handle unit --- homeassistant/components/enocean/sensor.py | 66 ++++++++++++++++++---- 1 file changed, 54 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/enocean/sensor.py b/homeassistant/components/enocean/sensor.py index cfab52b3665..59ca10da791 100644 --- a/homeassistant/components/enocean/sensor.py +++ b/homeassistant/components/enocean/sensor.py @@ -10,8 +10,11 @@ from homeassistant.const import ( CONF_ID, CONF_NAME, DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_POWER, DEVICE_CLASS_TEMPERATURE, POWER_WATT, + STATE_CLOSED, + STATE_OPEN, TEMP_CELSIUS, ) import homeassistant.helpers.config_validation as cv @@ -25,34 +28,44 @@ CONF_RANGE_TO = "range_to" DEFAULT_NAME = "EnOcean sensor" -DEVICE_CLASS_POWER = "powersensor" +SENSOR_TYPE_HUMIDITY = "humidity" +SENSOR_TYPE_POWER = "powersensor" +SENSOR_TYPE_TEMPERATURE = "temperature" +SENSOR_TYPE_WINDOWHANDLE = "windowhandle" SENSOR_TYPES = { - DEVICE_CLASS_HUMIDITY: { + SENSOR_TYPE_HUMIDITY: { "name": "Humidity", "unit": "%", "icon": "mdi:water-percent", "class": DEVICE_CLASS_HUMIDITY, }, - DEVICE_CLASS_POWER: { + SENSOR_TYPE_POWER: { "name": "Power", "unit": POWER_WATT, "icon": "mdi:power-plug", "class": DEVICE_CLASS_POWER, }, - DEVICE_CLASS_TEMPERATURE: { + SENSOR_TYPE_TEMPERATURE: { "name": "Temperature", "unit": TEMP_CELSIUS, "icon": "mdi:thermometer", "class": DEVICE_CLASS_TEMPERATURE, }, + SENSOR_TYPE_WINDOWHANDLE: { + "name": "WindowHandle", + "unit": None, + "icon": "mdi:window", + "class": None, + }, } + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_ID): vol.All(cv.ensure_list, [vol.Coerce(int)]), vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_DEVICE_CLASS, default=DEVICE_CLASS_POWER): cv.string, + vol.Optional(CONF_DEVICE_CLASS, default=SENSOR_TYPE_POWER): cv.string, vol.Optional(CONF_MAX_TEMP, default=40): vol.Coerce(int), vol.Optional(CONF_MIN_TEMP, default=0): vol.Coerce(int), vol.Optional(CONF_RANGE_FROM, default=255): cv.positive_int, @@ -65,9 +78,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up an EnOcean sensor device.""" dev_id = config.get(CONF_ID) dev_name = config.get(CONF_NAME) - dev_class = config.get(CONF_DEVICE_CLASS) + sensor_type = config.get(CONF_DEVICE_CLASS) - if dev_class == DEVICE_CLASS_TEMPERATURE: + if sensor_type == SENSOR_TYPE_TEMPERATURE: temp_min = config.get(CONF_MIN_TEMP) temp_max = config.get(CONF_MAX_TEMP) range_from = config.get(CONF_RANGE_FROM) @@ -80,12 +93,15 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ] ) - elif dev_class == DEVICE_CLASS_HUMIDITY: + elif sensor_type == SENSOR_TYPE_HUMIDITY: add_entities([EnOceanHumiditySensor(dev_id, dev_name)]) - elif dev_class == DEVICE_CLASS_POWER: + elif sensor_type == SENSOR_TYPE_POWER: add_entities([EnOceanPowerSensor(dev_id, dev_name)]) + elif sensor_type == SENSOR_TYPE_WINDOWHANDLE: + add_entities([EnOceanWindowHandle(dev_id, dev_name)]) + class EnOceanSensor(enocean.EnOceanDevice): """Representation of an EnOcean sensor device such as a power meter.""" @@ -140,7 +156,7 @@ class EnOceanPowerSensor(EnOceanSensor): def __init__(self, dev_id, dev_name): """Initialize the EnOcean power sensor device.""" - super().__init__(dev_id, dev_name, DEVICE_CLASS_POWER) + super().__init__(dev_id, dev_name, SENSOR_TYPE_POWER) def value_changed(self, packet): """Update the internal state of the sensor.""" @@ -175,7 +191,7 @@ class EnOceanTemperatureSensor(EnOceanSensor): def __init__(self, dev_id, dev_name, scale_min, scale_max, range_from, range_to): """Initialize the EnOcean temperature sensor device.""" - super().__init__(dev_id, dev_name, DEVICE_CLASS_TEMPERATURE) + super().__init__(dev_id, dev_name, SENSOR_TYPE_TEMPERATURE) self._scale_min = scale_min self._scale_max = scale_max self.range_from = range_from @@ -205,7 +221,7 @@ class EnOceanHumiditySensor(EnOceanSensor): def __init__(self, dev_id, dev_name): """Initialize the EnOcean humidity sensor device.""" - super().__init__(dev_id, dev_name, DEVICE_CLASS_HUMIDITY) + super().__init__(dev_id, dev_name, SENSOR_TYPE_HUMIDITY) def value_changed(self, packet): """Update the internal state of the sensor.""" @@ -214,3 +230,29 @@ class EnOceanHumiditySensor(EnOceanSensor): humidity = packet.data[2] * 100 / 250 self._state = round(humidity, 1) self.schedule_update_ha_state() + + +class EnOceanWindowHandle(EnOceanSensor): + """Representation of an EnOcean window handle device. + + EEPs (EnOcean Equipment Profiles): + - F6-10-00 (Mechanical handle / Hoppe AG) + """ + + def __init__(self, dev_id, dev_name): + """Initialize the EnOcean window handle sensor device.""" + super().__init__(dev_id, dev_name, SENSOR_TYPE_WINDOWHANDLE) + + def value_changed(self, packet): + """Update the internal state of the sensor.""" + + action = (packet.data[1] & 0x70) >> 4 + + if action == 0x07: + self._state = STATE_CLOSED + if action in (0x04, 0x06): + self._state = STATE_OPEN + if action == 0x05: + self._state = "tilt" + + self.schedule_update_ha_state() From f56797e413f36b25b04d8ffb2d6d1b82ed468538 Mon Sep 17 00:00:00 2001 From: Kevin McCormack Date: Wed, 25 Dec 2019 05:45:49 -0500 Subject: [PATCH 399/677] Update Vivotek camera component (#30191) - Bump libpyvivotek version to 0.4.0 - Add digest authentication --- homeassistant/components/vivotek/camera.py | 7 +++++++ homeassistant/components/vivotek/manifest.json | 2 +- requirements_all.txt | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/vivotek/camera.py b/homeassistant/components/vivotek/camera.py index 665db373440..f4a195f5b0c 100644 --- a/homeassistant/components/vivotek/camera.py +++ b/homeassistant/components/vivotek/camera.py @@ -7,12 +7,15 @@ import voluptuous as vol from homeassistant.components.camera import PLATFORM_SCHEMA, SUPPORT_STREAM, Camera from homeassistant.const import ( + CONF_AUTHENTICATION, CONF_IP_ADDRESS, CONF_NAME, CONF_PASSWORD, CONF_SSL, CONF_USERNAME, CONF_VERIFY_SSL, + HTTP_BASIC_AUTHENTICATION, + HTTP_DIGEST_AUTHENTICATION, ) from homeassistant.helpers import config_validation as cv @@ -34,6 +37,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Required(CONF_PASSWORD): cv.string, vol.Required(CONF_USERNAME): cv.string, + vol.Optional(CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION): vol.In( + [HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION] + ), vol.Optional(CONF_SSL, default=False): cv.boolean, vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean, vol.Optional(CONF_FRAMERATE, default=2): cv.positive_int, @@ -54,6 +60,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): verify_ssl=config[CONF_VERIFY_SSL], usr=config[CONF_USERNAME], pwd=config[CONF_PASSWORD], + digest_auth=config[CONF_AUTHENTICATION] == HTTP_DIGEST_AUTHENTICATION, sec_lvl=config[CONF_SECURITY_LEVEL], ), stream_source=f"rtsp://{creds}@{config[CONF_IP_ADDRESS]}:554/{config[CONF_STREAM_PATH]}", diff --git a/homeassistant/components/vivotek/manifest.json b/homeassistant/components/vivotek/manifest.json index c97a8461da9..afd7535aa0f 100644 --- a/homeassistant/components/vivotek/manifest.json +++ b/homeassistant/components/vivotek/manifest.json @@ -3,7 +3,7 @@ "name": "Vivotek", "documentation": "https://www.home-assistant.io/integrations/vivotek", "requirements": [ - "libpyvivotek==0.3.1" + "libpyvivotek==0.4.0" ], "dependencies": [], "codeowners": [ diff --git a/requirements_all.txt b/requirements_all.txt index fedfa2f61ec..f69a9b8c080 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -762,7 +762,7 @@ libpurecool==0.5.0 libpyfoscam==1.0 # homeassistant.components.vivotek -libpyvivotek==0.3.1 +libpyvivotek==0.4.0 # homeassistant.components.mikrotik librouteros==2.3.0 From a5c450857182595493e33dde62bfbeb376d9eca8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Wed, 25 Dec 2019 13:42:28 +0200 Subject: [PATCH 400/677] Make Huawei LTE notify service name configurable (#30208) * Default Huawei LTE notify service name to notify.huawei_lte, make configurable Closes https://github.com/home-assistant/home-assistant/issues/29409 * Set default notify service name for uninvoked options flow --- .../huawei_lte/.translations/en.json | 1 + .../components/huawei_lte/__init__.py | 18 ++++++++++++++++-- .../components/huawei_lte/config_flow.py | 18 +++++++++++++++--- homeassistant/components/huawei_lte/const.py | 1 + .../components/huawei_lte/strings.json | 1 + 5 files changed, 34 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/huawei_lte/.translations/en.json b/homeassistant/components/huawei_lte/.translations/en.json index 52aaafe595c..5cee60fb727 100644 --- a/homeassistant/components/huawei_lte/.translations/en.json +++ b/homeassistant/components/huawei_lte/.translations/en.json @@ -33,6 +33,7 @@ "step": { "init": { "data": { + "name": "Notification service name", "recipient": "SMS notification recipients", "track_new_devices": "Track new devices" } diff --git a/homeassistant/components/huawei_lte/__init__.py b/homeassistant/components/huawei_lte/__init__.py index 531529b17cd..c79170b19db 100644 --- a/homeassistant/components/huawei_lte/__init__.py +++ b/homeassistant/components/huawei_lte/__init__.py @@ -28,6 +28,7 @@ from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( + CONF_NAME, CONF_PASSWORD, CONF_RECIPIENT, CONF_URL, @@ -54,6 +55,7 @@ from .const import ( ALL_KEYS, CONNECTION_TIMEOUT, DEFAULT_DEVICE_NAME, + DEFAULT_NOTIFY_SERVICE_NAME, DOMAIN, KEY_DEVICE_BASIC_INFORMATION, KEY_DEVICE_INFORMATION, @@ -82,9 +84,10 @@ NOTIFY_SCHEMA = vol.Any( None, vol.Schema( { + vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_RECIPIENT): vol.Any( None, vol.All(cv.ensure_list, [cv.string]) - ) + ), } ), ) @@ -262,6 +265,13 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry) ): new_options[f"{CONF_RECIPIENT}_from_yaml"] = yaml_recipient new_options[CONF_RECIPIENT] = yaml_recipient + yaml_notify_name = yaml_config.get(NOTIFY_DOMAIN, {}).get(CONF_NAME) + if ( + yaml_notify_name is not None + and yaml_notify_name != config_entry.options.get(f"{CONF_NAME}_from_yaml") + ): + new_options[f"{CONF_NAME}_from_yaml"] = yaml_notify_name + new_options[CONF_NAME] = yaml_notify_name # Update entry if overrides were found if new_data or new_options: hass.config_entries.async_update_entry( @@ -353,7 +363,11 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry) hass, NOTIFY_DOMAIN, DOMAIN, - {CONF_URL: url, CONF_RECIPIENT: config_entry.options.get(CONF_RECIPIENT)}, + { + CONF_URL: url, + CONF_NAME: config_entry.options.get(CONF_NAME, DEFAULT_NOTIFY_SERVICE_NAME), + CONF_RECIPIENT: config_entry.options.get(CONF_RECIPIENT), + }, hass.data[DOMAIN].hass_config, ) diff --git a/homeassistant/components/huawei_lte/config_flow.py b/homeassistant/components/huawei_lte/config_flow.py index b316472efaf..64803d4ad45 100644 --- a/homeassistant/components/huawei_lte/config_flow.py +++ b/homeassistant/components/huawei_lte/config_flow.py @@ -21,11 +21,17 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.components import ssdp -from homeassistant.const import CONF_PASSWORD, CONF_RECIPIENT, CONF_URL, CONF_USERNAME +from homeassistant.const import ( + CONF_NAME, + CONF_PASSWORD, + CONF_RECIPIENT, + CONF_URL, + CONF_USERNAME, +) from homeassistant.core import callback # see https://github.com/PyCQA/pylint/issues/3202 about the DOMAIN's pylint issue -from .const import CONNECTION_TIMEOUT, DEFAULT_DEVICE_NAME +from .const import CONNECTION_TIMEOUT, DEFAULT_DEVICE_NAME, DEFAULT_NOTIFY_SERVICE_NAME from .const import DOMAIN # pylint: disable=unused-import _LOGGER = logging.getLogger(__name__) @@ -246,10 +252,16 @@ class OptionsFlowHandler(config_entries.OptionsFlow): data_schema = vol.Schema( { + vol.Optional( + CONF_NAME, + default=self.config_entry.options.get( + CONF_NAME, DEFAULT_NOTIFY_SERVICE_NAME + ), + ): str, vol.Optional( CONF_RECIPIENT, default=self.config_entry.options.get(CONF_RECIPIENT, ""), - ): str + ): str, } ) return self.async_show_form(step_id="init", data_schema=data_schema) diff --git a/homeassistant/components/huawei_lte/const.py b/homeassistant/components/huawei_lte/const.py index c71b51435e1..164d833f03a 100644 --- a/homeassistant/components/huawei_lte/const.py +++ b/homeassistant/components/huawei_lte/const.py @@ -3,6 +3,7 @@ DOMAIN = "huawei_lte" DEFAULT_DEVICE_NAME = "LTE" +DEFAULT_NOTIFY_SERVICE_NAME = DOMAIN UPDATE_SIGNAL = f"{DOMAIN}_update" UPDATE_OPTIONS_SIGNAL = f"{DOMAIN}_options_update" diff --git a/homeassistant/components/huawei_lte/strings.json b/homeassistant/components/huawei_lte/strings.json index 17684253671..0d586e0d0ad 100644 --- a/homeassistant/components/huawei_lte/strings.json +++ b/homeassistant/components/huawei_lte/strings.json @@ -33,6 +33,7 @@ "step": { "init": { "data": { + "name": "Notification service name", "recipient": "SMS notification recipients", "track_new_devices": "Track new devices" } From 50a87bbe18b9128a3e8ed861d9e2e42e9248bdea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Wed, 25 Dec 2019 13:43:51 +0200 Subject: [PATCH 401/677] Add Huawei LTE integration suspend and resume services (#30207) Useful e.g. if accessing the router web interface from another source such as a web browser is temporarily required. --- .../components/huawei_lte/__init__.py | 46 +++++++++++++++---- homeassistant/components/huawei_lte/const.py | 9 ++++ homeassistant/components/huawei_lte/notify.py | 6 +++ .../components/huawei_lte/services.yaml | 17 +++++++ 4 files changed, 68 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/huawei_lte/__init__.py b/homeassistant/components/huawei_lte/__init__.py index c79170b19db..deb92de218c 100644 --- a/homeassistant/components/huawei_lte/__init__.py +++ b/homeassistant/components/huawei_lte/__init__.py @@ -52,6 +52,7 @@ from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import HomeAssistantType from .const import ( + ADMIN_SERVICES, ALL_KEYS, CONNECTION_TIMEOUT, DEFAULT_DEVICE_NAME, @@ -66,6 +67,8 @@ from .const import ( KEY_WLAN_HOST_LIST, SERVICE_CLEAR_TRAFFIC_STATISTICS, SERVICE_REBOOT, + SERVICE_RESUME_INTEGRATION, + SERVICE_SUSPEND_INTEGRATION, UPDATE_OPTIONS_SIGNAL, UPDATE_SIGNAL, ) @@ -137,6 +140,7 @@ class Router: ) unload_handlers: List[CALLBACK_TYPE] = attr.ib(init=False, factory=list) client: Client + suspended = attr.ib(init=False, default=False) def __attrs_post_init__(self): """Set up internal state on init.""" @@ -191,6 +195,10 @@ class Router: def update(self) -> None: """Update router data.""" + if self.suspended: + _LOGGER.debug("Integration suspended, not updating data") + return + self._get_data(KEY_DEVICE_INFORMATION, self.client.device.information) if self.data.get(KEY_DEVICE_INFORMATION): # Full information includes everything in basic @@ -210,15 +218,8 @@ class Router: self.signal_update() - def cleanup(self, *_) -> None: - """Clean up resources.""" - - self.subscriptions.clear() - - for handler in self.unload_handlers: - handler() - self.unload_handlers.clear() - + def logout(self) -> None: + """Log out router session.""" if not isinstance(self.connection, AuthorizedConnection): return try: @@ -230,6 +231,17 @@ class Router: except Exception: # pylint: disable=broad-except _LOGGER.warning("Logout error", exc_info=True) + def cleanup(self, *_) -> None: + """Clean up resources.""" + + self.subscriptions.clear() + + for handler in self.unload_handlers: + handler() + self.unload_handlers.clear() + + self.logout() + @attr.s class HuaweiLteData: @@ -441,15 +453,29 @@ async def async_setup(hass: HomeAssistantType, config) -> bool: return if service.service == SERVICE_CLEAR_TRAFFIC_STATISTICS: + if router.suspended: + _LOGGER.debug("%s: ignored, integration suspended", service.service) + return result = router.client.monitoring.set_clear_traffic() _LOGGER.debug("%s: %s", service.service, result) elif service.service == SERVICE_REBOOT: + if router.suspended: + _LOGGER.debug("%s: ignored, integration suspended", service.service) + return result = router.client.device.reboot() _LOGGER.debug("%s: %s", service.service, result) + elif service.service == SERVICE_RESUME_INTEGRATION: + # Login will be handled automatically on demand + router.suspended = False + _LOGGER.debug("%s: %s", service.service, "done") + elif service.service == SERVICE_SUSPEND_INTEGRATION: + router.logout() + router.suspended = True + _LOGGER.debug("%s: %s", service.service, "done") else: _LOGGER.error("%s: unsupported service", service.service) - for service in (SERVICE_CLEAR_TRAFFIC_STATISTICS, SERVICE_REBOOT): + for service in ADMIN_SERVICES: hass.helpers.service.async_register_admin_service( DOMAIN, service, service_handler, schema=SERVICE_SCHEMA, ) diff --git a/homeassistant/components/huawei_lte/const.py b/homeassistant/components/huawei_lte/const.py index 164d833f03a..c6837fce06c 100644 --- a/homeassistant/components/huawei_lte/const.py +++ b/homeassistant/components/huawei_lte/const.py @@ -15,6 +15,15 @@ CONNECTION_TIMEOUT = 10 SERVICE_CLEAR_TRAFFIC_STATISTICS = "clear_traffic_statistics" SERVICE_REBOOT = "reboot" +SERVICE_RESUME_INTEGRATION = "resume_integration" +SERVICE_SUSPEND_INTEGRATION = "suspend_integration" + +ADMIN_SERVICES = { + SERVICE_CLEAR_TRAFFIC_STATISTICS, + SERVICE_REBOOT, + SERVICE_RESUME_INTEGRATION, + SERVICE_SUSPEND_INTEGRATION, +} KEY_DEVICE_BASIC_INFORMATION = "device_basic_information" KEY_DEVICE_INFORMATION = "device_information" diff --git a/homeassistant/components/huawei_lte/notify.py b/homeassistant/components/huawei_lte/notify.py index 494d0ec720e..5619a5d702c 100644 --- a/homeassistant/components/huawei_lte/notify.py +++ b/homeassistant/components/huawei_lte/notify.py @@ -44,6 +44,12 @@ class HuaweiLteSmsNotificationService(BaseNotificationService): if not targets or not message: return + if self.router.suspended: + _LOGGER.debug( + "Integration suspended, not sending notification to %s", targets + ) + return + try: resp = self.router.client.sms.send_sms( phone_numbers=targets, message=message diff --git a/homeassistant/components/huawei_lte/services.yaml b/homeassistant/components/huawei_lte/services.yaml index 428745ee33e..bcb9be33299 100644 --- a/homeassistant/components/huawei_lte/services.yaml +++ b/homeassistant/components/huawei_lte/services.yaml @@ -11,3 +11,20 @@ reboot: url: description: URL of router to reboot; optional when only one is configured. example: http://192.168.100.1/ + +resume_integration: + description: Resume suspended integration. + fields: + url: + description: URL of router to resume integration for; optional when only one is configured. + example: http://192.168.100.1/ + +suspend_integration: + description: > + Suspend integration. Suspending logs the integration out from the router, and stops accessing it. + Useful e.g. if accessing the router web interface from another source such as a web browser is temporarily required. + Invoke the resume_integration service to resume. + fields: + url: + description: URL of router to resume integration for; optional when only one is configured. + example: http://192.168.100.1/ From 169c4089ff7cdfebc49c5eb27a8766edaf63bd38 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Thu, 26 Dec 2019 00:32:16 +0000 Subject: [PATCH 402/677] [ci skip] Translation update --- .../geonetnz_quakes/.translations/hu.json | 2 +- .../huawei_lte/.translations/es.json | 1 + .../huawei_lte/.translations/ru.json | 1 + .../components/notion/.translations/hu.json | 2 +- .../components/tesla/.translations/es.json | 30 +++++++++++++++++++ .../components/tesla/.translations/hu.json | 21 +++++++++++++ 6 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/tesla/.translations/es.json create mode 100644 homeassistant/components/tesla/.translations/hu.json diff --git a/homeassistant/components/geonetnz_quakes/.translations/hu.json b/homeassistant/components/geonetnz_quakes/.translations/hu.json index 42de5a13142..4a163d24b75 100644 --- a/homeassistant/components/geonetnz_quakes/.translations/hu.json +++ b/homeassistant/components/geonetnz_quakes/.translations/hu.json @@ -5,7 +5,7 @@ "data": { "radius": "Sug\u00e1r" }, - "title": "T\u00f6ltse ki a sz\u0171r\u0151 adatait." + "title": "T\u00f6ltsd ki a sz\u0171r\u0151 adatait." } } } diff --git a/homeassistant/components/huawei_lte/.translations/es.json b/homeassistant/components/huawei_lte/.translations/es.json index 92ccf8fc048..c35d1eacf23 100644 --- a/homeassistant/components/huawei_lte/.translations/es.json +++ b/homeassistant/components/huawei_lte/.translations/es.json @@ -33,6 +33,7 @@ "step": { "init": { "data": { + "name": "Nombre del servicio de notificaci\u00f3n", "recipient": "Destinatarios de notificaciones por SMS", "track_new_devices": "Rastrea nuevos dispositivos" } diff --git a/homeassistant/components/huawei_lte/.translations/ru.json b/homeassistant/components/huawei_lte/.translations/ru.json index ec28325dcdd..6f478987f50 100644 --- a/homeassistant/components/huawei_lte/.translations/ru.json +++ b/homeassistant/components/huawei_lte/.translations/ru.json @@ -33,6 +33,7 @@ "step": { "init": { "data": { + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0441\u043b\u0443\u0436\u0431\u044b \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439", "recipient": "\u041f\u043e\u043b\u0443\u0447\u0430\u0442\u0435\u043b\u0438 SMS-\u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439", "track_new_devices": "\u041e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0442\u044c \u043d\u043e\u0432\u044b\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430" } diff --git a/homeassistant/components/notion/.translations/hu.json b/homeassistant/components/notion/.translations/hu.json index 2f7664cf74e..79878858ddc 100644 --- a/homeassistant/components/notion/.translations/hu.json +++ b/homeassistant/components/notion/.translations/hu.json @@ -11,7 +11,7 @@ "password": "Jelsz\u00f3", "username": "Felhaszn\u00e1l\u00f3n\u00e9v/Email C\u00edm" }, - "title": "T\u00f6ltse ki adatait" + "title": "T\u00f6ltsd ki az adataid" } } } diff --git a/homeassistant/components/tesla/.translations/es.json b/homeassistant/components/tesla/.translations/es.json new file mode 100644 index 00000000000..64bab24ee3f --- /dev/null +++ b/homeassistant/components/tesla/.translations/es.json @@ -0,0 +1,30 @@ +{ + "config": { + "error": { + "connection_error": "Error de conexi\u00f3n; compruebe la red y vuelva a intentarlo", + "identifier_exists": "Correo electr\u00f3nico ya registrado", + "invalid_credentials": "Credenciales no v\u00e1lidas", + "unknown_error": "Error desconocido, por favor reporte la informaci\u00f3n de registro" + }, + "step": { + "user": { + "data": { + "password": "Contrase\u00f1a", + "username": "Direcci\u00f3n de correo electr\u00f3nico" + }, + "description": "Por favor, introduzca su informaci\u00f3n.", + "title": "Tesla - Configuraci\u00f3n" + } + }, + "title": "Tesla" + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Segundos entre escaneos" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tesla/.translations/hu.json b/homeassistant/components/tesla/.translations/hu.json new file mode 100644 index 00000000000..7a9a3deff49 --- /dev/null +++ b/homeassistant/components/tesla/.translations/hu.json @@ -0,0 +1,21 @@ +{ + "config": { + "error": { + "connection_error": "Hiba a csatlakoz\u00e1skor; ellen\u0151rizd a h\u00e1l\u00f3zatot \u00e9s pr\u00f3b\u00e1ld \u00fajra", + "identifier_exists": "Az e-mail c\u00edm m\u00e1r regisztr\u00e1lva van", + "invalid_credentials": "\u00c9rv\u00e9nytelen hiteles\u00edt\u0151 adatok", + "unknown_error": "Ismeretlen hiba, k\u00e9rlek jelentsd a napl\u00f3f\u00e1jlban l\u00e9v\u0151 adatokat" + }, + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Email c\u00edm" + }, + "description": "K\u00e9rlek, add meg az adataidat.", + "title": "Tesla - Konfigur\u00e1ci\u00f3" + } + }, + "title": "Tesla" + } +} \ No newline at end of file From e58ef36adc1b1ac9faa21856d43e8557a13153c6 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Thu, 26 Dec 2019 04:25:49 -0800 Subject: [PATCH 403/677] Bump teslajsonpy to 0.2.1 (#30217) Closes #29922 --- homeassistant/components/tesla/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tesla/manifest.json b/homeassistant/components/tesla/manifest.json index 4a869ab0a41..09a579373d6 100644 --- a/homeassistant/components/tesla/manifest.json +++ b/homeassistant/components/tesla/manifest.json @@ -3,7 +3,7 @@ "name": "Tesla", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/tesla", - "requirements": ["teslajsonpy==0.2.0"], + "requirements": ["teslajsonpy==0.2.1"], "dependencies": [], "codeowners": ["@zabuldon", "@alandtse"] } diff --git a/requirements_all.txt b/requirements_all.txt index f69a9b8c080..ca7748f2583 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1938,7 +1938,7 @@ temperusb==1.5.3 # tensorflow==1.13.2 # homeassistant.components.tesla -teslajsonpy==0.2.0 +teslajsonpy==0.2.1 # homeassistant.components.thermoworks_smoke thermoworks_smoke==0.1.8 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 422af61d9df..0f6586ed2bc 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -599,7 +599,7 @@ sunwatcher==0.2.1 tellduslive==0.10.10 # homeassistant.components.tesla -teslajsonpy==0.2.0 +teslajsonpy==0.2.1 # homeassistant.components.toon toonapilib==3.2.4 From b2753b75785a090e85f4d5fd9f575511834299cf Mon Sep 17 00:00:00 2001 From: Paul Annekov Date: Thu, 26 Dec 2019 14:27:59 +0200 Subject: [PATCH 404/677] bump tuyaha 0.0.5 (#30213) --- homeassistant/components/tuya/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/tuya/manifest.json b/homeassistant/components/tuya/manifest.json index cf16d587e87..6479a26694d 100644 --- a/homeassistant/components/tuya/manifest.json +++ b/homeassistant/components/tuya/manifest.json @@ -3,7 +3,7 @@ "name": "Tuya", "documentation": "https://www.home-assistant.io/integrations/tuya", "requirements": [ - "tuyaha==0.0.4" + "tuyaha==0.0.5" ], "dependencies": [], "codeowners": [] diff --git a/requirements_all.txt b/requirements_all.txt index ca7748f2583..780bf419366 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1965,7 +1965,7 @@ tp-connected==0.0.4 transmissionrpc==0.11 # homeassistant.components.tuya -tuyaha==0.0.4 +tuyaha==0.0.5 # homeassistant.components.twentemilieu twentemilieu==0.1.0 From d6744fbc4eb5b3a8d7dd8f4a3cc4348f576d6878 Mon Sep 17 00:00:00 2001 From: Josh Bendavid Date: Thu, 26 Dec 2019 13:06:57 -0500 Subject: [PATCH 405/677] Fix handling of symlinked device descriptors in keyboard_remote and move remaining sync io to executor thread pool (#30206) * fix handling of symlinked device decriptors * make check for symlinked paths more efficient * make variable names pylint compliant * move sync io during setup and device connect/disconnect to executor thread pool * move remaining sync io during setup to executor thread pool * remove unnecessary lambda functions --- .../components/keyboard_remote/__init__.py | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/keyboard_remote/__init__.py b/homeassistant/components/keyboard_remote/__init__.py index 24889a3f820..310bd0189bd 100644 --- a/homeassistant/components/keyboard_remote/__init__.py +++ b/homeassistant/components/keyboard_remote/__init__.py @@ -2,6 +2,7 @@ # pylint: disable=import-error import asyncio import logging +import os import aionotify from evdev import InputDevice, categorize, ecodes, list_devices @@ -119,9 +120,11 @@ class KeyboardRemote: # add initial devices (do this AFTER starting watcher in order to # avoid race conditions leading to missing device connections) initial_start_monitoring = set() - descriptors = list_devices(DEVINPUT) + descriptors = await self.hass.async_add_executor_job(list_devices, DEVINPUT) for descriptor in descriptors: - dev, handler = self.get_device_handler(descriptor) + dev, handler = await self.hass.async_add_executor_job( + self.get_device_handler, descriptor + ) if handler is None: continue @@ -165,6 +168,15 @@ class KeyboardRemote: handler = self.handlers_by_descriptor[descriptor] elif dev.name in self.handlers_by_name: handler = self.handlers_by_name[dev.name] + else: + # check for symlinked paths matching descriptor + for test_descriptor, test_handler in self.handlers_by_descriptor.items(): + if test_handler.dev is not None: + fullpath = test_handler.dev.path + else: + fullpath = os.path.realpath(test_descriptor) + if fullpath == descriptor: + handler = test_handler return (dev, handler) @@ -186,7 +198,9 @@ class KeyboardRemote: (event.flags & aionotify.Flags.CREATE) or (event.flags & aionotify.Flags.ATTRIB) ) and not descriptor_active: - dev, handler = self.get_device_handler(descriptor) + dev, handler = await self.hass.async_add_executor_job( + self.get_device_handler, descriptor + ) if handler is None: continue self.active_handlers_by_descriptor[descriptor] = handler @@ -242,7 +256,7 @@ class KeyboardRemote: """Stop event monitoring task and issue event.""" if self.monitor_task is not None: try: - self.dev.ungrab() + await self.hass.async_add_executor_job(self.dev.ungrab) except OSError: pass # monitoring of the device form the event loop and closing of the @@ -272,7 +286,7 @@ class KeyboardRemote: try: _LOGGER.debug("Start device monitoring") - dev.grab() + await self.hass.async_add_executor_job(dev.grab) async for event in dev.async_read_loop(): if event.type is ecodes.EV_KEY: if event.value in self.key_values: From 05a0922dc092b7ab0eacb41c992f1ad2258c24da Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Thu, 26 Dec 2019 17:08:14 -0500 Subject: [PATCH 406/677] Bump ZHA dependencies (#30228) * bump dependencies * requirement files * bump quirks to 0.0.30 --- homeassistant/components/zha/manifest.json | 8 ++++---- requirements_all.txt | 8 ++++---- requirements_test_all.txt | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json index 8781625d326..3beca6fd3c5 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -4,11 +4,11 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zha", "requirements": [ - "bellows-homeassistant==0.11.0", - "zha-quirks==0.0.28", + "bellows-homeassistant==0.12.0", + "zha-quirks==0.0.30", "zigpy-deconz==0.7.0", - "zigpy-homeassistant==0.11.0", - "zigpy-xbee-homeassistant==0.7.0", + "zigpy-homeassistant==0.12.0", + "zigpy-xbee-homeassistant==0.8.0", "zigpy-zigate==0.5.0" ], "dependencies": [], diff --git a/requirements_all.txt b/requirements_all.txt index 780bf419366..c99b8af492e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -293,7 +293,7 @@ beautifulsoup4==4.8.1 beewi_smartclim==0.0.7 # homeassistant.components.zha -bellows-homeassistant==0.11.0 +bellows-homeassistant==0.12.0 # homeassistant.components.bmw_connected_drive bimmer_connected==0.6.2 @@ -2094,7 +2094,7 @@ zengge==0.2 zeroconf==0.24.3 # homeassistant.components.zha -zha-quirks==0.0.28 +zha-quirks==0.0.30 # homeassistant.components.zhong_hong zhong_hong_hvac==1.0.9 @@ -2106,10 +2106,10 @@ ziggo-mediabox-xl==1.1.0 zigpy-deconz==0.7.0 # homeassistant.components.zha -zigpy-homeassistant==0.11.0 +zigpy-homeassistant==0.12.0 # homeassistant.components.zha -zigpy-xbee-homeassistant==0.7.0 +zigpy-xbee-homeassistant==0.8.0 # homeassistant.components.zha zigpy-zigate==0.5.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0f6586ed2bc..9c2139244e4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -109,7 +109,7 @@ av==6.1.2 axis==25 # homeassistant.components.zha -bellows-homeassistant==0.11.0 +bellows-homeassistant==0.12.0 # homeassistant.components.bom bomradarloop==0.1.3 @@ -659,16 +659,16 @@ yahooweather==0.10 zeroconf==0.24.3 # homeassistant.components.zha -zha-quirks==0.0.28 +zha-quirks==0.0.30 # homeassistant.components.zha zigpy-deconz==0.7.0 # homeassistant.components.zha -zigpy-homeassistant==0.11.0 +zigpy-homeassistant==0.12.0 # homeassistant.components.zha -zigpy-xbee-homeassistant==0.7.0 +zigpy-xbee-homeassistant==0.8.0 # homeassistant.components.zha zigpy-zigate==0.5.0 From 39d38923b7d61eb1e6d8860f511305c8785a0af2 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Fri, 27 Dec 2019 00:32:13 +0000 Subject: [PATCH 407/677] [ci skip] Translation update --- .../huawei_lte/.translations/fr.json | 1 + .../huawei_lte/.translations/zh-Hant.json | 1 + .../tesla/.translations/zh-Hant.json | 30 +++++++++++++++++++ 3 files changed, 32 insertions(+) create mode 100644 homeassistant/components/tesla/.translations/zh-Hant.json diff --git a/homeassistant/components/huawei_lte/.translations/fr.json b/homeassistant/components/huawei_lte/.translations/fr.json index 34db4e93bc4..1b2be78109d 100644 --- a/homeassistant/components/huawei_lte/.translations/fr.json +++ b/homeassistant/components/huawei_lte/.translations/fr.json @@ -33,6 +33,7 @@ "step": { "init": { "data": { + "name": "Nom du service de notification", "recipient": "Destinataires des notifications SMS", "track_new_devices": "Suivre les nouveaux appareils" } diff --git a/homeassistant/components/huawei_lte/.translations/zh-Hant.json b/homeassistant/components/huawei_lte/.translations/zh-Hant.json index 37f1111b77f..7371669e157 100644 --- a/homeassistant/components/huawei_lte/.translations/zh-Hant.json +++ b/homeassistant/components/huawei_lte/.translations/zh-Hant.json @@ -33,6 +33,7 @@ "step": { "init": { "data": { + "name": "\u901a\u77e5\u670d\u52d9\u540d\u7a31", "recipient": "\u7c21\u8a0a\u901a\u77e5\u6536\u4ef6\u8005", "track_new_devices": "\u8ffd\u8e64\u65b0\u8a2d\u5099" } diff --git a/homeassistant/components/tesla/.translations/zh-Hant.json b/homeassistant/components/tesla/.translations/zh-Hant.json new file mode 100644 index 00000000000..776a80da7fb --- /dev/null +++ b/homeassistant/components/tesla/.translations/zh-Hant.json @@ -0,0 +1,30 @@ +{ + "config": { + "error": { + "connection_error": "\u9023\u7dda\u932f\u8aa4\uff1b\u8acb\u6aa2\u5bdf\u7db2\u8def\u5f8c\u518d\u8a66\u4e00\u6b21", + "identifier_exists": "\u90f5\u4ef6\u5df2\u8a3b\u518a", + "invalid_credentials": "\u6191\u8b49\u7121\u6548", + "unknown_error": "\u672a\u77e5\u932f\u8aa4\uff0c\u8acb\u56de\u5831\u7d00\u9304" + }, + "step": { + "user": { + "data": { + "password": "\u5bc6\u78bc", + "username": "\u96fb\u5b50\u90f5\u4ef6\u5730\u5740" + }, + "description": "\u8acb\u8f38\u5165\u8cc7\u8a0a\u3002", + "title": "Tesla - \u8a2d\u5b9a" + } + }, + "title": "Tesla" + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\u6383\u63cf\u9593\u9694\u79d2\u6578" + } + } + } + } +} \ No newline at end of file From 9159da6583f1405f6218eb21f41d562f7f3bc106 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 27 Dec 2019 20:35:25 +0100 Subject: [PATCH 408/677] Bump shodan to 1.21.1 (#30234) --- homeassistant/components/shodan/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/shodan/manifest.json b/homeassistant/components/shodan/manifest.json index 0ff3e44ece5..007f6ef1d99 100644 --- a/homeassistant/components/shodan/manifest.json +++ b/homeassistant/components/shodan/manifest.json @@ -2,7 +2,7 @@ "domain": "shodan", "name": "Shodan", "documentation": "https://www.home-assistant.io/integrations/shodan", - "requirements": ["shodan==1.21.0"], + "requirements": ["shodan==1.21.1"], "dependencies": [], "codeowners": ["@fabaff"] } diff --git a/requirements_all.txt b/requirements_all.txt index c99b8af492e..a9638a1d767 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1800,7 +1800,7 @@ sense_energy==0.7.0 sharp_aquos_rc==0.3.2 # homeassistant.components.shodan -shodan==1.21.0 +shodan==1.21.1 # homeassistant.components.simplepush simplepush==1.1.4 From 9d6f3654adeaf05d9b0ee547528167e1685d9dae Mon Sep 17 00:00:00 2001 From: Kerwood Date: Fri, 27 Dec 2019 21:47:45 +0100 Subject: [PATCH 409/677] DECONZ - Added support for Aqara single switch WXKG03LM (#30240) --- homeassistant/components/deconz/device_trigger.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/homeassistant/components/deconz/device_trigger.py b/homeassistant/components/deconz/device_trigger.py index d057de23d02..9c8a41453aa 100644 --- a/homeassistant/components/deconz/device_trigger.py +++ b/homeassistant/components/deconz/device_trigger.py @@ -216,6 +216,13 @@ AQARA_DOUBLE_WALL_SWITCH_WXKG02LM = { (CONF_SHORT_PRESS, CONF_BOTH_BUTTONS): 3002, } +AQARA_SINGLE_WALL_SWITCH_WXKG03LM_MODEL = "lumi.remote.b186acn01" +AQARA_SINGLE_WALL_SWITCH_WXKG03LM = { + (CONF_SHORT_PRESS, CONF_TURN_ON): 1002, + (CONF_LONG_PRESS, CONF_TURN_ON): 1001, + (CONF_DOUBLE_PRESS, CONF_TURN_ON): 1004, +} + AQARA_MINI_SWITCH_MODEL = "lumi.remote.b1acn01" AQARA_MINI_SWITCH = { (CONF_SHORT_PRESS, CONF_TURN_ON): 1002, @@ -266,6 +273,7 @@ REMOTES = { AQARA_CUBE_MODEL_ALT1: AQARA_CUBE, AQARA_DOUBLE_WALL_SWITCH_MODEL: AQARA_DOUBLE_WALL_SWITCH, AQARA_DOUBLE_WALL_SWITCH_WXKG02LM_MODEL: AQARA_DOUBLE_WALL_SWITCH_WXKG02LM, + AQARA_SINGLE_WALL_SWITCH_WXKG03LM_MODEL: AQARA_SINGLE_WALL_SWITCH_WXKG03LM, AQARA_MINI_SWITCH_MODEL: AQARA_MINI_SWITCH, AQARA_ROUND_SWITCH_MODEL: AQARA_ROUND_SWITCH, AQARA_SQUARE_SWITCH_MODEL: AQARA_SQUARE_SWITCH, From 2e079029999ac8fb1b25376aafdf4838e4929c71 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Sat, 28 Dec 2019 00:32:22 +0000 Subject: [PATCH 410/677] [ci skip] Translation update --- .../huawei_lte/.translations/ca.json | 1 + .../huawei_lte/.translations/lb.json | 1 + .../components/tesla/.translations/ca.json | 30 +++++++++++++++++++ .../components/tesla/.translations/lb.json | 30 +++++++++++++++++++ 4 files changed, 62 insertions(+) create mode 100644 homeassistant/components/tesla/.translations/ca.json create mode 100644 homeassistant/components/tesla/.translations/lb.json diff --git a/homeassistant/components/huawei_lte/.translations/ca.json b/homeassistant/components/huawei_lte/.translations/ca.json index b213da018d2..594c2e3b16d 100644 --- a/homeassistant/components/huawei_lte/.translations/ca.json +++ b/homeassistant/components/huawei_lte/.translations/ca.json @@ -33,6 +33,7 @@ "step": { "init": { "data": { + "name": "Nom del servei de notificacions", "recipient": "Destinataris de notificacions SMS", "track_new_devices": "Segueix dispositius nous" } diff --git a/homeassistant/components/huawei_lte/.translations/lb.json b/homeassistant/components/huawei_lte/.translations/lb.json index 3c8f0464a55..56d383edba3 100644 --- a/homeassistant/components/huawei_lte/.translations/lb.json +++ b/homeassistant/components/huawei_lte/.translations/lb.json @@ -33,6 +33,7 @@ "step": { "init": { "data": { + "name": "Numm vum Notifikatioun's Service", "recipient": "Empf\u00e4nger vun SMS Notifikatioune", "track_new_devices": "Nei Apparater verfollegen" } diff --git a/homeassistant/components/tesla/.translations/ca.json b/homeassistant/components/tesla/.translations/ca.json new file mode 100644 index 00000000000..cb4840dea7a --- /dev/null +++ b/homeassistant/components/tesla/.translations/ca.json @@ -0,0 +1,30 @@ +{ + "config": { + "error": { + "connection_error": "Error de connexi\u00f3; comprova la xarxa i torna-ho a intentar", + "identifier_exists": "Correu electr\u00f2nic ja registrat", + "invalid_credentials": "Credencials inv\u00e0lides", + "unknown_error": "Error desconegut, si us plau, envia la informaci\u00f3 del registre" + }, + "step": { + "user": { + "data": { + "password": "Contrasenya", + "username": "Correu electr\u00f2nic" + }, + "description": "Introdueix la teva informaci\u00f3.", + "title": "Configuraci\u00f3 de Tesla" + } + }, + "title": "Tesla" + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Segons entre escanejos" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tesla/.translations/lb.json b/homeassistant/components/tesla/.translations/lb.json new file mode 100644 index 00000000000..fa63c5a289a --- /dev/null +++ b/homeassistant/components/tesla/.translations/lb.json @@ -0,0 +1,30 @@ +{ + "config": { + "error": { + "connection_error": "Feeler beim verbannen, Iwwerpr\u00e9ift Netzwierk a prob\u00e9iert nach emol", + "identifier_exists": "E-Mail ass scho registr\u00e9iert", + "invalid_credentials": "Ong\u00eblteg Login Informatioune", + "unknown_error": "Onbekannte Feeler, mellt w.e.g. Logbuch Info" + }, + "step": { + "user": { + "data": { + "password": "Passwuert", + "username": "E-Mail Adress" + }, + "description": "F\u00ebllt \u00e4r Informatiounen aus.", + "title": "Tesla - Konfiguratioun" + } + }, + "title": "Tesla" + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Sekonnen t\u00ebscht Scannen" + } + } + } + } +} \ No newline at end of file From cf7b70dd8c39317a700fdeb96826a503cedc2a01 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Sat, 28 Dec 2019 10:09:42 +0100 Subject: [PATCH 411/677] Bump python-qbittorrent to 0.4.1 (#30239) --- homeassistant/components/qbittorrent/manifest.json | 4 +--- homeassistant/components/qbittorrent/sensor.py | 2 +- requirements_all.txt | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/qbittorrent/manifest.json b/homeassistant/components/qbittorrent/manifest.json index c41d4ba46d3..2125e8cfe7b 100644 --- a/homeassistant/components/qbittorrent/manifest.json +++ b/homeassistant/components/qbittorrent/manifest.json @@ -2,9 +2,7 @@ "domain": "qbittorrent", "name": "Qbittorrent", "documentation": "https://www.home-assistant.io/integrations/qbittorrent", - "requirements": [ - "python-qbittorrent==0.3.1" - ], + "requirements": ["python-qbittorrent==0.4.1"], "dependencies": [], "codeowners": [] } diff --git a/homeassistant/components/qbittorrent/sensor.py b/homeassistant/components/qbittorrent/sensor.py index 0299277059b..9544d74b1cd 100644 --- a/homeassistant/components/qbittorrent/sensor.py +++ b/homeassistant/components/qbittorrent/sensor.py @@ -107,7 +107,7 @@ class QBittorrentSensor(Entity): def update(self): """Get the latest data from qBittorrent and updates the state.""" try: - data = self.client.sync() + data = self.client.sync_main_data() self._available = True except RequestException: _LOGGER.error("Connection lost") diff --git a/requirements_all.txt b/requirements_all.txt index a9638a1d767..9e123555337 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1601,7 +1601,7 @@ python-nmap==0.6.1 python-pushover==0.4 # homeassistant.components.qbittorrent -python-qbittorrent==0.3.1 +python-qbittorrent==0.4.1 # homeassistant.components.ripple python-ripple-api==0.0.3 From 59fee12b450bf01d5398848c2fe7595cb579c8d1 Mon Sep 17 00:00:00 2001 From: Marcelo Moreira de Mello Date: Sat, 28 Dec 2019 04:15:35 -0500 Subject: [PATCH 412/677] Bump ring to 0.2.8 to fix Oauth issues (#30245) --- homeassistant/components/ring/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/ring/manifest.json b/homeassistant/components/ring/manifest.json index 5c23822fef9..6fc57244deb 100644 --- a/homeassistant/components/ring/manifest.json +++ b/homeassistant/components/ring/manifest.json @@ -2,7 +2,7 @@ "domain": "ring", "name": "Ring", "documentation": "https://www.home-assistant.io/integrations/ring", - "requirements": ["ring_doorbell==0.2.5"], + "requirements": ["ring_doorbell==0.2.8"], "dependencies": ["ffmpeg"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index 9e123555337..fa2c24f9a8d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1743,7 +1743,7 @@ rfk101py==0.0.1 rflink==0.0.46 # homeassistant.components.ring -ring_doorbell==0.2.5 +ring_doorbell==0.2.8 # homeassistant.components.fleetgo ritassist==0.9.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9c2139244e4..f2625b96c9c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -553,7 +553,7 @@ restrictedpython==5.0 rflink==0.0.46 # homeassistant.components.ring -ring_doorbell==0.2.5 +ring_doorbell==0.2.8 # homeassistant.components.yamaha rxv==0.6.0 From e4cda9ae0bcbe68449ff7cb600fe2daf80315206 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sat, 28 Dec 2019 13:55:45 +0200 Subject: [PATCH 413/677] Fix Huawei LTE error message on service call without URL and routers (#30250) --- homeassistant/components/huawei_lte/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/huawei_lte/__init__.py b/homeassistant/components/huawei_lte/__init__.py index deb92de218c..97a57405ae0 100644 --- a/homeassistant/components/huawei_lte/__init__.py +++ b/homeassistant/components/huawei_lte/__init__.py @@ -439,6 +439,9 @@ async def async_setup(hass: HomeAssistantType, config) -> bool: routers = hass.data[DOMAIN].routers if url: router = routers.get(url) + elif not routers: + _LOGGER.error("%s: no routers configured", service.service) + return elif len(routers) == 1: router = next(iter(routers.values())) else: From 134dc45b77519b3266b8a73e8562357bbd57e24d Mon Sep 17 00:00:00 2001 From: SukramJ Date: Sat, 28 Dec 2019 13:46:04 +0100 Subject: [PATCH 414/677] Bump dependency for HomematicIp cloud (#30237) * Bump dependency for HomematicIp cloud * Update test_data --- .../homematicip_cloud/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/fixtures/homematicip_cloud.json | 201 ++++++++++++++++-- 4 files changed, 191 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/homematicip_cloud/manifest.json b/homeassistant/components/homematicip_cloud/manifest.json index 4feef19c8da..9f965bfdb6d 100644 --- a/homeassistant/components/homematicip_cloud/manifest.json +++ b/homeassistant/components/homematicip_cloud/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/homematicip_cloud", "requirements": [ - "homematicip==0.10.13" + "homematicip==0.10.14" ], "dependencies": [], "codeowners": [ diff --git a/requirements_all.txt b/requirements_all.txt index fa2c24f9a8d..ee2d1b8fc65 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -675,7 +675,7 @@ homeassistant-pyozw==0.1.7 homekit[IP]==0.15.0 # homeassistant.components.homematicip_cloud -homematicip==0.10.13 +homematicip==0.10.14 # homeassistant.components.horizon horimote==0.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f2625b96c9c..f8ffbead228 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -240,7 +240,7 @@ homeassistant-pyozw==0.1.7 homekit[IP]==0.15.0 # homeassistant.components.homematicip_cloud -homematicip==0.10.13 +homematicip==0.10.14 # homeassistant.components.google # homeassistant.components.remember_the_milk diff --git a/tests/fixtures/homematicip_cloud.json b/tests/fixtures/homematicip_cloud.json index 8cec2462f32..01667f353d3 100644 --- a/tests/fixtures/homematicip_cloud.json +++ b/tests/fixtures/homematicip_cloud.json @@ -14,6 +14,68 @@ } }, "devices": { + "3014F7110000ABCDABCD0033": { + "availableFirmwareVersion": "1.0.6", + "firmwareVersion": "1.0.6", + "firmwareVersionInteger": 65542, + "functionalChannels": { + "0": { + "badBatteryHealth": true, + "coProFaulty": false, + "coProRestartNeeded": false, + "coProUpdateFailure": false, + "configPending": false, + "deviceId": "3014F7110000ABCDABCD0033", + "deviceOverheated": false, + "deviceOverloaded": false, + "deviceUndervoltage": false, + "dutyCycle": false, + "functionalChannelType": "DEVICE_RECHARGEABLE_WITH_SABOTAGE", + "groupIndex": 0, + "groups": [], + "index": 0, + "label": "", + "lowBat": false, + "routerModuleEnabled": false, + "routerModuleSupported": false, + "rssiDeviceValue": -51, + "rssiPeerValue": null, + "sabotage": false, + "supportedOptionalFeatures": { + "IFeatureDeviceCoProError": false, + "IFeatureDeviceCoProRestart": false, + "IFeatureDeviceCoProUpdate": false, + "IFeatureDeviceOverheated": false, + "IFeatureDeviceOverloaded": false, + "IFeatureDeviceTemperatureOutOfRange": false, + "IFeatureDeviceUndervoltage": false + }, + "temperatureOutOfRange": false, + "unreach": false + }, + "1": { + "deviceId": "3014F7110000ABCDABCD0033", + "functionalChannelType": "ALARM_SIREN_CHANNEL", + "groupIndex": 1, + "groups": [], + "index": 1, + "label": "" + } + }, + "homeId": "00000000-0000-0000-0000-000000000001", + "id": "3014F7110000ABCDABCD0033", + "label": "Alarmsirene \u2013 au\u00dfen", + "lastStatusUpdate": 1573078567665, + "liveUpdateState": "LIVE_UPDATE_NOT_SUPPORTED", + "manufacturerCode": 1, + "modelId": 385, + "modelType": "HmIP-ASIR-O", + "oem": "eQ-3", + "permanentlyReachable": true, + "serializedGlobalTradeItemNumber": "3014F7110000ABCDABCD0033", + "type": "ALARM_SIREN_OUTDOOR", + "updateState": "UP_TO_DATE" + }, "3014F7110000000000000031": { "availableFirmwareVersion": "1.2.1", "firmwareVersion": "1.2.1", @@ -685,14 +747,14 @@ "firmwareVersionInteger": 65542, "functionalChannels": { "0": { - "coProFaulty": false, - "coProRestartNeeded": false, - "coProUpdateFailure": false, + "coProFaulty": true, + "coProRestartNeeded": true, + "coProUpdateFailure": true, "configPending": false, "deviceId": "3014F7110000000000000064", "deviceOverheated": true, - "deviceOverloaded": false, - "deviceUndervoltage": false, + "deviceOverloaded": true, + "deviceUndervoltage": true, "dutyCycle": false, "functionalChannelType": "DEVICE_SABOTAGE", "groupIndex": 0, @@ -709,15 +771,15 @@ "rssiPeerValue": null, "sabotage": false, "supportedOptionalFeatures": { - "IFeatureDeviceCoProError": false, - "IFeatureDeviceCoProRestart": false, - "IFeatureDeviceCoProUpdate": false, + "IFeatureDeviceCoProError": true, + "IFeatureDeviceCoProRestart": true, + "IFeatureDeviceCoProUpdate": true, "IFeatureDeviceOverheated": true, - "IFeatureDeviceOverloaded": false, - "IFeatureDeviceTemperatureOutOfRange": false, - "IFeatureDeviceUndervoltage": false + "IFeatureDeviceOverloaded": true, + "IFeatureDeviceTemperatureOutOfRange": true, + "IFeatureDeviceUndervoltage": true }, - "temperatureOutOfRange": false, + "temperatureOutOfRange": true, "unreach": false }, "1": { @@ -4031,6 +4093,74 @@ "serializedGlobalTradeItemNumber": "3014F711BBBBBBBBBBBBB18", "type": "OPEN_COLLECTOR_8_MODULE", "updateState": "UP_TO_DATE" + }, + "3014F0000000000000FAF9B4": { + "availableFirmwareVersion": "1.0.0", + "firmwareVersion": "1.0.0", + "firmwareVersionInteger": 65536, + "functionalChannels": { + "0": { + "coProFaulty": false, + "coProRestartNeeded": false, + "coProUpdateFailure": false, + "configPending": false, + "deviceId": "3014F0000000000000FAF9B4", + "deviceOverheated": false, + "deviceOverloaded": false, + "deviceUndervoltage": false, + "dutyCycle": false, + "functionalChannelType": "DEVICE_BASE", + "groupIndex": 0, + "groups": [ + "00000000-0000-0000-0000-000000000004" + ], + "index": 0, + "label": "", + "lowBat": null, + "routerModuleEnabled": false, + "routerModuleSupported": false, + "rssiDeviceValue": -52, + "rssiPeerValue": -54, + "supportedOptionalFeatures": { + "IFeatureDeviceCoProError": false, + "IFeatureDeviceCoProRestart": false, + "IFeatureDeviceCoProUpdate": false, + "IFeatureDeviceOverheated": false, + "IFeatureDeviceOverloaded": false, + "IFeatureDeviceTemperatureOutOfRange": false, + "IFeatureDeviceUndervoltage": false + }, + "temperatureOutOfRange": false, + "unreach": false + }, + "1": { + "deviceId": "3014F0000000000000FAF9B4", + "doorState": "CLOSED", + "functionalChannelType": "DOOR_CHANNEL", + "groupIndex": 1, + "groups": [ + "00000000-0000-0000-0000-000000000005" + ], + "index": 1, + "label": "", + "on": false, + "processing": false, + "ventilationPositionSupported": true + } + }, + "homeId": "00000000-0000-0000-0000-000000000001", + "id": "3014F0000000000000FAF9B4", + "label": "Garage Door Module", + "lastStatusUpdate": 1574878451970, + "liveUpdateState": "LIVE_UPDATE_NOT_SUPPORTED", + "manufacturerCode": 1, + "modelId": 376, + "modelType": "HmIP-MOD-TM", + "oem": "eQ-3", + "permanentlyReachable": true, + "serializedGlobalTradeItemNumber": "3014F0000000000000FAF9B4", + "type": "TORMATIC_MODULE", + "updateState": "UP_TO_DATE" } }, "groups": { @@ -4406,7 +4536,11 @@ "lowBat": null, "metaGroupId": "00000000-0000-0000-0000-000000000017", "on": true, - "processing": null, + "primaryShadingLevel": 1.0, + "primaryShadingStateType": "POSITION_USED", + "processing": false, + "secondaryShadingLevel": null, + "secondaryShadingStateType": "NOT_EXISTENT", "shutterLevel": null, "slatsLevel": null, "type": "SWITCHING", @@ -5394,6 +5528,47 @@ "type": "HUMIDITY_WARNING_RULE_GROUP", "unreach": false, "ventilationRecommended": false + }, + "00000000-0000-0000-0000-000000000050": { + "bottomShutterLevel": 1.0, + "bottomSlatsLevel": 1.0, + "channels": [], + "dutyCycle": false, + "groupVisibility": "VISIBLE", + "homeId": "00000000-0000-0000-0000-000000000001", + "id": "00000000-0000-0000-0000-000000000050", + "label": "Rollos", + "lastStatusUpdate": 1573078054795, + "lowBat": null, + "metaGroupId": null, + "primaryShadingLevel": 1.0, + "primaryShadingStateType": "POSITION_USED", + "processing": false, + "secondaryShadingLevel": null, + "secondaryShadingStateType": "NOT_EXISTENT", + "sensorSpecificParameters": {}, + "shutterLevel": 1.0, + "slatsLevel": null, + "topShutterLevel": 0.0, + "topSlatsLevel": 0.0, + "type": "EXTENDED_LINKED_SHUTTER", + "unreach": false + }, + "00000000-0000-0000-0000-000000000067": { + "channels": [], + "dutyCycle": null, + "homeId": "00000000-0000-0000-0000-000000000001", + "id": "00000000-0000-0000-0000-000000000067", + "label": "HOT_WATER", + "lastStatusUpdate": 0, + "lowBat": null, + "metaGroupId": null, + "on": null, + "onTime": 900.0, + "profileId": "00000000-0000-0000-0000-000000000068", + "profileMode": null, + "type": "HOT_WATER", + "unreach": null } }, "home": { From 0323b246bd70b3624ff6874e7516907f1a2f78dc Mon Sep 17 00:00:00 2001 From: Felix Barbalet Date: Sun, 29 Dec 2019 02:19:18 +1100 Subject: [PATCH 415/677] Bump libpurecool to 0.6.0 (#30247) * bump libpurecool to 0.6.0 - fixes home-assistant/home-assistant#26367 * update manifest too --- homeassistant/components/dyson/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/dyson/manifest.json b/homeassistant/components/dyson/manifest.json index 92940c8c1e1..9b561d78f95 100644 --- a/homeassistant/components/dyson/manifest.json +++ b/homeassistant/components/dyson/manifest.json @@ -3,7 +3,7 @@ "name": "Dyson", "documentation": "https://www.home-assistant.io/integrations/dyson", "requirements": [ - "libpurecool==0.5.0" + "libpurecool==0.6.0" ], "dependencies": [], "codeowners": [] diff --git a/requirements_all.txt b/requirements_all.txt index ee2d1b8fc65..5fb21cd0955 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -756,7 +756,7 @@ konnected==0.1.5 lakeside==0.12 # homeassistant.components.dyson -libpurecool==0.5.0 +libpurecool==0.6.0 # homeassistant.components.foscam libpyfoscam==1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f8ffbead228..590775abeb2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -265,7 +265,7 @@ keyring==20.0.0 keyrings.alt==3.4.0 # homeassistant.components.dyson -libpurecool==0.5.0 +libpurecool==0.6.0 # homeassistant.components.soundtouch libsoundtouch==0.7.2 From c5a280c064220ce5ef9135700dc0fcbd384ec04c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sat, 28 Dec 2019 17:27:49 +0200 Subject: [PATCH 416/677] Huawei LTE: Fix YAML options overriding ones set from GUI (#30249) Closes https://github.com/home-assistant/home-assistant/issues/30221 --- homeassistant/components/huawei_lte/config_flow.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/huawei_lte/config_flow.py b/homeassistant/components/huawei_lte/config_flow.py index 64803d4ad45..0dcdb6636c6 100644 --- a/homeassistant/components/huawei_lte/config_flow.py +++ b/homeassistant/components/huawei_lte/config_flow.py @@ -248,7 +248,9 @@ class OptionsFlowHandler(config_entries.OptionsFlow): async def async_step_init(self, user_input=None): """Handle options flow.""" if user_input is not None: - return self.async_create_entry(title="", data=user_input) + # Preserve existing options, for example *_from_yaml markers + data = {**self.config_entry.options, **user_input} + return self.async_create_entry(title="", data=data) data_schema = vol.Schema( { From 658ec309aa65241b564c3983f61b339151d56b45 Mon Sep 17 00:00:00 2001 From: SukramJ Date: Sat, 28 Dec 2019 16:29:14 +0100 Subject: [PATCH 417/677] Add HmIP-MOD_TM to HomematicIP Cloud (#30255) --- .../components/homematicip_cloud/cover.py | 41 ++++++++++++++++- .../homematicip_cloud/test_cover.py | 46 +++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/homematicip_cloud/cover.py b/homeassistant/components/homematicip_cloud/cover.py index ef8cbacfde2..e3efe9a9508 100644 --- a/homeassistant/components/homematicip_cloud/cover.py +++ b/homeassistant/components/homematicip_cloud/cover.py @@ -2,7 +2,12 @@ import logging from typing import Optional -from homematicip.aio.device import AsyncFullFlushBlind, AsyncFullFlushShutter +from homematicip.aio.device import ( + AsyncFullFlushBlind, + AsyncFullFlushShutter, + AsyncGarageDoorModuleTormatic, +) +from homematicip.base.enums import DoorCommand, DoorState from homeassistant.components.cover import ( ATTR_POSITION, @@ -40,6 +45,8 @@ async def async_setup_entry( entities.append(HomematicipCoverSlats(hap, device)) elif isinstance(device, AsyncFullFlushShutter): entities.append(HomematicipCoverShutter(hap, device)) + elif isinstance(device, AsyncGarageDoorModuleTormatic): + entities.append(HomematicipGarageDoorModuleTormatic(hap, device)) if entities: async_add_entities(entities) @@ -106,3 +113,35 @@ class HomematicipCoverSlats(HomematicipCoverShutter, CoverDevice): async def async_stop_cover_tilt(self, **kwargs) -> None: """Stop the device if in motion.""" await self._device.set_shutter_stop() + + +class HomematicipGarageDoorModuleTormatic(HomematicipGenericDevice, CoverDevice): + """Representation of a HomematicIP Garage Door Module for Tormatic.""" + + @property + def current_cover_position(self) -> int: + """Return current position of cover.""" + door_state_to_position = { + DoorState.CLOSED: 0, + DoorState.OPEN: 100, + DoorState.VENTILATION_POSITION: 10, + DoorState.POSITION_UNKNOWN: None, + } + return door_state_to_position.get(self._device.doorState) + + @property + def is_closed(self) -> Optional[bool]: + """Return if the cover is closed.""" + return self._device.doorState == DoorState.CLOSED + + async def async_open_cover(self, **kwargs) -> None: + """Open the cover.""" + await self._device.send_door_command(DoorCommand.OPEN) + + async def async_close_cover(self, **kwargs) -> None: + """Close the cover.""" + await self._device.send_door_command(DoorCommand.CLOSE) + + async def async_stop_cover(self, **kwargs) -> None: + """Stop the cover.""" + await self._device.send_door_command(DoorCommand.STOP) diff --git a/tests/components/homematicip_cloud/test_cover.py b/tests/components/homematicip_cloud/test_cover.py index 22922303f9e..728d60d5501 100644 --- a/tests/components/homematicip_cloud/test_cover.py +++ b/tests/components/homematicip_cloud/test_cover.py @@ -1,4 +1,6 @@ """Tests for HomematicIP Cloud cover.""" +from homematicip.base.enums import DoorCommand, DoorState + from homeassistant.components.cover import ( ATTR_CURRENT_POSITION, ATTR_CURRENT_TILT_POSITION, @@ -153,3 +155,47 @@ async def test_hmip_cover_slats(hass, default_mock_hap): await async_manipulate_test_data(hass, hmip_device, "shutterLevel", None) ha_state = hass.states.get(entity_id) assert ha_state.state == STATE_OPEN + + +async def test_hmip_garage_door_tormatic(hass, default_mock_hap): + """Test HomematicipCoverShutte.""" + entity_id = "cover.garage_door_module" + entity_name = "Garage Door Module" + device_model = "HmIP-MOD-TM" + + ha_state, hmip_device = get_and_check_entity_basics( + hass, default_mock_hap, entity_id, entity_name, device_model + ) + + assert ha_state.state == "closed" + assert ha_state.attributes["current_position"] == 0 + service_call_counter = len(hmip_device.mock_calls) + + await hass.services.async_call( + "cover", "open_cover", {"entity_id": entity_id}, blocking=True + ) + assert len(hmip_device.mock_calls) == service_call_counter + 1 + assert hmip_device.mock_calls[-1][0] == "send_door_command" + assert hmip_device.mock_calls[-1][1] == (DoorCommand.OPEN,) + await async_manipulate_test_data(hass, hmip_device, "doorState", DoorState.OPEN) + ha_state = hass.states.get(entity_id) + assert ha_state.state == STATE_OPEN + assert ha_state.attributes[ATTR_CURRENT_POSITION] == 100 + + await hass.services.async_call( + "cover", "close_cover", {"entity_id": entity_id}, blocking=True + ) + assert len(hmip_device.mock_calls) == service_call_counter + 3 + assert hmip_device.mock_calls[-1][0] == "send_door_command" + assert hmip_device.mock_calls[-1][1] == (DoorCommand.CLOSE,) + await async_manipulate_test_data(hass, hmip_device, "doorState", DoorState.CLOSED) + ha_state = hass.states.get(entity_id) + assert ha_state.state == STATE_CLOSED + assert ha_state.attributes[ATTR_CURRENT_POSITION] == 0 + + await hass.services.async_call( + "cover", "stop_cover", {"entity_id": entity_id}, blocking=True + ) + assert len(hmip_device.mock_calls) == service_call_counter + 5 + assert hmip_device.mock_calls[-1][0] == "send_door_command" + assert hmip_device.mock_calls[-1][1] == (DoorCommand.STOP,) From 4e50a402c736f1102436e612b679eb5a71ac8ebd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sat, 28 Dec 2019 17:31:42 +0200 Subject: [PATCH 418/677] Note Huawei LTE notify service change requires restart (#30223) Refs https://github.com/home-assistant/home-assistant/issues/30222 --- homeassistant/components/huawei_lte/.translations/en.json | 2 +- homeassistant/components/huawei_lte/strings.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/huawei_lte/.translations/en.json b/homeassistant/components/huawei_lte/.translations/en.json index 5cee60fb727..c5f2b4a2a02 100644 --- a/homeassistant/components/huawei_lte/.translations/en.json +++ b/homeassistant/components/huawei_lte/.translations/en.json @@ -33,7 +33,7 @@ "step": { "init": { "data": { - "name": "Notification service name", + "name": "Notification service name (change requires restart)", "recipient": "SMS notification recipients", "track_new_devices": "Track new devices" } diff --git a/homeassistant/components/huawei_lte/strings.json b/homeassistant/components/huawei_lte/strings.json index 0d586e0d0ad..c5f2b4a2a02 100644 --- a/homeassistant/components/huawei_lte/strings.json +++ b/homeassistant/components/huawei_lte/strings.json @@ -7,13 +7,13 @@ }, "error": { "connection_failed": "Connection failed", + "connection_timeout": "Connection timeout", "incorrect_password": "Incorrect password", "incorrect_username": "Incorrect username", "incorrect_username_or_password": "Incorrect username or password", "invalid_url": "Invalid URL", "login_attempts_exceeded": "Maximum login attempts exceeded, please try again later", "response_error": "Unknown error from device", - "connection_timeout": "Connection timeout", "unknown_connection_error": "Unknown error connecting to device" }, "step": { @@ -33,7 +33,7 @@ "step": { "init": { "data": { - "name": "Notification service name", + "name": "Notification service name (change requires restart)", "recipient": "SMS notification recipients", "track_new_devices": "Track new devices" } From e1e8d6a562ed152a08ecb926b111a20d0be513df Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Sat, 28 Dec 2019 21:16:52 +0100 Subject: [PATCH 419/677] Bump python-velbus library to 2.0.32 to fix problems with the glaspanels (#30257) --- homeassistant/components/velbus/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/velbus/manifest.json b/homeassistant/components/velbus/manifest.json index 71a2ac2b993..007ca421276 100644 --- a/homeassistant/components/velbus/manifest.json +++ b/homeassistant/components/velbus/manifest.json @@ -3,7 +3,7 @@ "name": "Velbus", "documentation": "https://www.home-assistant.io/integrations/velbus", "requirements": [ - "python-velbus==2.0.30" + "python-velbus==2.0.32" ], "config_flow": true, "dependencies": [], diff --git a/requirements_all.txt b/requirements_all.txt index 5fb21cd0955..ca438455bba 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1628,7 +1628,7 @@ python-telnet-vlc==1.0.4 python-twitch-client==0.6.0 # homeassistant.components.velbus -python-velbus==2.0.30 +python-velbus==2.0.32 # homeassistant.components.vlc python-vlc==1.1.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 590775abeb2..5ee5e017864 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -523,7 +523,7 @@ python-miio==0.4.8 python-nest==4.1.0 # homeassistant.components.velbus -python-velbus==2.0.30 +python-velbus==2.0.32 # homeassistant.components.awair python_awair==0.0.4 From 5a9e543087da3cab2afad754e1e603cbb448851e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 28 Dec 2019 21:20:18 +0100 Subject: [PATCH 420/677] Whitelist Android/iOS auth callbacks (#30082) * Whitelist Android/iOS * Add iOS alternate flavor URLs * Update indieauth.py Co-authored-by: Robbie Trencheny --- homeassistant/components/auth/indieauth.py | 8 ++++++++ tests/components/auth/test_indieauth.py | 21 +++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/homeassistant/components/auth/indieauth.py b/homeassistant/components/auth/indieauth.py index c845f230bf3..5915a4ec301 100644 --- a/homeassistant/components/auth/indieauth.py +++ b/homeassistant/components/auth/indieauth.py @@ -30,6 +30,14 @@ async def verify_redirect_uri(hass, client_id, redirect_uri): if is_valid: return True + # Whitelist the iOS and Android callbacks so that people can link apps + # without being connected to the internet. + if redirect_uri == "homeassistant://auth-callback" and client_id in ( + "https://home-assistant.io/android", + "https://home-assistant.io/iOS", + ): + return True + # IndieAuth 4.2.2 allows for redirect_uri to be on different domain # but needs to be specified in link tag when fetching `client_id`. redirect_uris = await fetch_redirect_uris(hass, client_id) diff --git a/tests/components/auth/test_indieauth.py b/tests/components/auth/test_indieauth.py index 8cfb573939e..ce8edae1466 100644 --- a/tests/components/auth/test_indieauth.py +++ b/tests/components/auth/test_indieauth.py @@ -166,3 +166,24 @@ async def test_find_link_tag_max_size(hass, mock_session): redirect_uris = await indieauth.fetch_redirect_uris(hass, "http://127.0.0.1:8000") assert redirect_uris == ["http://127.0.0.1:8000/wine"] + + +@pytest.mark.parametrize( + "client_id", ["https://home-assistant.io/android", "https://home-assistant.io/iOS"] +) +async def test_verify_redirect_uri_android_ios(client_id): + """Test that we verify redirect uri correctly for Android/iOS.""" + with patch.object( + indieauth, "fetch_redirect_uris", side_effect=lambda *_: mock_coro([]) + ): + assert await indieauth.verify_redirect_uri( + None, client_id, "homeassistant://auth-callback" + ) + + assert not await indieauth.verify_redirect_uri( + None, client_id, "homeassistant://something-else" + ) + + assert not await indieauth.verify_redirect_uri( + None, "https://incorrect.com", "homeassistant://auth-callback" + ) From 08af98965866ac92abfefff63333c9ccef560cc0 Mon Sep 17 00:00:00 2001 From: Robert Van Gorkom Date: Sat, 28 Dec 2019 12:25:37 -0800 Subject: [PATCH 421/677] Fixing timezone issue which caused wrong selection of data to be used. (#30011) --- homeassistant/components/withings/common.py | 4 +-- tests/components/withings/test_common.py | 35 +++++++++++++++++++-- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/withings/common.py b/homeassistant/components/withings/common.py index 4c3772d77e7..9cba055bac4 100644 --- a/homeassistant/components/withings/common.py +++ b/homeassistant/components/withings/common.py @@ -259,8 +259,8 @@ class WithingsDataManager: async def update_sleep(self) -> SleepGetResponse: """Update the sleep data.""" - end_date = int(time.time()) - start_date = end_date - (6 * 60 * 60) + end_date = dt.now() + start_date = end_date - datetime.timedelta(hours=2) def function(): return self._api.sleep_get(startdate=start_date, enddate=end_date) diff --git a/tests/components/withings/test_common.py b/tests/components/withings/test_common.py index 9328526d6ef..37fcb4ce7f5 100644 --- a/tests/components/withings/test_common.py +++ b/tests/components/withings/test_common.py @@ -1,4 +1,6 @@ """Tests for the Withings component.""" +from datetime import timedelta + from asynctest import MagicMock import pytest from withings_api import WithingsApi @@ -8,15 +10,20 @@ from homeassistant.components.withings.common import ( NotAuthenticatedError, WithingsDataManager, ) +from homeassistant.config import async_process_ha_core_config +from homeassistant.core import HomeAssistant from homeassistant.exceptions import PlatformNotReady +from homeassistant.util import dt @pytest.fixture(name="withings_api") def withings_api_fixture() -> WithingsApi: """Provide withings api.""" withings_api = WithingsApi.__new__(WithingsApi) - withings_api.get_measures = MagicMock() - withings_api.get_sleep = MagicMock() + withings_api.user_get_device = MagicMock() + withings_api.measure_get_meas = MagicMock() + withings_api.sleep_get = MagicMock() + withings_api.sleep_get_summary = MagicMock() return withings_api @@ -101,3 +108,27 @@ async def test_data_manager_call_throttle_disabled( assert result == "HELLO2" assert hello_func.call_count == 2 + + +async def test_data_manager_update_sleep_date_range( + hass: HomeAssistant, data_manager: WithingsDataManager, +) -> None: + """Test method.""" + await async_process_ha_core_config( + hass=hass, config={"time_zone": "America/Los_Angeles"} + ) + + update_start_time = dt.now() + await data_manager.update_sleep() + + call_args = data_manager.api.sleep_get.call_args_list[0][1] + startdate = call_args.get("startdate") + enddate = call_args.get("enddate") + + assert startdate.tzname() == "PST" + + assert enddate.tzname() == "PST" + assert startdate.tzname() == "PST" + assert update_start_time < enddate + assert enddate < update_start_time + timedelta(seconds=1) + assert enddate > startdate From 36585558a5b37b21924c749ae1c8470de7c1c04c Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Sat, 28 Dec 2019 15:41:55 -0500 Subject: [PATCH 422/677] Refactor ZHA channel logging (#30259) Add channel.id property -- id unique for this the device only. --- .../components/zha/core/channels/__init__.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/zha/core/channels/__init__.py b/homeassistant/components/zha/core/channels/__init__.py index 4013f05e0b6..5a337b2a537 100644 --- a/homeassistant/components/zha/core/channels/__init__.py +++ b/homeassistant/components/zha/core/channels/__init__.py @@ -89,19 +89,19 @@ class ZigbeeChannel(LogMixin): self._generic_id = f"channel_0x{cluster.cluster_id:04x}" self._cluster = cluster self._zha_device = device - self._unique_id = "{}:{}:0x{:04x}".format( - str(device.ieee), cluster.endpoint.endpoint_id, cluster.cluster_id - ) - # this keeps logs consistent with zigpy logging - self._log_id = "0x{:04x}:{}:0x{:04x}".format( - device.nwk, cluster.endpoint.endpoint_id, cluster.cluster_id - ) + self._id = f"{cluster.endpoint.endpoint_id}:0x{cluster.cluster_id:04x}" + self._unique_id = f"{str(device.ieee)}:{self._id}" self._report_config = CLUSTER_REPORT_CONFIGS.get( self._cluster.cluster_id, self.REPORT_CONFIG ) self._status = ChannelStatus.CREATED self._cluster.add_listener(self) + @property + def id(self) -> str: + """Return channel id unique for this device only.""" + return self._id + @property def generic_id(self): """Return the generic id for this channel.""" @@ -263,8 +263,8 @@ class ZigbeeChannel(LogMixin): def log(self, level, msg, *args): """Log a message.""" - msg = "[%s]: " + msg - args = (self._log_id,) + args + msg = "[%s:%s]: " + msg + args = (self.device.nwk, self._id,) + args _LOGGER.log(level, msg, *args) def __getattr__(self, name): From 5d8dda4f687dcf4a072f50c423debe0fb01a910c Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Sun, 29 Dec 2019 00:32:14 +0000 Subject: [PATCH 423/677] [ci skip] Translation update --- .../alarm_control_panel/.translations/da.json | 7 ++++ .../components/almond/.translations/da.json | 15 +++++++ .../components/auth/.translations/da.json | 2 +- .../components/climate/.translations/da.json | 17 ++++++++ .../components/cover/.translations/da.json | 8 ++++ .../components/deconz/.translations/da.json | 20 ++++++++- .../components/demo/.translations/da.json | 5 +++ .../dialogflow/.translations/da.json | 2 +- .../components/elgato/.translations/da.json | 27 ++++++++++++ .../components/fan/.translations/da.json | 16 +++++++ .../components/geofency/.translations/da.json | 2 +- .../geonetnz_volcano/.translations/da.json | 16 +++++++ .../gpslogger/.translations/da.json | 2 +- .../hisense_aehw4a1/.translations/da.json | 15 +++++++ .../huawei_lte/.translations/da.json | 20 +++++++++ .../components/icloud/.translations/da.json | 38 +++++++++++++++++ .../components/ifttt/.translations/da.json | 2 +- .../components/lock/.translations/da.json | 4 ++ .../lutron_caseta/.translations/da.json | 5 +++ .../components/mailgun/.translations/da.json | 2 +- .../components/plaato/.translations/da.json | 2 +- .../components/plex/.translations/da.json | 1 + .../components/soma/.translations/da.json | 4 +- .../components/somfy/.translations/da.json | 5 +++ .../components/starline/.translations/da.json | 42 +++++++++++++++++++ .../components/tesla/.translations/da.json | 30 +++++++++++++ .../components/traccar/.translations/da.json | 2 +- .../components/twilio/.translations/da.json | 2 +- .../components/vacuum/.translations/da.json | 16 +++++++ .../components/wled/.translations/da.json | 26 ++++++++++++ 30 files changed, 343 insertions(+), 12 deletions(-) create mode 100644 homeassistant/components/almond/.translations/da.json create mode 100644 homeassistant/components/climate/.translations/da.json create mode 100644 homeassistant/components/demo/.translations/da.json create mode 100644 homeassistant/components/elgato/.translations/da.json create mode 100644 homeassistant/components/fan/.translations/da.json create mode 100644 homeassistant/components/geonetnz_volcano/.translations/da.json create mode 100644 homeassistant/components/hisense_aehw4a1/.translations/da.json create mode 100644 homeassistant/components/huawei_lte/.translations/da.json create mode 100644 homeassistant/components/icloud/.translations/da.json create mode 100644 homeassistant/components/lutron_caseta/.translations/da.json create mode 100644 homeassistant/components/starline/.translations/da.json create mode 100644 homeassistant/components/tesla/.translations/da.json create mode 100644 homeassistant/components/vacuum/.translations/da.json create mode 100644 homeassistant/components/wled/.translations/da.json diff --git a/homeassistant/components/alarm_control_panel/.translations/da.json b/homeassistant/components/alarm_control_panel/.translations/da.json index 74e02e10de4..304fa342bf2 100644 --- a/homeassistant/components/alarm_control_panel/.translations/da.json +++ b/homeassistant/components/alarm_control_panel/.translations/da.json @@ -2,6 +2,13 @@ "device_automation": { "action_type": { "trigger": "Udl\u00f8s {entity_name}" + }, + "trigger_type": { + "armed_away": "{entity_name} tilkoblet ude", + "armed_home": "{entity_name} tilkoblet hjemme", + "armed_night": "{entity_name} tilkoblet nat", + "disarmed": "{entity_name} frakoblet", + "triggered": "{entity_name} udl\u00f8st" } } } \ No newline at end of file diff --git a/homeassistant/components/almond/.translations/da.json b/homeassistant/components/almond/.translations/da.json new file mode 100644 index 00000000000..93158cee94f --- /dev/null +++ b/homeassistant/components/almond/.translations/da.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_setup": "Du kan kun konfigurere en Almond-konto.", + "cannot_connect": "Kan ikke oprette forbindelse til Almond-serveren.", + "missing_configuration": "Tjek venligst dokumentationen om, hvordan man indstiller Almond." + }, + "step": { + "pick_implementation": { + "title": "V\u00e6lg godkendelsesmetode" + } + }, + "title": "Almond" + } +} \ No newline at end of file diff --git a/homeassistant/components/auth/.translations/da.json b/homeassistant/components/auth/.translations/da.json index f461f376d16..8bf710a1932 100644 --- a/homeassistant/components/auth/.translations/da.json +++ b/homeassistant/components/auth/.translations/da.json @@ -2,7 +2,7 @@ "mfa_setup": { "notify": { "abort": { - "no_available_service": "Ingen underretningstjenester til r\u00e5dighed." + "no_available_service": "Ingen meddelelsestjenester tilg\u00e6ngelige." }, "error": { "invalid_code": "Ugyldig kode, pr\u00f8v venligst igen." diff --git a/homeassistant/components/climate/.translations/da.json b/homeassistant/components/climate/.translations/da.json new file mode 100644 index 00000000000..78731dd1577 --- /dev/null +++ b/homeassistant/components/climate/.translations/da.json @@ -0,0 +1,17 @@ +{ + "device_automation": { + "action_type": { + "set_hvac_mode": "Skift af klimaanl\u00e6gstilstand p\u00e5 {entity_name}", + "set_preset_mode": "Skift af forudindstilling p\u00e5 {entity_name}" + }, + "condition_type": { + "is_hvac_mode": "{entity_name} er indstillet til en bestemt klimaanl\u00e6gstilstand", + "is_preset_mode": "{entity_name} er indstillet til en bestemt forudindstillet tilstand" + }, + "trigger_type": { + "current_humidity_changed": "{entity_name} m\u00e5lte luftfugtighed \u00e6ndret", + "current_temperature_changed": "{entity_name} m\u00e5lte temperatur \u00e6ndret", + "hvac_mode_changed": "{entity_name} klimaanl\u00e6gstilstand \u00e6ndret" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cover/.translations/da.json b/homeassistant/components/cover/.translations/da.json index e603723b564..a645a3cc613 100644 --- a/homeassistant/components/cover/.translations/da.json +++ b/homeassistant/components/cover/.translations/da.json @@ -5,6 +5,14 @@ "is_closing": "{entity_name} lukker", "is_open": "{entity_name} er \u00e5ben", "is_opening": "{entity_name} \u00e5bnes" + }, + "trigger_type": { + "closed": "{entity_name} lukket", + "closing": "{entity_name} lukning", + "opened": "{entity_name} \u00e5bnet", + "opening": "{entity_name} \u00e5bning", + "position": "{entity_name} position \u00e6ndres", + "tilt_position": "{entity_name} vippeposition \u00e6ndres" } } } \ No newline at end of file diff --git a/homeassistant/components/deconz/.translations/da.json b/homeassistant/components/deconz/.translations/da.json index ec9c4dc35b1..80dc9ae6d3b 100644 --- a/homeassistant/components/deconz/.translations/da.json +++ b/homeassistant/components/deconz/.translations/da.json @@ -54,10 +54,26 @@ "dim_up": "D\u00e6mp op", "left": "Venstre", "open": "\u00c5ben", - "right": "H\u00f8jre" + "right": "H\u00f8jre", + "side_1": "Side 1", + "side_2": "Side 2", + "side_3": "Side 3", + "side_4": "Side 4", + "side_5": "Side 5", + "side_6": "Side 6" }, "trigger_type": { - "remote_gyro_activated": "Enhed rystet" + "remote_awakened": "Enheden v\u00e6kket", + "remote_double_tap": "Enheden \"{subtype}\" dobbelttappet", + "remote_falling": "Enheden er i frit fald", + "remote_gyro_activated": "Enhed rystet", + "remote_moved": "Enheden flyttede med \"{subtype}\" op", + "remote_rotate_from_side_1": "Enhed roteret fra \"side 1\" til \"{subtype}\"", + "remote_rotate_from_side_2": "Enhed roteret fra \"side 2\" til \"{subtype}\"", + "remote_rotate_from_side_3": "Enhed roteret fra \"side 3\" til \"{subtype}\"", + "remote_rotate_from_side_4": "Enhed roteret fra \"side 4\" til \"{subtype}\"", + "remote_rotate_from_side_5": "Enhed roteret fra \"side 5\" til \"{subtype}\"", + "remote_rotate_from_side_6": "Enhed roteret fra \"side 6\" til \"{subtype}\"" } }, "options": { diff --git a/homeassistant/components/demo/.translations/da.json b/homeassistant/components/demo/.translations/da.json new file mode 100644 index 00000000000..ef01fcb4f3c --- /dev/null +++ b/homeassistant/components/demo/.translations/da.json @@ -0,0 +1,5 @@ +{ + "config": { + "title": "Demo" + } +} \ No newline at end of file diff --git a/homeassistant/components/dialogflow/.translations/da.json b/homeassistant/components/dialogflow/.translations/da.json index 2fb203450a5..22f123a38a1 100644 --- a/homeassistant/components/dialogflow/.translations/da.json +++ b/homeassistant/components/dialogflow/.translations/da.json @@ -5,7 +5,7 @@ "one_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning" }, "create_entry": { - "default": "For at sende begivenheder til Home Assistant skal du konfigurere [Webhook integration med Dialogflow]({dialogflow_url}).\n\n Udfyld f\u00f8lgende oplysninger: \n\n - URL: `{webhook_url}`\n - Metode: POST\n - Indholdstype: application/json\n\n Se [dokumentationen]({docs_url}) for yderligere oplysninger." + "default": "For at sende h\u00e6ndelser til Home Assistant skal du konfigurere [webhook-integration med Dialogflow]({dialogflow_url}).\n\n Udfyld f\u00f8lgende oplysninger: \n\n - Webadresse: `{webhook_url}`\n - Metode: POST\n - Indholdstype: application/json\n\nSe [dokumentationen]({docs_url}) for yderligere oplysninger." }, "step": { "user": { diff --git a/homeassistant/components/elgato/.translations/da.json b/homeassistant/components/elgato/.translations/da.json new file mode 100644 index 00000000000..a10e4d9e89f --- /dev/null +++ b/homeassistant/components/elgato/.translations/da.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Denne Elgato Key Light-enhed er allerede konfigureret.", + "connection_error": "Kunne ikke oprette forbindelse til Elgato Key Light-enheden." + }, + "error": { + "connection_error": "Kunne ikke oprette forbindelse til Elgato Key Light-enheden." + }, + "flow_title": "Elgato Key Light: {serial_number}", + "step": { + "user": { + "data": { + "host": "V\u00e6rt eller IP-adresse", + "port": "Portnummer" + }, + "description": "Indstil din Elgato Key Light til at integrere med Home Assistant.", + "title": "Forbind din Elgato Key Light" + }, + "zeroconf_confirm": { + "description": "Vil du tilf\u00f8je Elgato Key Light med serienummer `{serial_number}` til Home Assistant?", + "title": "Fandt Elgato Key Light-enhed" + } + }, + "title": "Elgato Key Light" + } +} \ No newline at end of file diff --git a/homeassistant/components/fan/.translations/da.json b/homeassistant/components/fan/.translations/da.json new file mode 100644 index 00000000000..0c9556bfedb --- /dev/null +++ b/homeassistant/components/fan/.translations/da.json @@ -0,0 +1,16 @@ +{ + "device_automation": { + "action_type": { + "turn_off": "Sluk {entity_name}", + "turn_on": "T\u00e6nd for {entity_name}" + }, + "condition_type": { + "is_off": "{entity_name} er slukket", + "is_on": "{entity_name} er t\u00e6ndt" + }, + "trigger_type": { + "turned_off": "{entity_name} blev slukket", + "turned_on": "{entity_name} blev t\u00e6ndt" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geofency/.translations/da.json b/homeassistant/components/geofency/.translations/da.json index 1390dfb504a..21ff2e9fced 100644 --- a/homeassistant/components/geofency/.translations/da.json +++ b/homeassistant/components/geofency/.translations/da.json @@ -5,7 +5,7 @@ "one_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning" }, "create_entry": { - "default": "For at sende begivenheder til Home Assistant skal du konfigurere webhook funktionen i Geofency.\n\n Udfyld f\u00f8lgende oplysninger: \n\n - URL: `{webhook_url}`\n - Metode: POST\n \n Se [dokumentationen]({docs_url}) for yderligere oplysninger." + "default": "For at sende h\u00e6ndelser til Home Assistant skal du konfigurere webhook-funktionen i Geofency.\n\n Udfyld f\u00f8lgende oplysninger: \n\n - Webadresse: `{webhook_url}`\n - Metode: POST\n \nSe [dokumentationen]({docs_url}) for yderligere oplysninger." }, "step": { "user": { diff --git a/homeassistant/components/geonetnz_volcano/.translations/da.json b/homeassistant/components/geonetnz_volcano/.translations/da.json new file mode 100644 index 00000000000..a8c238a60b0 --- /dev/null +++ b/homeassistant/components/geonetnz_volcano/.translations/da.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "identifier_exists": "Lokalitet allerede registreret" + }, + "step": { + "user": { + "data": { + "radius": "Radius" + }, + "title": "Udfyld dine filteroplysninger." + } + }, + "title": "GeoNet NZ vulkan" + } +} \ No newline at end of file diff --git a/homeassistant/components/gpslogger/.translations/da.json b/homeassistant/components/gpslogger/.translations/da.json index 6d5c2185718..4aaebb7aa82 100644 --- a/homeassistant/components/gpslogger/.translations/da.json +++ b/homeassistant/components/gpslogger/.translations/da.json @@ -5,7 +5,7 @@ "one_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning" }, "create_entry": { - "default": "For at sende begivenheder til Home Assistant skal du konfigurere webhook funktionen i GPSLogger.\n\n Udfyld f\u00f8lgende oplysninger: \n\n - URL: `{webhook_url}`\n - Metode: POST\n \n Se [dokumentationen]({docs_url}) for yderligere oplysninger." + "default": "For at sende h\u00e6ndelser til Home Assistant skal du konfigurere webhook-funktionen i GPSLogger.\n\n Udfyld f\u00f8lgende oplysninger: \n\n - Webadresse: `{webhook_url}`\n - Metode: POST\n \nSe [dokumentationen]({docs_url}) for yderligere oplysninger." }, "step": { "user": { diff --git a/homeassistant/components/hisense_aehw4a1/.translations/da.json b/homeassistant/components/hisense_aehw4a1/.translations/da.json new file mode 100644 index 00000000000..3d479543231 --- /dev/null +++ b/homeassistant/components/hisense_aehw4a1/.translations/da.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "no_devices_found": "Ingen Hisense AEH-W4A1-enheder fundet p\u00e5 netv\u00e6rket.", + "single_instance_allowed": "Kun en enkelt konfiguration af Hisense AEH-W4A1 er mulig." + }, + "step": { + "confirm": { + "description": "Vil du konfigurere Hisense AEH-W4A1?", + "title": "Hisense AEH-W4A1" + } + }, + "title": "Hisense AEH-W4A1" + } +} \ No newline at end of file diff --git a/homeassistant/components/huawei_lte/.translations/da.json b/homeassistant/components/huawei_lte/.translations/da.json new file mode 100644 index 00000000000..b693d414ceb --- /dev/null +++ b/homeassistant/components/huawei_lte/.translations/da.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_in_progress": "Denne enhed er allerede ved at blive konfigureret", + "not_huawei_lte": "Ikke en Huawei LTE-enhed" + }, + "error": { + "connection_timeout": "Timeout for forbindelse" + } + }, + "options": { + "step": { + "init": { + "data": { + "name": "Navn p\u00e5 meddelelsestjeneste (\u00e6ndring kr\u00e6ver genstart)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/icloud/.translations/da.json b/homeassistant/components/icloud/.translations/da.json new file mode 100644 index 00000000000..1a06bd8e0f2 --- /dev/null +++ b/homeassistant/components/icloud/.translations/da.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "username_exists": "Kontoen er allerede konfigureret" + }, + "error": { + "login": "Loginfejl: Kontroller din email og adgangskode", + "send_verification_code": "Bekr\u00e6ftelseskoden kunne ikke sendes", + "username_exists": "Kontoen er allerede konfigureret", + "validate_verification_code": "Bekr\u00e6ftelseskoden kunne ikke bekr\u00e6ftes, V\u00e6lg en betroet enhed, og start bekr\u00e6ftelsen igen" + }, + "step": { + "trusted_device": { + "data": { + "trusted_device": "Betroet enhed" + }, + "description": "V\u00e6lg din betroede enhed", + "title": "iCloud-enhed, der er tillid til" + }, + "user": { + "data": { + "password": "Adgangskode", + "username": "Email" + }, + "description": "Indtast dine legitimationsoplysninger", + "title": "iCloud-legitimationsoplysninger" + }, + "verification_code": { + "data": { + "verification_code": "Bekr\u00e6ftelseskode" + }, + "description": "Indtast venligst den bekr\u00e6ftelseskode, du lige har modtaget fra iCloud", + "title": "iCloud-bekr\u00e6ftelseskode" + } + }, + "title": "Apple iCloud" + } +} \ No newline at end of file diff --git a/homeassistant/components/ifttt/.translations/da.json b/homeassistant/components/ifttt/.translations/da.json index 25c502ed05e..7e05d938999 100644 --- a/homeassistant/components/ifttt/.translations/da.json +++ b/homeassistant/components/ifttt/.translations/da.json @@ -5,7 +5,7 @@ "one_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning" }, "create_entry": { - "default": "For at sende begivenheder til Home Assistant skal du bruge handlingen \"Foretag en web foresp\u00f8rgsel\" fra [IFTTT Webhook applet] ({applet_url}).\n\n Udfyld f\u00f8lgende oplysninger: \n\n - URL: `{webhook_url}`\n - Metode: POST\n - Indholdstype: application/json\n\n Se [dokumentationen] ({docs_url}) om hvordan du konfigurerer automatiseringer til at h\u00e5ndtere indg\u00e5ende data." + "default": "For at sende h\u00e6ndelser til Home Assistant skal du bruge handlingen \"Foretag en web-foresp\u00f8rgsel\" fra [IFTTT Webhook-applet] ({applet_url}).\n\n Udfyld f\u00f8lgende oplysninger: \n\n - Webadresse: `{webhook_url}`\n - Metode: POST\n - Indholdstype: application/json\n\nSe [dokumentationen] ({docs_url}) om hvordan du konfigurerer automatiseringer til at h\u00e5ndtere indg\u00e5ende data." }, "step": { "user": { diff --git a/homeassistant/components/lock/.translations/da.json b/homeassistant/components/lock/.translations/da.json index de4f603ac43..517c86444fd 100644 --- a/homeassistant/components/lock/.translations/da.json +++ b/homeassistant/components/lock/.translations/da.json @@ -7,6 +7,10 @@ "condition_type": { "is_locked": "{entity_name} er l\u00e5st", "is_unlocked": "{entity_name} er l\u00e5st op" + }, + "trigger_type": { + "locked": "{entity_name} blev l\u00e5st", + "unlocked": "{entity_name} l\u00e5st op" } } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/.translations/da.json b/homeassistant/components/lutron_caseta/.translations/da.json new file mode 100644 index 00000000000..cfc3c290afe --- /dev/null +++ b/homeassistant/components/lutron_caseta/.translations/da.json @@ -0,0 +1,5 @@ +{ + "config": { + "title": "Lutron Cas\u00e9ta" + } +} \ No newline at end of file diff --git a/homeassistant/components/mailgun/.translations/da.json b/homeassistant/components/mailgun/.translations/da.json index 0e25974031d..475d560bad6 100644 --- a/homeassistant/components/mailgun/.translations/da.json +++ b/homeassistant/components/mailgun/.translations/da.json @@ -5,7 +5,7 @@ "one_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning." }, "create_entry": { - "default": "For at sende begivenheder til Home Assistant skal du konfigurere [Webhooks med Mailgun]({mailgun_url}).\n\n Udfyld f\u00f8lgende oplysninger: \n\n - URL: `{webhook_url}`\n - Metode: POST\n - Indholdstype: application/json\n\n Se [dokumentationen] ({docs_url}) om hvordan du konfigurerer automatiseringer til at h\u00e5ndtere indg\u00e5ende data." + "default": "For at sende h\u00e6ndelser til Home Assistant skal du konfigurere [Webhooks med Mailgun]({mailgun_url}).\n\n Udfyld f\u00f8lgende oplysninger: \n\n - Webadresse: `{webhook_url}`\n - Metode: POST\n - Indholdstype: application/json\n\nSe [dokumentationen] ({docs_url}) om hvordan du konfigurerer automatiseringer til at h\u00e5ndtere indg\u00e5ende data." }, "step": { "user": { diff --git a/homeassistant/components/plaato/.translations/da.json b/homeassistant/components/plaato/.translations/da.json index 12e95b25e0f..f8d59572388 100644 --- a/homeassistant/components/plaato/.translations/da.json +++ b/homeassistant/components/plaato/.translations/da.json @@ -5,7 +5,7 @@ "one_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning." }, "create_entry": { - "default": "For at sende begivenheder til Home Assistant skal du konfigurere webhook funktionen i Plaato Airlock.\n\n Udfyld f\u00f8lgende oplysninger: \n\n - URL: `{webhook_url}`\n - Metode: POST\n \n Se [dokumentationen]({docs_url}) for yderligere oplysninger." + "default": "For at sende h\u00e6ndelser til Home Assistant skal du konfigurere webhook-funktionen i Plaato Airlock.\n\n Udfyld f\u00f8lgende oplysninger: \n\n - Webadresse: `{webhook_url}`\n - Metode: POST\n \nSe [dokumentationen]({docs_url}) for yderligere oplysninger." }, "step": { "user": { diff --git a/homeassistant/components/plex/.translations/da.json b/homeassistant/components/plex/.translations/da.json index 99d5d4d1685..2c3a20ae94f 100644 --- a/homeassistant/components/plex/.translations/da.json +++ b/homeassistant/components/plex/.translations/da.json @@ -6,6 +6,7 @@ "already_in_progress": "Plex konfigureres", "discovery_no_file": "Der blev ikke fundet nogen legacy konfigurationsfil", "invalid_import": "Importeret konfiguration er ugyldig", + "non-interactive": "Ikke-interaktiv import", "token_request_timeout": "Timeout ved hentning af token", "unknown": "Mislykkedes af ukendt \u00e5rsag" }, diff --git a/homeassistant/components/soma/.translations/da.json b/homeassistant/components/soma/.translations/da.json index 557eeab55b1..49bf83148a2 100644 --- a/homeassistant/components/soma/.translations/da.json +++ b/homeassistant/components/soma/.translations/da.json @@ -3,7 +3,9 @@ "abort": { "already_setup": "Du kan kun konfigurere en Soma-konto.", "authorize_url_timeout": "Timeout ved generering af autoriseret url.", - "missing_configuration": "Soma-komponenten er ikke konfigureret. F\u00f8lg venligst dokumentationen." + "connection_error": "Kunne ikke oprette forbindelse til SOMA Connect.", + "missing_configuration": "Soma-komponenten er ikke konfigureret. F\u00f8lg venligst dokumentationen.", + "result_error": "SOMA Connect svarede med fejlstatus." }, "create_entry": { "default": "Godkendt med Soma." diff --git a/homeassistant/components/somfy/.translations/da.json b/homeassistant/components/somfy/.translations/da.json index 9d05fd65a06..b50c030c636 100644 --- a/homeassistant/components/somfy/.translations/da.json +++ b/homeassistant/components/somfy/.translations/da.json @@ -8,6 +8,11 @@ "create_entry": { "default": "Godkendt med Somfy." }, + "step": { + "pick_implementation": { + "title": "V\u00e6lg godkendelsesmetode" + } + }, "title": "Somfy" } } \ No newline at end of file diff --git a/homeassistant/components/starline/.translations/da.json b/homeassistant/components/starline/.translations/da.json new file mode 100644 index 00000000000..2a8cbcf1270 --- /dev/null +++ b/homeassistant/components/starline/.translations/da.json @@ -0,0 +1,42 @@ +{ + "config": { + "error": { + "error_auth_app": "Forkert applikations-id eller hemmelighed", + "error_auth_mfa": "Forkert kode", + "error_auth_user": "Forkert brugernavn eller adgangskode" + }, + "step": { + "auth_app": { + "data": { + "app_id": "App-id", + "app_secret": "Hemmelighed" + }, + "description": "Applikations-id og hemmelig kode fra StarLine-udviklerkonto ", + "title": "Applikations-legitimationsoplysninger" + }, + "auth_captcha": { + "data": { + "captcha_code": "Kode fra billede" + }, + "description": "{captcha_img}", + "title": "Captcha" + }, + "auth_mfa": { + "data": { + "mfa_code": "SMS-kode" + }, + "description": "Indtast koden, der er sendt til telefon {phone_number}", + "title": "Tofaktor-godkendelse" + }, + "auth_user": { + "data": { + "password": "Adgangskode", + "username": "Brugernavn" + }, + "description": "StarLine-konto email og adgangskode", + "title": "Brugeroplysninger" + } + }, + "title": "StarLine" + } +} \ No newline at end of file diff --git a/homeassistant/components/tesla/.translations/da.json b/homeassistant/components/tesla/.translations/da.json new file mode 100644 index 00000000000..85091c350d8 --- /dev/null +++ b/homeassistant/components/tesla/.translations/da.json @@ -0,0 +1,30 @@ +{ + "config": { + "error": { + "connection_error": "Fejl ved tilslutning; tjek netv\u00e6rk og pr\u00f8v igen", + "identifier_exists": "Email er allerede registreret", + "invalid_credentials": "Ugyldige legitimationsoplysninger", + "unknown_error": "Ukendt fejl, rapporter venligst loginfo" + }, + "step": { + "user": { + "data": { + "password": "Adgangskode", + "username": "Email-adresse" + }, + "description": "Indtast dine oplysninger.", + "title": "Tesla - Konfiguration" + } + }, + "title": "Tesla" + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Sekunder mellem scanninger" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/traccar/.translations/da.json b/homeassistant/components/traccar/.translations/da.json index af3963f8c0f..2b0ec0003d6 100644 --- a/homeassistant/components/traccar/.translations/da.json +++ b/homeassistant/components/traccar/.translations/da.json @@ -5,7 +5,7 @@ "one_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning." }, "create_entry": { - "default": "For at sende begivenheder til Home Assistant skal du konfigurere webhook funktionen i Traccar.\n\n Brug f\u00f8lgende URL: `{webhook_url}`\n \n Se [dokumentationen]({docs_url}) for yderligere oplysninger." + "default": "For at sende h\u00e6ndelser til Home Assistant skal du konfigurere webhook-funktionen i Traccar.\n\nBrug f\u00f8lgende webadresse: `{webhook_url}`\n \nSe [dokumentationen]({docs_url}) for yderligere oplysninger." }, "step": { "user": { diff --git a/homeassistant/components/twilio/.translations/da.json b/homeassistant/components/twilio/.translations/da.json index 3c1ab7c01b5..0bb40aae7f2 100644 --- a/homeassistant/components/twilio/.translations/da.json +++ b/homeassistant/components/twilio/.translations/da.json @@ -5,7 +5,7 @@ "one_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning." }, "create_entry": { - "default": "For at sende begivenheder til Home Assistant skal du konfigurere [Webhooks med Twilio]({twilio_url}).\n\n Udfyld f\u00f8lgende oplysninger: \n\n - URL: `{webhook_url}`\n - Metode: POST\n - Indholdstype: application/x-www-form-urlencoded\n\n Se [dokumentationen]({docs_url}) om hvordan du konfigurerer automatiseringer til at h\u00e5ndtere indg\u00e5ende data." + "default": "For at sende h\u00e6ndelser til Home Assistant skal du konfigurere [Webhooks med Twilio]({twilio_url}).\n\n Udfyld f\u00f8lgende oplysninger: \n\n - Webadresse: `{webhook_url}`\n - Metode: POST\n - Indholdstype: application/x-www-form-urlencoded\n\nSe [dokumentationen]({docs_url}) om hvordan du konfigurerer automatiseringer til at h\u00e5ndtere indg\u00e5ende data." }, "step": { "user": { diff --git a/homeassistant/components/vacuum/.translations/da.json b/homeassistant/components/vacuum/.translations/da.json new file mode 100644 index 00000000000..fac748ca464 --- /dev/null +++ b/homeassistant/components/vacuum/.translations/da.json @@ -0,0 +1,16 @@ +{ + "device_automation": { + "action_type": { + "clean": "Lad {entity_name} g\u00f8re rent", + "dock": "Lad {entity_name} vende tilbage til dock" + }, + "condition_type": { + "is_cleaning": "{entity_name} g\u00f8r rent", + "is_docked": "{entity_name} er i dock" + }, + "trigger_type": { + "cleaning": "{entity_name} begyndte at reng\u00f8re", + "docked": "{entity_name} er i dock" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wled/.translations/da.json b/homeassistant/components/wled/.translations/da.json new file mode 100644 index 00000000000..0ab3a789b3a --- /dev/null +++ b/homeassistant/components/wled/.translations/da.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Denne WLED-enhed er allerede konfigureret.", + "connection_error": "Kunne ikke oprette forbindelse til WLED-enheden." + }, + "error": { + "connection_error": "Kunne ikke oprette forbindelse til WLED-enheden." + }, + "flow_title": "WLED: {name}", + "step": { + "user": { + "data": { + "host": "V\u00e6rt eller IP-adresse" + }, + "description": "Indstil din WLED til at integrere med Home Assistant.", + "title": "Forbind din WLED" + }, + "zeroconf_confirm": { + "description": "\u00d8nsker du at tilf\u00f8je WLED-enhed med navnet `{name}' til Home Assistant?", + "title": "Fandt WLED-enhed" + } + }, + "title": "WLED" + } +} \ No newline at end of file From a91b0058229974aed578fb20e833df4454d007f9 Mon Sep 17 00:00:00 2001 From: LE LAY Olivier Date: Sun, 29 Dec 2019 08:26:43 +0100 Subject: [PATCH 424/677] Fix ble_tracker randomly pygatt thrown error (#28671) * fix(ble_tracker): catch randomly pygatt thrown error * fix(ble_tracker): merge except errors --- homeassistant/components/bluetooth_le_tracker/device_tracker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/bluetooth_le_tracker/device_tracker.py b/homeassistant/components/bluetooth_le_tracker/device_tracker.py index 40f25f2fc43..9c64232c6e9 100644 --- a/homeassistant/components/bluetooth_le_tracker/device_tracker.py +++ b/homeassistant/components/bluetooth_le_tracker/device_tracker.py @@ -77,7 +77,7 @@ def setup_scanner(hass, config, see, discovery_info=None): devices = {x["address"]: x["name"] for x in devs} _LOGGER.debug("Bluetooth LE devices discovered = %s", devices) - except RuntimeError as error: + except (RuntimeError, pygatt.exceptions.BLEError) as error: _LOGGER.error("Error during Bluetooth LE scan: %s", error) return {} return devices From 85624e80312e0487f51653ce24b398320972cbec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Sun, 29 Dec 2019 17:18:23 +0100 Subject: [PATCH 425/677] Fix creating smappee sensors when remote is not active (#30270) --- homeassistant/components/smappee/sensor.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/smappee/sensor.py b/homeassistant/components/smappee/sensor.py index 28abf759d09..c61d28bbaac 100644 --- a/homeassistant/components/smappee/sensor.py +++ b/homeassistant/components/smappee/sensor.py @@ -97,19 +97,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) if smappee.is_local_active: - for location_id in smappee.locations.keys(): + if smappee.is_remote_active: + location_keys = smappee.locations.keys() + else: + location_keys = [None] + for location_id in location_keys: for sensor in SENSOR_TYPES: if "local" in SENSOR_TYPES[sensor]: - if smappee.is_remote_active: - dev.append( - SmappeeSensor( - smappee, location_id, sensor, SENSOR_TYPES[sensor] - ) - ) - else: - dev.append( - SmappeeSensor(smappee, None, sensor, SENSOR_TYPES[sensor]) + dev.append( + SmappeeSensor( + smappee, location_id, sensor, SENSOR_TYPES[sensor] ) + ) add_entities(dev, True) From 9892564ab54bb38959fcfdbe9c1d2656af9cd0c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sun, 29 Dec 2019 21:12:49 +0200 Subject: [PATCH 426/677] Upgrade rflink to 0.0.50, ignore_devices now supports * and ? anywhere (#30268) https://github.com/aequitas/python-rflink/releases/tag/0.0.50 https://github.com/aequitas/python-rflink/releases/tag/0.0.49 https://github.com/aequitas/python-rflink/releases/tag/0.0.48 https://github.com/aequitas/python-rflink/releases/tag/0.0.47 --- homeassistant/components/rflink/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/rflink/manifest.json b/homeassistant/components/rflink/manifest.json index bda260bdff2..8c322e5bdf5 100644 --- a/homeassistant/components/rflink/manifest.json +++ b/homeassistant/components/rflink/manifest.json @@ -3,7 +3,7 @@ "name": "Rflink", "documentation": "https://www.home-assistant.io/integrations/rflink", "requirements": [ - "rflink==0.0.46" + "rflink==0.0.50" ], "dependencies": [], "codeowners": [] diff --git a/requirements_all.txt b/requirements_all.txt index ca438455bba..641667d4506 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1740,7 +1740,7 @@ restrictedpython==5.0 rfk101py==0.0.1 # homeassistant.components.rflink -rflink==0.0.46 +rflink==0.0.50 # homeassistant.components.ring ring_doorbell==0.2.8 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5ee5e017864..6b9a7c98702 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -550,7 +550,7 @@ regenmaschine==1.5.1 restrictedpython==5.0 # homeassistant.components.rflink -rflink==0.0.46 +rflink==0.0.50 # homeassistant.components.ring ring_doorbell==0.2.8 From 13116d8d3f6900a12a93e6eeb567263ab7349a1f Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Mon, 30 Dec 2019 00:32:19 +0000 Subject: [PATCH 427/677] [ci skip] Translation update --- .../components/abode/.translations/da.json | 2 +- .../components/adguard/.translations/da.json | 12 +++-- .../components/airly/.translations/da.json | 4 +- .../alarm_control_panel/.translations/da.json | 4 ++ .../alarm_control_panel/.translations/nl.json | 7 +++ .../ambient_station/.translations/da.json | 2 +- .../components/auth/.translations/da.json | 8 ++-- .../components/auth/.translations/nl.json | 2 +- .../components/axis/.translations/da.json | 8 ++-- .../binary_sensor/.translations/da.json | 29 +++++++++++- .../cert_expiry/.translations/da.json | 2 +- .../components/climate/.translations/nl.json | 17 +++++++ .../coolmaster/.translations/da.json | 10 +++- .../coolmaster/.translations/nl.json | 9 +++- .../components/cover/.translations/da.json | 4 +- .../components/deconz/.translations/da.json | 46 ++++++++++++------- .../device_tracker/.translations/da.json | 8 ++++ .../dialogflow/.translations/da.json | 2 +- .../components/ecobee/.translations/da.json | 5 +- .../components/elgato/.translations/nl.json | 27 +++++++++++ .../emulated_roku/.translations/da.json | 10 ++-- .../components/esphome/.translations/da.json | 4 +- .../components/geofency/.translations/da.json | 2 +- .../geonetnz_volcano/.translations/nl.json | 3 +- .../gpslogger/.translations/da.json | 2 +- .../components/hangouts/.translations/da.json | 8 ++-- .../hisense_aehw4a1/.translations/nl.json | 15 ++++++ .../homekit_controller/.translations/da.json | 22 ++++----- .../huawei_lte/.translations/da.json | 29 ++++++++++-- .../huawei_lte/.translations/ko.json | 1 + .../huawei_lte/.translations/nl.json | 1 + .../huawei_lte/.translations/no.json | 1 + .../components/hue/.translations/da.json | 16 +++---- .../components/icloud/.translations/nl.json | 38 +++++++++++++++ .../components/ifttt/.translations/da.json | 6 +-- .../components/ipma/.translations/da.json | 2 +- .../components/life360/.translations/da.json | 2 +- .../components/lifx/.translations/da.json | 4 +- .../components/light/.translations/da.json | 13 +++++- .../components/locative/.translations/da.json | 2 +- .../components/lock/.translations/da.json | 3 +- .../logi_circle/.translations/da.json | 6 +-- .../luftdaten/.translations/da.json | 2 +- .../lutron_caseta/.translations/nl.json | 5 ++ .../components/mailgun/.translations/da.json | 2 +- .../media_player/.translations/da.json | 11 +++++ .../components/met/.translations/da.json | 2 +- .../mobile_app/.translations/da.json | 8 ++-- .../components/mqtt/.translations/da.json | 10 ++-- .../components/neato/.translations/da.json | 9 ++-- .../components/nest/.translations/da.json | 4 +- .../components/notion/.translations/da.json | 2 +- .../opentherm_gw/.translations/da.json | 11 +++-- .../components/openuv/.translations/da.json | 4 +- .../owntracks/.translations/da.json | 2 +- .../components/plaato/.translations/da.json | 4 +- .../components/plex/.translations/da.json | 10 ++-- .../components/plex/.translations/nl.json | 1 + .../components/point/.translations/da.json | 2 +- .../components/ps4/.translations/da.json | 12 ++--- .../rainmachine/.translations/da.json | 2 +- .../components/sensor/.translations/da.json | 36 +++++++-------- .../simplisafe/.translations/da.json | 2 +- .../smartthings/.translations/da.json | 4 +- .../components/smhi/.translations/da.json | 4 +- .../components/soma/.translations/nl.json | 4 +- .../components/sonos/.translations/da.json | 2 +- .../components/starline/.translations/nl.json | 42 +++++++++++++++++ .../components/switch/.translations/da.json | 19 ++++++++ .../components/tesla/.translations/nl.json | 30 ++++++++++++ .../components/tesla/.translations/no.json | 30 ++++++++++++ .../components/toon/.translations/da.json | 8 ++-- .../components/tplink/.translations/da.json | 2 +- .../components/traccar/.translations/da.json | 4 +- .../components/traccar/.translations/tr.json | 10 ++++ .../transmission/.translations/da.json | 6 +-- .../twentemilieu/.translations/tr.json | 7 +++ .../components/twilio/.translations/da.json | 2 +- .../components/unifi/.translations/da.json | 8 +++- .../components/upnp/.translations/da.json | 4 +- .../components/vacuum/.translations/nl.json | 10 +++- .../components/vesync/.translations/da.json | 2 +- .../components/wemo/.translations/da.json | 4 +- .../components/withings/.translations/da.json | 10 ++++ .../components/wled/.translations/nl.json | 1 + .../components/wwlln/.translations/da.json | 6 +-- .../components/zha/.translations/da.json | 31 +++++++++++-- .../components/zwave/.translations/da.json | 6 +-- 88 files changed, 607 insertions(+), 188 deletions(-) create mode 100644 homeassistant/components/climate/.translations/nl.json create mode 100644 homeassistant/components/device_tracker/.translations/da.json create mode 100644 homeassistant/components/elgato/.translations/nl.json create mode 100644 homeassistant/components/hisense_aehw4a1/.translations/nl.json create mode 100644 homeassistant/components/icloud/.translations/nl.json create mode 100644 homeassistant/components/lutron_caseta/.translations/nl.json create mode 100644 homeassistant/components/media_player/.translations/da.json create mode 100644 homeassistant/components/starline/.translations/nl.json create mode 100644 homeassistant/components/switch/.translations/da.json create mode 100644 homeassistant/components/tesla/.translations/nl.json create mode 100644 homeassistant/components/tesla/.translations/no.json create mode 100644 homeassistant/components/traccar/.translations/tr.json create mode 100644 homeassistant/components/twentemilieu/.translations/tr.json diff --git a/homeassistant/components/abode/.translations/da.json b/homeassistant/components/abode/.translations/da.json index 3f094cb93bd..4a5fa763ea1 100644 --- a/homeassistant/components/abode/.translations/da.json +++ b/homeassistant/components/abode/.translations/da.json @@ -12,7 +12,7 @@ "user": { "data": { "password": "Adgangskode", - "username": "Email adresse" + "username": "Email-adresse" }, "title": "Udfyld dine Abode-loginoplysninger" } diff --git a/homeassistant/components/adguard/.translations/da.json b/homeassistant/components/adguard/.translations/da.json index 813405cec62..e9e6415518d 100644 --- a/homeassistant/components/adguard/.translations/da.json +++ b/homeassistant/components/adguard/.translations/da.json @@ -1,16 +1,18 @@ { "config": { "abort": { + "adguard_home_addon_outdated": "Denne integration kr\u00e6ver AdGuard Home {minimal_version} eller h\u00f8jere, du har {current_version}. Opdater venligst din Hass.io AdGuard Home-tilf\u00f8jelse.", + "adguard_home_outdated": "Denne integration kr\u00e6ver AdGuard Home {minimal_version} eller h\u00f8jere, du har {current_version}.", "existing_instance_updated": "Opdaterede eksisterende konfiguration.", - "single_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning af AdGuard Home." + "single_instance_allowed": "Kun en enkelt konfiguration af AdGuard Home er tilladt." }, "error": { "connection_error": "Forbindelse mislykkedes." }, "step": { "hassio_confirm": { - "description": "Vil du konfigurere Home Assistant til at oprette forbindelse til AdGuard Home, der leveres af Hass.io add-on: {addon}?", - "title": "AdGuard Home via Hass.io add-on" + "description": "Vil du konfigurere Home Assistant til at oprette forbindelse til AdGuard Home leveret af Hass.io-tilf\u00f8jelsen: {addon}?", + "title": "AdGuard Home via Hass.io-tilf\u00f8jelse" }, "user": { "data": { @@ -21,8 +23,8 @@ "username": "Brugernavn", "verify_ssl": "AdGuard Home bruger et korrekt certifikat" }, - "description": "Konfigurer din AdGuard Home instans for at tillade overv\u00e5gning og kontrol.", - "title": "Link AdGuard Home." + "description": "Konfigurer din AdGuard Home-instans for at tillade overv\u00e5gning og kontrol.", + "title": "Forbind din AdGuard Home." } }, "title": "AdGuard Home" diff --git a/homeassistant/components/airly/.translations/da.json b/homeassistant/components/airly/.translations/da.json index 652cc46a7b3..c2c14d1d101 100644 --- a/homeassistant/components/airly/.translations/da.json +++ b/homeassistant/components/airly/.translations/da.json @@ -3,7 +3,7 @@ "error": { "auth": "API-n\u00f8glen er ikke korrekt.", "name_exists": "Navnet findes allerede.", - "wrong_location": "Ingen Airly m\u00e5lestationer i dette omr\u00e5de." + "wrong_location": "Ingen Airly-m\u00e5lestationer i dette omr\u00e5de." }, "step": { "user": { @@ -13,7 +13,7 @@ "longitude": "L\u00e6ngdegrad", "name": "Integrationens navn" }, - "description": "Konfigurer Airly luftkvalitet integration. For at generere API-n\u00f8gle, g\u00e5 til https://developer.airly.eu/register", + "description": "Konfigurer Airly luftkvalitetsintegration. For at generere API-n\u00f8gle, g\u00e5 til https://developer.airly.eu/register", "title": "Airly" } }, diff --git a/homeassistant/components/alarm_control_panel/.translations/da.json b/homeassistant/components/alarm_control_panel/.translations/da.json index 304fa342bf2..220034d23e1 100644 --- a/homeassistant/components/alarm_control_panel/.translations/da.json +++ b/homeassistant/components/alarm_control_panel/.translations/da.json @@ -1,6 +1,10 @@ { "device_automation": { "action_type": { + "arm_away": "Tilkobl {entity_name} ude", + "arm_home": "Tilkobl {entity_name} hjemme", + "arm_night": "Tilkobl {entity_name} nat", + "disarm": "Frakobl {entity_name}", "trigger": "Udl\u00f8s {entity_name}" }, "trigger_type": { diff --git a/homeassistant/components/alarm_control_panel/.translations/nl.json b/homeassistant/components/alarm_control_panel/.translations/nl.json index 9329a089d32..5081ae992b4 100644 --- a/homeassistant/components/alarm_control_panel/.translations/nl.json +++ b/homeassistant/components/alarm_control_panel/.translations/nl.json @@ -6,6 +6,13 @@ "arm_night": "Inschakelen {entity_name} nacht", "disarm": "Uitschakelen {entity_name}", "trigger": "Trigger {entity_name}" + }, + "trigger_type": { + "armed_away": "{entity_name} afwezig ingeschakeld", + "armed_home": "{entity_name} thuis ingeschakeld", + "armed_night": "{entity_name} nachtstand ingeschakeld", + "disarmed": "{entity_name} uitgeschakeld", + "triggered": "{entity_name} geactiveerd" } } } \ No newline at end of file diff --git a/homeassistant/components/ambient_station/.translations/da.json b/homeassistant/components/ambient_station/.translations/da.json index ac3d86a995b..6cec31eca29 100644 --- a/homeassistant/components/ambient_station/.translations/da.json +++ b/homeassistant/components/ambient_station/.translations/da.json @@ -8,7 +8,7 @@ "step": { "user": { "data": { - "api_key": "API n\u00f8gle", + "api_key": "API-n\u00f8gle", "app_key": "Applikationsn\u00f8gle" }, "title": "Udfyld dine oplysninger" diff --git a/homeassistant/components/auth/.translations/da.json b/homeassistant/components/auth/.translations/da.json index 8bf710a1932..7877a813218 100644 --- a/homeassistant/components/auth/.translations/da.json +++ b/homeassistant/components/auth/.translations/da.json @@ -10,14 +10,14 @@ "step": { "init": { "description": "V\u00e6lg venligst en af meddelelsestjenesterne:", - "title": "Ops\u00e6t engangsadgangskode, der er leveret af besked komponenten" + "title": "Ops\u00e6t engangsadgangskoder leveret af notify-komponenten" }, "setup": { "description": "En engangsadgangskode er blevet sendt via **notify.{notify_service}**. Indtast den venligst nedenunder:", "title": "Bekr\u00e6ft ops\u00e6tningen" } }, - "title": "Advis\u00e9r engangskodeord" + "title": "Notify-engangsadgangskode" }, "totp": { "error": { @@ -25,8 +25,8 @@ }, "step": { "init": { - "description": "Hvis du vil aktivere tofaktorautentificering ved hj\u00e6lp af tidsbaserede engangskoder skal du scanne QR-koden med din autentificeringsapp. Hvis du ikke har en anbefaler vi enten [Google Authenticator] (https://support.google.com/accounts/answer/1066447) eller [Authy] (https://authy.com/). \n\n {qr_code} \n \nN\u00e5r du har scannet koden skal du indtaste den sekscifrede kode fra din app for at bekr\u00e6fte ops\u00e6tningen. Hvis du har problemer med at scanne QR-koden skal du lave en manuel ops\u00e6tning med kode **`{code}`**.", - "title": "Konfigurer to-faktors godkendelse ved hj\u00e6lp af TOTP" + "description": "Hvis du vil aktivere tofaktorgodkendelse ved hj\u00e6lp af tidsbaserede engangskoder skal du scanne QR-koden med din autentificeringsapp. Hvis du ikke har en anbefaler vi enten [Google Authenticator] (https://support.google.com/accounts/answer/1066447) eller [Authy] (https://authy.com/). \n\n {qr_code} \n \nN\u00e5r du har scannet koden skal du indtaste den sekscifrede kode fra din app for at bekr\u00e6fte ops\u00e6tningen. Hvis du har problemer med at scanne QR-koden skal du lave en manuel ops\u00e6tning med kode **`{code}`**.", + "title": "Konfigurer tofaktorgodkendelse ved hj\u00e6lp af TOTP" } }, "title": "TOTP" diff --git a/homeassistant/components/auth/.translations/nl.json b/homeassistant/components/auth/.translations/nl.json index 9ec8006507b..d61613097dd 100644 --- a/homeassistant/components/auth/.translations/nl.json +++ b/homeassistant/components/auth/.translations/nl.json @@ -9,7 +9,7 @@ }, "step": { "init": { - "description": "Selecteer een van de meldingsdiensten:", + "description": "Selecteer een van de meldingsservices:", "title": "Stel een \u00e9\u00e9nmalig wachtwoord in dat wordt afgegeven door een meldingscomponent" }, "setup": { diff --git a/homeassistant/components/axis/.translations/da.json b/homeassistant/components/axis/.translations/da.json index c169f85f280..21f33d120f7 100644 --- a/homeassistant/components/axis/.translations/da.json +++ b/homeassistant/components/axis/.translations/da.json @@ -8,11 +8,11 @@ }, "error": { "already_configured": "Enheden er allerede konfigureret", - "already_in_progress": "Enheds konfiguration er allerede i gang.", + "already_in_progress": "Enhedskonfiguration er allerede i gang.", "device_unavailable": "Enheden er ikke tilg\u00e6ngelig", "faulty_credentials": "Ugyldige legitimationsoplysninger" }, - "flow_title": "Axis enhed: {name} ({host})", + "flow_title": "Axis-enhed: {name} ({host})", "step": { "user": { "data": { @@ -21,9 +21,9 @@ "port": "Port", "username": "Brugernavn" }, - "title": "Konfigurer Axis enhed" + "title": "Indstil Axis-enhed" } }, - "title": "Axis enhed" + "title": "Axis-enhed" } } \ No newline at end of file diff --git a/homeassistant/components/binary_sensor/.translations/da.json b/homeassistant/components/binary_sensor/.translations/da.json index f7bd834561c..19229c16cb3 100644 --- a/homeassistant/components/binary_sensor/.translations/da.json +++ b/homeassistant/components/binary_sensor/.translations/da.json @@ -1,6 +1,7 @@ { "device_automation": { "condition_type": { + "is_bat_low": "{entity_name} batteri er lavt", "is_cold": "{entity_name} er kold", "is_connected": "{entity_name} er tilsluttet", "is_gas": "{entity_name} registrerer gas", @@ -17,6 +18,7 @@ "is_no_smoke": "{entity_name} registrerer ikke r\u00f8g", "is_no_sound": "{entity_name} registrerer ikke lyd", "is_no_vibration": "{entity_name} registrerer ikke vibration", + "is_not_bat_low": "{entity_name} batteri er normalt", "is_not_cold": "{entity_name} er ikke kold", "is_not_connected": "{entity_name} er afbrudt", "is_not_hot": "{entity_name} er ikke varm", @@ -25,10 +27,17 @@ "is_not_moving": "{entity_name} bev\u00e6ger sig ikke", "is_not_occupied": "{entity_name} er ikke optaget", "is_not_open": "{entity_name} er lukket", + "is_not_plugged_in": "{entity_name} er ikke tilsluttet str\u00f8m", + "is_not_powered": "{entity_name} er ikke tilsluttet str\u00f8m", "is_not_present": "{entity_name} er ikke til stede", "is_not_unsafe": "{entity_name} er sikker", "is_occupied": "{entity_name} er optaget", + "is_off": "{entity_name} er sl\u00e5et fra", + "is_on": "{entity_name} er sl\u00e5et til", "is_open": "{entity_name} er \u00e5ben", + "is_plugged_in": "{entity_name} er tilsluttet str\u00f8m", + "is_powered": "{entity_name} er tilsluttet str\u00f8m", + "is_present": "{entity_name} er til stede", "is_problem": "{entity_name} registrerer problem", "is_smoke": "{entity_name} registrerer r\u00f8g", "is_sound": "{entity_name} registrerer lyd", @@ -36,9 +45,14 @@ "is_vibration": "{entity_name} registrerer vibration" }, "trigger_type": { + "bat_low": "{entity_name} lavt batteriniveau", "closed": "{entity_name} lukket", "cold": "{entity_name} blev kold", "connected": "{entity_name} tilsluttet", + "gas": "{entity_name} begyndte at registrere gas", + "hot": "{entity_name} blev varm", + "light": "{entity_name} begyndte at registrere lys", + "locked": "{entity_name} l\u00e5st", "moist": "{entity_name} blev fugtig", "moist\u00a7": "{entity_name} blev fugtig", "motion": "{entity_name} begyndte at registrere bev\u00e6gelse", @@ -50,18 +64,31 @@ "no_smoke": "{entity_name} stoppede med at registrere r\u00f8g", "no_sound": "{entity_name} stoppede med at registrere lyd", "no_vibration": "{entity_name} stoppede med at registrere vibration", + "not_bat_low": "{entity_name} batteri normalt", + "not_cold": "{entity_name} blev ikke kold", "not_connected": "{entity_name} afbrudt", "not_hot": "{entity_name} blev ikke varm", "not_locked": "{entity_name} l\u00e5st op", "not_moist": "{entity_name} blev t\u00f8r", + "not_moving": "{entity_name} stoppede med at bev\u00e6ge sig", + "not_occupied": "{entity_name} blev ikke optaget", "not_opened": "{entity_name} lukket", + "not_plugged_in": "{entity_name} ikke tilsluttet str\u00f8m", + "not_powered": "{entity_name} ikke tilsluttet str\u00f8m", "not_present": "{entity_name} ikke til stede", "not_unsafe": "{entity_name} blev sikker", "occupied": "{entity_name} blev optaget", + "opened": "{entity_name} \u00e5bnet", + "plugged_in": "{entity_name} tilsluttet str\u00f8m", + "powered": "{entity_name} tilsluttet str\u00f8m", "present": "{entity_name} til stede", "problem": "{entity_name} begyndte at registrere problem", "smoke": "{entity_name} begyndte at registrere r\u00f8g", - "sound": "{entity_name} begyndte at registrere lyd" + "sound": "{entity_name} begyndte at registrere lyd", + "turned_off": "{entity_name} slukkede", + "turned_on": "{entity_name} t\u00e6ndte", + "unsafe": "{entity_name} blev usikker", + "vibration": "{entity_name} begyndte at registrere vibration" } } } \ No newline at end of file diff --git a/homeassistant/components/cert_expiry/.translations/da.json b/homeassistant/components/cert_expiry/.translations/da.json index c95a56320c9..26ee436860a 100644 --- a/homeassistant/components/cert_expiry/.translations/da.json +++ b/homeassistant/components/cert_expiry/.translations/da.json @@ -21,6 +21,6 @@ "title": "Definer certifikatet, der skal testes" } }, - "title": "Certifikat udl\u00f8b" + "title": "Certifikatets udl\u00f8bsdato" } } \ No newline at end of file diff --git a/homeassistant/components/climate/.translations/nl.json b/homeassistant/components/climate/.translations/nl.json new file mode 100644 index 00000000000..87e16c1c885 --- /dev/null +++ b/homeassistant/components/climate/.translations/nl.json @@ -0,0 +1,17 @@ +{ + "device_automation": { + "action_type": { + "set_hvac_mode": "Wijzig de HVAC-modus op {entity_name}", + "set_preset_mode": "Wijzig voorinstelling op {entity_name}" + }, + "condition_type": { + "is_hvac_mode": "{entity_name} is ingesteld op een specifieke HVAC-modus", + "is_preset_mode": "{entity_name} is ingesteld op een specifieke vooraf ingestelde modus" + }, + "trigger_type": { + "current_humidity_changed": "{entity_name} gemeten vochtigheid veranderd", + "current_temperature_changed": "{entity_name} gemeten temperatuur veranderd", + "hvac_mode_changed": "{entity_name} HVAC-modus gewijzigd" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/coolmaster/.translations/da.json b/homeassistant/components/coolmaster/.translations/da.json index 8f50a0eb6dd..882bc5de359 100644 --- a/homeassistant/components/coolmaster/.translations/da.json +++ b/homeassistant/components/coolmaster/.translations/da.json @@ -1,9 +1,17 @@ { "config": { + "error": { + "connection_error": "Kunne ikke oprette forbindelse til CoolMasterNet-instansen. Tjek din v\u00e6rt.", + "no_units": "Kunne ikke finde nogen klimaanl\u00e6g i CoolMasterNet-v\u00e6rt." + }, "step": { "user": { "data": { - "heat_cool": "Underst\u00f8t automatisk varm/k\u00f8l tilstand", + "cool": "Underst\u00f8tter k\u00f8lingstilstand", + "dry": "Underst\u00f8tter t\u00f8rringstilstand", + "fan_only": "Underst\u00f8tter kun-bl\u00e6ser-tilstand", + "heat": "Underst\u00f8tter varmetilstand", + "heat_cool": "Underst\u00f8tter automatisk varm/k\u00f8l-tilstand", "host": "V\u00e6rt", "off": "Kan slukkes" }, diff --git a/homeassistant/components/coolmaster/.translations/nl.json b/homeassistant/components/coolmaster/.translations/nl.json index 02b65cdfff9..e5b1683790f 100644 --- a/homeassistant/components/coolmaster/.translations/nl.json +++ b/homeassistant/components/coolmaster/.translations/nl.json @@ -1,11 +1,18 @@ { "config": { "error": { - "connection_error": "Kan geen verbinding maken met CoolMasterNet-instantie. Controleer uw host" + "connection_error": "Kan geen verbinding maken met CoolMasterNet-instantie. Controleer uw host", + "no_units": "Kon geen HVAC units vinden in CoolMasterNet host." }, "step": { "user": { "data": { + "cool": "Ondersteuning afkoelen modus", + "dry": "Ondersteuning droog modus", + "fan_only": "Ondersteunt alleen ventilatormodus", + "heat": "Ondersteuning warmtemodus", + "heat_cool": "Ondersteuning van automatische warmte/koelmodus", + "host": "Host", "off": "Kan uitgeschakeld worden" }, "title": "Stel uw CoolMasterNet-verbindingsgegevens in." diff --git a/homeassistant/components/cover/.translations/da.json b/homeassistant/components/cover/.translations/da.json index a645a3cc613..64b89be5267 100644 --- a/homeassistant/components/cover/.translations/da.json +++ b/homeassistant/components/cover/.translations/da.json @@ -4,7 +4,9 @@ "is_closed": "{entity_name} er lukket", "is_closing": "{entity_name} lukker", "is_open": "{entity_name} er \u00e5ben", - "is_opening": "{entity_name} \u00e5bnes" + "is_opening": "{entity_name} \u00e5bnes", + "is_position": "Aktuel {entity_name} position er", + "is_tilt_position": "Aktuel {entity_name} vippeposition er" }, "trigger_type": { "closed": "{entity_name} lukket", diff --git a/homeassistant/components/deconz/.translations/da.json b/homeassistant/components/deconz/.translations/da.json index 80dc9ae6d3b..1f0828274f0 100644 --- a/homeassistant/components/deconz/.translations/da.json +++ b/homeassistant/components/deconz/.translations/da.json @@ -2,11 +2,11 @@ "config": { "abort": { "already_configured": "Bridge er allerede konfigureret", - "already_in_progress": "Bro konfiguration er allerede i gang.", - "no_bridges": "Ingen deConz bridge fundet", - "not_deconz_bridge": "Ikke en deCONZ bro", - "one_instance_only": "Komponenten underst\u00f8tter kun \u00e9n deCONZ forekomst", - "updated_instance": "Opdaterede deCONZ instans med ny v\u00e6rtsadresse" + "already_in_progress": "Konfigurationsflow for bro er allerede i gang.", + "no_bridges": "Ingen deConz-bridge fundet", + "not_deconz_bridge": "Ikke en deCONZ-bro", + "one_instance_only": "Komponenten underst\u00f8tter kun \u00e9n deCONZ-instans", + "updated_instance": "Opdaterede deCONZ-instans med ny v\u00e6rtadresse" }, "error": { "no_key": "Kunne ikke f\u00e5 en API-n\u00f8gle" @@ -16,28 +16,28 @@ "hassio_confirm": { "data": { "allow_clip_sensor": "Tillad import af virtuelle sensorer", - "allow_deconz_groups": "Tillad import af deCONZ grupper" + "allow_deconz_groups": "Tillad import af deCONZ-grupper" }, - "description": "Vil du konfigurere Home Assistant til at oprette forbindelse til deCONZ gateway leveret af Hass.io add-on {addon}?", - "title": "deCONZ Zigbee-gateway via Hass.io add-on" + "description": "Vil du konfigurere Home Assistant til at oprette forbindelse til deCONZ-gateway'en leveret af Hass.io-tilf\u00f8jelsen {addon}?", + "title": "deCONZ Zigbee-gateway via Hass.io-tilf\u00f8jelse" }, "init": { "data": { "host": "V\u00e6rt", "port": "Port" }, - "title": "Definer deCONZ gateway" + "title": "Definer deCONZ-gateway" }, "link": { "description": "L\u00e5s din deCONZ-gateway op for at registrere dig med Home Assistant. \n\n 1. G\u00e5 til deCONZ settings -> Gateway -> Advanced\n 2. Tryk p\u00e5 knappen \"Authenticate app\"", - "title": "Link med deCONZ" + "title": "Forbind med deCONZ" }, "options": { "data": { "allow_clip_sensor": "Tillad import af virtuelle sensorer", - "allow_deconz_groups": "Tillad importering af deCONZ grupper" + "allow_deconz_groups": "Tillad import af deCONZ-grupper" }, - "title": "Ekstra konfiguration valgmuligheder for deCONZ" + "title": "Ekstra konfigurationsindstillinger for deCONZ" } }, "title": "deCONZ Zigbee gateway" @@ -60,10 +60,22 @@ "side_3": "Side 3", "side_4": "Side 4", "side_5": "Side 5", - "side_6": "Side 6" + "side_6": "Side 6", + "turn_off": "Sluk", + "turn_on": "T\u00e6nd" }, "trigger_type": { "remote_awakened": "Enheden v\u00e6kket", + "remote_button_double_press": "\"{subtype}\"-knappen er dobbeltklikket", + "remote_button_long_press": "\"{subtype}\"-knappen trykket p\u00e5 konstant", + "remote_button_long_release": "\"{subtype}\"-knappen frigivet efter langt tryk", + "remote_button_quadruple_press": "\"{subtype}\"-knappen firedobbelt-klikket", + "remote_button_quintuple_press": "\"{subtype}\"-knappen femdobbelt-klikket", + "remote_button_rotated": "Knap roteret \"{subtype}\"", + "remote_button_rotation_stopped": "Knaprotation \"{subtype}\" er stoppet", + "remote_button_short_press": "\"{subtype}\"-knappen trykket p\u00e5", + "remote_button_short_release": "\"{subtype}\"-knappen frigivet", + "remote_button_triple_press": "\"{subtype}\"-knappen tredobbeltklikkes", "remote_double_tap": "Enheden \"{subtype}\" dobbelttappet", "remote_falling": "Enheden er i frit fald", "remote_gyro_activated": "Enhed rystet", @@ -80,15 +92,15 @@ "step": { "async_step_deconz_devices": { "data": { - "allow_clip_sensor": "Tillad deCONZ CLIP sensorer", - "allow_deconz_groups": "Tillad deCONZ lys grupper" + "allow_clip_sensor": "Tillad deCONZ CLIP-sensorer", + "allow_deconz_groups": "Tillad deCONZ-lysgrupper" }, "description": "Konfigurer synligheden af deCONZ-enhedstyper" }, "deconz_devices": { "data": { - "allow_clip_sensor": "Tillad deCONZ CLIP sensorer", - "allow_deconz_groups": "Tillad deCONZ lys grupper" + "allow_clip_sensor": "Tillad deCONZ CLIP-sensorer", + "allow_deconz_groups": "Tillad deCONZ-lysgrupper" }, "description": "Konfigurer synligheden af deCONZ-enhedstyper" } diff --git a/homeassistant/components/device_tracker/.translations/da.json b/homeassistant/components/device_tracker/.translations/da.json new file mode 100644 index 00000000000..d714b5b7d31 --- /dev/null +++ b/homeassistant/components/device_tracker/.translations/da.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "condition_type": { + "is_home": "{entity_name} er hjemme", + "is_not_home": "{entity_name} er ikke hjemme" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dialogflow/.translations/da.json b/homeassistant/components/dialogflow/.translations/da.json index 22f123a38a1..c682c07a8b9 100644 --- a/homeassistant/components/dialogflow/.translations/da.json +++ b/homeassistant/components/dialogflow/.translations/da.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "not_internet_accessible": "Dit Home Assistant system skal v\u00e6re tilg\u00e6ngeligt fra internettet for at modtage Dialogflow meddelelser.", + "not_internet_accessible": "Din Home Assistant-instans skal v\u00e6re tilg\u00e6ngelig fra internettet for at modtage Dialogflow-meddelelser", "one_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning" }, "create_entry": { diff --git a/homeassistant/components/ecobee/.translations/da.json b/homeassistant/components/ecobee/.translations/da.json index 7a42a9470db..614811db45a 100644 --- a/homeassistant/components/ecobee/.translations/da.json +++ b/homeassistant/components/ecobee/.translations/da.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "one_instance_only": "Integrationen underst\u00f8tter kun \u00e9n ecobee forekomst" + "one_instance_only": "Denne integration underst\u00f8tter i \u00f8jeblikket kun en ecobee-instans." }, "error": { "pin_request_failed": "Fejl ved anmodning om pinkode fra ecobee. Kontroller at API-n\u00f8glen er korrekt.", @@ -9,7 +9,8 @@ }, "step": { "authorize": { - "title": "Autoriser app p\u00e5 ecobee.com" + "description": "Godkend denne app p\u00e5 https://www.ecobee.com/consumerportal/index.html med PIN-kode:\n\n{pin}\n\nTryk derefter p\u00e5 Indsend.", + "title": "Godkend app p\u00e5 ecobee.com" }, "user": { "data": { diff --git a/homeassistant/components/elgato/.translations/nl.json b/homeassistant/components/elgato/.translations/nl.json new file mode 100644 index 00000000000..ca05983eeb5 --- /dev/null +++ b/homeassistant/components/elgato/.translations/nl.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Dit Elgato Key Light apparaat is al geconfigureerd.", + "connection_error": "Kan geen verbinding maken met het Elgato Key Light apparaat." + }, + "error": { + "connection_error": "Kan geen verbinding maken met het Elgato Key Light apparaat." + }, + "flow_title": "Elgato Key Light: {serial_number}", + "step": { + "user": { + "data": { + "host": "Hostnaam of IP-adres", + "port": "Poortnummer" + }, + "description": "Stel uw Elgato Key Light in om te integreren met Home Assistant.", + "title": "Koppel uw Elgato Key Light" + }, + "zeroconf_confirm": { + "description": "Wilt u de Elgato Key Light met serienummer ` {serial_number} ` toevoegen aan Home Assistant?", + "title": "Elgato Key Light apparaat ontdekt" + } + }, + "title": "Elgato Key Light" + } +} \ No newline at end of file diff --git a/homeassistant/components/emulated_roku/.translations/da.json b/homeassistant/components/emulated_roku/.translations/da.json index 0479dee437d..0da64fac623 100644 --- a/homeassistant/components/emulated_roku/.translations/da.json +++ b/homeassistant/components/emulated_roku/.translations/da.json @@ -6,14 +6,14 @@ "step": { "user": { "data": { - "advertise_ip": "Adviserings IP", - "advertise_port": "Adviserings port", - "host_ip": "V\u00e6rt IP", - "listen_port": "Lytte port", + "advertise_ip": "Adviserings-IP", + "advertise_port": "Adviseringsport", + "host_ip": "V\u00e6rts-IP", + "listen_port": "Lytte-port", "name": "Navn", "upnp_bind_multicast": "Bind multicast (sand/falsk)" }, - "title": "Angiv server konfiguration" + "title": "Angiv server-konfiguration" } }, "title": "EmulatedRoku" diff --git a/homeassistant/components/esphome/.translations/da.json b/homeassistant/components/esphome/.translations/da.json index ba84ab40301..db4b4362a5e 100644 --- a/homeassistant/components/esphome/.translations/da.json +++ b/homeassistant/components/esphome/.translations/da.json @@ -18,8 +18,8 @@ "title": "Indtast adgangskode" }, "discovery_confirm": { - "description": "Vil du tilf\u00f8je ESPHome node `{name}` til Home Assistant?", - "title": "Fandt ESPHome node" + "description": "Vil du tilf\u00f8je ESPHome-knudepunkt `{name}` til Home Assistant?", + "title": "Fandt ESPHome-knudepunkt" }, "user": { "data": { diff --git a/homeassistant/components/geofency/.translations/da.json b/homeassistant/components/geofency/.translations/da.json index 21ff2e9fced..6e9443af5e8 100644 --- a/homeassistant/components/geofency/.translations/da.json +++ b/homeassistant/components/geofency/.translations/da.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "not_internet_accessible": "Dit Home Assistant system skal v\u00e6re tilg\u00e6ngeligt fra internettet for at modtage Geofency meddelelser.", + "not_internet_accessible": "Din Home Assistant-instans skal v\u00e6re tilg\u00e6ngelig fra internettet for at modtage Geofency-meddelelser.", "one_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning" }, "create_entry": { diff --git a/homeassistant/components/geonetnz_volcano/.translations/nl.json b/homeassistant/components/geonetnz_volcano/.translations/nl.json index 73c7c1eaab3..44d814b9db2 100644 --- a/homeassistant/components/geonetnz_volcano/.translations/nl.json +++ b/homeassistant/components/geonetnz_volcano/.translations/nl.json @@ -10,6 +10,7 @@ }, "title": "Vul uw filtergegevens in." } - } + }, + "title": "GeoNet NZ Volcano" } } \ No newline at end of file diff --git a/homeassistant/components/gpslogger/.translations/da.json b/homeassistant/components/gpslogger/.translations/da.json index 4aaebb7aa82..b118783cd3c 100644 --- a/homeassistant/components/gpslogger/.translations/da.json +++ b/homeassistant/components/gpslogger/.translations/da.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "not_internet_accessible": "Dit Home Assistant system skal v\u00e6re tilg\u00e6ngeligt fra internettet for at modtage GPSLogger meddelelser.", + "not_internet_accessible": "Din Home Assistant-instans skal v\u00e6re tilg\u00e6ngelig fra internettet for at modtage GPSLogger-meddelelser.", "one_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning" }, "create_entry": { diff --git a/homeassistant/components/hangouts/.translations/da.json b/homeassistant/components/hangouts/.translations/da.json index 4155da38f8f..2ceb78ddde8 100644 --- a/homeassistant/components/hangouts/.translations/da.json +++ b/homeassistant/components/hangouts/.translations/da.json @@ -5,7 +5,7 @@ "unknown": "Ukendt fejl opstod" }, "error": { - "invalid_2fa": "Ugyldig 2-faktor godkendelse, pr\u00f8v venligst igen.", + "invalid_2fa": "Ugyldig tofaktor-godkendelse, pr\u00f8v igen.", "invalid_2fa_method": "Ugyldig 2FA-metode (Bekr\u00e6ft p\u00e5 telefon).", "invalid_login": "Ugyldig login, pr\u00f8v venligst igen." }, @@ -14,12 +14,12 @@ "data": { "2fa": "2FA pin" }, - "title": "To-faktor autentificering" + "title": "Tofaktor-godkendelse" }, "user": { "data": { - "authorization_code": "Autorisationskode (kr\u00e6ves til manuel godkendelse)", - "email": "Email adresse", + "authorization_code": "Godkendelseskode (kr\u00e6vet til manuel godkendelse)", + "email": "Emailadresse", "password": "Adgangskode" }, "title": "Google Hangouts login" diff --git a/homeassistant/components/hisense_aehw4a1/.translations/nl.json b/homeassistant/components/hisense_aehw4a1/.translations/nl.json new file mode 100644 index 00000000000..7360908a11d --- /dev/null +++ b/homeassistant/components/hisense_aehw4a1/.translations/nl.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "no_devices_found": "Geen Hisense AEH-W4A1-apparaten gevonden op het netwerk.", + "single_instance_allowed": "Slechts een enkele configuratie van Hisense AEH-W4A1 is mogelijk." + }, + "step": { + "confirm": { + "description": "Wilt u Hisense AEH-W4A1 instellen?", + "title": "Hisense AEH-W4A1" + } + }, + "title": "Hisense AEH-W4A1" + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/.translations/da.json b/homeassistant/components/homekit_controller/.translations/da.json index 2bcda4fb1ad..20b209752eb 100644 --- a/homeassistant/components/homekit_controller/.translations/da.json +++ b/homeassistant/components/homekit_controller/.translations/da.json @@ -3,38 +3,38 @@ "abort": { "accessory_not_found_error": "Parring kan ikke tilf\u00f8jes da enheden ikke l\u00e6ngere findes.", "already_configured": "Tilbeh\u00f8ret er allerede konfigureret med denne controller.", - "already_in_progress": "Enheds konfiguration er allerede i gang.", + "already_in_progress": "Enhedskonfiguration er allerede i gang.", "already_paired": "Dette tilbeh\u00f8r er allerede parret med en anden enhed. Nulstil tilbeh\u00f8ret og pr\u00f8v igen.", - "ignored_model": "HomeKit underst\u00f8ttelse til denne model er blokeret da en mere komplet native integration er til r\u00e5dighed.", + "ignored_model": "HomeKit-underst\u00f8ttelse af denne model er blokeret, da en mere funktionskomplet indbygget integration er tilg\u00e6ngelig.", "invalid_config_entry": "Denne enhed vises som klar til parring, men der er allerede en modstridende konfigurationspost for den i Home Assistant, som f\u00f8rst skal fjernes.", "no_devices": "Der blev ikke fundet nogen uparrede enheder" }, "error": { - "authentication_error": "Forkert HomeKit kode. Kontroller den og pr\u00f8v igen.", - "busy_error": "Enheden n\u00e6gtede at tilf\u00f8je parring da den allerede parrer med en anden controller.", - "max_peers_error": "Enheden n\u00e6gtede at tilf\u00f8je parring da den ikke har nok frit parrings lager.", - "max_tries_error": "Enheden n\u00e6gtede at tilf\u00f8je parring da den har modtaget mere end 100 mislykkede godkendelsesfors\u00f8g.", + "authentication_error": "Forkert HomeKit-kode. Kontroller den og pr\u00f8v igen.", + "busy_error": "Enheden n\u00e6gtede at parre da den allerede er parret med en anden controller.", + "max_peers_error": "Enheden n\u00e6gtede at parre da den ikke har nok frit parringslagerplads.", + "max_tries_error": "Enheden n\u00e6gtede at parre da den har modtaget mere end 100 mislykkede godkendelsesfors\u00f8g.", "pairing_failed": "En uh\u00e5ndteret fejl opstod under fors\u00f8g p\u00e5 at parre med denne enhed. Dette kan v\u00e6re en midlertidig fejl eller din enhed muligvis ikke underst\u00f8ttes i \u00f8jeblikket.", "unable_to_pair": "Kunne ikke parre, pr\u00f8v venligst igen.", "unknown_error": "Enhed rapporterede en ukendt fejl. Parring mislykkedes." }, - "flow_title": "HomeKit tilbeh\u00f8r: {name}", + "flow_title": "HomeKit-tilbeh\u00f8r: {name}", "step": { "pair": { "data": { "pairing_code": "Parringskode" }, - "description": "Indtast din HomeKit parringskode (i formatet XXX-XX-XXX) for at bruge dette tilbeh\u00f8r", - "title": "Par med HomeKit tilbeh\u00f8r" + "description": "Indtast din HomeKit-parringskode (i formatet XXX-XX-XXX) for at bruge dette tilbeh\u00f8r", + "title": "Par med HomeKit-tilbeh\u00f8r" }, "user": { "data": { "device": "Enhed" }, "description": "V\u00e6lg den enhed du vil parre med", - "title": "Par med HomeKit tilbeh\u00f8r" + "title": "Par med HomeKit-tilbeh\u00f8r" } }, - "title": "HomeKit tilbeh\u00f8r" + "title": "HomeKit-tilbeh\u00f8r" } } \ No newline at end of file diff --git a/homeassistant/components/huawei_lte/.translations/da.json b/homeassistant/components/huawei_lte/.translations/da.json index b693d414ceb..19bc69b77fd 100644 --- a/homeassistant/components/huawei_lte/.translations/da.json +++ b/homeassistant/components/huawei_lte/.translations/da.json @@ -1,18 +1,41 @@ { "config": { "abort": { + "already_configured": "Denne enhed er allerede konfigureret", "already_in_progress": "Denne enhed er allerede ved at blive konfigureret", "not_huawei_lte": "Ikke en Huawei LTE-enhed" }, "error": { - "connection_timeout": "Timeout for forbindelse" - } + "connection_failed": "Forbindelsen mislykkedes", + "connection_timeout": "Timeout for forbindelse", + "incorrect_password": "Forkert adgangskode", + "incorrect_username": "Forkert brugernavn", + "incorrect_username_or_password": "Forkert brugernavn eller adgangskode", + "invalid_url": "Ugyldig webadresse", + "login_attempts_exceeded": "Maksimale loginfors\u00f8g overskredet. Pr\u00f8v igen senere", + "response_error": "Ukendt fejl fra enheden", + "unknown_connection_error": "Ukendt fejl ved tilslutning til enheden" + }, + "step": { + "user": { + "data": { + "password": "Adgangskode", + "url": "Webadresse", + "username": "Brugernavn" + }, + "description": "Indtast oplysninger om enhedsadgang. Det er valgfrit at specificere brugernavn og adgangskode, men muligg\u00f8r underst\u00f8ttelse af flere integrationsfunktioner. P\u00e5 den anden side kan brug af en autoriseret forbindelse for\u00e5rsage problemer med at f\u00e5 adgang til enhedens webgr\u00e6nseflade uden for Home Assistant, mens integrationen er aktiv, og omvendt.", + "title": "Konfigurer Huawei LTE" + } + }, + "title": "Huawei LTE" }, "options": { "step": { "init": { "data": { - "name": "Navn p\u00e5 meddelelsestjeneste (\u00e6ndring kr\u00e6ver genstart)" + "name": "Navn p\u00e5 meddelelsestjeneste (\u00e6ndring kr\u00e6ver genstart)", + "recipient": "Modtagere af SMS-meddelelse", + "track_new_devices": "Spor nye enheder" } } } diff --git a/homeassistant/components/huawei_lte/.translations/ko.json b/homeassistant/components/huawei_lte/.translations/ko.json index a9ac8d7f62c..f6b3d855679 100644 --- a/homeassistant/components/huawei_lte/.translations/ko.json +++ b/homeassistant/components/huawei_lte/.translations/ko.json @@ -33,6 +33,7 @@ "step": { "init": { "data": { + "name": "\uc54c\ub9bc \uc11c\ube44\uc2a4 \uc774\ub984 (\ubcc0\uacbd \uc2dc \ub2e4\uc2dc \uc2dc\uc791\ud574\uc57c \ud568)", "recipient": "SMS \uc54c\ub9bc \uc218\uc2e0\uc790", "track_new_devices": "\uc0c8\ub85c\uc6b4 \uae30\uae30 \ucd94\uc801" } diff --git a/homeassistant/components/huawei_lte/.translations/nl.json b/homeassistant/components/huawei_lte/.translations/nl.json index 6d5e5c3e957..297ec922abf 100644 --- a/homeassistant/components/huawei_lte/.translations/nl.json +++ b/homeassistant/components/huawei_lte/.translations/nl.json @@ -33,6 +33,7 @@ "step": { "init": { "data": { + "name": "Naam meldingsservice (wijziging vereist opnieuw opstarten)", "recipient": "Ontvangers van sms-berichten", "track_new_devices": "Volg nieuwe apparaten" } diff --git a/homeassistant/components/huawei_lte/.translations/no.json b/homeassistant/components/huawei_lte/.translations/no.json index 35a5d531c5d..39cb5bf87fe 100644 --- a/homeassistant/components/huawei_lte/.translations/no.json +++ b/homeassistant/components/huawei_lte/.translations/no.json @@ -33,6 +33,7 @@ "step": { "init": { "data": { + "name": "Navn p\u00e5 varslingstjeneste (endring krever omstart)", "recipient": "Mottakere av SMS-varsling", "track_new_devices": "Spor nye enheter" } diff --git a/homeassistant/components/hue/.translations/da.json b/homeassistant/components/hue/.translations/da.json index 756f3b7e44b..afcfd7071e7 100644 --- a/homeassistant/components/hue/.translations/da.json +++ b/homeassistant/components/hue/.translations/da.json @@ -1,17 +1,17 @@ { "config": { "abort": { - "all_configured": "Alle Philips Hue brigdes er konfigureret", + "all_configured": "Alle Philips Hue-broer er allerede konfigureret", "already_configured": "Bridgen er allerede konfigureret", - "already_in_progress": "Bro konfiguration er allerede i gang.", + "already_in_progress": "Bro-konfiguration er allerede i gang.", "cannot_connect": "Kunne ikke oprette forbindelse til bridgen", - "discover_timeout": "Ingen Philips Hue bridge fundet", - "no_bridges": "Ingen Philips Hue bridge fundet", - "not_hue_bridge": "Ikke en Hue bro", + "discover_timeout": "Ingen Philips Hue-bro fundet", + "no_bridges": "Ingen Philips Hue-broer fundet", + "not_hue_bridge": "Ikke en Hue-bro", "unknown": "Ukendt fejl opstod" }, "error": { - "linking": "Ukendt sammenkoblings fejl opstod", + "linking": "Der opstod en ukendt linkfejl.", "register_failed": "Det lykkedes ikke at registrere, pr\u00f8v igen" }, "step": { @@ -22,8 +22,8 @@ "title": "V\u00e6lg Hue bridge" }, "link": { - "description": "Tryk p\u00e5 knappen p\u00e5 bridgen for at registrere Philips Hue med Home Assistant. \n\n ! [Placering af knap p\u00e5 bro] (/static/images/config_philips_hue.jpg)", - "title": "Link Hub" + "description": "Tryk p\u00e5 knappen p\u00e5 broen for at registrere Philips Hue med Home Assistant. \n\n ![Placering af knap p\u00e5 bro](/static/images/config_philips_hue.jpg)", + "title": "Forbind Hub" } }, "title": "Philips Hue" diff --git a/homeassistant/components/icloud/.translations/nl.json b/homeassistant/components/icloud/.translations/nl.json new file mode 100644 index 00000000000..d35496b171b --- /dev/null +++ b/homeassistant/components/icloud/.translations/nl.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "username_exists": "Account reeds geconfigureerd" + }, + "error": { + "login": "Aanmeldingsfout: controleer uw e-mailadres en wachtwoord", + "send_verification_code": "Kan verificatiecode niet verzenden", + "username_exists": "Account reeds geconfigureerd", + "validate_verification_code": "Kan uw verificatiecode niet verifi\u00ebren, kies een vertrouwensapparaat en start de verificatie opnieuw" + }, + "step": { + "trusted_device": { + "data": { + "trusted_device": "Vertrouwd apparaat" + }, + "description": "Selecteer uw vertrouwde apparaat", + "title": "iCloud vertrouwd apparaat" + }, + "user": { + "data": { + "password": "Wachtwoord", + "username": "E-mail" + }, + "description": "Voer uw gegevens in", + "title": "iCloud inloggegevens" + }, + "verification_code": { + "data": { + "verification_code": "Verificatiecode" + }, + "description": "Voer de verificatiecode in die u zojuist van iCloud hebt ontvangen", + "title": "iCloud verificatiecode" + } + }, + "title": "Apple iCloud" + } +} \ No newline at end of file diff --git a/homeassistant/components/ifttt/.translations/da.json b/homeassistant/components/ifttt/.translations/da.json index 7e05d938999..0e0c735eb89 100644 --- a/homeassistant/components/ifttt/.translations/da.json +++ b/homeassistant/components/ifttt/.translations/da.json @@ -1,15 +1,15 @@ { "config": { "abort": { - "not_internet_accessible": "Dit Home Assistant system skal v\u00e6re tilg\u00e6ngeligt fra internettet for at modtage IFTTT meddelelser.", - "one_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning" + "not_internet_accessible": "Din Home Assistant-instans skal v\u00e6re tilg\u00e6ngelig fra internettet for at modtage IFTTT-meddelelser", + "one_instance_allowed": "Kun en enkelt instans er n\u00f8dvendig." }, "create_entry": { "default": "For at sende h\u00e6ndelser til Home Assistant skal du bruge handlingen \"Foretag en web-foresp\u00f8rgsel\" fra [IFTTT Webhook-applet] ({applet_url}).\n\n Udfyld f\u00f8lgende oplysninger: \n\n - Webadresse: `{webhook_url}`\n - Metode: POST\n - Indholdstype: application/json\n\nSe [dokumentationen] ({docs_url}) om hvordan du konfigurerer automatiseringer til at h\u00e5ndtere indg\u00e5ende data." }, "step": { "user": { - "description": "Er du sikker p\u00e5 at du vil oprette IFTTT?", + "description": "Er du sikker p\u00e5, at du vil konfigurere IFTTT?", "title": "Konfigurer IFTTT Webhook Applet" } }, diff --git a/homeassistant/components/ipma/.translations/da.json b/homeassistant/components/ipma/.translations/da.json index 080c41429ba..017aff4d0ec 100644 --- a/homeassistant/components/ipma/.translations/da.json +++ b/homeassistant/components/ipma/.translations/da.json @@ -11,7 +11,7 @@ "name": "Navn" }, "description": "Instituto Portugu\u00eas do Mar e Atmosfera", - "title": "Beliggenhed" + "title": "Lokalitet" } }, "title": "Portugisisk vejrservice (IPMA)" diff --git a/homeassistant/components/life360/.translations/da.json b/homeassistant/components/life360/.translations/da.json index 933fce4a4e8..32acc488dc6 100644 --- a/homeassistant/components/life360/.translations/da.json +++ b/homeassistant/components/life360/.translations/da.json @@ -20,7 +20,7 @@ "username": "Brugernavn" }, "description": "Hvis du vil angive avancerede indstillinger skal du se [Life360 dokumentation]({docs_url}).\nDu \u00f8nsker m\u00e5ske at g\u00f8re dette f\u00f8r du tilf\u00f8jer konti.", - "title": "Life360 kontooplysninger" + "title": "Life360-kontooplysninger" } }, "title": "Life360" diff --git a/homeassistant/components/lifx/.translations/da.json b/homeassistant/components/lifx/.translations/da.json index ffd8e20ce42..99143f38c98 100644 --- a/homeassistant/components/lifx/.translations/da.json +++ b/homeassistant/components/lifx/.translations/da.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Ingen LIFX enheder kunne findes p\u00e5 netv\u00e6rket.", - "single_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning af LIFX." + "no_devices_found": "Der blev ikke fundet nogen LIFX-enheder p\u00e5 netv\u00e6rket.", + "single_instance_allowed": "Kun en enkelt konfiguration af LIFX er mulig." }, "step": { "confirm": { diff --git a/homeassistant/components/light/.translations/da.json b/homeassistant/components/light/.translations/da.json index 14a747f6eff..eefa1e8bb6e 100644 --- a/homeassistant/components/light/.translations/da.json +++ b/homeassistant/components/light/.translations/da.json @@ -1,8 +1,17 @@ { "device_automation": { + "action_type": { + "toggle": "Skift {entity_name}", + "turn_off": "Sluk {entity_name}", + "turn_on": "T\u00e6nd for {entity_name}" + }, + "condition_type": { + "is_off": "{entity_name} er fra", + "is_on": "{entity_name} er til" + }, "trigger_type": { - "turned_off": "{entity_name} slukket", - "turned_on": "{entity_name} t\u00e6ndt" + "turned_off": "{entity_name} slukkede", + "turned_on": "{entity_name} t\u00e6ndte" } } } \ No newline at end of file diff --git a/homeassistant/components/locative/.translations/da.json b/homeassistant/components/locative/.translations/da.json index 8211d52fa5d..3752b23bbe3 100644 --- a/homeassistant/components/locative/.translations/da.json +++ b/homeassistant/components/locative/.translations/da.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "not_internet_accessible": "Dit Home Assistant system skal v\u00e6re tilg\u00e6ngeligt fra internettet for at modtage Geofency meddelelser.", + "not_internet_accessible": "Din Home Assistant-instans skal v\u00e6re tilg\u00e6ngelig fra internettet for at modtage Geofency-meddelelser.", "one_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning" }, "create_entry": { diff --git a/homeassistant/components/lock/.translations/da.json b/homeassistant/components/lock/.translations/da.json index 517c86444fd..e2f2588349c 100644 --- a/homeassistant/components/lock/.translations/da.json +++ b/homeassistant/components/lock/.translations/da.json @@ -2,7 +2,8 @@ "device_automation": { "action_type": { "lock": "L\u00e5s {entity_name}", - "open": "\u00c5ben {entity_name}" + "open": "\u00c5bn {entity_name}", + "unlock": "L\u00e5s {entity_name} op" }, "condition_type": { "is_locked": "{entity_name} er l\u00e5st", diff --git a/homeassistant/components/logi_circle/.translations/da.json b/homeassistant/components/logi_circle/.translations/da.json index 9de8d707ad4..1f2a96fe5b4 100644 --- a/homeassistant/components/logi_circle/.translations/da.json +++ b/homeassistant/components/logi_circle/.translations/da.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_setup": "Du kan kun konfigurere en enkelt Logi Circle konto.", - "external_error": "Der opstod en undtagelse fra et andet flow.", + "already_setup": "Du kan kun konfigurere en enkelt Logi Circle-konto.", + "external_error": "Undtagelse skete fra et andet flow.", "external_setup": "Logi Circle er konfigureret med succes fra et andet flow.", "no_flows": "Du skal konfigurere Logi Circle f\u00f8r du kan godkende med det. [L\u00e6s venligst vejledningen](https://www.home-assistant.io/components/logi_circle/)." }, @@ -24,7 +24,7 @@ "flow_impl": "Udbyder" }, "description": "V\u00e6lg via hvilken godkendelsesudbyder du vil godkende med Logi Circle.", - "title": "Godkendelses udbyder" + "title": "Godkendelsesudbyder" } }, "title": "Logi Circle" diff --git a/homeassistant/components/luftdaten/.translations/da.json b/homeassistant/components/luftdaten/.translations/da.json index d43fc1128ae..3a5f5e7b409 100644 --- a/homeassistant/components/luftdaten/.translations/da.json +++ b/homeassistant/components/luftdaten/.translations/da.json @@ -9,7 +9,7 @@ "user": { "data": { "show_on_map": "Vis p\u00e5 kort", - "station_id": "Luftdaten Sensor ID" + "station_id": "Luftdaten sensor-id" }, "title": "Definer Luftdaten" } diff --git a/homeassistant/components/lutron_caseta/.translations/nl.json b/homeassistant/components/lutron_caseta/.translations/nl.json new file mode 100644 index 00000000000..cfc3c290afe --- /dev/null +++ b/homeassistant/components/lutron_caseta/.translations/nl.json @@ -0,0 +1,5 @@ +{ + "config": { + "title": "Lutron Cas\u00e9ta" + } +} \ No newline at end of file diff --git a/homeassistant/components/mailgun/.translations/da.json b/homeassistant/components/mailgun/.translations/da.json index 475d560bad6..f9152633706 100644 --- a/homeassistant/components/mailgun/.translations/da.json +++ b/homeassistant/components/mailgun/.translations/da.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "not_internet_accessible": "Dit Home Assistant system skal v\u00e6re tilg\u00e6ngeligt fra internettet for at modtage Mailgun meddelelser.", + "not_internet_accessible": "Din Home Assistant-instans skal v\u00e6re tilg\u00e6ngelig fra internettet for at modtage Mailgun-meddelelser", "one_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning." }, "create_entry": { diff --git a/homeassistant/components/media_player/.translations/da.json b/homeassistant/components/media_player/.translations/da.json new file mode 100644 index 00000000000..a53bbed07d0 --- /dev/null +++ b/homeassistant/components/media_player/.translations/da.json @@ -0,0 +1,11 @@ +{ + "device_automation": { + "condition_type": { + "is_idle": "{entity_name} er inaktiv", + "is_off": "{entity_name} er slukket", + "is_on": "{entity_name} er t\u00e6ndt", + "is_paused": "{entity_name} er sat p\u00e5 pause", + "is_playing": "{entity_name} afspiller" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/met/.translations/da.json b/homeassistant/components/met/.translations/da.json index 9d44f1b2b6c..e36a6511aa3 100644 --- a/homeassistant/components/met/.translations/da.json +++ b/homeassistant/components/met/.translations/da.json @@ -12,7 +12,7 @@ "name": "Navn" }, "description": "Meteorologisk institutt", - "title": "Placering" + "title": "Lokalitet" } }, "title": "Met.no" diff --git a/homeassistant/components/mobile_app/.translations/da.json b/homeassistant/components/mobile_app/.translations/da.json index 551e9957254..54dc85e7255 100644 --- a/homeassistant/components/mobile_app/.translations/da.json +++ b/homeassistant/components/mobile_app/.translations/da.json @@ -1,14 +1,14 @@ { "config": { "abort": { - "install_app": "\u00c5bn Mobile App for at konfigurere integrationen med Home Assistant. Se [dokumentationen]({apps_url}) for at f\u00e5 en liste over kompatible apps." + "install_app": "\u00c5bn mobilappen for at konfigurere integrationen med Home Assistant. Se [dokumentationen]({apps_url}) for at f\u00e5 vist en liste over kompatible apps." }, "step": { "confirm": { - "description": "Vil du konfigurere Mobile App komponenten?", - "title": "Mobile App" + "description": "Vil du konfigurere mobilapp-komponenten?", + "title": "Mobilapp" } }, - "title": "Mobile App" + "title": "Mobilapp" } } \ No newline at end of file diff --git a/homeassistant/components/mqtt/.translations/da.json b/homeassistant/components/mqtt/.translations/da.json index ebe5696f514..93ea57d49ea 100644 --- a/homeassistant/components/mqtt/.translations/da.json +++ b/homeassistant/components/mqtt/.translations/da.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning af MQTT" + "single_instance_allowed": "Kun en enkelt konfiguration af MQTT er tilladt." }, "error": { "cannot_connect": "Kunne ikke oprette forbindelse til broker" @@ -10,20 +10,20 @@ "broker": { "data": { "broker": "Broker", - "discovery": "Aktiv\u00e9r opdagelse", + "discovery": "Aktiv\u00e9r automatisk fund", "password": "Adgangskode", "port": "Port", "username": "Brugernavn" }, - "description": "Indtast venligst forbindelsesindstillinger for din MQTT broker.", + "description": "Indtast venligst forbindelsesindstillinger for din MQTT-broker.", "title": "MQTT" }, "hassio_confirm": { "data": { "discovery": "Aktiv\u00e9r opdagelse" }, - "description": "Vil du konfigurere Home Assistant til at oprette forbindelse til MQTT brokeren, der leveres af hass.io add-on {addon}?", - "title": "MQTT Broker via Hass.io add-on" + "description": "Vil du konfigurere Home Assistant til at oprette forbindelse til MQTT-brokeren, der leveres af hass.io-tilf\u00f8jelsen {addon}?", + "title": "MQTT-broker via Hass.io-tilf\u00f8jelse" } }, "title": "MQTT" diff --git a/homeassistant/components/neato/.translations/da.json b/homeassistant/components/neato/.translations/da.json index ca180efa005..736234e92da 100644 --- a/homeassistant/components/neato/.translations/da.json +++ b/homeassistant/components/neato/.translations/da.json @@ -5,7 +5,7 @@ "invalid_credentials": "Ugyldige legitimationsoplysninger" }, "create_entry": { - "default": "Se [Neato-dokumentation] ({docs_url})." + "default": "Se [Neato-dokumentation]({docs_url})." }, "error": { "invalid_credentials": "Ugyldige legitimationsoplysninger", @@ -15,10 +15,11 @@ "user": { "data": { "password": "Adgangskode", - "username": "Brugernavn" + "username": "Brugernavn", + "vendor": "Udbyder" }, - "description": "Se [Neato-dokumentation] ({docs_url}).", - "title": "Neato kontooplysninger" + "description": "Se [Neato-dokumentation]({docs_url}).", + "title": "Neato-kontooplysninger" } }, "title": "Neato" diff --git a/homeassistant/components/nest/.translations/da.json b/homeassistant/components/nest/.translations/da.json index 7dfd1c8b250..39b85754c18 100644 --- a/homeassistant/components/nest/.translations/da.json +++ b/homeassistant/components/nest/.translations/da.json @@ -18,14 +18,14 @@ "flow_impl": "Udbyder" }, "description": "V\u00e6lg hvilken godkendelsesudbyder du vil godkende med Nest.", - "title": "Godkendelses udbyder" + "title": "Godkendelsesudbyder" }, "link": { "data": { "code": "PIN-kode" }, "description": "For at forbinde din Nest-konto, [godkend din konto]({url}). \n\nEfter godkendelse skal du kopiere pin koden nedenfor.", - "title": "Link Nest-konto" + "title": "Forbind Nest-konto" } }, "title": "Nest" diff --git a/homeassistant/components/notion/.translations/da.json b/homeassistant/components/notion/.translations/da.json index 2373920effe..bf17b41d777 100644 --- a/homeassistant/components/notion/.translations/da.json +++ b/homeassistant/components/notion/.translations/da.json @@ -9,7 +9,7 @@ "user": { "data": { "password": "Adgangskode", - "username": "Brugernavn/e-mail adresse" + "username": "Brugernavn/e-mailadresse" }, "title": "Udfyld dine oplysninger" } diff --git a/homeassistant/components/opentherm_gw/.translations/da.json b/homeassistant/components/opentherm_gw/.translations/da.json index 152e38a5bba..743adb715f6 100644 --- a/homeassistant/components/opentherm_gw/.translations/da.json +++ b/homeassistant/components/opentherm_gw/.translations/da.json @@ -3,14 +3,17 @@ "error": { "already_configured": "Gateway allerede konfigureret", "id_exists": "Gateway-id findes allerede", - "serial_error": "Fejl ved tilslutning til enheden" + "serial_error": "Fejl ved tilslutning til enheden", + "timeout": "Forbindelsesfors\u00f8g fik timeout" }, "step": { "init": { "data": { - "device": "Sti eller URL", - "id": "ID", - "name": "Navn" + "device": "Sti eller webadresse", + "floor_temperature": "Gulvklima-temperatur", + "id": "Id", + "name": "Navn", + "precision": "Klimatemperatur-pr\u00e6cision" }, "title": "OpenTherm Gateway" } diff --git a/homeassistant/components/openuv/.translations/da.json b/homeassistant/components/openuv/.translations/da.json index a783c8646e0..eaf2e127026 100644 --- a/homeassistant/components/openuv/.translations/da.json +++ b/homeassistant/components/openuv/.translations/da.json @@ -2,12 +2,12 @@ "config": { "error": { "identifier_exists": "Koordinater er allerede registreret", - "invalid_api_key": "Ugyldig API n\u00f8gle" + "invalid_api_key": "Ugyldig API-n\u00f8gle" }, "step": { "user": { "data": { - "api_key": "OpenUV API N\u00f8gle", + "api_key": "OpenUV API-n\u00f8gle", "elevation": "Elevation", "latitude": "Breddegrad", "longitude": "L\u00e6ngdegrad" diff --git a/homeassistant/components/owntracks/.translations/da.json b/homeassistant/components/owntracks/.translations/da.json index bc1328d57e4..110f60193e6 100644 --- a/homeassistant/components/owntracks/.translations/da.json +++ b/homeassistant/components/owntracks/.translations/da.json @@ -4,7 +4,7 @@ "one_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning" }, "create_entry": { - "default": "\n\nP\u00e5 Android skal du \u00e5bne [OwnTracks applikationen]({android_url}), g\u00e5 til indstillinger -> forbindelse. Skift f\u00f8lgende indstillinger: \n - Tilstand: Privat HTTP\n - V\u00e6rt: {webhook_url}\n - Identifikation:\n - Brugernavn: ` ` \n - Enheds-id: ` ` \n\nP\u00e5 iOS skal du \u00e5bne [OwnTracks applikationen]({ios_url}), tryk p\u00e5 (i) ikonet \u00f8verst til venstre -> indstillinger. Skift f\u00f8lgende indstillinger: \n - Tilstand: HTTP\n - URL: {webhook_url}\n - Aktiver godkendelse \n - Bruger ID: ` ` \n\n {secret}\n \n Se [dokumentationen]({docs_url}) for at f\u00e5 flere oplysninger." + "default": "\n\nP\u00e5 Android skal du \u00e5bne [OwnTracks-appen]({android_url}), g\u00e5 til indstillinger -> forbindelse. Skift f\u00f8lgende indstillinger: \n - Tilstand: Privat HTTP\n - V\u00e6rt: {webhook_url}\n - Identifikation:\n - Brugernavn: ` ` \n - Enheds-id: ` ` \n\nP\u00e5 iOS skal du \u00e5bne [OwnTracks-appen]({ios_url}), tryk p\u00e5 (i) ikonet \u00f8verst til venstre -> indstillinger. Skift f\u00f8lgende indstillinger: \n - Tilstand: HTTP\n - URL: {webhook_url}\n - Aktiver godkendelse \n - Bruger ID: ` ` \n\n {secret}\n \n Se [dokumentationen]({docs_url}) for at f\u00e5 flere oplysninger." }, "step": { "user": { diff --git a/homeassistant/components/plaato/.translations/da.json b/homeassistant/components/plaato/.translations/da.json index f8d59572388..c4dc5ae178d 100644 --- a/homeassistant/components/plaato/.translations/da.json +++ b/homeassistant/components/plaato/.translations/da.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "not_internet_accessible": "Din Home Assistant instans skal v\u00e6re tilg\u00e6ngelig fra internettet for at modtage meddelelser fra Plaato Airlock.", - "one_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning." + "not_internet_accessible": "Din Home Assistant-instans skal v\u00e6re tilg\u00e6ngelig fra internettet for at modtage meddelelser fra Plaato Airlock.", + "one_instance_allowed": "Kun en enkelt instans er n\u00f8dvendig." }, "create_entry": { "default": "For at sende h\u00e6ndelser til Home Assistant skal du konfigurere webhook-funktionen i Plaato Airlock.\n\n Udfyld f\u00f8lgende oplysninger: \n\n - Webadresse: `{webhook_url}`\n - Metode: POST\n \nSe [dokumentationen]({docs_url}) for yderligere oplysninger." diff --git a/homeassistant/components/plex/.translations/da.json b/homeassistant/components/plex/.translations/da.json index 2c3a20ae94f..18dbbb840c3 100644 --- a/homeassistant/components/plex/.translations/da.json +++ b/homeassistant/components/plex/.translations/da.json @@ -4,7 +4,7 @@ "all_configured": "Alle linkede servere er allerede konfigureret", "already_configured": "Denne Plex-server er allerede konfigureret", "already_in_progress": "Plex konfigureres", - "discovery_no_file": "Der blev ikke fundet nogen legacy konfigurationsfil", + "discovery_no_file": "Der blev ikke fundet nogen \u00e6ldre konfigurationsfil", "invalid_import": "Importeret konfiguration er ugyldig", "non-interactive": "Ikke-interaktiv import", "token_request_timeout": "Timeout ved hentning af token", @@ -35,13 +35,13 @@ "title": "V\u00e6lg Plex-server" }, "start_website_auth": { - "description": "Forts\u00e6t for at autorisere p\u00e5 plex.tv.", - "title": "Tilslut Plex-server" + "description": "Forts\u00e6t for at godkende p\u00e5 plex.tv.", + "title": "Forbind Plex-server" }, "user": { "data": { "manual_setup": "Manuel ops\u00e6tning", - "token": "Plex token" + "token": "Plex-token" }, "description": "Indtast et Plex-token til automatisk ops\u00e6tning eller konfigurerer en server manuelt.", "title": "Tilslut Plex-server" @@ -54,7 +54,7 @@ "plex_mp_settings": { "data": { "show_all_controls": "Vis alle kontrolelementer", - "use_episode_art": "Brug episode kunst" + "use_episode_art": "Brug episodekunst" }, "description": "Indstillinger for Plex-medieafspillere" } diff --git a/homeassistant/components/plex/.translations/nl.json b/homeassistant/components/plex/.translations/nl.json index c971ebb4762..515ee8798c7 100644 --- a/homeassistant/components/plex/.translations/nl.json +++ b/homeassistant/components/plex/.translations/nl.json @@ -6,6 +6,7 @@ "already_in_progress": "Plex wordt geconfigureerd", "discovery_no_file": "Geen legacy configuratiebestand gevonden", "invalid_import": "Ge\u00efmporteerde configuratie is ongeldig", + "non-interactive": "Niet-interactieve import", "token_request_timeout": "Time-out verkrijgen van token", "unknown": "Mislukt om onbekende reden" }, diff --git a/homeassistant/components/point/.translations/da.json b/homeassistant/components/point/.translations/da.json index 109bcbe6c37..4b6017ddd01 100644 --- a/homeassistant/components/point/.translations/da.json +++ b/homeassistant/components/point/.translations/da.json @@ -24,7 +24,7 @@ "flow_impl": "Udbyder" }, "description": "V\u00e6lg hvilken godkendelsesudbyder du vil godkende med Point.", - "title": "Godkendelses udbyder" + "title": "Godkendelsesudbyder" } }, "title": "Minut Point" diff --git a/homeassistant/components/ps4/.translations/da.json b/homeassistant/components/ps4/.translations/da.json index e9aca23bb43..cef13db3150 100644 --- a/homeassistant/components/ps4/.translations/da.json +++ b/homeassistant/components/ps4/.translations/da.json @@ -3,19 +3,19 @@ "abort": { "credential_error": "Fejl ved hentning af legitimationsoplysninger.", "devices_configured": "Alle de fundne enheder er allerede konfigureret.", - "no_devices_found": "Ingen PlayStation 4 enheder fundet p\u00e5 netv\u00e6rket.", + "no_devices_found": "Der blev ikke fundet nogen PlayStation 4-enheder p\u00e5 netv\u00e6rket.", "port_987_bind_error": "Kunne ikke binde til port 987. Se [dokumentationen](https://www.home-assistant.io/components/ps4/) for yderligere oplysninger.", "port_997_bind_error": "Kunne ikke binde til port 997. Se [dokumentationen](https://www.home-assistant.io/components/ps4/) for yderligere oplysninger." }, "error": { "credential_timeout": "Tjenesten for legitimationsoplysninger fik timeout. Tryk p\u00e5 send for at genstarte.", "login_failed": "Kunne ikke parre med PlayStation 4. Kontroller PIN er korrekt.", - "no_ipaddress": "Indtast IP adressen p\u00e5 den PlayStation 4 du gerne vil konfigurere.", + "no_ipaddress": "Indtast IP-adressen p\u00e5 den PlayStation 4, du gerne vil konfigurere.", "not_ready": "PlayStation 4 er ikke t\u00e6ndt eller tilsluttet til netv\u00e6rket." }, "step": { "creds": { - "description": "Legitimationsoplysninger er n\u00f8dvendige. Tryk p\u00e5 'Send' og derefter i PS4 2nd Screen App, v\u00e6lg opdater enheder og v\u00e6lg 'Home-Assistant' -enheden for at forts\u00e6tte.", + "description": "Der kr\u00e6ves legitimationsoplysninger. Tryk p\u00e5 'Indsend' og derefter i PS4 2. sk\u00e6rm-app, opdater enheder, og v\u00e6lg 'Home Assistant'-enhed for at forts\u00e6tte.", "title": "PlayStation 4" }, "link": { @@ -25,15 +25,15 @@ "name": "Navn", "region": "Omr\u00e5de" }, - "description": "Indtast dine PlayStation 4 oplysninger. For 'PIN' skal du navigere til 'Indstillinger' p\u00e5 din PlayStation 4 konsol. G\u00e5 derefter til 'Indstillinger for mobilapp-forbindelse' og v\u00e6lg 'Tilf\u00f8j enhed'. Indtast den PIN der vises.", + "description": "Indtast dine PlayStation 4-oplysninger. For 'PIN' skal du navigere til 'Indstillinger' p\u00e5 din PlayStation 4-konsol. Naviger derefter til 'Mobile App Connection Settings' og v\u00e6lg 'Add Device'. Indtast den pinkode, der vises. Se [dokumentation](https://www.home-assistant.io/components/ps4/) for yderligere oplysninger.", "title": "PlayStation 4" }, "mode": { "data": { - "ip_address": "IP adresse (Efterlad tom, hvis du bruger Auto Discovery).", + "ip_address": "IP-adresse (lad det v\u00e6re tomt, hvis du bruger automatisk registrering).", "mode": "Konfigurationstilstand" }, - "description": "V\u00e6lg tilstand til konfiguration. IP-adressefeltet kan st\u00e5 tomt hvis du v\u00e6lger Auto Discovery, da enheder automatisk bliver fundet.", + "description": "V\u00e6lg tilstand for konfiguration. IP-adressefeltet kan v\u00e6re tomt, hvis du v\u00e6lger automatisk registrering, da enheder automatisk bliver fundet.", "title": "PlayStation 4" } }, diff --git a/homeassistant/components/rainmachine/.translations/da.json b/homeassistant/components/rainmachine/.translations/da.json index 61d29894fe2..34f4fff4ed0 100644 --- a/homeassistant/components/rainmachine/.translations/da.json +++ b/homeassistant/components/rainmachine/.translations/da.json @@ -8,7 +8,7 @@ "user": { "data": { "ip_address": "V\u00e6rtsnavn eller IP-adresse", - "password": "Password", + "password": "Adgangskode", "port": "Port" }, "title": "Udfyld dine oplysninger" diff --git a/homeassistant/components/sensor/.translations/da.json b/homeassistant/components/sensor/.translations/da.json index df9b9935dc1..3febed8ac09 100644 --- a/homeassistant/components/sensor/.translations/da.json +++ b/homeassistant/components/sensor/.translations/da.json @@ -1,26 +1,26 @@ { "device_automation": { "condition_type": { - "is_battery_level": "{entity_name} batteriniveau", - "is_humidity": "{entity_name} fugtighed", - "is_illuminance": "{entity_name} belysningsstyrke", - "is_power": "{entity_name} str\u00f8m", - "is_pressure": "{entity_name} tryk", - "is_signal_strength": "{entity_name} signalstyrke", - "is_temperature": "{entity_name} temperatur", - "is_timestamp": "{entity_name} tidsstempel", - "is_value": "{entity_name} v\u00e6rdi" + "is_battery_level": "Aktuelt {entity_name}-batteriniveau", + "is_humidity": "Aktuel {entity_name}-luftfugtighed", + "is_illuminance": "Aktuel {entity_name}-lysstyrke", + "is_power": "Aktuel {entity_name}-str\u00f8m", + "is_pressure": "Aktuelt {entity_name}-lufttryk", + "is_signal_strength": "Aktuel {entity_name}-signalstyrke", + "is_temperature": "Aktuel {entity_name}-temperatur", + "is_timestamp": "Aktuel {entity_name}-tidsstempel", + "is_value": "Aktuel {entity_name}-v\u00e6rdi" }, "trigger_type": { - "battery_level": "{entity_name} batteriniveau", - "humidity": "{entity_name} fugtighed", - "illuminance": "{entity_name} belysningsstyrke", - "power": "{entity_name} str\u00f8m", - "pressure": "{entity_name} tryk", - "signal_strength": "{entity_name} signalstyrke", - "temperature": "{entity_name} temperatur", - "timestamp": "{entity_name} tidsstempel", - "value": "{entity_name} v\u00e6rdi" + "battery_level": "{entity_name} batteriniveau \u00e6ndres", + "humidity": "{entity_name} luftfugtighed \u00e6ndres", + "illuminance": "{entity_name} lysstyrke \u00e6ndres", + "power": "{entity_name} str\u00f8m \u00e6ndres", + "pressure": "{entity_name} lufttryk \u00e6ndres", + "signal_strength": "{entity_name} signalstyrke \u00e6ndres", + "temperature": "{entity_name} temperatur \u00e6ndres", + "timestamp": "{entity_name} tidsstempel \u00e6ndres", + "value": "{entity_name} v\u00e6rdi \u00e6ndres" } } } \ No newline at end of file diff --git a/homeassistant/components/simplisafe/.translations/da.json b/homeassistant/components/simplisafe/.translations/da.json index 3ec3d7b456c..0d3970eeba5 100644 --- a/homeassistant/components/simplisafe/.translations/da.json +++ b/homeassistant/components/simplisafe/.translations/da.json @@ -9,7 +9,7 @@ "data": { "code": "Kode (til Home Assistant)", "password": "Adgangskode", - "username": "Email adresse" + "username": "Emailadresse" }, "title": "Udfyld dine oplysninger" } diff --git a/homeassistant/components/smartthings/.translations/da.json b/homeassistant/components/smartthings/.translations/da.json index 18412069394..04fe2171f39 100644 --- a/homeassistant/components/smartthings/.translations/da.json +++ b/homeassistant/components/smartthings/.translations/da.json @@ -1,9 +1,9 @@ { "config": { "error": { - "app_not_installed": "S\u00f8rg for at du har installeret og autoriseret Home Assistant SmartApp og pr\u00f8v igen.", + "app_not_installed": "S\u00f8rg for, at du har installeret og godkendt Home Assistant SmartApp, og pr\u00f8v igen.", "app_setup_error": "SmartApp kunne ikke konfigureres. Pr\u00f8v igen.", - "base_url_not_https": "`base_url` til` http` komponenten skal konfigureres og starte med `https://`.", + "base_url_not_https": "`base_url` til `http`-komponenten skal konfigureres og starte med `https://`.", "token_already_setup": "Token er allerede konfigureret.", "token_forbidden": "Adgangstoken er ikke indenfor OAuth", "token_invalid_format": "Adgangstoken skal v\u00e6re i UID/GUID format", diff --git a/homeassistant/components/smhi/.translations/da.json b/homeassistant/components/smhi/.translations/da.json index b43fef7ec45..52c4f54ebd7 100644 --- a/homeassistant/components/smhi/.translations/da.json +++ b/homeassistant/components/smhi/.translations/da.json @@ -2,7 +2,7 @@ "config": { "error": { "name_exists": "Navnet findes allerede", - "wrong_location": "Placering kun i Sverige" + "wrong_location": "Lokalitet kun i Sverige" }, "step": { "user": { @@ -11,7 +11,7 @@ "longitude": "L\u00e6ngdegrad", "name": "Navn" }, - "title": "Placering i Sverige" + "title": "Lokalitet i Sverige" } }, "title": "Svensk vejr service (SMHI)" diff --git a/homeassistant/components/soma/.translations/nl.json b/homeassistant/components/soma/.translations/nl.json index c1188b0ac63..058f7222666 100644 --- a/homeassistant/components/soma/.translations/nl.json +++ b/homeassistant/components/soma/.translations/nl.json @@ -3,7 +3,9 @@ "abort": { "already_setup": "U kunt slechts \u00e9\u00e9n Soma-account configureren.", "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", - "missing_configuration": "De Soma-component is niet geconfigureerd. Gelieve de documentatie te volgen." + "connection_error": "Kan geen verbinding maken met SOMA Connect.", + "missing_configuration": "De Soma-component is niet geconfigureerd. Gelieve de documentatie te volgen.", + "result_error": "SOMA Connect reageerde met een foutstatus." }, "create_entry": { "default": "Succesvol geverifieerd met Soma." diff --git a/homeassistant/components/sonos/.translations/da.json b/homeassistant/components/sonos/.translations/da.json index c303bca0aa8..c4b1a555245 100644 --- a/homeassistant/components/sonos/.translations/da.json +++ b/homeassistant/components/sonos/.translations/da.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "no_devices_found": "Ingen Sonos-enheder kunne findes p\u00e5 netv\u00e6rket.", + "no_devices_found": "Der blev ikke fundet nogen Sonos-enheder p\u00e5 netv\u00e6rket.", "single_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning af Sonos" }, "step": { diff --git a/homeassistant/components/starline/.translations/nl.json b/homeassistant/components/starline/.translations/nl.json new file mode 100644 index 00000000000..d7963446865 --- /dev/null +++ b/homeassistant/components/starline/.translations/nl.json @@ -0,0 +1,42 @@ +{ + "config": { + "error": { + "error_auth_app": "Onjuiste applicatie-ID of geheim", + "error_auth_mfa": "Ongeldige code", + "error_auth_user": "Ongeldige gebruikersnaam of wachtwoord" + }, + "step": { + "auth_app": { + "data": { + "app_id": "Toepassings-ID ", + "app_secret": "Geheime code" + }, + "description": "Toepassings-ID en de geheime code van StarLine developer account", + "title": "Inloggegevens van de applicatie" + }, + "auth_captcha": { + "data": { + "captcha_code": "Code van afbeelding" + }, + "description": "{captcha_img}", + "title": "Captcha" + }, + "auth_mfa": { + "data": { + "mfa_code": "SMS code" + }, + "description": "Voer de code in die wordt verzonden naar telefoon {phone_number}", + "title": "Tweestapsverificatie" + }, + "auth_user": { + "data": { + "password": "Wachtwoord", + "username": "Gebruikersnaam" + }, + "description": "StarLine-account e-mailadres en wachtwoord", + "title": "Gebruikersgegevens" + } + }, + "title": "StarLine" + } +} \ No newline at end of file diff --git a/homeassistant/components/switch/.translations/da.json b/homeassistant/components/switch/.translations/da.json new file mode 100644 index 00000000000..2514a56a010 --- /dev/null +++ b/homeassistant/components/switch/.translations/da.json @@ -0,0 +1,19 @@ +{ + "device_automation": { + "action_type": { + "toggle": "Skift {entity_name}", + "turn_off": "Sluk {entity_name}", + "turn_on": "T\u00e6nd for {entity_name}" + }, + "condition_type": { + "is_off": "{entity_name} er fra", + "is_on": "{entity_name} er til", + "turn_off": "{entity_name} slukket", + "turn_on": "{entity_name} t\u00e6ndt" + }, + "trigger_type": { + "turned_off": "{entity_name} slukkede", + "turned_on": "{entity_name} t\u00e6ndte" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tesla/.translations/nl.json b/homeassistant/components/tesla/.translations/nl.json new file mode 100644 index 00000000000..5f3e83dd248 --- /dev/null +++ b/homeassistant/components/tesla/.translations/nl.json @@ -0,0 +1,30 @@ +{ + "config": { + "error": { + "connection_error": "Fout bij verbinden; controleer het netwerk en probeer het opnieuw", + "identifier_exists": "E-mail al geregistreerd", + "invalid_credentials": "Ongeldige inloggegevens", + "unknown_error": "Onbekende fout, meldt u log info" + }, + "step": { + "user": { + "data": { + "password": "Wachtwoord", + "username": "E-mailadres" + }, + "description": "Vul alstublieft uw gegevens in.", + "title": "Tesla - Configuratie" + } + }, + "title": "Tesla" + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Seconden tussen scans" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tesla/.translations/no.json b/homeassistant/components/tesla/.translations/no.json new file mode 100644 index 00000000000..0d73908f417 --- /dev/null +++ b/homeassistant/components/tesla/.translations/no.json @@ -0,0 +1,30 @@ +{ + "config": { + "error": { + "connection_error": "Feil ved tilkobling; sjekk nettverket og pr\u00f8v p\u00e5 nytt", + "identifier_exists": "E-post er allerede registrert", + "invalid_credentials": "Ugyldig brukerinformasjon", + "unknown_error": "Ukjent feil, Vennligst rapporter informasjon fra Loggen" + }, + "step": { + "user": { + "data": { + "password": "Passord", + "username": "E-postadresse" + }, + "description": "Vennligst skriv inn informasjonen din.", + "title": "Tesla - Konfigurasjon" + } + }, + "title": "Tesla" + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Sekunder mellom skanninger" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/toon/.translations/da.json b/homeassistant/components/toon/.translations/da.json index 9200f80add0..e4f73bc7c6b 100644 --- a/homeassistant/components/toon/.translations/da.json +++ b/homeassistant/components/toon/.translations/da.json @@ -3,9 +3,9 @@ "abort": { "client_id": "Klient-id'et fra konfigurationen er ugyldigt.", "client_secret": "Klientens hemmelighed fra konfigurationen er ugyldig.", - "no_agreements": "Denne konto har ingen Toon sk\u00e6rme.", + "no_agreements": "Denne konto har ingen Toon-sk\u00e6rme.", "no_app": "Du skal konfigurere Toon f\u00f8r du kan godkende med det. [L\u00e6s venligst vejledningen](https://www.home-assistant.io/components/toon/).", - "unknown_auth_fail": "Der opstod en uventet fejl under autentificering." + "unknown_auth_fail": "Der opstod en uventet fejl under godkendelse." }, "error": { "credentials": "De angivne legitimationsoplysninger er ugyldige.", @@ -18,8 +18,8 @@ "tenant": "Tenant", "username": "Brugernavn" }, - "description": "Godkend med din Eneco Toon konto (ikke udviklerkontoen).", - "title": "Link din Toon konto" + "description": "Godkend med din Eneco Toon-konto (ikke udviklerkontoen).", + "title": "Forbind din Toon-konto" }, "display": { "data": { diff --git a/homeassistant/components/tplink/.translations/da.json b/homeassistant/components/tplink/.translations/da.json index cdd953ff5c3..5225a89fb95 100644 --- a/homeassistant/components/tplink/.translations/da.json +++ b/homeassistant/components/tplink/.translations/da.json @@ -6,7 +6,7 @@ }, "step": { "confirm": { - "description": "Vil du konfigurere TP-Link smart devices?", + "description": "Vil du konfigurere TP-Link-smartenheder?", "title": "TP-Link Smart Home" } }, diff --git a/homeassistant/components/traccar/.translations/da.json b/homeassistant/components/traccar/.translations/da.json index 2b0ec0003d6..b1ab350c905 100644 --- a/homeassistant/components/traccar/.translations/da.json +++ b/homeassistant/components/traccar/.translations/da.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "not_internet_accessible": "Dit Home Assistant system skal v\u00e6re tilg\u00e6ngeligt fra internettet for at modtage Traccar meddelelser.", - "one_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning." + "not_internet_accessible": "Din Home Assistant-instans skal v\u00e6re tilg\u00e6ngelig fra internettet for at modtage Traccar-meddelelser.", + "one_instance_allowed": "Kun en enkelt instans er n\u00f8dvendig." }, "create_entry": { "default": "For at sende h\u00e6ndelser til Home Assistant skal du konfigurere webhook-funktionen i Traccar.\n\nBrug f\u00f8lgende webadresse: `{webhook_url}`\n \nSe [dokumentationen]({docs_url}) for yderligere oplysninger." diff --git a/homeassistant/components/traccar/.translations/tr.json b/homeassistant/components/traccar/.translations/tr.json new file mode 100644 index 00000000000..22944e1c4cc --- /dev/null +++ b/homeassistant/components/traccar/.translations/tr.json @@ -0,0 +1,10 @@ +{ + "config": { + "step": { + "user": { + "title": "Traccar'\u0131 kur" + } + }, + "title": "Traccar" + } +} \ No newline at end of file diff --git a/homeassistant/components/transmission/.translations/da.json b/homeassistant/components/transmission/.translations/da.json index b14fca00c2c..e84ec938ee2 100644 --- a/homeassistant/components/transmission/.translations/da.json +++ b/homeassistant/components/transmission/.translations/da.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "V\u00e6rten er allerede konfigureret.", - "one_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning." + "one_instance_allowed": "Kun en enkelt instans er n\u00f8dvendig." }, "error": { "cannot_connect": "Kunne ikke oprette forbindelse til v\u00e6rt", @@ -14,7 +14,7 @@ "data": { "scan_interval": "Opdateringsfrekvens" }, - "title": "Konfigurer indstillinger" + "title": "Konfigurationsmuligheder" }, "user": { "data": { @@ -24,7 +24,7 @@ "port": "Port", "username": "Brugernavn" }, - "title": "Konfigurer Transmission klient" + "title": "Konfigurer Transmission-klient" } }, "title": "Transmission" diff --git a/homeassistant/components/twentemilieu/.translations/tr.json b/homeassistant/components/twentemilieu/.translations/tr.json new file mode 100644 index 00000000000..ebe13a37003 --- /dev/null +++ b/homeassistant/components/twentemilieu/.translations/tr.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "address_exists": "Adres zaten kurulmu\u015f." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/twilio/.translations/da.json b/homeassistant/components/twilio/.translations/da.json index 0bb40aae7f2..d5f40d56446 100644 --- a/homeassistant/components/twilio/.translations/da.json +++ b/homeassistant/components/twilio/.translations/da.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "not_internet_accessible": "Dit Home Assistant system skal v\u00e6re tilg\u00e6ngeligt fra internettet for at modtage Twilio meddelelser.", + "not_internet_accessible": "Din Home Assistant-instans skal v\u00e6re tilg\u00e6ngelig fra internettet for at modtage Twilio-meddelelser.", "one_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning." }, "create_entry": { diff --git a/homeassistant/components/unifi/.translations/da.json b/homeassistant/components/unifi/.translations/da.json index 0d0315e49c7..46a94cc4047 100644 --- a/homeassistant/components/unifi/.translations/da.json +++ b/homeassistant/components/unifi/.translations/da.json @@ -33,9 +33,15 @@ "track_wired_clients": "Inkluder kablede netv\u00e6rksklienter" } }, + "init": { + "data": { + "one": "EN", + "other": "ANDEN" + } + }, "statistics_sensors": { "data": { - "allow_bandwidth_sensors": "Opret b\u00e5ndbredde sensorer for netv\u00e6rksklienter" + "allow_bandwidth_sensors": "Opret b\u00e5ndbredde-forbrugssensorer for netv\u00e6rksklienter" } } } diff --git a/homeassistant/components/upnp/.translations/da.json b/homeassistant/components/upnp/.translations/da.json index 1d0097c2f1f..c41741b8635 100644 --- a/homeassistant/components/upnp/.translations/da.json +++ b/homeassistant/components/upnp/.translations/da.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "UPnP/IGD er allerede konfigureret", "incomplete_device": "Ignorerer ufuldst\u00e6ndig UPnP-enhed", - "no_devices_discovered": "Ingen UPnP/IGD enheder fundet.", + "no_devices_discovered": "Ingen UPnP/IGD-enheder fundet.", "no_devices_found": "Ingen UPnP/IGD enheder kunne findes p\u00e5 netv\u00e6rket.", "no_sensors_or_port_mapping": "Aktiv\u00e9r enten sensorer eller porttilknytning", "single_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning af UPnP/IGD." @@ -23,7 +23,7 @@ "user": { "data": { "enable_port_mapping": "Aktiv\u00e9r porttilknytning til Home Assistent", - "enable_sensors": "Tilf\u00f8j trafik sensorer", + "enable_sensors": "Tilf\u00f8j trafiksensorer", "igd": "UPnP/IGD" }, "title": "Konfigurationsindstillinger for UPnP/IGD" diff --git a/homeassistant/components/vacuum/.translations/nl.json b/homeassistant/components/vacuum/.translations/nl.json index 3e49c926d2d..8ef0588796c 100644 --- a/homeassistant/components/vacuum/.translations/nl.json +++ b/homeassistant/components/vacuum/.translations/nl.json @@ -1,10 +1,16 @@ { "device_automation": { + "action_type": { + "clean": "Laat {entity_name} schoonmaken", + "dock": "Laat {entity_name} terugkeren naar het basisstation" + }, "condition_type": { - "is_cleaning": "{entity_name} is aan het schoonmaken" + "is_cleaning": "{entity_name} is aan het schoonmaken", + "is_docked": "{entity_name} is bij basisstation" }, "trigger_type": { - "cleaning": "{entity_name} begon met schoonmaken" + "cleaning": "{entity_name} begon met schoonmaken", + "docked": "{entity_name} is bij basisstation" } } } \ No newline at end of file diff --git a/homeassistant/components/vesync/.translations/da.json b/homeassistant/components/vesync/.translations/da.json index 43e56328f99..f2be5792f33 100644 --- a/homeassistant/components/vesync/.translations/da.json +++ b/homeassistant/components/vesync/.translations/da.json @@ -10,7 +10,7 @@ "user": { "data": { "password": "Adgangskode", - "username": "Email adresse" + "username": "Emailadresse" }, "title": "Indtast brugernavn og adgangskode" } diff --git a/homeassistant/components/wemo/.translations/da.json b/homeassistant/components/wemo/.translations/da.json index c69547c66ab..1da4d407849 100644 --- a/homeassistant/components/wemo/.translations/da.json +++ b/homeassistant/components/wemo/.translations/da.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Ingen Wemo enheder fundet p\u00e5 netv\u00e6rket.", - "single_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning af Wemo." + "no_devices_found": "Der blev ikke fundet nogen Wemo-enheder p\u00e5 netv\u00e6rket.", + "single_instance_allowed": "Kun en enkelt konfiguration af Wemo er mulig." }, "step": { "confirm": { diff --git a/homeassistant/components/withings/.translations/da.json b/homeassistant/components/withings/.translations/da.json index d2dddbbd204..e4599fe8ec2 100644 --- a/homeassistant/components/withings/.translations/da.json +++ b/homeassistant/components/withings/.translations/da.json @@ -1,9 +1,19 @@ { "config": { + "abort": { + "no_flows": "Du skal konfigurere Withings, f\u00f8r du kan godkende med den. L\u00e6s venligst dokumentationen." + }, "create_entry": { "default": "Godkendt med Withings for den valgte profil." }, "step": { + "profile": { + "data": { + "profile": "Profile" + }, + "description": "Hvilken profil har du valgt p\u00e5 Withings hjemmeside? Det er vigtigt, at profilerne matcher, ellers vil data blive m\u00e6rket forkert.", + "title": "Brugerprofil." + }, "user": { "data": { "profile": "Profil" diff --git a/homeassistant/components/wled/.translations/nl.json b/homeassistant/components/wled/.translations/nl.json index 1bf70b7a095..266f74ce6c2 100644 --- a/homeassistant/components/wled/.translations/nl.json +++ b/homeassistant/components/wled/.translations/nl.json @@ -13,6 +13,7 @@ "data": { "host": "Hostnaam of IP-adres" }, + "description": "Stel uw WLED in op integratie met Home Assistant.", "title": "Koppel je WLED" }, "zeroconf_confirm": { diff --git a/homeassistant/components/wwlln/.translations/da.json b/homeassistant/components/wwlln/.translations/da.json index 7d9a676e163..5d4f4c40b5d 100644 --- a/homeassistant/components/wwlln/.translations/da.json +++ b/homeassistant/components/wwlln/.translations/da.json @@ -1,16 +1,16 @@ { "config": { "error": { - "identifier_exists": "Placering er allerede registreret" + "identifier_exists": "Lokalitet er allerede registreret" }, "step": { "user": { "data": { "latitude": "Breddegrad", "longitude": "L\u00e6ngdegrad", - "radius": "Radius (ved hj\u00e6lp af dit basis enhedssystem)" + "radius": "Radius (ved hj\u00e6lp af dit basisenhedssystem)" }, - "title": "Udfyld dine placeringsoplysninger." + "title": "Udfyld dine lokalitetsoplysninger." } }, "title": "World Wide Lightning Location Network (WWLLN)" diff --git a/homeassistant/components/zha/.translations/da.json b/homeassistant/components/zha/.translations/da.json index 39f254ac9af..908d8113b2e 100644 --- a/homeassistant/components/zha/.translations/da.json +++ b/homeassistant/components/zha/.translations/da.json @@ -9,8 +9,8 @@ "step": { "user": { "data": { - "radio_type": "Radio type", - "usb_path": "Sti til USB enhed" + "radio_type": "Radio-type", + "usb_path": "Sti til USB-enhed" }, "title": "ZHA" } @@ -33,12 +33,35 @@ "close": "Luk", "dim_down": "D\u00e6mp ned", "dim_up": "D\u00e6mp op", + "face_1": "med ansigt 1 aktiveret", + "face_2": "med ansigt 2 aktiveret", + "face_3": "med ansigt 3 aktiveret", + "face_4": "med ansigt 4 aktiveret", + "face_5": "med ansigt 5 aktiveret", + "face_6": "med ansigt 6 aktiveret", + "face_any": "Med ethvert/specificeret ansigt(er) aktiveret", "left": "Venstre", "open": "\u00c5ben", - "right": "H\u00f8jre" + "right": "H\u00f8jre", + "turn_off": "Sluk", + "turn_on": "T\u00e6nd" }, "trigger_type": { - "device_shaken": "Enhed rystet" + "device_dropped": "Enhed faldt", + "device_flipped": "Enheden blev vendt \"{subtype}\"", + "device_knocked": "Enhed banket med \"{subtype}\"", + "device_rotated": "Enhed roteret \"{subtype}\"", + "device_shaken": "Enhed rystet", + "device_slid": "Enheden gled \"{subtype}\"", + "device_tilted": "Enheden vippes", + "remote_button_double_press": "\"{subtype}\"-knappen er dobbeltklikket", + "remote_button_long_press": "\"{subtype}\"-knappen trykket p\u00e5 konstant", + "remote_button_long_release": "\"{subtype}\"-knappen frigivet efter langt tryk", + "remote_button_quadruple_press": "\"{subtype}\"-knappen firedobbelt-klikket", + "remote_button_quintuple_press": "\"{subtype}\"-knappen femdobbelt-klikket", + "remote_button_short_press": "\"{subtype}\"-knappen trykket p\u00e5", + "remote_button_short_release": "\"{subtype}\"-knappen frigivet", + "remote_button_triple_press": "\"{subtype}\"-knappen tredobbeltklikkes" } } } \ No newline at end of file diff --git a/homeassistant/components/zwave/.translations/da.json b/homeassistant/components/zwave/.translations/da.json index e9049026a4f..25eee9b3d91 100644 --- a/homeassistant/components/zwave/.translations/da.json +++ b/homeassistant/components/zwave/.translations/da.json @@ -2,16 +2,16 @@ "config": { "abort": { "already_configured": "Z-Wave er allerede konfigureret", - "one_instance_only": "Komponenten underst\u00f8tter kun \u00e9n Z-Wave forekomst" + "one_instance_only": "Komponenten underst\u00f8tter kun \u00e9n Z-Wave-instans" }, "error": { - "option_error": "Z-Wave validering mislykkedes. Er stien til USB enhed korrekt?" + "option_error": "Z-Wave-validering mislykkedes. Er stien til USB-enhed korrekt?" }, "step": { "user": { "data": { "network_key": "Netv\u00e6rksn\u00f8gle (efterlad blank for autogenerering)", - "usb_path": "Sti til USB enhed" + "usb_path": "Sti til USB-enhed" }, "description": "Se https://www.home-assistant.io/docs/z-wave/installation/ for oplysninger om konfigurationsvariabler", "title": "Ops\u00e6t Z-Wave" From 8a22a3835334f62d62c51d929788111c5d5402e4 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Mon, 30 Dec 2019 07:36:01 +0000 Subject: [PATCH 428/677] Accept homekit_controller pairing codes both with and without dashes (#30273) * Handle MalformedPinError from homekit_python * Handle both formats of pin codes --- .../homekit_controller/config_flow.py | 22 ++++++++ .../homekit_controller/test_config_flow.py | 55 ++++++++++++++++--- 2 files changed, 69 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/homekit_controller/config_flow.py b/homeassistant/components/homekit_controller/config_flow.py index 3f230d923c7..507a5cbb70a 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -2,6 +2,7 @@ import json import logging import os +import re import homekit from homekit.controller.ip_implementation import IpPairing @@ -17,6 +18,8 @@ HOMEKIT_IGNORE = ["Home Assistant Bridge"] HOMEKIT_DIR = ".homekit" PAIRING_FILE = "pairing.json" +PIN_FORMAT = re.compile(r"^(\d{3})-{0,1}(\d{2})-{0,1}(\d{3})$") + _LOGGER = logging.getLogger(__name__) @@ -59,6 +62,20 @@ def find_existing_host(hass, serial): return entry +def ensure_pin_format(pin): + """ + Ensure a pin code is correctly formatted. + + Ensures a pin code is in the format 111-11-111. Handles codes with and without dashes. + + If incorrect code is entered, an exception is raised. + """ + match = PIN_FORMAT.search(pin) + if not match: + raise homekit.exceptions.MalformedPinError(f"Invalid PIN code f{pin}") + return "{}-{}-{}".format(*match.groups()) + + @config_entries.HANDLERS.register(DOMAIN) class HomekitControllerFlowHandler(config_entries.ConfigFlow): """Handle a HomeKit config flow.""" @@ -277,6 +294,8 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow): if pair_info: code = pair_info["pairing_code"] try: + code = ensure_pin_format(code) + await self.hass.async_add_executor_job(self.finish_pairing, code) pairing = self.controller.pairings.get(self.hkid) @@ -284,6 +303,9 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow): return await self._entry_from_accessory(pairing) errors["pairing_code"] = "unable_to_pair" + except homekit.exceptions.MalformedPinError: + # Library claimed pin was invalid before even making an API call + errors["pairing_code"] = "authentication_error" except homekit.AuthenticationError: # PairSetup M4 - SRP proof failed # PairSetup M6 - Ed25519 signature verification failed diff --git a/tests/components/homekit_controller/test_config_flow.py b/tests/components/homekit_controller/test_config_flow.py index 56c1c30e8f3..2a7f36ba470 100644 --- a/tests/components/homekit_controller/test_config_flow.py +++ b/tests/components/homekit_controller/test_config_flow.py @@ -27,6 +27,7 @@ PAIRING_START_ABORT_ERRORS = [ ] PAIRING_FINISH_FORM_ERRORS = [ + (homekit.exceptions.MalformedPinError, "authentication_error"), (homekit.MaxPeersError, "max_peers_error"), (homekit.AuthenticationError, "authentication_error"), (homekit.UnknownError, "unknown_error"), @@ -37,6 +38,27 @@ PAIRING_FINISH_ABORT_ERRORS = [ (homekit.AccessoryNotFoundError, "accessory_not_found_error") ] +INVALID_PAIRING_CODES = [ + "aaa-aa-aaa", + "aaa-11-aaa", + "111-aa-aaa", + "aaa-aa-111", + "1111-1-111", + "a111-11-111", + " 111-11-111", + "111-11-111 ", + "111-11-111a", + "1111111", +] + + +VALID_PAIRING_CODES = [ + "111-11-111", + "123-45-678", + "11111111", + "98765432", +] + def _setup_flow_handler(hass): flow = config_flow.HomekitControllerFlowHandler() @@ -56,6 +78,23 @@ async def _setup_flow_zeroconf(hass, discovery_info): return result +@pytest.mark.parametrize("pairing_code", INVALID_PAIRING_CODES) +def test_invalid_pairing_codes(pairing_code): + """Test ensure_pin_format raises for an invalid pin code.""" + with pytest.raises(homekit.exceptions.MalformedPinError): + config_flow.ensure_pin_format(pairing_code) + + +@pytest.mark.parametrize("pairing_code", VALID_PAIRING_CODES) +def test_valid_pairing_codes(pairing_code): + """Test ensure_pin_format corrects format for a valid pin in an alternative format.""" + valid_pin = config_flow.ensure_pin_format(pairing_code).split("-") + assert len(valid_pin) == 3 + assert len(valid_pin[0]) == 3 + assert len(valid_pin[1]) == 2 + assert len(valid_pin[2]) == 3 + + async def test_discovery_works(hass): """Test a device being discovered.""" discovery_info = { @@ -99,7 +138,7 @@ async def test_discovery_works(hass): # Pairing doesn't error error and pairing results flow.controller.pairings = {"00:00:00:00:00:00": pairing} - result = await flow.async_step_pair({"pairing_code": "111-22-33"}) + result = await flow.async_step_pair({"pairing_code": "111-22-333"}) assert result["type"] == "create_entry" assert result["title"] == "Koogeek-LS1-20833F" assert result["data"] == pairing.pairing_data @@ -147,7 +186,7 @@ async def test_discovery_works_upper_case(hass): ] flow.controller.pairings = {"00:00:00:00:00:00": pairing} - result = await flow.async_step_pair({"pairing_code": "111-22-33"}) + result = await flow.async_step_pair({"pairing_code": "111-22-333"}) assert result["type"] == "create_entry" assert result["title"] == "Koogeek-LS1-20833F" assert result["data"] == pairing.pairing_data @@ -196,7 +235,7 @@ async def test_discovery_works_missing_csharp(hass): flow.controller.pairings = {"00:00:00:00:00:00": pairing} - result = await flow.async_step_pair({"pairing_code": "111-22-33"}) + result = await flow.async_step_pair({"pairing_code": "111-22-333"}) assert result["type"] == "create_entry" assert result["title"] == "Koogeek-LS1-20833F" assert result["data"] == pairing.pairing_data @@ -379,7 +418,7 @@ async def test_pair_unable_to_pair(hass): assert flow.controller.start_pairing.call_count == 1 # Pairing doesn't error but no pairing object is generated - result = await flow.async_step_pair({"pairing_code": "111-22-33"}) + result = await flow.async_step_pair({"pairing_code": "111-22-333"}) assert result["type"] == "form" assert result["errors"]["pairing_code"] == "unable_to_pair" @@ -486,7 +525,7 @@ async def test_pair_abort_errors_on_finish(hass, exception, expected): # User submits code - pairing fails but can be retried flow.finish_pairing.side_effect = exception("error") - result = await flow.async_step_pair({"pairing_code": "111-22-33"}) + result = await flow.async_step_pair({"pairing_code": "111-22-333"}) assert result["type"] == "abort" assert result["reason"] == expected assert flow.context == { @@ -526,7 +565,7 @@ async def test_pair_form_errors_on_finish(hass, exception, expected): # User submits code - pairing fails but can be retried flow.finish_pairing.side_effect = exception("error") - result = await flow.async_step_pair({"pairing_code": "111-22-33"}) + result = await flow.async_step_pair({"pairing_code": "111-22-333"}) assert result["type"] == "form" assert result["errors"]["pairing_code"] == expected assert flow.context == { @@ -639,7 +678,7 @@ async def test_user_works(hass): assert result["type"] == "form" assert result["step_id"] == "pair" - result = await flow.async_step_pair({"pairing_code": "111-22-33"}) + result = await flow.async_step_pair({"pairing_code": "111-22-333"}) assert result["type"] == "create_entry" assert result["title"] == "Koogeek-LS1-20833F" assert result["data"] == pairing.pairing_data @@ -888,7 +927,7 @@ async def test_unignore_works(hass): assert flow.controller.start_pairing.call_count == 1 # Pairing finalized - result = await flow.async_step_pair({"pairing_code": "111-22-33"}) + result = await flow.async_step_pair({"pairing_code": "111-22-333"}) assert result["type"] == "create_entry" assert result["title"] == "Koogeek-LS1-20833F" assert result["data"] == pairing.pairing_data From fccb13b76257cc894fcbd219cdba0885f64579d3 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Mon, 30 Dec 2019 08:05:49 +0000 Subject: [PATCH 429/677] Add homekit_controller service.sensor.smoke (#30269) --- .../homekit_controller/binary_sensor.py | 37 ++++++++++++++++++- .../components/homekit_controller/const.py | 1 + .../homekit_controller/test_binary_sensor.py | 27 ++++++++++++++ 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/homekit_controller/binary_sensor.py b/homeassistant/components/homekit_controller/binary_sensor.py index 1e1c8ef5d44..2998ce18641 100644 --- a/homeassistant/components/homekit_controller/binary_sensor.py +++ b/homeassistant/components/homekit_controller/binary_sensor.py @@ -3,7 +3,10 @@ import logging from homekit.model.characteristics import CharacteristicsTypes -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_SMOKE, + BinarySensorDevice, +) from . import KNOWN_DEVICES, HomeKitEntity @@ -57,7 +60,37 @@ class HomeKitContactSensor(HomeKitEntity, BinarySensorDevice): return self._state == 1 -ENTITY_TYPES = {"motion": HomeKitMotionSensor, "contact": HomeKitContactSensor} +class HomeKitSmokeSensor(HomeKitEntity, BinarySensorDevice): + """Representation of a Homekit smoke sensor.""" + + def __init__(self, *args): + """Initialise the entity.""" + super().__init__(*args) + self._state = None + + @property + def device_class(self) -> str: + """Return the class of this sensor.""" + return DEVICE_CLASS_SMOKE + + def get_characteristic_types(self): + """Define the homekit characteristics the entity is tracking.""" + return [CharacteristicsTypes.SMOKE_DETECTED] + + def _update_smoke_detected(self, value): + self._state = value + + @property + def is_on(self): + """Return true if smoke is currently detected.""" + return self._state == 1 + + +ENTITY_TYPES = { + "motion": HomeKitMotionSensor, + "contact": HomeKitContactSensor, + "smoke": HomeKitSmokeSensor, +} async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): diff --git a/homeassistant/components/homekit_controller/const.py b/homeassistant/components/homekit_controller/const.py index 09a7df2a2bf..204f0e07d3e 100644 --- a/homeassistant/components/homekit_controller/const.py +++ b/homeassistant/components/homekit_controller/const.py @@ -26,4 +26,5 @@ HOMEKIT_ACCESSORY_DISPATCH = { "light": "sensor", "temperature": "sensor", "battery": "sensor", + "smoke": "binary_sensor", } diff --git a/tests/components/homekit_controller/test_binary_sensor.py b/tests/components/homekit_controller/test_binary_sensor.py index 1b73021e44c..f472ac38d1d 100644 --- a/tests/components/homekit_controller/test_binary_sensor.py +++ b/tests/components/homekit_controller/test_binary_sensor.py @@ -3,6 +3,7 @@ from tests.components.homekit_controller.common import FakeService, setup_test_c MOTION_DETECTED = ("motion", "motion-detected") CONTACT_STATE = ("contact", "contact-state") +SMOKE_DETECTED = ("smoke", "smoke-detected") def create_motion_sensor_service(): @@ -51,3 +52,29 @@ async def test_contact_sensor_read_state(hass, utcnow): helper.characteristics[CONTACT_STATE].value = 1 state = await helper.poll_and_get_state() assert state.state == "on" + + +def create_smoke_sensor_service(): + """Define smoke sensor characteristics.""" + service = FakeService("public.hap.service.sensor.smoke") + + cur_state = service.add_characteristic("smoke-detected") + cur_state.value = 0 + + return service + + +async def test_smoke_sensor_read_state(hass, utcnow): + """Test that we can read the state of a HomeKit contact accessory.""" + sensor = create_smoke_sensor_service() + helper = await setup_test_component(hass, [sensor]) + + helper.characteristics[SMOKE_DETECTED].value = 0 + state = await helper.poll_and_get_state() + assert state.state == "off" + + helper.characteristics[SMOKE_DETECTED].value = 1 + state = await helper.poll_and_get_state() + assert state.state == "on" + + assert state.attributes["device_class"] == "smoke" From 33738cc83aae78b99346a081c72ace4c5e5cd38f Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 30 Dec 2019 17:51:11 +0100 Subject: [PATCH 430/677] Upgrade beautifulsoup4 to 4.8.2 (#30274) --- homeassistant/components/scrape/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/scrape/manifest.json b/homeassistant/components/scrape/manifest.json index 5fdcca372b9..6c5bf608999 100644 --- a/homeassistant/components/scrape/manifest.json +++ b/homeassistant/components/scrape/manifest.json @@ -3,7 +3,7 @@ "name": "Scrape", "documentation": "https://www.home-assistant.io/integrations/scrape", "requirements": [ - "beautifulsoup4==4.8.1" + "beautifulsoup4==4.8.2" ], "dependencies": [], "codeowners": [ diff --git a/requirements_all.txt b/requirements_all.txt index 641667d4506..86fd67cc5bd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -287,7 +287,7 @@ batinfo==0.4.2 # beacontools[scan]==1.2.3 # homeassistant.components.scrape -beautifulsoup4==4.8.1 +beautifulsoup4==4.8.2 # homeassistant.components.beewi_smartclim beewi_smartclim==0.0.7 From d0c9a42b81db24f447c62f4bb5ac59668990b688 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 30 Dec 2019 17:51:25 +0100 Subject: [PATCH 431/677] Add custom validator for countries (#30280) --- CODEOWNERS | 1 + .../components/workday/binary_sensor.py | 141 ++++-------------- .../components/workday/manifest.json | 4 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../components/workday/test_binary_sensor.py | 27 +++- 6 files changed, 55 insertions(+), 122 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 52f13748303..1df0d2741cd 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -367,6 +367,7 @@ homeassistant/components/websocket_api/* @home-assistant/core homeassistant/components/wemo/* @sqldiablo homeassistant/components/withings/* @vangorra homeassistant/components/wled/* @frenck +homeassistant/components/workday/* @fabaff homeassistant/components/worldclock/* @fabaff homeassistant/components/wwlln/* @bachya homeassistant/components/xbox_live/* @MartinHjelmare diff --git a/homeassistant/components/workday/binary_sensor.py b/homeassistant/components/workday/binary_sensor.py index 866d87d6240..0aa1f5bfc42 100644 --- a/homeassistant/components/workday/binary_sensor.py +++ b/homeassistant/components/workday/binary_sensor.py @@ -1,6 +1,7 @@ """Sensor to indicate whether the current day is a workday.""" from datetime import datetime, timedelta import logging +from typing import Any import holidays import voluptuous as vol @@ -11,111 +12,6 @@ import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -# List of all countries currently supported by holidays -# Source: https://github.com/dr-prodigy/python-holidays#available-countries -# There seems to be no way to get the list out at runtime -ALL_COUNTRIES = [ - "Argentina", - "AR", - "Aruba", - "AW", - "Australia", - "AU", - "Austria", - "AT", - "Brazil", - "BR", - "Belarus", - "BY", - "Belgium", - "BE", - "Bulgaria", - "BG", - "Canada", - "CA", - "Colombia", - "CO", - "Croatia", - "HR", - "Czech", - "CZ", - "Denmark", - "DK", - "England", - "Estonia", - "EE", - "EuropeanCentralBank", - "ECB", - "TAR", - "Finland", - "FI", - "France", - "FRA", - "Germany", - "DE", - "Hungary", - "HU", - "Honduras", - "HND", - "Iceland", - "IS", - "India", - "IND", - "Ireland", - "IE", - "Isle of Man", - "Italy", - "IT", - "Japan", - "JP", - "Kenya", - "KE", - "Lithuania", - "LT", - "Luxembourg", - "LU", - "Mexico", - "MX", - "Netherlands", - "NL", - "NewZealand", - "NZ", - "Northern Ireland", - "Norway", - "NO", - "Peru", - "PE", - "Poland", - "Polish", - "PL", - "Portugal", - "PT", - "PortugalExt", - "PTE", - "Russia", - "RU", - "Scotland", - "Slovenia", - "SI", - "Slovakia", - "SK", - "South Africa", - "ZA", - "Spain", - "ES", - "Sweden", - "SE", - "Switzerland", - "CH", - "Ukraine", - "UA", - "UnitedKingdom", - "UK", - "UnitedStates", - "US", - "Wales", -] - ALLOWED_DAYS = WEEKDAYS + ["holiday"] CONF_COUNTRY = "country" @@ -132,9 +28,28 @@ DEFAULT_EXCLUDES = ["sat", "sun", "holiday"] DEFAULT_NAME = "Workday Sensor" DEFAULT_OFFSET = 0 + +def valid_country(value: Any) -> str: + """Validate that the given country is supported.""" + value = cv.string(value) + all_supported_countries = holidays.list_supported_countries() + + try: + raw_value = value.encode("utf-8") + except UnicodeError: + raise vol.Invalid( + "The country name or the abbreviation must be a valid UTF-8 string." + ) + if not raw_value: + raise vol.Invalid("Country name or the abbreviation must not be empty.") + if value not in all_supported_countries: + raise vol.Invalid("Country is not supported.") + return value + + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { - vol.Required(CONF_COUNTRY): vol.In(ALL_COUNTRIES), + vol.Required(CONF_COUNTRY): valid_country, vol.Optional(CONF_EXCLUDES, default=DEFAULT_EXCLUDES): vol.All( cv.ensure_list, [vol.In(ALLOWED_DAYS)] ), @@ -151,13 +66,13 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Workday sensor.""" - sensor_name = config.get(CONF_NAME) - country = config.get(CONF_COUNTRY) - province = config.get(CONF_PROVINCE) - workdays = config.get(CONF_WORKDAYS) - excludes = config.get(CONF_EXCLUDES) - days_offset = config.get(CONF_OFFSET) add_holidays = config.get(CONF_ADD_HOLIDAYS) + country = config[CONF_COUNTRY] + days_offset = config[CONF_OFFSET] + excludes = config[CONF_EXCLUDES] + province = config.get(CONF_PROVINCE) + sensor_name = config[CONF_NAME] + workdays = config[CONF_WORKDAYS] year = (get_date(datetime.today()) + timedelta(days=days_offset)).year obj_holidays = getattr(holidays, country)(years=year) @@ -259,7 +174,7 @@ class IsWorkdaySensor(BinarySensorDevice): # Default is no workday self._state = False - # Get iso day of the week (1 = Monday, 7 = Sunday) + # Get ISO day of the week (1 = Monday, 7 = Sunday) date = get_date(datetime.today()) + timedelta(days=self._days_offset) day = date.isoweekday() - 1 day_of_week = day_to_string(day) diff --git a/homeassistant/components/workday/manifest.json b/homeassistant/components/workday/manifest.json index 4b407e95235..ac3bee7d07c 100644 --- a/homeassistant/components/workday/manifest.json +++ b/homeassistant/components/workday/manifest.json @@ -3,8 +3,8 @@ "name": "Workday", "documentation": "https://www.home-assistant.io/integrations/workday", "requirements": [ - "holidays==0.9.11" + "holidays==0.9.12" ], "dependencies": [], - "codeowners": [] + "codeowners": ["@fabaff"] } \ No newline at end of file diff --git a/requirements_all.txt b/requirements_all.txt index 86fd67cc5bd..4cd122e8583 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -663,7 +663,7 @@ hlk-sw16==0.0.7 hole==0.5.0 # homeassistant.components.workday -holidays==0.9.11 +holidays==0.9.12 # homeassistant.components.frontend home-assistant-frontend==20191204.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6b9a7c98702..77936dd4729 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -228,7 +228,7 @@ herepy==2.0.0 hole==0.5.0 # homeassistant.components.workday -holidays==0.9.11 +holidays==0.9.12 # homeassistant.components.frontend home-assistant-frontend==20191204.1 diff --git a/tests/components/workday/test_binary_sensor.py b/tests/components/workday/test_binary_sensor.py index 19da0cbfd87..81ae18bfd3b 100644 --- a/tests/components/workday/test_binary_sensor.py +++ b/tests/components/workday/test_binary_sensor.py @@ -2,7 +2,10 @@ from datetime import date from unittest.mock import patch -from homeassistant.components.workday.binary_sensor import day_to_string +import pytest +import voluptuous as vol + +import homeassistant.components.workday.binary_sensor as binary_sensor from homeassistant.setup import setup_component from tests.common import assert_setup_component, get_test_home_assistant @@ -68,6 +71,20 @@ class TestWorkdaySetup: """Stop everything that was started.""" self.hass.stop() + def test_valid_country(self): + """Test topic name/filter validation.""" + # Invalid UTF-8, must not contain U+D800 to U+DFFF + with pytest.raises(vol.Invalid): + binary_sensor.valid_country("\ud800") + with pytest.raises(vol.Invalid): + binary_sensor.valid_country("\udfff") + # Country MUST NOT be empty + with pytest.raises(vol.Invalid): + binary_sensor.valid_country("") + # Country must be supported by holidays + with pytest.raises(vol.Invalid): + binary_sensor.valid_country("HomeAssistantLand") + def test_setup_component_province(self): """Set up workday component.""" with assert_setup_component(1, "binary_sensor"): @@ -214,7 +231,7 @@ class TestWorkdaySetup: def test_day_to_string(self): """Test if day_to_string is behaving correctly.""" - assert day_to_string(0) == "mon" - assert day_to_string(1) == "tue" - assert day_to_string(7) == "holiday" - assert day_to_string(8) is None + assert binary_sensor.day_to_string(0) == "mon" + assert binary_sensor.day_to_string(1) == "tue" + assert binary_sensor.day_to_string(7) == "holiday" + assert binary_sensor.day_to_string(8) is None From 40e3d6f773042e67d128694d63010232a3bce494 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Mon, 30 Dec 2019 08:52:34 -0800 Subject: [PATCH 432/677] Change default icons for Tesla components (#30288) --- homeassistant/components/tesla/__init__.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/tesla/__init__.py b/homeassistant/components/tesla/__init__.py index dbfe07271ee..a034d9132f1 100644 --- a/homeassistant/components/tesla/__init__.py +++ b/homeassistant/components/tesla/__init__.py @@ -26,7 +26,7 @@ from .config_flow import ( configured_instances, validate_input, ) -from .const import DATA_LISTENER, DOMAIN, TESLA_COMPONENTS +from .const import DATA_LISTENER, DOMAIN, SENSOR_ICONS, TESLA_COMPONENTS _LOGGER = logging.getLogger(__name__) @@ -186,6 +186,11 @@ class TeslaDevice(Entity): self._name = self.tesla_device.name self.tesla_id = slugify(self.tesla_device.uniq_name) self._attributes = {} + self._icon = ( + SENSOR_ICONS[self.tesla_device.type] + if self.tesla_device.type and self.tesla_device.type in SENSOR_ICONS.keys() + else None + ) @property def name(self): @@ -197,6 +202,11 @@ class TeslaDevice(Entity): """Return a unique ID.""" return self.tesla_id + @property + def icon(self): + """Return the icon of the sensor.""" + return self._icon + @property def should_poll(self): """Return the polling state.""" From 3b9f48b51dab8dc85fd71dbd0d081841c2fd12b7 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Mon, 30 Dec 2019 17:54:16 +0100 Subject: [PATCH 433/677] Contributing: Add note about feature suggestions and bug tracking (#30225) --- CONTRIBUTING.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fbe77c7756f..1921e5d38dd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ Everybody is invited and welcome to contribute to Home Assistant. There is a lot The process is straight-forward. - - Read [How to get faster PR reviews](https://github.com/kubernetes/community/blob/master/contributors/guide/pull-requests.md#best-practices-for-faster-reviews) by Kubernetes (but skip step 0) + - Read [How to get faster PR reviews](https://github.com/kubernetes/community/blob/master/contributors/guide/pull-requests.md#best-practices-for-faster-reviews) by Kubernetes (but skip step 0 and 1) - Fork the Home Assistant [git repository](https://github.com/home-assistant/home-assistant). - Write the code for your device, notification service, sensor, or IoT thing. - Ensure tests work. @@ -12,3 +12,7 @@ The process is straight-forward. Still interested? Then you should take a peek at the [developer documentation](https://developers.home-assistant.io/) to get more details. +## Feature suggestions + +If you want to suggest a new feature for Home Assistant (e.g., new integrations), please open a thread in our [Community Forum: Feature Requests](https://community.home-assistant.io/c/feature-requests). +We use [GitHub for tracking issues](https://github.com/home-assistant/home-assistant/issues), not for tracking feature requests. \ No newline at end of file From bad35577cb2fc47aa2dedeac4ced7985cf0accf3 Mon Sep 17 00:00:00 2001 From: Tais Hedegaard Holland <32095655+taisholland@users.noreply.github.com> Date: Mon, 30 Dec 2019 19:38:17 +0100 Subject: [PATCH 434/677] Bump ihcsdk to 2.4.0 (#30279) * Update requirements_all.txt update ihcsdk to version 2.4.0 * Update manifest.json upgrade to version 2.4.0 of ihcsdk --- homeassistant/components/ihc/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/ihc/manifest.json b/homeassistant/components/ihc/manifest.json index a415b0e3103..cfc86f5e3cb 100644 --- a/homeassistant/components/ihc/manifest.json +++ b/homeassistant/components/ihc/manifest.json @@ -4,7 +4,7 @@ "documentation": "https://www.home-assistant.io/integrations/ihc", "requirements": [ "defusedxml==0.6.0", - "ihcsdk==2.3.0" + "ihcsdk==2.4.0" ], "dependencies": [], "codeowners": [] diff --git a/requirements_all.txt b/requirements_all.txt index 4cd122e8583..0b48df5caa5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -708,7 +708,7 @@ ibmiotf==0.3.4 iglo==1.2.7 # homeassistant.components.ihc -ihcsdk==2.3.0 +ihcsdk==2.4.0 # homeassistant.components.incomfort incomfort-client==0.4.0 From 41d2d1f3095eab51046aa88f977d6988b56b1a11 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Mon, 30 Dec 2019 19:40:52 +0100 Subject: [PATCH 435/677] Handle wired bug on restart (#30276) --- .../components/unifi/device_tracker.py | 3 +++ tests/components/unifi/test_controller.py | 6 ++++++ tests/components/unifi/test_device_tracker.py | 21 +++++++++++++++++-- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index 6dfa6a6ecad..8b45a0f227b 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -130,6 +130,9 @@ class UniFiClientTracker(ScannerEntity): self.is_wired = self.client.mac not in controller.wireless_clients self.wired_bug = None + if self.is_wired != self.client.is_wired: + self.wired_bug = dt_util.utcnow() - self.controller.option_detection_time + @property def entity_registry_enabled_default(self): """Return if the entity should be enabled when first added to the entity registry.""" diff --git a/tests/components/unifi/test_controller.py b/tests/components/unifi/test_controller.py index c86e2f11538..ddd16f948cb 100644 --- a/tests/components/unifi/test_controller.py +++ b/tests/components/unifi/test_controller.py @@ -59,6 +59,7 @@ async def setup_unifi_integration( clients_response, devices_response, clients_all_response, + known_wireless_clients=None, ): """Create the UniFi controller.""" if UNIFI_CONFIG not in hass.data: @@ -76,6 +77,11 @@ async def setup_unifi_integration( entry_id=1, ) + if known_wireless_clients: + hass.data[UNIFI_WIRELESS_CLIENTS].update_data( + known_wireless_clients, config_entry + ) + mock_client_responses = deque() mock_client_responses.append(clients_response) diff --git a/tests/components/unifi/test_device_tracker.py b/tests/components/unifi/test_device_tracker.py index 42ba83a1612..5c1505653a3 100644 --- a/tests/components/unifi/test_device_tracker.py +++ b/tests/components/unifi/test_device_tracker.py @@ -43,6 +43,14 @@ CLIENT_3 = { "last_seen": 1562600145, "mac": "00:00:00:00:00:03", } +CLIENT_4 = { + "essid": "ssid", + "hostname": "client_4", + "ip": "10.0.0.4", + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:04", +} DEVICE_1 = { "board_rev": 3, @@ -102,16 +110,20 @@ async def test_no_clients(hass): async def test_tracked_devices(hass): """Test the update_items function with some clients.""" + client_4_copy = copy(CLIENT_4) + client_4_copy["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) + controller = await setup_unifi_integration( hass, ENTRY_CONFIG, options={CONF_SSID_FILTER: ["ssid"]}, sites=SITES, - clients_response=[CLIENT_1, CLIENT_2, CLIENT_3], + clients_response=[CLIENT_1, CLIENT_2, CLIENT_3, client_4_copy], devices_response=[DEVICE_1, DEVICE_2], clients_all_response={}, + known_wireless_clients=(CLIENT_4["mac"],), ) - assert len(hass.states.async_all()) == 5 + assert len(hass.states.async_all()) == 6 client_1 = hass.states.get("device_tracker.client_1") assert client_1 is not None @@ -124,6 +136,11 @@ async def test_tracked_devices(hass): client_3 = hass.states.get("device_tracker.client_3") assert client_3 is None + # Wireless client with wired bug, if bug active on restart mark device away + client_4 = hass.states.get("device_tracker.client_4") + assert client_4 is not None + assert client_4.state == "not_home" + device_1 = hass.states.get("device_tracker.device_1") assert device_1 is not None assert device_1.state == "not_home" From 914aea94467c3098c233f9aca91d2dfa2037451d Mon Sep 17 00:00:00 2001 From: Andrew Sayre <6730289+andrewsayre@users.noreply.github.com> Date: Mon, 30 Dec 2019 14:49:56 -0600 Subject: [PATCH 436/677] Bump pysmartthings 0.7.0 (#30302) --- homeassistant/components/smartthings/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/smartthings/manifest.json b/homeassistant/components/smartthings/manifest.json index 0ab71382fad..9fa156e0b28 100644 --- a/homeassistant/components/smartthings/manifest.json +++ b/homeassistant/components/smartthings/manifest.json @@ -3,7 +3,7 @@ "name": "Smartthings", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/smartthings", - "requirements": ["pysmartapp==0.3.2", "pysmartthings==0.6.9"], + "requirements": ["pysmartapp==0.3.2", "pysmartthings==0.7.0"], "dependencies": ["webhook"], "after_dependencies": ["cloud"], "codeowners": ["@andrewsayre"] diff --git a/requirements_all.txt b/requirements_all.txt index 0b48df5caa5..ed6a433616a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1496,7 +1496,7 @@ pysma==0.3.4 pysmartapp==0.3.2 # homeassistant.components.smartthings -pysmartthings==0.6.9 +pysmartthings==0.7.0 # homeassistant.components.smarty pysmarty==0.8 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 77936dd4729..cf62fd65288 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -496,7 +496,7 @@ pysma==0.3.4 pysmartapp==0.3.2 # homeassistant.components.smartthings -pysmartthings==0.6.9 +pysmartthings==0.7.0 # homeassistant.components.soma pysoma==0.0.10 From ae51331d498347dd6d73ad18fbec9d0861ed459e Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Tue, 31 Dec 2019 00:32:20 +0000 Subject: [PATCH 437/677] [ci skip] Translation update --- .../binary_sensor/.translations/es.json | 4 +-- .../components/deconz/.translations/es.json | 2 +- .../components/hangouts/.translations/ca.json | 2 ++ .../huawei_lte/.translations/it.json | 1 + .../huawei_lte/.translations/zh-Hant.json | 2 +- .../components/starline/.translations/ca.json | 1 + .../tellduslive/.translations/ca.json | 1 + .../components/tesla/.translations/it.json | 30 +++++++++++++++++++ .../components/wled/.translations/es.json | 2 +- .../components/zha/.translations/es.json | 16 +++++----- 10 files changed, 48 insertions(+), 13 deletions(-) create mode 100644 homeassistant/components/tesla/.translations/it.json diff --git a/homeassistant/components/binary_sensor/.translations/es.json b/homeassistant/components/binary_sensor/.translations/es.json index 756a370ca3c..9720fb974f6 100644 --- a/homeassistant/components/binary_sensor/.translations/es.json +++ b/homeassistant/components/binary_sensor/.translations/es.json @@ -72,14 +72,14 @@ "not_moist": "{entity_name} se sec\u00f3", "not_moving": "{entity_name} dej\u00f3 de moverse", "not_occupied": "{entity_name} no est\u00e1 ocupado", - "not_opened": "{nombre_de_la_entidad} cerrado", + "not_opened": "{entity_name} cerrado", "not_plugged_in": "{entity_name} desconectado", "not_powered": "{entity_name} no est\u00e1 activado", "not_present": "{entity_name} no est\u00e1 presente", "not_unsafe": "{entity_name} se volvi\u00f3 seguro", "occupied": "{entity_name} se convirti\u00f3 en ocupado", "opened": "{entity_name} abierto", - "plugged_in": "{nombre_de_la_entidad} conectado", + "plugged_in": "{entity_name} conectado", "powered": "{entity_name} alimentado", "present": "{entity_name} presente", "problem": "{entity_name} empez\u00f3 a detectar problemas", diff --git a/homeassistant/components/deconz/.translations/es.json b/homeassistant/components/deconz/.translations/es.json index 47fd99c48a2..adbe68153f7 100644 --- a/homeassistant/components/deconz/.translations/es.json +++ b/homeassistant/components/deconz/.translations/es.json @@ -72,7 +72,7 @@ "remote_button_quadruple_press": "Bot\u00f3n \"{subtype}\" pulsado cuatro veces consecutivas", "remote_button_quintuple_press": "Bot\u00f3n \"{subtype}\" pulsado cinco veces consecutivas", "remote_button_rotated": "Bot\u00f3n \"{subtype}\" girado", - "remote_button_rotation_stopped": "Bot\u00f3n rotativo \"{subtipo}\" detenido", + "remote_button_rotation_stopped": "Bot\u00f3n rotativo \"{subtype}\" detenido", "remote_button_short_press": "Bot\u00f3n \"{subtype}\" pulsado", "remote_button_short_release": "Bot\u00f3n \"{subtype}\" liberado", "remote_button_triple_press": "Bot\u00f3n \"{subtype}\" pulsado cuatro veces consecutivas", diff --git a/homeassistant/components/hangouts/.translations/ca.json b/homeassistant/components/hangouts/.translations/ca.json index ea43c804f2d..0dcc0f029c2 100644 --- a/homeassistant/components/hangouts/.translations/ca.json +++ b/homeassistant/components/hangouts/.translations/ca.json @@ -14,6 +14,7 @@ "data": { "2fa": "Pin 2FA" }, + "description": "buit", "title": "Verificaci\u00f3 en dos passos" }, "user": { @@ -22,6 +23,7 @@ "email": "Correu electr\u00f2nic", "password": "Contrasenya" }, + "description": "buit", "title": "Inici de sessi\u00f3 de Google Hangouts" } }, diff --git a/homeassistant/components/huawei_lte/.translations/it.json b/homeassistant/components/huawei_lte/.translations/it.json index bcbae3b1b25..4ad17ecaa36 100644 --- a/homeassistant/components/huawei_lte/.translations/it.json +++ b/homeassistant/components/huawei_lte/.translations/it.json @@ -33,6 +33,7 @@ "step": { "init": { "data": { + "name": "Nome del servizio di notifica (la modifica richiede il riavvio)", "recipient": "Destinatari della notifica SMS", "track_new_devices": "Traccia nuovi dispositivi" } diff --git a/homeassistant/components/huawei_lte/.translations/zh-Hant.json b/homeassistant/components/huawei_lte/.translations/zh-Hant.json index 7371669e157..201e9afec4b 100644 --- a/homeassistant/components/huawei_lte/.translations/zh-Hant.json +++ b/homeassistant/components/huawei_lte/.translations/zh-Hant.json @@ -33,7 +33,7 @@ "step": { "init": { "data": { - "name": "\u901a\u77e5\u670d\u52d9\u540d\u7a31", + "name": "\u901a\u77e5\u670d\u52d9\u540d\u7a31\uff08\u8b8a\u66f4\u5f8c\u9700\u91cd\u555f\uff09", "recipient": "\u7c21\u8a0a\u901a\u77e5\u6536\u4ef6\u8005", "track_new_devices": "\u8ffd\u8e64\u65b0\u8a2d\u5099" } diff --git a/homeassistant/components/starline/.translations/ca.json b/homeassistant/components/starline/.translations/ca.json index 04426c2acfa..72cf1a66760 100644 --- a/homeassistant/components/starline/.translations/ca.json +++ b/homeassistant/components/starline/.translations/ca.json @@ -11,6 +11,7 @@ "app_id": "ID d'aplicaci\u00f3", "app_secret": "Secret" }, + "description": "ID d'aplicaci\u00f3 i codi secret de compte de desenvolupador de StarLine", "title": "Credencials d'aplicaci\u00f3" }, "auth_captcha": { diff --git a/homeassistant/components/tellduslive/.translations/ca.json b/homeassistant/components/tellduslive/.translations/ca.json index fafa8798401..a337474c96b 100644 --- a/homeassistant/components/tellduslive/.translations/ca.json +++ b/homeassistant/components/tellduslive/.translations/ca.json @@ -18,6 +18,7 @@ "data": { "host": "Amfitri\u00f3" }, + "description": "buit", "title": "Selecci\u00f3 extrem" } }, diff --git a/homeassistant/components/tesla/.translations/it.json b/homeassistant/components/tesla/.translations/it.json new file mode 100644 index 00000000000..0e254cf2843 --- /dev/null +++ b/homeassistant/components/tesla/.translations/it.json @@ -0,0 +1,30 @@ +{ + "config": { + "error": { + "connection_error": "Errore durante la connessione; controllare la rete e riprovare", + "identifier_exists": "E-mail gi\u00e0 registrata", + "invalid_credentials": "Credenziali non valide", + "unknown_error": "Errore sconosciuto, si prega di segnalare le informazioni del registro" + }, + "step": { + "user": { + "data": { + "password": "Password", + "username": "Indirizzo E-Mail" + }, + "description": "Si prega di inserire le tue informazioni.", + "title": "Tesla - Configurazione" + } + }, + "title": "Tesla" + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Secondi tra le scansioni" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wled/.translations/es.json b/homeassistant/components/wled/.translations/es.json index 7dd388d41af..b7f567698ea 100644 --- a/homeassistant/components/wled/.translations/es.json +++ b/homeassistant/components/wled/.translations/es.json @@ -7,7 +7,7 @@ "error": { "connection_error": "No se ha podido conectar al dispositivo WLED." }, - "flow_title": "WLED: {nombre}", + "flow_title": "WLED: {name}", "step": { "user": { "data": { diff --git a/homeassistant/components/zha/.translations/es.json b/homeassistant/components/zha/.translations/es.json index b8529ce9047..fb2271b260a 100644 --- a/homeassistant/components/zha/.translations/es.json +++ b/homeassistant/components/zha/.translations/es.json @@ -54,14 +54,14 @@ "device_shaken": "Dispositivo agitado", "device_slid": "Dispositivo deslizado \" {subtype} \"", "device_tilted": "Dispositivo inclinado", - "remote_button_double_press": "\"{subtipo}\" bot\u00f3n de doble clic", - "remote_button_long_press": "Bot\u00f3n \"{subtipo}\" pulsado continuamente", - "remote_button_long_release": "Bot\u00f3n \"{subtipo}\" liberado despu\u00e9s de una pulsaci\u00f3n prolongada", - "remote_button_quadruple_press": "\"{subtipo}\" bot\u00f3n cu\u00e1druple pulsado", - "remote_button_quintuple_press": "\"{subtipo}\" bot\u00f3n qu\u00edntuple pulsado", - "remote_button_short_press": "Bot\u00f3n \"{subtipo}\" pulsado", - "remote_button_short_release": "Bot\u00f3n \"{subtipo}\" liberado", - "remote_button_triple_press": "\"{subtipo}\" bot\u00f3n de triple clic" + "remote_button_double_press": "\"{subtype}\" bot\u00f3n de doble clic", + "remote_button_long_press": "Bot\u00f3n \"{subtype}\" pulsado continuamente", + "remote_button_long_release": "Bot\u00f3n \"{subtype}\" liberado despu\u00e9s de una pulsaci\u00f3n prolongada", + "remote_button_quadruple_press": "\"{subtype}\" bot\u00f3n cu\u00e1druple pulsado", + "remote_button_quintuple_press": "\"{subtype}\" bot\u00f3n qu\u00edntuple pulsado", + "remote_button_short_press": "Bot\u00f3n \"{subtype}\" pulsado", + "remote_button_short_release": "Bot\u00f3n \"{subtype}\" liberado", + "remote_button_triple_press": "\"{subtype}\" bot\u00f3n de triple clic" } } } \ No newline at end of file From 1ee299b079b3672aad3dcdce8ce1ecf4e5238d07 Mon Sep 17 00:00:00 2001 From: Phil Bruckner Date: Mon, 30 Dec 2019 19:30:45 -0600 Subject: [PATCH 438/677] Ignore google_maps updates when last_seen goes backwards (#30178) --- .../components/google_maps/device_tracker.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/google_maps/device_tracker.py b/homeassistant/components/google_maps/device_tracker.py index 75f370e502e..9e33ff5f715 100644 --- a/homeassistant/components/google_maps/device_tracker.py +++ b/homeassistant/components/google_maps/device_tracker.py @@ -53,6 +53,7 @@ class GoogleMapsScanner: self.username = config[CONF_USERNAME] self.max_gps_accuracy = config[CONF_MAX_GPS_ACCURACY] self.scan_interval = config.get(CONF_SCAN_INTERVAL) or timedelta(seconds=60) + self._prev_seen = {} credfile = "{}.{}".format( hass.config.path(CREDENTIALS_FILE), slugify(self.username) @@ -92,11 +93,22 @@ class GoogleMapsScanner: ) continue + last_seen = dt_util.as_utc(person.datetime) + if last_seen < self._prev_seen.get(dev_id, last_seen): + _LOGGER.warning( + "Ignoring %s update because timestamp " + "is older than last timestamp", + person.nickname, + ) + _LOGGER.debug("%s < %s", last_seen, self._prev_seen[dev_id]) + continue + self._prev_seen[dev_id] = last_seen + attrs = { ATTR_ADDRESS: person.address, ATTR_FULL_NAME: person.full_name, ATTR_ID: person.id, - ATTR_LAST_SEEN: dt_util.as_utc(person.datetime), + ATTR_LAST_SEEN: last_seen, ATTR_NICKNAME: person.nickname, ATTR_BATTERY_CHARGING: person.charging, ATTR_BATTERY_LEVEL: person.battery_level, From 2c1a7a54cdae4619ab6f194af1f5554717f6ec2b Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Tue, 31 Dec 2019 13:05:31 +0100 Subject: [PATCH 439/677] Add GIOS integration (#28719) * Initial commit * Add gios to requirements * Add tests * Update .coveragerc file * Run gen_requirements_all.py * Change DEFAULT_SCAN_INTERVAL * Better strings * Bump library version * run script.hassfest * run isort * Add icons mapping * Remove unnecessary f-string * Remove unnecessary listener * Refactoring config_flow * Add unique_id to config entry * Change AQI states to consts in English * Remove unused init * Remove unused exception * Remove private instance attribute * Remove overwrite state property * Fix pylint error * Add SCAN_INTERVAL for air_quality entity * Add _abort_if_unique_id_configured() --- .coveragerc | 3 + CODEOWNERS | 1 + homeassistant/components/gios/__init__.py | 78 +++++++++ homeassistant/components/gios/air_quality.py | 158 +++++++++++++++++++ homeassistant/components/gios/config_flow.py | 65 ++++++++ homeassistant/components/gios/const.py | 25 +++ homeassistant/components/gios/manifest.json | 9 ++ homeassistant/components/gios/strings.json | 20 +++ homeassistant/generated/config_flows.py | 1 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/gios/__init__.py | 1 + tests/components/gios/test_config_flow.py | 104 ++++++++++++ 13 files changed, 471 insertions(+) create mode 100644 homeassistant/components/gios/__init__.py create mode 100644 homeassistant/components/gios/air_quality.py create mode 100644 homeassistant/components/gios/config_flow.py create mode 100644 homeassistant/components/gios/const.py create mode 100644 homeassistant/components/gios/manifest.json create mode 100644 homeassistant/components/gios/strings.json create mode 100644 tests/components/gios/__init__.py create mode 100644 tests/components/gios/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index 70d8f867e0e..10d56c4701d 100644 --- a/.coveragerc +++ b/.coveragerc @@ -258,6 +258,9 @@ omit = homeassistant/components/geniushub/* homeassistant/components/gearbest/sensor.py homeassistant/components/geizhals/sensor.py + homeassistant/components/gios/__init__.py + homeassistant/components/gios/air_quality.py + homeassistant/components/gios/consts.py homeassistant/components/github/sensor.py homeassistant/components/gitlab_ci/sensor.py homeassistant/components/gitter/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 1df0d2741cd..f5357d1348c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -119,6 +119,7 @@ homeassistant/components/geniushub/* @zxdavb homeassistant/components/geo_rss_events/* @exxamalte homeassistant/components/geonetnz_quakes/* @exxamalte homeassistant/components/geonetnz_volcano/* @exxamalte +homeassistant/components/gios/* @bieniu homeassistant/components/gitter/* @fabaff homeassistant/components/glances/* @fabaff @engrbm87 homeassistant/components/gntp/* @robbiet480 diff --git a/homeassistant/components/gios/__init__.py b/homeassistant/components/gios/__init__.py new file mode 100644 index 00000000000..981de6395de --- /dev/null +++ b/homeassistant/components/gios/__init__.py @@ -0,0 +1,78 @@ +"""The GIOS component.""" +import asyncio +import logging + +from aiohttp.client_exceptions import ClientConnectorError +from async_timeout import timeout +from gios import ApiError, Gios, NoStationError + +from homeassistant.core import Config, HomeAssistant +from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.util import Throttle + +from .const import CONF_STATION_ID, DATA_CLIENT, DEFAULT_SCAN_INTERVAL, DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup(hass: HomeAssistant, config: Config) -> bool: + """Set up configured GIOS.""" + hass.data[DOMAIN] = {} + hass.data[DOMAIN][DATA_CLIENT] = {} + return True + + +async def async_setup_entry(hass, config_entry): + """Set up GIOS as config entry.""" + station_id = config_entry.data[CONF_STATION_ID] + _LOGGER.debug("Using station_id: %s", station_id) + + websession = async_get_clientsession(hass) + + gios = GiosData(websession, station_id) + + await gios.async_update() + + hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = gios + + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, "air_quality") + ) + return True + + +async def async_unload_entry(hass, config_entry): + """Unload a config entry.""" + hass.data[DOMAIN][DATA_CLIENT].pop(config_entry.entry_id) + await hass.config_entries.async_forward_entry_unload(config_entry, "air_quality") + return True + + +class GiosData: + """Define an object to hold GIOS data.""" + + def __init__(self, session, station_id): + """Initialize.""" + self._gios = Gios(station_id, session) + self.station_id = station_id + self.sensors = {} + self.latitude = None + self.longitude = None + self.station_name = None + self.available = True + + @Throttle(DEFAULT_SCAN_INTERVAL) + async def async_update(self): + """Update GIOS data.""" + try: + with timeout(30): + await self._gios.update() + except asyncio.TimeoutError: + _LOGGER.error("Asyncio Timeout Error") + except (ApiError, NoStationError, ClientConnectorError) as error: + _LOGGER.error("GIOS data update failed: %s", error) + self.available = self._gios.available + self.latitude = self._gios.latitude + self.longitude = self._gios.longitude + self.station_name = self._gios.station_name + self.sensors = self._gios.data diff --git a/homeassistant/components/gios/air_quality.py b/homeassistant/components/gios/air_quality.py new file mode 100644 index 00000000000..f7285c8cc5a --- /dev/null +++ b/homeassistant/components/gios/air_quality.py @@ -0,0 +1,158 @@ +"""Support for the GIOS service.""" +from homeassistant.components.air_quality import ( + ATTR_CO, + ATTR_NO2, + ATTR_OZONE, + ATTR_PM_2_5, + ATTR_PM_10, + ATTR_SO2, + AirQualityEntity, +) +from homeassistant.const import CONF_NAME + +from .const import ATTR_STATION, DATA_CLIENT, DEFAULT_SCAN_INTERVAL, DOMAIN, ICONS_MAP + +ATTRIBUTION = "Data provided by GIOŚ" +SCAN_INTERVAL = DEFAULT_SCAN_INTERVAL + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Add a GIOS entities from a config_entry.""" + name = config_entry.data[CONF_NAME] + + data = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] + + async_add_entities([GiosAirQuality(data, name)], True) + + +def round_state(func): + """Round state.""" + + def _decorator(self): + res = func(self) + if isinstance(res, float): + return round(res) + return res + + return _decorator + + +class GiosAirQuality(AirQualityEntity): + """Define an GIOS sensor.""" + + def __init__(self, gios, name): + """Initialize.""" + self.gios = gios + self._name = name + self._aqi = None + self._co = None + self._no2 = None + self._o3 = None + self._pm_2_5 = None + self._pm_10 = None + self._so2 = None + self._attrs = {} + + @property + def name(self): + """Return the name.""" + return self._name + + @property + def icon(self): + """Return the icon.""" + if self._aqi in ICONS_MAP: + return ICONS_MAP[self._aqi] + return "mdi:blur" + + @property + def air_quality_index(self): + """Return the air quality index.""" + return self._aqi + + @property + @round_state + def particulate_matter_2_5(self): + """Return the particulate matter 2.5 level.""" + return self._pm_2_5 + + @property + @round_state + def particulate_matter_10(self): + """Return the particulate matter 10 level.""" + return self._pm_10 + + @property + @round_state + def ozone(self): + """Return the O3 (ozone) level.""" + return self._o3 + + @property + @round_state + def carbon_monoxide(self): + """Return the CO (carbon monoxide) level.""" + return self._co + + @property + @round_state + def sulphur_dioxide(self): + """Return the SO2 (sulphur dioxide) level.""" + return self._so2 + + @property + @round_state + def nitrogen_dioxide(self): + """Return the NO2 (nitrogen dioxide) level.""" + return self._no2 + + @property + def attribution(self): + """Return the attribution.""" + return ATTRIBUTION + + @property + def unique_id(self): + """Return a unique_id for this entity.""" + return self.gios.station_id + + @property + def available(self): + """Return True if entity is available.""" + return self.gios.available + + @property + def device_state_attributes(self): + """Return the state attributes.""" + self._attrs[ATTR_STATION] = self.gios.station_name + return self._attrs + + async def async_update(self): + """Get the data from GIOS.""" + await self.gios.async_update() + + if self.gios.available: + # Different measuring stations have different sets of sensors. We don't know + # what data we will get. + if "AQI" in self.gios.sensors: + self._aqi = self.gios.sensors["AQI"]["value"] + if "CO" in self.gios.sensors: + self._co = self.gios.sensors["CO"]["value"] + self._attrs[f"{ATTR_CO}_index"] = self.gios.sensors["CO"]["index"] + if "NO2" in self.gios.sensors: + self._no2 = self.gios.sensors["NO2"]["value"] + self._attrs[f"{ATTR_NO2}_index"] = self.gios.sensors["NO2"]["index"] + if "O3" in self.gios.sensors: + self._o3 = self.gios.sensors["O3"]["value"] + self._attrs[f"{ATTR_OZONE}_index"] = self.gios.sensors["O3"]["index"] + if "PM2.5" in self.gios.sensors: + self._pm_2_5 = self.gios.sensors["PM2.5"]["value"] + self._attrs[f"{ATTR_PM_2_5}_index"] = self.gios.sensors["PM2.5"][ + "index" + ] + if "PM10" in self.gios.sensors: + self._pm_10 = self.gios.sensors["PM10"]["value"] + self._attrs[f"{ATTR_PM_10}_index"] = self.gios.sensors["PM10"]["index"] + if "SO2" in self.gios.sensors: + self._so2 = self.gios.sensors["SO2"]["value"] + self._attrs[f"{ATTR_SO2}_index"] = self.gios.sensors["SO2"]["index"] diff --git a/homeassistant/components/gios/config_flow.py b/homeassistant/components/gios/config_flow.py new file mode 100644 index 00000000000..368d610c226 --- /dev/null +++ b/homeassistant/components/gios/config_flow.py @@ -0,0 +1,65 @@ +"""Adds config flow for GIOS.""" +import asyncio + +from aiohttp.client_exceptions import ClientConnectorError +from async_timeout import timeout +from gios import ApiError, Gios, NoStationError +import voluptuous as vol + +from homeassistant import config_entries, exceptions +from homeassistant.const import CONF_NAME +from homeassistant.helpers.aiohttp_client import async_get_clientsession + +from .const import CONF_STATION_ID, DEFAULT_NAME, DOMAIN # pylint:disable=unused-import + +DATA_SCHEMA = vol.Schema( + { + vol.Required(CONF_STATION_ID): int, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): str, + } +) + + +class GiosFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): + """Config flow for GIOS.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + + async def async_step_user(self, user_input=None): + """Handle a flow initialized by the user.""" + errors = {} + + if user_input is not None: + try: + await self.async_set_unique_id( + user_input[CONF_STATION_ID], raise_on_progress=False + ) + self._abort_if_unique_id_configured() + + websession = async_get_clientsession(self.hass) + + with timeout(30): + gios = Gios(user_input[CONF_STATION_ID], websession) + await gios.update() + + if not gios.available: + raise InvalidSensorsData() + + return self.async_create_entry( + title=user_input[CONF_STATION_ID], data=user_input, + ) + except (ApiError, ClientConnectorError, asyncio.TimeoutError): + errors["base"] = "cannot_connect" + except NoStationError: + errors[CONF_STATION_ID] = "wrong_station_id" + except InvalidSensorsData: + errors[CONF_STATION_ID] = "invalid_sensors_data" + + return self.async_show_form( + step_id="user", data_schema=DATA_SCHEMA, errors=errors + ) + + +class InvalidSensorsData(exceptions.HomeAssistantError): + """Error to indicate invalid sensors data.""" diff --git a/homeassistant/components/gios/const.py b/homeassistant/components/gios/const.py new file mode 100644 index 00000000000..3588b5e8dfc --- /dev/null +++ b/homeassistant/components/gios/const.py @@ -0,0 +1,25 @@ +"""Constants for GIOS integration.""" +from datetime import timedelta + +ATTR_NAME = "name" +ATTR_STATION = "station" +CONF_STATION_ID = "station_id" +DATA_CLIENT = "client" +DEFAULT_NAME = "GIOŚ" +# Term of service GIOŚ allow downloading data no more than twice an hour. +DEFAULT_SCAN_INTERVAL = timedelta(minutes=30) +DOMAIN = "gios" + +AQI_GOOD = "dobry" +AQI_MODERATE = "umiarkowany" +AQI_POOR = "dostateczny" +AQI_VERY_GOOD = "bardzo dobry" +AQI_VERY_POOR = "zły" + +ICONS_MAP = { + AQI_VERY_GOOD: "mdi:emoticon-excited", + AQI_GOOD: "mdi:emoticon-happy", + AQI_MODERATE: "mdi:emoticon-neutral", + AQI_POOR: "mdi:emoticon-sad", + AQI_VERY_POOR: "mdi:emoticon-dead", +} diff --git a/homeassistant/components/gios/manifest.json b/homeassistant/components/gios/manifest.json new file mode 100644 index 00000000000..b3d125d8ab6 --- /dev/null +++ b/homeassistant/components/gios/manifest.json @@ -0,0 +1,9 @@ +{ + "domain": "gios", + "name": "GIOŚ", + "documentation": "https://www.home-assistant.io/integrations/gios", + "dependencies": [], + "codeowners": ["@bieniu"], + "requirements": ["gios==0.0.3"], + "config_flow": true +} diff --git a/homeassistant/components/gios/strings.json b/homeassistant/components/gios/strings.json new file mode 100644 index 00000000000..cc05a471b4a --- /dev/null +++ b/homeassistant/components/gios/strings.json @@ -0,0 +1,20 @@ +{ + "config": { + "title": "GIOŚ", + "step": { + "user": { + "title": "GIOŚ (Polish Chief Inspectorate Of Environmental Protection)", + "description": "Set up GIOŚ (Polish Chief Inspectorate Of Environmental Protection) air quality integration. If you need help with the configuration have a look here: https://www.home-assistant.io/integrations/gios", + "data": { + "name": "Name of the integration", + "station_id": "ID of the measuring station" + } + } + }, + "error": { + "wrong_station_id": "ID of the measuring station is not correct.", + "invalid_sensors_data": "Invalid sensors' data for this measuring station.", + "cannot_connect": "Cannot connect to the GIOŚ server." + } + } +} diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 88ff92a57b0..55a4d76fdcd 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -26,6 +26,7 @@ FLOWS = [ "geofency", "geonetnz_quakes", "geonetnz_volcano", + "gios", "glances", "gpslogger", "hangouts", diff --git a/requirements_all.txt b/requirements_all.txt index ed6a433616a..d84815a42f3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -578,6 +578,9 @@ georss_qld_bushfire_alert_client==0.3 # homeassistant.components.nmap_tracker getmac==0.8.1 +# homeassistant.components.gios +gios==0.0.3 + # homeassistant.components.gitter gitterpy==0.1.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index cf62fd65288..d197ed0196a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -197,6 +197,9 @@ georss_qld_bushfire_alert_client==0.3 # homeassistant.components.nmap_tracker getmac==0.8.1 +# homeassistant.components.gios +gios==0.0.3 + # homeassistant.components.glances glances_api==0.2.0 diff --git a/tests/components/gios/__init__.py b/tests/components/gios/__init__.py new file mode 100644 index 00000000000..98528fda9f9 --- /dev/null +++ b/tests/components/gios/__init__.py @@ -0,0 +1 @@ +"""Tests for GIOS.""" diff --git a/tests/components/gios/test_config_flow.py b/tests/components/gios/test_config_flow.py new file mode 100644 index 00000000000..3a4aff6d9ad --- /dev/null +++ b/tests/components/gios/test_config_flow.py @@ -0,0 +1,104 @@ +"""Define tests for the GIOS config flow.""" +from asynctest import patch +from gios import ApiError + +from homeassistant import data_entry_flow +from homeassistant.components.gios import config_flow +from homeassistant.components.gios.const import CONF_STATION_ID +from homeassistant.const import CONF_NAME + +CONFIG = { + CONF_NAME: "Foo", + CONF_STATION_ID: 123, +} + +VALID_STATIONS = [ + {"id": 123, "stationName": "Test Name 1", "gegrLat": "99.99", "gegrLon": "88.88"}, + {"id": 321, "stationName": "Test Name 2", "gegrLat": "77.77", "gegrLon": "66.66"}, +] + +VALID_STATION = [ + {"id": 3764, "param": {"paramName": "particulate matter PM10", "paramCode": "PM10"}} +] + +VALID_INDEXES = { + "stIndexLevel": {"id": 1, "indexLevelName": "Good"}, + "pm10IndexLevel": {"id": 0, "indexLevelName": "Very good"}, +} + +VALID_SENSOR = {"key": "PM10", "values": [{"value": 11.11}]} + + +async def test_show_form(hass): + """Test that the form is served with no input.""" + flow = config_flow.GiosFlowHandler() + flow.hass = hass + + result = await flow.async_step_user(user_input=None) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + + +async def test_invalid_station_id(hass): + """Test that errors are shown when measuring station ID is invalid.""" + with patch("gios.Gios._get_stations", return_value=VALID_STATIONS): + flow = config_flow.GiosFlowHandler() + flow.hass = hass + flow.context = {} + + result = await flow.async_step_user( + user_input={CONF_NAME: "Foo", CONF_STATION_ID: 0} + ) + + assert result["errors"] == {CONF_STATION_ID: "wrong_station_id"} + + +async def test_invalid_sensor_data(hass): + """Test that errors are shown when sensor data is invalid.""" + with patch("gios.Gios._get_stations", return_value=VALID_STATIONS), patch( + "gios.Gios._get_station", return_value=VALID_STATION + ), patch("gios.Gios._get_station", return_value=VALID_STATION), patch( + "gios.Gios._get_sensor", return_value={} + ): + flow = config_flow.GiosFlowHandler() + flow.hass = hass + flow.context = {} + + result = await flow.async_step_user(user_input=CONFIG) + + assert result["errors"] == {CONF_STATION_ID: "invalid_sensors_data"} + + +async def test_cannot_connect(hass): + """Test that errors are shown when cannot connect to GIOS server.""" + with patch("gios.Gios._async_get", side_effect=ApiError("error")): + flow = config_flow.GiosFlowHandler() + flow.hass = hass + flow.context = {} + + result = await flow.async_step_user(user_input=CONFIG) + + assert result["errors"] == {"base": "cannot_connect"} + + +async def test_create_entry(hass): + """Test that the user step works.""" + with patch("gios.Gios._get_stations", return_value=VALID_STATIONS), patch( + "gios.Gios._get_station", return_value=VALID_STATION + ), patch("gios.Gios._get_station", return_value=VALID_STATION), patch( + "gios.Gios._get_sensor", return_value=VALID_SENSOR + ), patch( + "gios.Gios._get_indexes", return_value=VALID_INDEXES + ): + flow = config_flow.GiosFlowHandler() + flow.hass = hass + flow.context = {} + + result = await flow.async_step_user(user_input=CONFIG) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == CONFIG[CONF_STATION_ID] + assert result["data"][CONF_STATION_ID] == CONFIG[CONF_STATION_ID] + + assert flow.context["unique_id"] == CONFIG[CONF_STATION_ID] From bb14a083f0a6004737df15162d9e922eef545e5b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 31 Dec 2019 14:29:43 +0100 Subject: [PATCH 440/677] Store capabilities and supported features in entity registry, restore registered entities on startup (#30094) * Store capabilities and supported features in entity registry * Restore states at startup * Restore non-disabled entities on HA start * Fix test * Pass device class from entity platform * Clean up restored entities from state machine * Fix Z-Wave test? --- homeassistant/helpers/entity.py | 4 +- homeassistant/helpers/entity_platform.py | 17 ++- homeassistant/helpers/entity_registry.py | 91 +++++++++++++++- tests/common.py | 20 ++++ tests/components/zwave/test_init.py | 1 + tests/helpers/test_entity_component.py | 9 +- tests/helpers/test_entity_platform.py | 41 ++++++++ tests/helpers/test_entity_registry.py | 128 ++++++++++++++++++++++- 8 files changed, 293 insertions(+), 18 deletions(-) diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 531444b9d1e..b1786130b58 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -311,7 +311,9 @@ class Entity(ABC): start = timer() - attr = self.capability_attributes or {} + attr = self.capability_attributes + attr = dict(attr) if attr else {} + if not self.available: state = STATE_UNAVAILABLE else: diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index e171a4cade8..5fd88729f08 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -347,6 +347,9 @@ class EntityPlatform: device_id=device_id, known_object_ids=self.entities.keys(), disabled_by=disabled_by, + capabilities=entity.capability_attributes, + supported_features=entity.supported_features, + device_class=entity.device_class, ) entity.registry_entry = entry @@ -387,10 +390,16 @@ class EntityPlatform: # Make sure it is valid in case an entity set the value themselves if not valid_entity_id(entity.entity_id): raise HomeAssistantError(f"Invalid entity id: {entity.entity_id}") - if ( - entity.entity_id in self.entities - or entity.entity_id in self.hass.states.async_entity_ids(self.domain) - ): + + already_exists = entity.entity_id in self.entities + + if not already_exists: + existing = self.hass.states.get(entity.entity_id) + + if existing and not existing.attributes.get("restored"): + already_exists = True + + if already_exists: msg = f"Entity id already exists: {entity.entity_id}" if entity.unique_id is not None: msg += ". Platform {} does not generate unique IDs".format( diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 5eb79965880..77d8ccc00e0 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -15,6 +15,12 @@ from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, cast import attr +from homeassistant.const import ( + ATTR_DEVICE_CLASS, + ATTR_SUPPORTED_FEATURES, + EVENT_HOMEASSISTANT_START, + STATE_UNAVAILABLE, +) from homeassistant.core import Event, callback, split_entity_id, valid_entity_id from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED from homeassistant.loader import bind_hass @@ -39,6 +45,8 @@ DISABLED_HASS = "hass" DISABLED_USER = "user" DISABLED_INTEGRATION = "integration" +ATTR_RESTORED = "restored" + STORAGE_VERSION = 1 STORAGE_KEY = "core.entity_registry" @@ -66,6 +74,9 @@ class RegistryEntry: ) ), ) + capabilities: Optional[Dict[str, Any]] = attr.ib(default=None) + supported_features: int = attr.ib(default=0) + device_class: Optional[str] = attr.ib(default=None) domain = attr.ib(type=str, init=False, repr=False) @domain.default @@ -142,11 +153,17 @@ class EntityRegistry: platform: str, unique_id: str, *, + # To influence entity ID generation suggested_object_id: Optional[str] = None, + known_object_ids: Optional[Iterable[str]] = None, + # To disable an entity if it gets created + disabled_by: Optional[str] = None, + # Data that we want entry to have config_entry: Optional["ConfigEntry"] = None, device_id: Optional[str] = None, - known_object_ids: Optional[Iterable[str]] = None, - disabled_by: Optional[str] = None, + capabilities: Optional[Dict[str, Any]] = None, + supported_features: Optional[int] = None, + device_class: Optional[str] = None, ) -> RegistryEntry: """Get entity. Create if it doesn't exist.""" config_entry_id = None @@ -160,6 +177,9 @@ class EntityRegistry: entity_id, config_entry_id=config_entry_id or _UNDEF, device_id=device_id or _UNDEF, + capabilities=capabilities or _UNDEF, + supported_features=supported_features or _UNDEF, + device_class=device_class or _UNDEF, # When we changed our slugify algorithm, we invalidated some # stored entity IDs with either a __ or ending in _. # Fix introduced in 0.86 (Jan 23, 2019). Next line can be @@ -187,6 +207,9 @@ class EntityRegistry: unique_id=unique_id, platform=platform, disabled_by=disabled_by, + capabilities=capabilities, + supported_features=supported_features or 0, + device_class=device_class, ) self.entities[entity_id] = entity _LOGGER.info("Registered new %s.%s entity: %s", domain, platform, entity_id) @@ -253,6 +276,9 @@ class EntityRegistry: device_id=_UNDEF, new_unique_id=_UNDEF, disabled_by=_UNDEF, + capabilities=_UNDEF, + supported_features=_UNDEF, + device_class=_UNDEF, ): """Private facing update properties method.""" old = self.entities[entity_id] @@ -264,6 +290,9 @@ class EntityRegistry: ("config_entry_id", config_entry_id), ("device_id", device_id), ("disabled_by", disabled_by), + ("capabilities", capabilities), + ("supported_features", supported_features), + ("device_class", device_class), ): if value is not _UNDEF and value != getattr(old, attr_name): changes[attr_name] = value @@ -318,6 +347,8 @@ class EntityRegistry: async def async_load(self) -> None: """Load the entity registry.""" + async_setup_entity_restore(self.hass, self) + data = await self.hass.helpers.storage.async_migrator( self.hass.config.path(PATH_REGISTRY), self._store, @@ -336,6 +367,9 @@ class EntityRegistry: platform=entity["platform"], name=entity.get("name"), disabled_by=entity.get("disabled_by"), + capabilities=entity.get("capabilities") or {}, + supported_features=entity.get("supported_features", 0), + device_class=entity.get("device_class"), ) self.entities = entities @@ -359,6 +393,9 @@ class EntityRegistry: "platform": entry.platform, "name": entry.name, "disabled_by": entry.disabled_by, + "capabilities": entry.capabilities, + "supported_features": entry.supported_features, + "device_class": entry.device_class, } for entry in self.entities.values() ] @@ -416,3 +453,53 @@ async def _async_migrate(entities: Dict[str, Any]) -> Dict[str, List[Dict[str, A {"entity_id": entity_id, **info} for entity_id, info in entities.items() ] } + + +@callback +def async_setup_entity_restore( + hass: HomeAssistantType, registry: EntityRegistry +) -> None: + """Set up the entity restore mechanism.""" + + @callback + def cleanup_restored_states(event: Event) -> None: + """Clean up restored states.""" + if event.data["action"] != "remove": + return + + state = hass.states.get(event.data["entity_id"]) + + if state is None or not state.attributes.get(ATTR_RESTORED): + return + + hass.states.async_remove(event.data["entity_id"]) + + hass.bus.async_listen(EVENT_ENTITY_REGISTRY_UPDATED, cleanup_restored_states) + + if hass.is_running: + return + + @callback + def _write_unavailable_states(_: Event) -> None: + """Make sure state machine contains entry for each registered entity.""" + states = hass.states + existing = set(states.async_entity_ids()) + + for entry in registry.entities.values(): + if entry.entity_id in existing or entry.disabled: + continue + + attrs: Dict[str, Any] = {ATTR_RESTORED: True} + + if entry.capabilities: + attrs.update(entry.capabilities) + + if entry.supported_features: + attrs[ATTR_SUPPORTED_FEATURES] = entry.supported_features + + if entry.device_class: + attrs[ATTR_DEVICE_CLASS] = entry.device_class + + states.async_set(entry.entity_id, STATE_UNAVAILABLE, attrs) + + hass.bus.async_listen(EVENT_HOMEASSISTANT_START, _write_unavailable_states) diff --git a/tests/common.py b/tests/common.py index 5d13da74e88..e57710c46cc 100644 --- a/tests/common.py +++ b/tests/common.py @@ -906,6 +906,11 @@ class MockEntity(entity.Entity): """Return the unique ID of the entity.""" return self._handle("unique_id") + @property + def state(self): + """Return the state of the entity.""" + return self._handle("state") + @property def available(self): """Return True if entity is available.""" @@ -916,6 +921,21 @@ class MockEntity(entity.Entity): """Info how it links to a device.""" return self._handle("device_info") + @property + def device_class(self): + """Info how device should be classified.""" + return self._handle("device_class") + + @property + def capability_attributes(self): + """Info about capabilities.""" + return self._handle("capability_attributes") + + @property + def supported_features(self): + """Info about supported features.""" + return self._handle("supported_features") + @property def entity_registry_enabled_default(self): """Return if the entity should be enabled when first added to the entity registry.""" diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py index 36c91823220..b7ffaba7e42 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -130,6 +130,7 @@ async def test_auto_heal_midnight(hass, mock_openzwave): time = utc.localize(datetime(2017, 5, 6, 0, 0, 0)) async_fire_time_changed(hass, time) await hass.async_block_till_done() + await hass.async_block_till_done() assert network.heal.called assert len(network.heal.mock_calls) == 1 diff --git a/tests/helpers/test_entity_component.py b/tests/helpers/test_entity_component.py index 81fbe2d6520..a069c050cf4 100644 --- a/tests/helpers/test_entity_component.py +++ b/tests/helpers/test_entity_component.py @@ -8,7 +8,6 @@ from unittest.mock import Mock, patch import asynctest import pytest -from homeassistant.components import group from homeassistant.const import ENTITY_MATCH_ALL import homeassistant.core as ha from homeassistant.exceptions import PlatformNotReady @@ -285,15 +284,13 @@ async def test_extract_from_service_filter_out_non_existing_entities(hass): async def test_extract_from_service_no_group_expand(hass): """Test not expanding a group.""" component = EntityComponent(_LOGGER, DOMAIN, hass) - test_group = await group.Group.async_create_group( - hass, "test_group", ["light.Ceiling", "light.Kitchen"] - ) - await component.async_add_entities([test_group]) + await component.async_add_entities([MockEntity(entity_id="group.test_group")]) call = ha.ServiceCall("test", "service", {"entity_id": ["group.test_group"]}) extracted = await component.async_extract_from_service(call, expand_group=False) - assert extracted == [test_group] + assert len(extracted) == 1 + assert extracted[0].entity_id == "group.test_group" async def test_setup_dependencies_platform(hass): diff --git a/tests/helpers/test_entity_platform.py b/tests/helpers/test_entity_platform.py index 5909dfaf3aa..0f73699c896 100644 --- a/tests/helpers/test_entity_platform.py +++ b/tests/helpers/test_entity_platform.py @@ -793,3 +793,44 @@ async def test_entity_disabled_by_integration(hass): assert entry_default.disabled_by is None entry_disabled = registry.async_get_or_create(DOMAIN, DOMAIN, "disabled") assert entry_disabled.disabled_by == "integration" + + +async def test_entity_info_added_to_entity_registry(hass): + """Test entity info is written to entity registry.""" + component = EntityComponent(_LOGGER, DOMAIN, hass, timedelta(seconds=20)) + + entity_default = MockEntity( + unique_id="default", + capability_attributes={"max": 100}, + supported_features=5, + device_class="mock-device-class", + ) + + await component.async_add_entities([entity_default]) + + registry = await hass.helpers.entity_registry.async_get_registry() + + entry_default = registry.async_get_or_create(DOMAIN, DOMAIN, "default") + print(entry_default) + assert entry_default.capabilities == {"max": 100} + assert entry_default.supported_features == 5 + assert entry_default.device_class == "mock-device-class" + + +async def test_override_restored_entities(hass): + """Test that we allow overriding restored entities.""" + registry = mock_registry(hass) + registry.async_get_or_create( + "test_domain", "test_domain", "1234", suggested_object_id="world" + ) + + hass.states.async_set("test_domain.world", "unavailable", {"restored": True}) + + component = EntityComponent(_LOGGER, DOMAIN, hass) + + await component.async_add_entities( + [MockEntity(unique_id="1234", state="on", entity_id="test_domain.world")], True + ) + + state = hass.states.get("test_domain.world") + assert state.state == "on" diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py index b07c5237116..7f45ff0d174 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -5,7 +5,8 @@ from unittest.mock import patch import asynctest import pytest -from homeassistant.core import callback, valid_entity_id +from homeassistant.const import EVENT_HOMEASSISTANT_START, STATE_UNAVAILABLE +from homeassistant.core import CoreState, callback, valid_entity_id from homeassistant.helpers import entity_registry from tests.common import MockConfigEntry, flush_store, mock_registry @@ -57,6 +58,52 @@ def test_get_or_create_suggested_object_id(registry): assert entry.entity_id == "light.beer" +def test_get_or_create_updates_data(registry): + """Test that we update data in get_or_create.""" + orig_config_entry = MockConfigEntry(domain="light") + + orig_entry = registry.async_get_or_create( + "light", + "hue", + "5678", + config_entry=orig_config_entry, + device_id="mock-dev-id", + capabilities={"max": 100}, + supported_features=5, + device_class="mock-device-class", + disabled_by=entity_registry.DISABLED_HASS, + ) + + assert orig_entry.config_entry_id == orig_config_entry.entry_id + assert orig_entry.device_id == "mock-dev-id" + assert orig_entry.capabilities == {"max": 100} + assert orig_entry.supported_features == 5 + assert orig_entry.device_class == "mock-device-class" + assert orig_entry.disabled_by == entity_registry.DISABLED_HASS + + new_config_entry = MockConfigEntry(domain="light") + + new_entry = registry.async_get_or_create( + "light", + "hue", + "5678", + config_entry=new_config_entry, + device_id="new-mock-dev-id", + capabilities={"new-max": 100}, + supported_features=10, + device_class="new-mock-device-class", + disabled_by=entity_registry.DISABLED_USER, + ) + + assert new_entry.config_entry_id == new_config_entry.entry_id + assert new_entry.device_id == "new-mock-dev-id" + assert new_entry.capabilities == {"new-max": 100} + assert new_entry.supported_features == 10 + assert new_entry.device_class == "new-mock-device-class" + # Should not be updated + assert new_entry.disabled_by == entity_registry.DISABLED_HASS + + def test_get_or_create_suggested_object_id_conflict_register(registry): """Test that we don't generate an entity id that is already registered.""" entry = registry.async_get_or_create( @@ -91,7 +138,15 @@ async def test_loading_saving_data(hass, registry): orig_entry1 = registry.async_get_or_create("light", "hue", "1234") orig_entry2 = registry.async_get_or_create( - "light", "hue", "5678", config_entry=mock_config + "light", + "hue", + "5678", + device_id="mock-dev-id", + config_entry=mock_config, + capabilities={"max": 100}, + supported_features=5, + device_class="mock-device-class", + disabled_by=entity_registry.DISABLED_HASS, ) assert len(registry.entities) == 2 @@ -104,13 +159,17 @@ async 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", config_entry=mock_config - ) + new_entry2 = registry.async_get_or_create("light", "hue", "5678") assert orig_entry1 == new_entry1 assert orig_entry2 == new_entry2 + assert new_entry2.device_id == "mock-dev-id" + assert new_entry2.disabled_by == entity_registry.DISABLED_HASS + assert new_entry2.capabilities == {"max": 100} + assert new_entry2.supported_features == 5 + assert new_entry2.device_class == "mock-device-class" + def test_generate_entity_considers_registered_entities(registry): """Test that we don't create entity id that are already registered.""" @@ -417,3 +476,62 @@ async def test_disabled_by_system_options(registry): "light", "hue", "BBBB", config_entry=mock_config, disabled_by="user" ) assert entry2.disabled_by == "user" + + +async def test_restore_states(hass): + """Test restoring states.""" + hass.state = CoreState.not_running + + registry = await entity_registry.async_get_registry(hass) + + registry.async_get_or_create( + "light", "hue", "1234", suggested_object_id="simple", + ) + # Should not be created + registry.async_get_or_create( + "light", + "hue", + "5678", + suggested_object_id="disabled", + disabled_by=entity_registry.DISABLED_HASS, + ) + registry.async_get_or_create( + "light", + "hue", + "9012", + suggested_object_id="all_info_set", + capabilities={"max": 100}, + supported_features=5, + device_class="mock-device-class", + ) + + hass.bus.async_fire(EVENT_HOMEASSISTANT_START, {}) + await hass.async_block_till_done() + + simple = hass.states.get("light.simple") + assert simple is not None + assert simple.state == STATE_UNAVAILABLE + assert simple.attributes == {"restored": True} + + disabled = hass.states.get("light.disabled") + assert disabled is None + + all_info_set = hass.states.get("light.all_info_set") + assert all_info_set is not None + assert all_info_set.state == STATE_UNAVAILABLE + assert all_info_set.attributes == { + "max": 100, + "supported_features": 5, + "device_class": "mock-device-class", + "restored": True, + } + + registry.async_remove("light.disabled") + registry.async_remove("light.simple") + registry.async_remove("light.all_info_set") + + await hass.async_block_till_done() + + assert hass.states.get("light.simple") is None + assert hass.states.get("light.disabled") is None + assert hass.states.get("light.all_info_set") is None From 5414e9d155c26963428dc3cc4b986b2238c08d1e Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 31 Dec 2019 14:30:09 +0100 Subject: [PATCH 441/677] Fix Withings leaking time zone change into other tests (#30320) * Fix Withings leaking time zone change in other tests * Fix spelling error in code doc --- tests/components/withings/test_common.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/components/withings/test_common.py b/tests/components/withings/test_common.py index 37fcb4ce7f5..6c8c0a4c310 100644 --- a/tests/components/withings/test_common.py +++ b/tests/components/withings/test_common.py @@ -15,6 +15,13 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import PlatformNotReady from homeassistant.util import dt +DEFAULT_TIME_ZONE = dt.DEFAULT_TIME_ZONE + + +def teardown(): + """Ensure the time zone is reverted after tests finish.""" + dt.set_default_time_zone(DEFAULT_TIME_ZONE) + @pytest.fixture(name="withings_api") def withings_api_fixture() -> WithingsApi: From 3f570245aa6ebf4c85a27a2b773b89ff4082d0af Mon Sep 17 00:00:00 2001 From: Issac Date: Tue, 31 Dec 2019 15:34:53 +0200 Subject: [PATCH 442/677] Add local_ip component (#29973) * Added localip component * Split config and core logic, and migrate to sensor platform (requested by @MartinHjelmare) Also allow overriding the sensor name via the config * Tweak docstring Co-Authored-By: Fabian Affolter * Initial support for config entries * Rename localip to local_ip (1/2) * Rename localip to local_ip (2/2) * Add test for config_flow * Split and rename tests * Remove unneeded code from config_flow * Implement configuration as config entry import. Other misc requested changes from code review. * Fix tests * minor code review fixes * remove unneeded code Co-authored-by: Fabian Affolter --- CODEOWNERS | 1 + homeassistant/components/local_ip/__init__.py | 42 +++++++++++++++++++ .../components/local_ip/config_flow.py | 34 +++++++++++++++ .../components/local_ip/manifest.json | 9 ++++ homeassistant/components/local_ip/sensor.py | 34 +++++++++++++++ .../components/local_ip/strings.json | 16 +++++++ homeassistant/generated/config_flows.py | 1 + tests/components/local_ip/__init__.py | 1 + tests/components/local_ip/test_config_flow.py | 19 +++++++++ tests/components/local_ip/test_init.py | 22 ++++++++++ 10 files changed, 179 insertions(+) create mode 100644 homeassistant/components/local_ip/__init__.py create mode 100644 homeassistant/components/local_ip/config_flow.py create mode 100644 homeassistant/components/local_ip/manifest.json create mode 100644 homeassistant/components/local_ip/sensor.py create mode 100644 homeassistant/components/local_ip/strings.json create mode 100644 tests/components/local_ip/__init__.py create mode 100644 tests/components/local_ip/test_config_flow.py create mode 100644 tests/components/local_ip/test_init.py diff --git a/CODEOWNERS b/CODEOWNERS index f5357d1348c..23005cb5273 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -187,6 +187,7 @@ homeassistant/components/life360/* @pnbruckner homeassistant/components/linky/* @Quentame homeassistant/components/linux_battery/* @fabaff homeassistant/components/liveboxplaytv/* @pschmitt +homeassistant/components/local_ip/* @issacg homeassistant/components/logger/* @home-assistant/core homeassistant/components/logi_circle/* @evanjd homeassistant/components/lovelace/* @home-assistant/frontend diff --git a/homeassistant/components/local_ip/__init__.py b/homeassistant/components/local_ip/__init__.py new file mode 100644 index 00000000000..c93b7a5a81b --- /dev/null +++ b/homeassistant/components/local_ip/__init__.py @@ -0,0 +1,42 @@ +"""Get the local IP address of the Home Assistant instance.""" +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_NAME +from homeassistant.core import HomeAssistant +import homeassistant.helpers.config_validation as cv + +DOMAIN = "local_ip" +PLATFORM = "sensor" + +CONFIG_SCHEMA = vol.Schema( + {DOMAIN: vol.Schema({vol.Optional(CONF_NAME, default=DOMAIN): cv.string})}, + extra=vol.ALLOW_EXTRA, +) + + +async def async_setup(hass: HomeAssistant, config: dict): + """Set up local_ip from configuration.yaml.""" + conf = config.get(DOMAIN) + if conf: + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, data=conf, context={"source": config_entries.SOURCE_IMPORT} + ) + ) + + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: config_entries.ConfigEntry): + """Set up local_ip from a config entry.""" + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, PLATFORM) + ) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: config_entries.ConfigEntry): + """Unload a config entry.""" + return await hass.config_entries.async_forward_entry_unload(entry, PLATFORM) diff --git a/homeassistant/components/local_ip/config_flow.py b/homeassistant/components/local_ip/config_flow.py new file mode 100644 index 00000000000..58a666a68f3 --- /dev/null +++ b/homeassistant/components/local_ip/config_flow.py @@ -0,0 +1,34 @@ +"""Config flow for local_ip.""" +import voluptuous as vol + +from homeassistant import config_entries + +from . import DOMAIN + + +class SimpleConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for local_ip.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL + + async def async_step_user(self, user_input=None): + """Handle the initial step.""" + if user_input is not None: + if any( + user_input["name"] == entry.data["name"] + for entry in self._async_current_entries() + ): + return self.async_abort(reason="already_configured") + + return self.async_create_entry(title=user_input["name"], data=user_input) + + return self.async_show_form( + step_id="user", + data_schema=vol.Schema({vol.Required("name", default=DOMAIN): str}), + errors={}, + ) + + async def async_step_import(self, import_info): + """Handle import from config file.""" + return await self.async_step_user(import_info) diff --git a/homeassistant/components/local_ip/manifest.json b/homeassistant/components/local_ip/manifest.json new file mode 100644 index 00000000000..4e97c32afa0 --- /dev/null +++ b/homeassistant/components/local_ip/manifest.json @@ -0,0 +1,9 @@ +{ + "domain": "local_ip", + "name": "Local IP Address", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/local_ip", + "dependencies": [], + "codeowners": ["@issacg"], + "requirements": [] +} diff --git a/homeassistant/components/local_ip/sensor.py b/homeassistant/components/local_ip/sensor.py new file mode 100644 index 00000000000..274a11faec6 --- /dev/null +++ b/homeassistant/components/local_ip/sensor.py @@ -0,0 +1,34 @@ +"""Sensor platform for local_ip.""" + +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity +from homeassistant.util import get_local_ip + + +async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entities): + """Set up the platform from config_entry.""" + name = config_entry.data["name"] + async_add_entities([IPSensor(name)], True) + + +class IPSensor(Entity): + """A simple sensor.""" + + def __init__(self, name: str): + """Initialize the sensor.""" + self._state = None + self._name = name + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + def update(self): + """Fetch new state data for the sensor.""" + self._state = get_local_ip() diff --git a/homeassistant/components/local_ip/strings.json b/homeassistant/components/local_ip/strings.json new file mode 100644 index 00000000000..43a88be3325 --- /dev/null +++ b/homeassistant/components/local_ip/strings.json @@ -0,0 +1,16 @@ +{ + "config": { + "title": "Local IP Address", + "step": { + "user": { + "title": "Local IP Address", + "data": { + "name": "Sensor Name" + } + } + }, + "abort": { + "already_configured": "Integration is already configured with an existing sensor with that name" + } + } +} diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 55a4d76fdcd..135fab2b746 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -46,6 +46,7 @@ FLOWS = [ "life360", "lifx", "linky", + "local_ip", "locative", "logi_circle", "luftdaten", diff --git a/tests/components/local_ip/__init__.py b/tests/components/local_ip/__init__.py new file mode 100644 index 00000000000..47e1f70fcf8 --- /dev/null +++ b/tests/components/local_ip/__init__.py @@ -0,0 +1 @@ +"""Tests for the local_ip integration.""" diff --git a/tests/components/local_ip/test_config_flow.py b/tests/components/local_ip/test_config_flow.py new file mode 100644 index 00000000000..f355e5c75b2 --- /dev/null +++ b/tests/components/local_ip/test_config_flow.py @@ -0,0 +1,19 @@ +"""Tests for the local_ip config_flow.""" +from homeassistant.components.local_ip import DOMAIN + + +async def test_config_flow(hass): + """Test we can finish a config flow.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"} + ) + assert result["type"] == "form" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"name": "test"} + ) + assert result["type"] == "create_entry" + + await hass.async_block_till_done() + state = hass.states.get("sensor.test") + assert state diff --git a/tests/components/local_ip/test_init.py b/tests/components/local_ip/test_init.py new file mode 100644 index 00000000000..fb43f06eea2 --- /dev/null +++ b/tests/components/local_ip/test_init.py @@ -0,0 +1,22 @@ +"""Tests for the local_ip component.""" +import pytest + +from homeassistant.components.local_ip import DOMAIN +from homeassistant.setup import async_setup_component +from homeassistant.util import get_local_ip + + +@pytest.fixture(name="config") +def config_fixture(): + """Create hass config fixture.""" + return {DOMAIN: {"name": "test"}} + + +async def test_basic_setup(hass, config): + """Test component setup creates entry from config.""" + assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() + local_ip = await hass.async_add_executor_job(get_local_ip) + state = hass.states.get("sensor.test") + assert state + assert state.state == local_ip From 72e97792bd083d905640bc5e17b1f33710c8dd88 Mon Sep 17 00:00:00 2001 From: Philipp Schmitt Date: Tue, 31 Dec 2019 14:38:46 +0100 Subject: [PATCH 443/677] Update liveboxplay and pyteleloisirs (#30093) --- homeassistant/components/liveboxplaytv/manifest.json | 4 ++-- requirements_all.txt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/liveboxplaytv/manifest.json b/homeassistant/components/liveboxplaytv/manifest.json index bcb2b53f081..39692b1e282 100644 --- a/homeassistant/components/liveboxplaytv/manifest.json +++ b/homeassistant/components/liveboxplaytv/manifest.json @@ -3,8 +3,8 @@ "name": "Liveboxplaytv", "documentation": "https://www.home-assistant.io/integrations/liveboxplaytv", "requirements": [ - "liveboxplaytv==2.0.2", - "pyteleloisirs==3.5" + "liveboxplaytv==2.0.3", + "pyteleloisirs==3.6" ], "dependencies": [], "codeowners": [ diff --git a/requirements_all.txt b/requirements_all.txt index d84815a42f3..55dcf41e6d4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -792,7 +792,7 @@ limitlessled==1.1.3 linode-api==4.1.9b1 # homeassistant.components.liveboxplaytv -liveboxplaytv==2.0.2 +liveboxplaytv==2.0.3 # homeassistant.components.lametric lmnotify==0.0.4 @@ -1532,7 +1532,7 @@ pysyncthru==0.5.0 pytautulli==0.5.0 # homeassistant.components.liveboxplaytv -pyteleloisirs==3.5 +pyteleloisirs==3.6 # homeassistant.components.tfiac pytfiac==0.4 From 4bfc7b984852398238053cde0e9dcf334bd5f4c7 Mon Sep 17 00:00:00 2001 From: Steven Barth Date: Tue, 31 Dec 2019 14:41:44 +0100 Subject: [PATCH 444/677] Add homematic host port config for HMIP-only CCUs (#30077) * homematic: Add host port config for HMIP-only CCUs When adding a host (CCU) to the homematic component currently the hardcoded port 2001 is used to communicate with it. However that port is only available on the target if the target supports HM (wireless) protocol which is not the case e.g. for the Hass.io Homematic CCU addon when running in HMIP-only mode with the HMIP-RFUSB stick. This allows to change the port home assistant uses to talk to the CCU in order to provide services under hte homematic homain, e.g. homematic.set_variable_value The default value for this option is the old hardcoded value this the change should be backwards compatible with existing configurations. * Change style of config retrieval --- homeassistant/components/homematic/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/homematic/__init__.py b/homeassistant/components/homematic/__init__.py index 828e170c67d..d0776baa106 100644 --- a/homeassistant/components/homematic/__init__.py +++ b/homeassistant/components/homematic/__init__.py @@ -298,6 +298,7 @@ CONFIG_SCHEMA = vol.Schema( vol.Optional(CONF_HOSTS, default={}): { cv.match_all: { vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, vol.Optional( CONF_USERNAME, default=DEFAULT_USERNAME ): cv.string, @@ -392,7 +393,7 @@ def setup(hass, config): for sname, sconfig in conf[CONF_HOSTS].items(): remotes[sname] = { "ip": sconfig.get(CONF_HOST), - "port": DEFAULT_PORT, + "port": sconfig[CONF_PORT], "username": sconfig.get(CONF_USERNAME), "password": sconfig.get(CONF_PASSWORD), "connect": False, From b0a0871bed9c6ba0360e4de19d6e48fdc6b80250 Mon Sep 17 00:00:00 2001 From: SukramJ Date: Tue, 31 Dec 2019 14:45:17 +0100 Subject: [PATCH 445/677] Bump dependency for HomematicIP Cloud (#30319) --- homeassistant/components/homematicip_cloud/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/homematicip_cloud/manifest.json b/homeassistant/components/homematicip_cloud/manifest.json index 9f965bfdb6d..14677dd71a0 100644 --- a/homeassistant/components/homematicip_cloud/manifest.json +++ b/homeassistant/components/homematicip_cloud/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/homematicip_cloud", "requirements": [ - "homematicip==0.10.14" + "homematicip==0.10.15" ], "dependencies": [], "codeowners": [ diff --git a/requirements_all.txt b/requirements_all.txt index 55dcf41e6d4..15cc43e5356 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -678,7 +678,7 @@ homeassistant-pyozw==0.1.7 homekit[IP]==0.15.0 # homeassistant.components.homematicip_cloud -homematicip==0.10.14 +homematicip==0.10.15 # homeassistant.components.horizon horimote==0.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d197ed0196a..25d16e27ce8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -243,7 +243,7 @@ homeassistant-pyozw==0.1.7 homekit[IP]==0.15.0 # homeassistant.components.homematicip_cloud -homematicip==0.10.14 +homematicip==0.10.15 # homeassistant.components.google # homeassistant.components.remember_the_milk From dedd1aec8c75d1c9c364bdeda3420d4b31a020db Mon Sep 17 00:00:00 2001 From: rhadamantys <46837767+rhadamantys@users.noreply.github.com> Date: Tue, 31 Dec 2019 14:48:31 +0100 Subject: [PATCH 446/677] Add support for Somfy RTS power socket and Somfy io Temperature sensor (#30053) * Added support for Somfy RTS wireless power socket and Somfy Temperature Sensore Thermos Wirefree io * Added code formatting fixes for commit 5faaf9c * added support for RollerShutterRTSComponent from Somfy * Added support for RTS roller shutter in set_cover_position * Add support for Somfy RTS power socket and Somfy io temperature sensor * black and isort fixes --- homeassistant/components/tahoma/__init__.py | 2 ++ homeassistant/components/tahoma/sensor.py | 11 ++++++++--- homeassistant/components/tahoma/switch.py | 11 ++++++++--- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/tahoma/__init__.py b/homeassistant/components/tahoma/__init__.py index 640cc6418d0..6bb4fc200af 100644 --- a/homeassistant/components/tahoma/__init__.py +++ b/homeassistant/components/tahoma/__init__.py @@ -45,6 +45,7 @@ TAHOMA_TYPES = { "io:RollerShutterWithLowSpeedManagementIOComponent": "cover", "io:SomfyBasicContactIOSystemSensor": "sensor", "io:SomfyContactIOSystemSensor": "sensor", + "io:TemperatureIOSystemSensor": "sensor", "io:VerticalExteriorAwningIOComponent": "cover", "io:VerticalInteriorBlindVeluxIOComponent": "cover", "io:WindowOpenerVeluxIOComponent": "cover", @@ -59,6 +60,7 @@ TAHOMA_TYPES = { "rts:ExteriorVenetianBlindRTSComponent": "cover", "rts:GarageDoor4TRTSComponent": "switch", "rts:RollerShutterRTSComponent": "cover", + "rts:OnOffRTSComponent": "switch", "rts:VenetianBlindRTSComponent": "cover", } diff --git a/homeassistant/components/tahoma/sensor.py b/homeassistant/components/tahoma/sensor.py index 5279b160d9c..85ccb55761d 100644 --- a/homeassistant/components/tahoma/sensor.py +++ b/homeassistant/components/tahoma/sensor.py @@ -2,7 +2,7 @@ from datetime import timedelta import logging -from homeassistant.const import ATTR_BATTERY_LEVEL +from homeassistant.const import ATTR_BATTERY_LEVEL, TEMP_CELSIUS from homeassistant.helpers.entity import Entity from . import DOMAIN as TAHOMA_DOMAIN, TahomaDevice @@ -40,8 +40,8 @@ class TahomaSensor(TahomaDevice, Entity): @property def unit_of_measurement(self): """Return the unit of measurement of this entity, if any.""" - if self.tahoma_device.type == "Temperature Sensor": - return None + if self.tahoma_device.type == "io:TemperatureIOSystemSensor": + return TEMP_CELSIUS if self.tahoma_device.type == "io:SomfyContactIOSystemSensor": return None if self.tahoma_device.type == "io:SomfyBasicContactIOSystemSensor": @@ -79,6 +79,11 @@ class TahomaSensor(TahomaDevice, Entity): if self.tahoma_device.type == "rtds:RTDSMotionSensor": self.current_value = self.tahoma_device.active_states["core:OccupancyState"] self._available = True + if self.tahoma_device.type == "io:TemperatureIOSystemSensor": + self.current_value = round( + float(self.tahoma_device.active_states["core:TemperatureState"]), 1 + ) + self._available = True _LOGGER.debug("Update %s, value: %d", self._name, self.current_value) diff --git a/homeassistant/components/tahoma/switch.py b/homeassistant/components/tahoma/switch.py index a0a95ab47ce..1612120f313 100644 --- a/homeassistant/components/tahoma/switch.py +++ b/homeassistant/components/tahoma/switch.py @@ -45,9 +45,14 @@ class TahomaSwitch(TahomaDevice, SwitchDevice): else: self._state = STATE_OFF - self._available = bool( - self.tahoma_device.active_states.get("core:StatusState") == "available" - ) + # A RTS power socket doesn't have a feedback channel, + # so we must assume the socket is available. + if self.tahoma_device.type == "rts:OnOffRTSComponent": + self._available = True + else: + self._available = bool( + self.tahoma_device.active_states.get("core:StatusState") == "available" + ) _LOGGER.debug("Update %s, state: %s", self._name, self._state) From e68cd339b962b2f43064ed32e0eb37a78da56d58 Mon Sep 17 00:00:00 2001 From: Shawn Wilsher <656602+sdwilsh@users.noreply.github.com> Date: Tue, 31 Dec 2019 05:56:23 -0800 Subject: [PATCH 447/677] Reduce solaredge logging severity (#30305) * [solaredge] Reduce Severity of Log Line This log error happens frequently for some sites, but it shouldn't be an error. It is expected, per the SolarEdge Monitoring API, that some sites do not support this information, and the expected result is that this would be empty (see comments on #27959). Fixes #27959 * Fix a typo Co-authored-by: Fabian Affolter --- homeassistant/components/solaredge/sensor.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/solaredge/sensor.py b/homeassistant/components/solaredge/sensor.py index f0f1660a821..60cabaf38f0 100644 --- a/homeassistant/components/solaredge/sensor.py +++ b/homeassistant/components/solaredge/sensor.py @@ -41,7 +41,7 @@ async def async_setup_entry(hass, entry, async_add_entities): return _LOGGER.debug("Credentials correct and site is active") except KeyError: - _LOGGER.error("Missing details data in solaredge response") + _LOGGER.error("Missing details data in SolarEdge response") return except (ConnectTimeout, HTTPError): _LOGGER.error("Could not retrieve details from SolarEdge API") @@ -350,7 +350,9 @@ class SolarEdgePowerFlowDataService(SolarEdgeDataService): power_to = [] if "connections" not in power_flow: - _LOGGER.error("Missing connections in power flow data") + _LOGGER.debug( + "Missing connections in power flow data. Assuming site does not have any" + ) return for connection in power_flow["connections"]: From 609bf445f0f64b9e1d3f78169b2d0e179010a740 Mon Sep 17 00:00:00 2001 From: Phil Bruckner Date: Tue, 31 Dec 2019 08:16:41 -0600 Subject: [PATCH 448/677] Remove Amcrest deprecated sensors and switches (#30308) --- homeassistant/components/amcrest/__init__.py | 93 ++++---------- homeassistant/components/amcrest/sensor.py | 8 +- homeassistant/components/amcrest/switch.py | 126 ------------------- 3 files changed, 27 insertions(+), 200 deletions(-) delete mode 100644 homeassistant/components/amcrest/switch.py diff --git a/homeassistant/components/amcrest/__init__.py b/homeassistant/components/amcrest/__init__.py index 3420f42f9c9..b934a7e0549 100644 --- a/homeassistant/components/amcrest/__init__.py +++ b/homeassistant/components/amcrest/__init__.py @@ -11,7 +11,6 @@ from homeassistant.auth.permissions.const import POLICY_CONTROL from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR from homeassistant.components.camera import DOMAIN as CAMERA from homeassistant.components.sensor import DOMAIN as SENSOR -from homeassistant.components.switch import DOMAIN as SWITCH from homeassistant.const import ( ATTR_ENTITY_ID, CONF_AUTHENTICATION, @@ -22,7 +21,6 @@ from homeassistant.const import ( CONF_PORT, CONF_SCAN_INTERVAL, CONF_SENSORS, - CONF_SWITCHES, CONF_USERNAME, ENTITY_MATCH_ALL, HTTP_BASIC_AUTHENTICATION, @@ -34,12 +32,11 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send, dispatcher_s from homeassistant.helpers.event import track_time_interval from homeassistant.helpers.service import async_extract_entity_ids -from .binary_sensor import BINARY_SENSOR_MOTION_DETECTED, BINARY_SENSORS +from .binary_sensor import BINARY_SENSORS from .camera import CAMERA_SERVICES, STREAM_SOURCE_LIST from .const import CAMERAS, DATA_AMCREST, DEVICES, DOMAIN, SERVICE_UPDATE from .helpers import service_signal -from .sensor import SENSOR_MOTION_DETECTOR, SENSORS -from .switch import SWITCHES +from .sensor import SENSORS _LOGGER = logging.getLogger(__name__) @@ -65,68 +62,36 @@ SCAN_INTERVAL = timedelta(seconds=10) AUTHENTICATION_LIST = {"basic": "basic"} -def _deprecated_sensor_values(sensors): - if SENSOR_MOTION_DETECTOR in sensors: - _LOGGER.warning( - "The '%s' option value '%s' is deprecated, " - "please remove it from your configuration and use " - "the '%s' option with value '%s' instead", - CONF_SENSORS, - SENSOR_MOTION_DETECTOR, - CONF_BINARY_SENSORS, - BINARY_SENSOR_MOTION_DETECTED, - ) - return sensors - - -def _deprecated_switches(config): - if CONF_SWITCHES in config: - _LOGGER.warning( - "The '%s' option (with value %s) is deprecated, " - "please remove it from your configuration and use " - "services and attributes instead", - CONF_SWITCHES, - config[CONF_SWITCHES], - ) - return config - - def _has_unique_names(devices): names = [device[CONF_NAME] for device in devices] vol.Schema(vol.Unique())(names) return devices -AMCREST_SCHEMA = vol.All( - vol.Schema( - { - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional( - CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION - ): vol.All(vol.In(AUTHENTICATION_LIST)), - vol.Optional(CONF_RESOLUTION, default=DEFAULT_RESOLUTION): vol.All( - vol.In(RESOLUTION_LIST) - ), - vol.Optional(CONF_STREAM_SOURCE, default=STREAM_SOURCE_LIST[0]): vol.All( - vol.In(STREAM_SOURCE_LIST) - ), - vol.Optional(CONF_FFMPEG_ARGUMENTS, default=DEFAULT_ARGUMENTS): cv.string, - vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL): cv.time_period, - vol.Optional(CONF_BINARY_SENSORS): vol.All( - cv.ensure_list, [vol.In(BINARY_SENSORS)] - ), - vol.Optional(CONF_SENSORS): vol.All( - cv.ensure_list, [vol.In(SENSORS)], _deprecated_sensor_values - ), - vol.Optional(CONF_SWITCHES): vol.All(cv.ensure_list, [vol.In(SWITCHES)]), - vol.Optional(CONF_CONTROL_LIGHT, default=True): cv.boolean, - } - ), - _deprecated_switches, +AMCREST_SCHEMA = vol.Schema( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION): vol.All( + vol.In(AUTHENTICATION_LIST) + ), + vol.Optional(CONF_RESOLUTION, default=DEFAULT_RESOLUTION): vol.All( + vol.In(RESOLUTION_LIST) + ), + vol.Optional(CONF_STREAM_SOURCE, default=STREAM_SOURCE_LIST[0]): vol.All( + vol.In(STREAM_SOURCE_LIST) + ), + vol.Optional(CONF_FFMPEG_ARGUMENTS, default=DEFAULT_ARGUMENTS): cv.string, + vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL): cv.time_period, + vol.Optional(CONF_BINARY_SENSORS): vol.All( + cv.ensure_list, [vol.In(BINARY_SENSORS)] + ), + vol.Optional(CONF_SENSORS): vol.All(cv.ensure_list, [vol.In(SENSORS)]), + vol.Optional(CONF_CONTROL_LIGHT, default=True): cv.boolean, + } ) CONFIG_SCHEMA = vol.Schema( @@ -216,7 +181,6 @@ def setup(hass, config): resolution = RESOLUTION_LIST[device[CONF_RESOLUTION]] binary_sensors = device.get(CONF_BINARY_SENSORS) sensors = device.get(CONF_SENSORS) - switches = device.get(CONF_SWITCHES) stream_source = device[CONF_STREAM_SOURCE] control_light = device.get(CONF_CONTROL_LIGHT) @@ -252,11 +216,6 @@ def setup(hass, config): hass, SENSOR, DOMAIN, {CONF_NAME: name, CONF_SENSORS: sensors}, config ) - if switches: - discovery.load_platform( - hass, SWITCH, DOMAIN, {CONF_NAME: name, CONF_SWITCHES: switches}, config - ) - if not hass.data[DATA_AMCREST][DEVICES]: return False diff --git a/homeassistant/components/amcrest/sensor.py b/homeassistant/components/amcrest/sensor.py index b53f05273fa..be03b3bedff 100644 --- a/homeassistant/components/amcrest/sensor.py +++ b/homeassistant/components/amcrest/sensor.py @@ -15,12 +15,10 @@ _LOGGER = logging.getLogger(__name__) SCAN_INTERVAL = timedelta(seconds=SENSOR_SCAN_INTERVAL_SECS) -SENSOR_MOTION_DETECTOR = "motion_detector" SENSOR_PTZ_PRESET = "ptz_preset" SENSOR_SDCARD = "sdcard" # Sensor types are defined like: Name, units, icon SENSORS = { - SENSOR_MOTION_DETECTOR: ["Motion Detected", None, "mdi:run"], SENSOR_PTZ_PRESET: ["PTZ Preset", None, "mdi:camera-iris"], SENSOR_SDCARD: ["SD Used", "%", "mdi:sd"], } @@ -94,11 +92,7 @@ class AmcrestSensor(Entity): _LOGGER.debug("Updating %s sensor", self._name) try: - if self._sensor_type == SENSOR_MOTION_DETECTOR: - self._state = self._api.is_motion_detected - self._attrs["Record Mode"] = self._api.record_mode - - elif self._sensor_type == SENSOR_PTZ_PRESET: + if self._sensor_type == SENSOR_PTZ_PRESET: self._state = self._api.ptz_presets_count elif self._sensor_type == SENSOR_SDCARD: diff --git a/homeassistant/components/amcrest/switch.py b/homeassistant/components/amcrest/switch.py deleted file mode 100644 index 0c3390c16f9..00000000000 --- a/homeassistant/components/amcrest/switch.py +++ /dev/null @@ -1,126 +0,0 @@ -"""Support for toggling Amcrest IP camera settings.""" -import logging - -from amcrest import AmcrestError - -from homeassistant.const import CONF_NAME, CONF_SWITCHES -from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import ToggleEntity - -from .const import DATA_AMCREST, DEVICES, SERVICE_UPDATE -from .helpers import log_update_error, service_signal - -_LOGGER = logging.getLogger(__name__) - -MOTION_DETECTION = "motion_detection" -MOTION_RECORDING = "motion_recording" -# Switch types are defined like: Name, icon -SWITCHES = { - MOTION_DETECTION: ["Motion Detection", "mdi:run-fast"], - MOTION_RECORDING: ["Motion Recording", "mdi:record-rec"], -} - - -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the IP Amcrest camera switch platform.""" - if discovery_info is None: - return - - name = discovery_info[CONF_NAME] - device = hass.data[DATA_AMCREST][DEVICES][name] - async_add_entities( - [ - AmcrestSwitch(name, device, setting) - for setting in discovery_info[CONF_SWITCHES] - ], - True, - ) - - -class AmcrestSwitch(ToggleEntity): - """Representation of an Amcrest IP camera switch.""" - - def __init__(self, name, device, setting): - """Initialize the Amcrest switch.""" - self._name = "{} {}".format(name, SWITCHES[setting][0]) - self._signal_name = name - self._api = device.api - self._setting = setting - self._state = False - self._icon = SWITCHES[setting][1] - self._unsub_dispatcher = None - - @property - def name(self): - """Return the name of the switch if any.""" - return self._name - - @property - def is_on(self): - """Return true if switch is on.""" - return self._state - - def turn_on(self, **kwargs): - """Turn setting on.""" - if not self.available: - return - try: - if self._setting == MOTION_DETECTION: - self._api.motion_detection = "true" - elif self._setting == MOTION_RECORDING: - self._api.motion_recording = "true" - except AmcrestError as error: - log_update_error(_LOGGER, "turn on", self.name, "switch", error) - - def turn_off(self, **kwargs): - """Turn setting off.""" - if not self.available: - return - try: - if self._setting == MOTION_DETECTION: - self._api.motion_detection = "false" - elif self._setting == MOTION_RECORDING: - self._api.motion_recording = "false" - except AmcrestError as error: - log_update_error(_LOGGER, "turn off", self.name, "switch", error) - - @property - def available(self): - """Return True if entity is available.""" - return self._api.available - - def update(self): - """Update setting state.""" - if not self.available: - return - _LOGGER.debug("Updating %s switch", self._name) - - try: - if self._setting == MOTION_DETECTION: - detection = self._api.is_motion_detector_on() - elif self._setting == MOTION_RECORDING: - detection = self._api.is_record_on_motion_detection() - self._state = detection - except AmcrestError as error: - log_update_error(_LOGGER, "update", self.name, "switch", error) - - @property - def icon(self): - """Return the icon for the switch.""" - return self._icon - - async def async_on_demand_update(self): - """Update state.""" - self.async_schedule_update_ha_state(True) - - async def async_added_to_hass(self): - """Subscribe to update signal.""" - self._unsub_dispatcher = async_dispatcher_connect( - self.hass, - service_signal(SERVICE_UPDATE, self._signal_name), - self.async_on_demand_update, - ) - - async def async_will_remove_from_hass(self): - """Disconnect from update signal.""" - self._unsub_dispatcher() From fd0375ac204c3ebfc8304d7a57b662e257af41ec Mon Sep 17 00:00:00 2001 From: Christian Date: Tue, 31 Dec 2019 15:17:17 +0100 Subject: [PATCH 449/677] Add support for Velux garage doors (#30214) * Update manifest.json * Update cover.py Add GarageDoor * Update to pyvlx 0.2.12 * Sort --- homeassistant/components/velux/cover.py | 4 +++- homeassistant/components/velux/manifest.json | 2 +- requirements_all.txt | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/velux/cover.py b/homeassistant/components/velux/cover.py index 7d4adc7350c..c9b4aa53fe5 100644 --- a/homeassistant/components/velux/cover.py +++ b/homeassistant/components/velux/cover.py @@ -1,6 +1,6 @@ """Support for Velux covers.""" from pyvlx import OpeningDevice, Position -from pyvlx.opening_device import Awning, Blind, RollerShutter, Window +from pyvlx.opening_device import Awning, Blind, GarageDoor, RollerShutter, Window from homeassistant.components.cover import ( ATTR_POSITION, @@ -77,6 +77,8 @@ class VeluxCover(CoverDevice): return "shutter" if isinstance(self.node, Awning): return "awning" + if isinstance(self.node, GarageDoor): + return "garage" return "window" @property diff --git a/homeassistant/components/velux/manifest.json b/homeassistant/components/velux/manifest.json index 783e23a8171..d2fbb3b728a 100644 --- a/homeassistant/components/velux/manifest.json +++ b/homeassistant/components/velux/manifest.json @@ -3,7 +3,7 @@ "name": "Velux", "documentation": "https://www.home-assistant.io/integrations/velux", "requirements": [ - "pyvlx==0.2.11" + "pyvlx==0.2.12" ], "dependencies": [], "codeowners": [ diff --git a/requirements_all.txt b/requirements_all.txt index 15cc43e5356..0e163221d11 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1692,7 +1692,7 @@ pyvesync==1.1.0 pyvizio==0.0.7 # homeassistant.components.velux -pyvlx==0.2.11 +pyvlx==0.2.12 # homeassistant.components.html5 pywebpush==1.9.2 From 1c2618d99a22b12a73b5ea5959b66985cafc1910 Mon Sep 17 00:00:00 2001 From: Iulius Date: Tue, 31 Dec 2019 15:24:09 +0100 Subject: [PATCH 450/677] Add separate command and state topics for mqtt lock (#29808) * Update lock.py Allow different command and state topic + different command and state values. * Formatting updated after black run * TC updated to reflect different state & cmd values * Abbreviations for lock states added * additional non-default state test * whitespaces fixed * black formatting run --- .../components/mqtt/abbreviations.py | 2 + homeassistant/components/mqtt/lock.py | 14 ++- tests/components/mqtt/test_lock.py | 88 ++++++++++++++++++- 3 files changed, 98 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/mqtt/abbreviations.py b/homeassistant/components/mqtt/abbreviations.py index 5e995494a64..6f9b1720102 100644 --- a/homeassistant/components/mqtt/abbreviations.py +++ b/homeassistant/components/mqtt/abbreviations.py @@ -135,6 +135,8 @@ ABBREVIATIONS = { "stat_off": "state_off", "stat_on": "state_on", "stat_open": "state_open", + "stat_locked": "state_locked", + "stat_unlocked": "state_unlocked", "stat_t": "state_topic", "stat_tpl": "state_template", "stat_val_tpl": "state_value_template", diff --git a/homeassistant/components/mqtt/lock.py b/homeassistant/components/mqtt/lock.py index ccf8f2569fa..6910e955288 100644 --- a/homeassistant/components/mqtt/lock.py +++ b/homeassistant/components/mqtt/lock.py @@ -36,10 +36,16 @@ _LOGGER = logging.getLogger(__name__) CONF_PAYLOAD_LOCK = "payload_lock" CONF_PAYLOAD_UNLOCK = "payload_unlock" +CONF_STATE_LOCKED = "state_locked" +CONF_STATE_UNLOCKED = "state_unlocked" + DEFAULT_NAME = "MQTT Lock" DEFAULT_OPTIMISTIC = False DEFAULT_PAYLOAD_LOCK = "LOCK" DEFAULT_PAYLOAD_UNLOCK = "UNLOCK" +DEFAULT_STATE_LOCKED = "LOCKED" +DEFAULT_STATE_UNLOCKED = "UNLOCKED" + PLATFORM_SCHEMA = ( mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( { @@ -50,6 +56,10 @@ PLATFORM_SCHEMA = ( vol.Optional( CONF_PAYLOAD_UNLOCK, default=DEFAULT_PAYLOAD_UNLOCK ): cv.string, + vol.Optional(CONF_STATE_LOCKED, default=DEFAULT_STATE_LOCKED): cv.string, + vol.Optional( + CONF_STATE_UNLOCKED, default=DEFAULT_STATE_UNLOCKED + ): cv.string, vol.Optional(CONF_UNIQUE_ID): cv.string, } ) @@ -152,9 +162,9 @@ class MqttLock( payload = msg.payload if value_template is not None: payload = value_template.async_render_with_possible_json_value(payload) - if payload == self._config[CONF_PAYLOAD_LOCK]: + if payload == self._config[CONF_STATE_LOCKED]: self._state = True - elif payload == self._config[CONF_PAYLOAD_UNLOCK]: + elif payload == self._config[CONF_STATE_UNLOCKED]: self._state = False self.async_write_ha_state() diff --git a/tests/components/mqtt/test_lock.py b/tests/components/mqtt/test_lock.py index fbaedd3f945..9b89fa7159d 100644 --- a/tests/components/mqtt/test_lock.py +++ b/tests/components/mqtt/test_lock.py @@ -34,6 +34,8 @@ async def test_controlling_state_via_topic(hass, mqtt_mock): "command_topic": "command-topic", "payload_lock": "LOCK", "payload_unlock": "UNLOCK", + "state_locked": "LOCKED", + "state_unlocked": "UNLOCKED", } }, ) @@ -42,12 +44,46 @@ async def test_controlling_state_via_topic(hass, mqtt_mock): assert state.state is STATE_UNLOCKED assert not state.attributes.get(ATTR_ASSUMED_STATE) - async_fire_mqtt_message(hass, "state-topic", "LOCK") + async_fire_mqtt_message(hass, "state-topic", "LOCKED") state = hass.states.get("lock.test") assert state.state is STATE_LOCKED - async_fire_mqtt_message(hass, "state-topic", "UNLOCK") + async_fire_mqtt_message(hass, "state-topic", "UNLOCKED") + + state = hass.states.get("lock.test") + assert state.state is STATE_UNLOCKED + + +async def test_controlling_non_default_state_via_topic(hass, mqtt_mock): + """Test the controlling state via topic.""" + assert await async_setup_component( + hass, + lock.DOMAIN, + { + lock.DOMAIN: { + "platform": "mqtt", + "name": "test", + "state_topic": "state-topic", + "command_topic": "command-topic", + "payload_lock": "LOCK", + "payload_unlock": "UNLOCK", + "state_locked": "closed", + "state_unlocked": "open", + } + }, + ) + + state = hass.states.get("lock.test") + assert state.state is STATE_UNLOCKED + assert not state.attributes.get(ATTR_ASSUMED_STATE) + + async_fire_mqtt_message(hass, "state-topic", "closed") + + state = hass.states.get("lock.test") + assert state.state is STATE_LOCKED + + async_fire_mqtt_message(hass, "state-topic", "open") state = hass.states.get("lock.test") assert state.state is STATE_UNLOCKED @@ -66,6 +102,8 @@ async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock): "command_topic": "command-topic", "payload_lock": "LOCK", "payload_unlock": "UNLOCK", + "state_locked": "LOCKED", + "state_unlocked": "UNLOCKED", "value_template": "{{ value_json.val }}", } }, @@ -74,12 +112,48 @@ async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock): state = hass.states.get("lock.test") assert state.state is STATE_UNLOCKED - async_fire_mqtt_message(hass, "state-topic", '{"val":"LOCK"}') + async_fire_mqtt_message(hass, "state-topic", '{"val":"LOCKED"}') state = hass.states.get("lock.test") assert state.state is STATE_LOCKED - async_fire_mqtt_message(hass, "state-topic", '{"val":"UNLOCK"}') + async_fire_mqtt_message(hass, "state-topic", '{"val":"UNLOCKED"}') + + state = hass.states.get("lock.test") + assert state.state is STATE_UNLOCKED + + +async def test_controlling_non_default_state_via_topic_and_json_message( + hass, mqtt_mock +): + """Test the controlling state via topic and JSON message.""" + assert await async_setup_component( + hass, + lock.DOMAIN, + { + lock.DOMAIN: { + "platform": "mqtt", + "name": "test", + "state_topic": "state-topic", + "command_topic": "command-topic", + "payload_lock": "LOCK", + "payload_unlock": "UNLOCK", + "state_locked": "closed", + "state_unlocked": "open", + "value_template": "{{ value_json.val }}", + } + }, + ) + + state = hass.states.get("lock.test") + assert state.state is STATE_UNLOCKED + + async_fire_mqtt_message(hass, "state-topic", '{"val":"closed"}') + + state = hass.states.get("lock.test") + assert state.state is STATE_LOCKED + + async_fire_mqtt_message(hass, "state-topic", '{"val":"open"}') state = hass.states.get("lock.test") assert state.state is STATE_UNLOCKED @@ -97,6 +171,8 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): "command_topic": "command-topic", "payload_lock": "LOCK", "payload_unlock": "UNLOCK", + "state_locked": "LOCKED", + "state_unlocked": "UNLOCKED", } }, ) @@ -135,6 +211,8 @@ async def test_sending_mqtt_commands_and_explicit_optimistic(hass, mqtt_mock): "command_topic": "command-topic", "payload_lock": "LOCK", "payload_unlock": "UNLOCK", + "state_locked": "LOCKED", + "state_unlocked": "UNLOCKED", "optimistic": True, } }, @@ -206,6 +284,8 @@ async def test_custom_availability_payload(hass, mqtt_mock): "command_topic": "command-topic", "payload_lock": "LOCK", "payload_unlock": "UNLOCK", + "state_locked": "LOCKED", + "state_unlocked": "UNLOCKED", "availability_topic": "availability-topic", "payload_available": "good", "payload_not_available": "nogood", From 734ef5a7a9070dd02dee1c5616188c54b6df4d36 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 31 Dec 2019 15:32:26 +0100 Subject: [PATCH 451/677] Upgrade Sphinx to 2.3.1 (#30310) --- requirements_docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_docs.txt b/requirements_docs.txt index 55f0f2d162d..a27f3a4a306 100644 --- a/requirements_docs.txt +++ b/requirements_docs.txt @@ -1,3 +1,3 @@ -Sphinx==2.2.2 +Sphinx==2.3.1 sphinx-autodoc-typehints==1.10.3 sphinx-autodoc-annotation==1.0.post1 \ No newline at end of file From 3c8ebf184459b7d647d2c163c8ed613133d3d160 Mon Sep 17 00:00:00 2001 From: brefra Date: Tue, 31 Dec 2019 15:46:02 +0100 Subject: [PATCH 452/677] Add light support to Velbus integration (#30323) * Add light support to Velbus integration * Add Velbus light.py to .coveragerc * Applied black formatting --- .coveragerc | 1 + homeassistant/components/velbus/__init__.py | 2 +- homeassistant/components/velbus/light.py | 77 +++++++++++++++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/velbus/light.py diff --git a/.coveragerc b/.coveragerc index 10d56c4701d..e96895429a6 100644 --- a/.coveragerc +++ b/.coveragerc @@ -758,6 +758,7 @@ omit = homeassistant/components/velbus/climate.py homeassistant/components/velbus/const.py homeassistant/components/velbus/cover.py + homeassistant/components/velbus/light.py homeassistant/components/velbus/sensor.py homeassistant/components/velbus/switch.py homeassistant/components/velux/* diff --git a/homeassistant/components/velbus/__init__.py b/homeassistant/components/velbus/__init__.py index 317c305254b..de48f846540 100644 --- a/homeassistant/components/velbus/__init__.py +++ b/homeassistant/components/velbus/__init__.py @@ -22,7 +22,7 @@ CONFIG_SCHEMA = vol.Schema( {DOMAIN: vol.Schema({vol.Required(CONF_PORT): cv.string})}, extra=vol.ALLOW_EXTRA ) -COMPONENT_TYPES = ["switch", "sensor", "binary_sensor", "cover", "climate"] +COMPONENT_TYPES = ["switch", "sensor", "binary_sensor", "cover", "climate", "light"] async def async_setup(hass, config): diff --git a/homeassistant/components/velbus/light.py b/homeassistant/components/velbus/light.py new file mode 100644 index 00000000000..6b34182e559 --- /dev/null +++ b/homeassistant/components/velbus/light.py @@ -0,0 +1,77 @@ +"""Support for Velbus light.""" +import logging + +from velbus.util import VelbusException + +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + ATTR_TRANSITION, + SUPPORT_BRIGHTNESS, + SUPPORT_TRANSITION, + Light, +) + +from . import VelbusEntity +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Old way.""" + pass + + +async def async_setup_entry(hass, entry, async_add_entities): + """Set up Velbus light based on config_entry.""" + cntrl = hass.data[DOMAIN][entry.entry_id]["cntrl"] + modules_data = hass.data[DOMAIN][entry.entry_id]["light"] + entities = [] + for address, channel in modules_data: + module = cntrl.get_module(address) + entities.append(VelbusLight(module, channel)) + async_add_entities(entities) + + +class VelbusLight(VelbusEntity, Light): + """Representation of a Velbus light.""" + + @property + def supported_features(self): + """Flag supported features.""" + return SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION + + @property + def is_on(self): + """Return true if the light is on.""" + return self._module.is_on(self._channel) + + @property + def brightness(self): + """Return the brightness of the light.""" + return self._module.get_dimmer_state(self._channel) + + def turn_on(self, **kwargs): + """Instruct the Velbus light to turn on.""" + try: + if ATTR_BRIGHTNESS in kwargs: + self._module.set_dimmer_state( + self._channel, + kwargs[ATTR_BRIGHTNESS], + kwargs.get(ATTR_TRANSITION, 0), + ) + else: + self._module.restore_dimmer_state( + self._channel, kwargs.get(ATTR_TRANSITION, 0), + ) + except VelbusException as err: + _LOGGER.error("A Velbus error occurred: %s", err) + + def turn_off(self, **kwargs): + """Instruct the velbus light to turn off.""" + try: + self._module.set_dimmer_state( + self._channel, 0, kwargs.get(ATTR_TRANSITION, 0), + ) + except VelbusException as err: + _LOGGER.error("A Velbus error occurred: %s", err) From 4031596aa7f16274f98622ba30127c03b7e60f5d Mon Sep 17 00:00:00 2001 From: Lars-P Date: Tue, 31 Dec 2019 15:58:54 +0100 Subject: [PATCH 453/677] Fix luftdaten integration by adding a sensor for pressure at sealevel (#30317) --- homeassistant/components/luftdaten/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/luftdaten/__init__.py b/homeassistant/components/luftdaten/__init__.py index 3dca82404c0..d797fbbf4ba 100644 --- a/homeassistant/components/luftdaten/__init__.py +++ b/homeassistant/components/luftdaten/__init__.py @@ -34,6 +34,7 @@ SENSOR_HUMIDITY = "humidity" SENSOR_PM10 = "P1" SENSOR_PM2_5 = "P2" SENSOR_PRESSURE = "pressure" +SENSOR_PRESSURE_AT_SEALEVEL = "pressure_at_sealevel" SENSOR_TEMPERATURE = "temperature" TOPIC_UPDATE = f"{DOMAIN}_data_update" @@ -44,6 +45,7 @@ SENSORS = { SENSOR_TEMPERATURE: ["Temperature", "mdi:thermometer", TEMP_CELSIUS], SENSOR_HUMIDITY: ["Humidity", "mdi:water-percent", "%"], SENSOR_PRESSURE: ["Pressure", "mdi:arrow-down-bold", "Pa"], + SENSOR_PRESSURE_AT_SEALEVEL: ["Pressure at sealevel", "mdi:mdi-download", "Pa"], SENSOR_PM10: ["PM10", "mdi:thought-bubble", VOLUME_MICROGRAMS_PER_CUBIC_METER], SENSOR_PM2_5: [ "PM2.5", From 5ed44297e6de8492f5c851cc5bc4e638b6c47086 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Tue, 31 Dec 2019 07:27:39 -0800 Subject: [PATCH 454/677] Simplify Tesla icon get code (#30301) * Simplify icon get code * Remove extraneous None --- homeassistant/components/tesla/__init__.py | 8 ++------ homeassistant/components/tesla/const.py | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/tesla/__init__.py b/homeassistant/components/tesla/__init__.py index a034d9132f1..1ae65f66821 100644 --- a/homeassistant/components/tesla/__init__.py +++ b/homeassistant/components/tesla/__init__.py @@ -26,7 +26,7 @@ from .config_flow import ( configured_instances, validate_input, ) -from .const import DATA_LISTENER, DOMAIN, SENSOR_ICONS, TESLA_COMPONENTS +from .const import DATA_LISTENER, DOMAIN, ICONS, TESLA_COMPONENTS _LOGGER = logging.getLogger(__name__) @@ -186,11 +186,7 @@ class TeslaDevice(Entity): self._name = self.tesla_device.name self.tesla_id = slugify(self.tesla_device.uniq_name) self._attributes = {} - self._icon = ( - SENSOR_ICONS[self.tesla_device.type] - if self.tesla_device.type and self.tesla_device.type in SENSOR_ICONS.keys() - else None - ) + self._icon = ICONS.get(self.tesla_device.type) @property def name(self): diff --git a/homeassistant/components/tesla/const.py b/homeassistant/components/tesla/const.py index 30a58b733ed..be460a430ac 100644 --- a/homeassistant/components/tesla/const.py +++ b/homeassistant/components/tesla/const.py @@ -9,7 +9,7 @@ TESLA_COMPONENTS = [ "device_tracker", "switch", ] -SENSOR_ICONS = { +ICONS = { "battery sensor": "mdi:battery", "range sensor": "mdi:gauge", "mileage sensor": "mdi:counter", From a3061bda60c9136859224d55cd194d61a0b0a37c Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Tue, 31 Dec 2019 11:09:58 -0500 Subject: [PATCH 455/677] Make the rest of ZHA platforms to use ZHA class registry (#30261) * Refactor ZHA component tests fixtures. * Add tests for ZHA device discovery. * Refactor ZHA registry MatchRule. Allow callables as a matching criteria. Allow sets for model & manufacturer. * Minor ZHA class registry refactoring. Less cluttered strict_matching registrations. * Add entities only if there are any. * Migrate rest of ZHA platforms to ZHA registry. * Pylint fixes. --- homeassistant/components/zha/binary_sensor.py | 13 +- .../components/zha/core/registries.py | 65 +- .../components/zha/device_tracker.py | 16 +- homeassistant/components/zha/fan.py | 14 +- homeassistant/components/zha/light.py | 15 +- homeassistant/components/zha/lock.py | 14 +- homeassistant/components/zha/sensor.py | 21 +- homeassistant/components/zha/switch.py | 14 +- tests/components/zha/common.py | 39 +- tests/components/zha/test_channels.py | 8 +- tests/components/zha/test_discover.py | 55 + tests/components/zha/test_registries.py | 84 +- tests/components/zha/zha_devices_list.py | 1874 +++++++++++++++++ 13 files changed, 2152 insertions(+), 80 deletions(-) create mode 100644 tests/components/zha/test_discover.py create mode 100644 tests/components/zha/zha_devices_list.py diff --git a/homeassistant/components/zha/binary_sensor.py b/homeassistant/components/zha/binary_sensor.py index 954fa8b29aa..d8bc1187be8 100644 --- a/homeassistant/components/zha/binary_sensor.py +++ b/homeassistant/components/zha/binary_sensor.py @@ -28,7 +28,7 @@ from .core.const import ( SIGNAL_ATTR_UPDATED, ZHA_DISCOVERY_NEW, ) -from .core.registries import ZHA_ENTITIES, MatchRule +from .core.registries import ZHA_ENTITIES from .entity import ZhaEntity _LOGGER = logging.getLogger(__name__) @@ -85,7 +85,8 @@ async def _async_setup_entities( if entity: entities.append(entity(**discovery_info)) - async_add_entities(entities, update_before_add=True) + if entities: + async_add_entities(entities, update_before_add=True) class BinarySensor(ZhaEntity, BinarySensorDevice): @@ -141,28 +142,28 @@ class BinarySensor(ZhaEntity, BinarySensorDevice): self._state = await self._channel.get_attribute_value(attribute) -@STRICT_MATCH(MatchRule(channel_names={CHANNEL_ACCELEROMETER})) +@STRICT_MATCH(channel_names=CHANNEL_ACCELEROMETER) class Accelerometer(BinarySensor): """ZHA BinarySensor.""" DEVICE_CLASS = DEVICE_CLASS_MOVING -@STRICT_MATCH(MatchRule(channel_names={CHANNEL_OCCUPANCY})) +@STRICT_MATCH(channel_names=CHANNEL_OCCUPANCY) class Occupancy(BinarySensor): """ZHA BinarySensor.""" DEVICE_CLASS = DEVICE_CLASS_OCCUPANCY -@STRICT_MATCH(MatchRule(channel_names={CHANNEL_ON_OFF})) +@STRICT_MATCH(channel_names=CHANNEL_ON_OFF) class Opening(BinarySensor): """ZHA BinarySensor.""" DEVICE_CLASS = DEVICE_CLASS_OPENING -@STRICT_MATCH(MatchRule(channel_names={CHANNEL_ZONE})) +@STRICT_MATCH(channel_names=CHANNEL_ZONE) class IASZone(BinarySensor): """ZHA IAS BinarySensor.""" diff --git a/homeassistant/components/zha/core/registries.py b/homeassistant/components/zha/core/registries.py index f235b459b87..d2ba0243a5c 100644 --- a/homeassistant/components/zha/core/registries.py +++ b/homeassistant/components/zha/core/registries.py @@ -5,7 +5,7 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/integrations/zha/ """ import collections -from typing import Callable, Set +from typing import Callable, Set, Union import attr import bellows.ezsp @@ -171,14 +171,33 @@ def establish_device_mappings(): REMOTE_DEVICE_TYPES[zll.PROFILE_ID].append(zll.DeviceType.SCENE_CONTROLLER) +def set_or_callable(value): + """Convert single str or None to a set. Pass through callables and sets.""" + if value is None: + return frozenset() + if callable(value): + return value + if isinstance(value, (frozenset, set, list)): + return frozenset(value) + return frozenset([str(value)]) + + @attr.s(frozen=True) class MatchRule: """Match a ZHA Entity to a channel name or generic id.""" - channel_names: Set[str] = attr.ib(factory=frozenset, converter=frozenset) - generic_ids: Set[str] = attr.ib(factory=frozenset, converter=frozenset) - manufacturer: str = attr.ib(default=None) - model: str = attr.ib(default=None) + channel_names: Union[Callable, Set[str], str] = attr.ib( + factory=frozenset, converter=set_or_callable + ) + generic_ids: Union[Callable, Set[str], str] = attr.ib( + factory=frozenset, converter=set_or_callable + ) + manufacturers: Union[Callable, Set[str], str] = attr.ib( + factory=frozenset, converter=set_or_callable + ) + models: Union[Callable, Set[str], str] = attr.ib( + factory=frozenset, converter=set_or_callable + ) class ZHAEntityRegistry: @@ -190,7 +209,7 @@ class ZHAEntityRegistry: self._loose_registry = collections.defaultdict(dict) def get_entity( - self, component: str, zha_device, chnls: list, default: CALLABLE_T = None + self, component: str, zha_device, chnls: dict, default: CALLABLE_T = None ) -> CALLABLE_T: """Match a ZHA Channels to a ZHA Entity class.""" for match in self._strict_registry[component]: @@ -200,10 +219,17 @@ class ZHAEntityRegistry: return default def strict_match( - self, component: str, rule: MatchRule + self, + component: str, + channel_names: Union[Callable, Set[str], str] = None, + generic_ids: Union[Callable, Set[str], str] = None, + manufacturers: Union[Callable, Set[str], str] = None, + models: Union[Callable, Set[str], str] = None, ) -> Callable[[CALLABLE_T], CALLABLE_T]: """Decorate a strict match rule.""" + rule = MatchRule(channel_names, generic_ids, manufacturers, models) + def decorator(zha_ent: CALLABLE_T) -> CALLABLE_T: """Register a strict match rule. @@ -215,10 +241,17 @@ class ZHAEntityRegistry: return decorator def loose_match( - self, component: str, rule: MatchRule + self, + component: str, + channel_names: Union[Callable, Set[str], str] = None, + generic_ids: Union[Callable, Set[str], str] = None, + manufacturers: Union[Callable, Set[str], str] = None, + models: Union[Callable, Set[str], str] = None, ) -> Callable[[CALLABLE_T], CALLABLE_T]: """Decorate a loose match rule.""" + rule = MatchRule(channel_names, generic_ids, manufacturers, models) + def decorator(zha_entity: CALLABLE_T) -> CALLABLE_T: """Register a loose match rule. @@ -238,7 +271,7 @@ class ZHAEntityRegistry: return any(self._matched(zha_device, chnls, rule)) @staticmethod - def _matched(zha_device, chnls: list, rule: MatchRule) -> bool: + def _matched(zha_device, chnls: dict, rule: MatchRule) -> list: """Return a list of field matches.""" if not any(attr.asdict(rule).values()): return [False] @@ -252,11 +285,17 @@ class ZHAEntityRegistry: all_generic_ids = {ch.generic_id for ch in chnls} matches.append(rule.generic_ids.issubset(all_generic_ids)) - if rule.manufacturer: - matches.append(zha_device.manufacturer == rule.manufacturer) + if rule.manufacturers: + if callable(rule.manufacturers): + matches.append(rule.manufacturers(zha_device.manufacturer)) + else: + matches.append(zha_device.manufacturer in rule.manufacturers) - if rule.model: - matches.append(zha_device.model == rule.model) + if rule.models: + if callable(rule.models): + matches.append(rule.models(zha_device.model)) + else: + matches.append(zha_device.model in rule.models) return matches diff --git a/homeassistant/components/zha/device_tracker.py b/homeassistant/components/zha/device_tracker.py index e7663b35686..76548935814 100644 --- a/homeassistant/components/zha/device_tracker.py +++ b/homeassistant/components/zha/device_tracker.py @@ -1,4 +1,5 @@ """Support for the ZHA platform.""" +import functools import logging import time @@ -14,9 +15,11 @@ from .core.const import ( SIGNAL_ATTR_UPDATED, ZHA_DISCOVERY_NEW, ) +from .core.registries import ZHA_ENTITIES from .entity import ZhaEntity from .sensor import Battery +STRICT_MATCH = functools.partial(ZHA_ENTITIES.strict_match, DOMAIN) _LOGGER = logging.getLogger(__name__) @@ -47,11 +50,20 @@ async def _async_setup_entities( """Set up the ZHA device trackers.""" entities = [] for discovery_info in discovery_infos: - entities.append(ZHADeviceScannerEntity(**discovery_info)) + zha_dev = discovery_info["zha_device"] + channels = discovery_info["channels"] - async_add_entities(entities, update_before_add=True) + entity = ZHA_ENTITIES.get_entity( + DOMAIN, zha_dev, channels, ZHADeviceScannerEntity + ) + if entity: + entities.append(entity(**discovery_info)) + + if entities: + async_add_entities(entities, update_before_add=True) +@STRICT_MATCH(channel_names=CHANNEL_POWER_CONFIGURATION) class ZHADeviceScannerEntity(ScannerEntity, ZhaEntity): """Represent a tracked device.""" diff --git a/homeassistant/components/zha/fan.py b/homeassistant/components/zha/fan.py index bccdf260a11..f489447e530 100644 --- a/homeassistant/components/zha/fan.py +++ b/homeassistant/components/zha/fan.py @@ -1,4 +1,5 @@ """Fans on Zigbee Home Automation networks.""" +import functools import logging from homeassistant.components.fan import ( @@ -20,6 +21,7 @@ from .core.const import ( SIGNAL_ATTR_UPDATED, ZHA_DISCOVERY_NEW, ) +from .core.registries import ZHA_ENTITIES from .entity import ZhaEntity _LOGGER = logging.getLogger(__name__) @@ -45,6 +47,7 @@ SPEED_LIST = [ VALUE_TO_SPEED = dict(enumerate(SPEED_LIST)) SPEED_TO_VALUE = {speed: i for i, speed in enumerate(SPEED_LIST)} +STRICT_MATCH = functools.partial(ZHA_ENTITIES.strict_match, DOMAIN) async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): @@ -79,11 +82,18 @@ async def _async_setup_entities( """Set up the ZHA fans.""" entities = [] for discovery_info in discovery_infos: - entities.append(ZhaFan(**discovery_info)) + zha_dev = discovery_info["zha_device"] + channels = discovery_info["channels"] - async_add_entities(entities, update_before_add=True) + entity = ZHA_ENTITIES.get_entity(DOMAIN, zha_dev, channels, ZhaFan) + if entity: + entities.append(entity(**discovery_info)) + + if entities: + async_add_entities(entities, update_before_add=True) +@STRICT_MATCH(channel_names=CHANNEL_FAN) class ZhaFan(ZhaEntity, FanEntity): """Representation of a ZHA fan.""" diff --git a/homeassistant/components/zha/light.py b/homeassistant/components/zha/light.py index 08d74f9fdb3..eb7d3297b43 100644 --- a/homeassistant/components/zha/light.py +++ b/homeassistant/components/zha/light.py @@ -1,5 +1,6 @@ """Lights on Zigbee Home Automation networks.""" from datetime import timedelta +import functools import logging from zigpy.zcl.foundation import Status @@ -21,6 +22,7 @@ from .core.const import ( SIGNAL_SET_LEVEL, ZHA_DISCOVERY_NEW, ) +from .core.registries import ZHA_ENTITIES from .entity import ZhaEntity _LOGGER = logging.getLogger(__name__) @@ -36,6 +38,7 @@ UPDATE_COLORLOOP_HUE = 0x8 UNSUPPORTED_ATTRIBUTE = 0x86 SCAN_INTERVAL = timedelta(minutes=60) +STRICT_MATCH = functools.partial(ZHA_ENTITIES.strict_match, light.DOMAIN) PARALLEL_UPDATES = 5 @@ -71,12 +74,18 @@ async def _async_setup_entities( """Set up the ZHA lights.""" entities = [] for discovery_info in discovery_infos: - zha_light = Light(**discovery_info) - entities.append(zha_light) + zha_dev = discovery_info["zha_device"] + channels = discovery_info["channels"] - async_add_entities(entities, update_before_add=True) + entity = ZHA_ENTITIES.get_entity(light.DOMAIN, zha_dev, channels, Light) + if entity: + entities.append(entity(**discovery_info)) + + if entities: + async_add_entities(entities, update_before_add=True) +@STRICT_MATCH(channel_names=CHANNEL_ON_OFF) class Light(ZhaEntity, light.Light): """Representation of a ZHA or ZLL light.""" diff --git a/homeassistant/components/zha/lock.py b/homeassistant/components/zha/lock.py index 2458bf4be5b..bf82252246c 100644 --- a/homeassistant/components/zha/lock.py +++ b/homeassistant/components/zha/lock.py @@ -1,4 +1,5 @@ """Locks on Zigbee Home Automation networks.""" +import functools import logging from zigpy.zcl.foundation import Status @@ -19,6 +20,7 @@ from .core.const import ( SIGNAL_ATTR_UPDATED, ZHA_DISCOVERY_NEW, ) +from .core.registries import ZHA_ENTITIES from .entity import ZhaEntity _LOGGER = logging.getLogger(__name__) @@ -26,6 +28,7 @@ _LOGGER = logging.getLogger(__name__) """ The first state is Zigbee 'Not fully locked' """ STATE_LIST = [STATE_UNLOCKED, STATE_LOCKED, STATE_UNLOCKED] +STRICT_MATCH = functools.partial(ZHA_ENTITIES.strict_match, DOMAIN) VALUE_TO_STATE = dict(enumerate(STATE_LIST)) @@ -62,11 +65,18 @@ async def _async_setup_entities( """Set up the ZHA locks.""" entities = [] for discovery_info in discovery_infos: - entities.append(ZhaDoorLock(**discovery_info)) + zha_dev = discovery_info["zha_device"] + channels = discovery_info["channels"] - async_add_entities(entities, update_before_add=True) + entity = ZHA_ENTITIES.get_entity(DOMAIN, zha_dev, channels, ZhaDoorLock) + if entity: + entities.append(entity(**discovery_info)) + + if entities: + async_add_entities(entities, update_before_add=True) +@STRICT_MATCH(channel_names=CHANNEL_DOORLOCK) class ZhaDoorLock(ZhaEntity, LockDevice): """Representation of a ZHA lock.""" diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index 26dc25c71dc..2d39d562bf5 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -30,7 +30,7 @@ from .core.const import ( SIGNAL_STATE_ATTR, ZHA_DISCOVERY_NEW, ) -from .core.registries import SMARTTHINGS_HUMIDITY_CLUSTER, ZHA_ENTITIES, MatchRule +from .core.registries import SMARTTHINGS_HUMIDITY_CLUSTER, ZHA_ENTITIES from .entity import ZhaEntity PARALLEL_UPDATES = 5 @@ -90,7 +90,8 @@ async def _async_setup_entities( for discovery_info in discovery_infos: entities.append(await make_sensor(discovery_info)) - async_add_entities(entities, update_before_add=True) + if entities: + async_add_entities(entities, update_before_add=True) async def make_sensor(discovery_info): @@ -175,7 +176,7 @@ class Sensor(ZhaEntity): return round(float(value * self._multiplier) / self._divisor) -@STRICT_MATCH(MatchRule(channel_names={CHANNEL_POWER_CONFIGURATION})) +@STRICT_MATCH(channel_names=CHANNEL_POWER_CONFIGURATION) class Battery(Sensor): """Battery sensor of power configuration cluster.""" @@ -203,7 +204,7 @@ class Battery(Sensor): return state_attrs -@STRICT_MATCH(MatchRule(channel_names={CHANNEL_ELECTRICAL_MEASUREMENT})) +@STRICT_MATCH(channel_names=CHANNEL_ELECTRICAL_MEASUREMENT) class ElectricalMeasurement(Sensor): """Active power measurement.""" @@ -221,8 +222,8 @@ class ElectricalMeasurement(Sensor): return round(value * self._channel.multiplier / self._channel.divisor) -@STRICT_MATCH(MatchRule(generic_ids={CHANNEL_ST_HUMIDITY_CLUSTER})) -@STRICT_MATCH(MatchRule(channel_names={CHANNEL_HUMIDITY})) +@STRICT_MATCH(generic_ids=CHANNEL_ST_HUMIDITY_CLUSTER) +@STRICT_MATCH(channel_names=CHANNEL_HUMIDITY) class Humidity(Sensor): """Humidity sensor.""" @@ -231,7 +232,7 @@ class Humidity(Sensor): _unit = "%" -@STRICT_MATCH(MatchRule(channel_names={CHANNEL_ILLUMINANCE})) +@STRICT_MATCH(channel_names=CHANNEL_ILLUMINANCE) class Illuminance(Sensor): """Illuminance Sensor.""" @@ -244,7 +245,7 @@ class Illuminance(Sensor): return round(pow(10, ((value - 1) / 10000)), 1) -@STRICT_MATCH(MatchRule(channel_names={CHANNEL_SMARTENERGY_METERING})) +@STRICT_MATCH(channel_names=CHANNEL_SMARTENERGY_METERING) class SmartEnergyMetering(Sensor): """Metering sensor.""" @@ -260,7 +261,7 @@ class SmartEnergyMetering(Sensor): return self._channel.unit_of_measurement -@STRICT_MATCH(MatchRule(channel_names={CHANNEL_PRESSURE})) +@STRICT_MATCH(channel_names=CHANNEL_PRESSURE) class Pressure(Sensor): """Pressure sensor.""" @@ -269,7 +270,7 @@ class Pressure(Sensor): _unit = "hPa" -@STRICT_MATCH(MatchRule(channel_names={CHANNEL_TEMPERATURE})) +@STRICT_MATCH(channel_names=CHANNEL_TEMPERATURE) class Temperature(Sensor): """Temperature Sensor.""" diff --git a/homeassistant/components/zha/switch.py b/homeassistant/components/zha/switch.py index 03296e8a553..cbd29925f62 100644 --- a/homeassistant/components/zha/switch.py +++ b/homeassistant/components/zha/switch.py @@ -1,4 +1,5 @@ """Switches on Zigbee Home Automation networks.""" +import functools import logging from zigpy.zcl.foundation import Status @@ -15,9 +16,11 @@ from .core.const import ( SIGNAL_ATTR_UPDATED, ZHA_DISCOVERY_NEW, ) +from .core.registries import ZHA_ENTITIES from .entity import ZhaEntity _LOGGER = logging.getLogger(__name__) +STRICT_MATCH = functools.partial(ZHA_ENTITIES.strict_match, DOMAIN) async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): @@ -52,11 +55,18 @@ async def _async_setup_entities( """Set up the ZHA switches.""" entities = [] for discovery_info in discovery_infos: - entities.append(Switch(**discovery_info)) + zha_dev = discovery_info["zha_device"] + channels = discovery_info["channels"] - async_add_entities(entities, update_before_add=True) + entity = ZHA_ENTITIES.get_entity(DOMAIN, zha_dev, channels, Switch) + if entity: + entities.append(entity(**discovery_info)) + + if entities: + async_add_entities(entities, update_before_add=True) +@STRICT_MATCH(channel_names=CHANNEL_ON_OFF) class Switch(ZhaEntity, SwitchDevice): """ZHA switch.""" diff --git a/tests/components/zha/common.py b/tests/components/zha/common.py index 57fb26db7f0..06712e638f6 100644 --- a/tests/components/zha/common.py +++ b/tests/components/zha/common.py @@ -36,10 +36,10 @@ APPLICATION = FakeApplication() class FakeEndpoint: """Fake endpoint for moking zigpy.""" - def __init__(self, manufacturer, model): + def __init__(self, manufacturer, model, epid=1): """Init fake endpoint.""" self.device = None - self.endpoint_id = 1 + self.endpoint_id = epid self.in_clusters = {} self.out_clusters = {} self._cluster_attr = {} @@ -97,21 +97,23 @@ class FakeDevice: self.remove_from_group = CoroutineMock() -def make_device( - in_cluster_ids, out_cluster_ids, device_type, ieee, manufacturer, model -): +def make_device(endpoints, ieee, manufacturer, model): """Make a fake device using the specified cluster classes.""" device = FakeDevice(ieee, manufacturer, model) - endpoint = FakeEndpoint(manufacturer, model) - endpoint.device = device - device.endpoints[endpoint.endpoint_id] = endpoint - endpoint.device_type = device_type + for epid, ep in endpoints.items(): + endpoint = FakeEndpoint(manufacturer, model, epid) + endpoint.device = device + device.endpoints[epid] = endpoint + endpoint.device_type = ep["device_type"] + profile_id = ep.get("profile_id") + if profile_id: + endpoint.profile_id = profile_id - for cluster_id in in_cluster_ids: - endpoint.add_input_cluster(cluster_id) + for cluster_id in ep.get("in_clusters", []): + endpoint.add_input_cluster(cluster_id) - for cluster_id in out_cluster_ids: - endpoint.add_output_cluster(cluster_id) + for cluster_id in ep.get("out_clusters", []): + endpoint.add_output_cluster(cluster_id) return device @@ -136,7 +138,16 @@ async def async_init_zigpy_device( happens when the device is paired to the network for the first time. """ device = make_device( - in_cluster_ids, out_cluster_ids, device_type, ieee, manufacturer, model + { + 1: { + "in_clusters": in_cluster_ids, + "out_clusters": out_cluster_ids, + "device_type": device_type, + } + }, + ieee, + manufacturer, + model, ) if is_new_join: await gateway.async_device_initialized(device) diff --git a/tests/components/zha/test_channels.py b/tests/components/zha/test_channels.py index 3be3aaf0930..557cc0f2c5c 100644 --- a/tests/components/zha/test_channels.py +++ b/tests/components/zha/test_channels.py @@ -67,9 +67,7 @@ def nwk(): async def test_in_channel_config(cluster_id, bind_count, attrs, zha_gateway, hass): """Test ZHA core channel configuration for input clusters.""" zigpy_dev = make_device( - [cluster_id], - [], - 0x1234, + {1: {"in_clusters": [cluster_id], "out_clusters": [], "device_type": 0x1234}}, "00:11:22:33:44:55:66:77", "test manufacturer", "test model", @@ -125,9 +123,7 @@ async def test_in_channel_config(cluster_id, bind_count, attrs, zha_gateway, has async def test_out_channel_config(cluster_id, bind_count, zha_gateway, hass): """Test ZHA core channel configuration for output clusters.""" zigpy_dev = make_device( - [], - [cluster_id], - 0x1234, + {1: {"out_clusters": [cluster_id], "in_clusters": [], "device_type": 0x1234}}, "00:11:22:33:44:55:66:77", "test manufacturer", "test model", diff --git a/tests/components/zha/test_discover.py b/tests/components/zha/test_discover.py new file mode 100644 index 00000000000..91805acc448 --- /dev/null +++ b/tests/components/zha/test_discover.py @@ -0,0 +1,55 @@ +"""Test zha device discovery.""" + +import asyncio +from unittest import mock + +import pytest + +from homeassistant.components.zha.core.channels import EventRelayChannel +import homeassistant.components.zha.core.const as zha_const +import homeassistant.components.zha.core.discovery as disc +import homeassistant.components.zha.core.gateway as core_zha_gw + +from .common import make_device +from .zha_devices_list import DEVICES + + +@pytest.mark.parametrize("device", DEVICES) +async def test_devices(device, zha_gateway: core_zha_gw.ZHAGateway, hass, config_entry): + """Test device discovery.""" + + zigpy_device = make_device( + device["endpoints"], + "00:11:22:33:44:55:66:77", + device["manufacturer"], + device["model"], + ) + + with mock.patch( + "homeassistant.components.zha.core.discovery._async_create_cluster_channel", + wraps=disc._async_create_cluster_channel, + ) as cr_ch: + await zha_gateway.async_device_restored(zigpy_device) + await hass.async_block_till_done() + tasks = [ + hass.config_entries.async_forward_entry_setup(config_entry, component) + for component in zha_const.COMPONENTS + ] + await asyncio.gather(*tasks) + + await hass.async_block_till_done() + + entity_ids = hass.states.async_entity_ids() + await hass.async_block_till_done() + zha_entities = { + ent for ent in entity_ids if ent.split(".")[0] in zha_const.COMPONENTS + } + + event_channels = { + arg[0].cluster_id + for arg, kwarg in cr_ch.call_args_list + if kwarg.get("channel_class") == EventRelayChannel + } + + assert zha_entities == set(device["entities"]) + assert event_channels == set(device["event_channels"]) diff --git a/tests/components/zha/test_registries.py b/tests/components/zha/test_registries.py index a0eef355229..9f77330dd55 100644 --- a/tests/components/zha/test_registries.py +++ b/tests/components/zha/test_registries.py @@ -59,24 +59,68 @@ def channels(): True, ), # manufacturer matching - (registries.MatchRule(manufacturer="no match"), False), - (registries.MatchRule(manufacturer=MANUFACTURER), True), - (registries.MatchRule(model=MODEL), True), - (registries.MatchRule(model="no match"), False), + (registries.MatchRule(manufacturers="no match"), False), + (registries.MatchRule(manufacturers=MANUFACTURER), True), + (registries.MatchRule(models=MODEL), True), + (registries.MatchRule(models="no match"), False), # match everything ( registries.MatchRule( generic_ids={"channel_0x0006", "channel_0x0008"}, channel_names={"on_off", "level"}, - manufacturer=MANUFACTURER, - model=MODEL, + manufacturers=MANUFACTURER, + models=MODEL, ), True, ), + ( + registries.MatchRule( + channel_names="on_off", manufacturers={"random manuf", MANUFACTURER} + ), + True, + ), + ( + registries.MatchRule( + channel_names="on_off", manufacturers={"random manuf", "Another manuf"} + ), + False, + ), + ( + registries.MatchRule( + channel_names="on_off", manufacturers=lambda x: x == MANUFACTURER + ), + True, + ), + ( + registries.MatchRule( + channel_names="on_off", manufacturers=lambda x: x != MANUFACTURER + ), + False, + ), + ( + registries.MatchRule( + channel_names="on_off", models={"random model", MODEL} + ), + True, + ), + ( + registries.MatchRule( + channel_names="on_off", models={"random model", "Another model"} + ), + False, + ), + ( + registries.MatchRule(channel_names="on_off", models=lambda x: x == MODEL), + True, + ), + ( + registries.MatchRule(channel_names="on_off", models=lambda x: x != MODEL), + False, + ), ], ) def test_registry_matching(rule, matched, zha_device, channels): - """Test empty rule matching.""" + """Test strict rule matching.""" reg = registries.ZHAEntityRegistry() assert reg._strict_matched(zha_device, channels, rule) is matched @@ -92,22 +136,22 @@ def test_registry_matching(rule, matched, zha_device, channels): (registries.MatchRule(channel_names={"on_off", "level"}), True), (registries.MatchRule(channel_names={"on_off", "level", "no match"}), False), ( - registries.MatchRule(channel_names={"on_off", "level"}, model="no match"), + registries.MatchRule(channel_names={"on_off", "level"}, models="no match"), True, ), ( registries.MatchRule( channel_names={"on_off", "level"}, - model="no match", - manufacturer="no match", + models="no match", + manufacturers="no match", ), True, ), ( registries.MatchRule( channel_names={"on_off", "level"}, - model="no match", - manufacturer=MANUFACTURER, + models="no match", + manufacturers=MANUFACTURER, ), True, ), @@ -124,14 +168,14 @@ def test_registry_matching(rule, matched, zha_device, channels): ( registries.MatchRule( generic_ids={"channel_0x0006", "channel_0x0008", "channel_0x0009"}, - model="mo match", + models="mo match", ), False, ), ( registries.MatchRule( generic_ids={"channel_0x0006", "channel_0x0008", "channel_0x0009"}, - model=MODEL, + models=MODEL, ), True, ), @@ -143,17 +187,17 @@ def test_registry_matching(rule, matched, zha_device, channels): True, ), # manufacturer matching - (registries.MatchRule(manufacturer="no match"), False), - (registries.MatchRule(manufacturer=MANUFACTURER), True), - (registries.MatchRule(model=MODEL), True), - (registries.MatchRule(model="no match"), False), + (registries.MatchRule(manufacturers="no match"), False), + (registries.MatchRule(manufacturers=MANUFACTURER), True), + (registries.MatchRule(models=MODEL), True), + (registries.MatchRule(models="no match"), False), # match everything ( registries.MatchRule( generic_ids={"channel_0x0006", "channel_0x0008"}, channel_names={"on_off", "level"}, - manufacturer=MANUFACTURER, - model=MODEL, + manufacturers=MANUFACTURER, + models=MODEL, ), True, ), diff --git a/tests/components/zha/zha_devices_list.py b/tests/components/zha/zha_devices_list.py new file mode 100644 index 00000000000..d5875edc9e2 --- /dev/null +++ b/tests/components/zha/zha_devices_list.py @@ -0,0 +1,1874 @@ +"""Example Zigbee Devices.""" + +DEVICES = [ + { + "endpoints": { + "1": { + "device_type": 2080, + "endpoint_id": 1, + "in_clusters": [0, 3, 4096, 64716], + "out_clusters": [3, 4, 6, 8, 4096, 64716], + "profile_id": 260, + } + }, + "entities": [], + "event_channels": [6, 8], + "manufacturer": "ADUROLIGHT", + "model": "Adurolight_NCC", + }, + { + "endpoints": { + "5": { + "device_type": 1026, + "endpoint_id": 5, + "in_clusters": [0, 1, 3, 32, 1026, 1280, 2821], + "out_clusters": [25], + "profile_id": 260, + } + }, + "entities": [ + "binary_sensor.bosch_isw_zpr1_wp13_77665544_ias_zone", + "sensor.bosch_isw_zpr1_wp13_77665544_power", + "sensor.bosch_isw_zpr1_wp13_77665544_temperature", + ], + "event_channels": [], + "manufacturer": "Bosch", + "model": "ISW-ZPR1-WP13", + }, + { + "endpoints": { + "1": { + "device_type": 1, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 32, 2821], + "out_clusters": [3, 6, 8, 25], + "profile_id": 260, + } + }, + "entities": [ + "binary_sensor.centralite_3130_77665544_on_off", + "sensor.centralite_3130_77665544_power", + ], + "event_channels": [6, 8], + "manufacturer": "CentraLite", + "model": "3130", + }, + { + "endpoints": { + "1": { + "device_type": 81, + "endpoint_id": 1, + "in_clusters": [0, 3, 4, 5, 6, 1794, 2820, 2821, 64515], + "out_clusters": [25], + "profile_id": 260, + } + }, + "entities": [ + "sensor.centralite_3210_l_77665544_smartenergy_metering", + "sensor.centralite_3210_l_77665544_electrical_measurement", + "switch.centralite_3210_l_77665544_on_off", + ], + "event_channels": [], + "manufacturer": "CentraLite", + "model": "3210-L", + }, + { + "endpoints": { + "1": { + "device_type": 770, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 32, 1026, 2821, 64581], + "out_clusters": [3, 25], + "profile_id": 260, + } + }, + "entities": [ + "sensor.centralite_3310_s_77665544_power", + "sensor.centralite_3310_s_77665544_temperature", + "sensor.centralite_3310_s_77665544_manufacturer_specific", + ], + "event_channels": [], + "manufacturer": "CentraLite", + "model": "3310-S", + }, + { + "endpoints": { + "1": { + "device_type": 1026, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 32, 1026, 1280, 2821], + "out_clusters": [25], + "profile_id": 260, + }, + "2": { + "device_type": 12, + "endpoint_id": 2, + "in_clusters": [0, 3, 2821, 64527], + "out_clusters": [3], + "profile_id": 49887, + }, + }, + "entities": [ + "binary_sensor.centralite_3315_s_77665544_ias_zone", + "sensor.centralite_3315_s_77665544_temperature", + "sensor.centralite_3315_s_77665544_power", + ], + "event_channels": [], + "manufacturer": "CentraLite", + "model": "3315-S", + }, + { + "endpoints": { + "1": { + "device_type": 1026, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 32, 1026, 1280, 2821], + "out_clusters": [25], + "profile_id": 260, + }, + "2": { + "device_type": 12, + "endpoint_id": 2, + "in_clusters": [0, 3, 2821, 64527], + "out_clusters": [3], + "profile_id": 49887, + }, + }, + "entities": [ + "binary_sensor.centralite_3320_l_77665544_ias_zone", + "sensor.centralite_3320_l_77665544_temperature", + "sensor.centralite_3320_l_77665544_power", + ], + "event_channels": [], + "manufacturer": "CentraLite", + "model": "3320-L", + }, + { + "endpoints": { + "1": { + "device_type": 1026, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 32, 1026, 1280, 2821], + "out_clusters": [25], + "profile_id": 260, + }, + "2": { + "device_type": 263, + "endpoint_id": 2, + "in_clusters": [0, 3, 2821, 64582], + "out_clusters": [3], + "profile_id": 49887, + }, + }, + "entities": [ + "binary_sensor.centralite_3326_l_77665544_ias_zone", + "sensor.centralite_3326_l_77665544_temperature", + "sensor.centralite_3326_l_77665544_power", + ], + "event_channels": [], + "manufacturer": "CentraLite", + "model": "3326-L", + }, + { + "endpoints": { + "1": { + "device_type": 1026, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 32, 1026, 1280, 2821], + "out_clusters": [25], + "profile_id": 260, + }, + "2": { + "device_type": 263, + "endpoint_id": 2, + "in_clusters": [0, 3, 1030, 2821], + "out_clusters": [3], + "profile_id": 260, + }, + }, + "entities": [ + "binary_sensor.centralite_motion_sensor_a_77665544_occupancy", + "binary_sensor.centralite_motion_sensor_a_77665544_ias_zone", + "sensor.centralite_motion_sensor_a_77665544_temperature", + "sensor.centralite_motion_sensor_a_77665544_power", + ], + "event_channels": [], + "manufacturer": "CentraLite", + "model": "Motion Sensor-A", + }, + { + "endpoints": { + "1": { + "device_type": 81, + "endpoint_id": 1, + "in_clusters": [0, 3, 4, 5, 6, 1794], + "out_clusters": [0], + "profile_id": 260, + }, + "4": { + "device_type": 9, + "endpoint_id": 4, + "in_clusters": [], + "out_clusters": [25], + "profile_id": 260, + }, + }, + "entities": [ + "sensor.climaxtechnology_psmp5_00_00_02_02tc_77665544_smartenergy_metering", + "switch.climaxtechnology_psmp5_00_00_02_02tc_77665544_on_off", + ], + "event_channels": [], + "manufacturer": "ClimaxTechnology", + "model": "PSMP5_00.00.02.02TC", + }, + { + "endpoints": { + "1": { + "device_type": 1026, + "endpoint_id": 1, + "in_clusters": [0, 3, 1280, 1282], + "out_clusters": [0], + "profile_id": 260, + } + }, + "entities": [ + "binary_sensor.climaxtechnology_sd8sc_00_00_03_12tc_77665544_ias_zone" + ], + "event_channels": [], + "manufacturer": "ClimaxTechnology", + "model": "SD8SC_00.00.03.12TC", + }, + { + "endpoints": { + "1": { + "device_type": 1026, + "endpoint_id": 1, + "in_clusters": [0, 3, 1280], + "out_clusters": [0], + "profile_id": 260, + } + }, + "entities": [ + "binary_sensor.climaxtechnology_ws15_00_00_03_03tc_77665544_ias_zone" + ], + "event_channels": [], + "manufacturer": "ClimaxTechnology", + "model": "WS15_00.00.03.03TC", + }, + { + "endpoints": { + "11": { + "device_type": 528, + "endpoint_id": 11, + "in_clusters": [0, 3, 4, 5, 6, 8, 768], + "out_clusters": [], + "profile_id": 49246, + }, + "13": { + "device_type": 57694, + "endpoint_id": 13, + "in_clusters": [4096], + "out_clusters": [4096], + "profile_id": 49246, + }, + }, + "entities": [ + "light.feibit_inc_co_fb56_zcw08ku1_1_77665544_level_light_color_on_off" + ], + "event_channels": [], + "manufacturer": "Feibit Inc co.", + "model": "FB56-ZCW08KU1.1", + }, + { + "endpoints": { + "1": { + "device_type": 1027, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 4, 9, 1280, 1282], + "out_clusters": [3, 25], + "profile_id": 260, + } + }, + "entities": [ + "binary_sensor.heiman_warningdevice_77665544_ias_zone", + "sensor.heiman_warningdevice_77665544_power", + ], + "event_channels": [], + "manufacturer": "Heiman", + "model": "WarningDevice", + }, + { + "endpoints": { + "6": { + "device_type": 1026, + "endpoint_id": 6, + "in_clusters": [0, 1, 3, 32, 1024, 1026, 1280], + "out_clusters": [25], + "profile_id": 260, + } + }, + "entities": [ + "sensor.hivehome_com_mot003_77665544_temperature", + "sensor.hivehome_com_mot003_77665544_power", + "sensor.hivehome_com_mot003_77665544_illuminance", + "binary_sensor.hivehome_com_mot003_77665544_ias_zone", + ], + "event_channels": [], + "manufacturer": "HiveHome.com", + "model": "MOT003", + }, + { + "endpoints": { + "1": { + "device_type": 268, + "endpoint_id": 1, + "in_clusters": [0, 3, 4, 5, 6, 8, 768, 4096, 64636], + "out_clusters": [5, 25, 32, 4096], + "profile_id": 260, + }, + "242": { + "device_type": 97, + "endpoint_id": 242, + "in_clusters": [33], + "out_clusters": [33], + "profile_id": 41440, + }, + }, + "entities": [ + "light.ikea_of_sweden_tradfri_bulb_e12_ws_opal_600lm_77665544_level_light_color_on_off" + ], + "event_channels": [], + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI bulb E12 WS opal 600lm", + }, + { + "endpoints": { + "1": { + "device_type": 512, + "endpoint_id": 1, + "in_clusters": [0, 3, 4, 5, 6, 8, 768, 2821, 4096], + "out_clusters": [5, 25, 32, 4096], + "profile_id": 49246, + } + }, + "entities": [ + "light.ikea_of_sweden_tradfri_bulb_e26_cws_opal_600lm_77665544_level_light_color_on_off" + ], + "event_channels": [], + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI bulb E26 CWS opal 600lm", + }, + { + "endpoints": { + "1": { + "device_type": 256, + "endpoint_id": 1, + "in_clusters": [0, 3, 4, 5, 6, 8, 2821, 4096], + "out_clusters": [5, 25, 32, 4096], + "profile_id": 49246, + } + }, + "entities": [ + "light.ikea_of_sweden_tradfri_bulb_e26_w_opal_1000lm_77665544_level_on_off" + ], + "event_channels": [], + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI bulb E26 W opal 1000lm", + }, + { + "endpoints": { + "1": { + "device_type": 544, + "endpoint_id": 1, + "in_clusters": [0, 3, 4, 5, 6, 8, 768, 2821, 4096], + "out_clusters": [5, 25, 32, 4096], + "profile_id": 49246, + } + }, + "entities": [ + "light.ikea_of_sweden_tradfri_bulb_e26_ws_opal_980lm_77665544_level_light_color_on_off" + ], + "event_channels": [], + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI bulb E26 WS opal 980lm", + }, + { + "endpoints": { + "1": { + "device_type": 256, + "endpoint_id": 1, + "in_clusters": [0, 3, 4, 5, 6, 8, 2821, 4096], + "out_clusters": [5, 25, 32, 4096], + "profile_id": 260, + } + }, + "entities": [ + "light.ikea_of_sweden_tradfri_bulb_e26_opal_1000lm_77665544_level_on_off" + ], + "event_channels": [], + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI bulb E26 opal 1000lm", + }, + { + "endpoints": { + "1": { + "device_type": 266, + "endpoint_id": 1, + "in_clusters": [0, 3, 4, 5, 6, 64636], + "out_clusters": [5, 25, 32], + "profile_id": 260, + } + }, + "entities": ["switch.ikea_of_sweden_tradfri_control_outlet_77665544_on_off"], + "event_channels": [], + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI control outlet", + }, + { + "endpoints": { + "1": { + "device_type": 2128, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 9, 2821, 4096], + "out_clusters": [3, 4, 6, 25, 4096], + "profile_id": 49246, + } + }, + "entities": [ + "binary_sensor.ikea_of_sweden_tradfri_motion_sensor_77665544_on_off", + "sensor.ikea_of_sweden_tradfri_motion_sensor_77665544_power", + ], + "event_channels": [6], + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI motion sensor", + }, + { + "endpoints": { + "1": { + "device_type": 2080, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 9, 32, 4096, 64636], + "out_clusters": [3, 4, 6, 8, 25, 258, 4096], + "profile_id": 260, + } + }, + "entities": ["sensor.ikea_of_sweden_tradfri_on_off_switch_77665544_power"], + "event_channels": [6, 8], + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI on/off switch", + }, + { + "endpoints": { + "1": { + "device_type": 2096, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 9, 2821, 4096], + "out_clusters": [3, 4, 5, 6, 8, 25, 4096], + "profile_id": 49246, + } + }, + "entities": ["sensor.ikea_of_sweden_tradfri_remote_control_77665544_power"], + "event_channels": [6, 8], + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI remote control", + }, + { + "endpoints": { + "1": { + "device_type": 8, + "endpoint_id": 1, + "in_clusters": [0, 3, 9, 2821, 4096, 64636], + "out_clusters": [25, 32, 4096], + "profile_id": 260, + }, + "242": { + "device_type": 97, + "endpoint_id": 242, + "in_clusters": [33], + "out_clusters": [33], + "profile_id": 41440, + }, + }, + "entities": [], + "event_channels": [], + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI signal repeater", + }, + { + "endpoints": { + "1": { + "device_type": 2064, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 9, 2821, 4096], + "out_clusters": [3, 4, 6, 8, 25, 4096], + "profile_id": 260, + } + }, + "entities": ["sensor.ikea_of_sweden_tradfri_wireless_dimmer_77665544_power"], + "event_channels": [6, 8], + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI wireless dimmer", + }, + { + "endpoints": { + "1": { + "device_type": 257, + "endpoint_id": 1, + "in_clusters": [0, 3, 4, 5, 6, 8, 1794, 2821], + "out_clusters": [10, 25], + "profile_id": 260, + }, + "2": { + "device_type": 260, + "endpoint_id": 2, + "in_clusters": [0, 3, 2821], + "out_clusters": [3, 6, 8], + "profile_id": 260, + }, + }, + "entities": [ + "sensor.jasco_products_45852_77665544_smartenergy_metering", + "light.jasco_products_45852_77665544_level_on_off", + ], + "event_channels": [6, 8], + "manufacturer": "Jasco Products", + "model": "45852", + }, + { + "endpoints": { + "1": { + "device_type": 256, + "endpoint_id": 1, + "in_clusters": [0, 3, 4, 5, 6, 1794, 2821], + "out_clusters": [10, 25], + "profile_id": 260, + }, + "2": { + "device_type": 259, + "endpoint_id": 2, + "in_clusters": [0, 3, 2821], + "out_clusters": [3, 6], + "profile_id": 260, + }, + }, + "entities": [ + "sensor.jasco_products_45856_77665544_smartenergy_metering", + "switch.jasco_products_45856_77665544_on_off", + "light.jasco_products_45856_77665544_on_off", + ], + "event_channels": [6], + "manufacturer": "Jasco Products", + "model": "45856", + }, + { + "endpoints": { + "1": { + "device_type": 257, + "endpoint_id": 1, + "in_clusters": [0, 3, 4, 5, 6, 8, 1794, 2821], + "out_clusters": [10, 25], + "profile_id": 260, + }, + "2": { + "device_type": 260, + "endpoint_id": 2, + "in_clusters": [0, 3, 2821], + "out_clusters": [3, 6, 8], + "profile_id": 260, + }, + }, + "entities": [ + "sensor.jasco_products_45857_77665544_smartenergy_metering", + "light.jasco_products_45857_77665544_level_on_off", + ], + "event_channels": [6, 8], + "manufacturer": "Jasco Products", + "model": "45857", + }, + { + "endpoints": { + "1": { + "device_type": 3, + "endpoint_id": 1, + "in_clusters": [ + 0, + 1, + 3, + 4, + 5, + 6, + 8, + 32, + 1026, + 1027, + 2821, + 64513, + 64514, + ], + "out_clusters": [25], + "profile_id": 260, + } + }, + "entities": [ + "binary_sensor.keen_home_inc_sv02_610_mp_1_3_77665544_manufacturer_specific", + "sensor.keen_home_inc_sv02_610_mp_1_3_77665544_pressure", + "sensor.keen_home_inc_sv02_610_mp_1_3_77665544_temperature", + "sensor.keen_home_inc_sv02_610_mp_1_3_77665544_power", + "light.keen_home_inc_sv02_610_mp_1_3_77665544_level_on_off", + ], + "event_channels": [], + "manufacturer": "Keen Home Inc", + "model": "SV02-610-MP-1.3", + }, + { + "endpoints": { + "1": { + "device_type": 3, + "endpoint_id": 1, + "in_clusters": [ + 0, + 1, + 3, + 4, + 5, + 6, + 8, + 32, + 1026, + 1027, + 2821, + 64513, + 64514, + ], + "out_clusters": [25], + "profile_id": 260, + } + }, + "entities": [ + "binary_sensor.keen_home_inc_sv02_612_mp_1_2_77665544_manufacturer_specific", + "sensor.keen_home_inc_sv02_612_mp_1_2_77665544_temperature", + "sensor.keen_home_inc_sv02_612_mp_1_2_77665544_power", + "sensor.keen_home_inc_sv02_612_mp_1_2_77665544_pressure", + "light.keen_home_inc_sv02_612_mp_1_2_77665544_level_on_off", + ], + "event_channels": [], + "manufacturer": "Keen Home Inc", + "model": "SV02-612-MP-1.2", + }, + { + "endpoints": { + "1": { + "device_type": 3, + "endpoint_id": 1, + "in_clusters": [ + 0, + 1, + 3, + 4, + 5, + 6, + 8, + 32, + 1026, + 1027, + 2821, + 64513, + 64514, + ], + "out_clusters": [25], + "profile_id": 260, + } + }, + "entities": [ + "binary_sensor.keen_home_inc_sv02_612_mp_1_3_77665544_manufacturer_specific", + "sensor.keen_home_inc_sv02_612_mp_1_3_77665544_pressure", + "sensor.keen_home_inc_sv02_612_mp_1_3_77665544_power", + "sensor.keen_home_inc_sv02_612_mp_1_3_77665544_temperature", + "light.keen_home_inc_sv02_612_mp_1_3_77665544_level_on_off", + ], + "event_channels": [], + "manufacturer": "Keen Home Inc", + "model": "SV02-612-MP-1.3", + }, + { + "endpoints": { + "1": { + "device_type": 14, + "endpoint_id": 1, + "in_clusters": [0, 3, 4, 5, 6, 8, 514], + "out_clusters": [3, 25], + "profile_id": 260, + } + }, + "entities": [ + "fan.king_of_fans_inc_hbuniversalcfremote_77665544_fan", + "switch.king_of_fans_inc_hbuniversalcfremote_77665544_on_off", + ], + "event_channels": [], + "manufacturer": "King Of Fans, Inc.", + "model": "HBUniversalCFRemote", + }, + { + "endpoints": { + "1": { + "device_type": 258, + "endpoint_id": 1, + "in_clusters": [0, 3, 4, 5, 6, 8, 768, 2821, 64513], + "out_clusters": [25], + "profile_id": 260, + } + }, + "entities": ["light.ledvance_a19_rgbw_77665544_level_light_color_on_off"], + "event_channels": [], + "manufacturer": "LEDVANCE", + "model": "A19 RGBW", + }, + { + "endpoints": { + "1": { + "device_type": 258, + "endpoint_id": 1, + "in_clusters": [0, 3, 4, 5, 6, 8, 768, 2821, 64513], + "out_clusters": [25], + "profile_id": 260, + } + }, + "entities": ["light.ledvance_flex_rgbw_77665544_level_light_color_on_off"], + "event_channels": [], + "manufacturer": "LEDVANCE", + "model": "FLEX RGBW", + }, + { + "endpoints": { + "1": { + "device_type": 81, + "endpoint_id": 1, + "in_clusters": [0, 3, 4, 5, 6, 2821, 64513, 64520], + "out_clusters": [3, 25], + "profile_id": 260, + } + }, + "entities": ["switch.ledvance_plug_77665544_on_off"], + "event_channels": [], + "manufacturer": "LEDVANCE", + "model": "PLUG", + }, + { + "endpoints": { + "1": { + "device_type": 258, + "endpoint_id": 1, + "in_clusters": [0, 3, 4, 5, 6, 8, 768, 2821, 64513], + "out_clusters": [25], + "profile_id": 260, + } + }, + "entities": ["light.ledvance_rt_rgbw_77665544_level_light_color_on_off"], + "event_channels": [], + "manufacturer": "LEDVANCE", + "model": "RT RGBW", + }, + { + "endpoints": { + "1": { + "device_type": 81, + "endpoint_id": 1, + "in_clusters": [0, 1, 2, 3, 4, 5, 6, 10, 16, 2820], + "out_clusters": [10, 25], + "profile_id": 260, + }, + "100": { + "device_type": 263, + "endpoint_id": 100, + "in_clusters": [15], + "out_clusters": [4, 15], + "profile_id": 260, + }, + "2": { + "device_type": 9, + "endpoint_id": 2, + "in_clusters": [12], + "out_clusters": [4, 12], + "profile_id": 260, + }, + "3": { + "device_type": 83, + "endpoint_id": 3, + "in_clusters": [12], + "out_clusters": [12], + "profile_id": 260, + }, + }, + "entities": [ + "sensor.lumi_lumi_plug_maus01_77665544_electrical_measurement", + "sensor.lumi_lumi_plug_maus01_77665544_analog_input", + "sensor.lumi_lumi_plug_maus01_77665544_analog_input_2", + "sensor.lumi_lumi_plug_maus01_77665544_power", + "switch.lumi_lumi_plug_maus01_77665544_on_off", + ], + "event_channels": [], + "manufacturer": "LUMI", + "model": "lumi.plug.maus01", + }, + { + "endpoints": { + "1": { + "device_type": 257, + "endpoint_id": 1, + "in_clusters": [0, 1, 2, 3, 4, 5, 6, 10, 12, 16, 2820], + "out_clusters": [10, 25], + "profile_id": 260, + }, + "2": { + "device_type": 257, + "endpoint_id": 2, + "in_clusters": [4, 5, 6, 16], + "out_clusters": [], + "profile_id": 260, + }, + }, + "entities": [ + "sensor.lumi_lumi_relay_c2acn01_77665544_analog_input", + "sensor.lumi_lumi_relay_c2acn01_77665544_electrical_measurement", + "sensor.lumi_lumi_relay_c2acn01_77665544_power", + "light.lumi_lumi_relay_c2acn01_77665544_on_off", + "light.lumi_lumi_relay_c2acn01_77665544_on_off_2", + ], + "event_channels": [], + "manufacturer": "LUMI", + "model": "lumi.relay.c2acn01", + }, + { + "endpoints": { + "1": { + "device_type": 24321, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 18, 25, 65535], + "out_clusters": [0, 3, 4, 5, 18, 25, 65535], + "profile_id": 260, + }, + "2": { + "device_type": 24322, + "endpoint_id": 2, + "in_clusters": [3, 18], + "out_clusters": [3, 4, 5, 18], + "profile_id": 260, + }, + "3": { + "device_type": 24323, + "endpoint_id": 3, + "in_clusters": [3, 18], + "out_clusters": [3, 4, 5, 12, 18], + "profile_id": 260, + }, + }, + "entities": [ + "sensor.lumi_lumi_remote_b186acn01_77665544_multistate_input", + "sensor.lumi_lumi_remote_b186acn01_77665544_power", + "sensor.lumi_lumi_remote_b186acn01_77665544_multistate_input_2", + "sensor.lumi_lumi_remote_b186acn01_77665544_multistate_input_3", + ], + "event_channels": [], + "manufacturer": "LUMI", + "model": "lumi.remote.b186acn01", + }, + { + "endpoints": { + "1": { + "device_type": 24321, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 18, 25, 65535], + "out_clusters": [0, 3, 4, 5, 18, 25, 65535], + "profile_id": 260, + }, + "2": { + "device_type": 24322, + "endpoint_id": 2, + "in_clusters": [3, 18], + "out_clusters": [3, 4, 5, 18], + "profile_id": 260, + }, + "3": { + "device_type": 24323, + "endpoint_id": 3, + "in_clusters": [3, 18], + "out_clusters": [3, 4, 5, 12, 18], + "profile_id": 260, + }, + }, + "entities": [ + "sensor.lumi_lumi_remote_b286acn01_77665544_multistate_input", + "sensor.lumi_lumi_remote_b286acn01_77665544_power", + "sensor.lumi_lumi_remote_b286acn01_77665544_multistate_input_2", + "sensor.lumi_lumi_remote_b286acn01_77665544_multistate_input_3", + ], + "event_channels": [], + "manufacturer": "LUMI", + "model": "lumi.remote.b286acn01", + }, + { + "endpoints": { + "1": { + "device_type": 261, + "endpoint_id": 1, + "in_clusters": [0, 1, 3], + "out_clusters": [3, 6, 8, 768], + "profile_id": 260, + }, + "2": { + "device_type": -1, + "endpoint_id": 2, + "in_clusters": [], + "out_clusters": [], + "profile_id": -1, + }, + "3": { + "device_type": -1, + "endpoint_id": 3, + "in_clusters": [], + "out_clusters": [], + "profile_id": -1, + }, + "4": { + "device_type": -1, + "endpoint_id": 4, + "in_clusters": [], + "out_clusters": [], + "profile_id": -1, + }, + "5": { + "device_type": -1, + "endpoint_id": 5, + "in_clusters": [], + "out_clusters": [], + "profile_id": -1, + }, + "6": { + "device_type": -1, + "endpoint_id": 6, + "in_clusters": [], + "out_clusters": [], + "profile_id": -1, + }, + }, + "entities": ["sensor.lumi_lumi_remote_b286opcn01_77665544_power"], + "event_channels": [6, 8, 768], + "manufacturer": "LUMI", + "model": "lumi.remote.b286opcn01", + }, + { + "endpoints": { + "1": { + "device_type": 261, + "endpoint_id": 1, + "in_clusters": [0, 1, 3], + "out_clusters": [3, 6, 8, 768], + "profile_id": 260, + }, + "2": { + "device_type": 259, + "endpoint_id": 2, + "in_clusters": [3], + "out_clusters": [3, 6], + "profile_id": 260, + }, + "3": { + "device_type": -1, + "endpoint_id": 3, + "in_clusters": [], + "out_clusters": [], + "profile_id": -1, + }, + "4": { + "device_type": -1, + "endpoint_id": 4, + "in_clusters": [], + "out_clusters": [], + "profile_id": -1, + }, + "5": { + "device_type": -1, + "endpoint_id": 5, + "in_clusters": [], + "out_clusters": [], + "profile_id": -1, + }, + "6": { + "device_type": -1, + "endpoint_id": 6, + "in_clusters": [], + "out_clusters": [], + "profile_id": -1, + }, + }, + "entities": [ + "sensor.lumi_lumi_remote_b486opcn01_77665544_power", + "switch.lumi_lumi_remote_b486opcn01_77665544_on_off", + ], + "event_channels": [6, 8, 768, 6], + "manufacturer": "LUMI", + "model": "lumi.remote.b486opcn01", + }, + { + "endpoints": { + "1": { + "device_type": 261, + "endpoint_id": 1, + "in_clusters": [0, 1, 3], + "out_clusters": [3, 6, 8, 768], + "profile_id": 260, + }, + "2": { + "device_type": 259, + "endpoint_id": 2, + "in_clusters": [3], + "out_clusters": [3, 6], + "profile_id": 260, + }, + "3": { + "device_type": None, + "endpoint_id": 3, + "in_clusters": [], + "out_clusters": [], + "profile_id": None, + }, + "4": { + "device_type": None, + "endpoint_id": 4, + "in_clusters": [], + "out_clusters": [], + "profile_id": None, + }, + "5": { + "device_type": None, + "endpoint_id": 5, + "in_clusters": [], + "out_clusters": [], + "profile_id": None, + }, + "6": { + "device_type": None, + "endpoint_id": 6, + "in_clusters": [], + "out_clusters": [], + "profile_id": None, + }, + }, + "entities": [ + "sensor.lumi_lumi_remote_b686opcn01_77665544_power", + "switch.lumi_lumi_remote_b686opcn01_77665544_on_off", + ], + "event_channels": [6, 8, 768, 6], + "manufacturer": "LUMI", + "model": "lumi.remote.b686opcn01", + }, + { + "endpoints": { + "8": { + "device_type": 256, + "endpoint_id": 8, + "in_clusters": [0, 6, 11, 17], + "out_clusters": [0, 6], + "profile_id": 260, + } + }, + "entities": ["light.lumi_lumi_router_77665544_on_off_on_off"], + "event_channels": [6], + "manufacturer": "LUMI", + "model": "lumi.router", + }, + { + "endpoints": { + "1": { + "device_type": 28417, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 25], + "out_clusters": [0, 3, 4, 5, 18, 25], + "profile_id": 260, + }, + "2": { + "device_type": 28418, + "endpoint_id": 2, + "in_clusters": [3, 18], + "out_clusters": [3, 4, 5, 18], + "profile_id": 260, + }, + "3": { + "device_type": 28419, + "endpoint_id": 3, + "in_clusters": [3, 12], + "out_clusters": [3, 4, 5, 12], + "profile_id": 260, + }, + }, + "entities": [ + "sensor.lumi_lumi_sensor_cube_aqgl01_77665544_analog_input", + "sensor.lumi_lumi_sensor_cube_aqgl01_77665544_multistate_input", + "sensor.lumi_lumi_sensor_cube_aqgl01_77665544_power", + ], + "event_channels": [], + "manufacturer": "LUMI", + "model": "lumi.sensor_cube.aqgl01", + }, + { + "endpoints": { + "1": { + "device_type": 24322, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 25, 1026, 1029, 65535], + "out_clusters": [0, 3, 4, 5, 18, 25, 65535], + "profile_id": 260, + }, + "2": { + "device_type": 24322, + "endpoint_id": 2, + "in_clusters": [3], + "out_clusters": [3, 4, 5, 18], + "profile_id": 260, + }, + "3": { + "device_type": 24323, + "endpoint_id": 3, + "in_clusters": [3], + "out_clusters": [3, 4, 5, 12], + "profile_id": 260, + }, + }, + "entities": [ + "sensor.lumi_lumi_sensor_ht_77665544_power", + "sensor.lumi_lumi_sensor_ht_77665544_temperature", + "sensor.lumi_lumi_sensor_ht_77665544_humidity", + ], + "event_channels": [], + "manufacturer": "LUMI", + "model": "lumi.sensor_ht", + }, + { + "endpoints": { + "1": { + "device_type": 2128, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 25, 65535], + "out_clusters": [0, 3, 4, 5, 6, 8, 25], + "profile_id": 260, + } + }, + "entities": [ + "sensor.lumi_lumi_sensor_magnet_77665544_power", + "binary_sensor.lumi_lumi_sensor_magnet_77665544_on_off", + ], + "event_channels": [6, 8], + "manufacturer": "LUMI", + "model": "lumi.sensor_magnet", + }, + { + "endpoints": { + "1": { + "device_type": 24321, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 65535], + "out_clusters": [0, 4, 6, 65535], + "profile_id": 260, + } + }, + "entities": [ + "binary_sensor.lumi_lumi_sensor_magnet_aq2_77665544_on_off", + "sensor.lumi_lumi_sensor_magnet_aq2_77665544_power", + ], + "event_channels": [6], + "manufacturer": "LUMI", + "model": "lumi.sensor_magnet.aq2", + }, + { + "endpoints": { + "1": { + "device_type": 263, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 1024, 1030, 1280, 65535], + "out_clusters": [0, 25], + "profile_id": 260, + } + }, + "entities": [ + "binary_sensor.lumi_lumi_sensor_motion_aq2_77665544_occupancy", + "binary_sensor.lumi_lumi_sensor_motion_aq2_77665544_ias_zone", + "sensor.lumi_lumi_sensor_motion_aq2_77665544_illuminance", + "sensor.lumi_lumi_sensor_motion_aq2_77665544_power", + ], + "event_channels": [], + "manufacturer": "LUMI", + "model": "lumi.sensor_motion.aq2", + }, + { + "endpoints": { + "1": { + "device_type": 6, + "endpoint_id": 1, + "in_clusters": [0, 1, 3], + "out_clusters": [0, 4, 5, 6, 8, 25], + "profile_id": 260, + } + }, + "entities": ["sensor.lumi_lumi_sensor_switch_77665544_power"], + "event_channels": [6, 8], + "manufacturer": "LUMI", + "model": "lumi.sensor_switch", + }, + { + "endpoints": { + "1": { + "device_type": 6, + "endpoint_id": 1, + "in_clusters": [0, 1, 65535], + "out_clusters": [0, 4, 6, 65535], + "profile_id": 260, + } + }, + "entities": ["sensor.lumi_lumi_sensor_switch_aq2_77665544_power"], + "event_channels": [6], + "manufacturer": "LUMI", + "model": "lumi.sensor_switch.aq2", + }, + { + "endpoints": { + "1": { + "device_type": 6, + "endpoint_id": 1, + "in_clusters": [0, 1, 18], + "out_clusters": [0, 6], + "profile_id": 260, + } + }, + "entities": [ + "sensor.lumi_lumi_sensor_switch_aq3_77665544_multistate_input", + "sensor.lumi_lumi_sensor_switch_aq3_77665544_power", + ], + "event_channels": [6], + "manufacturer": "LUMI", + "model": "lumi.sensor_switch.aq3", + }, + { + "endpoints": { + "1": { + "device_type": 1026, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 1280], + "out_clusters": [25], + "profile_id": 260, + } + }, + "entities": [ + "binary_sensor.lumi_lumi_sensor_wleak_aq1_77665544_ias_zone", + "sensor.lumi_lumi_sensor_wleak_aq1_77665544_power", + ], + "event_channels": [], + "manufacturer": "LUMI", + "model": "lumi.sensor_wleak.aq1", + }, + { + "endpoints": { + "1": { + "device_type": 10, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 25, 257, 1280], + "out_clusters": [0, 3, 4, 5, 25], + "profile_id": 260, + }, + "2": { + "device_type": 24322, + "endpoint_id": 2, + "in_clusters": [3], + "out_clusters": [3, 4, 5, 18], + "profile_id": 260, + }, + }, + "entities": [ + "binary_sensor.lumi_lumi_vibration_aq1_77665544_ias_zone", + "sensor.lumi_lumi_vibration_aq1_77665544_power", + "lock.lumi_lumi_vibration_aq1_77665544_door_lock", + ], + "event_channels": [], + "manufacturer": "LUMI", + "model": "lumi.vibration.aq1", + }, + { + "endpoints": { + "1": { + "device_type": 24321, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 1026, 1027, 1029, 65535], + "out_clusters": [0, 4, 65535], + "profile_id": 260, + } + }, + "entities": [ + "sensor.lumi_lumi_weather_77665544_temperature", + "sensor.lumi_lumi_weather_77665544_power", + "sensor.lumi_lumi_weather_77665544_humidity", + "sensor.lumi_lumi_weather_77665544_pressure", + ], + "event_channels": [], + "manufacturer": "LUMI", + "model": "lumi.weather", + }, + { + "endpoints": { + "1": { + "device_type": 1026, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 32, 1280], + "out_clusters": [], + "profile_id": 260, + } + }, + "entities": [ + "binary_sensor.nyce_3010_77665544_ias_zone", + "sensor.nyce_3010_77665544_power", + ], + "event_channels": [], + "manufacturer": "NYCE", + "model": "3010", + }, + { + "endpoints": { + "1": { + "device_type": 1026, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 32, 1280], + "out_clusters": [], + "profile_id": 260, + } + }, + "entities": [ + "binary_sensor.nyce_3014_77665544_ias_zone", + "sensor.nyce_3014_77665544_power", + ], + "event_channels": [], + "manufacturer": "NYCE", + "model": "3014", + }, + { + "endpoints": { + "3": { + "device_type": 258, + "endpoint_id": 3, + "in_clusters": [0, 3, 4, 5, 6, 8, 768, 64527], + "out_clusters": [25], + "profile_id": 260, + } + }, + "entities": ["light.osram_lightify_a19_rgbw_77665544_level_light_color_on_off"], + "event_channels": [], + "manufacturer": "OSRAM", + "model": "LIGHTIFY A19 RGBW", + }, + { + "endpoints": { + "1": { + "device_type": 1, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 32, 2821], + "out_clusters": [3, 6, 8, 25], + "profile_id": 260, + } + }, + "entities": [ + "binary_sensor.osram_lightify_dimming_switch_77665544_on_off", + "sensor.osram_lightify_dimming_switch_77665544_power", + ], + "event_channels": [6, 8], + "manufacturer": "OSRAM", + "model": "LIGHTIFY Dimming Switch", + }, + { + "endpoints": { + "3": { + "device_type": 258, + "endpoint_id": 3, + "in_clusters": [0, 3, 4, 5, 6, 8, 768, 64527], + "out_clusters": [25], + "profile_id": 260, + } + }, + "entities": [ + "light.osram_lightify_flex_rgbw_77665544_level_light_color_on_off" + ], + "event_channels": [], + "manufacturer": "OSRAM", + "model": "LIGHTIFY Flex RGBW", + }, + { + "endpoints": { + "3": { + "device_type": 258, + "endpoint_id": 3, + "in_clusters": [0, 3, 4, 5, 6, 8, 768, 2820, 64527], + "out_clusters": [25], + "profile_id": 260, + } + }, + "entities": [ + "sensor.osram_lightify_rt_tunable_white_77665544_electrical_measurement", + "light.osram_lightify_rt_tunable_white_77665544_level_light_color_on_off", + ], + "event_channels": [], + "manufacturer": "OSRAM", + "model": "LIGHTIFY RT Tunable White", + }, + { + "endpoints": { + "3": { + "device_type": 16, + "endpoint_id": 3, + "in_clusters": [0, 3, 4, 5, 6, 2820, 4096, 64527], + "out_clusters": [25], + "profile_id": 49246, + } + }, + "entities": [ + "sensor.osram_plug_01_77665544_electrical_measurement", + "switch.osram_plug_01_77665544_on_off", + ], + "event_channels": [], + "manufacturer": "OSRAM", + "model": "Plug 01", + }, + { + "endpoints": { + "1": { + "device_type": 2064, + "endpoint_id": 1, + "in_clusters": [0, 1, 32, 4096, 64768], + "out_clusters": [3, 4, 5, 6, 8, 25, 768, 4096], + "profile_id": 260, + }, + "2": { + "device_type": 2064, + "endpoint_id": 2, + "in_clusters": [0, 4096, 64768], + "out_clusters": [3, 4, 5, 6, 8, 768, 4096], + "profile_id": 260, + }, + "3": { + "device_type": 2064, + "endpoint_id": 3, + "in_clusters": [0, 4096, 64768], + "out_clusters": [3, 4, 5, 6, 8, 768, 4096], + "profile_id": 260, + }, + "4": { + "device_type": 2064, + "endpoint_id": 4, + "in_clusters": [0, 4096, 64768], + "out_clusters": [3, 4, 5, 6, 8, 768, 4096], + "profile_id": 260, + }, + "5": { + "device_type": 2064, + "endpoint_id": 5, + "in_clusters": [0, 4096, 64768], + "out_clusters": [3, 4, 5, 6, 8, 768, 4096], + "profile_id": 260, + }, + "6": { + "device_type": 2064, + "endpoint_id": 6, + "in_clusters": [0, 4096, 64768], + "out_clusters": [3, 4, 5, 6, 8, 768, 4096], + "profile_id": 260, + }, + }, + "entities": ["sensor.osram_switch_4x_lightify_77665544_power"], + "event_channels": [ + 6, + 8, + 768, + 6, + 8, + 768, + 6, + 8, + 768, + 6, + 8, + 768, + 6, + 8, + 768, + 6, + 8, + 768, + ], + "manufacturer": "OSRAM", + "model": "Switch 4x-LIGHTIFY", + }, + { + "endpoints": { + "1": { + "device_type": 2096, + "endpoint_id": 1, + "in_clusters": [0], + "out_clusters": [0, 3, 4, 5, 6, 8], + "profile_id": 49246, + }, + "2": { + "device_type": 12, + "endpoint_id": 2, + "in_clusters": [0, 1, 3, 15, 64512], + "out_clusters": [25], + "profile_id": 260, + }, + }, + "entities": ["sensor.philips_rwl020_77665544_power"], + "event_channels": [6, 8], + "manufacturer": "Philips", + "model": "RWL020", + }, + { + "endpoints": { + "1": { + "device_type": 1026, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 32, 1026, 1280, 2821], + "out_clusters": [3, 25], + "profile_id": 260, + } + }, + "entities": [ + "binary_sensor.samjin_button_77665544_ias_zone", + "sensor.samjin_button_77665544_temperature", + "sensor.samjin_button_77665544_power", + ], + "event_channels": [], + "manufacturer": "Samjin", + "model": "button", + }, + { + "endpoints": { + "1": { + "device_type": 1026, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 32, 1026, 1280, 64514], + "out_clusters": [3, 25], + "profile_id": 260, + } + }, + "entities": [ + "sensor.samjin_multi_77665544_power", + "sensor.samjin_multi_77665544_temperature", + "binary_sensor.samjin_multi_77665544_ias_zone", + "binary_sensor.samjin_multi_77665544_manufacturer_specific", + ], + "event_channels": [], + "manufacturer": "Samjin", + "model": "multi", + }, + { + "endpoints": { + "1": { + "device_type": 1026, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 32, 1026, 1280], + "out_clusters": [3, 25], + "profile_id": 260, + } + }, + "entities": [ + "binary_sensor.samjin_water_77665544_ias_zone", + "sensor.samjin_water_77665544_power", + "sensor.samjin_water_77665544_temperature", + ], + "event_channels": [], + "manufacturer": "Samjin", + "model": "water", + }, + { + "endpoints": { + "1": { + "device_type": 0, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 4, 5, 6, 2820, 2821], + "out_clusters": [0, 1, 3, 4, 5, 6, 25, 2820, 2821], + "profile_id": 260, + } + }, + "entities": [ + "binary_sensor.securifi_ltd_unk_model_77665544_on_off", + "sensor.securifi_ltd_unk_model_77665544_electrical_measurement", + "sensor.securifi_ltd_unk_model_77665544_power", + "switch.securifi_ltd_unk_model_77665544_on_off", + ], + "event_channels": [6], + "manufacturer": "Securifi Ltd.", + "model": None, + }, + { + "endpoints": { + "1": { + "device_type": 1026, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 32, 1026, 1280, 2821], + "out_clusters": [3, 25], + "profile_id": 260, + } + }, + "entities": [ + "binary_sensor.sercomm_corp_sz_dws04n_sf_77665544_ias_zone", + "sensor.sercomm_corp_sz_dws04n_sf_77665544_power", + "sensor.sercomm_corp_sz_dws04n_sf_77665544_temperature", + ], + "event_channels": [], + "manufacturer": "Sercomm Corp.", + "model": "SZ-DWS04N_SF", + }, + { + "endpoints": { + "1": { + "device_type": 256, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 4, 5, 6, 1794, 2820, 2821], + "out_clusters": [3, 10, 25, 2821], + "profile_id": 260, + }, + "2": { + "device_type": 259, + "endpoint_id": 2, + "in_clusters": [0, 1, 3], + "out_clusters": [3, 6], + "profile_id": 260, + }, + }, + "entities": [ + "sensor.sercomm_corp_sz_esw01_77665544_smartenergy_metering", + "sensor.sercomm_corp_sz_esw01_77665544_power", + "sensor.sercomm_corp_sz_esw01_77665544_power_2", + "sensor.sercomm_corp_sz_esw01_77665544_electrical_measurement", + "switch.sercomm_corp_sz_esw01_77665544_on_off", + "light.sercomm_corp_sz_esw01_77665544_on_off", + ], + "event_channels": [6], + "manufacturer": "Sercomm Corp.", + "model": "SZ-ESW01", + }, + { + "endpoints": { + "1": { + "device_type": 1026, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 32, 1024, 1026, 1280, 2821], + "out_clusters": [25], + "profile_id": 260, + } + }, + "entities": [ + "binary_sensor.sercomm_corp_sz_pir04_77665544_ias_zone", + "sensor.sercomm_corp_sz_pir04_77665544_temperature", + "sensor.sercomm_corp_sz_pir04_77665544_illuminance", + "sensor.sercomm_corp_sz_pir04_77665544_power", + ], + "event_channels": [], + "manufacturer": "Sercomm Corp.", + "model": "SZ-PIR04", + }, + { + "endpoints": { + "1": { + "device_type": 2, + "endpoint_id": 1, + "in_clusters": [0, 3, 4, 5, 6, 2820, 2821, 65281], + "out_clusters": [3, 4, 25], + "profile_id": 260, + } + }, + "entities": [ + "sensor.sinope_technologies_rm3250zb_77665544_electrical_measurement", + "switch.sinope_technologies_rm3250zb_77665544_on_off", + ], + "event_channels": [], + "manufacturer": "Sinope Technologies", + "model": "RM3250ZB", + }, + { + "endpoints": { + "1": { + "device_type": 769, + "endpoint_id": 1, + "in_clusters": [0, 3, 4, 5, 513, 516, 1026, 2820, 2821, 65281], + "out_clusters": [25, 65281], + "profile_id": 260, + }, + "196": { + "device_type": 769, + "endpoint_id": 196, + "in_clusters": [1], + "out_clusters": [], + "profile_id": 49757, + }, + }, + "entities": [ + "sensor.sinope_technologies_th1124zb_77665544_temperature", + "sensor.sinope_technologies_th1124zb_77665544_power", + "sensor.sinope_technologies_th1124zb_77665544_electrical_measurement", + ], + "event_channels": [], + "manufacturer": "Sinope Technologies", + "model": "TH1124ZB", + }, + { + "endpoints": { + "1": { + "device_type": 2, + "endpoint_id": 1, + "in_clusters": [0, 3, 4, 5, 6, 9, 15, 2820], + "out_clusters": [25], + "profile_id": 260, + } + }, + "entities": [ + "sensor.smartthings_outletv4_77665544_electrical_measurement", + "switch.smartthings_outletv4_77665544_on_off", + ], + "event_channels": [], + "manufacturer": "SmartThings", + "model": "outletv4", + }, + { + "endpoints": { + "1": { + "device_type": 32768, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 15, 32], + "out_clusters": [3, 25], + "profile_id": 260, + } + }, + "entities": ["device_tracker.smartthings_tagv4_77665544_power"], + "event_channels": [], + "manufacturer": "SmartThings", + "model": "tagv4", + }, + { + "endpoints": { + "1": { + "device_type": 2, + "endpoint_id": 1, + "in_clusters": [0, 3, 4, 5, 6, 25], + "out_clusters": [], + "profile_id": 260, + } + }, + "entities": ["switch.third_reality_inc_3rss007z_77665544_on_off"], + "event_channels": [], + "manufacturer": "Third Reality, Inc", + "model": "3RSS007Z", + }, + { + "endpoints": { + "1": { + "device_type": 2, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 4, 5, 6, 25], + "out_clusters": [1], + "profile_id": 260, + } + }, + "entities": [ + "sensor.third_reality_inc_3rss008z_77665544_power", + "switch.third_reality_inc_3rss008z_77665544_on_off", + ], + "event_channels": [], + "manufacturer": "Third Reality, Inc", + "model": "3RSS008Z", + }, + { + "endpoints": { + "1": { + "device_type": 1026, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 32, 1026, 1280, 2821], + "out_clusters": [25], + "profile_id": 260, + } + }, + "entities": [ + "binary_sensor.visonic_mct_340_e_77665544_ias_zone", + "sensor.visonic_mct_340_e_77665544_temperature", + "sensor.visonic_mct_340_e_77665544_power", + ], + "event_channels": [], + "manufacturer": "Visonic", + "model": "MCT-340 E", + }, + { + "endpoints": { + "1": { + "device_type": 1026, + "endpoint_id": 1, + "in_clusters": [0, 1, 3, 21, 32, 1280, 2821], + "out_clusters": [], + "profile_id": 260, + } + }, + "entities": [ + "binary_sensor.netvox_z308e3ed_77665544_ias_zone", + "sensor.netvox_z308e3ed_77665544_power", + ], + "event_channels": [], + "manufacturer": "netvox", + "model": "Z308E3ED", + }, + { + "endpoints": { + "1": { + "device_type": 257, + "endpoint_id": 1, + "in_clusters": [0, 3, 4, 5, 6, 8, 1794, 2821], + "out_clusters": [25], + "profile_id": 260, + } + }, + "entities": [ + "light.sengled_e11_g13_77665544_level_on_off", + "sensor.sengled_e11_g13_77665544_smartenergy_metering", + ], + "event_channels": [], + "manufacturer": "sengled", + "model": "E11-G13", + }, + { + "endpoints": { + "1": { + "device_type": 257, + "endpoint_id": 1, + "in_clusters": [0, 3, 4, 5, 6, 8, 1794, 2821], + "out_clusters": [25], + "profile_id": 260, + } + }, + "entities": [ + "sensor.sengled_e12_n14_77665544_smartenergy_metering", + "light.sengled_e12_n14_77665544_level_on_off", + ], + "event_channels": [], + "manufacturer": "sengled", + "model": "E12-N14", + }, + { + "endpoints": { + "1": { + "device_type": 257, + "endpoint_id": 1, + "in_clusters": [0, 3, 4, 5, 6, 8, 768, 1794, 2821], + "out_clusters": [25], + "profile_id": 260, + } + }, + "entities": [ + "sensor.sengled_z01_a19nae26_77665544_smartenergy_metering", + "light.sengled_z01_a19nae26_77665544_level_light_color_on_off", + ], + "event_channels": [], + "manufacturer": "sengled", + "model": "Z01-A19NAE26", + }, +] From 2620a95944e2f364487caaa849cf6f3219a50a52 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 31 Dec 2019 17:10:39 +0100 Subject: [PATCH 456/677] Improve Withings tests in different time zone (#30326) * Improve Withings tests in different time zone * Address code review comment * Spelling error in code doc --- tests/components/withings/test_common.py | 26 ++++++++++++------------ 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/components/withings/test_common.py b/tests/components/withings/test_common.py index 6c8c0a4c310..acb69dddf4e 100644 --- a/tests/components/withings/test_common.py +++ b/tests/components/withings/test_common.py @@ -1,5 +1,6 @@ """Tests for the Withings component.""" from datetime import timedelta +from unittest.mock import patch from asynctest import MagicMock import pytest @@ -10,18 +11,10 @@ from homeassistant.components.withings.common import ( NotAuthenticatedError, WithingsDataManager, ) -from homeassistant.config import async_process_ha_core_config from homeassistant.core import HomeAssistant from homeassistant.exceptions import PlatformNotReady from homeassistant.util import dt -DEFAULT_TIME_ZONE = dt.DEFAULT_TIME_ZONE - - -def teardown(): - """Ensure the time zone is reverted after tests finish.""" - dt.set_default_time_zone(DEFAULT_TIME_ZONE) - @pytest.fixture(name="withings_api") def withings_api_fixture() -> WithingsApi: @@ -34,6 +27,17 @@ def withings_api_fixture() -> WithingsApi: return withings_api +@pytest.fixture +def mock_time_zone(): + """Provide an alternative time zone.""" + patch_time_zone = patch( + "homeassistant.util.dt.DEFAULT_TIME_ZONE", + new=dt.get_time_zone("America/Los_Angeles"), + ) + with patch_time_zone: + yield + + @pytest.fixture(name="data_manager") def data_manager_fixture(hass, withings_api: WithingsApi) -> WithingsDataManager: """Provide data manager.""" @@ -118,13 +122,9 @@ async def test_data_manager_call_throttle_disabled( async def test_data_manager_update_sleep_date_range( - hass: HomeAssistant, data_manager: WithingsDataManager, + hass: HomeAssistant, data_manager: WithingsDataManager, mock_time_zone ) -> None: """Test method.""" - await async_process_ha_core_config( - hass=hass, config={"time_zone": "America/Los_Angeles"} - ) - update_start_time = dt.now() await data_manager.update_sleep() From 30dbed3f983b85ff3f71118e0ccd7ebb75ce32f0 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 31 Dec 2019 21:03:21 +0100 Subject: [PATCH 457/677] Migrate dsmr tests from coroutine to async/await (#30333) --- tests/components/dsmr/test_sensor.py | 45 ++++++++++++---------------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/tests/components/dsmr/test_sensor.py b/tests/components/dsmr/test_sensor.py index 195345dd489..81249c04046 100644 --- a/tests/components/dsmr/test_sensor.py +++ b/tests/components/dsmr/test_sensor.py @@ -27,8 +27,7 @@ def mock_connection_factory(monkeypatch): transport = asynctest.Mock(spec=asyncio.Transport) protocol = asynctest.Mock(spec=DSMRProtocol) - @asyncio.coroutine - def connection_factory(*args, **kwargs): + async def connection_factory(*args, **kwargs): """Return mocked out Asyncio classes.""" return (transport, protocol) @@ -46,8 +45,7 @@ def mock_connection_factory(monkeypatch): return connection_factory, transport, protocol -@asyncio.coroutine -def test_default_setup(hass, mock_connection_factory): +async def test_default_setup(hass, mock_connection_factory): """Test the default setup.""" (connection_factory, transport, protocol) = mock_connection_factory @@ -67,7 +65,7 @@ def test_default_setup(hass, mock_connection_factory): } with assert_setup_component(1): - yield from async_setup_component(hass, "sensor", {"sensor": config}) + await async_setup_component(hass, "sensor", {"sensor": config}) telegram_callback = connection_factory.call_args_list[0][0][2] @@ -80,7 +78,7 @@ def test_default_setup(hass, mock_connection_factory): telegram_callback(telegram) # after receiving telegram entities need to have the chance to update - yield from asyncio.sleep(0) + await asyncio.sleep(0) # ensure entities have new state value after incoming telegram power_consumption = hass.states.get("sensor.power_consumption") @@ -93,15 +91,14 @@ def test_default_setup(hass, mock_connection_factory): assert power_tariff.attributes.get("unit_of_measurement") == "" -@asyncio.coroutine -def test_derivative(): +async def test_derivative(): """Test calculation of derivative value.""" from dsmr_parser.objects import MBusObject config = {"platform": "dsmr"} entity = DerivativeDSMREntity("test", "1.0.0", config) - yield from entity.async_update() + await entity.async_update() assert entity.state is None, "initial state not unknown" @@ -113,7 +110,7 @@ def test_derivative(): ] ) } - yield from entity.async_update() + await entity.async_update() assert entity.state is None, "state after first update should still be unknown" @@ -125,7 +122,7 @@ def test_derivative(): ] ) } - yield from entity.async_update() + await entity.async_update() assert ( abs(entity.state - 0.033) < 0.00001 @@ -134,22 +131,20 @@ def test_derivative(): assert entity.unit_of_measurement == "m3/h" -@asyncio.coroutine -def test_tcp(hass, mock_connection_factory): +async def test_tcp(hass, mock_connection_factory): """If proper config provided TCP connection should be made.""" (connection_factory, transport, protocol) = mock_connection_factory config = {"platform": "dsmr", "host": "localhost", "port": 1234} with assert_setup_component(1): - yield from async_setup_component(hass, "sensor", {"sensor": config}) + await async_setup_component(hass, "sensor", {"sensor": config}) assert connection_factory.call_args_list[0][0][0] == "localhost" assert connection_factory.call_args_list[0][0][1] == "1234" -@asyncio.coroutine -def test_connection_errors_retry(hass, monkeypatch, mock_connection_factory): +async def test_connection_errors_retry(hass, monkeypatch, mock_connection_factory): """Connection should be retried on error during setup.""" (connection_factory, transport, protocol) = mock_connection_factory @@ -164,15 +159,14 @@ def test_connection_errors_retry(hass, monkeypatch, mock_connection_factory): "homeassistant.components.dsmr.sensor.create_dsmr_reader", first_fail_connection_factory, ) - yield from async_setup_component(hass, "sensor", {"sensor": config}) + await async_setup_component(hass, "sensor", {"sensor": config}) # wait for sleep to resolve - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert first_fail_connection_factory.call_count == 2, "connecting not retried" -@asyncio.coroutine -def test_reconnect(hass, monkeypatch, mock_connection_factory): +async def test_reconnect(hass, monkeypatch, mock_connection_factory): """If transport disconnects, the connection should be retried.""" (connection_factory, transport, protocol) = mock_connection_factory config = {"platform": "dsmr", "reconnect_interval": 0} @@ -182,26 +176,25 @@ def test_reconnect(hass, monkeypatch, mock_connection_factory): # Handshake so that `hass.async_block_till_done()` doesn't cycle forever closed2 = asyncio.Event() - @asyncio.coroutine - def wait_closed(): - yield from closed.wait() + async def wait_closed(): + await closed.wait() closed2.set() closed.clear() protocol.wait_closed = wait_closed - yield from async_setup_component(hass, "sensor", {"sensor": config}) + await async_setup_component(hass, "sensor", {"sensor": config}) assert connection_factory.call_count == 1 # indicate disconnect, release wait lock and allow reconnect to happen closed.set() # wait for lock set to resolve - yield from closed2.wait() + await closed2.wait() closed2.clear() assert not closed.is_set() closed.set() - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert connection_factory.call_count >= 2, "connecting not retried" From cd8f954a4d57130921c3023a5d53f386f9309ada Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 31 Dec 2019 21:04:24 +0100 Subject: [PATCH 458/677] Migrate wunderground tests from coroutine to async/await (#30330) --- tests/components/wunderground/test_sensor.py | 32 ++++++++------------ 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/tests/components/wunderground/test_sensor.py b/tests/components/wunderground/test_sensor.py index 70264249157..bfe9f83fbc3 100644 --- a/tests/components/wunderground/test_sensor.py +++ b/tests/components/wunderground/test_sensor.py @@ -1,6 +1,4 @@ """The tests for the WUnderground platform.""" -import asyncio - import aiohttp from pytest import raises @@ -59,39 +57,35 @@ INVALID_URL = ( ) -@asyncio.coroutine -def test_setup(hass, aioclient_mock): +async def test_setup(hass, aioclient_mock): """Test that the component is loaded.""" aioclient_mock.get(URL, text=load_fixture("wunderground-valid.json")) with assert_setup_component(1, "sensor"): - yield from async_setup_component(hass, "sensor", {"sensor": VALID_CONFIG}) + await async_setup_component(hass, "sensor", {"sensor": VALID_CONFIG}) -@asyncio.coroutine -def test_setup_pws(hass, aioclient_mock): +async def test_setup_pws(hass, aioclient_mock): """Test that the component is loaded with PWS id.""" aioclient_mock.get(PWS_URL, text=load_fixture("wunderground-valid.json")) with assert_setup_component(1, "sensor"): - yield from async_setup_component(hass, "sensor", {"sensor": VALID_CONFIG_PWS}) + await async_setup_component(hass, "sensor", {"sensor": VALID_CONFIG_PWS}) -@asyncio.coroutine -def test_setup_invalid(hass, aioclient_mock): +async def test_setup_invalid(hass, aioclient_mock): """Test that the component is not loaded with invalid config.""" aioclient_mock.get(INVALID_URL, text=load_fixture("wunderground-error.json")) with assert_setup_component(0, "sensor"): - yield from async_setup_component(hass, "sensor", {"sensor": INVALID_CONFIG}) + await async_setup_component(hass, "sensor", {"sensor": INVALID_CONFIG}) -@asyncio.coroutine -def test_sensor(hass, aioclient_mock): +async def test_sensor(hass, aioclient_mock): """Test the WUnderground sensor class and methods.""" aioclient_mock.get(URL, text=load_fixture("wunderground-valid.json")) - yield from async_setup_component(hass, "sensor", {"sensor": VALID_CONFIG}) + await async_setup_component(hass, "sensor", {"sensor": VALID_CONFIG}) state = hass.states.get("sensor.pws_weather") assert state.state == "Clear" @@ -132,20 +126,18 @@ def test_sensor(hass, aioclient_mock): assert state.attributes["unit_of_measurement"] == LENGTH_INCHES -@asyncio.coroutine -def test_connect_failed(hass, aioclient_mock): +async def test_connect_failed(hass, aioclient_mock): """Test the WUnderground connection error.""" aioclient_mock.get(URL, exc=aiohttp.ClientError()) with raises(PlatformNotReady): - yield from wunderground.async_setup_platform(hass, VALID_CONFIG, lambda _: None) + await wunderground.async_setup_platform(hass, VALID_CONFIG, lambda _: None) -@asyncio.coroutine -def test_invalid_data(hass, aioclient_mock): +async def test_invalid_data(hass, aioclient_mock): """Test the WUnderground invalid data.""" aioclient_mock.get(URL, text=load_fixture("wunderground-invalid.json")) - yield from async_setup_component(hass, "sensor", {"sensor": VALID_CONFIG}) + await async_setup_component(hass, "sensor", {"sensor": VALID_CONFIG}) for condition in VALID_CONFIG["monitored_conditions"]: state = hass.states.get("sensor.pws_" + condition) From d8d75d882be0b485c1f54969eaae6713ec71f01d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 31 Dec 2019 21:52:45 +0100 Subject: [PATCH 459/677] Bump pyps4-2ndscreen to 1.0.4 (#30327) --- homeassistant/components/ps4/manifest.json | 8 ++------ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/ps4/manifest.json b/homeassistant/components/ps4/manifest.json index add52231a07..c7e6d1d9ba7 100644 --- a/homeassistant/components/ps4/manifest.json +++ b/homeassistant/components/ps4/manifest.json @@ -3,11 +3,7 @@ "name": "Ps4", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/ps4", - "requirements": [ - "pyps4-2ndscreen==1.0.3" - ], + "requirements": ["pyps4-2ndscreen==1.0.4"], "dependencies": [], - "codeowners": [ - "@ktnrg45" - ] + "codeowners": ["@ktnrg45"] } diff --git a/requirements_all.txt b/requirements_all.txt index 0e163221d11..31c45d2e3b9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1448,7 +1448,7 @@ pypjlink2==1.2.0 pypoint==1.1.2 # homeassistant.components.ps4 -pyps4-2ndscreen==1.0.3 +pyps4-2ndscreen==1.0.4 # homeassistant.components.qwikswitch pyqwikswitch==0.93 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 25d16e27ce8..f3ba482a977 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -487,7 +487,7 @@ pyotp==2.3.0 pypoint==1.1.2 # homeassistant.components.ps4 -pyps4-2ndscreen==1.0.3 +pyps4-2ndscreen==1.0.4 # homeassistant.components.qwikswitch pyqwikswitch==0.93 From f11d39f8ebf281a2069dc9f01777869c59405be4 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Tue, 31 Dec 2019 21:54:31 +0100 Subject: [PATCH 460/677] Bump alarmdecoder to 1.13.9 (#30303) --- homeassistant/components/alarmdecoder/manifest.json | 4 +--- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/alarmdecoder/manifest.json b/homeassistant/components/alarmdecoder/manifest.json index 5ab69d94cf2..0f41405d431 100644 --- a/homeassistant/components/alarmdecoder/manifest.json +++ b/homeassistant/components/alarmdecoder/manifest.json @@ -2,9 +2,7 @@ "domain": "alarmdecoder", "name": "Alarmdecoder", "documentation": "https://www.home-assistant.io/integrations/alarmdecoder", - "requirements": [ - "alarmdecoder==1.13.2" - ], + "requirements": ["alarmdecoder==1.13.9"], "dependencies": [], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index 31c45d2e3b9..3d60bc22d11 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -202,7 +202,7 @@ airly==0.0.2 aladdin_connect==0.3 # homeassistant.components.alarmdecoder -alarmdecoder==1.13.2 +alarmdecoder==1.13.9 # homeassistant.components.alpha_vantage alpha_vantage==2.1.2 From 687a052d40150c41263b8aea4ac1c8b036e350ef Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 31 Dec 2019 22:48:43 +0100 Subject: [PATCH 461/677] Migrate python_script tests from coroutine to async/await (#30335) --- tests/components/python_script/test_init.py | 92 ++++++++------------- 1 file changed, 36 insertions(+), 56 deletions(-) diff --git a/tests/components/python_script/test_init.py b/tests/components/python_script/test_init.py index b5879479837..f61f0004723 100644 --- a/tests/components/python_script/test_init.py +++ b/tests/components/python_script/test_init.py @@ -1,5 +1,4 @@ """Test the python_script component.""" -import asyncio import logging from unittest.mock import mock_open, patch @@ -10,8 +9,7 @@ from homeassistant.setup import async_setup_component from tests.common import patch_yaml_files -@asyncio.coroutine -def test_setup(hass): +async def test_setup(hass): """Test we can discover scripts.""" scripts = [ "/some/config/dir/python_scripts/hello.py", @@ -20,7 +18,7 @@ def test_setup(hass): with patch( "homeassistant.components.python_script.os.path.isdir", return_value=True ), patch("homeassistant.components.python_script.glob.iglob", return_value=scripts): - res = yield from async_setup_component(hass, "python_script", {}) + res = await async_setup_component(hass, "python_script", {}) assert res assert hass.services.has_service("python_script", "hello") @@ -31,7 +29,7 @@ def test_setup(hass): mock_open(read_data="fake source"), create=True, ), patch("homeassistant.components.python_script.execute") as mock_ex: - yield from hass.services.async_call( + await hass.services.async_call( "python_script", "hello", {"some": "data"}, blocking=True ) @@ -44,20 +42,18 @@ def test_setup(hass): assert data == {"some": "data"} -@asyncio.coroutine -def test_setup_fails_on_no_dir(hass, caplog): +async def test_setup_fails_on_no_dir(hass, caplog): """Test we fail setup when no dir found.""" with patch( "homeassistant.components.python_script.os.path.isdir", return_value=False ): - res = yield from async_setup_component(hass, "python_script", {}) + res = await async_setup_component(hass, "python_script", {}) assert not res assert "Folder python_scripts not found in configuration folder" in caplog.text -@asyncio.coroutine -def test_execute_with_data(hass, caplog): +async def test_execute_with_data(hass, caplog): """Test executing a script.""" caplog.set_level(logging.WARNING) source = """ @@ -65,7 +61,7 @@ hass.states.set('test.entity', data.get('name', 'not set')) """ hass.async_add_job(execute, hass, "test.py", source, {"name": "paulus"}) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert hass.states.is_state("test.entity", "paulus") @@ -73,8 +69,7 @@ hass.states.set('test.entity', data.get('name', 'not set')) assert caplog.text == "" -@asyncio.coroutine -def test_execute_warns_print(hass, caplog): +async def test_execute_warns_print(hass, caplog): """Test print triggers warning.""" caplog.set_level(logging.WARNING) source = """ @@ -82,13 +77,12 @@ print("This triggers warning.") """ hass.async_add_job(execute, hass, "test.py", source, {}) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert "Don't use print() inside scripts." in caplog.text -@asyncio.coroutine -def test_execute_logging(hass, caplog): +async def test_execute_logging(hass, caplog): """Test logging works.""" caplog.set_level(logging.INFO) source = """ @@ -96,13 +90,12 @@ logger.info('Logging from inside script') """ hass.async_add_job(execute, hass, "test.py", source, {}) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert "Logging from inside script" in caplog.text -@asyncio.coroutine -def test_execute_compile_error(hass, caplog): +async def test_execute_compile_error(hass, caplog): """Test compile error logs error.""" caplog.set_level(logging.ERROR) source = """ @@ -110,13 +103,12 @@ this is not valid Python """ hass.async_add_job(execute, hass, "test.py", source, {}) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert "Error loading script test.py" in caplog.text -@asyncio.coroutine -def test_execute_runtime_error(hass, caplog): +async def test_execute_runtime_error(hass, caplog): """Test compile error logs error.""" caplog.set_level(logging.ERROR) source = """ @@ -124,13 +116,12 @@ raise Exception('boom') """ hass.async_add_job(execute, hass, "test.py", source, {}) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert "Error executing script: boom" in caplog.text -@asyncio.coroutine -def test_accessing_async_methods(hass, caplog): +async def test_accessing_async_methods(hass, caplog): """Test compile error logs error.""" caplog.set_level(logging.ERROR) source = """ @@ -138,13 +129,12 @@ hass.async_stop() """ hass.async_add_job(execute, hass, "test.py", source, {}) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert "Not allowed to access async methods" in caplog.text -@asyncio.coroutine -def test_using_complex_structures(hass, caplog): +async def test_using_complex_structures(hass, caplog): """Test that dicts and lists work.""" caplog.set_level(logging.INFO) source = """ @@ -154,13 +144,12 @@ logger.info('Logging from inside script: %s %s' % (mydict["a"], mylist[2])) """ hass.async_add_job(execute, hass, "test.py", source, {}) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert "Logging from inside script: 1 3" in caplog.text -@asyncio.coroutine -def test_accessing_forbidden_methods(hass, caplog): +async def test_accessing_forbidden_methods(hass, caplog): """Test compile error logs error.""" caplog.set_level(logging.ERROR) @@ -172,12 +161,11 @@ def test_accessing_forbidden_methods(hass, caplog): }.items(): caplog.records.clear() hass.async_add_job(execute, hass, "test.py", source, {}) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert "Not allowed to access {}".format(name) in caplog.text -@asyncio.coroutine -def test_iterating(hass): +async def test_iterating(hass): """Test compile error logs error.""" source = """ for i in [1, 2]: @@ -185,14 +173,13 @@ for i in [1, 2]: """ hass.async_add_job(execute, hass, "test.py", source, {}) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert hass.states.is_state("hello.1", "world") assert hass.states.is_state("hello.2", "world") -@asyncio.coroutine -def test_unpacking_sequence(hass, caplog): +async def test_unpacking_sequence(hass, caplog): """Test compile error logs error.""" caplog.set_level(logging.ERROR) source = """ @@ -204,7 +191,7 @@ hass.states.set('hello.ab_list', '{}'.format(ab_list)) """ hass.async_add_job(execute, hass, "test.py", source, {}) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert hass.states.is_state("hello.a", "1") assert hass.states.is_state("hello.b", "2") @@ -214,8 +201,7 @@ hass.states.set('hello.ab_list', '{}'.format(ab_list)) assert caplog.text == "" -@asyncio.coroutine -def test_execute_sorted(hass, caplog): +async def test_execute_sorted(hass, caplog): """Test sorted() function.""" caplog.set_level(logging.ERROR) source = """ @@ -226,7 +212,7 @@ hass.states.set('hello.b', a[1]) hass.states.set('hello.c', a[2]) """ hass.async_add_job(execute, hass, "test.py", source, {}) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert hass.states.is_state("hello.a", "1") assert hass.states.is_state("hello.b", "2") @@ -235,8 +221,7 @@ hass.states.set('hello.c', a[2]) assert caplog.text == "" -@asyncio.coroutine -def test_exposed_modules(hass, caplog): +async def test_exposed_modules(hass, caplog): """Test datetime and time modules exposed.""" caplog.set_level(logging.ERROR) source = """ @@ -248,7 +233,7 @@ hass.states.set('module.datetime', """ hass.async_add_job(execute, hass, "test.py", source, {}) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert hass.states.is_state("module.time", "1986") assert hass.states.is_state("module.time_strptime", "12:34") @@ -258,8 +243,7 @@ hass.states.set('module.datetime', assert caplog.text == "" -@asyncio.coroutine -def test_execute_functions(hass, caplog): +async def test_execute_functions(hass, caplog): """Test functions defined in script can call one another.""" caplog.set_level(logging.ERROR) source = """ @@ -273,7 +257,7 @@ def b(): b() """ hass.async_add_job(execute, hass, "test.py", source, {}) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert hass.states.is_state("hello.a", "one") assert hass.states.is_state("hello.b", "two") @@ -281,8 +265,7 @@ b() assert caplog.text == "" -@asyncio.coroutine -def test_reload(hass): +async def test_reload(hass): """Test we can re-discover scripts.""" scripts = [ "/some/config/dir/python_scripts/hello.py", @@ -291,7 +274,7 @@ def test_reload(hass): with patch( "homeassistant.components.python_script.os.path.isdir", return_value=True ), patch("homeassistant.components.python_script.glob.iglob", return_value=scripts): - res = yield from async_setup_component(hass, "python_script", {}) + res = await async_setup_component(hass, "python_script", {}) assert res assert hass.services.has_service("python_script", "hello") @@ -305,9 +288,7 @@ def test_reload(hass): with patch( "homeassistant.components.python_script.os.path.isdir", return_value=True ), patch("homeassistant.components.python_script.glob.iglob", return_value=scripts): - yield from hass.services.async_call( - "python_script", "reload", {}, blocking=True - ) + await hass.services.async_call("python_script", "reload", {}, blocking=True) assert not hass.services.has_service("python_script", "hello") assert hass.services.has_service("python_script", "hello2") @@ -410,8 +391,7 @@ async def test_service_descriptions(hass): ) -@asyncio.coroutine -def test_sleep_warns_one(hass, caplog): +async def test_sleep_warns_one(hass, caplog): """Test time.sleep warns once.""" caplog.set_level(logging.WARNING) source = """ @@ -421,6 +401,6 @@ time.sleep(5) with patch("homeassistant.components.python_script.time.sleep"): hass.async_add_job(execute, hass, "test.py", source, {}) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert caplog.text.count("time.sleep") == 1 From 0280862780100684d17ee86272ebb11a1d5925b8 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 31 Dec 2019 22:58:40 +0100 Subject: [PATCH 462/677] Migrate api tests from coroutine to async/await (#30338) --- tests/components/api/test_init.py | 157 +++++++++++++----------------- 1 file changed, 65 insertions(+), 92 deletions(-) diff --git a/tests/components/api/test_init.py b/tests/components/api/test_init.py index a6dbf3f1d96..28cad6dd04b 100644 --- a/tests/components/api/test_init.py +++ b/tests/components/api/test_init.py @@ -1,6 +1,5 @@ """The tests for the Home Assistant API component.""" # pylint: disable=protected-access -import asyncio import json from unittest.mock import patch @@ -23,27 +22,23 @@ def mock_api_client(hass, hass_client): return hass.loop.run_until_complete(hass_client()) -@asyncio.coroutine -def test_api_list_state_entities(hass, mock_api_client): +async def test_api_list_state_entities(hass, mock_api_client): """Test if the debug interface allows us to list state entities.""" hass.states.async_set("test.entity", "hello") - resp = yield from mock_api_client.get(const.URL_API_STATES) + resp = await mock_api_client.get(const.URL_API_STATES) assert resp.status == 200 - json = yield from resp.json() + json = await resp.json() remote_data = [ha.State.from_dict(item) for item in json] assert remote_data == hass.states.async_all() -@asyncio.coroutine -def test_api_get_state(hass, mock_api_client): +async def test_api_get_state(hass, mock_api_client): """Test if the debug interface allows us to get a state.""" hass.states.async_set("hello.world", "nice", {"attr": 1}) - resp = yield from mock_api_client.get( - const.URL_API_STATES_ENTITY.format("hello.world") - ) + resp = await mock_api_client.get(const.URL_API_STATES_ENTITY.format("hello.world")) assert resp.status == 200 - json = yield from resp.json() + json = await resp.json() data = ha.State.from_dict(json) @@ -54,21 +49,19 @@ def test_api_get_state(hass, mock_api_client): assert data.attributes == state.attributes -@asyncio.coroutine -def test_api_get_non_existing_state(hass, mock_api_client): +async def test_api_get_non_existing_state(hass, mock_api_client): """Test if the debug interface allows us to get a state.""" - resp = yield from mock_api_client.get( + resp = await mock_api_client.get( const.URL_API_STATES_ENTITY.format("does_not_exist") ) assert resp.status == 404 -@asyncio.coroutine -def test_api_state_change(hass, mock_api_client): +async def test_api_state_change(hass, mock_api_client): """Test if we can change the state of an entity that exists.""" hass.states.async_set("test.test", "not_to_be_set") - yield from mock_api_client.post( + await mock_api_client.post( const.URL_API_STATES_ENTITY.format("test.test"), json={"state": "debug_state_change2"}, ) @@ -77,12 +70,11 @@ def test_api_state_change(hass, mock_api_client): # pylint: disable=invalid-name -@asyncio.coroutine -def test_api_state_change_of_non_existing_entity(hass, mock_api_client): +async def test_api_state_change_of_non_existing_entity(hass, mock_api_client): """Test if changing a state of a non existing entity is possible.""" new_state = "debug_state_change" - resp = yield from mock_api_client.post( + resp = await mock_api_client.post( const.URL_API_STATES_ENTITY.format("test_entity.that_does_not_exist"), json={"state": new_state}, ) @@ -93,10 +85,9 @@ def test_api_state_change_of_non_existing_entity(hass, mock_api_client): # pylint: disable=invalid-name -@asyncio.coroutine -def test_api_state_change_with_bad_data(hass, mock_api_client): +async def test_api_state_change_with_bad_data(hass, mock_api_client): """Test if API sends appropriate error if we omit state.""" - resp = yield from mock_api_client.post( + resp = await mock_api_client.post( const.URL_API_STATES_ENTITY.format("test_entity.that_does_not_exist"), json={} ) @@ -104,17 +95,16 @@ def test_api_state_change_with_bad_data(hass, mock_api_client): # pylint: disable=invalid-name -@asyncio.coroutine -def test_api_state_change_to_zero_value(hass, mock_api_client): +async def test_api_state_change_to_zero_value(hass, mock_api_client): """Test if changing a state to a zero value is possible.""" - resp = yield from mock_api_client.post( + resp = await mock_api_client.post( const.URL_API_STATES_ENTITY.format("test_entity.with_zero_state"), json={"state": 0}, ) assert resp.status == 201 - resp = yield from mock_api_client.post( + resp = await mock_api_client.post( const.URL_API_STATES_ENTITY.format("test_entity.with_zero_state"), json={"state": 0.0}, ) @@ -123,8 +113,7 @@ def test_api_state_change_to_zero_value(hass, mock_api_client): # pylint: disable=invalid-name -@asyncio.coroutine -def test_api_state_change_push(hass, mock_api_client): +async def test_api_state_change_push(hass, mock_api_client): """Test if we can push a change the state of an entity.""" hass.states.async_set("test.test", "not_to_be_set") @@ -137,23 +126,22 @@ def test_api_state_change_push(hass, mock_api_client): hass.bus.async_listen(const.EVENT_STATE_CHANGED, event_listener) - yield from mock_api_client.post( + await mock_api_client.post( const.URL_API_STATES_ENTITY.format("test.test"), json={"state": "not_to_be_set"} ) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert len(events) == 0 - yield from mock_api_client.post( + await mock_api_client.post( const.URL_API_STATES_ENTITY.format("test.test"), json={"state": "not_to_be_set", "force_update": True}, ) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert len(events) == 1 # pylint: disable=invalid-name -@asyncio.coroutine -def test_api_fire_event_with_no_data(hass, mock_api_client): +async def test_api_fire_event_with_no_data(hass, mock_api_client): """Test if the API allows us to fire an event.""" test_value = [] @@ -164,17 +152,14 @@ def test_api_fire_event_with_no_data(hass, mock_api_client): hass.bus.async_listen_once("test.event_no_data", listener) - yield from mock_api_client.post( - const.URL_API_EVENTS_EVENT.format("test.event_no_data") - ) - yield from hass.async_block_till_done() + await mock_api_client.post(const.URL_API_EVENTS_EVENT.format("test.event_no_data")) + await hass.async_block_till_done() assert len(test_value) == 1 # pylint: disable=invalid-name -@asyncio.coroutine -def test_api_fire_event_with_data(hass, mock_api_client): +async def test_api_fire_event_with_data(hass, mock_api_client): """Test if the API allows us to fire an event.""" test_value = [] @@ -189,18 +174,17 @@ def test_api_fire_event_with_data(hass, mock_api_client): hass.bus.async_listen_once("test_event_with_data", listener) - yield from mock_api_client.post( + await mock_api_client.post( const.URL_API_EVENTS_EVENT.format("test_event_with_data"), json={"test": 1} ) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert len(test_value) == 1 # pylint: disable=invalid-name -@asyncio.coroutine -def test_api_fire_event_with_invalid_json(hass, mock_api_client): +async def test_api_fire_event_with_invalid_json(hass, mock_api_client): """Test if the API allows us to fire an event.""" test_value = [] @@ -211,33 +195,32 @@ def test_api_fire_event_with_invalid_json(hass, mock_api_client): hass.bus.async_listen_once("test_event_bad_data", listener) - resp = yield from mock_api_client.post( + resp = await mock_api_client.post( const.URL_API_EVENTS_EVENT.format("test_event_bad_data"), data=json.dumps("not an object"), ) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert resp.status == 400 assert len(test_value) == 0 # Try now with valid but unusable JSON - resp = yield from mock_api_client.post( + resp = await mock_api_client.post( const.URL_API_EVENTS_EVENT.format("test_event_bad_data"), data=json.dumps([1, 2, 3]), ) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert resp.status == 400 assert len(test_value) == 0 -@asyncio.coroutine -def test_api_get_config(hass, mock_api_client): +async def test_api_get_config(hass, mock_api_client): """Test the return of the configuration.""" - resp = yield from mock_api_client.get(const.URL_API_CONFIG) - result = yield from resp.json() + resp = await mock_api_client.get(const.URL_API_CONFIG) + result = await resp.json() if "components" in result: result["components"] = set(result["components"]) if "whitelist_external_dirs" in result: @@ -246,19 +229,17 @@ def test_api_get_config(hass, mock_api_client): assert hass.config.as_dict() == result -@asyncio.coroutine -def test_api_get_components(hass, mock_api_client): +async def test_api_get_components(hass, mock_api_client): """Test the return of the components.""" - resp = yield from mock_api_client.get(const.URL_API_COMPONENTS) - result = yield from resp.json() + resp = await mock_api_client.get(const.URL_API_COMPONENTS) + result = await resp.json() assert set(result) == hass.config.components -@asyncio.coroutine -def test_api_get_event_listeners(hass, mock_api_client): +async def test_api_get_event_listeners(hass, mock_api_client): """Test if we can get the list of events being listened for.""" - resp = yield from mock_api_client.get(const.URL_API_EVENTS) - data = yield from resp.json() + resp = await mock_api_client.get(const.URL_API_EVENTS) + data = await resp.json() local = hass.bus.async_listeners() @@ -268,11 +249,10 @@ def test_api_get_event_listeners(hass, mock_api_client): assert len(local) == 0 -@asyncio.coroutine -def test_api_get_services(hass, mock_api_client): +async def test_api_get_services(hass, mock_api_client): """Test if we can get a dict describing current services.""" - resp = yield from mock_api_client.get(const.URL_API_SERVICES) - data = yield from resp.json() + resp = await mock_api_client.get(const.URL_API_SERVICES) + data = await resp.json() local_services = hass.services.async_services() for serv_domain in data: @@ -281,8 +261,7 @@ def test_api_get_services(hass, mock_api_client): assert serv_domain["services"] == local -@asyncio.coroutine -def test_api_call_service_no_data(hass, mock_api_client): +async def test_api_call_service_no_data(hass, mock_api_client): """Test if the API allows us to call a service.""" test_value = [] @@ -293,15 +272,14 @@ def test_api_call_service_no_data(hass, mock_api_client): hass.services.async_register("test_domain", "test_service", listener) - yield from mock_api_client.post( + await mock_api_client.post( const.URL_API_SERVICES_SERVICE.format("test_domain", "test_service") ) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert len(test_value) == 1 -@asyncio.coroutine -def test_api_call_service_with_data(hass, mock_api_client): +async def test_api_call_service_with_data(hass, mock_api_client): """Test if the API allows us to call a service.""" test_value = [] @@ -316,88 +294,83 @@ def test_api_call_service_with_data(hass, mock_api_client): hass.services.async_register("test_domain", "test_service", listener) - yield from mock_api_client.post( + await mock_api_client.post( const.URL_API_SERVICES_SERVICE.format("test_domain", "test_service"), json={"test": 1}, ) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert len(test_value) == 1 -@asyncio.coroutine -def test_api_template(hass, mock_api_client): +async def test_api_template(hass, mock_api_client): """Test the template API.""" hass.states.async_set("sensor.temperature", 10) - resp = yield from mock_api_client.post( + resp = await mock_api_client.post( const.URL_API_TEMPLATE, json={"template": "{{ states.sensor.temperature.state }}"}, ) - body = yield from resp.text() + body = await resp.text() assert body == "10" -@asyncio.coroutine -def test_api_template_error(hass, mock_api_client): +async def test_api_template_error(hass, mock_api_client): """Test the template API.""" hass.states.async_set("sensor.temperature", 10) - resp = yield from mock_api_client.post( + resp = await mock_api_client.post( const.URL_API_TEMPLATE, json={"template": "{{ states.sensor.temperature.state"} ) assert resp.status == 400 -@asyncio.coroutine -def test_stream(hass, mock_api_client): +async def test_stream(hass, mock_api_client): """Test the stream.""" listen_count = _listen_count(hass) - resp = yield from mock_api_client.get(const.URL_API_STREAM) + resp = await mock_api_client.get(const.URL_API_STREAM) assert resp.status == 200 assert listen_count + 1 == _listen_count(hass) hass.bus.async_fire("test_event") - data = yield from _stream_next_event(resp.content) + data = await _stream_next_event(resp.content) assert data["event_type"] == "test_event" -@asyncio.coroutine -def test_stream_with_restricted(hass, mock_api_client): +async def test_stream_with_restricted(hass, mock_api_client): """Test the stream with restrictions.""" listen_count = _listen_count(hass) - resp = yield from mock_api_client.get( + resp = await mock_api_client.get( "{}?restrict=test_event1,test_event3".format(const.URL_API_STREAM) ) assert resp.status == 200 assert listen_count + 1 == _listen_count(hass) hass.bus.async_fire("test_event1") - data = yield from _stream_next_event(resp.content) + data = await _stream_next_event(resp.content) assert data["event_type"] == "test_event1" hass.bus.async_fire("test_event2") hass.bus.async_fire("test_event3") - data = yield from _stream_next_event(resp.content) + data = await _stream_next_event(resp.content) assert data["event_type"] == "test_event3" -@asyncio.coroutine -def _stream_next_event(stream): +async def _stream_next_event(stream): """Read the stream for next event while ignoring ping.""" while True: last_new_line = False data = b"" while True: - dat = yield from stream.read(1) + dat = await stream.read(1) if dat == b"\n" and last_new_line: break data += dat From 2ac5537495ccdf865b33946e050664d515e75fa2 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 31 Dec 2019 23:48:17 +0100 Subject: [PATCH 463/677] Migrate alexa tests from coroutine to async/await (#30332) --- .../components/alexa/test_flash_briefings.py | 22 +++--- tests/components/alexa/test_intent.py | 75 ++++++++----------- 2 files changed, 41 insertions(+), 56 deletions(-) diff --git a/tests/components/alexa/test_flash_briefings.py b/tests/components/alexa/test_flash_briefings.py index 7a20c05ed86..d3fe28d227d 100644 --- a/tests/components/alexa/test_flash_briefings.py +++ b/tests/components/alexa/test_flash_briefings.py @@ -1,6 +1,5 @@ """The tests for the Alexa component.""" # pylint: disable=protected-access -import asyncio import datetime import pytest @@ -67,21 +66,19 @@ def _flash_briefing_req(client, briefing_id): return client.get("/api/alexa/flash_briefings/{}".format(briefing_id)) -@asyncio.coroutine -def test_flash_briefing_invalid_id(alexa_client): +async def test_flash_briefing_invalid_id(alexa_client): """Test an invalid Flash Briefing ID.""" - req = yield from _flash_briefing_req(alexa_client, 10000) + req = await _flash_briefing_req(alexa_client, 10000) assert req.status == 404 - text = yield from req.text() + text = await req.text() assert text == "" -@asyncio.coroutine -def test_flash_briefing_date_from_str(alexa_client): +async def test_flash_briefing_date_from_str(alexa_client): """Test the response has a valid date parsed from string.""" - req = yield from _flash_briefing_req(alexa_client, "weather") + req = await _flash_briefing_req(alexa_client, "weather") assert req.status == 200 - data = yield from req.json() + data = await req.json() assert isinstance( datetime.datetime.strptime( data[0].get(const.ATTR_UPDATE_DATE), const.DATE_FORMAT @@ -90,8 +87,7 @@ def test_flash_briefing_date_from_str(alexa_client): ) -@asyncio.coroutine -def test_flash_briefing_valid(alexa_client): +async def test_flash_briefing_valid(alexa_client): """Test the response is valid.""" data = [ { @@ -104,9 +100,9 @@ def test_flash_briefing_valid(alexa_client): } ] - req = yield from _flash_briefing_req(alexa_client, "news_audio") + req = await _flash_briefing_req(alexa_client, "news_audio") assert req.status == 200 - json = yield from req.json() + json = await req.json() assert isinstance( datetime.datetime.strptime( json[0].get(const.ATTR_UPDATE_DATE), const.DATE_FORMAT diff --git a/tests/components/alexa/test_intent.py b/tests/components/alexa/test_intent.py index ac41e6d3b4d..962ba677403 100644 --- a/tests/components/alexa/test_intent.py +++ b/tests/components/alexa/test_intent.py @@ -1,6 +1,5 @@ """The tests for the Alexa component.""" # pylint: disable=protected-access -import asyncio import json import pytest @@ -115,8 +114,7 @@ def _intent_req(client, data=None): ) -@asyncio.coroutine -def test_intent_launch_request(alexa_client): +async def test_intent_launch_request(alexa_client): """Test the launch of a request.""" data = { "version": "1.0", @@ -133,15 +131,14 @@ def test_intent_launch_request(alexa_client): "timestamp": "2015-05-13T12:34:56Z", }, } - req = yield from _intent_req(alexa_client, data) + req = await _intent_req(alexa_client, data) assert req.status == 200 - data = yield from req.json() + data = await req.json() text = data.get("response", {}).get("outputSpeech", {}).get("text") assert text == "LaunchRequest has been received." -@asyncio.coroutine -def test_intent_launch_request_not_configured(alexa_client): +async def test_intent_launch_request_not_configured(alexa_client): """Test the launch of a request.""" data = { "version": "1.0", @@ -160,15 +157,14 @@ def test_intent_launch_request_not_configured(alexa_client): "timestamp": "2015-05-13T12:34:56Z", }, } - req = yield from _intent_req(alexa_client, data) + req = await _intent_req(alexa_client, data) assert req.status == 200 - data = yield from req.json() + data = await req.json() text = data.get("response", {}).get("outputSpeech", {}).get("text") assert text == "This intent is not yet configured within Home Assistant." -@asyncio.coroutine -def test_intent_request_with_slots(alexa_client): +async def test_intent_request_with_slots(alexa_client): """Test a request with slots.""" data = { "version": "1.0", @@ -195,15 +191,14 @@ def test_intent_request_with_slots(alexa_client): }, }, } - req = yield from _intent_req(alexa_client, data) + req = await _intent_req(alexa_client, data) assert req.status == 200 - data = yield from req.json() + data = await req.json() text = data.get("response", {}).get("outputSpeech", {}).get("text") assert text == "You told us your sign is virgo." -@asyncio.coroutine -def test_intent_request_with_slots_and_synonym_resolution(alexa_client): +async def test_intent_request_with_slots_and_synonym_resolution(alexa_client): """Test a request with slots and a name synonym.""" data = { "version": "1.0", @@ -249,15 +244,14 @@ def test_intent_request_with_slots_and_synonym_resolution(alexa_client): }, }, } - req = yield from _intent_req(alexa_client, data) + req = await _intent_req(alexa_client, data) assert req.status == 200 - data = yield from req.json() + data = await req.json() text = data.get("response", {}).get("outputSpeech", {}).get("text") assert text == "You told us your sign is Virgo." -@asyncio.coroutine -def test_intent_request_with_slots_and_multi_synonym_resolution(alexa_client): +async def test_intent_request_with_slots_and_multi_synonym_resolution(alexa_client): """Test a request with slots and multiple name synonyms.""" data = { "version": "1.0", @@ -303,15 +297,14 @@ def test_intent_request_with_slots_and_multi_synonym_resolution(alexa_client): }, }, } - req = yield from _intent_req(alexa_client, data) + req = await _intent_req(alexa_client, data) assert req.status == 200 - data = yield from req.json() + data = await req.json() text = data.get("response", {}).get("outputSpeech", {}).get("text") assert text == "You told us your sign is V zodiac." -@asyncio.coroutine -def test_intent_request_with_slots_but_no_value(alexa_client): +async def test_intent_request_with_slots_but_no_value(alexa_client): """Test a request with slots but no value.""" data = { "version": "1.0", @@ -338,15 +331,14 @@ def test_intent_request_with_slots_but_no_value(alexa_client): }, }, } - req = yield from _intent_req(alexa_client, data) + req = await _intent_req(alexa_client, data) assert req.status == 200 - data = yield from req.json() + data = await req.json() text = data.get("response", {}).get("outputSpeech", {}).get("text") assert text == "You told us your sign is ." -@asyncio.coroutine -def test_intent_request_without_slots(hass, alexa_client): +async def test_intent_request_without_slots(hass, alexa_client): """Test a request without slots.""" data = { "version": "1.0", @@ -370,9 +362,9 @@ def test_intent_request_without_slots(hass, alexa_client): "intent": {"name": "WhereAreWeIntent"}, }, } - req = yield from _intent_req(alexa_client, data) + req = await _intent_req(alexa_client, data) assert req.status == 200 - json = yield from req.json() + json = await req.json() text = json.get("response", {}).get("outputSpeech", {}).get("text") assert text == "Anne Therese is at unknown and Paulus is at unknown" @@ -380,15 +372,14 @@ def test_intent_request_without_slots(hass, alexa_client): hass.states.async_set("device_tracker.paulus", "home") hass.states.async_set("device_tracker.anne_therese", "home") - req = yield from _intent_req(alexa_client, data) + req = await _intent_req(alexa_client, data) assert req.status == 200 - json = yield from req.json() + json = await req.json() text = json.get("response", {}).get("outputSpeech", {}).get("text") assert text == "You are both home, you silly" -@asyncio.coroutine -def test_intent_request_calling_service(alexa_client): +async def test_intent_request_calling_service(alexa_client): """Test a request for calling a service.""" data = { "version": "1.0", @@ -410,7 +401,7 @@ def test_intent_request_calling_service(alexa_client): }, } call_count = len(calls) - req = yield from _intent_req(alexa_client, data) + req = await _intent_req(alexa_client, data) assert req.status == 200 assert call_count + 1 == len(calls) call = calls[-1] @@ -419,15 +410,14 @@ def test_intent_request_calling_service(alexa_client): assert call.data.get("entity_id") == ["switch.test"] assert call.data.get("hello") == "virgo" - data = yield from req.json() + data = await req.json() assert data["response"]["card"]["title"] == "Card title for virgo" assert data["response"]["card"]["content"] == "Card content: virgo" assert data["response"]["outputSpeech"]["type"] == "PlainText" assert data["response"]["outputSpeech"]["text"] == "Service called for virgo" -@asyncio.coroutine -def test_intent_session_ended_request(alexa_client): +async def test_intent_session_ended_request(alexa_client): """Test the request for ending the session.""" data = { "version": "1.0", @@ -452,14 +442,13 @@ def test_intent_session_ended_request(alexa_client): }, } - req = yield from _intent_req(alexa_client, data) + req = await _intent_req(alexa_client, data) assert req.status == 200 - text = yield from req.text() + text = await req.text() assert text == "" -@asyncio.coroutine -def test_intent_from_built_in_intent_library(alexa_client): +async def test_intent_from_built_in_intent_library(alexa_client): """Test intents from the Built-in Intent Library.""" data = { "request": { @@ -490,8 +479,8 @@ def test_intent_from_built_in_intent_library(alexa_client): "application": {"applicationId": APPLICATION_ID}, }, } - req = yield from _intent_req(alexa_client, data) + req = await _intent_req(alexa_client, data) assert req.status == 200 - data = yield from req.json() + data = await req.json() text = data.get("response", {}).get("outputSpeech", {}).get("text") assert text == "Playing the shins." From 194cb8dbf58c4a5e97fd62d50a5416ccb4281ea7 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 31 Dec 2019 23:49:00 +0100 Subject: [PATCH 464/677] Migrate xiaomi_miio tests from coroutine to async/await (#30329) --- tests/components/xiaomi_miio/test_vacuum.py | 52 ++++++++++----------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/tests/components/xiaomi_miio/test_vacuum.py b/tests/components/xiaomi_miio/test_vacuum.py index 6b2eb87f153..7ebc59a964a 100644 --- a/tests/components/xiaomi_miio/test_vacuum.py +++ b/tests/components/xiaomi_miio/test_vacuum.py @@ -1,5 +1,4 @@ """The tests for the Xiaomi vacuum platform.""" -import asyncio from datetime import time, timedelta from unittest import mock @@ -149,11 +148,10 @@ def mirobo_errors_fixture(): yield mock_vacuum -@asyncio.coroutine -def test_xiaomi_exceptions(hass, caplog, mock_mirobo_errors): +async def test_xiaomi_exceptions(hass, caplog, mock_mirobo_errors): """Test vacuum supported features.""" entity_name = "test_vacuum_cleaner_error" - yield from async_setup_component( + await async_setup_component( hass, DOMAIN, { @@ -165,7 +163,7 @@ def test_xiaomi_exceptions(hass, caplog, mock_mirobo_errors): } }, ) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert "Initializing with host 127.0.0.1 (token 12345...)" in caplog.text assert mock_mirobo_errors.status.call_count == 1 @@ -173,13 +171,12 @@ def test_xiaomi_exceptions(hass, caplog, mock_mirobo_errors): assert "Got OSError while fetching the state" in caplog.text -@asyncio.coroutine -def test_xiaomi_vacuum_services(hass, caplog, mock_mirobo_is_got_error): +async def test_xiaomi_vacuum_services(hass, caplog, mock_mirobo_is_got_error): """Test vacuum supported features.""" entity_name = "test_vacuum_cleaner_1" entity_id = "{}.{}".format(DOMAIN, entity_name) - yield from async_setup_component( + await async_setup_component( hass, DOMAIN, { @@ -191,7 +188,7 @@ def test_xiaomi_vacuum_services(hass, caplog, mock_mirobo_is_got_error): } }, ) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert "Initializing with host 127.0.0.1 (token 12345...)" in caplog.text @@ -223,7 +220,7 @@ def test_xiaomi_vacuum_services(hass, caplog, mock_mirobo_is_got_error): assert state.attributes.get(ATTR_CLEANING_TOTAL_TIME) == 695 # Call services - yield from hass.services.async_call( + await hass.services.async_call( DOMAIN, SERVICE_START, {"entity_id": entity_id}, blocking=True ) mock_mirobo_is_got_error.assert_has_calls( @@ -232,28 +229,28 @@ def test_xiaomi_vacuum_services(hass, caplog, mock_mirobo_is_got_error): mock_mirobo_is_got_error.assert_has_calls(STATUS_CALLS, any_order=True) mock_mirobo_is_got_error.reset_mock() - yield from hass.services.async_call( + await hass.services.async_call( DOMAIN, SERVICE_STOP, {"entity_id": entity_id}, blocking=True ) mock_mirobo_is_got_error.assert_has_calls([mock.call.stop()], any_order=True) mock_mirobo_is_got_error.assert_has_calls(STATUS_CALLS, any_order=True) mock_mirobo_is_got_error.reset_mock() - yield from hass.services.async_call( + await hass.services.async_call( DOMAIN, SERVICE_RETURN_TO_BASE, {"entity_id": entity_id}, blocking=True ) mock_mirobo_is_got_error.assert_has_calls([mock.call.home()], any_order=True) mock_mirobo_is_got_error.assert_has_calls(STATUS_CALLS, any_order=True) mock_mirobo_is_got_error.reset_mock() - yield from hass.services.async_call( + await hass.services.async_call( DOMAIN, SERVICE_LOCATE, {"entity_id": entity_id}, blocking=True ) mock_mirobo_is_got_error.assert_has_calls([mock.call.find()], any_order=True) mock_mirobo_is_got_error.assert_has_calls(STATUS_CALLS, any_order=True) mock_mirobo_is_got_error.reset_mock() - yield from hass.services.async_call( + await hass.services.async_call( DOMAIN, SERVICE_CLEAN_SPOT, {"entity_id": entity_id}, blocking=True ) mock_mirobo_is_got_error.assert_has_calls([mock.call.spot()], any_order=True) @@ -261,7 +258,7 @@ def test_xiaomi_vacuum_services(hass, caplog, mock_mirobo_is_got_error): mock_mirobo_is_got_error.reset_mock() # Set speed service: - yield from hass.services.async_call( + await hass.services.async_call( DOMAIN, SERVICE_SET_FAN_SPEED, {"entity_id": entity_id, "fan_speed": 60}, @@ -273,7 +270,7 @@ def test_xiaomi_vacuum_services(hass, caplog, mock_mirobo_is_got_error): mock_mirobo_is_got_error.assert_has_calls(STATUS_CALLS, any_order=True) mock_mirobo_is_got_error.reset_mock() - yield from hass.services.async_call( + await hass.services.async_call( DOMAIN, SERVICE_SET_FAN_SPEED, {"entity_id": entity_id, "fan_speed": "turbo"}, @@ -286,7 +283,7 @@ def test_xiaomi_vacuum_services(hass, caplog, mock_mirobo_is_got_error): mock_mirobo_is_got_error.reset_mock() assert "ERROR" not in caplog.text - yield from hass.services.async_call( + await hass.services.async_call( DOMAIN, SERVICE_SET_FAN_SPEED, {"entity_id": entity_id, "fan_speed": "invent"}, @@ -294,7 +291,7 @@ def test_xiaomi_vacuum_services(hass, caplog, mock_mirobo_is_got_error): ) assert "ERROR" in caplog.text - yield from hass.services.async_call( + await hass.services.async_call( DOMAIN, SERVICE_SEND_COMMAND, {"entity_id": entity_id, "command": "raw"}, @@ -306,7 +303,7 @@ def test_xiaomi_vacuum_services(hass, caplog, mock_mirobo_is_got_error): mock_mirobo_is_got_error.assert_has_calls(STATUS_CALLS, any_order=True) mock_mirobo_is_got_error.reset_mock() - yield from hass.services.async_call( + await hass.services.async_call( DOMAIN, SERVICE_SEND_COMMAND, {"entity_id": entity_id, "command": "raw", "params": {"k1": 2}}, @@ -319,13 +316,12 @@ def test_xiaomi_vacuum_services(hass, caplog, mock_mirobo_is_got_error): mock_mirobo_is_got_error.reset_mock() -@asyncio.coroutine -def test_xiaomi_specific_services(hass, caplog, mock_mirobo_is_on): +async def test_xiaomi_specific_services(hass, caplog, mock_mirobo_is_on): """Test vacuum supported features.""" entity_name = "test_vacuum_cleaner_2" entity_id = "{}.{}".format(DOMAIN, entity_name) - yield from async_setup_component( + await async_setup_component( hass, DOMAIN, { @@ -337,7 +333,7 @@ def test_xiaomi_specific_services(hass, caplog, mock_mirobo_is_on): } }, ) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert "Initializing with host 192.168.1.100 (token 12345" in caplog.text @@ -366,7 +362,7 @@ def test_xiaomi_specific_services(hass, caplog, mock_mirobo_is_on): assert state.attributes.get(ATTR_CLEANING_TOTAL_TIME) == 675 # Xiaomi vacuum specific services: - yield from hass.services.async_call( + await hass.services.async_call( XIAOMI_DOMAIN, SERVICE_START_REMOTE_CONTROL, {ATTR_ENTITY_ID: entity_id}, @@ -378,7 +374,7 @@ def test_xiaomi_specific_services(hass, caplog, mock_mirobo_is_on): mock_mirobo_is_on.reset_mock() control = {"duration": 1000, "rotation": -40, "velocity": -0.1} - yield from hass.services.async_call( + await hass.services.async_call( XIAOMI_DOMAIN, SERVICE_MOVE_REMOTE_CONTROL, control, blocking=True ) mock_mirobo_is_on.manual_control.assert_has_calls( @@ -387,7 +383,7 @@ def test_xiaomi_specific_services(hass, caplog, mock_mirobo_is_on): mock_mirobo_is_on.assert_has_calls(STATUS_CALLS, any_order=True) mock_mirobo_is_on.reset_mock() - yield from hass.services.async_call( + await hass.services.async_call( XIAOMI_DOMAIN, SERVICE_STOP_REMOTE_CONTROL, {}, blocking=True ) mock_mirobo_is_on.assert_has_calls([mock.call.manual_stop()], any_order=True) @@ -395,7 +391,7 @@ def test_xiaomi_specific_services(hass, caplog, mock_mirobo_is_on): mock_mirobo_is_on.reset_mock() control_once = {"duration": 2000, "rotation": 120, "velocity": 0.1} - yield from hass.services.async_call( + await hass.services.async_call( XIAOMI_DOMAIN, SERVICE_MOVE_REMOTE_CONTROL_STEP, control_once, blocking=True ) mock_mirobo_is_on.manual_control_once.assert_has_calls( @@ -405,7 +401,7 @@ def test_xiaomi_specific_services(hass, caplog, mock_mirobo_is_on): mock_mirobo_is_on.reset_mock() control = {"zone": [[123, 123, 123, 123]], "repeats": 2} - yield from hass.services.async_call( + await hass.services.async_call( XIAOMI_DOMAIN, SERVICE_CLEAN_ZONE, control, blocking=True ) mock_mirobo_is_on.zoned_clean.assert_has_calls( From af153521dcff04138f8e79823c5ed686b76ca4a6 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 31 Dec 2019 23:50:07 +0100 Subject: [PATCH 465/677] Migrate emulated_hue tests from coroutine to async/await (#30331) --- tests/components/emulated_hue/test_hue_api.py | 158 ++++++++---------- 1 file changed, 67 insertions(+), 91 deletions(-) diff --git a/tests/components/emulated_hue/test_hue_api.py b/tests/components/emulated_hue/test_hue_api.py index 32543602f89..6c1a17c0538 100644 --- a/tests/components/emulated_hue/test_hue_api.py +++ b/tests/components/emulated_hue/test_hue_api.py @@ -1,5 +1,4 @@ """The tests for the emulated Hue component.""" -import asyncio from datetime import timedelta from ipaddress import ip_address import json @@ -194,15 +193,14 @@ def hue_client(loop, hass_hue, aiohttp_client): return loop.run_until_complete(aiohttp_client(web_app)) -@asyncio.coroutine -def test_discover_lights(hue_client): +async def test_discover_lights(hue_client): """Test the discovery of lights.""" - result = yield from hue_client.get("/api/username/lights") + result = await hue_client.get("/api/username/lights") assert result.status == 200 assert "application/json" in result.headers["content-type"] - result_json = yield from result.json() + result_json = await result.json() devices = set(val["uniqueid"] for val in result_json.values()) @@ -223,10 +221,9 @@ def test_discover_lights(hue_client): assert "00:57:77:a1:6a:8e:ef:b3-6c" not in devices # climate.ecobee -@asyncio.coroutine -def test_light_without_brightness_supported(hass_hue, hue_client): +async def test_light_without_brightness_supported(hass_hue, hue_client): """Test that light without brightness is supported.""" - light_without_brightness_json = yield from perform_get_light_state( + light_without_brightness_json = await perform_get_light_state( hue_client, "light.no_brightness", 200 ) @@ -234,7 +231,6 @@ def test_light_without_brightness_supported(hass_hue, hue_client): assert light_without_brightness_json["type"] == "On/off light" -@asyncio.coroutine @pytest.mark.parametrize( "state,is_reachable", [ @@ -243,26 +239,25 @@ def test_light_without_brightness_supported(hass_hue, hue_client): (const.STATE_UNKNOWN, True), ], ) -def test_reachable_for_state(hass_hue, hue_client, state, is_reachable): +async def test_reachable_for_state(hass_hue, hue_client, state, is_reachable): """Test that an entity is reported as unreachable if in unavailable state.""" entity_id = "light.ceiling_lights" hass_hue.states.async_set(entity_id, state) - state_json = yield from perform_get_light_state(hue_client, entity_id, 200) + state_json = await perform_get_light_state(hue_client, entity_id, 200) assert state_json["state"]["reachable"] == is_reachable, state_json -@asyncio.coroutine -def test_discover_full_state(hue_client): +async def test_discover_full_state(hue_client): """Test the discovery of full state.""" - result = yield from hue_client.get("/api/" + HUE_API_USERNAME) + result = await hue_client.get("/api/" + HUE_API_USERNAME) assert result.status == 200 assert "application/json" in result.headers["content-type"] - result_json = yield from result.json() + result_json = await result.json() # Make sure array has correct content assert "lights" in result_json @@ -297,11 +292,10 @@ def test_discover_full_state(hue_client): assert "127.0.0.1:8300" in config_json["ipaddress"] -@asyncio.coroutine -def test_get_light_state(hass_hue, hue_client): +async def test_get_light_state(hass_hue, hue_client): """Test the getting of light state.""" # Turn office light on and set to 127 brightness, and set light color - yield from hass_hue.services.async_call( + await hass_hue.services.async_call( light.DOMAIN, const.SERVICE_TURN_ON, { @@ -312,9 +306,7 @@ def test_get_light_state(hass_hue, hue_client): blocking=True, ) - office_json = yield from perform_get_light_state( - hue_client, "light.ceiling_lights", 200 - ) + office_json = await perform_get_light_state(hue_client, "light.ceiling_lights", 200) assert office_json["state"][HUE_API_STATE_ON] is True assert office_json["state"][HUE_API_STATE_BRI] == 127 @@ -322,27 +314,25 @@ def test_get_light_state(hass_hue, hue_client): assert office_json["state"][HUE_API_STATE_SAT] == 217 # Check all lights view - result = yield from hue_client.get("/api/username/lights") + result = await hue_client.get("/api/username/lights") assert result.status == 200 assert "application/json" in result.headers["content-type"] - result_json = yield from result.json() + result_json = await result.json() assert "light.ceiling_lights" in result_json assert result_json["light.ceiling_lights"]["state"][HUE_API_STATE_BRI] == 127 # Turn office light off - yield from hass_hue.services.async_call( + await hass_hue.services.async_call( light.DOMAIN, const.SERVICE_TURN_OFF, {const.ATTR_ENTITY_ID: "light.ceiling_lights"}, blocking=True, ) - office_json = yield from perform_get_light_state( - hue_client, "light.ceiling_lights", 200 - ) + office_json = await perform_get_light_state(hue_client, "light.ceiling_lights", 200) assert office_json["state"][HUE_API_STATE_ON] is False # Removed assert HUE_API_STATE_BRI == 0 as Hue API states bri must be 1..254 @@ -350,19 +340,18 @@ def test_get_light_state(hass_hue, hue_client): assert office_json["state"][HUE_API_STATE_SAT] == 0 # Make sure bedroom light isn't accessible - yield from perform_get_light_state(hue_client, "light.bed_light", 401) + await perform_get_light_state(hue_client, "light.bed_light", 401) # Make sure kitchen light isn't accessible - yield from perform_get_light_state(hue_client, "light.kitchen_lights", 401) + await perform_get_light_state(hue_client, "light.kitchen_lights", 401) -@asyncio.coroutine -def test_put_light_state(hass_hue, hue_client): +async def test_put_light_state(hass_hue, hue_client): """Test the setting of light states.""" - yield from perform_put_test_on_ceiling_lights(hass_hue, hue_client) + await perform_put_test_on_ceiling_lights(hass_hue, hue_client) # Turn the bedroom light on first - yield from hass_hue.services.async_call( + await hass_hue.services.async_call( light.DOMAIN, const.SERVICE_TURN_ON, {const.ATTR_ENTITY_ID: "light.ceiling_lights", light.ATTR_BRIGHTNESS: 153}, @@ -374,7 +363,7 @@ def test_put_light_state(hass_hue, hue_client): assert ceiling_lights.attributes[light.ATTR_BRIGHTNESS] == 153 # update light state through api - yield from perform_put_light_state( + await perform_put_light_state( hass_hue, hue_client, "light.ceiling_lights", @@ -385,7 +374,7 @@ def test_put_light_state(hass_hue, hue_client): ) # go through api to get the state back - ceiling_json = yield from perform_get_light_state( + ceiling_json = await perform_get_light_state( hue_client, "light.ceiling_lights", 200 ) assert ceiling_json["state"][HUE_API_STATE_BRI] == 123 @@ -393,11 +382,11 @@ def test_put_light_state(hass_hue, hue_client): assert ceiling_json["state"][HUE_API_STATE_SAT] == 127 # Go through the API to turn it off - ceiling_result = yield from perform_put_light_state( + ceiling_result = await perform_put_light_state( hass_hue, hue_client, "light.ceiling_lights", False ) - ceiling_result_json = yield from ceiling_result.json() + ceiling_result_json = await ceiling_result.json() assert ceiling_result.status == 200 assert "application/json" in ceiling_result.headers["content-type"] @@ -407,7 +396,7 @@ def test_put_light_state(hass_hue, hue_client): # Check to make sure the state changed ceiling_lights = hass_hue.states.get("light.ceiling_lights") assert ceiling_lights.state == STATE_OFF - ceiling_json = yield from perform_get_light_state( + ceiling_json = await perform_get_light_state( hue_client, "light.ceiling_lights", 200 ) # Removed assert HUE_API_STATE_BRI == 0 as Hue API states bri must be 1..254 @@ -415,23 +404,22 @@ def test_put_light_state(hass_hue, hue_client): assert ceiling_json["state"][HUE_API_STATE_SAT] == 0 # Make sure we can't change the bedroom light state - bedroom_result = yield from perform_put_light_state( + bedroom_result = await perform_put_light_state( hass_hue, hue_client, "light.bed_light", True ) assert bedroom_result.status == 401 # Make sure we can't change the kitchen light state - kitchen_result = yield from perform_put_light_state( + kitchen_result = await perform_put_light_state( hass_hue, hue_client, "light.kitchen_light", True ) assert kitchen_result.status == 404 -@asyncio.coroutine -def test_put_light_state_script(hass_hue, hue_client): +async def test_put_light_state_script(hass_hue, hue_client): """Test the setting of script variables.""" # Turn the kitchen light off first - yield from hass_hue.services.async_call( + await hass_hue.services.async_call( light.DOMAIN, const.SERVICE_TURN_OFF, {const.ATTR_ENTITY_ID: "light.kitchen_lights"}, @@ -442,11 +430,11 @@ def test_put_light_state_script(hass_hue, hue_client): level = 23 brightness = round(level * 255 / 100) - script_result = yield from perform_put_light_state( + script_result = await perform_put_light_state( hass_hue, hue_client, "script.set_kitchen_light", True, brightness ) - script_result_json = yield from script_result.json() + script_result_json = await script_result.json() assert script_result.status == 200 assert len(script_result_json) == 2 @@ -456,17 +444,16 @@ def test_put_light_state_script(hass_hue, hue_client): assert kitchen_light.attributes[light.ATTR_BRIGHTNESS] == level -@asyncio.coroutine -def test_put_light_state_climate_set_temperature(hass_hue, hue_client): +async def test_put_light_state_climate_set_temperature(hass_hue, hue_client): """Test setting climate temperature.""" brightness = 19 temperature = round(brightness / 255 * 100) - hvac_result = yield from perform_put_light_state( + hvac_result = await perform_put_light_state( hass_hue, hue_client, "climate.hvac", True, brightness ) - hvac_result_json = yield from hvac_result.json() + hvac_result_json = await hvac_result.json() assert hvac_result.status == 200 assert len(hvac_result_json) == 2 @@ -476,17 +463,16 @@ def test_put_light_state_climate_set_temperature(hass_hue, hue_client): assert hvac.attributes[climate.ATTR_TEMPERATURE] == temperature # Make sure we can't change the ecobee temperature since it's not exposed - ecobee_result = yield from perform_put_light_state( + ecobee_result = await perform_put_light_state( hass_hue, hue_client, "climate.ecobee", True ) assert ecobee_result.status == 401 -@asyncio.coroutine -def test_put_light_state_media_player(hass_hue, hue_client): +async def test_put_light_state_media_player(hass_hue, hue_client): """Test turning on media player and setting volume.""" # Turn the music player off first - yield from hass_hue.services.async_call( + await hass_hue.services.async_call( media_player.DOMAIN, const.SERVICE_TURN_OFF, {const.ATTR_ENTITY_ID: "media_player.walkman"}, @@ -497,11 +483,11 @@ def test_put_light_state_media_player(hass_hue, hue_client): level = 0.25 brightness = round(level * 255) - mp_result = yield from perform_put_light_state( + mp_result = await perform_put_light_state( hass_hue, hue_client, "media_player.walkman", True, brightness ) - mp_result_json = yield from mp_result.json() + mp_result_json = await mp_result.json() assert mp_result.status == 200 assert len(mp_result_json) == 2 @@ -610,11 +596,10 @@ async def test_set_position_cover(hass_hue, hue_client): assert cover_test_2.attributes.get("current_position") == level -@asyncio.coroutine -def test_put_light_state_fan(hass_hue, hue_client): +async def test_put_light_state_fan(hass_hue, hue_client): """Test turning on fan and setting speed.""" # Turn the fan off first - yield from hass_hue.services.async_call( + await hass_hue.services.async_call( fan.DOMAIN, const.SERVICE_TURN_OFF, {const.ATTR_ENTITY_ID: "fan.living_room_fan"}, @@ -625,11 +610,11 @@ def test_put_light_state_fan(hass_hue, hue_client): level = 43 brightness = round(level * 255 / 100) - fan_result = yield from perform_put_light_state( + fan_result = await perform_put_light_state( hass_hue, hue_client, "fan.living_room_fan", True, brightness ) - fan_result_json = yield from fan_result.json() + fan_result_json = await fan_result.json() assert fan_result.status == 200 assert len(fan_result_json) == 2 @@ -640,17 +625,16 @@ def test_put_light_state_fan(hass_hue, hue_client): # pylint: disable=invalid-name -@asyncio.coroutine -def test_put_with_form_urlencoded_content_type(hass_hue, hue_client): +async def test_put_with_form_urlencoded_content_type(hass_hue, hue_client): """Test the form with urlencoded content.""" # Needed for Alexa - yield from perform_put_test_on_ceiling_lights( + await perform_put_test_on_ceiling_lights( hass_hue, hue_client, "application/x-www-form-urlencoded" ) # Make sure we fail gracefully when we can't parse the data data = {"key1": "value1", "key2": "value2"} - result = yield from hue_client.put( + result = await hue_client.put( "/api/username/lights/light.ceiling_lights/state", headers={"content-type": "application/x-www-form-urlencoded"}, data=data, @@ -659,41 +643,36 @@ def test_put_with_form_urlencoded_content_type(hass_hue, hue_client): assert result.status == 400 -@asyncio.coroutine -def test_entity_not_found(hue_client): +async def test_entity_not_found(hue_client): """Test for entity which are not found.""" - result = yield from hue_client.get("/api/username/lights/not.existant_entity") + result = await hue_client.get("/api/username/lights/not.existant_entity") assert result.status == 404 - result = yield from hue_client.put("/api/username/lights/not.existant_entity/state") + result = await hue_client.put("/api/username/lights/not.existant_entity/state") assert result.status == 404 -@asyncio.coroutine -def test_allowed_methods(hue_client): +async def test_allowed_methods(hue_client): """Test the allowed methods.""" - result = yield from hue_client.get( - "/api/username/lights/light.ceiling_lights/state" - ) + result = await hue_client.get("/api/username/lights/light.ceiling_lights/state") assert result.status == 405 - result = yield from hue_client.put("/api/username/lights/light.ceiling_lights") + result = await hue_client.put("/api/username/lights/light.ceiling_lights") assert result.status == 405 - result = yield from hue_client.put("/api/username/lights") + result = await hue_client.put("/api/username/lights") assert result.status == 405 -@asyncio.coroutine -def test_proper_put_state_request(hue_client): +async def test_proper_put_state_request(hue_client): """Test the request to set the state.""" # Test proper on value parsing - result = yield from hue_client.put( + result = await hue_client.put( "/api/username/lights/{}/state".format("light.ceiling_lights"), data=json.dumps({HUE_API_STATE_ON: 1234}), ) @@ -701,7 +680,7 @@ def test_proper_put_state_request(hue_client): assert result.status == 400 # Test proper brightness value parsing - result = yield from hue_client.put( + result = await hue_client.put( "/api/username/lights/{}/state".format("light.ceiling_lights"), data=json.dumps({HUE_API_STATE_ON: True, HUE_API_STATE_BRI: "Hello world!"}), ) @@ -709,15 +688,14 @@ def test_proper_put_state_request(hue_client): assert result.status == 400 -@asyncio.coroutine -def test_get_empty_groups_state(hue_client): +async def test_get_empty_groups_state(hue_client): """Test the request to get groups endpoint.""" # Test proper on value parsing - result = yield from hue_client.get("/api/username/groups") + result = await hue_client.get("/api/username/groups") assert result.status == 200 - result_json = yield from result.json() + result_json = await result.json() assert result_json == {} @@ -756,23 +734,21 @@ async def perform_put_test_on_ceiling_lights( assert ceiling_lights.attributes[light.ATTR_BRIGHTNESS] == 56 -@asyncio.coroutine -def perform_get_light_state(client, entity_id, expected_status): +async def perform_get_light_state(client, entity_id, expected_status): """Test the getting of a light state.""" - result = yield from client.get("/api/username/lights/{}".format(entity_id)) + result = await client.get("/api/username/lights/{}".format(entity_id)) assert result.status == expected_status if expected_status == 200: assert "application/json" in result.headers["content-type"] - return (yield from result.json()) + return await result.json() return None -@asyncio.coroutine -def perform_put_light_state( +async def perform_put_light_state( hass_hue, client, entity_id, @@ -794,14 +770,14 @@ def perform_put_light_state( if saturation is not None: data[HUE_API_STATE_SAT] = saturation - result = yield from client.put( + result = await client.put( "/api/username/lights/{}/state".format(entity_id), headers=req_headers, data=json.dumps(data).encode(), ) # Wait until state change is complete before continuing - yield from hass_hue.async_block_till_done() + await hass_hue.async_block_till_done() return result From fc23b4f83f977f798a991662dee85f04d027d6f5 Mon Sep 17 00:00:00 2001 From: Josh Bendavid Date: Tue, 31 Dec 2019 18:26:35 -0500 Subject: [PATCH 466/677] Migrate webostv to new library and make integration async with callback state updates (#29296) * migrate webostv to new aiopylgtv version of the library and add support for generic commands, input/button commands, and callback state updates * update requirements * cleanup and bump aiopylgtv version * update webostv unit tests * make webostv unit tests work with python 3.7 * cleanup for code checks * cleanup and code review * make all client request functions coroutines * make host required for webostv configuration * remove generic command and button functionality plus related cleanup * fix previous track function * update unit tests * fix imports for unit tests * update unit test * further unit test updates * remove unnecessary setup call in unit tests * restore previous behaviour with client key config file in hass configuration directory --- homeassistant/components/webostv/__init__.py | 151 +++++++ .../components/webostv/manifest.json | 3 +- .../components/webostv/media_player.py | 380 ++++++++---------- homeassistant/components/webostv/notify.py | 63 ++- requirements_all.txt | 9 +- requirements_test_all.txt | 9 +- tests/components/webostv/test_media_player.py | 105 +++-- 7 files changed, 420 insertions(+), 300 deletions(-) diff --git a/homeassistant/components/webostv/__init__.py b/homeassistant/components/webostv/__init__.py index f0b3c2c5f7e..b34dba3ad94 100644 --- a/homeassistant/components/webostv/__init__.py +++ b/homeassistant/components/webostv/__init__.py @@ -1 +1,152 @@ """Support for WebOS TV.""" +import asyncio +import logging + +from aiopylgtv import PyLGTVCmdException, PyLGTVPairException, WebOsClient +import voluptuous as vol +from websockets.exceptions import ConnectionClosed + +from homeassistant.const import ( + CONF_CUSTOMIZE, + CONF_HOST, + CONF_ICON, + CONF_NAME, + EVENT_HOMEASSISTANT_STOP, +) +import homeassistant.helpers.config_validation as cv + +DOMAIN = "webostv" + +CONF_SOURCES = "sources" +CONF_ON_ACTION = "turn_on_action" +CONF_STANDBY_CONNECTION = "standby_connection" +DEFAULT_NAME = "LG webOS Smart TV" +WEBOSTV_CONFIG_FILE = "webostv.conf" + +CUSTOMIZE_SCHEMA = vol.Schema( + {vol.Optional(CONF_SOURCES, default=[]): vol.All(cv.ensure_list, [cv.string])} +) + +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.All( + cv.ensure_list, + [ + vol.Schema( + { + vol.Optional(CONF_CUSTOMIZE, default={}): CUSTOMIZE_SCHEMA, + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_ON_ACTION): cv.SCRIPT_SCHEMA, + vol.Optional( + CONF_STANDBY_CONNECTION, default=False + ): cv.boolean, + vol.Optional(CONF_ICON): cv.string, + } + ) + ], + ) + }, + extra=vol.ALLOW_EXTRA, +) + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup(hass, config): + """Set up the LG WebOS TV platform.""" + hass.data[DOMAIN] = {} + + tasks = [async_setup_tv(hass, config, conf) for conf in config[DOMAIN]] + if tasks: + await asyncio.gather(*tasks) + + return True + + +async def async_setup_tv(hass, config, conf): + """Set up a LG WebOS TV based on host parameter.""" + + host = conf[CONF_HOST] + config_file = hass.config.path(WEBOSTV_CONFIG_FILE) + standby_connection = conf[CONF_STANDBY_CONNECTION] + + client = WebOsClient(host, config_file, standby_connection=standby_connection) + hass.data[DOMAIN][host] = {"client": client} + + if client.is_registered(): + await async_setup_tv_finalize(hass, config, conf, client) + else: + _LOGGER.warning("LG webOS TV %s needs to be paired", host) + await async_request_configuration(hass, config, conf, client) + + +async def async_connect(client): + """Attempt a connection, but fail gracefully if tv is off for example.""" + try: + await client.connect() + except ( + OSError, + ConnectionClosed, + ConnectionRefusedError, + asyncio.TimeoutError, + asyncio.CancelledError, + PyLGTVPairException, + PyLGTVCmdException, + ): + pass + + +async def async_setup_tv_finalize(hass, config, conf, client): + """Make initial connection attempt and call platform setup.""" + + async def async_on_stop(event): + """Unregister callbacks and disconnect.""" + client.clear_state_update_callbacks() + await client.disconnect() + + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_on_stop) + + await async_connect(client) + hass.async_create_task( + hass.helpers.discovery.async_load_platform("media_player", DOMAIN, conf, config) + ) + hass.async_create_task( + hass.helpers.discovery.async_load_platform("notify", DOMAIN, conf, config) + ) + + +async def async_request_configuration(hass, config, conf, client): + """Request configuration steps from the user.""" + host = conf.get(CONF_HOST) + name = conf.get(CONF_NAME) + configurator = hass.components.configurator + + async def lgtv_configuration_callback(data): + """Handle actions when configuration callback is called.""" + try: + await client.connect() + except PyLGTVPairException: + _LOGGER.warning("Connected to LG webOS TV %s but not paired", host) + return + except ( + OSError, + ConnectionClosed, + ConnectionRefusedError, + asyncio.TimeoutError, + asyncio.CancelledError, + PyLGTVCmdException, + ): + _LOGGER.error("Unable to connect to host %s", host) + return + + await async_setup_tv_finalize(hass, config, conf, client) + configurator.async_request_done(request_id) + + request_id = configurator.async_request_config( + name, + lgtv_configuration_callback, + description="Click start and accept the pairing request on your TV.", + description_image="/static/images/config_webos.png", + submit_caption="Start pairing request", + ) diff --git a/homeassistant/components/webostv/manifest.json b/homeassistant/components/webostv/manifest.json index dcf908cd603..016f14f0f94 100644 --- a/homeassistant/components/webostv/manifest.json +++ b/homeassistant/components/webostv/manifest.json @@ -3,8 +3,7 @@ "name": "Webostv", "documentation": "https://www.home-assistant.io/integrations/webostv", "requirements": [ - "pylgtv==0.1.9", - "websockets==6.0" + "aiopylgtv==0.2.4" ], "dependencies": ["configurator"], "codeowners": [] diff --git a/homeassistant/components/webostv/media_player.py b/homeassistant/components/webostv/media_player.py index 3bf0011907d..fd47cf0a114 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -1,16 +1,14 @@ """Support for interface with an LG webOS Smart TV.""" import asyncio from datetime import timedelta +from functools import wraps import logging -from typing import Dict -from urllib.parse import urlparse -from pylgtv import PyLGTVPairException, WebOsClient -import voluptuous as vol +from aiopylgtv import PyLGTVCmdException, PyLGTVPairException from websockets.exceptions import ConnectionClosed from homeassistant import util -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import MediaPlayerDevice from homeassistant.components.media_player.const import ( MEDIA_TYPE_CHANNEL, SUPPORT_NEXT_TRACK, @@ -27,27 +25,21 @@ from homeassistant.components.media_player.const import ( ) from homeassistant.const import ( CONF_CUSTOMIZE, - CONF_FILENAME, CONF_HOST, CONF_NAME, - CONF_TIMEOUT, STATE_OFF, STATE_PAUSED, STATE_PLAYING, ) -import homeassistant.helpers.config_validation as cv from homeassistant.helpers.script import Script -_CONFIGURING: Dict[str, str] = {} +from . import CONF_ON_ACTION, CONF_SOURCES, DOMAIN + _LOGGER = logging.getLogger(__name__) -CONF_SOURCES = "sources" -CONF_ON_ACTION = "turn_on_action" -DEFAULT_NAME = "LG webOS Smart TV" LIVETV_APP_ID = "com.webos.app.livetv" -WEBOSTV_CONFIG_FILE = "webostv.conf" SUPPORT_WEBOSTV = ( SUPPORT_TURN_OFF @@ -65,131 +57,65 @@ SUPPORT_WEBOSTV = ( MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1) -CUSTOMIZE_SCHEMA = vol.Schema( - {vol.Optional(CONF_SOURCES): vol.All(cv.ensure_list, [cv.string])} -) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_CUSTOMIZE, default={}): CUSTOMIZE_SCHEMA, - vol.Optional(CONF_FILENAME, default=WEBOSTV_CONFIG_FILE): cv.string, - vol.Optional(CONF_HOST): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_ON_ACTION): cv.SCRIPT_SCHEMA, - vol.Optional(CONF_TIMEOUT, default=8): cv.positive_int, - } -) - - -def setup_platform(hass, config, add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the LG WebOS TV platform.""" - if discovery_info is not None: - host = urlparse(discovery_info[1]).hostname - else: - host = config.get(CONF_HOST) - if host is None: - _LOGGER.error("No TV found in configuration file or with discovery") - return False - - # Only act if we are not already configuring this host - if host in _CONFIGURING: + if discovery_info is None: return - name = config.get(CONF_NAME) - customize = config.get(CONF_CUSTOMIZE) - timeout = config.get(CONF_TIMEOUT) - turn_on_action = config.get(CONF_ON_ACTION) + host = discovery_info[CONF_HOST] + name = discovery_info[CONF_NAME] + customize = discovery_info[CONF_CUSTOMIZE] + turn_on_action = discovery_info.get(CONF_ON_ACTION) - config = hass.config.path(config.get(CONF_FILENAME)) + client = hass.data[DOMAIN][host]["client"] + on_script = Script(hass, turn_on_action) if turn_on_action else None - setup_tv(host, name, customize, config, timeout, hass, add_entities, turn_on_action) + entity = LgWebOSMediaPlayerEntity(client, name, customize, on_script) + + async_add_entities([entity], update_before_add=False) -def setup_tv( - host, name, customize, config, timeout, hass, add_entities, turn_on_action -): - """Set up a LG WebOS TV based on host parameter.""" +def cmd(func): + """Catch command exceptions.""" - client = WebOsClient(host, config, timeout) - - if not client.is_registered(): - if host in _CONFIGURING: - # Try to pair. - try: - client.register() - except PyLGTVPairException: - _LOGGER.warning("Connected to LG webOS TV %s but not paired", host) - return - except (OSError, ConnectionClosed, asyncio.TimeoutError): - _LOGGER.error("Unable to connect to host %s", host) - return - else: - # Not registered, request configuration. - _LOGGER.warning("LG webOS TV %s needs to be paired", host) - request_configuration( - host, - name, - customize, - config, - timeout, - hass, - add_entities, - turn_on_action, + @wraps(func) + async def wrapper(obj, *args, **kwargs): + """Wrap all command methods.""" + try: + await func(obj, *args, **kwargs) + except ( + asyncio.TimeoutError, + asyncio.CancelledError, + PyLGTVCmdException, + ) as exc: + # If TV is off, we expect calls to fail. + if obj.state == STATE_OFF: + level = logging.INFO + else: + level = logging.ERROR + _LOGGER.log( + level, + "Error calling %s on entity %s: %r", + func.__name__, + obj.entity_id, + exc, ) - return - # If we came here and configuring this host, mark as done. - if client.is_registered() and host in _CONFIGURING: - request_id = _CONFIGURING.pop(host) - configurator = hass.components.configurator - configurator.request_done(request_id) - - add_entities( - [LgWebOSDevice(host, name, customize, config, timeout, hass, turn_on_action)], - True, - ) + return wrapper -def request_configuration( - host, name, customize, config, timeout, hass, add_entities, turn_on_action -): - """Request configuration steps from the user.""" - configurator = hass.components.configurator - - # We got an error if this method is called while we are configuring - if host in _CONFIGURING: - configurator.notify_errors( - _CONFIGURING[host], "Failed to pair, please try again." - ) - return - - def lgtv_configuration_callback(data): - """Handle actions when configuration callback is called.""" - setup_tv( - host, name, customize, config, timeout, hass, add_entities, turn_on_action - ) - - _CONFIGURING[host] = configurator.request_config( - name, - lgtv_configuration_callback, - description="Click start and accept the pairing request on your TV.", - description_image="/static/images/config_webos.png", - submit_caption="Start pairing request", - ) - - -class LgWebOSDevice(MediaPlayerDevice): +class LgWebOSMediaPlayerEntity(MediaPlayerDevice): """Representation of a LG WebOS TV.""" - def __init__(self, host, name, customize, config, timeout, hass, on_action): + def __init__(self, client, name, customize, on_script=None): """Initialize the webos device.""" - - self._client = WebOsClient(host, config, timeout) - self._on_script = Script(hass, on_action) if on_action else None - self._customize = customize - + self._client = client self._name = name + self._customize = customize + self._on_script = on_script + # Assume that the TV is not muted self._muted = False # Assume that the TV is in Play mode @@ -200,64 +126,86 @@ class LgWebOSDevice(MediaPlayerDevice): self._state = None self._source_list = {} self._app_list = {} + self._input_list = {} self._channel = None self._last_icon = None - @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS) - def update(self): - """Retrieve the latest data.""" + async def async_added_to_hass(self): + """Connect and subscribe to state updates.""" + await self._client.register_state_update_callback( + self.async_handle_state_update + ) - try: - current_input = self._client.get_input() - if current_input is not None: - self._current_source_id = current_input - if self._state in (None, STATE_OFF): - self._state = STATE_PLAYING - else: - self._state = STATE_OFF - self._current_source = None - self._current_source_id = None - self._channel = None + # force state update if needed + if self._state is None: + await self.async_handle_state_update() - if self._state is not STATE_OFF: - self._muted = self._client.get_muted() - self._volume = self._client.get_volume() - self._channel = self._client.get_current_channel() + async def async_will_remove_from_hass(self): + """Call disconnect on removal.""" + self._client.unregister_state_update_callback(self.async_handle_state_update) - self._source_list = {} - self._app_list = {} - conf_sources = self._customize.get(CONF_SOURCES, []) + async def async_handle_state_update(self): + """Update state from WebOsClient.""" + self._current_source_id = self._client.current_appId + self._muted = self._client.muted + self._volume = self._client.volume + self._channel = self._client.current_channel + self._app_list = self._client.apps + self._input_list = self._client.inputs - for app in self._client.get_apps(): - self._app_list[app["id"]] = app - if app["id"] == self._current_source_id: - self._current_source = app["title"] - self._source_list[app["title"]] = app - elif ( - not conf_sources - or app["id"] in conf_sources - or any(word in app["title"] for word in conf_sources) - or any(word in app["id"] for word in conf_sources) - ): - self._source_list[app["title"]] = app - - for source in self._client.get_inputs(): - if source["id"] == self._current_source_id: - self._current_source = source["label"] - self._source_list[source["label"]] = source - elif ( - not conf_sources - or source["label"] in conf_sources - or any( - source["label"].find(word) != -1 for word in conf_sources - ) - ): - self._source_list[source["label"]] = source - except (OSError, ConnectionClosed, TypeError, asyncio.TimeoutError): + if self._current_source_id == "": self._state = STATE_OFF - self._current_source = None - self._current_source_id = None - self._channel = None + else: + self._state = STATE_PLAYING + + self.update_sources() + + self.async_schedule_update_ha_state(False) + + def update_sources(self): + """Update list of sources from current source, apps, inputs and configured list.""" + self._source_list = {} + conf_sources = self._customize[CONF_SOURCES] + + for app in self._app_list.values(): + if app["id"] == self._current_source_id: + self._current_source = app["title"] + self._source_list[app["title"]] = app + elif ( + not conf_sources + or app["id"] in conf_sources + or any(word in app["title"] for word in conf_sources) + or any(word in app["id"] for word in conf_sources) + ): + self._source_list[app["title"]] = app + + for source in self._input_list.values(): + if source["appId"] == self._current_source_id: + self._current_source = source["label"] + self._source_list[source["label"]] = source + elif ( + not conf_sources + or source["label"] in conf_sources + or any(source["label"].find(word) != -1 for word in conf_sources) + ): + self._source_list[source["label"]] = source + + @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS) + async def async_update(self): + """Connect.""" + if not self._client.is_connected(): + try: + await self._client.connect() + except ( + OSError, + ConnectionClosed, + ConnectionRefusedError, + asyncio.TimeoutError, + asyncio.CancelledError, + PyLGTVPairException, + PyLGTVCmdException, + ): + pass @property def name(self): @@ -326,46 +274,54 @@ class LgWebOSDevice(MediaPlayerDevice): return SUPPORT_WEBOSTV | SUPPORT_TURN_ON return SUPPORT_WEBOSTV - def turn_off(self): + @cmd + async def async_turn_off(self): """Turn off media player.""" + await self._client.power_off() - self._state = STATE_OFF - try: - self._client.power_off() - except (OSError, ConnectionClosed, TypeError, asyncio.TimeoutError): - pass - - def turn_on(self): + async def async_turn_on(self): """Turn on the media player.""" + connected = self._client.is_connected() if self._on_script: - self._on_script.run() + await self._on_script.async_run() - def volume_up(self): + # if connection was already active + # ensure is still alive + if connected: + await self._client.get_current_app() + + @cmd + async def async_volume_up(self): """Volume up the media player.""" - self._client.volume_up() + await self._client.volume_up() - def volume_down(self): + @cmd + async def async_volume_down(self): """Volume down media player.""" - self._client.volume_down() + await self._client.volume_down() - def set_volume_level(self, volume): + @cmd + async def async_set_volume_level(self, volume): """Set volume level, range 0..1.""" tv_volume = volume * 100 - self._client.set_volume(tv_volume) + await self._client.set_volume(tv_volume) - def mute_volume(self, mute): + @cmd + async def async_mute_volume(self, mute): """Send mute command.""" self._muted = mute - self._client.set_mute(mute) + await self._client.set_mute(mute) - def media_play_pause(self): + @cmd + async def async_media_play_pause(self): """Simulate play pause media player.""" if self._playing: - self.media_pause() + await self.media_pause() else: - self.media_play() + await self.media_play() - def select_source(self, source): + @cmd + async def async_select_source(self, source): """Select input source.""" source_dict = self._source_list.get(source) if source_dict is None: @@ -374,12 +330,13 @@ class LgWebOSDevice(MediaPlayerDevice): self._current_source_id = source_dict["id"] if source_dict.get("title"): self._current_source = source_dict["title"] - self._client.launch_app(source_dict["id"]) + await self._client.launch_app(source_dict["id"]) elif source_dict.get("label"): self._current_source = source_dict["label"] - self._client.set_input(source_dict["id"]) + await self._client.set_input(source_dict["id"]) - def play_media(self, media_type, media_id, **kwargs): + @cmd + async def async_play_media(self, media_type, media_id, **kwargs): """Play a piece of media.""" _LOGGER.debug("Call play media type <%s>, Id <%s>", media_type, media_id) @@ -405,40 +362,47 @@ class LgWebOSDevice(MediaPlayerDevice): "Switching to channel <%s> with perfect match", perfect_match_channel_id, ) - self._client.set_channel(perfect_match_channel_id) + await self._client.set_channel(perfect_match_channel_id) elif partial_match_channel_id is not None: _LOGGER.info( "Switching to channel <%s> with partial match", partial_match_channel_id, ) - self._client.set_channel(partial_match_channel_id) + await self._client.set_channel(partial_match_channel_id) - return - - def media_play(self): + @cmd + async def async_media_play(self): """Send play command.""" self._playing = True self._state = STATE_PLAYING - self._client.play() + await self._client.play() - def media_pause(self): + @cmd + async def async_media_pause(self): """Send media pause command to media player.""" self._playing = False self._state = STATE_PAUSED - self._client.pause() + await self._client.pause() - def media_next_track(self): + @cmd + async def async_media_stop(self): + """Send stop command to media player.""" + await self._client.stop() + + @cmd + async def async_media_next_track(self): """Send next track command.""" current_input = self._client.get_input() if current_input == LIVETV_APP_ID: - self._client.channel_up() + await self._client.channel_up() else: - self._client.fast_forward() + await self._client.fast_forward() - def media_previous_track(self): + @cmd + async def async_media_previous_track(self): """Send the previous track command.""" current_input = self._client.get_input() if current_input == LIVETV_APP_ID: - self._client.channel_down() + await self._client.channel_down() else: - self._client.rewind() + await self._client.rewind() diff --git a/homeassistant/components/webostv/notify.py b/homeassistant/components/webostv/notify.py index f62c41e9a95..e75fafbfe23 100644 --- a/homeassistant/components/webostv/notify.py +++ b/homeassistant/components/webostv/notify.py @@ -1,47 +1,29 @@ """Support for LG WebOS TV notification service.""" +import asyncio import logging -from pylgtv import PyLGTVPairException, WebOsClient -import voluptuous as vol +from aiopylgtv import PyLGTVCmdException, PyLGTVPairException +from websockets.exceptions import ConnectionClosed -from homeassistant.components.notify import ( - ATTR_DATA, - PLATFORM_SCHEMA, - BaseNotificationService, -) -from homeassistant.const import CONF_FILENAME, CONF_HOST, CONF_ICON -import homeassistant.helpers.config_validation as cv +from homeassistant.components.notify import ATTR_DATA, BaseNotificationService +from homeassistant.const import CONF_HOST, CONF_ICON + +from . import DOMAIN _LOGGER = logging.getLogger(__name__) -WEBOSTV_CONFIG_FILE = "webostv.conf" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_FILENAME, default=WEBOSTV_CONFIG_FILE): cv.string, - vol.Optional(CONF_ICON): cv.string, - } -) - - -def get_service(hass, config, discovery_info=None): +async def async_get_service(hass, config, discovery_info=None): """Return the notify service.""" - path = hass.config.path(config.get(CONF_FILENAME)) - client = WebOsClient(config.get(CONF_HOST), key_file_path=path, timeout_connect=8) + host = discovery_info.get(CONF_HOST) + icon_path = discovery_info.get(CONF_ICON) - if not client.is_registered(): - try: - client.register() - except PyLGTVPairException: - _LOGGER.error("Pairing with TV failed") - return None - except OSError: - _LOGGER.error("TV unreachable") - return None + client = hass.data[DOMAIN][host]["client"] - return LgWebOSNotificationService(client, config.get(CONF_ICON)) + svc = LgWebOSNotificationService(client, icon_path) + + return svc class LgWebOSNotificationService(BaseNotificationService): @@ -52,18 +34,27 @@ class LgWebOSNotificationService(BaseNotificationService): self._client = client self._icon_path = icon_path - def send_message(self, message="", **kwargs): + async def async_send_message(self, message="", **kwargs): """Send a message to the tv.""" - try: + if not self._client.is_connected(): + await self._client.connect() + data = kwargs.get(ATTR_DATA) icon_path = ( data.get(CONF_ICON, self._icon_path) if data else self._icon_path ) - self._client.send_message(message, icon_path=icon_path) + await self._client.send_message(message, icon_path=icon_path) except PyLGTVPairException: _LOGGER.error("Pairing with TV failed") except FileNotFoundError: _LOGGER.error("Icon %s not found", icon_path) - except OSError: + except ( + OSError, + ConnectionClosed, + ConnectionRefusedError, + asyncio.TimeoutError, + asyncio.CancelledError, + PyLGTVCmdException, + ): _LOGGER.error("TV unreachable") diff --git a/requirements_all.txt b/requirements_all.txt index 3d60bc22d11..4d697933f66 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -186,6 +186,9 @@ aionotion==1.1.0 # homeassistant.components.hunterdouglas_powerview aiopvapi==1.6.14 +# homeassistant.components.webostv +aiopylgtv==0.2.4 + # homeassistant.components.switcher_kis aioswitcher==2019.4.26 @@ -1325,9 +1328,6 @@ pylaunches==0.2.0 # homeassistant.components.lg_netcast pylgnetcast-homeassistant==0.2.0.dev0 -# homeassistant.components.webostv -pylgtv==0.1.9 - # homeassistant.components.linky pylinky==0.4.0 @@ -2035,9 +2035,6 @@ webexteamssdk==1.1.1 # homeassistant.components.gpmdp websocket-client==0.54.0 -# homeassistant.components.webostv -websockets==6.0 - # homeassistant.components.wirelesstag wirelesstagpy==0.4.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f3ba482a977..83e2299c4ac 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -68,6 +68,9 @@ aiohue==1.10.1 # homeassistant.components.notion aionotion==1.1.0 +# homeassistant.components.webostv +aiopylgtv==0.2.4 + # homeassistant.components.switcher_kis aioswitcher==2019.4.26 @@ -442,9 +445,6 @@ pyiqvia==0.2.1 # homeassistant.components.kira pykira==0.1.1 -# homeassistant.components.webostv -pylgtv==0.1.9 - # homeassistant.components.linky pylinky==0.4.0 @@ -636,9 +636,6 @@ wakeonlan==1.1.6 # homeassistant.components.folder_watcher watchdog==0.8.3 -# homeassistant.components.webostv -websockets==6.0 - # homeassistant.components.withings withings-api==2.1.3 diff --git a/tests/components/webostv/test_media_player.py b/tests/components/webostv/test_media_player.py index e5729a2d8d0..023e0e2dc07 100644 --- a/tests/components/webostv/test_media_player.py +++ b/tests/components/webostv/test_media_player.py @@ -1,56 +1,77 @@ """The tests for the LG webOS media player platform.""" -import unittest -from unittest import mock +import sys -from homeassistant.components.webostv import media_player as webostv +import pytest + +from homeassistant.components import media_player +from homeassistant.components.media_player.const import ( + ATTR_INPUT_SOURCE, + ATTR_MEDIA_VOLUME_MUTED, + SERVICE_SELECT_SOURCE, +) +from homeassistant.components.webostv import DOMAIN +from homeassistant.const import ( + ATTR_ENTITY_ID, + CONF_HOST, + CONF_NAME, + SERVICE_VOLUME_MUTE, +) +from homeassistant.setup import async_setup_component + +if sys.version_info >= (3, 8, 0): + from unittest.mock import patch +else: + from asynctest import patch -class FakeLgWebOSDevice(webostv.LgWebOSDevice): - """A fake device without the client setup required for the real one.""" - - def __init__(self, *args, **kwargs): - """Initialise parameters needed for tests with fake values.""" - self._source_list = {} - self._client = mock.MagicMock() - self._name = "fake_device" - self._current_source = None +NAME = "fake" +ENTITY_ID = f"{media_player.DOMAIN}.{NAME}" -class TestLgWebOSDevice(unittest.TestCase): - """Test the LgWebOSDevice class.""" +@pytest.fixture(name="client") +def client_fixture(): + """Patch of client library for tests.""" + with patch( + "homeassistant.components.webostv.WebOsClient", autospec=True + ) as mock_client_class: + yield mock_client_class.return_value - def setUp(self): - """Configure a fake device for each test.""" - self.device = FakeLgWebOSDevice() - def test_select_source_with_empty_source_list(self): - """Ensure we don't call client methods when we don't have sources.""" - self.device.select_source("nonexistent") - assert 0 == self.device._client.launch_app.call_count - assert 0 == self.device._client.set_input.call_count +async def setup_webostv(hass): + """Initialize webostv and media_player for tests.""" + assert await async_setup_component( + hass, DOMAIN, {DOMAIN: {CONF_HOST: "fake", CONF_NAME: NAME}}, + ) + await hass.async_block_till_done() - def test_select_source_with_titled_entry(self): - """Test that a titled source is treated as an app.""" - self.device._source_list = { - "existent": {"id": "existent_id", "title": "existent_title"} - } - self.device.select_source("existent") +async def test_mute(hass, client): + """Test simple service call.""" - assert "existent_title" == self.device._current_source - assert [mock.call("existent_id")] == ( - self.device._client.launch_app.call_args_list - ) + await setup_webostv(hass) - def test_select_source_with_labelled_entry(self): - """Test that a labelled source is treated as an input source.""" - self.device._source_list = { - "existent": {"id": "existent_id", "label": "existent_label"} - } + data = { + ATTR_ENTITY_ID: ENTITY_ID, + ATTR_MEDIA_VOLUME_MUTED: True, + } + await hass.services.async_call(media_player.DOMAIN, SERVICE_VOLUME_MUTE, data) + await hass.async_block_till_done() - self.device.select_source("existent") + client.set_mute.assert_called_once() - assert "existent_label" == self.device._current_source - assert [mock.call("existent_id")] == ( - self.device._client.set_input.call_args_list - ) + +async def test_select_source_with_empty_source_list(hass, client): + """Ensure we don't call client methods when we don't have sources.""" + + await setup_webostv(hass) + + data = { + ATTR_ENTITY_ID: ENTITY_ID, + ATTR_INPUT_SOURCE: "nonexistent", + } + await hass.services.async_call(media_player.DOMAIN, SERVICE_SELECT_SOURCE, data) + await hass.async_block_till_done() + + assert hass.states.is_state(ENTITY_ID, "playing") + client.launch_app.assert_not_called() + client.set_input.assert_not_called() From 52ed9608e238fe7d8c72b7a1b4cd59cff2e31a5e Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 1 Jan 2020 01:22:44 +0100 Subject: [PATCH 467/677] Migrate input_* tests from coroutine to async/await (#30336) --- tests/components/input_boolean/test_init.py | 11 ++++------- tests/components/input_datetime/test_init.py | 18 +++++++----------- tests/components/input_number/test_init.py | 18 ++++++------------ tests/components/input_select/test_init.py | 15 ++++----------- tests/components/input_text/test_init.py | 13 ++++--------- 5 files changed, 25 insertions(+), 50 deletions(-) diff --git a/tests/components/input_boolean/test_init.py b/tests/components/input_boolean/test_init.py index ed5e927c2ca..2d504114c78 100644 --- a/tests/components/input_boolean/test_init.py +++ b/tests/components/input_boolean/test_init.py @@ -1,6 +1,5 @@ """The tests for the input_boolean component.""" # pylint: disable=protected-access -import asyncio import logging from unittest.mock import patch @@ -96,8 +95,7 @@ async def test_config_options(hass): assert "mdi:work" == state_2.attributes.get(ATTR_ICON) -@asyncio.coroutine -def test_restore_state(hass): +async def test_restore_state(hass): """Ensure states are restored on startup.""" mock_restore_cache( hass, @@ -111,7 +109,7 @@ def test_restore_state(hass): hass.state = CoreState.starting mock_component(hass, "recorder") - yield from async_setup_component(hass, DOMAIN, {DOMAIN: {"b1": None, "b2": None}}) + await async_setup_component(hass, DOMAIN, {DOMAIN: {"b1": None, "b2": None}}) state = hass.states.get("input_boolean.b1") assert state @@ -122,8 +120,7 @@ def test_restore_state(hass): assert state.state == "off" -@asyncio.coroutine -def test_initial_state_overrules_restore_state(hass): +async def test_initial_state_overrules_restore_state(hass): """Ensure states are restored on startup.""" mock_restore_cache( hass, (State("input_boolean.b1", "on"), State("input_boolean.b2", "off")) @@ -131,7 +128,7 @@ def test_initial_state_overrules_restore_state(hass): hass.state = CoreState.starting - yield from async_setup_component( + await async_setup_component( hass, DOMAIN, {DOMAIN: {"b1": {CONF_INITIAL: False}, "b2": {CONF_INITIAL: True}}}, diff --git a/tests/components/input_datetime/test_init.py b/tests/components/input_datetime/test_init.py index 427433e22d2..6908c4fc5f1 100644 --- a/tests/components/input_datetime/test_init.py +++ b/tests/components/input_datetime/test_init.py @@ -1,6 +1,5 @@ """Tests for the Input slider component.""" # pylint: disable=protected-access -import asyncio import datetime from unittest.mock import patch @@ -192,10 +191,9 @@ async def test_set_invalid_2(hass): assert state.state == initial -@asyncio.coroutine -def test_set_datetime_date(hass): +async def test_set_datetime_date(hass): """Test set_datetime method with only date.""" - yield from async_setup_component( + await async_setup_component( hass, DOMAIN, {DOMAIN: {"test_date": {"has_time": False, "has_date": True}}} ) @@ -204,7 +202,7 @@ def test_set_datetime_date(hass): dt_obj = datetime.datetime(2017, 9, 7, 19, 46) date_portion = dt_obj.date() - yield from async_set_date_and_time(hass, entity_id, dt_obj) + await async_set_date_and_time(hass, entity_id, dt_obj) state = hass.states.get(entity_id) assert state.state == str(date_portion) @@ -215,8 +213,7 @@ def test_set_datetime_date(hass): assert state.attributes["timestamp"] == date_dt_obj.timestamp() -@asyncio.coroutine -def test_restore_state(hass): +async def test_restore_state(hass): """Ensure states are restored on startup.""" mock_restore_cache( hass, @@ -232,7 +229,7 @@ def test_restore_state(hass): initial = datetime.datetime(2017, 1, 1, 23, 42) - yield from async_setup_component( + await async_setup_component( hass, DOMAIN, { @@ -263,10 +260,9 @@ def test_restore_state(hass): assert state_bogus.state == str(initial) -@asyncio.coroutine -def test_default_value(hass): +async def test_default_value(hass): """Test default value if none has been set via inital or restore state.""" - yield from async_setup_component( + await async_setup_component( hass, DOMAIN, { diff --git a/tests/components/input_number/test_init.py b/tests/components/input_number/test_init.py index a3b46212daf..6d032b639cf 100644 --- a/tests/components/input_number/test_init.py +++ b/tests/components/input_number/test_init.py @@ -1,6 +1,5 @@ """The tests for the Input number component.""" # pylint: disable=protected-access -import asyncio from unittest.mock import patch import pytest @@ -171,8 +170,7 @@ async def test_mode(hass): assert "slider" == state.attributes["mode"] -@asyncio.coroutine -def test_restore_state(hass): +async def test_restore_state(hass): """Ensure states are restored on startup.""" mock_restore_cache( hass, (State("input_number.b1", "70"), State("input_number.b2", "200")) @@ -180,7 +178,7 @@ def test_restore_state(hass): hass.state = CoreState.starting - yield from async_setup_component( + await async_setup_component( hass, DOMAIN, {DOMAIN: {"b1": {"min": 0, "max": 100}, "b2": {"min": 10, "max": 100}}}, @@ -195,8 +193,7 @@ def test_restore_state(hass): assert float(state.state) == 10 -@asyncio.coroutine -def test_initial_state_overrules_restore_state(hass): +async def test_initial_state_overrules_restore_state(hass): """Ensure states are restored on startup.""" mock_restore_cache( hass, (State("input_number.b1", "70"), State("input_number.b2", "200")) @@ -204,7 +201,7 @@ def test_initial_state_overrules_restore_state(hass): hass.state = CoreState.starting - yield from async_setup_component( + await async_setup_component( hass, DOMAIN, { @@ -224,14 +221,11 @@ def test_initial_state_overrules_restore_state(hass): assert float(state.state) == 60 -@asyncio.coroutine -def test_no_initial_state_and_no_restore_state(hass): +async def test_no_initial_state_and_no_restore_state(hass): """Ensure that entity is create without initial and restore feature.""" hass.state = CoreState.starting - yield from async_setup_component( - hass, DOMAIN, {DOMAIN: {"b1": {"min": 0, "max": 100}}} - ) + await async_setup_component(hass, DOMAIN, {DOMAIN: {"b1": {"min": 0, "max": 100}}}) state = hass.states.get("input_number.b1") assert state diff --git a/tests/components/input_select/test_init.py b/tests/components/input_select/test_init.py index 51f0b24bc8a..8fda80cd3d2 100644 --- a/tests/components/input_select/test_init.py +++ b/tests/components/input_select/test_init.py @@ -1,6 +1,5 @@ """The tests for the Input select component.""" # pylint: disable=protected-access -import asyncio from unittest.mock import patch import pytest @@ -249,8 +248,7 @@ async def test_set_options_service(hass): assert "test2" == state.state -@asyncio.coroutine -def test_restore_state(hass): +async def test_restore_state(hass): """Ensure states are restored on startup.""" mock_restore_cache( hass, @@ -262,9 +260,7 @@ def test_restore_state(hass): options = {"options": ["first option", "middle option", "last option"]} - yield from async_setup_component( - hass, DOMAIN, {DOMAIN: {"s1": options, "s2": options}} - ) + await async_setup_component(hass, DOMAIN, {DOMAIN: {"s1": options, "s2": options}}) state = hass.states.get("input_select.s1") assert state @@ -275,8 +271,7 @@ def test_restore_state(hass): assert state.state == "first option" -@asyncio.coroutine -def test_initial_state_overrules_restore_state(hass): +async def test_initial_state_overrules_restore_state(hass): """Ensure states are restored on startup.""" mock_restore_cache( hass, @@ -291,9 +286,7 @@ def test_initial_state_overrules_restore_state(hass): "initial": "middle option", } - yield from async_setup_component( - hass, DOMAIN, {DOMAIN: {"s1": options, "s2": options}} - ) + await async_setup_component(hass, DOMAIN, {DOMAIN: {"s1": options, "s2": options}}) state = hass.states.get("input_select.s1") assert state diff --git a/tests/components/input_text/test_init.py b/tests/components/input_text/test_init.py index d37fe01cd29..8835128d672 100644 --- a/tests/components/input_text/test_init.py +++ b/tests/components/input_text/test_init.py @@ -1,6 +1,5 @@ """The tests for the Input text component.""" # pylint: disable=protected-access -import asyncio from unittest.mock import patch import pytest @@ -122,8 +121,7 @@ async def test_restore_state(hass): assert str(state.state) == "unknown" -@asyncio.coroutine -def test_initial_state_overrules_restore_state(hass): +async def test_initial_state_overrules_restore_state(hass): """Ensure states are restored on startup.""" mock_restore_cache( hass, @@ -132,7 +130,7 @@ def test_initial_state_overrules_restore_state(hass): hass.state = CoreState.starting - yield from async_setup_component( + await async_setup_component( hass, DOMAIN, { @@ -152,14 +150,11 @@ def test_initial_state_overrules_restore_state(hass): assert str(state.state) == "test" -@asyncio.coroutine -def test_no_initial_state_and_no_restore_state(hass): +async def test_no_initial_state_and_no_restore_state(hass): """Ensure that entity is create without initial and restore feature.""" hass.state = CoreState.starting - yield from async_setup_component( - hass, DOMAIN, {DOMAIN: {"b1": {"min": 0, "max": 100}}} - ) + await async_setup_component(hass, DOMAIN, {DOMAIN: {"b1": {"min": 0, "max": 100}}}) state = hass.states.get("input_text.b1") assert state From bb2d8e3f7d90c13e148bac0d88dd3511bf6f4202 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Wed, 1 Jan 2020 00:32:22 +0000 Subject: [PATCH 468/677] [ci skip] Translation update --- .../components/gios/.translations/en.json | 20 +++++++++++++ .../components/gios/.translations/ru.json | 20 +++++++++++++ .../huawei_lte/.translations/pl.json | 1 + .../huawei_lte/.translations/ru.json | 2 +- .../components/local_ip/.translations/en.json | 16 ++++++++++ .../components/local_ip/.translations/ru.json | 16 ++++++++++ .../components/tesla/.translations/pl.json | 30 +++++++++++++++++++ 7 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/gios/.translations/en.json create mode 100644 homeassistant/components/gios/.translations/ru.json create mode 100644 homeassistant/components/local_ip/.translations/en.json create mode 100644 homeassistant/components/local_ip/.translations/ru.json create mode 100644 homeassistant/components/tesla/.translations/pl.json diff --git a/homeassistant/components/gios/.translations/en.json b/homeassistant/components/gios/.translations/en.json new file mode 100644 index 00000000000..2ff0d8c60f3 --- /dev/null +++ b/homeassistant/components/gios/.translations/en.json @@ -0,0 +1,20 @@ +{ + "config": { + "error": { + "cannot_connect": "Cannot connect to the GIO\u015a server.", + "invalid_sensors_data": "Invalid sensors' data for this measuring station.", + "wrong_station_id": "ID of the measuring station is not correct." + }, + "step": { + "user": { + "data": { + "name": "Name of the integration", + "station_id": "ID of the measuring station" + }, + "description": "Set up GIO\u015a (Polish Chief Inspectorate Of Environmental Protection) air quality integration. If you need help with the configuration have a look here: https://www.home-assistant.io/integrations/gios", + "title": "GIO\u015a (Polish Chief Inspectorate Of Environmental Protection)" + } + }, + "title": "GIO\u015a" + } +} \ No newline at end of file diff --git a/homeassistant/components/gios/.translations/ru.json b/homeassistant/components/gios/.translations/ru.json new file mode 100644 index 00000000000..f45ad965550 --- /dev/null +++ b/homeassistant/components/gios/.translations/ru.json @@ -0,0 +1,20 @@ +{ + "config": { + "error": { + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443 GIO\u015a.", + "invalid_sensors_data": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u0430\u0442\u0447\u0438\u043a\u043e\u0432 \u0434\u043b\u044f \u044d\u0442\u043e\u0439 \u0438\u0437\u043c\u0435\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0441\u0442\u0430\u043d\u0446\u0438\u0438.", + "wrong_station_id": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 ID \u0438\u0437\u043c\u0435\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0441\u0442\u0430\u043d\u0446\u0438\u0438." + }, + "step": { + "user": { + "data": { + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", + "station_id": "ID \u0438\u0437\u043c\u0435\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0441\u0442\u0430\u043d\u0446\u0438\u0438" + }, + "description": "\u0418\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0432\u043e\u0437\u0434\u0443\u0445\u0430 \u043e\u0442 \u041f\u043e\u043b\u044c\u0441\u043a\u043e\u0439 \u0438\u043d\u0441\u043f\u0435\u043a\u0446\u0438\u0438 \u043f\u043e \u043e\u0445\u0440\u0430\u043d\u0435 \u043e\u043a\u0440\u0443\u0436\u0430\u044e\u0449\u0435\u0439 \u0441\u0440\u0435\u0434\u044b (GIO\u015a). \u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0435\u0439 \u043f\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0435 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438: https://www.home-assistant.io/integrations/gios.", + "title": "GIO\u015a (\u041f\u043e\u043b\u044c\u0441\u043a\u0430\u044f \u0438\u043d\u0441\u043f\u0435\u043a\u0446\u0438\u044f \u043f\u043e \u043e\u0445\u0440\u0430\u043d\u0435 \u043e\u043a\u0440\u0443\u0436\u0430\u044e\u0449\u0435\u0439 \u0441\u0440\u0435\u0434\u044b)" + } + }, + "title": "GIO\u015a" + } +} \ No newline at end of file diff --git a/homeassistant/components/huawei_lte/.translations/pl.json b/homeassistant/components/huawei_lte/.translations/pl.json index 3851d0a409f..a4e7d72852a 100644 --- a/homeassistant/components/huawei_lte/.translations/pl.json +++ b/homeassistant/components/huawei_lte/.translations/pl.json @@ -33,6 +33,7 @@ "step": { "init": { "data": { + "name": "Nazwa us\u0142ugi powiadomie\u0144 (zmiana wymaga ponownego uruchomienia)", "recipient": "Odbiorcy powiadomie\u0144 SMS", "track_new_devices": "\u015aled\u017a nowe urz\u0105dzenia" } diff --git a/homeassistant/components/huawei_lte/.translations/ru.json b/homeassistant/components/huawei_lte/.translations/ru.json index 6f478987f50..3850b86167a 100644 --- a/homeassistant/components/huawei_lte/.translations/ru.json +++ b/homeassistant/components/huawei_lte/.translations/ru.json @@ -33,7 +33,7 @@ "step": { "init": { "data": { - "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0441\u043b\u0443\u0436\u0431\u044b \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0441\u043b\u0443\u0436\u0431\u044b \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439 (\u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a)", "recipient": "\u041f\u043e\u043b\u0443\u0447\u0430\u0442\u0435\u043b\u0438 SMS-\u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439", "track_new_devices": "\u041e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0442\u044c \u043d\u043e\u0432\u044b\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430" } diff --git a/homeassistant/components/local_ip/.translations/en.json b/homeassistant/components/local_ip/.translations/en.json new file mode 100644 index 00000000000..869bb5a23d5 --- /dev/null +++ b/homeassistant/components/local_ip/.translations/en.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "Integration is already configured with an existing sensor with that name" + }, + "step": { + "user": { + "data": { + "name": "Sensor Name" + }, + "title": "Local IP Address" + } + }, + "title": "Local IP Address" + } +} \ No newline at end of file diff --git a/homeassistant/components/local_ip/.translations/ru.json b/homeassistant/components/local_ip/.translations/ru.json new file mode 100644 index 00000000000..de92b9680f0 --- /dev/null +++ b/homeassistant/components/local_ip/.translations/ru.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "\u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0443\u0436\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u0430 \u0441 \u0442\u0430\u043a\u0438\u043c \u0436\u0435 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\u043c \u0434\u0430\u0442\u0447\u0438\u043a\u0430." + }, + "step": { + "user": { + "data": { + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" + }, + "title": "\u041b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439 IP-\u0430\u0434\u0440\u0435\u0441" + } + }, + "title": "\u041b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439 IP-\u0430\u0434\u0440\u0435\u0441" + } +} \ No newline at end of file diff --git a/homeassistant/components/tesla/.translations/pl.json b/homeassistant/components/tesla/.translations/pl.json new file mode 100644 index 00000000000..5a8a3d2ebd3 --- /dev/null +++ b/homeassistant/components/tesla/.translations/pl.json @@ -0,0 +1,30 @@ +{ + "config": { + "error": { + "connection_error": "B\u0142\u0105d po\u0142\u0105czenia; sprawd\u017a sie\u0107 i spr\u00f3buj ponownie", + "identifier_exists": "Adres e-mail ju\u017c zarejestrowany", + "invalid_credentials": "Nieprawid\u0142owe po\u015bwiadczenia", + "unknown_error": "Nieznany b\u0142\u0105d, prosz\u0119 zg\u0142osi\u0107 dane z loga" + }, + "step": { + "user": { + "data": { + "password": "Has\u0142o", + "username": "Adres e-mail" + }, + "description": "Wprowad\u017a dane", + "title": "Tesla - konfiguracja" + } + }, + "title": "Tesla" + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Cz\u0119stotliwo\u015b\u0107 aktualizacji" + } + } + } + } +} \ No newline at end of file From 272c00e81bc155f0cadf04ae108db53ef7d3f173 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Wed, 1 Jan 2020 03:23:29 -0800 Subject: [PATCH 469/677] Remove use of bin_type in Tesla component (#30315) * Remove use of bin_type * Convert _unit attribute to units * Remove unnecessary variable assignment * Change to using util library convert --- homeassistant/components/tesla/sensor.py | 39 ++++++++++-------------- homeassistant/components/tesla/switch.py | 4 +-- 2 files changed, 18 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/tesla/sensor.py b/homeassistant/components/tesla/sensor.py index d93d3fa45d6..78e1106ed43 100644 --- a/homeassistant/components/tesla/sensor.py +++ b/homeassistant/components/tesla/sensor.py @@ -8,6 +8,7 @@ from homeassistant.const import ( TEMP_FAHRENHEIT, ) from homeassistant.helpers.entity import Entity +from homeassistant.util.distance import convert from . import DOMAIN as TESLA_DOMAIN, TeslaDevice @@ -24,10 +25,10 @@ async def async_setup_entry(hass, config_entry, async_add_entities): controller = hass.data[TESLA_DOMAIN][config_entry.entry_id]["controller"] entities = [] for device in hass.data[TESLA_DOMAIN][config_entry.entry_id]["devices"]["sensor"]: - if device.bin_type == 0x4: + if device.type == "temperature sensor": entities.append(TeslaSensor(device, controller, config_entry, "inside")) entities.append(TeslaSensor(device, controller, config_entry, "outside")) - elif device.bin_type in [0xA, 0xB, 0x5]: + else: entities.append(TeslaSensor(device, controller, config_entry)) async_add_entities(entities, True) @@ -38,7 +39,7 @@ class TeslaSensor(TeslaDevice, Entity): def __init__(self, tesla_device, controller, config_entry, sensor_type=None): """Initialize of the sensor.""" self.current_value = None - self._unit = None + self.units = None self.last_changed_time = None self.type = sensor_type super().__init__(tesla_device, controller, config_entry) @@ -61,7 +62,7 @@ class TeslaSensor(TeslaDevice, Entity): @property def unit_of_measurement(self): """Return the unit_of_measurement of the device.""" - return self._unit + return self.units async def async_update(self): """Update the state from the sensor.""" @@ -69,32 +70,24 @@ class TeslaSensor(TeslaDevice, Entity): await super().async_update() units = self.tesla_device.measurement - if self.tesla_device.bin_type == 0x4: + if self.tesla_device.type == "temperature sensor": if self.type == "outside": self.current_value = self.tesla_device.get_outside_temp() else: self.current_value = self.tesla_device.get_inside_temp() if units == "F": - self._unit = TEMP_FAHRENHEIT + self.units = TEMP_FAHRENHEIT else: - self._unit = TEMP_CELSIUS - elif self.tesla_device.bin_type == 0xA or self.tesla_device.bin_type == 0xB: + self.units = TEMP_CELSIUS + elif self.tesla_device.type in ["range sensor", "mileage sensor"]: self.current_value = self.tesla_device.get_value() - tesla_dist_unit = self.tesla_device.measurement - if tesla_dist_unit == "LENGTH_MILES": - self._unit = LENGTH_MILES + if units == "LENGTH_MILES": + self.units = LENGTH_MILES else: - self._unit = LENGTH_KILOMETERS - self.current_value /= 0.621371 - self.current_value = round(self.current_value, 2) + self.units = LENGTH_KILOMETERS + self.current_value = round( + convert(self.current_value, LENGTH_MILES, LENGTH_KILOMETERS), 2 + ) else: self.current_value = self.tesla_device.get_value() - if self.tesla_device.bin_type == 0x5: - self._unit = units - elif self.tesla_device.bin_type in (0xA, 0xB): - if units == "LENGTH_MILES": - self._unit = LENGTH_MILES - else: - self._unit = LENGTH_KILOMETERS - self.current_value /= 0.621371 - self.current_value = round(self.current_value, 2) + self.units = units diff --git a/homeassistant/components/tesla/switch.py b/homeassistant/components/tesla/switch.py index 3fc424e390d..fc9b5e1ba88 100644 --- a/homeassistant/components/tesla/switch.py +++ b/homeassistant/components/tesla/switch.py @@ -19,10 +19,10 @@ async def async_setup_entry(hass, config_entry, async_add_entities): controller = hass.data[TESLA_DOMAIN][config_entry.entry_id]["controller"] entities = [] for device in hass.data[TESLA_DOMAIN][config_entry.entry_id]["devices"]["switch"]: - if device.bin_type == 0x8: + if device.type == "charger switch": entities.append(ChargerSwitch(device, controller, config_entry)) entities.append(UpdateSwitch(device, controller, config_entry)) - elif device.bin_type == 0x9: + elif device.type == "maxrange switch": entities.append(RangeSwitch(device, controller, config_entry)) async_add_entities(entities, True) From 298d8986274baf5ac1fbf15bf1ecfa23953d419c Mon Sep 17 00:00:00 2001 From: Josef Schlehofer Date: Wed, 1 Jan 2020 12:36:43 +0100 Subject: [PATCH 470/677] Upgrade youtube_dl to version 2020.01.01 (#30341) --- homeassistant/components/media_extractor/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_extractor/manifest.json b/homeassistant/components/media_extractor/manifest.json index b57b4396869..0c816686557 100644 --- a/homeassistant/components/media_extractor/manifest.json +++ b/homeassistant/components/media_extractor/manifest.json @@ -3,7 +3,7 @@ "name": "Media extractor", "documentation": "https://www.home-assistant.io/integrations/media_extractor", "requirements": [ - "youtube_dl==2019.12.25" + "youtube_dl==2020.01.01" ], "dependencies": [ "media_player" diff --git a/requirements_all.txt b/requirements_all.txt index 4d697933f66..378af0971c5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2085,7 +2085,7 @@ yeelight==0.5.0 yeelightsunflower==0.0.10 # homeassistant.components.media_extractor -youtube_dl==2019.12.25 +youtube_dl==2020.01.01 # homeassistant.components.zengge zengge==0.2 From e099d57bdec79c3b92daf12f0a621d993ff55982 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Wed, 1 Jan 2020 14:04:37 +0100 Subject: [PATCH 471/677] Upgrade zeroconf to 0.24.4 (#30347) --- homeassistant/components/zeroconf/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/zeroconf/manifest.json b/homeassistant/components/zeroconf/manifest.json index 720ffc3e69e..ec4db5931dc 100644 --- a/homeassistant/components/zeroconf/manifest.json +++ b/homeassistant/components/zeroconf/manifest.json @@ -3,7 +3,7 @@ "name": "Zeroconf", "documentation": "https://www.home-assistant.io/integrations/zeroconf", "requirements": [ - "zeroconf==0.24.3" + "zeroconf==0.24.4" ], "dependencies": [ "api" diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 4bbbad9ad72..8347567246e 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -24,7 +24,7 @@ ruamel.yaml==0.15.100 sqlalchemy==1.3.12 voluptuous-serialize==2.3.0 voluptuous==0.11.7 -zeroconf==0.24.3 +zeroconf==0.24.4 pycryptodome>=3.6.6 diff --git a/requirements_all.txt b/requirements_all.txt index 378af0971c5..5ca35843279 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2091,7 +2091,7 @@ youtube_dl==2020.01.01 zengge==0.2 # homeassistant.components.zeroconf -zeroconf==0.24.3 +zeroconf==0.24.4 # homeassistant.components.zha zha-quirks==0.0.30 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 83e2299c4ac..ff1ded4434c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -656,7 +656,7 @@ ya_ma==0.3.8 yahooweather==0.10 # homeassistant.components.zeroconf -zeroconf==0.24.3 +zeroconf==0.24.4 # homeassistant.components.zha zha-quirks==0.0.30 From 9fbe6d60cb724d18aea91c6d8ffb419a6ad3e9c2 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 1 Jan 2020 16:17:35 +0100 Subject: [PATCH 472/677] Migrate startca tests from coroutine to async/await (#30354) --- tests/components/startca/test_sensor.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/tests/components/startca/test_sensor.py b/tests/components/startca/test_sensor.py index ab043c44d11..aa06e13a812 100644 --- a/tests/components/startca/test_sensor.py +++ b/tests/components/startca/test_sensor.py @@ -1,13 +1,10 @@ """Tests for the Start.ca sensor platform.""" -import asyncio - from homeassistant.bootstrap import async_setup_component from homeassistant.components.startca.sensor import StartcaData from homeassistant.helpers.aiohttp_client import async_get_clientsession -@asyncio.coroutine -def test_capped_setup(hass, aioclient_mock): +async def test_capped_setup(hass, aioclient_mock): """Test the default setup.""" config = { "platform": "startca", @@ -51,7 +48,7 @@ def test_capped_setup(hass, aioclient_mock): "https://www.start.ca/support/usage/api?key=" "NOTAKEY", text=result ) - yield from async_setup_component(hass, "sensor", {"sensor": config}) + await async_setup_component(hass, "sensor", {"sensor": config}) state = hass.states.get("sensor.start_ca_usage_ratio") assert state.attributes.get("unit_of_measurement") == "%" @@ -102,8 +99,7 @@ def test_capped_setup(hass, aioclient_mock): assert state.state == "95.05" -@asyncio.coroutine -def test_unlimited_setup(hass, aioclient_mock): +async def test_unlimited_setup(hass, aioclient_mock): """Test the default setup.""" config = { "platform": "startca", @@ -147,7 +143,7 @@ def test_unlimited_setup(hass, aioclient_mock): "https://www.start.ca/support/usage/api?key=" "NOTAKEY", text=result ) - yield from async_setup_component(hass, "sensor", {"sensor": config}) + await async_setup_component(hass, "sensor", {"sensor": config}) state = hass.states.get("sensor.start_ca_usage_ratio") assert state.attributes.get("unit_of_measurement") == "%" @@ -198,8 +194,7 @@ def test_unlimited_setup(hass, aioclient_mock): assert state.state == "inf" -@asyncio.coroutine -def test_bad_return_code(hass, aioclient_mock): +async def test_bad_return_code(hass, aioclient_mock): """Test handling a return code that isn't HTTP OK.""" aioclient_mock.get( "https://www.start.ca/support/usage/api?key=" "NOTAKEY", status=404 @@ -207,12 +202,11 @@ def test_bad_return_code(hass, aioclient_mock): scd = StartcaData(hass.loop, async_get_clientsession(hass), "NOTAKEY", 400) - result = yield from scd.async_update() + result = await scd.async_update() assert result is False -@asyncio.coroutine -def test_bad_json_decode(hass, aioclient_mock): +async def test_bad_json_decode(hass, aioclient_mock): """Test decoding invalid json result.""" aioclient_mock.get( "https://www.start.ca/support/usage/api?key=" "NOTAKEY", text="this is not xml" @@ -220,5 +214,5 @@ def test_bad_json_decode(hass, aioclient_mock): scd = StartcaData(hass.loop, async_get_clientsession(hass), "NOTAKEY", 400) - result = yield from scd.async_update() + result = await scd.async_update() assert result is False From 9ba0daa35818b16bf7004005724957afff18994a Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 1 Jan 2020 16:18:17 +0100 Subject: [PATCH 473/677] Migrate teksavvy tests from coroutine to async/await (#30353) --- tests/components/teksavvy/test_sensor.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/tests/components/teksavvy/test_sensor.py b/tests/components/teksavvy/test_sensor.py index 723cf07f173..30bb98911f8 100644 --- a/tests/components/teksavvy/test_sensor.py +++ b/tests/components/teksavvy/test_sensor.py @@ -1,13 +1,10 @@ """Tests for the TekSavvy sensor platform.""" -import asyncio - from homeassistant.bootstrap import async_setup_component from homeassistant.components.teksavvy.sensor import TekSavvyData from homeassistant.helpers.aiohttp_client import async_get_clientsession -@asyncio.coroutine -def test_capped_setup(hass, aioclient_mock): +async def test_capped_setup(hass, aioclient_mock): """Test the default setup.""" config = { "platform": "teksavvy", @@ -45,7 +42,7 @@ def test_capped_setup(hass, aioclient_mock): text=result, ) - yield from async_setup_component(hass, "sensor", {"sensor": config}) + await async_setup_component(hass, "sensor", {"sensor": config}) state = hass.states.get("sensor.teksavvy_data_limit") assert state.attributes.get("unit_of_measurement") == "GB" @@ -88,8 +85,7 @@ def test_capped_setup(hass, aioclient_mock): assert state.state == "173.25" -@asyncio.coroutine -def test_unlimited_setup(hass, aioclient_mock): +async def test_unlimited_setup(hass, aioclient_mock): """Test the default setup.""" config = { "platform": "teksavvy", @@ -127,7 +123,7 @@ def test_unlimited_setup(hass, aioclient_mock): text=result, ) - yield from async_setup_component(hass, "sensor", {"sensor": config}) + await async_setup_component(hass, "sensor", {"sensor": config}) state = hass.states.get("sensor.teksavvy_data_limit") assert state.attributes.get("unit_of_measurement") == "GB" @@ -170,8 +166,7 @@ def test_unlimited_setup(hass, aioclient_mock): assert state.state == "inf" -@asyncio.coroutine -def test_bad_return_code(hass, aioclient_mock): +async def test_bad_return_code(hass, aioclient_mock): """Test handling a return code that isn't HTTP OK.""" aioclient_mock.get( "https://api.teksavvy.com/" @@ -182,12 +177,11 @@ def test_bad_return_code(hass, aioclient_mock): tsd = TekSavvyData(hass.loop, async_get_clientsession(hass), "notakey", 400) - result = yield from tsd.async_update() + result = await tsd.async_update() assert result is False -@asyncio.coroutine -def test_bad_json_decode(hass, aioclient_mock): +async def test_bad_json_decode(hass, aioclient_mock): """Test decoding invalid json result.""" aioclient_mock.get( "https://api.teksavvy.com/" @@ -198,5 +192,5 @@ def test_bad_json_decode(hass, aioclient_mock): tsd = TekSavvyData(hass.loop, async_get_clientsession(hass), "notakey", 400) - result = yield from tsd.async_update() + result = await tsd.async_update() assert result is False From 33828ae514fed0ed70c6b474d5f410bd9b8f2961 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 1 Jan 2020 16:20:18 +0100 Subject: [PATCH 474/677] Migrate timer tests from coroutine to async/await (#30352) --- tests/components/timer/test_init.py | 32 ++++++++++------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/tests/components/timer/test_init.py b/tests/components/timer/test_init.py index 547f7e4ab05..93493fc3a55 100644 --- a/tests/components/timer/test_init.py +++ b/tests/components/timer/test_init.py @@ -1,6 +1,5 @@ """The tests for the timer component.""" # pylint: disable=protected-access -import asyncio from datetime import timedelta import logging from unittest.mock import patch @@ -89,14 +88,11 @@ async def test_config_options(hass): assert "0:00:10" == state_2.attributes.get(ATTR_DURATION) -@asyncio.coroutine -def test_methods_and_events(hass): +async def test_methods_and_events(hass): """Test methods and events.""" hass.state = CoreState.starting - yield from async_setup_component( - hass, DOMAIN, {DOMAIN: {"test1": {CONF_DURATION: 10}}} - ) + await async_setup_component(hass, DOMAIN, {DOMAIN: {"test1": {CONF_DURATION: 10}}}) state = hass.states.get("timer.test1") assert state @@ -129,10 +125,10 @@ def test_methods_and_events(hass): expectedEvents = 0 for step in steps: if step["call"] is not None: - yield from hass.services.async_call( + await hass.services.async_call( DOMAIN, step["call"], {CONF_ENTITY_ID: "timer.test1"} ) - yield from hass.async_block_till_done() + await hass.async_block_till_done() state = hass.states.get("timer.test1") assert state @@ -145,14 +141,11 @@ def test_methods_and_events(hass): assert len(results) == expectedEvents -@asyncio.coroutine -def test_wait_till_timer_expires(hass): +async def test_wait_till_timer_expires(hass): """Test for a timer to end.""" hass.state = CoreState.starting - yield from async_setup_component( - hass, DOMAIN, {DOMAIN: {"test1": {CONF_DURATION: 10}}} - ) + await async_setup_component(hass, DOMAIN, {DOMAIN: {"test1": {CONF_DURATION: 10}}}) state = hass.states.get("timer.test1") assert state @@ -169,10 +162,10 @@ def test_wait_till_timer_expires(hass): hass.bus.async_listen(EVENT_TIMER_FINISHED, fake_event_listener) hass.bus.async_listen(EVENT_TIMER_CANCELLED, fake_event_listener) - yield from hass.services.async_call( + await hass.services.async_call( DOMAIN, SERVICE_START, {CONF_ENTITY_ID: "timer.test1"} ) - yield from hass.async_block_till_done() + await hass.async_block_till_done() state = hass.states.get("timer.test1") assert state @@ -182,7 +175,7 @@ def test_wait_till_timer_expires(hass): assert len(results) == 1 async_fire_time_changed(hass, utcnow() + timedelta(seconds=10)) - yield from hass.async_block_till_done() + await hass.async_block_till_done() state = hass.states.get("timer.test1") assert state @@ -192,14 +185,11 @@ def test_wait_till_timer_expires(hass): assert len(results) == 2 -@asyncio.coroutine -def test_no_initial_state_and_no_restore_state(hass): +async def test_no_initial_state_and_no_restore_state(hass): """Ensure that entity is create without initial and restore feature.""" hass.state = CoreState.starting - yield from async_setup_component( - hass, DOMAIN, {DOMAIN: {"test1": {CONF_DURATION: 10}}} - ) + await async_setup_component(hass, DOMAIN, {DOMAIN: {"test1": {CONF_DURATION: 10}}}) state = hass.states.get("timer.test1") assert state From bc6e2a06e6d7bf265db55ce44f36ac31bde5d600 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 1 Jan 2020 16:21:37 +0100 Subject: [PATCH 475/677] Migrate yr tests from coroutine to async/await (#30351) --- tests/components/yr/test_sensor.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/tests/components/yr/test_sensor.py b/tests/components/yr/test_sensor.py index 21ce0bbe7ce..7e2e8543f77 100644 --- a/tests/components/yr/test_sensor.py +++ b/tests/components/yr/test_sensor.py @@ -1,5 +1,4 @@ """The tests for the Yr sensor platform.""" -import asyncio from datetime import datetime from unittest.mock import patch @@ -11,8 +10,7 @@ from tests.common import assert_setup_component, load_fixture NOW = datetime(2016, 6, 9, 1, tzinfo=dt_util.UTC) -@asyncio.coroutine -def test_default_setup(hass, aioclient_mock): +async def test_default_setup(hass, aioclient_mock): """Test the default setup.""" aioclient_mock.get( "https://aa015h6buqvih86i1.api.met.no/" "weatherapi/locationforecast/1.9/", @@ -23,7 +21,7 @@ def test_default_setup(hass, aioclient_mock): with patch( "homeassistant.components.yr.sensor.dt_util.utcnow", return_value=NOW ), assert_setup_component(1): - yield from async_setup_component(hass, "sensor", {"sensor": config}) + await async_setup_component(hass, "sensor", {"sensor": config}) state = hass.states.get("sensor.yr_symbol") @@ -31,8 +29,7 @@ def test_default_setup(hass, aioclient_mock): assert state.attributes.get("unit_of_measurement") is None -@asyncio.coroutine -def test_custom_setup(hass, aioclient_mock): +async def test_custom_setup(hass, aioclient_mock): """Test a custom setup.""" aioclient_mock.get( "https://aa015h6buqvih86i1.api.met.no/" "weatherapi/locationforecast/1.9/", @@ -54,7 +51,7 @@ def test_custom_setup(hass, aioclient_mock): with patch( "homeassistant.components.yr.sensor.dt_util.utcnow", return_value=NOW ), assert_setup_component(1): - yield from async_setup_component(hass, "sensor", {"sensor": config}) + await async_setup_component(hass, "sensor", {"sensor": config}) state = hass.states.get("sensor.yr_pressure") assert state.attributes.get("unit_of_measurement") == "hPa" @@ -77,8 +74,7 @@ def test_custom_setup(hass, aioclient_mock): assert state.state == "3.5" -@asyncio.coroutine -def test_forecast_setup(hass, aioclient_mock): +async def test_forecast_setup(hass, aioclient_mock): """Test a custom setup with 24h forecast.""" aioclient_mock.get( "https://aa015h6buqvih86i1.api.met.no/" "weatherapi/locationforecast/1.9/", @@ -101,7 +97,7 @@ def test_forecast_setup(hass, aioclient_mock): with patch( "homeassistant.components.yr.sensor.dt_util.utcnow", return_value=NOW ), assert_setup_component(1): - yield from async_setup_component(hass, "sensor", {"sensor": config}) + await async_setup_component(hass, "sensor", {"sensor": config}) state = hass.states.get("sensor.yr_pressure") assert state.attributes.get("unit_of_measurement") == "hPa" From 83e05da0b3d0bf644cfb25b52d527c7915685492 Mon Sep 17 00:00:00 2001 From: Jeff Irion Date: Wed, 1 Jan 2020 10:40:11 -0800 Subject: [PATCH 476/677] Fix media_player example for select_source service (#30358) --- homeassistant/components/media_player/services.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/media_player/services.yaml b/homeassistant/components/media_player/services.yaml index ad6b8a78957..19ef1cb14c0 100644 --- a/homeassistant/components/media_player/services.yaml +++ b/homeassistant/components/media_player/services.yaml @@ -125,7 +125,7 @@ select_source: fields: entity_id: description: Name(s) of entities to change source on. - example: 'media_player.media_player.txnr535_0009b0d81f82' + example: 'media_player.txnr535_0009b0d81f82' source: description: Name of the source to switch to. Platform dependent. example: 'video1' From 0479e93de7266031ed1a7798fbad205d736719f4 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Wed, 1 Jan 2020 20:06:20 +0100 Subject: [PATCH 477/677] Upgrade python_opendata_transport to 0.2.1 (#30348) --- homeassistant/components/swiss_public_transport/manifest.json | 4 ++-- requirements_all.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/swiss_public_transport/manifest.json b/homeassistant/components/swiss_public_transport/manifest.json index c91d85fecd7..387b3da5da4 100644 --- a/homeassistant/components/swiss_public_transport/manifest.json +++ b/homeassistant/components/swiss_public_transport/manifest.json @@ -3,10 +3,10 @@ "name": "Swiss public transport", "documentation": "https://www.home-assistant.io/integrations/swiss_public_transport", "requirements": [ - "python_opendata_transport==0.1.4" + "python_opendata_transport==0.2.1" ], "dependencies": [], "codeowners": [ "@fabaff" ] -} +} \ No newline at end of file diff --git a/requirements_all.txt b/requirements_all.txt index 5ca35843279..d2ed4e0e1e2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1646,7 +1646,7 @@ python-wink==1.10.5 python_awair==0.0.4 # homeassistant.components.swiss_public_transport -python_opendata_transport==0.1.4 +python_opendata_transport==0.2.1 # homeassistant.components.egardia pythonegardia==1.0.40 From 790b2d00c71ebfd112a078e2bd85ad6c26c6c158 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Wed, 1 Jan 2020 11:49:20 -0800 Subject: [PATCH 478/677] Fix HVAC mode for Tesla (#30287) * Fix HVAC mode for Tesla * Change HVAC_MODE to HEAT_COOL --- homeassistant/components/tesla/climate.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/tesla/climate.py b/homeassistant/components/tesla/climate.py index d3c87035c9c..d7f21d7895f 100644 --- a/homeassistant/components/tesla/climate.py +++ b/homeassistant/components/tesla/climate.py @@ -3,7 +3,7 @@ import logging from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( - HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, SUPPORT_TARGET_TEMPERATURE, ) @@ -13,7 +13,7 @@ from . import DOMAIN as TESLA_DOMAIN, TeslaDevice _LOGGER = logging.getLogger(__name__) -SUPPORT_HVAC = [HVAC_MODE_HEAT, HVAC_MODE_OFF] +SUPPORT_HVAC = [HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF] async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): @@ -59,7 +59,7 @@ class TeslaThermostat(TeslaDevice, ClimateDevice): Need to be one of HVAC_MODE_*. """ if self.tesla_device.is_hvac_enabled(): - return HVAC_MODE_HEAT + return HVAC_MODE_HEAT_COOL return HVAC_MODE_OFF @property @@ -108,5 +108,5 @@ class TeslaThermostat(TeslaDevice, ClimateDevice): _LOGGER.debug("Setting mode for: %s", self._name) if hvac_mode == HVAC_MODE_OFF: await self.tesla_device.set_status(False) - elif hvac_mode == HVAC_MODE_HEAT: + elif hvac_mode == HVAC_MODE_HEAT_COOL: await self.tesla_device.set_status(True) From 99bc911f7ff9ec1af9036549957d38b6094a37b3 Mon Sep 17 00:00:00 2001 From: Aaron David Schneider Date: Wed, 1 Jan 2020 22:03:38 +0100 Subject: [PATCH 479/677] Add attributes to fritz device_tracker (#30350) * add attributes to device_tracker * fix pylint * requested changes * docstring updated --- homeassistant/components/fritz/device_tracker.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/homeassistant/components/fritz/device_tracker.py b/homeassistant/components/fritz/device_tracker.py index ab4deec96f7..d16fcbb3a1e 100644 --- a/homeassistant/components/fritz/device_tracker.py +++ b/homeassistant/components/fritz/device_tracker.py @@ -79,6 +79,14 @@ class FritzBoxScanner(DeviceScanner): return None return ret + def get_extra_attributes(self, device): + """Return the attributes (ip, mac) of the given device or None if is not known.""" + ip_device = self.fritz_box.get_specific_host_entry(device).get("NewIPAddress") + + if not ip_device: + return None + return {"ip": ip_device, "mac": device} + def _update_info(self): """Retrieve latest information from the FRITZ!Box.""" if not self.success_init: From 3a4db2fae7733025f6b52f4fc5acb77676cec1d7 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 2 Jan 2020 00:13:04 +0100 Subject: [PATCH 480/677] Migrate mailbox tests from coroutine to async/await (#30361) --- tests/components/mailbox/test_init.py | 51 +++++++++++---------------- 1 file changed, 21 insertions(+), 30 deletions(-) diff --git a/tests/components/mailbox/test_init.py b/tests/components/mailbox/test_init.py index ec398be0d4a..258e0cc7ebf 100644 --- a/tests/components/mailbox/test_init.py +++ b/tests/components/mailbox/test_init.py @@ -1,5 +1,4 @@ """The tests for the mailbox component.""" -import asyncio from hashlib import sha1 import pytest @@ -16,44 +15,40 @@ def mock_http_client(hass, hass_client): return hass.loop.run_until_complete(hass_client()) -@asyncio.coroutine -def test_get_platforms_from_mailbox(mock_http_client): +async def test_get_platforms_from_mailbox(mock_http_client): """Get platforms from mailbox.""" url = "/api/mailbox/platforms" - req = yield from mock_http_client.get(url) + req = await mock_http_client.get(url) assert req.status == 200 - result = yield from req.json() + result = await req.json() assert len(result) == 1 and "DemoMailbox" == result[0].get("name", None) -@asyncio.coroutine -def test_get_messages_from_mailbox(mock_http_client): +async def test_get_messages_from_mailbox(mock_http_client): """Get messages from mailbox.""" url = "/api/mailbox/messages/DemoMailbox" - req = yield from mock_http_client.get(url) + req = await mock_http_client.get(url) assert req.status == 200 - result = yield from req.json() + result = await req.json() assert len(result) == 10 -@asyncio.coroutine -def test_get_media_from_mailbox(mock_http_client): +async def test_get_media_from_mailbox(mock_http_client): """Get audio from mailbox.""" mp3sha = "3f67c4ea33b37d1710f772a26dd3fb43bb159d50" msgtxt = "Message 1. " "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " msgsha = sha1(msgtxt.encode("utf-8")).hexdigest() url = "/api/mailbox/media/DemoMailbox/%s" % (msgsha) - req = yield from mock_http_client.get(url) + req = await mock_http_client.get(url) assert req.status == 200 - data = yield from req.read() + data = await req.read() assert sha1(data).hexdigest() == mp3sha -@asyncio.coroutine -def test_delete_from_mailbox(mock_http_client): +async def test_delete_from_mailbox(mock_http_client): """Get audio from mailbox.""" msgtxt1 = "Message 1. " "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " msgtxt2 = "Message 3. " "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " @@ -62,50 +57,46 @@ def test_delete_from_mailbox(mock_http_client): for msg in [msgsha1, msgsha2]: url = "/api/mailbox/delete/DemoMailbox/%s" % (msg) - req = yield from mock_http_client.delete(url) + req = await mock_http_client.delete(url) assert req.status == 200 url = "/api/mailbox/messages/DemoMailbox" - req = yield from mock_http_client.get(url) + req = await mock_http_client.get(url) assert req.status == 200 - result = yield from req.json() + result = await req.json() assert len(result) == 8 -@asyncio.coroutine -def test_get_messages_from_invalid_mailbox(mock_http_client): +async def test_get_messages_from_invalid_mailbox(mock_http_client): """Get messages from mailbox.""" url = "/api/mailbox/messages/mailbox.invalid_mailbox" - req = yield from mock_http_client.get(url) + req = await mock_http_client.get(url) assert req.status == 404 -@asyncio.coroutine -def test_get_media_from_invalid_mailbox(mock_http_client): +async def test_get_media_from_invalid_mailbox(mock_http_client): """Get messages from mailbox.""" msgsha = "0000000000000000000000000000000000000000" url = "/api/mailbox/media/mailbox.invalid_mailbox/%s" % (msgsha) - req = yield from mock_http_client.get(url) + req = await mock_http_client.get(url) assert req.status == 404 -@asyncio.coroutine -def test_get_media_from_invalid_msgid(mock_http_client): +async def test_get_media_from_invalid_msgid(mock_http_client): """Get messages from mailbox.""" msgsha = "0000000000000000000000000000000000000000" url = "/api/mailbox/media/DemoMailbox/%s" % (msgsha) - req = yield from mock_http_client.get(url) + req = await mock_http_client.get(url) assert req.status == 500 -@asyncio.coroutine -def test_delete_from_invalid_mailbox(mock_http_client): +async def test_delete_from_invalid_mailbox(mock_http_client): """Get audio from mailbox.""" msgsha = "0000000000000000000000000000000000000000" url = "/api/mailbox/delete/mailbox.invalid_mailbox/%s" % (msgsha) - req = yield from mock_http_client.delete(url) + req = await mock_http_client.delete(url) assert req.status == 404 From 0fb5fbd85c3a0057ef537f57f5675fae29fae269 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 2 Jan 2020 00:13:43 +0100 Subject: [PATCH 481/677] Migrate hassio tests from coroutine to async/await (#30363) --- tests/components/hassio/test_http.py | 40 ++++++------- tests/components/hassio/test_init.py | 86 ++++++++++++---------------- 2 files changed, 56 insertions(+), 70 deletions(-) diff --git a/tests/components/hassio/test_http.py b/tests/components/hassio/test_http.py index 96d53f93c3a..52cb3232ca6 100644 --- a/tests/components/hassio/test_http.py +++ b/tests/components/hassio/test_http.py @@ -5,35 +5,32 @@ from unittest.mock import patch import pytest -@asyncio.coroutine -def test_forward_request(hassio_client, aioclient_mock): +async def test_forward_request(hassio_client, aioclient_mock): """Test fetching normal path.""" aioclient_mock.post("http://127.0.0.1/beer", text="response") - resp = yield from hassio_client.post("/api/hassio/beer") + resp = await hassio_client.post("/api/hassio/beer") # Check we got right response assert resp.status == 200 - body = yield from resp.text() + body = await resp.text() assert body == "response" # Check we forwarded command assert len(aioclient_mock.mock_calls) == 1 -@asyncio.coroutine @pytest.mark.parametrize( "build_type", ["supervisor/info", "homeassistant/update", "host/info"] ) -def test_auth_required_forward_request(hassio_noauth_client, build_type): +async def test_auth_required_forward_request(hassio_noauth_client, build_type): """Test auth required for normal request.""" - resp = yield from hassio_noauth_client.post("/api/hassio/{}".format(build_type)) + resp = await hassio_noauth_client.post("/api/hassio/{}".format(build_type)) # Check we got right response assert resp.status == 401 -@asyncio.coroutine @pytest.mark.parametrize( "build_type", [ @@ -45,61 +42,60 @@ def test_auth_required_forward_request(hassio_noauth_client, build_type): "app/app.js", ], ) -def test_forward_request_no_auth_for_panel(hassio_client, build_type, aioclient_mock): +async def test_forward_request_no_auth_for_panel( + hassio_client, build_type, aioclient_mock +): """Test no auth needed for .""" aioclient_mock.get("http://127.0.0.1/{}".format(build_type), text="response") - resp = yield from hassio_client.get("/api/hassio/{}".format(build_type)) + resp = await hassio_client.get("/api/hassio/{}".format(build_type)) # Check we got right response assert resp.status == 200 - body = yield from resp.text() + body = await resp.text() assert body == "response" # Check we forwarded command assert len(aioclient_mock.mock_calls) == 1 -@asyncio.coroutine -def test_forward_request_no_auth_for_logo(hassio_client, aioclient_mock): +async def test_forward_request_no_auth_for_logo(hassio_client, aioclient_mock): """Test no auth needed for .""" aioclient_mock.get("http://127.0.0.1/addons/bl_b392/logo", text="response") - resp = yield from hassio_client.get("/api/hassio/addons/bl_b392/logo") + resp = await hassio_client.get("/api/hassio/addons/bl_b392/logo") # Check we got right response assert resp.status == 200 - body = yield from resp.text() + body = await resp.text() assert body == "response" # Check we forwarded command assert len(aioclient_mock.mock_calls) == 1 -@asyncio.coroutine -def test_forward_log_request(hassio_client, aioclient_mock): +async def test_forward_log_request(hassio_client, aioclient_mock): """Test fetching normal log path doesn't remove ANSI color escape codes.""" aioclient_mock.get("http://127.0.0.1/beer/logs", text="\033[32mresponse\033[0m") - resp = yield from hassio_client.get("/api/hassio/beer/logs") + resp = await hassio_client.get("/api/hassio/beer/logs") # Check we got right response assert resp.status == 200 - body = yield from resp.text() + body = await resp.text() assert body == "\033[32mresponse\033[0m" # Check we forwarded command assert len(aioclient_mock.mock_calls) == 1 -@asyncio.coroutine -def test_bad_gateway_when_cannot_find_supervisor(hassio_client): +async def test_bad_gateway_when_cannot_find_supervisor(hassio_client): """Test we get a bad gateway error if we can't find supervisor.""" with patch( "homeassistant.components.hassio.http.async_timeout.timeout", side_effect=asyncio.TimeoutError, ): - resp = yield from hassio_client.get("/api/hassio/addons/test/info") + resp = await hassio_client.get("/api/hassio/addons/test/info") assert resp.status == 502 diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py index c1e3d7ab2bf..1e227f943ed 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -1,5 +1,4 @@ """The tests for the hassio component.""" -import asyncio import os from unittest.mock import Mock, patch @@ -30,11 +29,10 @@ def mock_all(aioclient_mock): ) -@asyncio.coroutine -def test_setup_api_ping(hass, aioclient_mock): +async 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", {}) + result = await async_setup_component(hass, "hassio", {}) assert result assert aioclient_mock.call_count == 5 @@ -68,11 +66,10 @@ async def test_setup_api_panel(hass, aioclient_mock): } -@asyncio.coroutine -def test_setup_api_push_api_data(hass, aioclient_mock): +async def test_setup_api_push_api_data(hass, aioclient_mock): """Test setup with API push.""" with patch.dict(os.environ, MOCK_ENVIRON): - result = yield from async_setup_component( + result = await async_setup_component( hass, "hassio", {"http": {"server_port": 9999}, "hassio": {}} ) assert result @@ -83,11 +80,10 @@ def test_setup_api_push_api_data(hass, aioclient_mock): assert aioclient_mock.mock_calls[1][2]["watchdog"] -@asyncio.coroutine -def test_setup_api_push_api_data_server_host(hass, aioclient_mock): +async def test_setup_api_push_api_data_server_host(hass, aioclient_mock): """Test setup with API push with active server host.""" with patch.dict(os.environ, MOCK_ENVIRON): - result = yield from async_setup_component( + result = await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, @@ -175,45 +171,41 @@ async def test_setup_core_push_timezone(hass, aioclient_mock): assert aioclient_mock.mock_calls[-1][2]["timezone"] == "America/New_York" -@asyncio.coroutine -def test_setup_hassio_no_additional_data(hass, aioclient_mock): +async def test_setup_hassio_no_additional_data(hass, aioclient_mock): """Test setup with API push default data.""" with patch.dict(os.environ, MOCK_ENVIRON), patch.dict( os.environ, {"HASSIO_TOKEN": "123456"} ): - result = yield from async_setup_component(hass, "hassio", {"hassio": {}}) + result = await async_setup_component(hass, "hassio", {"hassio": {}}) assert result assert aioclient_mock.call_count == 5 assert aioclient_mock.mock_calls[-1][3]["X-Hassio-Key"] == "123456" -@asyncio.coroutine -def test_fail_setup_without_environ_var(hass): +async def test_fail_setup_without_environ_var(hass): """Fail setup if no environ variable set.""" with patch.dict(os.environ, {}, clear=True): - result = yield from async_setup_component(hass, "hassio", {}) + result = await async_setup_component(hass, "hassio", {}) assert not result -@asyncio.coroutine -def test_warn_when_cannot_connect(hass, caplog): +async def test_warn_when_cannot_connect(hass, caplog): """Fail warn when we cannot connect.""" with patch.dict(os.environ, MOCK_ENVIRON), patch( "homeassistant.components.hassio.HassIO.is_connected", Mock(return_value=mock_coro(None)), ): - result = yield from async_setup_component(hass, "hassio", {}) + result = await async_setup_component(hass, "hassio", {}) assert result assert hass.components.hassio.is_hassio() assert "Not connected with Hass.io / system to busy!" in caplog.text -@asyncio.coroutine -def test_service_register(hassio_env, hass): +async def test_service_register(hassio_env, hass): """Check if service will be setup.""" - assert (yield from async_setup_component(hass, "hassio", {})) + assert await async_setup_component(hass, "hassio", {}) assert hass.services.has_service("hassio", "addon_start") assert hass.services.has_service("hassio", "addon_stop") assert hass.services.has_service("hassio", "addon_restart") @@ -227,10 +219,9 @@ def test_service_register(hassio_env, hass): assert hass.services.has_service("hassio", "restore_partial") -@asyncio.coroutine -def test_service_calls(hassio_env, hass, aioclient_mock): +async def test_service_calls(hassio_env, hass, aioclient_mock): """Call service and check the API calls behind that.""" - assert (yield from async_setup_component(hass, "hassio", {})) + assert await async_setup_component(hass, "hassio", {}) aioclient_mock.post("http://127.0.0.1/addons/test/start", json={"result": "ok"}) aioclient_mock.post("http://127.0.0.1/addons/test/stop", json={"result": "ok"}) @@ -247,30 +238,30 @@ def test_service_calls(hassio_env, hass, aioclient_mock): "http://127.0.0.1/snapshots/test/restore/partial", json={"result": "ok"} ) - yield from hass.services.async_call("hassio", "addon_start", {"addon": "test"}) - yield from hass.services.async_call("hassio", "addon_stop", {"addon": "test"}) - yield from hass.services.async_call("hassio", "addon_restart", {"addon": "test"}) - yield from hass.services.async_call( + await hass.services.async_call("hassio", "addon_start", {"addon": "test"}) + await hass.services.async_call("hassio", "addon_stop", {"addon": "test"}) + await hass.services.async_call("hassio", "addon_restart", {"addon": "test"}) + await hass.services.async_call( "hassio", "addon_stdin", {"addon": "test", "input": "test"} ) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert aioclient_mock.call_count == 7 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() + await hass.services.async_call("hassio", "host_shutdown", {}) + await hass.services.async_call("hassio", "host_reboot", {}) + await hass.async_block_till_done() assert aioclient_mock.call_count == 9 - yield from hass.services.async_call("hassio", "snapshot_full", {}) - yield from hass.services.async_call( + await hass.services.async_call("hassio", "snapshot_full", {}) + await hass.services.async_call( "hassio", "snapshot_partial", {"addons": ["test"], "folders": ["ssl"], "password": "123456"}, ) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert aioclient_mock.call_count == 11 assert aioclient_mock.mock_calls[-1][2] == { @@ -279,8 +270,8 @@ def test_service_calls(hassio_env, hass, aioclient_mock): "password": "123456", } - yield from hass.services.async_call("hassio", "restore_full", {"snapshot": "test"}) - yield from hass.services.async_call( + await hass.services.async_call("hassio", "restore_full", {"snapshot": "test"}) + await hass.services.async_call( "hassio", "restore_partial", { @@ -291,7 +282,7 @@ def test_service_calls(hassio_env, hass, aioclient_mock): "password": "123456", }, ) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert aioclient_mock.call_count == 13 assert aioclient_mock.mock_calls[-1][2] == { @@ -302,29 +293,28 @@ def test_service_calls(hassio_env, hass, aioclient_mock): } -@asyncio.coroutine -def test_service_calls_core(hassio_env, hass, aioclient_mock): +async def test_service_calls_core(hassio_env, hass, aioclient_mock): """Call core service and check the API calls behind that.""" - assert (yield from async_setup_component(hass, "hassio", {})) + assert await async_setup_component(hass, "hassio", {}) aioclient_mock.post("http://127.0.0.1/homeassistant/restart", json={"result": "ok"}) aioclient_mock.post("http://127.0.0.1/homeassistant/stop", json={"result": "ok"}) - yield from hass.services.async_call("homeassistant", "stop") - yield from hass.async_block_till_done() + await hass.services.async_call("homeassistant", "stop") + await hass.async_block_till_done() assert aioclient_mock.call_count == 4 - yield from hass.services.async_call("homeassistant", "check_config") - yield from hass.async_block_till_done() + await hass.services.async_call("homeassistant", "check_config") + await hass.async_block_till_done() assert aioclient_mock.call_count == 4 with patch( "homeassistant.config.async_check_ha_config_file", return_value=mock_coro() ) as mock_check_config: - yield from hass.services.async_call("homeassistant", "restart") - yield from hass.async_block_till_done() + await hass.services.async_call("homeassistant", "restart") + await hass.async_block_till_done() assert mock_check_config.called assert aioclient_mock.call_count == 5 From 4e7b35355dcbf0193804a58ed28af0ec585c5088 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 2 Jan 2020 00:15:29 +0100 Subject: [PATCH 482/677] Migrate websocket_api tests from coroutine to async/await (#30364) --- tests/components/websocket_api/test_init.py | 36 +++++++++------------ 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/tests/components/websocket_api/test_init.py b/tests/components/websocket_api/test_init.py index 7cda3200e7b..d32f55516aa 100644 --- a/tests/components/websocket_api/test_init.py +++ b/tests/components/websocket_api/test_init.py @@ -1,5 +1,4 @@ """Tests for the Home Assistant Websocket API.""" -import asyncio from unittest.mock import Mock, patch from aiohttp import WSMsgType @@ -16,12 +15,11 @@ def mock_low_queue(): yield -@asyncio.coroutine -def test_invalid_message_format(websocket_client): +async def test_invalid_message_format(websocket_client): """Test sending invalid JSON.""" - yield from websocket_client.send_json({"type": 5}) + await websocket_client.send_json({"type": 5}) - msg = yield from websocket_client.receive_json() + msg = await websocket_client.receive_json() assert msg["type"] == const.TYPE_RESULT error = msg["error"] @@ -29,42 +27,38 @@ def test_invalid_message_format(websocket_client): assert error["message"].startswith("Message incorrectly formatted") -@asyncio.coroutine -def test_invalid_json(websocket_client): +async def test_invalid_json(websocket_client): """Test sending invalid JSON.""" - yield from websocket_client.send_str("this is not JSON") + await websocket_client.send_str("this is not JSON") - msg = yield from websocket_client.receive() + msg = await websocket_client.receive() assert msg.type == WSMsgType.close -@asyncio.coroutine -def test_quiting_hass(hass, websocket_client): +async def test_quiting_hass(hass, websocket_client): """Test sending invalid JSON.""" with patch.object(hass.loop, "stop"): - yield from hass.async_stop() + await hass.async_stop() - msg = yield from websocket_client.receive() + msg = await websocket_client.receive() assert msg.type == WSMsgType.CLOSE -@asyncio.coroutine -def test_pending_msg_overflow(hass, mock_low_queue, websocket_client): +async def test_pending_msg_overflow(hass, mock_low_queue, websocket_client): """Test get_panels command.""" for idx in range(10): - yield from websocket_client.send_json({"id": idx + 1, "type": "ping"}) - msg = yield from websocket_client.receive() + await websocket_client.send_json({"id": idx + 1, "type": "ping"}) + msg = await websocket_client.receive() assert msg.type == WSMsgType.close -@asyncio.coroutine -def test_unknown_command(websocket_client): +async def test_unknown_command(websocket_client): """Test get_panels command.""" - yield from websocket_client.send_json({"id": 5, "type": "unknown_command"}) + await websocket_client.send_json({"id": 5, "type": "unknown_command"}) - msg = yield from websocket_client.receive_json() + msg = await websocket_client.receive_json() assert not msg["success"] assert msg["error"]["code"] == const.ERR_UNKNOWN_COMMAND From 320dc52bb3811621cbabbc95aae34a83bac3823e Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 2 Jan 2020 00:16:27 +0100 Subject: [PATCH 483/677] Migrate config tests from coroutine to async/await (#30366) --- .../components/config/test_config_entries.py | 85 +++++------- tests/components/config/test_customize.py | 43 +++---- tests/components/config/test_group.py | 52 ++++---- tests/components/config/test_init.py | 20 ++- tests/components/config/test_zwave.py | 121 ++++++++---------- 5 files changed, 133 insertions(+), 188 deletions(-) diff --git a/tests/components/config/test_config_entries.py b/tests/components/config/test_config_entries.py index 6631bbf8fbf..ccd41eeb3a5 100644 --- a/tests/components/config/test_config_entries.py +++ b/tests/components/config/test_config_entries.py @@ -1,6 +1,5 @@ """Test config entries API.""" -import asyncio from collections import OrderedDict from unittest.mock import patch @@ -94,16 +93,15 @@ async def test_get_entries(hass, client): ] -@asyncio.coroutine -def test_remove_entry(hass, client): +async def test_remove_entry(hass, client): """Test removing an entry via the API.""" entry = MockConfigEntry(domain="demo", state=core_ce.ENTRY_STATE_LOADED) entry.add_to_hass(hass) - resp = yield from client.delete( + resp = await client.delete( "/api/config/config_entries/entry/{}".format(entry.entry_id) ) assert resp.status == 200 - data = yield from resp.json() + data = await resp.json() assert data == {"require_restart": True} assert len(hass.config_entries.async_entries()) == 0 @@ -120,13 +118,12 @@ async def test_remove_entry_unauth(hass, client, hass_admin_user): assert len(hass.config_entries.async_entries()) == 1 -@asyncio.coroutine -def test_available_flows(hass, client): +async def test_available_flows(hass, client): """Test querying the available flows.""" with patch.object(config_flows, "FLOWS", ["hello", "world"]): - resp = yield from client.get("/api/config/config_entries/flow_handlers") + resp = await client.get("/api/config/config_entries/flow_handlers") assert resp.status == 200 - data = yield from resp.json() + data = await resp.json() assert set(data) == set(["hello", "world"]) @@ -135,14 +132,12 @@ def test_available_flows(hass, client): ############################ -@asyncio.coroutine -def test_initialize_flow(hass, client): +async def test_initialize_flow(hass, client): """Test we can initialize a flow.""" mock_entity_platform(hass, "config_flow.test", None) class TestFlow(core_ce.ConfigFlow): - @asyncio.coroutine - def async_step_user(self, user_input=None): + async def async_step_user(self, user_input=None): schema = OrderedDict() schema[vol.Required("username")] = str schema[vol.Required("password")] = str @@ -155,12 +150,12 @@ def test_initialize_flow(hass, client): ) with patch.dict(HANDLERS, {"test": TestFlow}): - resp = yield from client.post( + resp = await client.post( "/api/config/config_entries/flow", json={"handler": "test"} ) assert resp.status == 200 - data = yield from resp.json() + data = await resp.json() data.pop("flow_id") @@ -182,8 +177,7 @@ async def test_initialize_flow_unauth(hass, client, hass_admin_user): hass_admin_user.groups = [] class TestFlow(core_ce.ConfigFlow): - @asyncio.coroutine - def async_step_user(self, user_input=None): + async def async_step_user(self, user_input=None): schema = OrderedDict() schema[vol.Required("username")] = str schema[vol.Required("password")] = str @@ -203,23 +197,21 @@ async def test_initialize_flow_unauth(hass, client, hass_admin_user): assert resp.status == 401 -@asyncio.coroutine -def test_abort(hass, client): +async def test_abort(hass, client): """Test a flow that aborts.""" mock_entity_platform(hass, "config_flow.test", None) class TestFlow(core_ce.ConfigFlow): - @asyncio.coroutine - def async_step_user(self, user_input=None): + async def async_step_user(self, user_input=None): return self.async_abort(reason="bla") with patch.dict(HANDLERS, {"test": TestFlow}): - resp = yield from client.post( + resp = await client.post( "/api/config/config_entries/flow", json={"handler": "test"} ) assert resp.status == 200 - data = yield from resp.json() + data = await resp.json() data.pop("flow_id") assert data == { "description_placeholders": None, @@ -229,8 +221,7 @@ def test_abort(hass, client): } -@asyncio.coroutine -def test_create_account(hass, client): +async def test_create_account(hass, client): """Test a flow that creates an account.""" mock_entity_platform(hass, "config_flow.test", None) @@ -239,14 +230,13 @@ def test_create_account(hass, client): class TestFlow(core_ce.ConfigFlow): VERSION = 1 - @asyncio.coroutine - def async_step_user(self, user_input=None): + async def async_step_user(self, user_input=None): return self.async_create_entry( title="Test Entry", data={"secret": "account_token"} ) with patch.dict(HANDLERS, {"test": TestFlow}): - resp = yield from client.post( + resp = await client.post( "/api/config/config_entries/flow", json={"handler": "test"} ) @@ -255,7 +245,7 @@ def test_create_account(hass, client): entries = hass.config_entries.async_entries("test") assert len(entries) == 1 - data = yield from resp.json() + data = await resp.json() data.pop("flow_id") assert data == { "handler": "test", @@ -268,8 +258,7 @@ def test_create_account(hass, client): } -@asyncio.coroutine -def test_two_step_flow(hass, client): +async def test_two_step_flow(hass, client): """Test we can finish a two step flow.""" mock_integration(hass, MockModule("test", async_setup_entry=mock_coro_func(True))) mock_entity_platform(hass, "config_flow.test", None) @@ -277,24 +266,22 @@ def test_two_step_flow(hass, client): class TestFlow(core_ce.ConfigFlow): VERSION = 1 - @asyncio.coroutine - def async_step_user(self, user_input=None): + async def async_step_user(self, user_input=None): return self.async_show_form( step_id="account", data_schema=vol.Schema({"user_title": str}) ) - @asyncio.coroutine - def async_step_account(self, user_input=None): + async def async_step_account(self, user_input=None): return self.async_create_entry( title=user_input["user_title"], data={"secret": "account_token"} ) with patch.dict(HANDLERS, {"test": TestFlow}): - resp = yield from client.post( + resp = await client.post( "/api/config/config_entries/flow", json={"handler": "test"} ) assert resp.status == 200 - data = yield from resp.json() + data = await resp.json() flow_id = data.pop("flow_id") assert data == { "type": "form", @@ -306,7 +293,7 @@ def test_two_step_flow(hass, client): } with patch.dict(HANDLERS, {"test": TestFlow}): - resp = yield from client.post( + resp = await client.post( "/api/config/config_entries/flow/{}".format(flow_id), json={"user_title": "user-title"}, ) @@ -315,7 +302,7 @@ def test_two_step_flow(hass, client): entries = hass.config_entries.async_entries("test") assert len(entries) == 1 - data = yield from resp.json() + data = await resp.json() data.pop("flow_id") assert data == { "handler": "test", @@ -336,14 +323,12 @@ async def test_continue_flow_unauth(hass, client, hass_admin_user): class TestFlow(core_ce.ConfigFlow): VERSION = 1 - @asyncio.coroutine - def async_step_user(self, user_input=None): + async def async_step_user(self, user_input=None): return self.async_show_form( step_id="account", data_schema=vol.Schema({"user_title": str}) ) - @asyncio.coroutine - def async_step_account(self, user_input=None): + async def async_step_account(self, user_input=None): return self.async_create_entry( title=user_input["user_title"], data={"secret": "account_token"} ) @@ -415,14 +400,12 @@ async def test_get_progress_index_unauth(hass, hass_ws_client, hass_admin_user): assert response["error"]["code"] == "unauthorized" -@asyncio.coroutine -def test_get_progress_flow(hass, client): +async def test_get_progress_flow(hass, client): """Test we can query the API for same result as we get from init a flow.""" mock_entity_platform(hass, "config_flow.test", None) class TestFlow(core_ce.ConfigFlow): - @asyncio.coroutine - def async_step_user(self, user_input=None): + async def async_step_user(self, user_input=None): schema = OrderedDict() schema[vol.Required("username")] = str schema[vol.Required("password")] = str @@ -434,19 +417,19 @@ def test_get_progress_flow(hass, client): ) with patch.dict(HANDLERS, {"test": TestFlow}): - resp = yield from client.post( + resp = await client.post( "/api/config/config_entries/flow", json={"handler": "test"} ) assert resp.status == 200 - data = yield from resp.json() + data = await resp.json() - resp2 = yield from client.get( + resp2 = await client.get( "/api/config/config_entries/flow/{}".format(data["flow_id"]) ) assert resp2.status == 200 - data2 = yield from resp2.json() + data2 = await resp2.json() assert data == data2 diff --git a/tests/components/config/test_customize.py b/tests/components/config/test_customize.py index f6a678ce8f0..45c1f40d4ad 100644 --- a/tests/components/config/test_customize.py +++ b/tests/components/config/test_customize.py @@ -1,5 +1,4 @@ """Test Customize config panel.""" -import asyncio import json from unittest.mock import patch @@ -8,13 +7,12 @@ from homeassistant.components import config from homeassistant.config import DATA_CUSTOMIZE -@asyncio.coroutine -def test_get_entity(hass, hass_client): +async def test_get_entity(hass, hass_client): """Test getting entity.""" with patch.object(config, "SECTIONS", ["customize"]): - yield from async_setup_component(hass, "config", {}) + await async_setup_component(hass, "config", {}) - client = yield from hass_client() + client = await hass_client() def mock_read(path): """Mock reading data.""" @@ -22,21 +20,20 @@ def test_get_entity(hass, hass_client): hass.data[DATA_CUSTOMIZE] = {"hello.beer": {"cold": "beer"}} with patch("homeassistant.components.config._read", mock_read): - resp = yield from client.get("/api/config/customize/config/hello.beer") + resp = await client.get("/api/config/customize/config/hello.beer") assert resp.status == 200 - result = yield from resp.json() + result = await resp.json() assert result == {"local": {"free": "beer"}, "global": {"cold": "beer"}} -@asyncio.coroutine -def test_update_entity(hass, hass_client): +async def test_update_entity(hass, hass_client): """Test updating entity.""" with patch.object(config, "SECTIONS", ["customize"]): - yield from async_setup_component(hass, "config", {}) + await async_setup_component(hass, "config", {}) - client = yield from hass_client() + client = await hass_client() orig_data = { "hello.beer": {"ignored": True}, @@ -57,7 +54,7 @@ def test_update_entity(hass, hass_client): with patch("homeassistant.components.config._read", mock_read), patch( "homeassistant.components.config._write", mock_write ): - resp = yield from client.post( + resp = await client.post( "/api/config/customize/config/hello.world", data=json.dumps( {"name": "Beer", "entities": ["light.top", "light.bottom"]} @@ -65,7 +62,7 @@ def test_update_entity(hass, hass_client): ) assert resp.status == 200 - result = yield from resp.json() + result = await resp.json() assert result == {"result": "ok"} state = hass.states.get("hello.world") @@ -82,31 +79,27 @@ def test_update_entity(hass, hass_client): assert written[0] == orig_data -@asyncio.coroutine -def test_update_entity_invalid_key(hass, hass_client): +async def test_update_entity_invalid_key(hass, hass_client): """Test updating entity.""" with patch.object(config, "SECTIONS", ["customize"]): - yield from async_setup_component(hass, "config", {}) + await async_setup_component(hass, "config", {}) - client = yield from hass_client() + client = await hass_client() - resp = yield from client.post( + resp = await client.post( "/api/config/customize/config/not_entity", data=json.dumps({"name": "YO"}) ) assert resp.status == 400 -@asyncio.coroutine -def test_update_entity_invalid_json(hass, hass_client): +async def test_update_entity_invalid_json(hass, hass_client): """Test updating entity.""" with patch.object(config, "SECTIONS", ["customize"]): - yield from async_setup_component(hass, "config", {}) + await async_setup_component(hass, "config", {}) - client = yield from hass_client() + client = await hass_client() - resp = yield from client.post( - "/api/config/customize/config/hello.beer", data="not json" - ) + resp = await client.post("/api/config/customize/config/hello.beer", data="not json") assert resp.status == 400 diff --git a/tests/components/config/test_group.py b/tests/components/config/test_group.py index 3a4a145105a..1b79f30a5b6 100644 --- a/tests/components/config/test_group.py +++ b/tests/components/config/test_group.py @@ -1,5 +1,4 @@ """Test Group config panel.""" -import asyncio import json from unittest.mock import MagicMock, patch @@ -9,34 +8,32 @@ from homeassistant.components import config VIEW_NAME = "api:config:group:config" -@asyncio.coroutine -def test_get_device_config(hass, hass_client): +async def test_get_device_config(hass, hass_client): """Test getting device config.""" with patch.object(config, "SECTIONS", ["group"]): - yield from async_setup_component(hass, "config", {}) + await async_setup_component(hass, "config", {}) - client = yield from hass_client() + client = await hass_client() def mock_read(path): """Mock reading data.""" return {"hello.beer": {"free": "beer"}, "other.entity": {"do": "something"}} with patch("homeassistant.components.config._read", mock_read): - resp = yield from client.get("/api/config/group/config/hello.beer") + resp = await client.get("/api/config/group/config/hello.beer") assert resp.status == 200 - result = yield from resp.json() + result = await resp.json() assert result == {"free": "beer"} -@asyncio.coroutine -def test_update_device_config(hass, hass_client): +async def test_update_device_config(hass, hass_client): """Test updating device config.""" with patch.object(config, "SECTIONS", ["group"]): - yield from async_setup_component(hass, "config", {}) + await async_setup_component(hass, "config", {}) - client = yield from hass_client() + client = await hass_client() orig_data = { "hello.beer": {"ignored": True}, @@ -58,7 +55,7 @@ def test_update_device_config(hass, hass_client): with patch("homeassistant.components.config._read", mock_read), patch( "homeassistant.components.config._write", mock_write ), patch.object(hass.services, "async_call", mock_call): - resp = yield from client.post( + resp = await client.post( "/api/config/group/config/hello_beer", data=json.dumps( {"name": "Beer", "entities": ["light.top", "light.bottom"]} @@ -66,7 +63,7 @@ def test_update_device_config(hass, hass_client): ) assert resp.status == 200 - result = yield from resp.json() + result = await resp.json() assert result == {"result": "ok"} orig_data["hello_beer"]["name"] = "Beer" @@ -76,46 +73,41 @@ def test_update_device_config(hass, hass_client): mock_call.assert_called_once_with("group", "reload") -@asyncio.coroutine -def test_update_device_config_invalid_key(hass, hass_client): +async def test_update_device_config_invalid_key(hass, hass_client): """Test updating device config.""" with patch.object(config, "SECTIONS", ["group"]): - yield from async_setup_component(hass, "config", {}) + await async_setup_component(hass, "config", {}) - client = yield from hass_client() + client = await hass_client() - resp = yield from client.post( + resp = await client.post( "/api/config/group/config/not a slug", data=json.dumps({"name": "YO"}) ) assert resp.status == 400 -@asyncio.coroutine -def test_update_device_config_invalid_data(hass, hass_client): +async def test_update_device_config_invalid_data(hass, hass_client): """Test updating device config.""" with patch.object(config, "SECTIONS", ["group"]): - yield from async_setup_component(hass, "config", {}) + await async_setup_component(hass, "config", {}) - client = yield from hass_client() + client = await hass_client() - resp = yield from client.post( + resp = await client.post( "/api/config/group/config/hello_beer", data=json.dumps({"invalid_option": 2}) ) assert resp.status == 400 -@asyncio.coroutine -def test_update_device_config_invalid_json(hass, hass_client): +async def test_update_device_config_invalid_json(hass, hass_client): """Test updating device config.""" with patch.object(config, "SECTIONS", ["group"]): - yield from async_setup_component(hass, "config", {}) + await async_setup_component(hass, "config", {}) - client = yield from hass_client() + client = await hass_client() - resp = yield from client.post( - "/api/config/group/config/hello_beer", data="not json" - ) + resp = await client.post("/api/config/group/config/hello_beer", data="not json") assert resp.status == 400 diff --git a/tests/components/config/test_init.py b/tests/components/config/test_init.py index 21af45a1203..7f9b62d71f6 100644 --- a/tests/components/config/test_init.py +++ b/tests/components/config/test_init.py @@ -1,5 +1,4 @@ """Test config init.""" -import asyncio from unittest.mock import patch from homeassistant.components import config @@ -9,15 +8,13 @@ from homeassistant.setup import ATTR_COMPONENT, async_setup_component from tests.common import mock_component, mock_coro -@asyncio.coroutine -def test_config_setup(hass, loop): +async def test_config_setup(hass, loop): """Test it sets up hassbian.""" - yield from async_setup_component(hass, "config", {}) + await async_setup_component(hass, "config", {}) assert "config" in hass.config.components -@asyncio.coroutine -def test_load_on_demand_already_loaded(hass, aiohttp_client): +async def test_load_on_demand_already_loaded(hass, aiohttp_client): """Test getting suites.""" mock_component(hass, "zwave") @@ -26,25 +23,24 @@ def test_load_on_demand_already_loaded(hass, aiohttp_client): ), patch("homeassistant.components.config.zwave.async_setup") as stp: stp.return_value = mock_coro(True) - yield from async_setup_component(hass, "config", {}) + await async_setup_component(hass, "config", {}) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert stp.called -@asyncio.coroutine -def test_load_on_demand_on_load(hass, aiohttp_client): +async def test_load_on_demand_on_load(hass, aiohttp_client): """Test getting suites.""" with patch.object(config, "SECTIONS", []), patch.object( config, "ON_DEMAND", ["zwave"] ): - yield from async_setup_component(hass, "config", {}) + await async_setup_component(hass, "config", {}) assert "config.zwave" not in hass.config.components with patch("homeassistant.components.config.zwave.async_setup") as stp: stp.return_value = mock_coro(True) hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: "zwave"}) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert stp.called diff --git a/tests/components/config/test_zwave.py b/tests/components/config/test_zwave.py index c2490de23ea..267c57717f9 100644 --- a/tests/components/config/test_zwave.py +++ b/tests/components/config/test_zwave.py @@ -1,5 +1,4 @@ """Test Z-Wave config panel.""" -import asyncio import json from unittest.mock import MagicMock, patch @@ -23,8 +22,7 @@ def client(loop, hass, hass_client): return loop.run_until_complete(hass_client()) -@asyncio.coroutine -def test_get_device_config(client): +async def test_get_device_config(client): """Test getting device config.""" def mock_read(path): @@ -32,16 +30,15 @@ def test_get_device_config(client): return {"hello.beer": {"free": "beer"}, "other.entity": {"do": "something"}} with patch("homeassistant.components.config._read", mock_read): - resp = yield from client.get("/api/config/zwave/device_config/hello.beer") + resp = await client.get("/api/config/zwave/device_config/hello.beer") assert resp.status == 200 - result = yield from resp.json() + result = await resp.json() assert result == {"free": "beer"} -@asyncio.coroutine -def test_update_device_config(client): +async def test_update_device_config(client): """Test updating device config.""" orig_data = { "hello.beer": {"ignored": True}, @@ -61,13 +58,13 @@ def test_update_device_config(client): with patch("homeassistant.components.config._read", mock_read), patch( "homeassistant.components.config._write", mock_write ): - resp = yield from client.post( + resp = await client.post( "/api/config/zwave/device_config/hello.beer", data=json.dumps({"polling_intensity": 2}), ) assert resp.status == 200 - result = yield from resp.json() + result = await resp.json() assert result == {"result": "ok"} orig_data["hello.beer"]["polling_intensity"] = 2 @@ -75,10 +72,9 @@ def test_update_device_config(client): assert written[0] == orig_data -@asyncio.coroutine -def test_update_device_config_invalid_key(client): +async def test_update_device_config_invalid_key(client): """Test updating device config.""" - resp = yield from client.post( + resp = await client.post( "/api/config/zwave/device_config/invalid_entity", data=json.dumps({"polling_intensity": 2}), ) @@ -86,10 +82,9 @@ def test_update_device_config_invalid_key(client): assert resp.status == 400 -@asyncio.coroutine -def test_update_device_config_invalid_data(client): +async def test_update_device_config_invalid_data(client): """Test updating device config.""" - resp = yield from client.post( + resp = await client.post( "/api/config/zwave/device_config/hello.beer", data=json.dumps({"invalid_option": 2}), ) @@ -97,18 +92,16 @@ def test_update_device_config_invalid_data(client): assert resp.status == 400 -@asyncio.coroutine -def test_update_device_config_invalid_json(client): +async def test_update_device_config_invalid_json(client): """Test updating device config.""" - resp = yield from client.post( + resp = await client.post( "/api/config/zwave/device_config/hello.beer", data="not json" ) assert resp.status == 400 -@asyncio.coroutine -def test_get_values(hass, client): +async def test_get_values(hass, client): """Test getting values on node.""" node = MockNode(node_id=1) value = MockValue( @@ -125,10 +118,10 @@ def test_get_values(hass, client): values2 = MockEntityValues(primary=value2) hass.data[const.DATA_ENTITY_VALUES] = [values, values2] - resp = yield from client.get("/api/zwave/values/1") + resp = await client.get("/api/zwave/values/1") assert resp.status == 200 - result = yield from resp.json() + result = await resp.json() assert result == { "123456": { @@ -140,8 +133,7 @@ def test_get_values(hass, client): } -@asyncio.coroutine -def test_get_groups(hass, client): +async def test_get_groups(hass, client): """Test getting groupdata on node.""" network = hass.data[DATA_NETWORK] = MagicMock() node = MockNode(node_id=2) @@ -152,10 +144,10 @@ def test_get_groups(hass, client): node.groups = {1: node.groups} network.nodes = {2: node} - resp = yield from client.get("/api/zwave/groups/2") + resp = await client.get("/api/zwave/groups/2") assert resp.status == 200 - result = yield from resp.json() + result = await resp.json() assert result == { "1": { @@ -167,38 +159,35 @@ def test_get_groups(hass, client): } -@asyncio.coroutine -def test_get_groups_nogroups(hass, client): +async def test_get_groups_nogroups(hass, client): """Test getting groupdata on node with no groups.""" network = hass.data[DATA_NETWORK] = MagicMock() node = MockNode(node_id=2) network.nodes = {2: node} - resp = yield from client.get("/api/zwave/groups/2") + resp = await client.get("/api/zwave/groups/2") assert resp.status == 200 - result = yield from resp.json() + result = await resp.json() assert result == {} -@asyncio.coroutine -def test_get_groups_nonode(hass, client): +async def test_get_groups_nonode(hass, client): """Test getting groupdata on nonexisting node.""" network = hass.data[DATA_NETWORK] = MagicMock() network.nodes = {1: 1, 5: 5} - resp = yield from client.get("/api/zwave/groups/2") + resp = await client.get("/api/zwave/groups/2") assert resp.status == 404 - result = yield from resp.json() + result = await resp.json() assert result == {"message": "Node not found"} -@asyncio.coroutine -def test_get_config(hass, client): +async def test_get_config(hass, client): """Test getting config on node.""" network = hass.data[DATA_NETWORK] = MagicMock() node = MockNode(node_id=2) @@ -214,10 +203,10 @@ def test_get_config(hass, client): network.nodes = {2: node} node.get_values.return_value = node.values - resp = yield from client.get("/api/zwave/config/2") + resp = await client.get("/api/zwave/config/2") assert resp.status == 200 - result = yield from resp.json() + result = await resp.json() assert result == { "12": { @@ -232,8 +221,7 @@ def test_get_config(hass, client): } -@asyncio.coroutine -def test_get_config_noconfig_node(hass, client): +async def test_get_config_noconfig_node(hass, client): """Test getting config on node without config.""" network = hass.data[DATA_NETWORK] = MagicMock() node = MockNode(node_id=2) @@ -241,44 +229,41 @@ def test_get_config_noconfig_node(hass, client): network.nodes = {2: node} node.get_values.return_value = node.values - resp = yield from client.get("/api/zwave/config/2") + resp = await client.get("/api/zwave/config/2") assert resp.status == 200 - result = yield from resp.json() + result = await resp.json() assert result == {} -@asyncio.coroutine -def test_get_config_nonode(hass, client): +async def test_get_config_nonode(hass, client): """Test getting config on nonexisting node.""" network = hass.data[DATA_NETWORK] = MagicMock() network.nodes = {1: 1, 5: 5} - resp = yield from client.get("/api/zwave/config/2") + resp = await client.get("/api/zwave/config/2") assert resp.status == 404 - result = yield from resp.json() + result = await resp.json() assert result == {"message": "Node not found"} -@asyncio.coroutine -def test_get_usercodes_nonode(hass, client): +async def test_get_usercodes_nonode(hass, client): """Test getting usercodes on nonexisting node.""" network = hass.data[DATA_NETWORK] = MagicMock() network.nodes = {1: 1, 5: 5} - resp = yield from client.get("/api/zwave/usercodes/2") + resp = await client.get("/api/zwave/usercodes/2") assert resp.status == 404 - result = yield from resp.json() + result = await resp.json() assert result == {"message": "Node not found"} -@asyncio.coroutine -def test_get_usercodes(hass, client): +async def test_get_usercodes(hass, client): """Test getting usercodes on node.""" network = hass.data[DATA_NETWORK] = MagicMock() node = MockNode(node_id=18, command_classes=[const.COMMAND_CLASS_USER_CODE]) @@ -290,16 +275,15 @@ def test_get_usercodes(hass, client): network.nodes = {18: node} node.get_values.return_value = node.values - resp = yield from client.get("/api/zwave/usercodes/18") + resp = await client.get("/api/zwave/usercodes/18") assert resp.status == 200 - result = yield from resp.json() + result = await resp.json() assert result == {"0": {"code": "1234", "label": "label", "length": 4}} -@asyncio.coroutine -def test_get_usercode_nousercode_node(hass, client): +async def test_get_usercode_nousercode_node(hass, client): """Test getting usercodes on node without usercodes.""" network = hass.data[DATA_NETWORK] = MagicMock() node = MockNode(node_id=18) @@ -307,16 +291,15 @@ def test_get_usercode_nousercode_node(hass, client): network.nodes = {18: node} node.get_values.return_value = node.values - resp = yield from client.get("/api/zwave/usercodes/18") + resp = await client.get("/api/zwave/usercodes/18") assert resp.status == 200 - result = yield from resp.json() + result = await resp.json() assert result == {} -@asyncio.coroutine -def test_get_usercodes_no_genreuser(hass, client): +async def test_get_usercodes_no_genreuser(hass, client): """Test getting usercodes on node missing genre user.""" network = hass.data[DATA_NETWORK] = MagicMock() node = MockNode(node_id=18, command_classes=[const.COMMAND_CLASS_USER_CODE]) @@ -328,33 +311,31 @@ def test_get_usercodes_no_genreuser(hass, client): network.nodes = {18: node} node.get_values.return_value = node.values - resp = yield from client.get("/api/zwave/usercodes/18") + resp = await client.get("/api/zwave/usercodes/18") assert resp.status == 200 - result = yield from resp.json() + result = await resp.json() assert result == {} -@asyncio.coroutine -def test_save_config_no_network(hass, client): +async def test_save_config_no_network(hass, client): """Test saving configuration without network data.""" - resp = yield from client.post("/api/zwave/saveconfig") + resp = await client.post("/api/zwave/saveconfig") assert resp.status == 404 - result = yield from resp.json() + result = await resp.json() assert result == {"message": "No Z-Wave network data found"} -@asyncio.coroutine -def test_save_config(hass, client): +async def test_save_config(hass, client): """Test saving configuration.""" network = hass.data[DATA_NETWORK] = MagicMock() - resp = yield from client.post("/api/zwave/saveconfig") + resp = await client.post("/api/zwave/saveconfig") assert resp.status == 200 - result = yield from resp.json() + result = await resp.json() assert network.write_config.called assert result == {"message": "Z-Wave configuration saved to file."} From 1a2a976be2d796616be09af07c5414144a5d84c7 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 2 Jan 2020 00:17:55 +0100 Subject: [PATCH 484/677] Migrate counter tests from coroutine to async/await (#30368) --- tests/components/counter/test_init.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/tests/components/counter/test_init.py b/tests/components/counter/test_init.py index 35512129aed..3e85a080806 100644 --- a/tests/components/counter/test_init.py +++ b/tests/components/counter/test_init.py @@ -1,6 +1,5 @@ """The tests for the counter component.""" # pylint: disable=protected-access -import asyncio import logging from homeassistant.components.counter import ( @@ -143,8 +142,7 @@ async def test_methods_with_config(hass): assert 15 == int(state.state) -@asyncio.coroutine -def test_initial_state_overrules_restore_state(hass): +async def test_initial_state_overrules_restore_state(hass): """Ensure states are restored on startup.""" mock_restore_cache( hass, (State("counter.test1", "11"), State("counter.test2", "-22")) @@ -152,7 +150,7 @@ def test_initial_state_overrules_restore_state(hass): hass.state = CoreState.starting - yield from async_setup_component( + await async_setup_component( hass, DOMAIN, { @@ -172,8 +170,7 @@ def test_initial_state_overrules_restore_state(hass): assert int(state.state) == 10 -@asyncio.coroutine -def test_restore_state_overrules_initial_state(hass): +async def test_restore_state_overrules_initial_state(hass): """Ensure states are restored on startup.""" attr = {"initial": 6, "minimum": 1, "maximum": 8, "step": 2} @@ -189,7 +186,7 @@ def test_restore_state_overrules_initial_state(hass): hass.state = CoreState.starting - yield from async_setup_component( + await async_setup_component( hass, DOMAIN, {DOMAIN: {"test1": {}, "test2": {CONF_INITIAL: 10}, "test3": {}}} ) @@ -210,12 +207,11 @@ def test_restore_state_overrules_initial_state(hass): assert state.attributes.get("step") == 2 -@asyncio.coroutine -def test_no_initial_state_and_no_restore_state(hass): +async def test_no_initial_state_and_no_restore_state(hass): """Ensure that entity is create without initial and restore feature.""" hass.state = CoreState.starting - yield from async_setup_component(hass, DOMAIN, {DOMAIN: {"test1": {CONF_STEP: 5}}}) + await async_setup_component(hass, DOMAIN, {DOMAIN: {"test1": {CONF_STEP: 5}}}) state = hass.states.get("counter.test1") assert state From 47aa0043bf0246fb11db0002cd85aa86463b12a6 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 2 Jan 2020 00:18:20 +0100 Subject: [PATCH 485/677] Migrate owntracks tests from coroutine to async/await (#30369) --- tests/components/owntracks/test_init.py | 44 ++++++++++--------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/tests/components/owntracks/test_init.py b/tests/components/owntracks/test_init.py index 78ae687e208..e60efb42ee2 100644 --- a/tests/components/owntracks/test_init.py +++ b/tests/components/owntracks/test_init.py @@ -1,6 +1,4 @@ """Test the owntracks_http platform.""" -import asyncio - import pytest from homeassistant.components import owntracks @@ -55,10 +53,9 @@ def mock_client(hass, aiohttp_client): return hass.loop.run_until_complete(aiohttp_client(hass.http.app)) -@asyncio.coroutine -def test_handle_valid_message(mock_client): +async def test_handle_valid_message(mock_client): """Test that we forward messages correctly to OwnTracks.""" - resp = yield from mock_client.post( + resp = await mock_client.post( "/api/webhook/owntracks_test", json=LOCATION_MESSAGE, headers={"X-Limit-u": "Paulus", "X-Limit-d": "Pixel"}, @@ -66,14 +63,13 @@ def test_handle_valid_message(mock_client): assert resp.status == 200 - json = yield from resp.json() + json = await resp.json() assert json == [] -@asyncio.coroutine -def test_handle_valid_minimal_message(mock_client): +async def test_handle_valid_minimal_message(mock_client): """Test that we forward messages correctly to OwnTracks.""" - resp = yield from mock_client.post( + resp = await mock_client.post( "/api/webhook/owntracks_test", json=MINIMAL_LOCATION_MESSAGE, headers={"X-Limit-u": "Paulus", "X-Limit-d": "Pixel"}, @@ -81,14 +77,13 @@ def test_handle_valid_minimal_message(mock_client): assert resp.status == 200 - json = yield from resp.json() + json = await resp.json() assert json == [] -@asyncio.coroutine -def test_handle_value_error(mock_client): +async def test_handle_value_error(mock_client): """Test we don't disclose that this is a valid webhook.""" - resp = yield from mock_client.post( + resp = await mock_client.post( "/api/webhook/owntracks_test", json="", headers={"X-Limit-u": "Paulus", "X-Limit-d": "Pixel"}, @@ -96,14 +91,13 @@ def test_handle_value_error(mock_client): assert resp.status == 200 - json = yield from resp.text() + json = await resp.text() assert json == "" -@asyncio.coroutine -def test_returns_error_missing_username(mock_client, caplog): +async def test_returns_error_missing_username(mock_client, caplog): """Test that an error is returned when username is missing.""" - resp = yield from mock_client.post( + resp = await mock_client.post( "/api/webhook/owntracks_test", json=LOCATION_MESSAGE, headers={"X-Limit-d": "Pixel"}, @@ -111,29 +105,27 @@ def test_returns_error_missing_username(mock_client, caplog): # Needs to be 200 or OwnTracks keeps retrying bad packet. assert resp.status == 200 - json = yield from resp.json() + json = await resp.json() assert json == [] assert "No topic or user found" in caplog.text -@asyncio.coroutine -def test_returns_error_incorrect_json(mock_client, caplog): +async def test_returns_error_incorrect_json(mock_client, caplog): """Test that an error is returned when username is missing.""" - resp = yield from mock_client.post( + resp = await mock_client.post( "/api/webhook/owntracks_test", data="not json", headers={"X-Limit-d": "Pixel"} ) # Needs to be 200 or OwnTracks keeps retrying bad packet. assert resp.status == 200 - json = yield from resp.json() + json = await resp.json() assert json == [] assert "invalid JSON" in caplog.text -@asyncio.coroutine -def test_returns_error_missing_device(mock_client): +async def test_returns_error_missing_device(mock_client): """Test that an error is returned when device name is missing.""" - resp = yield from mock_client.post( + resp = await mock_client.post( "/api/webhook/owntracks_test", json=LOCATION_MESSAGE, headers={"X-Limit-u": "Paulus"}, @@ -141,7 +133,7 @@ def test_returns_error_missing_device(mock_client): assert resp.status == 200 - json = yield from resp.json() + json = await resp.json() assert json == [] From 8814e1eadcda8e7bd4a79a4ac1f34ac98aabd4b0 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 2 Jan 2020 00:18:41 +0100 Subject: [PATCH 486/677] Migrate no_ip tests from coroutine to async/await (#30370) --- tests/components/no_ip/test_init.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/tests/components/no_ip/test_init.py b/tests/components/no_ip/test_init.py index 50063ab3fff..c9b8d05906e 100644 --- a/tests/components/no_ip/test_init.py +++ b/tests/components/no_ip/test_init.py @@ -1,5 +1,4 @@ """Test the NO-IP component.""" -import asyncio from datetime import timedelta import pytest @@ -39,12 +38,11 @@ def setup_no_ip(hass, aioclient_mock): ) -@asyncio.coroutine -def test_setup(hass, aioclient_mock): +async def test_setup(hass, aioclient_mock): """Test setup works if update passes.""" aioclient_mock.get(UPDATE_URL, params={"hostname": DOMAIN}, text="nochg 0.0.0.0") - result = yield from async_setup_component( + result = await async_setup_component( hass, no_ip.DOMAIN, {no_ip.DOMAIN: {"domain": DOMAIN, "username": USERNAME, "password": PASSWORD}}, @@ -53,16 +51,15 @@ def test_setup(hass, aioclient_mock): assert aioclient_mock.call_count == 1 async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert aioclient_mock.call_count == 2 -@asyncio.coroutine -def test_setup_fails_if_update_fails(hass, aioclient_mock): +async def test_setup_fails_if_update_fails(hass, aioclient_mock): """Test setup fails if first update fails.""" aioclient_mock.get(UPDATE_URL, params={"hostname": DOMAIN}, text="nohost") - result = yield from async_setup_component( + result = await async_setup_component( hass, no_ip.DOMAIN, {no_ip.DOMAIN: {"domain": DOMAIN, "username": USERNAME, "password": PASSWORD}}, @@ -71,12 +68,11 @@ def test_setup_fails_if_update_fails(hass, aioclient_mock): assert aioclient_mock.call_count == 1 -@asyncio.coroutine -def test_setup_fails_if_wrong_auth(hass, aioclient_mock): +async def test_setup_fails_if_wrong_auth(hass, aioclient_mock): """Test setup fails if first update fails through wrong authentication.""" aioclient_mock.get(UPDATE_URL, params={"hostname": DOMAIN}, text="badauth") - result = yield from async_setup_component( + result = await async_setup_component( hass, no_ip.DOMAIN, {no_ip.DOMAIN: {"domain": DOMAIN, "username": USERNAME, "password": PASSWORD}}, From c837f185f71c6cd86a81696591183d49fd1aa2f5 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 2 Jan 2020 00:21:21 +0100 Subject: [PATCH 487/677] Migrate camera tests from coroutine to async/await (#30372) --- tests/components/camera/test_init.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/components/camera/test_init.py b/tests/components/camera/test_init.py index 89a19d0458a..4bd13c35ad8 100644 --- a/tests/components/camera/test_init.py +++ b/tests/components/camera/test_init.py @@ -147,8 +147,7 @@ class TestGetImage: ).result() -@asyncio.coroutine -def test_snapshot_service(hass, mock_camera): +async def test_snapshot_service(hass, mock_camera): """Test snapshot service.""" mopen = mock_open() @@ -156,7 +155,7 @@ def test_snapshot_service(hass, mock_camera): "homeassistant.components.camera.open", mopen, create=True ), patch.object(hass.config, "is_allowed_path", return_value=True): common.async_snapshot(hass, "/tmp/bla") - yield from hass.async_block_till_done() + await hass.async_block_till_done() mock_write = mopen().write From c5298dc4dcdb78ef5d5116763a317c4bba2f103b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 2 Jan 2020 00:21:42 +0100 Subject: [PATCH 488/677] Migrate cast tests from coroutine to async/await (#30374) --- tests/components/cast/test_media_player.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index fd565ab59d9..93df75db8ba 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -1,6 +1,5 @@ """The tests for the Cast Media player platform.""" # pylint: disable=protected-access -import asyncio from typing import Optional from unittest.mock import MagicMock, Mock, patch from uuid import UUID @@ -124,23 +123,21 @@ async def async_setup_media_player_cast(hass: HomeAssistantType, info: Chromecas return chromecast, entity -@asyncio.coroutine -def test_start_discovery_called_once(hass): +async def test_start_discovery_called_once(hass): """Test pychromecast.start_discovery called exactly once.""" with patch( "homeassistant.components.cast.discovery.pychromecast.start_discovery", return_value=(None, None), ) as start_discovery: - yield from async_setup_cast(hass) + await async_setup_cast(hass) assert start_discovery.call_count == 1 - yield from async_setup_cast(hass) + await async_setup_cast(hass) assert start_discovery.call_count == 1 -@asyncio.coroutine -def test_stop_discovery_called_on_stop(hass): +async def test_stop_discovery_called_on_stop(hass): """Test pychromecast.stop_discovery called on shutdown.""" browser = MagicMock(zc={}) @@ -149,7 +146,7 @@ def test_stop_discovery_called_on_stop(hass): return_value=(None, browser), ) as start_discovery: # start_discovery should be called with empty config - yield from async_setup_cast(hass, {}) + await async_setup_cast(hass, {}) assert start_discovery.call_count == 1 @@ -158,7 +155,7 @@ def test_stop_discovery_called_on_stop(hass): ) as stop_discovery: # stop discovery should be called on shutdown hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) - yield from hass.async_block_till_done() + await hass.async_block_till_done() stop_discovery.assert_called_once_with(browser) @@ -167,7 +164,7 @@ def test_stop_discovery_called_on_stop(hass): return_value=(None, browser), ) as start_discovery: # start_discovery should be called again on re-startup - yield from async_setup_cast(hass) + await async_setup_cast(hass) assert start_discovery.call_count == 1 From b43b50b6d23e59541088e5dace5ac95481aa385d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 2 Jan 2020 00:22:08 +0100 Subject: [PATCH 489/677] Migrate ffmpeg tests from coroutine to async/await (#30375) --- tests/components/ffmpeg/test_init.py | 57 ++++++++++++---------------- 1 file changed, 24 insertions(+), 33 deletions(-) diff --git a/tests/components/ffmpeg/test_init.py b/tests/components/ffmpeg/test_init.py index 1b21f07cd36..3c6a2fbb92d 100644 --- a/tests/components/ffmpeg/test_init.py +++ b/tests/components/ffmpeg/test_init.py @@ -1,5 +1,4 @@ """The tests for Home Assistant ffmpeg.""" -import asyncio from unittest.mock import MagicMock import homeassistant.components.ffmpeg as ffmpeg @@ -61,14 +60,12 @@ class MockFFmpegDev(ffmpeg.FFmpegBase): self.called_restart = False self.called_entities = None - @asyncio.coroutine - def _async_start_ffmpeg(self, entity_ids): + async def _async_start_ffmpeg(self, entity_ids): """Mock start.""" self.called_start = True self.called_entities = entity_ids - @asyncio.coroutine - def _async_stop_ffmpeg(self, entity_ids): + async def _async_stop_ffmpeg(self, entity_ids): """Mock stop.""" self.called_stop = True self.called_entities = entity_ids @@ -102,91 +99,85 @@ class TestFFmpegSetup: assert self.hass.services.has_service(ffmpeg.DOMAIN, "restart") -@asyncio.coroutine -def test_setup_component_test_register(hass): +async def test_setup_component_test_register(hass): """Set up ffmpeg component test register.""" with assert_setup_component(1): - yield from async_setup_component(hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}}) + await async_setup_component(hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}}) hass.bus.async_listen_once = MagicMock() ffmpeg_dev = MockFFmpegDev(hass) - yield from ffmpeg_dev.async_added_to_hass() + await ffmpeg_dev.async_added_to_hass() assert hass.bus.async_listen_once.called assert hass.bus.async_listen_once.call_count == 2 -@asyncio.coroutine -def test_setup_component_test_register_no_startup(hass): +async def test_setup_component_test_register_no_startup(hass): """Set up ffmpeg component test register without startup.""" with assert_setup_component(1): - yield from async_setup_component(hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}}) + await async_setup_component(hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}}) hass.bus.async_listen_once = MagicMock() ffmpeg_dev = MockFFmpegDev(hass, False) - yield from ffmpeg_dev.async_added_to_hass() + await ffmpeg_dev.async_added_to_hass() assert hass.bus.async_listen_once.called assert hass.bus.async_listen_once.call_count == 1 -@asyncio.coroutine -def test_setup_component_test_service_start(hass): +async def test_setup_component_test_service_start(hass): """Set up ffmpeg component test service start.""" with assert_setup_component(1): - yield from async_setup_component(hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}}) + await async_setup_component(hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}}) ffmpeg_dev = MockFFmpegDev(hass, False) - yield from ffmpeg_dev.async_added_to_hass() + await ffmpeg_dev.async_added_to_hass() async_start(hass) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert ffmpeg_dev.called_start -@asyncio.coroutine -def test_setup_component_test_service_stop(hass): +async def test_setup_component_test_service_stop(hass): """Set up ffmpeg component test service stop.""" with assert_setup_component(1): - yield from async_setup_component(hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}}) + await async_setup_component(hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}}) ffmpeg_dev = MockFFmpegDev(hass, False) - yield from ffmpeg_dev.async_added_to_hass() + await ffmpeg_dev.async_added_to_hass() async_stop(hass) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert ffmpeg_dev.called_stop -@asyncio.coroutine -def test_setup_component_test_service_restart(hass): +async def test_setup_component_test_service_restart(hass): """Set up ffmpeg component test service restart.""" with assert_setup_component(1): - yield from async_setup_component(hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}}) + await async_setup_component(hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}}) ffmpeg_dev = MockFFmpegDev(hass, False) - yield from ffmpeg_dev.async_added_to_hass() + await ffmpeg_dev.async_added_to_hass() async_restart(hass) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert ffmpeg_dev.called_stop assert ffmpeg_dev.called_start -@asyncio.coroutine -def test_setup_component_test_service_start_with_entity(hass): +async def test_setup_component_test_service_start_with_entity(hass): """Set up ffmpeg component test service start.""" with assert_setup_component(1): - yield from async_setup_component(hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}}) + await async_setup_component(hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}}) ffmpeg_dev = MockFFmpegDev(hass, False) - yield from ffmpeg_dev.async_added_to_hass() + await ffmpeg_dev.async_added_to_hass() async_start(hass, "test.ffmpeg_device") - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert ffmpeg_dev.called_start assert ffmpeg_dev.called_entities == ["test.ffmpeg_device"] From bcb47dab454fae19d7d1c8a29f25042c9685bba0 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 2 Jan 2020 00:22:32 +0100 Subject: [PATCH 490/677] Migrate discovery tests from coroutine to async/await (#30376) --- tests/components/discovery/test_init.py | 28 +++++++++---------------- 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/tests/components/discovery/test_init.py b/tests/components/discovery/test_init.py index de63a0bf495..1d11bba9e16 100644 --- a/tests/components/discovery/test_init.py +++ b/tests/components/discovery/test_init.py @@ -1,5 +1,4 @@ """The tests for the discovery component.""" -import asyncio from unittest.mock import MagicMock, patch import pytest @@ -55,29 +54,27 @@ async def mock_discovery(hass, discoveries, config=BASE_CONFIG): return mock_discover, mock_platform -@asyncio.coroutine -def test_unknown_service(hass): +async def test_unknown_service(hass): """Test that unknown service is ignored.""" def discover(netdisco): """Fake discovery.""" return [("this_service_will_never_be_supported", {"info": "some"})] - mock_discover, mock_platform = yield from mock_discovery(hass, discover) + mock_discover, mock_platform = await mock_discovery(hass, discover) assert not mock_discover.called assert not mock_platform.called -@asyncio.coroutine -def test_load_platform(hass): +async def test_load_platform(hass): """Test load a platform.""" def discover(netdisco): """Fake discovery.""" return [(SERVICE, SERVICE_INFO)] - mock_discover, mock_platform = yield from mock_discovery(hass, discover) + mock_discover, mock_platform = await mock_discovery(hass, discover) assert not mock_discover.called assert mock_platform.called @@ -86,15 +83,14 @@ def test_load_platform(hass): ) -@asyncio.coroutine -def test_load_component(hass): +async def test_load_component(hass): """Test load a component.""" def discover(netdisco): """Fake discovery.""" return [(SERVICE_NO_PLATFORM, SERVICE_INFO)] - mock_discover, mock_platform = yield from mock_discovery(hass, discover) + mock_discover, mock_platform = await mock_discovery(hass, discover) assert mock_discover.called assert not mock_platform.called @@ -107,24 +103,20 @@ def test_load_component(hass): ) -@asyncio.coroutine -def test_ignore_service(hass): +async def test_ignore_service(hass): """Test ignore service.""" def discover(netdisco): """Fake discovery.""" return [(SERVICE_NO_PLATFORM, SERVICE_INFO)] - mock_discover, mock_platform = yield from mock_discovery( - hass, discover, IGNORE_CONFIG - ) + mock_discover, mock_platform = await mock_discovery(hass, discover, IGNORE_CONFIG) assert not mock_discover.called assert not mock_platform.called -@asyncio.coroutine -def test_discover_duplicates(hass): +async def test_discover_duplicates(hass): """Test load a component.""" def discover(netdisco): @@ -134,7 +126,7 @@ def test_discover_duplicates(hass): (SERVICE_NO_PLATFORM, SERVICE_INFO), ] - mock_discover, mock_platform = yield from mock_discovery(hass, discover) + mock_discover, mock_platform = await mock_discovery(hass, discover) assert mock_discover.called assert mock_discover.call_count == 1 From 37d1771400c4ab8c23eace4532a48030995323e5 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 2 Jan 2020 00:24:30 +0100 Subject: [PATCH 491/677] Migrate google_* tests from coroutine to async/await (#30377) --- .../google_assistant/test_google_assistant.py | 36 ++++++++----------- .../components/google_assistant/test_init.py | 9 ++--- tests/components/google_domains/test_init.py | 13 +++---- 3 files changed, 23 insertions(+), 35 deletions(-) diff --git a/tests/components/google_assistant/test_google_assistant.py b/tests/components/google_assistant/test_google_assistant.py index d1d58422884..3be97013e4d 100644 --- a/tests/components/google_assistant/test_google_assistant.py +++ b/tests/components/google_assistant/test_google_assistant.py @@ -1,6 +1,5 @@ """The tests for the Google Assistant component.""" # pylint: disable=protected-access -import asyncio import json from aiohttp.hdrs import AUTHORIZATION @@ -115,18 +114,17 @@ def hass_fixture(loop, hass): # pylint: disable=redefined-outer-name -@asyncio.coroutine -def test_sync_request(hass_fixture, assistant_client, auth_header): +async def test_sync_request(hass_fixture, assistant_client, auth_header): """Test a sync request.""" reqid = "5711642932632160983" data = {"requestId": reqid, "inputs": [{"intent": "action.devices.SYNC"}]} - result = yield from assistant_client.post( + result = await assistant_client.post( ga.const.GOOGLE_ASSISTANT_API_ENDPOINT, data=json.dumps(data), headers=auth_header, ) assert result.status == 200 - body = yield from result.json() + body = await result.json() assert body.get("requestId") == reqid devices = body["payload"]["devices"] assert sorted([dev["id"] for dev in devices]) == sorted( @@ -145,8 +143,7 @@ def test_sync_request(hass_fixture, assistant_client, auth_header): assert dev["type"] == demo["type"] -@asyncio.coroutine -def test_query_request(hass_fixture, assistant_client, auth_header): +async def test_query_request(hass_fixture, assistant_client, auth_header): """Test a query request.""" reqid = "5711642932632160984" data = { @@ -165,13 +162,13 @@ def test_query_request(hass_fixture, assistant_client, auth_header): } ], } - result = yield from assistant_client.post( + result = await assistant_client.post( ga.const.GOOGLE_ASSISTANT_API_ENDPOINT, data=json.dumps(data), headers=auth_header, ) assert result.status == 200 - body = yield from result.json() + body = await result.json() assert body.get("requestId") == reqid devices = body["payload"]["devices"] assert len(devices) == 4 @@ -187,8 +184,7 @@ def test_query_request(hass_fixture, assistant_client, auth_header): assert devices["media_player.lounge_room"]["on"] is True -@asyncio.coroutine -def test_query_climate_request(hass_fixture, assistant_client, auth_header): +async def test_query_climate_request(hass_fixture, assistant_client, auth_header): """Test a query request.""" reqid = "5711642932632160984" data = { @@ -206,13 +202,13 @@ def test_query_climate_request(hass_fixture, assistant_client, auth_header): } ], } - result = yield from assistant_client.post( + result = await assistant_client.post( ga.const.GOOGLE_ASSISTANT_API_ENDPOINT, data=json.dumps(data), headers=auth_header, ) assert result.status == 200 - body = yield from result.json() + body = await result.json() assert body.get("requestId") == reqid devices = body["payload"]["devices"] assert len(devices) == 3 @@ -238,8 +234,7 @@ def test_query_climate_request(hass_fixture, assistant_client, auth_header): } -@asyncio.coroutine -def test_query_climate_request_f(hass_fixture, assistant_client, auth_header): +async def test_query_climate_request_f(hass_fixture, assistant_client, auth_header): """Test a query request.""" # Mock demo devices as fahrenheit to see if we convert to celsius hass_fixture.config.units.temperature_unit = const.TEMP_FAHRENHEIT @@ -264,13 +259,13 @@ def test_query_climate_request_f(hass_fixture, assistant_client, auth_header): } ], } - result = yield from assistant_client.post( + result = await assistant_client.post( ga.const.GOOGLE_ASSISTANT_API_ENDPOINT, data=json.dumps(data), headers=auth_header, ) assert result.status == 200 - body = yield from result.json() + body = await result.json() assert body.get("requestId") == reqid devices = body["payload"]["devices"] assert len(devices) == 3 @@ -297,8 +292,7 @@ def test_query_climate_request_f(hass_fixture, assistant_client, auth_header): hass_fixture.config.units.temperature_unit = const.TEMP_CELSIUS -@asyncio.coroutine -def test_execute_request(hass_fixture, assistant_client, auth_header): +async def test_execute_request(hass_fixture, assistant_client, auth_header): """Test an execute request.""" reqid = "5711642932632160985" data = { @@ -357,13 +351,13 @@ def test_execute_request(hass_fixture, assistant_client, auth_header): } ], } - result = yield from assistant_client.post( + result = await assistant_client.post( ga.const.GOOGLE_ASSISTANT_API_ENDPOINT, data=json.dumps(data), headers=auth_header, ) assert result.status == 200 - body = yield from result.json() + body = await result.json() assert body.get("requestId") == reqid commands = body["payload"]["commands"] assert len(commands) == 6 diff --git a/tests/components/google_assistant/test_init.py b/tests/components/google_assistant/test_init.py index 7c5d14ae6d7..2773f3c3329 100644 --- a/tests/components/google_assistant/test_init.py +++ b/tests/components/google_assistant/test_init.py @@ -1,6 +1,4 @@ """The tests for google-assistant init.""" -import asyncio - from homeassistant.components import google_assistant as ga from homeassistant.core import Context from homeassistant.setup import async_setup_component @@ -8,19 +6,18 @@ from homeassistant.setup import async_setup_component GA_API_KEY = "Agdgjsj399sdfkosd932ksd" -@asyncio.coroutine -def test_request_sync_service(aioclient_mock, hass): +async def test_request_sync_service(aioclient_mock, hass): """Test that it posts to the request_sync url.""" aioclient_mock.post(ga.const.REQUEST_SYNC_BASE_URL, status=200) - yield from async_setup_component( + await async_setup_component( hass, "google_assistant", {"google_assistant": {"project_id": "test_project", "api_key": GA_API_KEY}}, ) assert aioclient_mock.call_count == 0 - yield from hass.services.async_call( + await hass.services.async_call( ga.const.DOMAIN, ga.const.SERVICE_REQUEST_SYNC, blocking=True, diff --git a/tests/components/google_domains/test_init.py b/tests/components/google_domains/test_init.py index 80844063b00..66e334d342f 100644 --- a/tests/components/google_domains/test_init.py +++ b/tests/components/google_domains/test_init.py @@ -1,5 +1,4 @@ """Test the Google Domains component.""" -import asyncio from datetime import timedelta import pytest @@ -37,12 +36,11 @@ def setup_google_domains(hass, aioclient_mock): ) -@asyncio.coroutine -def test_setup(hass, aioclient_mock): +async def test_setup(hass, aioclient_mock): """Test setup works if update passes.""" aioclient_mock.get(UPDATE_URL, params={"hostname": DOMAIN}, text="nochg 0.0.0.0") - result = yield from async_setup_component( + result = await async_setup_component( hass, google_domains.DOMAIN, { @@ -57,16 +55,15 @@ def test_setup(hass, aioclient_mock): assert aioclient_mock.call_count == 1 async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert aioclient_mock.call_count == 2 -@asyncio.coroutine -def test_setup_fails_if_update_fails(hass, aioclient_mock): +async def test_setup_fails_if_update_fails(hass, aioclient_mock): """Test setup fails if first update fails.""" aioclient_mock.get(UPDATE_URL, params={"hostname": DOMAIN}, text="nohost") - result = yield from async_setup_component( + result = await async_setup_component( hass, google_domains.DOMAIN, { From fc08c62a31d29a6cff73b038b03a1e2d664bcacd Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Wed, 1 Jan 2020 15:36:14 -0800 Subject: [PATCH 492/677] Add charging rate sensor to Tesla (#30286) * Add charging rate sensor to Tesla * Remove reference to bin_type --- homeassistant/components/tesla/sensor.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/homeassistant/components/tesla/sensor.py b/homeassistant/components/tesla/sensor.py index 78e1106ed43..a282f65f9e1 100644 --- a/homeassistant/components/tesla/sensor.py +++ b/homeassistant/components/tesla/sensor.py @@ -88,6 +88,16 @@ class TeslaSensor(TeslaDevice, Entity): self.current_value = round( convert(self.current_value, LENGTH_MILES, LENGTH_KILOMETERS), 2 ) + elif self.tesla_device.type == "charging rate sensor": + self.current_value = self.tesla_device.charging_rate + self.units = units + self._attributes = { + "time_left": self.tesla_device.time_left, + "added_range": self.tesla_device.added_range, + "charge_current_request": self.tesla_device.charge_current_request, + "charger_actual_current": self.tesla_device.charger_actual_current, + "charger_voltage": self.tesla_device.charger_voltage, + } else: self.current_value = self.tesla_device.get_value() self.units = units From 769cf19052f8c9ef374d8ba8ae7705ccc7bf4cf4 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Thu, 2 Jan 2020 00:32:24 +0000 Subject: [PATCH 493/677] [ci skip] Translation update --- .../components/gios/.translations/ca.json | 20 +++++++++++++++++++ .../components/gios/.translations/da.json | 20 +++++++++++++++++++ .../components/gios/.translations/ko.json | 20 +++++++++++++++++++ .../components/local_ip/.translations/ca.json | 16 +++++++++++++++ .../components/local_ip/.translations/da.json | 16 +++++++++++++++ .../components/local_ip/.translations/ko.json | 16 +++++++++++++++ 6 files changed, 108 insertions(+) create mode 100644 homeassistant/components/gios/.translations/ca.json create mode 100644 homeassistant/components/gios/.translations/da.json create mode 100644 homeassistant/components/gios/.translations/ko.json create mode 100644 homeassistant/components/local_ip/.translations/ca.json create mode 100644 homeassistant/components/local_ip/.translations/da.json create mode 100644 homeassistant/components/local_ip/.translations/ko.json diff --git a/homeassistant/components/gios/.translations/ca.json b/homeassistant/components/gios/.translations/ca.json new file mode 100644 index 00000000000..80fedcafdd9 --- /dev/null +++ b/homeassistant/components/gios/.translations/ca.json @@ -0,0 +1,20 @@ +{ + "config": { + "error": { + "cannot_connect": "No s'ha pogut connectar al servidor de GIO\u015a.", + "invalid_sensors_data": "Les dades dels sensors d'aquesta estaci\u00f3 de mesura s\u00f3n inv\u00e0lides.", + "wrong_station_id": "L'ID de l'estaci\u00f3 de mesura \u00e9s incorrecte." + }, + "step": { + "user": { + "data": { + "name": "Nom de la integraci\u00f3", + "station_id": "ID de l'estaci\u00f3 de mesura" + }, + "description": "Integraci\u00f3 de mesura de qualitat de l\u2019aire GIO\u015a (Polish Chief Inspectorate Of Environmental Protection). Si necessites ajuda amb la configuraci\u00f3, fes un cop d'ull a: https://www.home-assistant.io/integrations/gios", + "title": "GIO\u015a (Polish Chief Inspectorate Of Environmental Protection)" + } + }, + "title": "GIO\u015a" + } +} \ No newline at end of file diff --git a/homeassistant/components/gios/.translations/da.json b/homeassistant/components/gios/.translations/da.json new file mode 100644 index 00000000000..b4855da7951 --- /dev/null +++ b/homeassistant/components/gios/.translations/da.json @@ -0,0 +1,20 @@ +{ + "config": { + "error": { + "cannot_connect": "Kan ikke oprette forbindelse til GIO\u015a-serveren.", + "invalid_sensors_data": "Ugyldige sensordata for denne m\u00e5lestation.", + "wrong_station_id": "M\u00e5lestationens ID er ikke korrekt." + }, + "step": { + "user": { + "data": { + "name": "Navn p\u00e5 integrationen", + "station_id": "ID for m\u00e5lestationen" + }, + "description": "Ops\u00e6t GIO\u015a (polsk inspektorat for milj\u00f8beskyttelse) luftkvalitet-integration. Hvis du har brug for hj\u00e6lp med konfigurationen, kig her: https://www.home-assistant.io/integrations/gios", + "title": "GIO\u015a (Polish Chief Inspectorate Of Environmental Protection)" + } + }, + "title": "GIO\u015a" + } +} \ No newline at end of file diff --git a/homeassistant/components/gios/.translations/ko.json b/homeassistant/components/gios/.translations/ko.json new file mode 100644 index 00000000000..6fb37205502 --- /dev/null +++ b/homeassistant/components/gios/.translations/ko.json @@ -0,0 +1,20 @@ +{ + "config": { + "error": { + "cannot_connect": "GIO\u015a \uc11c\ubc84\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", + "invalid_sensors_data": "\uc774 \uce21\uc815 \uc2a4\ud14c\uc774\uc158\uc5d0 \ub300\ud55c \uc13c\uc11c \ub370\uc774\ud130\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "wrong_station_id": "\uce21\uc815 \uc2a4\ud14c\uc774\uc158\uc758 ID \uac00 \uc62c\ubc14\ub974\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." + }, + "step": { + "user": { + "data": { + "name": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uc758 \uc774\ub984", + "station_id": "\uce21\uc815 \uc2a4\ud14c\uc774\uc158\uc758 ID" + }, + "description": "GIO\u015a (\ud3f4\ub780\ub4dc \ud658\uacbd \ubcf4\ud638\uccad) \ub300\uae30\uc9c8 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4. \uad6c\uc131\uc5d0 \ub3c4\uc6c0\uc774 \ud544\uc694\ud55c \uacbd\uc6b0 https://www.home-assistant.io/integrations/gios \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694", + "title": "\ud3f4\ub780\ub4dc \ud658\uacbd\uccad (GIO\u015a)" + } + }, + "title": "GIO\u015a" + } +} \ No newline at end of file diff --git a/homeassistant/components/local_ip/.translations/ca.json b/homeassistant/components/local_ip/.translations/ca.json new file mode 100644 index 00000000000..b2b7ee89c16 --- /dev/null +++ b/homeassistant/components/local_ip/.translations/ca.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "Integraci\u00f3 ja configurada amb un sensor amb aquest nom" + }, + "step": { + "user": { + "data": { + "name": "Nom del sensor" + }, + "title": "Adre\u00e7a IP local" + } + }, + "title": "Adre\u00e7a IP local" + } +} \ No newline at end of file diff --git a/homeassistant/components/local_ip/.translations/da.json b/homeassistant/components/local_ip/.translations/da.json new file mode 100644 index 00000000000..c0396ccb182 --- /dev/null +++ b/homeassistant/components/local_ip/.translations/da.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "Integration er allerede konfigureret med en eksisterende sensor med det navn" + }, + "step": { + "user": { + "data": { + "name": "Sensornavn" + }, + "title": "Lokal IP-adresse" + } + }, + "title": "Lokal IP-adresse" + } +} \ No newline at end of file diff --git a/homeassistant/components/local_ip/.translations/ko.json b/homeassistant/components/local_ip/.translations/ko.json new file mode 100644 index 00000000000..a00a130bfca --- /dev/null +++ b/homeassistant/components/local_ip/.translations/ko.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uac00 \uc774\ubbf8 \ud574\ub2f9 \uc774\ub984\uc758 \uc13c\uc11c\ub85c \uad6c\uc131\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "name": "\uc13c\uc11c \uc774\ub984" + }, + "title": "\ub85c\uceec IP \uc8fc\uc18c" + } + }, + "title": "\ub85c\uceec IP \uc8fc\uc18c" + } +} \ No newline at end of file From 77978a979be435539c81b591a633d3663c01b83c Mon Sep 17 00:00:00 2001 From: Ernst Klamer Date: Thu, 2 Jan 2020 07:59:13 +0100 Subject: [PATCH 494/677] Restore state for Rfxtrx devices (#30309) * Restore state rfxtrx switch * Restore state RFXtrx lights * Restore state RFXtrx covers * Restore comment * Remove line * Remove logging * fix black * Fix typo --- homeassistant/components/rfxtrx/cover.py | 13 +++++++++-- homeassistant/components/rfxtrx/light.py | 28 +++++++++++++++++++++-- homeassistant/components/rfxtrx/switch.py | 13 +++++++++-- 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/rfxtrx/cover.py b/homeassistant/components/rfxtrx/cover.py index 4806fd9a6b7..e1eb6ae77f5 100644 --- a/homeassistant/components/rfxtrx/cover.py +++ b/homeassistant/components/rfxtrx/cover.py @@ -3,8 +3,9 @@ import RFXtrx as rfxtrxmod import voluptuous as vol from homeassistant.components.cover import PLATFORM_SCHEMA, CoverDevice -from homeassistant.const import CONF_NAME +from homeassistant.const import CONF_NAME, STATE_OPEN from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.restore_state import RestoreEntity from . import ( CONF_AUTOMATIC_ADD, @@ -62,9 +63,17 @@ def setup_platform(hass, config, add_entities, discovery_info=None): RECEIVED_EVT_SUBSCRIBERS.append(cover_update) -class RfxtrxCover(RfxtrxDevice, CoverDevice): +class RfxtrxCover(RfxtrxDevice, CoverDevice, RestoreEntity): """Representation of a RFXtrx cover.""" + async def async_added_to_hass(self): + """Restore RFXtrx cover device state (OPEN/CLOSE).""" + await super().async_added_to_hass() + + old_state = await self.async_get_last_state() + if old_state is not None: + self._state = old_state.state == STATE_OPEN + @property def should_poll(self): """Return the polling state. No polling available in RFXtrx cover.""" diff --git a/homeassistant/components/rfxtrx/light.py b/homeassistant/components/rfxtrx/light.py index a29b8bfa660..9c0157870cb 100644 --- a/homeassistant/components/rfxtrx/light.py +++ b/homeassistant/components/rfxtrx/light.py @@ -10,8 +10,9 @@ from homeassistant.components.light import ( SUPPORT_BRIGHTNESS, Light, ) -from homeassistant.const import CONF_NAME +from homeassistant.const import CONF_NAME, STATE_ON from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.restore_state import RestoreEntity from . import ( CONF_AUTOMATIC_ADD, @@ -72,14 +73,37 @@ def setup_platform(hass, config, add_entities, discovery_info=None): RECEIVED_EVT_SUBSCRIBERS.append(light_update) -class RfxtrxLight(RfxtrxDevice, Light): +class RfxtrxLight(RfxtrxDevice, Light, RestoreEntity): """Representation of a RFXtrx light.""" + async def async_added_to_hass(self): + """Restore RFXtrx device state (ON/OFF).""" + await super().async_added_to_hass() + + old_state = await self.async_get_last_state() + if old_state is not None: + self._state = old_state.state == STATE_ON + + # Restore the brightness of dimmable devices + if ( + old_state is not None + and old_state.attributes.get(ATTR_BRIGHTNESS) is not None + ): + self._brightness = int(old_state.attributes[ATTR_BRIGHTNESS]) + @property def brightness(self): """Return the brightness of this light between 0..255.""" return self._brightness + @property + def device_state_attributes(self): + """Return the device state attributes.""" + attr = {} + if self._brightness is not None: + attr[ATTR_BRIGHTNESS] = self._brightness + return attr + @property def supported_features(self): """Flag supported features.""" diff --git a/homeassistant/components/rfxtrx/switch.py b/homeassistant/components/rfxtrx/switch.py index 49bcd1b3924..05e4a37ab44 100644 --- a/homeassistant/components/rfxtrx/switch.py +++ b/homeassistant/components/rfxtrx/switch.py @@ -5,8 +5,9 @@ import RFXtrx as rfxtrxmod import voluptuous as vol from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice -from homeassistant.const import CONF_NAME +from homeassistant.const import CONF_NAME, STATE_ON from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.restore_state import RestoreEntity from . import ( CONF_AUTOMATIC_ADD, @@ -67,9 +68,17 @@ def setup_platform(hass, config, add_entities_callback, discovery_info=None): RECEIVED_EVT_SUBSCRIBERS.append(switch_update) -class RfxtrxSwitch(RfxtrxDevice, SwitchDevice): +class RfxtrxSwitch(RfxtrxDevice, SwitchDevice, RestoreEntity): """Representation of a RFXtrx switch.""" + async def async_added_to_hass(self): + """Restore RFXtrx switch device state (ON/OFF).""" + await super().async_added_to_hass() + + old_state = await self.async_get_last_state() + if old_state is not None: + self._state = old_state.state == STATE_ON + def turn_on(self, **kwargs): """Turn the device on.""" self._send_command("turn_on") From cac750066ae5ccaf906a4fbf1c45c4756bcefca5 Mon Sep 17 00:00:00 2001 From: Ernst Klamer Date: Thu, 2 Jan 2020 16:44:29 +0100 Subject: [PATCH 495/677] Remove unnessecary rfxtrx light property def (#30397) --- homeassistant/components/rfxtrx/light.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/homeassistant/components/rfxtrx/light.py b/homeassistant/components/rfxtrx/light.py index 9c0157870cb..437cce89c49 100644 --- a/homeassistant/components/rfxtrx/light.py +++ b/homeassistant/components/rfxtrx/light.py @@ -96,14 +96,6 @@ class RfxtrxLight(RfxtrxDevice, Light, RestoreEntity): """Return the brightness of this light between 0..255.""" return self._brightness - @property - def device_state_attributes(self): - """Return the device state attributes.""" - attr = {} - if self._brightness is not None: - attr[ATTR_BRIGHTNESS] = self._brightness - return attr - @property def supported_features(self): """Flag supported features.""" From e6388e186ce97b48853f1f196193018cc4c4e109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Thu, 2 Jan 2020 21:17:10 +0200 Subject: [PATCH 496/677] Remove unnecessary string literal concatenations (#30360) --- homeassistant/__main__.py | 8 +-- homeassistant/auth/providers/command_line.py | 2 +- homeassistant/bootstrap.py | 4 +- homeassistant/components/alexa/auth.py | 2 +- .../components/arest/binary_sensor.py | 2 +- homeassistant/components/arest/sensor.py | 2 +- homeassistant/components/arest/switch.py | 2 +- homeassistant/components/august/__init__.py | 8 +-- .../components/aurora/binary_sensor.py | 2 +- homeassistant/components/auth/indieauth.py | 2 +- .../components/automatic/device_tracker.py | 2 +- .../components/automation/__init__.py | 2 +- homeassistant/components/buienradar/camera.py | 2 +- homeassistant/components/buienradar/util.py | 4 +- homeassistant/components/camera/__init__.py | 2 +- .../cisco_mobility_express/device_tracker.py | 2 +- .../components/cisco_webex_teams/notify.py | 2 +- homeassistant/components/citybikes/sensor.py | 6 +- homeassistant/components/cloud/http_api.py | 2 +- homeassistant/components/coinbase/__init__.py | 2 +- homeassistant/components/decora/light.py | 3 +- homeassistant/components/demo/media_player.py | 2 +- .../components/directv/media_player.py | 4 +- homeassistant/components/dyson/__init__.py | 2 +- .../components/ecoal_boiler/__init__.py | 2 +- homeassistant/components/ecobee/climate.py | 2 +- .../components/egardia/alarm_control_panel.py | 2 +- homeassistant/components/esphome/__init__.py | 2 +- .../components/feedreader/__init__.py | 2 +- homeassistant/components/fibaro/__init__.py | 6 +- homeassistant/components/flux/switch.py | 2 +- .../components/foursquare/__init__.py | 2 +- .../components/fritz/device_tracker.py | 2 +- .../components/generic_thermostat/climate.py | 2 +- homeassistant/components/harmony/remote.py | 2 +- .../components/haveibeenpwned/sensor.py | 2 +- .../components/history_stats/sensor.py | 6 +- homeassistant/components/homekit/__init__.py | 2 +- .../components/homekit/type_media_players.py | 2 +- homeassistant/components/homekit/util.py | 2 +- homeassistant/components/html5/notify.py | 6 +- homeassistant/components/http/ban.py | 4 +- homeassistant/components/http/view.py | 8 ++- homeassistant/components/hue/__init__.py | 4 +- homeassistant/components/hue/light.py | 2 +- homeassistant/components/imap/sensor.py | 2 +- homeassistant/components/influxdb/sensor.py | 5 +- .../components/input_datetime/__init__.py | 2 +- homeassistant/components/ios/notify.py | 6 +- .../components/isy994/binary_sensor.py | 6 +- homeassistant/components/knx/__init__.py | 2 +- homeassistant/components/kodi/media_player.py | 2 +- homeassistant/components/lametric/notify.py | 2 +- homeassistant/components/lcn/helpers.py | 2 +- homeassistant/components/lcn/services.py | 4 +- .../components/logi_circle/config_flow.py | 2 +- homeassistant/components/min_max/sensor.py | 2 +- homeassistant/components/mobile_app/notify.py | 6 +- homeassistant/components/modbus/sensor.py | 4 +- homeassistant/components/modbus/switch.py | 2 +- .../components/mold_indicator/sensor.py | 10 ++-- homeassistant/components/mqtt/__init__.py | 8 +-- homeassistant/components/nest/__init__.py | 2 +- .../components/nissan_leaf/__init__.py | 6 +- homeassistant/components/nut/sensor.py | 2 +- homeassistant/components/ohmconnect/sensor.py | 4 +- homeassistant/components/opensky/sensor.py | 2 +- .../components/openweathermap/weather.py | 2 +- homeassistant/components/owlet/__init__.py | 2 +- .../components/owntracks/__init__.py | 2 +- homeassistant/components/prowl/notify.py | 2 +- .../components/proximity/__init__.py | 2 +- .../components/python_script/__init__.py | 2 +- .../quantum_gateway/device_tracker.py | 2 +- homeassistant/components/recorder/__init__.py | 2 +- .../components/remember_the_milk/__init__.py | 10 ++-- .../components/satel_integra/__init__.py | 4 +- .../components/satel_integra/switch.py | 4 +- homeassistant/components/slide/__init__.py | 5 +- .../components/smartthings/smartapp.py | 2 +- .../components/spotify/media_player.py | 2 +- .../swiss_hydrological_data/sensor.py | 2 +- .../components/tado/device_tracker.py | 4 +- .../components/telegram_bot/__init__.py | 2 +- .../components/template/binary_sensor.py | 4 +- homeassistant/components/template/cover.py | 5 +- homeassistant/components/template/light.py | 2 +- homeassistant/components/template/sensor.py | 4 +- homeassistant/components/template/switch.py | 2 +- .../components/tomato/device_tracker.py | 2 +- .../totalconnect/alarm_control_panel.py | 2 +- homeassistant/components/uscis/sensor.py | 2 +- homeassistant/components/wink/__init__.py | 5 +- .../components/wirelesstag/__init__.py | 2 +- .../components/wunderground/sensor.py | 2 +- .../components/xfinity/device_tracker.py | 4 +- homeassistant/components/xiaomi_miio/light.py | 12 ++-- .../components/xiaomi_miio/vacuum.py | 2 +- homeassistant/components/xmpp/notify.py | 2 +- homeassistant/components/xs1/__init__.py | 3 +- homeassistant/components/yeelight/light.py | 4 +- homeassistant/components/yr/sensor.py | 2 +- homeassistant/components/zigbee/__init__.py | 2 +- homeassistant/components/zwave/__init__.py | 16 ++--- homeassistant/components/zwave/lock.py | 4 +- homeassistant/config.py | 2 +- homeassistant/helpers/condition.py | 2 +- homeassistant/helpers/config_validation.py | 2 +- homeassistant/helpers/entity_platform.py | 2 +- homeassistant/helpers/template.py | 2 +- homeassistant/loader.py | 2 +- homeassistant/scripts/ensure_config.py | 4 +- homeassistant/setup.py | 6 +- homeassistant/util/dt.py | 2 +- homeassistant/util/yaml/loader.py | 4 +- script/hassfest/codeowners.py | 2 +- script/hassfest/ssdp.py | 2 +- script/hassfest/zeroconf.py | 2 +- tests/components/aprs/test_device_tracker.py | 2 +- tests/components/automation/test_time.py | 2 +- tests/components/binary_sensor/test_init.py | 5 +- tests/components/buienradar/test_camera.py | 2 +- tests/components/camera/test_init.py | 2 +- tests/components/cloud/test_http_api.py | 6 +- tests/components/demo/test_media_player.py | 2 +- tests/components/device_tracker/test_init.py | 4 +- tests/components/emulated_hue/test_hue_api.py | 2 +- tests/components/emulated_hue/test_init.py | 6 +- tests/components/emulated_hue/test_upnp.py | 2 +- .../facebox/test_image_processing.py | 2 +- tests/components/feedreader/test_init.py | 10 ++-- tests/components/filter/test_sensor.py | 8 +-- tests/components/hassio/conftest.py | 2 +- tests/components/hassio/test_discovery.py | 8 +-- .../here_travel_time/test_sensor.py | 2 +- tests/components/history_stats/test_sensor.py | 6 +- tests/components/homekit/test_accessories.py | 6 +- tests/components/honeywell/test_climate.py | 12 ++-- .../ign_sismologia/test_geo_location.py | 4 +- .../islamic_prayer_times/test_sensor.py | 2 +- tests/components/logentries/test_init.py | 2 +- tests/components/mailbox/test_init.py | 6 +- .../manual/test_alarm_control_panel.py | 60 +++++++++---------- tests/components/microsoft_face/test_init.py | 14 ++--- .../test_image_processing.py | 4 +- .../test_image_processing.py | 4 +- tests/components/mqtt/test_binary_sensor.py | 4 +- tests/components/mqtt/test_climate.py | 2 +- tests/components/mqtt/test_device_tracker.py | 2 +- tests/components/mqtt/test_discovery.py | 8 +-- tests/components/mqtt/test_light.py | 2 +- tests/components/mqtt/test_light_json.py | 2 +- tests/components/mqtt/test_light_template.py | 2 +- tests/components/mqtt/test_sensor.py | 4 +- tests/components/mqtt/test_switch.py | 2 +- .../mqtt_json/test_device_tracker.py | 2 +- .../components/owntracks/test_config_flow.py | 4 +- .../owntracks/test_device_tracker.py | 6 +- .../qld_bushfire/test_geo_location.py | 4 +- tests/components/radarr/test_sensor.py | 2 +- tests/components/rflink/test_binary_sensor.py | 8 +-- tests/components/shell_command/test_init.py | 2 +- tests/components/shopping_list/conftest.py | 2 +- tests/components/startca/test_sensor.py | 10 ++-- .../components/tomato/test_device_tracker.py | 6 +- tests/components/tplink/test_init.py | 2 +- tests/components/tradfri/conftest.py | 2 +- tests/components/tradfri/test_config_flow.py | 4 +- tests/components/tts/test_init.py | 20 +++---- tests/components/websocket_api/test_auth.py | 2 +- tests/components/wunderground/test_sensor.py | 6 +- tests/components/yr/test_sensor.py | 6 +- tests/components/zha/test_config_flow.py | 4 +- tests/conftest.py | 2 +- tests/helpers/test_check_config.py | 5 +- tests/helpers/test_device_registry.py | 2 +- tests/helpers/test_entity_component.py | 6 +- tests/helpers/test_entity_platform.py | 2 +- tests/helpers/test_event.py | 4 +- tests/helpers/test_service.py | 2 +- tests/helpers/test_template.py | 22 +++---- tests/scripts/test_check_config.py | 9 ++- tests/util/test_yaml.py | 8 +-- 183 files changed, 362 insertions(+), 393 deletions(-) diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index bcc97252255..a0243e2dd8c 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -78,11 +78,7 @@ def ensure_config_path(config_dir: str) -> None: try: os.mkdir(lib_dir) except OSError: - print( - ("Fatal Error: Unable to create library " "directory {} ").format( - lib_dir - ) - ) + print("Fatal Error: Unable to create library directory {}".format(lib_dir)) sys.exit(1) @@ -147,7 +143,7 @@ def get_arguments() -> argparse.Namespace: "--log-file", type=str, default=None, - help="Log file to write to. If not set, CONFIG/home-assistant.log " "is used", + help="Log file to write to. If not set, CONFIG/home-assistant.log is used", ) parser.add_argument( "--log-no-color", action="store_true", help="Disable color logs" diff --git a/homeassistant/auth/providers/command_line.py b/homeassistant/auth/providers/command_line.py index 203bc191193..12e27c01504 100644 --- a/homeassistant/auth/providers/command_line.py +++ b/homeassistant/auth/providers/command_line.py @@ -76,7 +76,7 @@ class CommandLineAuthProvider(AuthProvider): if process.returncode != 0: _LOGGER.error( - "User %r failed to authenticate, command exited " "with code %d.", + "User %r failed to authenticate, command exited with code %d.", username, process.returncode, ) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 12fbc6f232f..48ca96c7254 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -66,7 +66,7 @@ async def async_from_config_dict( hass.config.skip_pip = skip_pip if skip_pip: _LOGGER.warning( - "Skipping pip installation of required modules. " "This may cause issues" + "Skipping pip installation of required modules. This may cause issues" ) core_config = config.get(core.DOMAIN, {}) @@ -168,7 +168,7 @@ def async_enable_logging( This method must be run in the event loop. """ - fmt = "%(asctime)s %(levelname)s (%(threadName)s) " "[%(name)s] %(message)s" + fmt = "%(asctime)s %(levelname)s (%(threadName)s) [%(name)s] %(message)s" datefmt = "%Y-%m-%d %H:%M:%S" if not log_no_color: diff --git a/homeassistant/components/alexa/auth.py b/homeassistant/components/alexa/auth.py index 33c25b73d7e..94789c33305 100644 --- a/homeassistant/components/alexa/auth.py +++ b/homeassistant/components/alexa/auth.py @@ -51,7 +51,7 @@ class Auth: "client_secret": self.client_secret, } _LOGGER.debug( - "Calling LWA to get the access token (first time), " "with: %s", + "Calling LWA to get the access token (first time), with: %s", json.dumps(lwa_params), ) diff --git a/homeassistant/components/arest/binary_sensor.py b/homeassistant/components/arest/binary_sensor.py index caabe3333f8..3bd0a85c6f0 100644 --- a/homeassistant/components/arest/binary_sensor.py +++ b/homeassistant/components/arest/binary_sensor.py @@ -38,7 +38,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): response = requests.get(resource, timeout=10).json() except requests.exceptions.MissingSchema: _LOGGER.error( - "Missing resource or schema in configuration. " "Add http:// to your URL" + "Missing resource or schema in configuration. Add http:// to your URL" ) return False except requests.exceptions.ConnectionError: diff --git a/homeassistant/components/arest/sensor.py b/homeassistant/components/arest/sensor.py index 270a3cda269..2533ce3619e 100644 --- a/homeassistant/components/arest/sensor.py +++ b/homeassistant/components/arest/sensor.py @@ -59,7 +59,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): response = requests.get(resource, timeout=10).json() except requests.exceptions.MissingSchema: _LOGGER.error( - "Missing resource or schema in configuration. " "Add http:// to your URL" + "Missing resource or schema in configuration. Add http:// to your URL" ) return False except requests.exceptions.ConnectionError: diff --git a/homeassistant/components/arest/switch.py b/homeassistant/components/arest/switch.py index b3db6684cf2..ccc2c5d8bf5 100644 --- a/homeassistant/components/arest/switch.py +++ b/homeassistant/components/arest/switch.py @@ -46,7 +46,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): response = requests.get(resource, timeout=10) except requests.exceptions.MissingSchema: _LOGGER.error( - "Missing resource or schema in configuration. " "Add http:// to your URL" + "Missing resource or schema in configuration. Add http:// to your URL" ) return False except requests.exceptions.ConnectionError: diff --git a/homeassistant/components/august/__init__.py b/homeassistant/components/august/__init__.py index 468e6e429a7..8cbe41dac9e 100644 --- a/homeassistant/components/august/__init__.py +++ b/homeassistant/components/august/__init__.py @@ -254,7 +254,7 @@ class AugustData: ) except RequestException as ex: _LOGGER.error( - "Request error trying to retrieve doorbell" " status for %s. %s", + "Request error trying to retrieve doorbell status for %s. %s", doorbell.device_name, ex, ) @@ -301,7 +301,7 @@ class AugustData: ) except RequestException as ex: _LOGGER.error( - "Request error trying to retrieve door" " status for %s. %s", + "Request error trying to retrieve door status for %s. %s", lock.device_name, ex, ) @@ -327,7 +327,7 @@ class AugustData: ) except RequestException as ex: _LOGGER.error( - "Request error trying to retrieve door" " status for %s. %s", + "Request error trying to retrieve door status for %s. %s", lock.device_name, ex, ) @@ -342,7 +342,7 @@ class AugustData: ) except RequestException as ex: _LOGGER.error( - "Request error trying to retrieve door" " details for %s. %s", + "Request error trying to retrieve door details for %s. %s", lock.device_name, ex, ) diff --git a/homeassistant/components/aurora/binary_sensor.py b/homeassistant/components/aurora/binary_sensor.py index d76884d2895..454c3ad2405 100644 --- a/homeassistant/components/aurora/binary_sensor.py +++ b/homeassistant/components/aurora/binary_sensor.py @@ -13,7 +13,7 @@ from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -ATTRIBUTION = "Data provided by the National Oceanic and Atmospheric " "Administration" +ATTRIBUTION = "Data provided by the National Oceanic and Atmospheric Administration" CONF_THRESHOLD = "forecast_threshold" DEFAULT_DEVICE_CLASS = "visible" diff --git a/homeassistant/components/auth/indieauth.py b/homeassistant/components/auth/indieauth.py index 5915a4ec301..3266ae65d7a 100644 --- a/homeassistant/components/auth/indieauth.py +++ b/homeassistant/components/auth/indieauth.py @@ -99,7 +99,7 @@ async def fetch_redirect_uris(hass, url): pass except aiohttp.client_exceptions.ClientConnectionError: _LOGGER.error( - ("Low level connection error while looking up " "redirect_uri %s"), url + "Low level connection error while looking up redirect_uri %s", url ) pass except aiohttp.client_exceptions.ClientError: diff --git a/homeassistant/components/automatic/device_tracker.py b/homeassistant/components/automatic/device_tracker.py index bb403687963..3c9e33cdc84 100644 --- a/homeassistant/components/automatic/device_tracker.py +++ b/homeassistant/components/automatic/device_tracker.py @@ -232,7 +232,7 @@ class AutomaticData: if event.created_at < self.vehicle_seen[event.vehicle.id]: # Skip events received out of order _LOGGER.debug( - "Skipping out of order event. Event Created %s. " "Last seen event: %s", + "Skipping out of order event. Event Created %s. Last seen event: %s", event.created_at, self.vehicle_seen[event.vehicle.id], ) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 4441b028565..671d7bd3d5b 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -275,7 +275,7 @@ class AutomationEntity(ToggleEntity, RestoreEntity): else: enable_automation = DEFAULT_INITIAL_STATE _LOGGER.debug( - "Automation %s not in state storage, state %s from " "default is used.", + "Automation %s not in state storage, state %s from default is used.", self.entity_id, enable_automation, ) diff --git a/homeassistant/components/buienradar/camera.py b/homeassistant/components/buienradar/camera.py index 3d30e330bc9..c1ef11c1d0d 100644 --- a/homeassistant/components/buienradar/camera.py +++ b/homeassistant/components/buienradar/camera.py @@ -16,7 +16,7 @@ from homeassistant.util import dt as dt_util CONF_DIMENSION = "dimension" CONF_DELTA = "delta" -RADAR_MAP_URL_TEMPLATE = "https://api.buienradar.nl/image/1.0/" "RadarMapNL?w={w}&h={h}" +RADAR_MAP_URL_TEMPLATE = "https://api.buienradar.nl/image/1.0/RadarMapNL?w={w}&h={h}" _LOG = logging.getLogger(__name__) diff --git a/homeassistant/components/buienradar/util.py b/homeassistant/components/buienradar/util.py index 2ef0713713b..37c518cef7a 100644 --- a/homeassistant/components/buienradar/util.py +++ b/homeassistant/components/buienradar/util.py @@ -115,7 +115,7 @@ class BrData: if raincontent.get(SUCCESS) is not True: # unable to get the data _LOGGER.warning( - "Unable to retrieve raindata from Buienradar." "(Msg: %s, status: %s,)", + "Unable to retrieve raindata from Buienradar. (Msg: %s, status: %s)", raincontent.get(MESSAGE), raincontent.get(STATUS_CODE), ) @@ -136,7 +136,7 @@ class BrData: if result.get(SUCCESS) is not True: if int(datetime.now().strftime("%H")) > 0: _LOGGER.warning( - "Unable to parse data from Buienradar." "(Msg: %s)", + "Unable to parse data from Buienradar. (Msg: %s)", result.get(MESSAGE), ) await self.schedule_update(SCHEDULE_NOK) diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index b3d5935784f..4fe52a7d164 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -168,7 +168,7 @@ async def async_get_still_stream(request, image_cb, content_type, interval): This method must be run in the event loop. """ response = 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_to_mjpeg_stream(img_bytes): diff --git a/homeassistant/components/cisco_mobility_express/device_tracker.py b/homeassistant/components/cisco_mobility_express/device_tracker.py index 702ebdfa611..db504e3d19b 100644 --- a/homeassistant/components/cisco_mobility_express/device_tracker.py +++ b/homeassistant/components/cisco_mobility_express/device_tracker.py @@ -89,5 +89,5 @@ class CiscoMEDeviceScanner(DeviceScanner): """Check the Cisco ME controller for devices.""" self.last_results = self.controller.get_associated_devices() _LOGGER.debug( - "Cisco Mobility Express controller returned:" " %s", self.last_results + "Cisco Mobility Express controller returned: %s", self.last_results ) diff --git a/homeassistant/components/cisco_webex_teams/notify.py b/homeassistant/components/cisco_webex_teams/notify.py index 6f80fa138d4..7be53d1fb6c 100644 --- a/homeassistant/components/cisco_webex_teams/notify.py +++ b/homeassistant/components/cisco_webex_teams/notify.py @@ -54,5 +54,5 @@ class CiscoWebexTeamsNotificationService(BaseNotificationService): self.client.messages.create(roomId=self.room, html=f"{title}{message}") except ApiError as api_error: _LOGGER.error( - "Could not send CiscoWebexTeams notification. " "Error: %s", api_error + "Could not send CiscoWebexTeams notification. Error: %s", api_error ) diff --git a/homeassistant/components/citybikes/sensor.py b/homeassistant/components/citybikes/sensor.py index cb2647487ea..8e0b883b726 100644 --- a/homeassistant/components/citybikes/sensor.py +++ b/homeassistant/components/citybikes/sensor.py @@ -57,7 +57,7 @@ SCAN_INTERVAL = timedelta(minutes=5) # Timely, and doesn't suffocate the API STATIONS_URI = "v2/networks/{uid}?fields=network.stations" CITYBIKES_ATTRIBUTION = ( - "Information provided by the CityBikes Project " "(https://citybik.es/#about)" + "Information provided by the CityBikes Project (https://citybik.es/#about)" ) CITYBIKES_NETWORKS = "citybikes_networks" @@ -143,9 +143,7 @@ async def async_citybikes_request(hass, uri, schema): except ValueError: _LOGGER.error("Received non-JSON data from CityBikes API endpoint") except vol.Invalid as err: - _LOGGER.error( - "Received unexpected JSON from CityBikes" " API endpoint: %s", err - ) + _LOGGER.error("Received unexpected JSON from CityBikes API endpoint: %s", err) raise CityBikesRequestError diff --git a/homeassistant/components/cloud/http_api.py b/homeassistant/components/cloud/http_api.py index c68f24172f0..b97feb7c6f4 100644 --- a/homeassistant/components/cloud/http_api.py +++ b/homeassistant/components/cloud/http_api.py @@ -583,7 +583,7 @@ async def alexa_sync(hass, connection, msg): connection.send_error( msg["id"], "alexa_relink", - "Please go to the Alexa app and re-link the Home Assistant " "skill.", + "Please go to the Alexa app and re-link the Home Assistant skill.", ) return diff --git a/homeassistant/components/coinbase/__init__.py b/homeassistant/components/coinbase/__init__.py index 67869e6b88c..d52c0867e24 100644 --- a/homeassistant/components/coinbase/__init__.py +++ b/homeassistant/components/coinbase/__init__.py @@ -94,5 +94,5 @@ class CoinbaseData: self.exchange_rates = self.client.get_exchange_rates() except AuthenticationError as coinbase_error: _LOGGER.error( - "Authentication error connecting" " to coinbase: %s", coinbase_error + "Authentication error connecting to coinbase: %s", coinbase_error ) diff --git a/homeassistant/components/decora/light.py b/homeassistant/components/decora/light.py index 6ca427f2476..f4035352e51 100644 --- a/homeassistant/components/decora/light.py +++ b/homeassistant/components/decora/light.py @@ -62,8 +62,7 @@ def retry(method): return method(device, *args, **kwargs) except (decora.decoraException, AttributeError, BTLEException): _LOGGER.warning( - "Decora connect error for device %s. " "Reconnecting...", - device.name, + "Decora connect error for device %s. Reconnecting...", device.name, ) # pylint: disable=protected-access device._switch.connect() diff --git a/homeassistant/components/demo/media_player.py b/homeassistant/components/demo/media_player.py index 9d7c3892af8..33fe4ee3647 100644 --- a/homeassistant/components/demo/media_player.py +++ b/homeassistant/components/demo/media_player.py @@ -340,7 +340,7 @@ class DemoMusicPlayer(AbstractDemoPlayer): @property def media_image_url(self): """Return the image url of current playing media.""" - return "https://graph.facebook.com/v2.5/107771475912710/" "picture?type=large" + return "https://graph.facebook.com/v2.5/107771475912710/picture?type=large" @property def media_title(self): diff --git a/homeassistant/components/directv/media_player.py b/homeassistant/components/directv/media_player.py index 5dd673ca93f..cd4f910c707 100644 --- a/homeassistant/components/directv/media_player.py +++ b/homeassistant/components/directv/media_player.py @@ -129,7 +129,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) else: _LOGGER.debug( - "Adding discovered device %s with" " client address %s", + "Adding discovered device %s with client address %s", str.title(loc["locationName"]), loc["clientAddr"], ) @@ -214,7 +214,7 @@ class DirecTvDevice(MediaPlayerDevice): except requests.RequestException as ex: _LOGGER.error( - "%s: Request error trying to update current status: " "%s", + "%s: Request error trying to update current status: %s", self.entity_id, ex, ) diff --git a/homeassistant/components/dyson/__init__.py b/homeassistant/components/dyson/__init__.py index a5dde58d30f..fbe7897e6bb 100644 --- a/homeassistant/components/dyson/__init__.py +++ b/homeassistant/components/dyson/__init__.py @@ -89,7 +89,7 @@ def setup(hass, config): # Not yet reliable for device in dyson_devices: _LOGGER.info( - "Trying to connect to device %s with timeout=%i " "and retry=%i", + "Trying to connect to device %s with timeout=%i and retry=%i", device, timeout, retry, diff --git a/homeassistant/components/ecoal_boiler/__init__.py b/homeassistant/components/ecoal_boiler/__init__.py index ed8e315bfed..608e4a59a3f 100644 --- a/homeassistant/components/ecoal_boiler/__init__.py +++ b/homeassistant/components/ecoal_boiler/__init__.py @@ -91,7 +91,7 @@ def setup(hass, hass_config): if ecoal_contr.version is None: # Wrong credentials nor network config _LOGGER.error( - "Unable to read controller status from %s@%s" " (wrong host/credentials)", + "Unable to read controller status from %s@%s (wrong host/credentials)", username, host, ) diff --git a/homeassistant/components/ecobee/climate.py b/homeassistant/components/ecobee/climate.py index 83a1453a23a..5915e64334f 100644 --- a/homeassistant/components/ecobee/climate.py +++ b/homeassistant/components/ecobee/climate.py @@ -550,7 +550,7 @@ class Thermostat(ClimateDevice): self.hold_preference(), ) _LOGGER.debug( - "Setting ecobee hold_temp to: heat=%s, is=%s, " "cool=%s, is=%s", + "Setting ecobee hold_temp to: heat=%s, is=%s, cool=%s, is=%s", heat_temp, isinstance(heat_temp, (int, float)), cool_temp, diff --git a/homeassistant/components/egardia/alarm_control_panel.py b/homeassistant/components/egardia/alarm_control_panel.py index 2c18be47a1f..7e5f88cff3e 100644 --- a/homeassistant/components/egardia/alarm_control_panel.py +++ b/homeassistant/components/egardia/alarm_control_panel.py @@ -139,7 +139,7 @@ class EgardiaAlarm(alarm.AlarmControlPanel): self._egardiasystem.alarm_disarm() except requests.exceptions.RequestException as err: _LOGGER.error( - "Egardia device exception occurred when " "sending disarm command: %s", + "Egardia device exception occurred when sending disarm command: %s", err, ) diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py index 2ad24e6f75e..cabba95ea7e 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -130,7 +130,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool # ESPHome uses servicecall packet for both events and service calls # Ensure the user can only send events of form 'esphome.xyz' if domain != "esphome": - _LOGGER.error("Can only generate events under esphome " "domain!") + _LOGGER.error("Can only generate events under esphome domain!") return hass.bus.async_fire(service.service, service_data) else: diff --git a/homeassistant/components/feedreader/__init__.py b/homeassistant/components/feedreader/__init__.py index bf1e55370bc..2643607c3a8 100644 --- a/homeassistant/components/feedreader/__init__.py +++ b/homeassistant/components/feedreader/__init__.py @@ -131,7 +131,7 @@ class FeedManager: """Filter the entries provided and return the ones to keep.""" if len(self._feed.entries) > self._max_entries: _LOGGER.debug( - "Processing only the first %s entries " "in feed %s", + "Processing only the first %s entries in feed %s", self._max_entries, self._url, ) diff --git a/homeassistant/components/fibaro/__init__.py b/homeassistant/components/fibaro/__init__.py index d44819e758b..aeb7c0879e0 100644 --- a/homeassistant/components/fibaro/__init__.py +++ b/homeassistant/components/fibaro/__init__.py @@ -134,11 +134,11 @@ class FibaroController: info = self._client.info.get() self.hub_serial = slugify(info.serialNumber) except AssertionError: - _LOGGER.error("Can't connect to Fibaro HC. " "Please check URL.") + _LOGGER.error("Can't connect to Fibaro HC. Please check URL.") return False if login is None or login.status is False: _LOGGER.error( - "Invalid login for Fibaro HC. " "Please check username and password" + "Invalid login for Fibaro HC. Please check username and password" ) return False @@ -380,7 +380,7 @@ class FibaroDevice(Entity): def dont_know_message(self, action): """Make a warning in case we don't know how to perform an action.""" _LOGGER.warning( - "Not sure how to setValue: %s " "(available actions: %s)", + "Not sure how to setValue: %s (available actions: %s)", str(self.ha_id), str(self.fibaro_device.actions), ) diff --git a/homeassistant/components/flux/switch.py b/homeassistant/components/flux/switch.py index a02b1b2504b..f22b6335911 100644 --- a/homeassistant/components/flux/switch.py +++ b/homeassistant/components/flux/switch.py @@ -323,7 +323,7 @@ class FluxSwitch(SwitchDevice, RestoreEntity): elif self._mode == MODE_RGB: await async_set_lights_rgb(self.hass, self._lights, rgb, self._transition) _LOGGER.debug( - "Lights updated to rgb:%s, %s%% " "of %s cycle complete at %s", + "Lights updated to rgb:%s, %s%% of %s cycle complete at %s", rgb, round(percentage_complete * 100), time_state, diff --git a/homeassistant/components/foursquare/__init__.py b/homeassistant/components/foursquare/__init__.py index 3f0578cf5b4..af15c4e5fa8 100644 --- a/homeassistant/components/foursquare/__init__.py +++ b/homeassistant/components/foursquare/__init__.py @@ -103,7 +103,7 @@ class FoursquarePushReceiver(HomeAssistantView): if self.push_secret != secret: _LOGGER.error( - "Received Foursquare push with invalid" "push secret: %s", secret + "Received Foursquare push with invalid push secret: %s", secret ) return self.json_message("Incorrect secret", HTTP_BAD_REQUEST) diff --git a/homeassistant/components/fritz/device_tracker.py b/homeassistant/components/fritz/device_tracker.py index d16fcbb3a1e..e2382490cde 100644 --- a/homeassistant/components/fritz/device_tracker.py +++ b/homeassistant/components/fritz/device_tracker.py @@ -60,7 +60,7 @@ class FritzBoxScanner(DeviceScanner): self._update_info() else: _LOGGER.error( - "Failed to establish connection to FRITZ!Box " "with IP: %s", self.host + "Failed to establish connection to FRITZ!Box with IP: %s", self.host ) def scan_devices(self): diff --git a/homeassistant/components/generic_thermostat/climate.py b/homeassistant/components/generic_thermostat/climate.py index cb5ae275df7..58514934fc7 100644 --- a/homeassistant/components/generic_thermostat/climate.py +++ b/homeassistant/components/generic_thermostat/climate.py @@ -214,7 +214,7 @@ class GenericThermostat(ClimateDevice, RestoreEntity): else: self._target_temp = self.min_temp _LOGGER.warning( - "Undefined target temperature," "falling back to %s", + "Undefined target temperature, falling back to %s", self._target_temp, ) else: diff --git a/homeassistant/components/harmony/remote.py b/homeassistant/components/harmony/remote.py index 7f4d03ccbb0..c48d5fb00b0 100644 --- a/homeassistant/components/harmony/remote.py +++ b/homeassistant/components/harmony/remote.py @@ -369,7 +369,7 @@ class HarmonyRemote(remote.RemoteDevice): for result in result_list: _LOGGER.error( - "Sending command %s to device %s failed with code " "%s: %s", + "Sending command %s to device %s failed with code %s: %s", result.command.command, result.command.device, result.code, diff --git a/homeassistant/components/haveibeenpwned/sensor.py b/homeassistant/components/haveibeenpwned/sensor.py index a0f30dd1a8b..99f94499478 100644 --- a/homeassistant/components/haveibeenpwned/sensor.py +++ b/homeassistant/components/haveibeenpwned/sensor.py @@ -178,7 +178,7 @@ class HaveIBeenPwnedData: else: _LOGGER.error( - "Failed fetching data for %s" "(HTTP Status_code = %d)", + "Failed fetching data for %s (HTTP Status_code = %d)", self._email, req.status_code, ) diff --git a/homeassistant/components/history_stats/sensor.py b/homeassistant/components/history_stats/sensor.py index 0bded03a29c..3eb604b3957 100644 --- a/homeassistant/components/history_stats/sensor.py +++ b/homeassistant/components/history_stats/sensor.py @@ -45,7 +45,7 @@ def exactly_two_period_keys(conf): """Ensure exactly 2 of CONF_PERIOD_KEYS are provided.""" if sum(param in conf for param in CONF_PERIOD_KEYS) != 2: raise vol.Invalid( - "You must provide exactly 2 of the following:" " start, end, duration" + "You must provide exactly 2 of the following: start, end, duration" ) return conf @@ -262,7 +262,7 @@ class HistoryStatsSensor(Entity): ) except ValueError: _LOGGER.error( - "Parsing error: start must be a datetime" "or a timestamp" + "Parsing error: start must be a datetime or a timestamp" ) return @@ -281,7 +281,7 @@ class HistoryStatsSensor(Entity): ) except ValueError: _LOGGER.error( - "Parsing error: end must be a datetime " "or a timestamp" + "Parsing error: end must be a datetime or a timestamp" ) return diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index ea2c466092e..ca5a601068a 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -328,7 +328,7 @@ class HomeKit: aid = generate_aid(entity_id) if aid not in self.bridge.accessories: _LOGGER.warning( - "Could not reset accessory. entity_id " "not found %s", entity_id + "Could not reset accessory. entity_id not found %s", entity_id ) continue acc = self.remove_bridge_accessory(aid) diff --git a/homeassistant/components/homekit/type_media_players.py b/homeassistant/components/homekit/type_media_players.py index 450ae818ec8..9942c42a0de 100644 --- a/homeassistant/components/homekit/type_media_players.py +++ b/homeassistant/components/homekit/type_media_players.py @@ -422,7 +422,7 @@ class TelevisionMediaPlayer(HomeAccessory): self.char_input_source.set_value(index) else: _LOGGER.warning( - "%s: Sources out of sync. " "Restart HomeAssistant", + "%s: Sources out of sync. Restart HomeAssistant", self.entity_id, ) self.char_input_source.set_value(0) diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index 608c9a974e5..0fe97cfca63 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -103,7 +103,7 @@ def validate_entity_config(values): if not isinstance(config, dict): raise vol.Invalid( - "The configuration for {} must be " " a dictionary.".format(entity) + "The configuration for {} must be a dictionary.".format(entity) ) if domain in ("alarm_control_panel", "lock"): diff --git a/homeassistant/components/html5/notify.py b/homeassistant/components/html5/notify.py index 6d6fcd5c377..b966f5ae6a1 100644 --- a/homeassistant/components/html5/notify.py +++ b/homeassistant/components/html5/notify.py @@ -345,12 +345,12 @@ class HTML5PushCallbackView(HomeAssistantView): if parts[0].lower() != "bearer": return self.json_message( - "Authorization header must " "start with Bearer", + "Authorization header must start with Bearer", status_code=HTTP_UNAUTHORIZED, ) if len(parts) != 2: return self.json_message( - "Authorization header must " "be Bearer token", + "Authorization header must be Bearer token", status_code=HTTP_UNAUTHORIZED, ) @@ -507,7 +507,7 @@ class HTML5NotificationService(BaseNotificationService): info = REGISTER_SCHEMA(info) except vol.Invalid: _LOGGER.error( - "%s is not a valid HTML5 push notification" " target", target + "%s is not a valid HTML5 push notification target", target ) continue payload[ATTR_DATA][ATTR_JWT] = add_jwt( diff --git a/homeassistant/components/http/ban.py b/homeassistant/components/http/ban.py index 553d3657160..da406c071e4 100644 --- a/homeassistant/components/http/ban.py +++ b/homeassistant/components/http/ban.py @@ -96,7 +96,7 @@ async def process_wrong_login(request): """ remote_addr = request[KEY_REAL_IP] - msg = "Login attempt or request with invalid authentication " "from {}".format( + msg = "Login attempt or request with invalid authentication from {}".format( remote_addr ) _LOGGER.warning(msg) @@ -150,7 +150,7 @@ async def process_success_login(request): and request.app[KEY_FAILED_LOGIN_ATTEMPTS][remote_addr] > 0 ): _LOGGER.debug( - "Login success, reset failed login attempts counter" " from %s", remote_addr + "Login success, reset failed login attempts counter from %s", remote_addr ) request.app[KEY_FAILED_LOGIN_ATTEMPTS].pop(remote_addr) diff --git a/homeassistant/components/http/view.py b/homeassistant/components/http/view.py index 31f96833667..e60091684d3 100644 --- a/homeassistant/components/http/view.py +++ b/homeassistant/components/http/view.py @@ -142,9 +142,11 @@ def request_handler_factory(view, handler): elif result is None: result = b"" elif not isinstance(result, bytes): - assert False, ( - "Result should be None, string, bytes or Response. " "Got: {}" - ).format(result) + assert ( + False + ), "Result should be None, string, bytes or Response. Got: {}".format( + result + ) return web.Response(body=result, status=status_code) diff --git a/homeassistant/components/hue/__init__.py b/homeassistant/components/hue/__init__.py index 7239efafd10..cbcb21db7d0 100644 --- a/homeassistant/components/hue/__init__.py +++ b/homeassistant/components/hue/__init__.py @@ -136,9 +136,7 @@ async def async_setup_entry( ) if config.swupdate2_bridge_state == "readytoinstall": - err = ( - "Please check for software updates of the bridge " "in the Philips Hue App." - ) + err = "Please check for software updates of the bridge in the Philips Hue App." _LOGGER.warning(err) return True diff --git a/homeassistant/components/hue/light.py b/homeassistant/components/hue/light.py index ad511639d57..d81bbd4c438 100644 --- a/homeassistant/components/hue/light.py +++ b/homeassistant/components/hue/light.py @@ -277,7 +277,7 @@ class HueLight(Light): _LOGGER.warning(err, self.name) if self.gamut: if not color.check_valid_gamut(self.gamut): - err = "Color gamut of %s: %s, not valid, " "setting gamut to None." + err = "Color gamut of %s: %s, not valid, setting gamut to None." _LOGGER.warning(err, self.name, str(self.gamut)) self.gamut_typ = GAMUT_TYPE_UNAVAILABLE self.gamut = None diff --git a/homeassistant/components/imap/sensor.py b/homeassistant/components/imap/sensor.py index db2f528153b..ceef8acf7c3 100644 --- a/homeassistant/components/imap/sensor.py +++ b/homeassistant/components/imap/sensor.py @@ -162,7 +162,7 @@ class ImapSensor(Entity): self._email_count = len(lines[0].split()) else: _LOGGER.error( - "Can't parse IMAP server response to search " "'%s': %s / %s", + "Can't parse IMAP server response to search '%s': %s / %s", self._search, result, lines[0], diff --git a/homeassistant/components/influxdb/sensor.py b/homeassistant/components/influxdb/sensor.py index 58fbc5605db..4a169453e35 100644 --- a/homeassistant/components/influxdb/sensor.py +++ b/homeassistant/components/influxdb/sensor.py @@ -205,14 +205,13 @@ class InfluxSensorData: points = list(self.influx.query(self.query).get_points()) if not points: _LOGGER.warning( - "Query returned no points, sensor state set " "to UNKNOWN: %s", - self.query, + "Query returned no points, sensor state set to UNKNOWN: %s", self.query, ) self.value = None else: if len(points) > 1: _LOGGER.warning( - "Query returned multiple points, only first " "one shown: %s", + "Query returned multiple points, only first one shown: %s", self.query, ) self.value = points[0].get("value") diff --git a/homeassistant/components/input_datetime/__init__.py b/homeassistant/components/input_datetime/__init__.py index 654f3547ad6..da684e03ddc 100644 --- a/homeassistant/components/input_datetime/__init__.py +++ b/homeassistant/components/input_datetime/__init__.py @@ -98,7 +98,7 @@ async def async_setup(hass, config): and not (time or dttm) ): _LOGGER.error( - "Invalid service data for %s " "input_datetime.set_datetime: %s", + "Invalid service data for %s input_datetime.set_datetime: %s", entity.entity_id, str(call.data), ) diff --git a/homeassistant/components/ios/notify.py b/homeassistant/components/ios/notify.py index 80dbad5336d..63ed6a6ee26 100644 --- a/homeassistant/components/ios/notify.py +++ b/homeassistant/components/ios/notify.py @@ -92,9 +92,9 @@ class iOSNotificationService(BaseNotificationService): if req.status_code != 201: fallback_error = req.json().get("errorMessage", "Unknown error") - fallback_message = ( - "Internal server error, " "please try again later: " "{}" - ).format(fallback_error) + fallback_message = "Internal server error, please try again later: {}".format( + fallback_error + ) message = req.json().get("message", fallback_message) if req.status_code == 429: _LOGGER.warning(message) diff --git a/homeassistant/components/isy994/binary_sensor.py b/homeassistant/components/isy994/binary_sensor.py index eed5f1a81a0..9cf1332c4f4 100644 --- a/homeassistant/components/isy994/binary_sensor.py +++ b/homeassistant/components/isy994/binary_sensor.py @@ -165,7 +165,7 @@ class ISYBinarySensorDevice(ISYDevice, BinarySensorDevice): """Handle an "On" control event from the "negative" node.""" if event == "DON": _LOGGER.debug( - "Sensor %s turning Off via the Negative node " "sending a DON command", + "Sensor %s turning Off via the Negative node sending a DON command", self.name, ) self._computed_state = False @@ -181,7 +181,7 @@ class ISYBinarySensorDevice(ISYDevice, BinarySensorDevice): """ if event == "DON": _LOGGER.debug( - "Sensor %s turning On via the Primary node " "sending a DON command", + "Sensor %s turning On via the Primary node sending a DON command", self.name, ) self._computed_state = True @@ -189,7 +189,7 @@ class ISYBinarySensorDevice(ISYDevice, BinarySensorDevice): self._heartbeat() if event == "DOF": _LOGGER.debug( - "Sensor %s turning Off via the Primary node " "sending a DOF command", + "Sensor %s turning Off via the Primary node sending a DOF command", self.name, ) self._computed_state = False diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index 00d5d18f013..61a497e938a 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -102,7 +102,7 @@ async def async_setup(hass, config): except XKNXException as ex: _LOGGER.warning("Can't connect to KNX interface: %s", ex) hass.components.persistent_notification.async_create( - "Can't connect to KNX interface:
" "{0}".format(ex), title="KNX" + "Can't connect to KNX interface:
{0}".format(ex), title="KNX" ) for component, discovery_type in ( diff --git a/homeassistant/components/kodi/media_player.py b/homeassistant/components/kodi/media_player.py index 9721ea2d31f..71418927ed2 100644 --- a/homeassistant/components/kodi/media_player.py +++ b/homeassistant/components/kodi/media_player.py @@ -811,7 +811,7 @@ class KodiDevice(MediaPlayerDevice): except jsonrpc_base.jsonrpc.TransportError: result = None _LOGGER.warning( - "TransportError trying to run API method " "%s.%s(%s)", + "TransportError trying to run API method %s.%s(%s)", self.entity_id, method, kwargs, diff --git a/homeassistant/components/lametric/notify.py b/homeassistant/components/lametric/notify.py index b8dd610b1a0..052eb3bceac 100644 --- a/homeassistant/components/lametric/notify.py +++ b/homeassistant/components/lametric/notify.py @@ -113,7 +113,7 @@ class LaMetricNotificationService(BaseNotificationService): self._devices = lmn.get_devices() except RequestsConnectionError: _LOGGER.warning( - "Problem connecting to LaMetric, " "using cached devices instead" + "Problem connecting to LaMetric, using cached devices instead" ) for dev in self._devices: if targets is None or dev["name"] in targets: diff --git a/homeassistant/components/lcn/helpers.py b/homeassistant/components/lcn/helpers.py index 236035b0400..f4545817c9f 100644 --- a/homeassistant/components/lcn/helpers.py +++ b/homeassistant/components/lcn/helpers.py @@ -9,7 +9,7 @@ from .const import DEFAULT_NAME # Regex for address validation PATTERN_ADDRESS = re.compile( - "^((?P\\w+)\\.)?s?(?P\\d+)" "\\.(?Pm|g)?(?P\\d+)$" + "^((?P\\w+)\\.)?s?(?P\\d+)\\.(?Pm|g)?(?P\\d+)$" ) diff --git a/homeassistant/components/lcn/services.py b/homeassistant/components/lcn/services.py index 3c775224623..c35a0cc00bf 100644 --- a/homeassistant/components/lcn/services.py +++ b/homeassistant/components/lcn/services.py @@ -305,7 +305,7 @@ class SendKeys(LcnServiceCall): hit = pypck.lcn_defs.SendKeyCommand.HIT if pypck.lcn_defs.SendKeyCommand[call.data[CONF_STATE]] != hit: raise ValueError( - "Only hit command is allowed when sending" " deferred keys." + "Only hit command is allowed when sending deferred keys." ) delay_unit = pypck.lcn_defs.TimeUnit.parse(call.data[CONF_TIME_UNIT]) address_connection.send_keys_hit_deferred(keys, delay_time, delay_unit) @@ -344,7 +344,7 @@ class LockKeys(LcnServiceCall): if delay_time != 0: if table_id != 0: raise ValueError( - "Only table A is allowed when locking keys" " for a specific time." + "Only table A is allowed when locking keys for a specific time." ) delay_unit = pypck.lcn_defs.TimeUnit.parse(call.data[CONF_TIME_UNIT]) address_connection.lock_keys_tab_a_temporary(delay_time, delay_unit, states) diff --git a/homeassistant/components/logi_circle/config_flow.py b/homeassistant/components/logi_circle/config_flow.py index ce8460233d6..bc585153b64 100644 --- a/homeassistant/components/logi_circle/config_flow.py +++ b/homeassistant/components/logi_circle/config_flow.py @@ -207,5 +207,5 @@ class LogiCircleAuthCallbackView(HomeAssistantView): ) return self.json_message("Authorisation code saved") return self.json_message( - "Authorisation code missing " "from query string", status_code=400 + "Authorisation code missing from query string", status_code=400 ) diff --git a/homeassistant/components/min_max/sensor.py b/homeassistant/components/min_max/sensor.py index 977ee51cd1c..80beaf1f798 100644 --- a/homeassistant/components/min_max/sensor.py +++ b/homeassistant/components/min_max/sensor.py @@ -153,7 +153,7 @@ class MinMaxSensor(Entity): self.last = float(new_state.state) except ValueError: _LOGGER.warning( - "Unable to store state. " "Only numerical states are supported" + "Unable to store state. Only numerical states are supported" ) hass.async_add_job(self.async_update_ha_state, True) diff --git a/homeassistant/components/mobile_app/notify.py b/homeassistant/components/mobile_app/notify.py index 8ac34c9af1d..b51bf235cf0 100644 --- a/homeassistant/components/mobile_app/notify.py +++ b/homeassistant/components/mobile_app/notify.py @@ -139,9 +139,9 @@ class MobileAppNotificationService(BaseNotificationService): return fallback_error = result.get("errorMessage", "Unknown error") - fallback_message = ( - "Internal server error, " "please try again later: " "{}" - ).format(fallback_error) + fallback_message = "Internal server error, please try again later: {}".format( + fallback_error + ) message = result.get("message", fallback_message) if response.status == 429: _LOGGER.warning(message) diff --git a/homeassistant/components/modbus/sensor.py b/homeassistant/components/modbus/sensor.py index 5b04a898ab9..484382983ac 100644 --- a/homeassistant/components/modbus/sensor.py +++ b/homeassistant/components/modbus/sensor.py @@ -102,7 +102,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) except KeyError: _LOGGER.error( - "Unable to detect data type for %s sensor, " "try a custom type", + "Unable to detect data type for %s sensor, try a custom type", register.get(CONF_NAME), ) continue @@ -119,7 +119,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if register.get(CONF_COUNT) * 2 != size: _LOGGER.error( - "Structure size (%d bytes) mismatch registers count " "(%d words)", + "Structure size (%d bytes) mismatch registers count (%d words)", size, register.get(CONF_COUNT), ) diff --git a/homeassistant/components/modbus/switch.py b/homeassistant/components/modbus/switch.py index eba0c754f45..0ed33dedb57 100644 --- a/homeassistant/components/modbus/switch.py +++ b/homeassistant/components/modbus/switch.py @@ -236,7 +236,7 @@ class ModbusRegisterSwitch(ModbusCoilSwitch): self._is_on = False else: _LOGGER.error( - "Unexpected response from hub %s, slave %s " "register %s, got 0x%2x", + "Unexpected response from hub %s, slave %s register %s, got 0x%2x", self._hub.name, self._slave, self._verify_register, diff --git a/homeassistant/components/mold_indicator/sensor.py b/homeassistant/components/mold_indicator/sensor.py index 15f8b80a5ab..0d6c6f55284 100644 --- a/homeassistant/components/mold_indicator/sensor.py +++ b/homeassistant/components/mold_indicator/sensor.py @@ -110,7 +110,7 @@ class MoldIndicator(Entity): def mold_indicator_sensors_state_listener(entity, old_state, new_state): """Handle for state changes for dependent sensors.""" _LOGGER.debug( - "Sensor state change for %s that had old state %s " "and new state %s", + "Sensor state change for %s that had old state %s and new state %s", entity, old_state, new_state, @@ -188,7 +188,7 @@ class MoldIndicator(Entity): # Return an error if the sensor change its state to Unknown. if state.state == STATE_UNKNOWN: _LOGGER.error( - "Unable to parse temperature sensor %s with state:" " %s", + "Unable to parse temperature sensor %s with state: %s", state.entity_id, state.state, ) @@ -199,7 +199,7 @@ class MoldIndicator(Entity): if temp is None: _LOGGER.error( - "Unable to parse temperature sensor %s with state:" " %s", + "Unable to parse temperature sensor %s with state: %s", state.entity_id, state.state, ) @@ -211,7 +211,7 @@ class MoldIndicator(Entity): if unit == TEMP_CELSIUS: return temp _LOGGER.error( - "Temp sensor %s has unsupported unit: %s (allowed: %s, " "%s)", + "Temp sensor %s has unsupported unit: %s (allowed: %s, %s)", state.entity_id, unit, TEMP_CELSIUS, @@ -306,7 +306,7 @@ class MoldIndicator(Entity): if None in (self._dewpoint, self._calib_factor) or self._calib_factor == 0: _LOGGER.debug( - "Invalid inputs - dewpoint: %s," " calibration-factor: %s", + "Invalid inputs - dewpoint: %s, calibration-factor: %s", self._dewpoint, self._calib_factor, ) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index d8dc584ae30..a9d5ac93ebc 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -136,10 +136,10 @@ def valid_topic(value: Any) -> str: raise vol.Invalid("MQTT topic name/filter must not be empty.") if len(raw_value) > 65535: raise vol.Invalid( - "MQTT topic name/filter must not be longer than " "65535 encoded bytes." + "MQTT topic name/filter must not be longer than 65535 encoded bytes." ) if "\0" in value: - raise vol.Invalid("MQTT topic name/filter must not contain null " "character.") + raise vol.Invalid("MQTT topic name/filter must not contain null character.") return value @@ -151,7 +151,7 @@ def valid_subscribe_topic(value: Any) -> str: i < len(value) - 1 and value[i + 1] != "/" ): raise vol.Invalid( - "Single-level wildcard must occupy an entire " "level of the filter" + "Single-level wildcard must occupy an entire level of the filter" ) index = value.find("#") @@ -164,7 +164,7 @@ def valid_subscribe_topic(value: Any) -> str: ) if len(value) > 1 and value[index - 1] != "/": raise vol.Invalid( - "Multi-level wildcard must be after a topic " "level separator." + "Multi-level wildcard must be after a topic level separator." ) return value diff --git a/homeassistant/components/nest/__init__.py b/homeassistant/components/nest/__init__.py index e2a1479595e..73a28aa121f 100644 --- a/homeassistant/components/nest/__init__.py +++ b/homeassistant/components/nest/__init__.py @@ -216,7 +216,7 @@ async def async_setup_entry(hass, entry): structure.set_eta(trip_id, eta_begin, eta_end) else: _LOGGER.info( - "No thermostats found in structure: %s, " "unable to set ETA", + "No thermostats found in structure: %s, unable to set ETA", structure.name, ) diff --git a/homeassistant/components/nissan_leaf/__init__.py b/homeassistant/components/nissan_leaf/__init__.py index e5b4f34812a..fba84c936f5 100644 --- a/homeassistant/components/nissan_leaf/__init__.py +++ b/homeassistant/components/nissan_leaf/__init__.py @@ -124,9 +124,7 @@ def setup(hass, config): # for the charging request to reach the car. result = await hass.async_add_executor_job(data_store.leaf.start_charging) if result: - _LOGGER.debug( - "Start charging sent, " "request updated data in 1 minute" - ) + _LOGGER.debug("Start charging sent, request updated data in 1 minute") check_charge_at = utcnow() + timedelta(minutes=1) data_store.next_update = check_charge_at async_track_point_in_utc_time( @@ -414,7 +412,7 @@ class LeafDataStore: for attempt in range(MAX_RESPONSE_ATTEMPTS): if attempt > 0: _LOGGER.debug( - "Climate data not in yet (%s) (%s). " "Waiting (%s) seconds", + "Climate data not in yet (%s) (%s). Waiting (%s) seconds", self.leaf.vin, attempt, PYCARWINGS2_SLEEP, diff --git a/homeassistant/components/nut/sensor.py b/homeassistant/components/nut/sensor.py index 34e3bfaf086..bdf0eaafc99 100644 --- a/homeassistant/components/nut/sensor.py +++ b/homeassistant/components/nut/sensor.py @@ -189,7 +189,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): data.update(no_throttle=True) except data.pynuterror as err: _LOGGER.error( - "Failure while testing NUT status retrieval. " "Cannot continue setup: %s", + "Failure while testing NUT status retrieval. Cannot continue setup: %s", err, ) raise PlatformNotReady diff --git a/homeassistant/components/ohmconnect/sensor.py b/homeassistant/components/ohmconnect/sensor.py index a9606e25bad..490ebbe75b3 100644 --- a/homeassistant/components/ohmconnect/sensor.py +++ b/homeassistant/components/ohmconnect/sensor.py @@ -66,9 +66,7 @@ class OhmconnectSensor(Entity): def update(self): """Get the latest data from OhmConnect.""" try: - url = ("https://login.ohmconnect.com" "/verify-ohm-hour/{}").format( - self._ohmid - ) + url = "https://login.ohmconnect.com/verify-ohm-hour/{}".format(self._ohmid) response = requests.get(url, timeout=10) root = ET.fromstring(response.text) diff --git a/homeassistant/components/opensky/sensor.py b/homeassistant/components/opensky/sensor.py index cef99902d23..d916d9f7f29 100644 --- a/homeassistant/components/opensky/sensor.py +++ b/homeassistant/components/opensky/sensor.py @@ -40,7 +40,7 @@ EVENT_OPENSKY_EXIT = f"{DOMAIN}_exit" SCAN_INTERVAL = timedelta(seconds=12) # opensky public limit is 10 seconds OPENSKY_ATTRIBUTION = ( - "Information provided by the OpenSky Network " "(https://opensky-network.org)" + "Information provided by the OpenSky Network (https://opensky-network.org)" ) OPENSKY_API_URL = "https://opensky-network.org/api/states/all" OPENSKY_API_FIELDS = [ diff --git a/homeassistant/components/openweathermap/weather.py b/homeassistant/components/openweathermap/weather.py index 69ca965d660..ce8676ad440 100644 --- a/homeassistant/components/openweathermap/weather.py +++ b/homeassistant/components/openweathermap/weather.py @@ -272,7 +272,7 @@ class WeatherData: self.latitude, self.longitude ) except APICallError: - _LOGGER.error("Exception when calling OWM web API " "to update forecast") + _LOGGER.error("Exception when calling OWM web API to update forecast") return if fcd is None: diff --git a/homeassistant/components/owlet/__init__.py b/homeassistant/components/owlet/__init__.py index afde50cae49..3882ba4bf7d 100644 --- a/homeassistant/components/owlet/__init__.py +++ b/homeassistant/components/owlet/__init__.py @@ -51,7 +51,7 @@ def setup(hass, config): device = PyOwlet(username, password) except KeyError: _LOGGER.error( - "Owlet authentication failed. Please verify your " "credentials are correct" + "Owlet authentication failed. Please verify your credentials are correct" ) return False diff --git a/homeassistant/components/owntracks/__init__.py b/homeassistant/components/owntracks/__init__.py index b75be465aa1..71494e9e805 100644 --- a/homeassistant/components/owntracks/__init__.py +++ b/homeassistant/components/owntracks/__init__.py @@ -233,7 +233,7 @@ class OwnTracksContext: if self.max_gps_accuracy is not None and acc > self.max_gps_accuracy: _LOGGER.info( - "Ignoring %s update because expected GPS " "accuracy %s is not met: %s", + "Ignoring %s update because expected GPS accuracy %s is not met: %s", message["_type"], self.max_gps_accuracy, message, diff --git a/homeassistant/components/prowl/notify.py b/homeassistant/components/prowl/notify.py index 9690e748887..d5167ebfdc9 100644 --- a/homeassistant/components/prowl/notify.py +++ b/homeassistant/components/prowl/notify.py @@ -59,7 +59,7 @@ class ProwlNotificationService(BaseNotificationService): if response.status != 200 or "error" in result: _LOGGER.error( - "Prowl service returned http " "status %d, response %s", + "Prowl service returned http status %d, response %s", response.status, result, ) diff --git a/homeassistant/components/proximity/__init__.py b/homeassistant/components/proximity/__init__.py index 45a1c19c29e..7e5f6436757 100644 --- a/homeassistant/components/proximity/__init__.py +++ b/homeassistant/components/proximity/__init__.py @@ -268,7 +268,7 @@ class Proximity(Entity): self.nearest = entity_name self.schedule_update_ha_state() _LOGGER.debug( - "proximity.%s update entity: distance=%s: direction=%s: " "device=%s", + "proximity.%s update entity: distance=%s: direction=%s: device=%s", self.friendly_name, round(dist_to_zone), direction_of_travel, diff --git a/homeassistant/components/python_script/__init__.py b/homeassistant/components/python_script/__init__.py index ddae8a81db1..0c5886e177c 100644 --- a/homeassistant/components/python_script/__init__.py +++ b/homeassistant/components/python_script/__init__.py @@ -225,7 +225,7 @@ class TimeWrapper: if not TimeWrapper.warned: TimeWrapper.warned = True _LOGGER.warning( - "Using time.sleep can reduce the performance of " "Home Assistant" + "Using time.sleep can reduce the performance of Home Assistant" ) time.sleep(*args, **kwargs) diff --git a/homeassistant/components/quantum_gateway/device_tracker.py b/homeassistant/components/quantum_gateway/device_tracker.py index 97eb8eedfd3..58151fa02ce 100644 --- a/homeassistant/components/quantum_gateway/device_tracker.py +++ b/homeassistant/components/quantum_gateway/device_tracker.py @@ -54,7 +54,7 @@ class QuantumGatewayDeviceScanner(DeviceScanner): _LOGGER.error("Unable to connect to gateway. Check host.") if not self.success_init: - _LOGGER.error("Unable to login to gateway. Check password and " "host.") + _LOGGER.error("Unable to login to gateway. Check password and host.") def scan_devices(self): """Scan for new devices and return a list of found MACs.""" diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index 7ae1cb1a220..ab56a5fc33b 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -228,7 +228,7 @@ class Recorder(threading.Thread): _LOGGER.debug("Connected to recorder database") except Exception as err: # pylint: disable=broad-except _LOGGER.error( - "Error during connection setup: %s (retrying " "in %s seconds)", + "Error during connection setup: %s (retrying in %s seconds)", err, CONNECT_RETRY_WAIT, ) diff --git a/homeassistant/components/remember_the_milk/__init__.py b/homeassistant/components/remember_the_milk/__init__.py index fdfbdfd5cdc..02875cb8aa9 100644 --- a/homeassistant/components/remember_the_milk/__init__.py +++ b/homeassistant/components/remember_the_milk/__init__.py @@ -166,7 +166,7 @@ class RememberTheMilkConfiguration: self._config = json.load(config_file) except ValueError: _LOGGER.error( - "Failed to load configuration file, creating a " "new one: %s", + "Failed to load configuration file, creating a new one: %s", self._config_file_path, ) self._config = dict() @@ -258,7 +258,7 @@ class RememberTheMilk(Entity): valid = self._rtm_api.token_valid() if not valid: _LOGGER.error( - "Token for account %s is invalid. You need to " "register again!", + "Token for account %s is invalid. You need to register again!", self.name, ) self._rtm_config.delete_token(self._name) @@ -306,14 +306,14 @@ class RememberTheMilk(Entity): timeline=timeline, ) _LOGGER.debug( - "Updated task with id '%s' in account " "%s to name %s", + "Updated task with id '%s' in account %s to name %s", hass_id, self.name, task_name, ) except RtmRequestFailedException as rtm_exception: _LOGGER.error( - "Error creating new Remember The Milk task for " "account %s: %s", + "Error creating new Remember The Milk task for account %s: %s", self._name, rtm_exception, ) @@ -347,7 +347,7 @@ class RememberTheMilk(Entity): ) except RtmRequestFailedException as rtm_exception: _LOGGER.error( - "Error creating new Remember The Milk task for " "account %s: %s", + "Error creating new Remember The Milk task for account %s: %s", self._name, rtm_exception, ) diff --git a/homeassistant/components/satel_integra/__init__.py b/homeassistant/components/satel_integra/__init__.py index 1972eefd6b5..84bb3b570d8 100644 --- a/homeassistant/components/satel_integra/__init__.py +++ b/homeassistant/components/satel_integra/__init__.py @@ -62,9 +62,7 @@ PARTITION_SCHEMA = vol.Schema( def is_alarm_code_necessary(value): """Check if alarm code must be configured.""" if value.get(CONF_SWITCHABLE_OUTPUTS) and CONF_DEVICE_CODE not in value: - raise vol.Invalid( - "You need to specify alarm " " code to use switchable_outputs" - ) + raise vol.Invalid("You need to specify alarm code to use switchable_outputs") return value diff --git a/homeassistant/components/satel_integra/switch.py b/homeassistant/components/satel_integra/switch.py index c20f30cf871..9233b3d152d 100644 --- a/homeassistant/components/satel_integra/switch.py +++ b/homeassistant/components/satel_integra/switch.py @@ -69,14 +69,14 @@ class SatelIntegraSwitch(SwitchDevice): async def async_turn_on(self, **kwargs): """Turn the device on.""" - _LOGGER.debug("Switch: %s status: %s," " turning on", self._name, self._state) + _LOGGER.debug("Switch: %s status: %s, turning on", self._name, self._state) await self._satel.set_output(self._code, self._device_number, True) self.async_schedule_update_ha_state() async def async_turn_off(self, **kwargs): """Turn the device off.""" _LOGGER.debug( - "Switch name: %s status: %s," " turning off", self._name, self._state + "Switch name: %s status: %s, turning off", self._name, self._state ) await self._satel.set_output(self._code, self._device_number, False) self.async_schedule_update_ha_state() diff --git a/homeassistant/components/slide/__init__.py b/homeassistant/components/slide/__init__.py index 49e50e601dd..ccf4465577b 100644 --- a/homeassistant/components/slide/__init__.py +++ b/homeassistant/components/slide/__init__.py @@ -60,8 +60,7 @@ async def async_setup(hass, config): for slide in result: if "device_id" not in slide: _LOGGER.error( - "Found invalid Slide entry, device_id is " "missing. Entry=%s", - slide, + "Found invalid Slide entry, device_id is missing. Entry=%s", slide, ) continue @@ -104,7 +103,7 @@ async def async_setup(hass, config): ) elif "code" in slide["device_info"]: _LOGGER.warning( - "Slide %s (%s) is offline with " "code=%s", + "Slide %s (%s) is offline with code=%s", slide["id"], slidenew["mac"], slide["device_info"]["code"], diff --git a/homeassistant/components/smartthings/smartapp.py b/homeassistant/components/smartthings/smartapp.py index 74feb93eec4..d0487290926 100644 --- a/homeassistant/components/smartthings/smartapp.py +++ b/homeassistant/components/smartthings/smartapp.py @@ -339,7 +339,7 @@ async def smartapp_sync_subscriptions( ) except Exception as error: # pylint:disable=broad-except _LOGGER.error( - "Failed to remove subscription for '%s' under app " "'%s': %s", + "Failed to remove subscription for '%s' under app '%s': %s", sub.capability, installed_app_id, error, diff --git a/homeassistant/components/spotify/media_player.py b/homeassistant/components/spotify/media_player.py index ec21a5d7822..ba0c725eb7f 100644 --- a/homeassistant/components/spotify/media_player.py +++ b/homeassistant/components/spotify/media_player.py @@ -37,7 +37,7 @@ CONF_CLIENT_ID = "client_id" CONF_CLIENT_SECRET = "client_secret" CONFIGURATOR_DESCRIPTION = ( - "To link your Spotify account, " "click the link, login, and authorize:" + "To link your Spotify account, click the link, login, and authorize:" ) CONFIGURATOR_LINK_NAME = "Link Spotify account" CONFIGURATOR_SUBMIT_CAPTION = "I authorized successfully" diff --git a/homeassistant/components/swiss_hydrological_data/sensor.py b/homeassistant/components/swiss_hydrological_data/sensor.py index c8e7b9d6fc2..d4624e82bb7 100644 --- a/homeassistant/components/swiss_hydrological_data/sensor.py +++ b/homeassistant/components/swiss_hydrological_data/sensor.py @@ -13,7 +13,7 @@ from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -ATTRIBUTION = "Data provided by the Swiss Federal Office for the " "Environment FOEN" +ATTRIBUTION = "Data provided by the Swiss Federal Office for the Environment FOEN" ATTR_DELTA_24H = "delta-24h" ATTR_MAX_1H = "max-1h" diff --git a/homeassistant/components/tado/device_tracker.py b/homeassistant/components/tado/device_tracker.py index c63f5061dfa..ea797754da8 100644 --- a/homeassistant/components/tado/device_tracker.py +++ b/homeassistant/components/tado/device_tracker.py @@ -60,9 +60,7 @@ class TadoDeviceScanner(DeviceScanner): if self.home_id is None: self.tadoapiurl = "https://my.tado.com/api/v2/me" else: - self.tadoapiurl = ( - "https://my.tado.com/api/v2" "/homes/{home_id}/mobileDevices" - ) + self.tadoapiurl = "https://my.tado.com/api/v2/homes/{home_id}/mobileDevices" # The API URL always needs a username and password self.tadoapiurl += "?username={username}&password={password}" diff --git a/homeassistant/components/telegram_bot/__init__.py b/homeassistant/components/telegram_bot/__init__.py index b91c37b35de..fc37121f3f9 100644 --- a/homeassistant/components/telegram_bot/__init__.py +++ b/homeassistant/components/telegram_bot/__init__.py @@ -628,7 +628,7 @@ class TelegramNotificationService: """Answer a callback originated with a press in an inline keyboard.""" params = self._get_msg_kwargs(kwargs) _LOGGER.debug( - "Answer callback query with callback ID %s: %s, " "alert: %s.", + "Answer callback query with callback ID %s: %s, alert: %s.", callback_query_id, message, show_alert, diff --git a/homeassistant/components/template/binary_sensor.py b/homeassistant/components/template/binary_sensor.py index 3ca25a33d64..7de43ea0702 100644 --- a/homeassistant/components/template/binary_sensor.py +++ b/homeassistant/components/template/binary_sensor.py @@ -223,7 +223,7 @@ class BinarySensorTemplate(BinarySensorDevice): ): # Common during HA startup - so just a warning _LOGGER.warning( - "Could not render template %s, " "the state is unknown", self._name + "Could not render template %s, the state is unknown", self._name ) return _LOGGER.error("Could not render template %s: %s", self._name, ex) @@ -259,7 +259,7 @@ class BinarySensorTemplate(BinarySensorDevice): ): # Common during HA startup - so just a warning _LOGGER.warning( - "Could not render %s template %s," " the state is unknown.", + "Could not render %s template %s, the state is unknown.", friendly_property_name, self._name, ) diff --git a/homeassistant/components/template/cover.py b/homeassistant/components/template/cover.py index c55c3f97df5..f6678067d70 100644 --- a/homeassistant/components/template/cover.py +++ b/homeassistant/components/template/cover.py @@ -437,8 +437,7 @@ class CoverTemplate(CoverDevice): if state < 0 or state > 100: self._tilt_value = None _LOGGER.error( - "Tilt value must be between 0 and 100." " Value was: %.2f", - state, + "Tilt value must be between 0 and 100. Value was: %.2f", state, ) else: self._tilt_value = state @@ -466,7 +465,7 @@ class CoverTemplate(CoverDevice): ): # Common during HA startup - so just a warning _LOGGER.warning( - "Could not render %s template %s," " the state is unknown.", + "Could not render %s template %s, the state is unknown.", friendly_property_name, self._name, ) diff --git a/homeassistant/components/template/light.py b/homeassistant/components/template/light.py index e18833aae39..f4682fa903d 100644 --- a/homeassistant/components/template/light.py +++ b/homeassistant/components/template/light.py @@ -320,7 +320,7 @@ class LightTemplate(Light): ): # Common during HA startup - so just a warning _LOGGER.warning( - "Could not render %s template %s," " the state is unknown.", + "Could not render %s template %s, the state is unknown.", friendly_property_name, self._name, ) diff --git a/homeassistant/components/template/sensor.py b/homeassistant/components/template/sensor.py index a4e28265e1d..0ca5571515a 100644 --- a/homeassistant/components/template/sensor.py +++ b/homeassistant/components/template/sensor.py @@ -230,7 +230,7 @@ class SensorTemplate(Entity): ): # Common during HA startup - so just a warning _LOGGER.warning( - "Could not render template %s," " the state is unknown.", self._name + "Could not render template %s, the state is unknown.", self._name ) else: self._state = None @@ -268,7 +268,7 @@ class SensorTemplate(Entity): ): # Common during HA startup - so just a warning _LOGGER.warning( - "Could not render %s template %s," " the state is unknown.", + "Could not render %s template %s, the state is unknown.", friendly_property_name, self._name, ) diff --git a/homeassistant/components/template/switch.py b/homeassistant/components/template/switch.py index f44f7256e19..c2d8e8158c1 100644 --- a/homeassistant/components/template/switch.py +++ b/homeassistant/components/template/switch.py @@ -233,7 +233,7 @@ class SwitchTemplate(SwitchDevice): ): # Common during HA startup - so just a warning _LOGGER.warning( - "Could not render %s template %s," " the state is unknown.", + "Could not render %s template %s, the state is unknown.", friendly_property_name, self._name, ) diff --git a/homeassistant/components/tomato/device_tracker.py b/homeassistant/components/tomato/device_tracker.py index d53b5ab6cf0..5a5f1b1985b 100644 --- a/homeassistant/components/tomato/device_tracker.py +++ b/homeassistant/components/tomato/device_tracker.py @@ -124,7 +124,7 @@ class TomatoDeviceScanner(DeviceScanner): # We get this if we could not connect to the router or # an invalid http_id was supplied. _LOGGER.exception( - "Failed to connect to the router or " "invalid http_id supplied" + "Failed to connect to the router or invalid http_id supplied" ) return False diff --git a/homeassistant/components/totalconnect/alarm_control_panel.py b/homeassistant/components/totalconnect/alarm_control_panel.py index b8b4236806f..ed77fc4eea0 100644 --- a/homeassistant/components/totalconnect/alarm_control_panel.py +++ b/homeassistant/components/totalconnect/alarm_control_panel.py @@ -119,7 +119,7 @@ class TotalConnectAlarm(alarm.AlarmControlPanel): attr["triggered_source"] = "Carbon Monoxide" else: logging.info( - "Total Connect Client returned unknown " "status code: %s", status + "Total Connect Client returned unknown status code: %s", status ) state = None diff --git a/homeassistant/components/uscis/sensor.py b/homeassistant/components/uscis/sensor.py index 6f94d5c38b0..12e84a9dbf8 100644 --- a/homeassistant/components/uscis/sensor.py +++ b/homeassistant/components/uscis/sensor.py @@ -30,7 +30,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if uscis.valid_case_id: add_entities([uscis]) else: - _LOGGER.error("Setup USCIS Sensor Fail" " check if your Case ID is Valid") + _LOGGER.error("Setup USCIS Sensor Fail check if your Case ID is Valid") class UscisSensor(Entity): diff --git a/homeassistant/components/wink/__init__.py b/homeassistant/components/wink/__init__.py index b3ae01c8f67..b71d44206c8 100644 --- a/homeassistant/components/wink/__init__.py +++ b/homeassistant/components/wink/__init__.py @@ -740,7 +740,7 @@ class WinkDevice(Entity): try: if message is None: _LOGGER.error( - "Error on pubnub update for %s " "polling API for current state", + "Error on pubnub update for %s polling API for current state", self.name, ) self.schedule_update_ha_state(True) @@ -749,8 +749,7 @@ class WinkDevice(Entity): self.schedule_update_ha_state() except (ValueError, KeyError, AttributeError): _LOGGER.error( - "Error in pubnub JSON for %s " "polling API for current state", - self.name, + "Error in pubnub JSON for %s polling API for current state", self.name, ) self.schedule_update_ha_state(True) diff --git a/homeassistant/components/wirelesstag/__init__.py b/homeassistant/components/wirelesstag/__init__.py index 1bc971f1372..c0a30a8867f 100644 --- a/homeassistant/components/wirelesstag/__init__.py +++ b/homeassistant/components/wirelesstag/__init__.py @@ -199,7 +199,7 @@ def setup(hass, config): except (ConnectTimeout, HTTPError, WirelessTagsException) as ex: _LOGGER.error("Unable to connect to wirelesstag.net service: %s", str(ex)) hass.components.persistent_notification.create( - "Error: {}
" "Please restart hass after fixing this." "".format(ex), + "Error: {}
Please restart hass after fixing this.".format(ex), title=NOTIFICATION_TITLE, notification_id=NOTIFICATION_ID, ) diff --git a/homeassistant/components/wunderground/sensor.py b/homeassistant/components/wunderground/sensor.py index cc71e71a1d0..5d3bf1f74b8 100644 --- a/homeassistant/components/wunderground/sensor.py +++ b/homeassistant/components/wunderground/sensor.py @@ -1012,7 +1012,7 @@ class WUndergroundSensor(Entity): val = val(self.rest) except (KeyError, IndexError, TypeError, ValueError) as err: _LOGGER.warning( - "Failed to expand cfg from WU API." " Condition: %s Attr: %s Error: %s", + "Failed to expand cfg from WU API. Condition: %s Attr: %s Error: %s", self._condition, what, repr(err), diff --git a/homeassistant/components/xfinity/device_tracker.py b/homeassistant/components/xfinity/device_tracker.py index 524929ae42d..20e13682979 100644 --- a/homeassistant/components/xfinity/device_tracker.py +++ b/homeassistant/components/xfinity/device_tracker.py @@ -32,7 +32,7 @@ def get_scanner(hass, config): scanner = XfinityDeviceScanner(gateway) except (RequestException, ValueError): _LOGGER.error( - "Error communicating with Xfinity Gateway. " "Check host: %s", gateway.host + "Error communicating with Xfinity Gateway. Check host: %s", gateway.host ) return scanner @@ -51,7 +51,7 @@ class XfinityDeviceScanner(DeviceScanner): try: connected_devices = self.gateway.scan_devices() except (RequestException, ValueError): - _LOGGER.error("Unable to scan devices. " "Check connection to gateway") + _LOGGER.error("Unable to scan devices. Check connection to gateway") return connected_devices def get_device_name(self, device): diff --git a/homeassistant/components/xiaomi_miio/light.py b/homeassistant/components/xiaomi_miio/light.py index 5a7b743b362..bcc83bae454 100644 --- a/homeassistant/components/xiaomi_miio/light.py +++ b/homeassistant/components/xiaomi_miio/light.py @@ -467,7 +467,7 @@ class XiaomiPhilipsBulb(XiaomiPhilipsGenericLight): ) result = await self._try_command( - "Setting brightness and color temperature failed: " "%s bri, %s cct", + "Setting brightness and color temperature failed: %s bri, %s cct", self._light.set_brightness_and_color_temperature, percent_brightness, percent_color_temp, @@ -479,7 +479,7 @@ class XiaomiPhilipsBulb(XiaomiPhilipsGenericLight): elif ATTR_COLOR_TEMP in kwargs: _LOGGER.debug( - "Setting color temperature: " "%s mireds, %s%% cct", + "Setting color temperature: %s mireds, %s%% cct", color_temp, percent_color_temp, ) @@ -825,14 +825,14 @@ class XiaomiPhilipsMoonlightLamp(XiaomiPhilipsBulb): if ATTR_BRIGHTNESS in kwargs and ATTR_HS_COLOR in kwargs: _LOGGER.debug( - "Setting brightness and color: " "%s %s%%, %s", + "Setting brightness and color: %s %s%%, %s", brightness, percent_brightness, rgb, ) result = await self._try_command( - "Setting brightness and color failed: " "%s bri, %s color", + "Setting brightness and color failed: %s bri, %s color", self._light.set_brightness_and_rgb, percent_brightness, rgb, @@ -853,7 +853,7 @@ class XiaomiPhilipsMoonlightLamp(XiaomiPhilipsBulb): ) result = await self._try_command( - "Setting brightness and color temperature failed: " "%s bri, %s cct", + "Setting brightness and color temperature failed: %s bri, %s cct", self._light.set_brightness_and_color_temperature, percent_brightness, percent_color_temp, @@ -875,7 +875,7 @@ class XiaomiPhilipsMoonlightLamp(XiaomiPhilipsBulb): elif ATTR_COLOR_TEMP in kwargs: _LOGGER.debug( - "Setting color temperature: " "%s mireds, %s%% cct", + "Setting color temperature: %s mireds, %s%% cct", color_temp, percent_color_temp, ) diff --git a/homeassistant/components/xiaomi_miio/vacuum.py b/homeassistant/components/xiaomi_miio/vacuum.py index bc703c769a5..4ef34e8ff56 100644 --- a/homeassistant/components/xiaomi_miio/vacuum.py +++ b/homeassistant/components/xiaomi_miio/vacuum.py @@ -379,7 +379,7 @@ class MiroboVacuum(StateVacuumDevice): fan_speed = int(fan_speed) except ValueError as exc: _LOGGER.error( - "Fan speed step not recognized (%s). " "Valid speeds are: %s", + "Fan speed step not recognized (%s). Valid speeds are: %s", exc, self.fan_speed_list, ) diff --git a/homeassistant/components/xmpp/notify.py b/homeassistant/components/xmpp/notify.py index d26b1eed151..28d42698657 100644 --- a/homeassistant/components/xmpp/notify.py +++ b/homeassistant/components/xmpp/notify.py @@ -201,7 +201,7 @@ async def async_send_message( except FileTooBig as ex: _LOGGER.error("File too big for server, could not upload file %s", ex) except UploadServiceNotFound as ex: - _LOGGER.error("UploadServiceNotFound: " " could not upload file %s", ex) + _LOGGER.error("UploadServiceNotFound, could not upload file %s", ex) except FileUploadError as ex: _LOGGER.error("FileUploadError, could not upload file %s", ex) except requests.exceptions.SSLError as ex: diff --git a/homeassistant/components/xs1/__init__.py b/homeassistant/components/xs1/__init__.py index aeb6204265b..1fbcb49d0c9 100644 --- a/homeassistant/components/xs1/__init__.py +++ b/homeassistant/components/xs1/__init__.py @@ -63,8 +63,7 @@ def setup(hass, config): ) except ConnectionError as error: _LOGGER.error( - "Failed to create XS1 API client " "because of a connection error: %s", - error, + "Failed to create XS1 API client because of a connection error: %s", error, ) return False diff --git a/homeassistant/components/yeelight/light.py b/homeassistant/components/yeelight/light.py index 0039755a6af..c40ead2a892 100644 --- a/homeassistant/components/yeelight/light.py +++ b/homeassistant/components/yeelight/light.py @@ -298,7 +298,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): else: _lights_setup_helper(YeelightGenericLight) _LOGGER.warning( - "Cannot determine device type for %s, %s. " "Falling back to white only", + "Cannot determine device type for %s, %s. Falling back to white only", device.ipaddr, device.name, ) @@ -743,7 +743,7 @@ class YeelightGenericLight(Light): self.set_music_mode(self.config[CONF_MODE_MUSIC]) except BulbException as ex: _LOGGER.error( - "Unable to turn on music mode," "consider disabling it: %s", ex + "Unable to turn on music mode, consider disabling it: %s", ex ) try: diff --git a/homeassistant/components/yr/sensor.py b/homeassistant/components/yr/sensor.py index f8fbc97962f..c9392561fc8 100644 --- a/homeassistant/components/yr/sensor.py +++ b/homeassistant/components/yr/sensor.py @@ -160,7 +160,7 @@ class YrData: def __init__(self, hass, coordinates, forecast, devices): """Initialize the data object.""" self._url = ( - "https://aa015h6buqvih86i1.api.met.no/" "weatherapi/locationforecast/1.9/" + "https://aa015h6buqvih86i1.api.met.no/weatherapi/locationforecast/1.9/" ) self._urlparams = coordinates self._forecast = forecast diff --git a/homeassistant/components/zigbee/__init__.py b/homeassistant/components/zigbee/__init__.py index d63a713045b..475d63a5c3b 100644 --- a/homeassistant/components/zigbee/__init__.py +++ b/homeassistant/components/zigbee/__init__.py @@ -335,7 +335,7 @@ class ZigBeeDigitalIn(Entity): pin_name = DIGITAL_PINS[self._config.pin] if pin_name not in sample: _LOGGER.warning( - "Pin %s (%s) was not in the sample provided by Zigbee device " "%s.", + "Pin %s (%s) was not in the sample provided by Zigbee device %s.", self._config.pin, pin_name, hexlify(self._config.address), diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index cb494b5fa6f..32348a8becc 100644 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -275,7 +275,7 @@ def nice_print_node(node): value_id: _obj_to_dict(value) for value_id, value in node.values.items() } - _LOGGER.info("FOUND NODE %s \n" "%s", node.product_name, node_dict) + _LOGGER.info("FOUND NODE %s \n%s", node.product_name, node_dict) def get_config_value(node, value_index, tries=5): @@ -476,7 +476,7 @@ async def async_setup_entry(hass, config_entry): @callback def _on_timeout(sec): _LOGGER.warning( - "Z-Wave node %d not ready after %d seconds, " "continuing anyway", + "Z-Wave node %d not ready after %d seconds, continuing anyway", entity.node_id, sec, ) @@ -526,7 +526,7 @@ async def async_setup_entry(hass, config_entry): def network_complete(): """Handle the querying of all nodes on network.""" _LOGGER.info( - "Z-Wave network is complete. All nodes on the network " "have been queried" + "Z-Wave network is complete. All nodes on the network have been queried" ) hass.bus.fire(const.EVENT_NETWORK_COMPLETE) @@ -684,7 +684,7 @@ async def async_setup_entry(hass, config_entry): if value.type == const.TYPE_BOOL: value.data = int(selection == "True") _LOGGER.info( - "Setting config parameter %s on Node %s " "with bool selection %s", + "Setting config parameter %s on Node %s with bool selection %s", param, node_id, str(selection), @@ -693,7 +693,7 @@ async def async_setup_entry(hass, config_entry): if value.type == const.TYPE_LIST: value.data = str(selection) _LOGGER.info( - "Setting config parameter %s on Node %s " "with list selection %s", + "Setting config parameter %s on Node %s with list selection %s", param, node_id, str(selection), @@ -712,7 +712,7 @@ async def async_setup_entry(hass, config_entry): return value.data = int(selection) _LOGGER.info( - "Setting config parameter %s on Node %s " "with selection %s", + "Setting config parameter %s on Node %s with selection %s", param, node_id, selection, @@ -720,7 +720,7 @@ async def async_setup_entry(hass, config_entry): return node.set_config_param(param, selection, size) _LOGGER.info( - "Setting unknown config parameter %s on Node %s " "with selection %s", + "Setting unknown config parameter %s on Node %s with selection %s", param, node_id, selection, @@ -831,7 +831,7 @@ async def async_setup_entry(hass, config_entry): ) return _LOGGER.info( - "Node %s on instance %s does not have resettable " "meters.", + "Node %s on instance %s does not have resettable meters.", node_id, instance, ) diff --git a/homeassistant/components/zwave/lock.py b/homeassistant/components/zwave/lock.py index 9d7b2bffddd..f84b1b5cfd4 100644 --- a/homeassistant/components/zwave/lock.py +++ b/homeassistant/components/zwave/lock.py @@ -270,7 +270,7 @@ class ZwaveLock(ZWaveDeviceEntity, LockDevice): workaround = DEVICE_MAPPINGS[specific_sensor_key] if workaround & WORKAROUND_V2BTZE: self._v2btze = 1 - _LOGGER.debug("Polycontrol Danalock v2 BTZE " "workaround enabled") + _LOGGER.debug("Polycontrol Danalock v2 BTZE workaround enabled") if workaround & WORKAROUND_DEVICE_STATE: self._state_workaround = True _LOGGER.debug("Notification device state workaround enabled") @@ -299,7 +299,7 @@ class ZwaveLock(ZWaveDeviceEntity, LockDevice): ): self._state = LOCK_STATUS.get(str(notification_data)) _LOGGER.debug( - "Lock state set from Access Control value and is %s, " "get=%s", + "Lock state set from Access Control value and is %s, get=%s", str(notification_data), self.state, ) diff --git a/homeassistant/config.py b/homeassistant/config.py index c3a97a1184c..ee3ccc15f81 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -209,7 +209,7 @@ CORE_CONFIG_SCHEMA = CUSTOMIZE_CONFIG_SCHEMA.extend( { CONF_TYPE: vol.NotIn( ["insecure_example"], - "The insecure_example mfa module" " is for testing only.", + "The insecure_example mfa module is for testing only.", ) } ) diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index c02c49ce311..02853f7615b 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -192,7 +192,7 @@ def async_numeric_state( fvalue = float(value) except ValueError: _LOGGER.warning( - "Value cannot be processed as a number: %s " "(Offending entity: %s)", + "Value cannot be processed as a number: %s (Offending entity: %s)", entity, value, ) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 035e1f678bf..bcf0d42df70 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -544,7 +544,7 @@ def socket_timeout(value: Optional[Any]) -> object: float_value = float(value) if float_value > 0.0: return float_value - raise vol.Invalid("Invalid socket timeout value." " float > 0.0 required.") + raise vol.Invalid("Invalid socket timeout value. float > 0.0 required.") except Exception as _: raise vol.Invalid("Invalid socket timeout: {err}".format(err=_)) diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 5fd88729f08..9b82eb76dec 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -459,7 +459,7 @@ class EntityPlatform: self._process_updates = asyncio.Lock() if self._process_updates.locked(): self.logger.warning( - "Updating %s %s took longer than the scheduled update " "interval %s", + "Updating %s %s took longer than the scheduled update interval %s", self.platform_name, self.domain, self.scan_interval, diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 4bdee750034..b27120e1825 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -615,7 +615,7 @@ def distance(hass, *args): if latitude is None or longitude is None: _LOGGER.warning( - "Distance:Unable to process latitude and " "longitude: %s, %s", + "Distance:Unable to process latitude and longitude: %s, %s", value, value_2, ) diff --git a/homeassistant/loader.py b/homeassistant/loader.py index de05c944aaf..a14a5209840 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -407,7 +407,7 @@ def _load_file( if str(err) not in white_listed_errors: _LOGGER.exception( - ("Error loading %s. Make sure all " "dependencies are installed"), + ("Error loading %s. Make sure all dependencies are installed"), path, ) diff --git a/homeassistant/scripts/ensure_config.py b/homeassistant/scripts/ensure_config.py index cb2c4088049..0b5d1104997 100644 --- a/homeassistant/scripts/ensure_config.py +++ b/homeassistant/scripts/ensure_config.py @@ -11,9 +11,7 @@ from homeassistant.core import HomeAssistant def run(args): """Handle ensure config commandline script.""" parser = argparse.ArgumentParser( - description=( - "Ensure a Home Assistant config exists, " "creates one if necessary." - ) + description=("Ensure a Home Assistant config exists, creates one if necessary.") ) parser.add_argument( "-c", diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 2424f5fc465..f0d1e492b99 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -75,7 +75,7 @@ async def _async_process_dependencies( if failed: _LOGGER.error( - "Unable to set up dependencies of %s. " "Setup failed for dependencies: %s", + "Unable to set up dependencies of %s. Setup failed for dependencies: %s", name, ", ".join(failed), ) @@ -108,14 +108,14 @@ async def _async_setup_component( await loader.async_component_dependencies(hass, domain) except loader.IntegrationNotFound as err: _LOGGER.error( - "Not setting up %s because we are unable to resolve " "(sub)dependency %s", + "Not setting up %s because we are unable to resolve (sub)dependency %s", domain, err.domain, ) return False except loader.CircularDependency as err: _LOGGER.error( - "Not setting up %s because it contains a circular dependency: " "%s -> %s", + "Not setting up %s because it contains a circular dependency: %s -> %s", domain, err.from_domain, err.to_domain, diff --git a/homeassistant/util/dt.py b/homeassistant/util/dt.py index 791b36a4236..49f9d7d5f99 100644 --- a/homeassistant/util/dt.py +++ b/homeassistant/util/dt.py @@ -253,7 +253,7 @@ def find_next_time_expression_time( including daylight saving time. """ if not seconds or not minutes or not hours: - raise ValueError("Cannot find a next time: Time expression never " "matches!") + raise ValueError("Cannot find a next time: Time expression never matches!") def _lower_bound(arr: List[int], cmp: int) -> Optional[int]: """Return the first value in arr greater or equal to cmp. diff --git a/homeassistant/util/yaml/loader.py b/homeassistant/util/yaml/loader.py index 65422f231ba..6b921ade961 100644 --- a/homeassistant/util/yaml/loader.py +++ b/homeassistant/util/yaml/loader.py @@ -258,7 +258,7 @@ def _load_secret_yaml(secret_path: str) -> JSON_TYPE: _LOGGER.setLevel(logging.DEBUG) else: _LOGGER.error( - "secrets.yaml: 'logger: debug' expected," " but 'logger: %s' found", + "secrets.yaml: 'logger: debug' expected, but 'logger: %s' found", logger, ) del secrets["logger"] @@ -276,7 +276,7 @@ def secret_yaml(loader: SafeLineLoader, node: yaml.nodes.Node) -> JSON_TYPE: if node.value in secrets: _LOGGER.debug( - "Secret %s retrieved from secrets.yaml in " "folder %s", + "Secret %s retrieved from secrets.yaml in folder %s", node.value, secret_path, ) diff --git a/script/hassfest/codeowners.py b/script/hassfest/codeowners.py index f6970b50a3c..cfbd112100a 100644 --- a/script/hassfest/codeowners.py +++ b/script/hassfest/codeowners.py @@ -66,7 +66,7 @@ def validate(integrations: Dict[str, Integration], config: Config): if fp.read().strip() != content: config.add_error( "codeowners", - "File CODEOWNERS is not up to date. " "Run python3 -m script.hassfest", + "File CODEOWNERS is not up to date. Run python3 -m script.hassfest", fixable=True, ) return diff --git a/script/hassfest/ssdp.py b/script/hassfest/ssdp.py index 7578d52ed4f..5ee2076ecf4 100644 --- a/script/hassfest/ssdp.py +++ b/script/hassfest/ssdp.py @@ -68,7 +68,7 @@ def validate(integrations: Dict[str, Integration], config: Config): if fp.read().strip() != content: config.add_error( "ssdp", - "File ssdp.py is not up to date. " "Run python3 -m script.hassfest", + "File ssdp.py is not up to date. Run python3 -m script.hassfest", fixable=True, ) return diff --git a/script/hassfest/zeroconf.py b/script/hassfest/zeroconf.py index f864d3e0327..2a1bb936871 100644 --- a/script/hassfest/zeroconf.py +++ b/script/hassfest/zeroconf.py @@ -122,7 +122,7 @@ def validate(integrations: Dict[str, Integration], config: Config): if current != content: config.add_error( "zeroconf", - "File zeroconf.py is not up to date. " "Run python3 -m script.hassfest", + "File zeroconf.py is not up to date. Run python3 -m script.hassfest", fixable=True, ) return diff --git a/tests/components/aprs/test_device_tracker.py b/tests/components/aprs/test_device_tracker.py index d02188a3079..dc0cf09f28d 100644 --- a/tests/components/aprs/test_device_tracker.py +++ b/tests/components/aprs/test_device_tracker.py @@ -302,7 +302,7 @@ def test_aprs_listener_rx_msg_no_position(): def test_setup_scanner(): """Test setup_scanner.""" with patch( - "homeassistant.components." "aprs.device_tracker.AprsListenerThread" + "homeassistant.components.aprs.device_tracker.AprsListenerThread" ) as listener: hass = get_test_home_assistant() hass.start() diff --git a/tests/components/automation/test_time.py b/tests/components/automation/test_time.py index e12ce6684d2..d84fd18fb6b 100644 --- a/tests/components/automation/test_time.py +++ b/tests/components/automation/test_time.py @@ -39,7 +39,7 @@ async def test_if_fires_using_at(hass, calls): "action": { "service": "test.automation", "data_template": { - "some": "{{ trigger.platform }} - " "{{ trigger.now.hour }}" + "some": "{{ trigger.platform }} - {{ trigger.now.hour }}" }, }, } diff --git a/tests/components/binary_sensor/test_init.py b/tests/components/binary_sensor/test_init.py index 9759d3281e6..2299ba4c9a2 100644 --- a/tests/components/binary_sensor/test_init.py +++ b/tests/components/binary_sensor/test_init.py @@ -14,12 +14,11 @@ class TestBinarySensor(unittest.TestCase): sensor = binary_sensor.BinarySensorDevice() assert STATE_OFF == sensor.state with mock.patch( - "homeassistant.components.binary_sensor." "BinarySensorDevice.is_on", + "homeassistant.components.binary_sensor.BinarySensorDevice.is_on", new=False, ): assert STATE_OFF == binary_sensor.BinarySensorDevice().state with mock.patch( - "homeassistant.components.binary_sensor." "BinarySensorDevice.is_on", - new=True, + "homeassistant.components.binary_sensor.BinarySensorDevice.is_on", new=True, ): assert STATE_ON == binary_sensor.BinarySensorDevice().state diff --git a/tests/components/buienradar/test_camera.py b/tests/components/buienradar/test_camera.py index 2dd63583741..6faac295d54 100644 --- a/tests/components/buienradar/test_camera.py +++ b/tests/components/buienradar/test_camera.py @@ -12,7 +12,7 @@ EPSILON_DELTA = 0.0000000001 def radar_map_url(dim: int = 512) -> str: """Build map url, defaulting to 512 wide (as in component).""" - return ("https://api.buienradar.nl/" "image/1.0/RadarMapNL?w={dim}&h={dim}").format( + return ("https://api.buienradar.nl/image/1.0/RadarMapNL?w={dim}&h={dim}").format( dim=dim ) diff --git a/tests/components/camera/test_init.py b/tests/components/camera/test_init.py index 4bd13c35ad8..de48a1d48f3 100644 --- a/tests/components/camera/test_init.py +++ b/tests/components/camera/test_init.py @@ -119,7 +119,7 @@ class TestGetImage: def test_get_image_without_exists_camera(self): """Try to get image without exists camera.""" with patch( - "homeassistant.helpers.entity_component.EntityComponent." "get_entity", + "homeassistant.helpers.entity_component.EntityComponent.get_entity", return_value=None, ), pytest.raises(HomeAssistantError): asyncio.run_coroutine_threadsafe( diff --git a/tests/components/cloud/test_http_api.py b/tests/components/cloud/test_http_api.py index 515489035fb..b82b2b5481e 100644 --- a/tests/components/cloud/test_http_api.py +++ b/tests/components/cloud/test_http_api.py @@ -321,7 +321,7 @@ async def test_websocket_status( client = await hass_ws_client(hass) with patch.dict( - "homeassistant.components.google_assistant.const." "DOMAIN_TO_GOOGLE_TYPES", + "homeassistant.components.google_assistant.const.DOMAIN_TO_GOOGLE_TYPES", {"light": None}, clear=True, ), patch.dict( @@ -683,7 +683,7 @@ async def test_list_google_entities(hass, hass_ws_client, setup_api, mock_cloud_ hass, MockConfig(should_expose=lambda *_: False), State("light.kitchen", "on") ) with patch( - "homeassistant.components.google_assistant.helpers" ".async_get_entities", + "homeassistant.components.google_assistant.helpers.async_get_entities", return_value=[entity], ): await client.send_json({"id": 5, "type": "cloud/google_assistant/entities"}) @@ -779,7 +779,7 @@ async def test_list_alexa_entities(hass, hass_ws_client, setup_api, mock_cloud_l hass, MagicMock(entity_config={}), State("light.kitchen", "on") ) with patch( - "homeassistant.components.alexa.entities" ".async_get_entities", + "homeassistant.components.alexa.entities.async_get_entities", return_value=[entity], ): await client.send_json({"id": 5, "type": "cloud/alexa/entities"}) diff --git a/tests/components/demo/test_media_player.py b/tests/components/demo/test_media_player.py index 60402844d24..a70e7ea4b5d 100644 --- a/tests/components/demo/test_media_player.py +++ b/tests/components/demo/test_media_player.py @@ -209,7 +209,7 @@ class TestDemoMediaPlayer(unittest.TestCase): assert "some_id" == state.attributes.get("media_content_id") @patch( - "homeassistant.components.demo.media_player.DemoYoutubePlayer." "media_seek", + "homeassistant.components.demo.media_player.DemoYoutubePlayer.media_seek", autospec=True, ) def test_seek(self, mock_seek): diff --git a/tests/components/device_tracker/test_init.py b/tests/components/device_tracker/test_init.py index e839a88536e..c82f36f92e7 100644 --- a/tests/components/device_tracker/test_init.py +++ b/tests/components/device_tracker/test_init.py @@ -77,7 +77,7 @@ async def test_reading_broken_yaml_config(hass): "badkey.yaml": "@:\n name: Device", "noname.yaml": "my_device:\n", "allok.yaml": "My Device:\n name: Device", - "oneok.yaml": ("My Device!:\n name: Device\n" "bad_device:\n nme: Device"), + "oneok.yaml": ("My Device!:\n name: Device\nbad_device:\n nme: Device"), } args = {"hass": hass, "consider_home": timedelta(seconds=60)} with patch_yaml_files(files): @@ -341,7 +341,7 @@ async def test_group_all_devices(hass, mock_device_tracker_conf): assert (entity_id,) == state.attributes.get(ATTR_ENTITY_ID) -@patch("homeassistant.components.device_tracker.legacy." "DeviceTracker.async_see") +@patch("homeassistant.components.device_tracker.legacy.DeviceTracker.async_see") async def test_see_service(mock_see, hass): """Test the see service with a unicode dev_id and NO MAC.""" with assert_setup_component(1, device_tracker.DOMAIN): diff --git a/tests/components/emulated_hue/test_hue_api.py b/tests/components/emulated_hue/test_hue_api.py index 6c1a17c0538..2fb5c48e768 100644 --- a/tests/components/emulated_hue/test_hue_api.py +++ b/tests/components/emulated_hue/test_hue_api.py @@ -56,7 +56,7 @@ def hass_hue(loop, hass): ) ) - with patch("homeassistant.components" ".emulated_hue.UPNPResponderThread"): + with patch("homeassistant.components.emulated_hue.UPNPResponderThread"): loop.run_until_complete( setup.async_setup_component( hass, diff --git a/tests/components/emulated_hue/test_init.py b/tests/components/emulated_hue/test_init.py index 09c0731e4cd..6fa6d969539 100644 --- a/tests/components/emulated_hue/test_init.py +++ b/tests/components/emulated_hue/test_init.py @@ -14,7 +14,7 @@ def test_config_google_home_entity_id_to_number(): "homeassistant.components.emulated_hue.load_json", return_value={"1": "light.test2"}, ) as json_loader: - with patch("homeassistant.components.emulated_hue" ".save_json") as json_saver: + with patch("homeassistant.components.emulated_hue.save_json") as json_saver: number = conf.entity_id_to_number("light.test") assert number == "2" @@ -48,7 +48,7 @@ def test_config_google_home_entity_id_to_number_altered(): "homeassistant.components.emulated_hue.load_json", return_value={"21": "light.test2"}, ) as json_loader: - with patch("homeassistant.components.emulated_hue" ".save_json") as json_saver: + with patch("homeassistant.components.emulated_hue.save_json") as json_saver: number = conf.entity_id_to_number("light.test") assert number == "22" assert json_saver.call_count == 1 @@ -80,7 +80,7 @@ def test_config_google_home_entity_id_to_number_empty(): with patch( "homeassistant.components.emulated_hue.load_json", return_value={} ) as json_loader: - with patch("homeassistant.components.emulated_hue" ".save_json") as json_saver: + with patch("homeassistant.components.emulated_hue.save_json") as json_saver: number = conf.entity_id_to_number("light.test") assert number == "1" assert json_saver.call_count == 1 diff --git a/tests/components/emulated_hue/test_upnp.py b/tests/components/emulated_hue/test_upnp.py index 2fc9d903d3b..5897b80659a 100644 --- a/tests/components/emulated_hue/test_upnp.py +++ b/tests/components/emulated_hue/test_upnp.py @@ -32,7 +32,7 @@ class TestEmulatedHue(unittest.TestCase): hass, http.DOMAIN, {http.DOMAIN: {http.CONF_SERVER_PORT: HTTP_SERVER_PORT}} ) - with patch("homeassistant.components" ".emulated_hue.UPNPResponderThread"): + with patch("homeassistant.components.emulated_hue.UPNPResponderThread"): setup.setup_component( hass, emulated_hue.DOMAIN, diff --git a/tests/components/facebox/test_image_processing.py b/tests/components/facebox/test_image_processing.py index d82f70e7ca1..6b248ba1c3c 100644 --- a/tests/components/facebox/test_image_processing.py +++ b/tests/components/facebox/test_image_processing.py @@ -81,7 +81,7 @@ VALID_CONFIG = { def mock_healthybox(): """Mock fb.check_box_health.""" check_box_health = ( - "homeassistant.components.facebox.image_processing." "check_box_health" + "homeassistant.components.facebox.image_processing.check_box_health" ) with patch(check_box_health, return_value=MOCK_BOX_ID) as _mock_healthybox: yield _mock_healthybox diff --git a/tests/components/feedreader/test_init.py b/tests/components/feedreader/test_init.py index 62412e53900..048be11e079 100644 --- a/tests/components/feedreader/test_init.py +++ b/tests/components/feedreader/test_init.py @@ -50,7 +50,7 @@ class TestFeedreaderComponent(unittest.TestCase): def test_setup_one_feed(self): """Test the general setup of this component.""" with patch( - "homeassistant.components.feedreader." "track_time_interval" + "homeassistant.components.feedreader.track_time_interval" ) as track_method: assert setup_component(self.hass, feedreader.DOMAIN, VALID_CONFIG_1) track_method.assert_called_once_with( @@ -60,7 +60,7 @@ class TestFeedreaderComponent(unittest.TestCase): def test_setup_scan_interval(self): """Test the setup of this component with scan interval.""" with patch( - "homeassistant.components.feedreader." "track_time_interval" + "homeassistant.components.feedreader.track_time_interval" ) as track_method: assert setup_component(self.hass, feedreader.DOMAIN, VALID_CONFIG_2) track_method.assert_called_once_with( @@ -88,7 +88,7 @@ class TestFeedreaderComponent(unittest.TestCase): data_file = self.hass.config.path("{}.pickle".format(feedreader.DOMAIN)) storage = StoredData(data_file) with patch( - "homeassistant.components.feedreader." "track_time_interval" + "homeassistant.components.feedreader.track_time_interval" ) as track_method: manager = FeedManager( feed_data, DEFAULT_SCAN_INTERVAL, max_entries, self.hass, storage @@ -131,7 +131,7 @@ class TestFeedreaderComponent(unittest.TestCase): # Must patch 'get_timestamp' method because the timestamp is stored # with the URL which in these tests is the raw XML data. with patch( - "homeassistant.components.feedreader.StoredData." "get_timestamp", + "homeassistant.components.feedreader.StoredData.get_timestamp", return_value=time.struct_time((2018, 4, 30, 5, 10, 0, 0, 120, 0)), ): manager2, events2 = self.setup_manager(feed_data2) @@ -139,7 +139,7 @@ class TestFeedreaderComponent(unittest.TestCase): # 3. Run feed_data3 = load_fixture("feedreader1.xml") with patch( - "homeassistant.components.feedreader.StoredData." "get_timestamp", + "homeassistant.components.feedreader.StoredData.get_timestamp", return_value=time.struct_time((2018, 4, 30, 5, 11, 0, 0, 120, 0)), ): manager3, events3 = self.setup_manager(feed_data3) diff --git a/tests/components/filter/test_sensor.py b/tests/components/filter/test_sensor.py index a5f23b464dd..9ae4245ed70 100644 --- a/tests/components/filter/test_sensor.py +++ b/tests/components/filter/test_sensor.py @@ -117,11 +117,11 @@ class TestFilterSensor(unittest.TestCase): } with patch( - "homeassistant.components.history." "state_changes_during_period", + "homeassistant.components.history.state_changes_during_period", return_value=fake_states, ): with patch( - "homeassistant.components.history." "get_last_state_changes", + "homeassistant.components.history.get_last_state_changes", return_value=fake_states, ): with assert_setup_component(1, "sensor"): @@ -165,11 +165,11 @@ class TestFilterSensor(unittest.TestCase): ] } with patch( - "homeassistant.components.history." "state_changes_during_period", + "homeassistant.components.history.state_changes_during_period", return_value=fake_states, ): with patch( - "homeassistant.components.history." "get_last_state_changes", + "homeassistant.components.history.get_last_state_changes", return_value=fake_states, ): with assert_setup_component(1, "sensor"): diff --git a/tests/components/hassio/conftest.py b/tests/components/hassio/conftest.py index d7ef853012e..091270c12c4 100644 --- a/tests/components/hassio/conftest.py +++ b/tests/components/hassio/conftest.py @@ -20,7 +20,7 @@ def hassio_env(): "homeassistant.components.hassio.HassIO.is_connected", Mock(return_value=mock_coro({"result": "ok", "data": {}})), ), patch.dict(os.environ, {"HASSIO_TOKEN": "123456"}), patch( - "homeassistant.components.hassio.HassIO." "get_homeassistant_info", + "homeassistant.components.hassio.HassIO.get_homeassistant_info", Mock(side_effect=HassioAPIError()), ): yield diff --git a/tests/components/hassio/test_discovery.py b/tests/components/hassio/test_discovery.py index 2a2fdc4deaa..a0d64440041 100644 --- a/tests/components/hassio/test_discovery.py +++ b/tests/components/hassio/test_discovery.py @@ -40,7 +40,7 @@ async def test_hassio_discovery_startup(hass, aioclient_mock, hassio_client): assert aioclient_mock.call_count == 0 with patch( - "homeassistant.components.mqtt." "config_flow.FlowHandler.async_step_hassio", + "homeassistant.components.mqtt.config_flow.FlowHandler.async_step_hassio", Mock(return_value=mock_coro({"type": "abort"})), ) as mock_mqtt: hass.bus.async_fire(EVENT_HOMEASSISTANT_START) @@ -93,10 +93,10 @@ async def test_hassio_discovery_startup_done(hass, aioclient_mock, hassio_client "homeassistant.components.hassio.HassIO.update_hass_api", Mock(return_value=mock_coro({"result": "ok"})), ), patch( - "homeassistant.components.hassio.HassIO." "get_homeassistant_info", + "homeassistant.components.hassio.HassIO.get_homeassistant_info", Mock(side_effect=HassioAPIError()), ), patch( - "homeassistant.components.mqtt." "config_flow.FlowHandler.async_step_hassio", + "homeassistant.components.mqtt.config_flow.FlowHandler.async_step_hassio", Mock(return_value=mock_coro({"type": "abort"})), ) as mock_mqtt: await hass.async_start() @@ -143,7 +143,7 @@ async def test_hassio_discovery_webhook(hass, aioclient_mock, hassio_client): ) with patch( - "homeassistant.components.mqtt." "config_flow.FlowHandler.async_step_hassio", + "homeassistant.components.mqtt.config_flow.FlowHandler.async_step_hassio", Mock(return_value=mock_coro({"type": "abort"})), ) as mock_mqtt: resp = await hassio_client.post( diff --git a/tests/components/here_travel_time/test_sensor.py b/tests/components/here_travel_time/test_sensor.py index 6b9c52b1042..4456b256f6e 100644 --- a/tests/components/here_travel_time/test_sensor.py +++ b/tests/components/here_travel_time/test_sensor.py @@ -387,7 +387,7 @@ async def test_public_transport(hass, requests_mock_credentials_check): assert sensor.attributes.get(ATTR_DURATION) == 89.16666666666667 assert sensor.attributes.get(ATTR_DISTANCE) == 22.325 assert sensor.attributes.get(ATTR_ROUTE) == ( - "332 - Palmer/Schiller; 332 - Cargo Rd./Delta Cargo; " "332 - Palmer/Schiller" + "332 - Palmer/Schiller; 332 - Cargo Rd./Delta Cargo; 332 - Palmer/Schiller" ) assert sensor.attributes.get(CONF_UNIT_SYSTEM) == "metric" assert sensor.attributes.get(ATTR_DURATION_IN_TRAFFIC) == 89.16666666666667 diff --git a/tests/components/history_stats/test_sensor.py b/tests/components/history_stats/test_sensor.py index 492f928c9f0..588e0df81db 100644 --- a/tests/components/history_stats/test_sensor.py +++ b/tests/components/history_stats/test_sensor.py @@ -50,7 +50,7 @@ class TestHistoryStatsSensor(unittest.TestCase): assert state.state == STATE_UNKNOWN @patch( - "homeassistant.helpers.template.TemplateEnvironment." "is_safe_callable", + "homeassistant.helpers.template.TemplateEnvironment.is_safe_callable", return_value=True, ) def test_period_parsing(self, mock): @@ -58,7 +58,7 @@ class TestHistoryStatsSensor(unittest.TestCase): now = datetime(2019, 1, 1, 23, 30, 0, tzinfo=pytz.utc) with patch("homeassistant.util.dt.now", return_value=now): today = Template( - "{{ now().replace(hour=0).replace(minute=0)" ".replace(second=0) }}", + "{{ now().replace(hour=0).replace(minute=0).replace(second=0) }}", self.hass, ) duration = timedelta(hours=2, minutes=1) @@ -137,7 +137,7 @@ class TestHistoryStatsSensor(unittest.TestCase): assert sensor4._type == "ratio" with patch( - "homeassistant.components.history." "state_changes_during_period", + "homeassistant.components.history.state_changes_during_period", return_value=fake_states, ): with patch("homeassistant.components.history.get_state", return_value=None): diff --git a/tests/components/homekit/test_accessories.py b/tests/components/homekit/test_accessories.py index 0c5810a5b10..f67e0e2478d 100644 --- a/tests/components/homekit/test_accessories.py +++ b/tests/components/homekit/test_accessories.py @@ -97,7 +97,7 @@ async def test_home_accessory(hass, hk_driver): hass.states.async_set(entity_id, "on") await hass.async_block_till_done() with patch( - "homeassistant.components.homekit.accessories." "HomeAccessory.update_state" + "homeassistant.components.homekit.accessories.HomeAccessory.update_state" ) as mock_update_state: await hass.async_add_job(acc.run) await hass.async_block_till_done() @@ -343,7 +343,7 @@ def test_home_driver(): # pair with patch("pyhap.accessory_driver.AccessoryDriver.pair") as mock_pair, patch( - "homeassistant.components.homekit.accessories." "dismiss_setup_message" + "homeassistant.components.homekit.accessories.dismiss_setup_message" ) as mock_dissmiss_msg: driver.pair("client_uuid", "client_public") @@ -352,7 +352,7 @@ def test_home_driver(): # unpair with patch("pyhap.accessory_driver.AccessoryDriver.unpair") as mock_unpair, patch( - "homeassistant.components.homekit.accessories." "show_setup_message" + "homeassistant.components.homekit.accessories.show_setup_message" ) as mock_show_msg: driver.unpair("client_uuid") diff --git a/tests/components/honeywell/test_climate.py b/tests/components/honeywell/test_climate.py index feba4f6410e..058988203e5 100644 --- a/tests/components/honeywell/test_climate.py +++ b/tests/components/honeywell/test_climate.py @@ -27,7 +27,7 @@ class TestHoneywell(unittest.TestCase): """A test class for Honeywell themostats.""" @mock.patch("somecomfort.SomeComfort") - @mock.patch("homeassistant.components.honeywell." "climate.HoneywellUSThermostat") + @mock.patch("homeassistant.components.honeywell.climate.HoneywellUSThermostat") def test_setup_us(self, mock_ht, mock_sc): """Test for the US setup.""" config = { @@ -98,7 +98,7 @@ class TestHoneywell(unittest.TestCase): assert not add_entities.called @mock.patch("somecomfort.SomeComfort") - @mock.patch("homeassistant.components.honeywell." "climate.HoneywellUSThermostat") + @mock.patch("homeassistant.components.honeywell.climate.HoneywellUSThermostat") def _test_us_filtered_devices(self, mock_ht, mock_sc, loc=None, dev=None): """Test for US filtered thermostats.""" config = { @@ -157,7 +157,7 @@ class TestHoneywell(unittest.TestCase): assert [mock.sentinel.loc2dev1] == devices @mock.patch("evohomeclient.EvohomeClient") - @mock.patch("homeassistant.components.honeywell.climate." "HoneywellUSThermostat") + @mock.patch("homeassistant.components.honeywell.climate.HoneywellUSThermostat") def test_eu_setup_full_config(self, mock_round, mock_evo): """Test the EU setup with complete configuration.""" config = { @@ -184,7 +184,7 @@ class TestHoneywell(unittest.TestCase): assert 2 == add_entities.call_count @mock.patch("evohomeclient.EvohomeClient") - @mock.patch("homeassistant.components.honeywell.climate." "HoneywellUSThermostat") + @mock.patch("homeassistant.components.honeywell.climate.HoneywellUSThermostat") def test_eu_setup_partial_config(self, mock_round, mock_evo): """Test the EU setup with partial configuration.""" config = { @@ -206,7 +206,7 @@ class TestHoneywell(unittest.TestCase): ) @mock.patch("evohomeclient.EvohomeClient") - @mock.patch("homeassistant.components.honeywell.climate." "HoneywellUSThermostat") + @mock.patch("homeassistant.components.honeywell.climate.HoneywellUSThermostat") def test_eu_setup_bad_temp(self, mock_round, mock_evo): """Test the EU setup with invalid temperature.""" config = { @@ -219,7 +219,7 @@ class TestHoneywell(unittest.TestCase): honeywell.PLATFORM_SCHEMA(config) @mock.patch("evohomeclient.EvohomeClient") - @mock.patch("homeassistant.components.honeywell.climate." "HoneywellUSThermostat") + @mock.patch("homeassistant.components.honeywell.climate.HoneywellUSThermostat") def test_eu_setup_error(self, mock_round, mock_evo): """Test the EU setup with errors.""" config = { diff --git a/tests/components/ign_sismologia/test_geo_location.py b/tests/components/ign_sismologia/test_geo_location.py index 2d869c1a062..0f0191f3b82 100644 --- a/tests/components/ign_sismologia/test_geo_location.py +++ b/tests/components/ign_sismologia/test_geo_location.py @@ -94,7 +94,7 @@ async def test_setup(hass): # Patching 'utcnow' to gain more control over the timed update. utcnow = dt_util.utcnow() with patch("homeassistant.util.dt.utcnow", return_value=utcnow), patch( - "georss_ign_sismologia_client." "IgnSismologiaFeed" + "georss_ign_sismologia_client.IgnSismologiaFeed" ) as mock_feed: mock_feed.return_value.update.return_value = ( "OK", @@ -199,7 +199,7 @@ async def test_setup_with_custom_location(hass): # Set up some mock feed entries for this test. mock_entry_1 = _generate_mock_feed_entry("1234", "Title 1", 20.5, (38.1, -3.1)) - with patch("georss_ign_sismologia_client." "IgnSismologiaFeed") as mock_feed: + with patch("georss_ign_sismologia_client.IgnSismologiaFeed") as mock_feed: mock_feed.return_value.update.return_value = "OK", [mock_entry_1] with assert_setup_component(1, geo_location.DOMAIN): diff --git a/tests/components/islamic_prayer_times/test_sensor.py b/tests/components/islamic_prayer_times/test_sensor.py index 389fa43945e..3151b030637 100644 --- a/tests/components/islamic_prayer_times/test_sensor.py +++ b/tests/components/islamic_prayer_times/test_sensor.py @@ -175,7 +175,7 @@ async def test_islamic_prayer_times_sensor_update(hass): future = midnight_dt + timedelta(days=1, minutes=1) with patch( - "homeassistant.components.islamic_prayer_times.sensor" ".dt_util.utcnow", + "homeassistant.components.islamic_prayer_times.sensor.dt_util.utcnow", return_value=future, ): diff --git a/tests/components/logentries/test_init.py b/tests/components/logentries/test_init.py index 7125822e93e..f850a7dd62b 100644 --- a/tests/components/logentries/test_init.py +++ b/tests/components/logentries/test_init.py @@ -70,7 +70,7 @@ class TestLogentries(unittest.TestCase): } ] payload = { - "host": "https://webhook.logentries.com/noformat/" "logs/token", + "host": "https://webhook.logentries.com/noformat/logs/token", "event": body, } self.handler_method(event) diff --git a/tests/components/mailbox/test_init.py b/tests/components/mailbox/test_init.py index 258e0cc7ebf..6536e1317fa 100644 --- a/tests/components/mailbox/test_init.py +++ b/tests/components/mailbox/test_init.py @@ -38,7 +38,7 @@ async def test_get_messages_from_mailbox(mock_http_client): async def test_get_media_from_mailbox(mock_http_client): """Get audio from mailbox.""" mp3sha = "3f67c4ea33b37d1710f772a26dd3fb43bb159d50" - msgtxt = "Message 1. " "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + msgtxt = "Message 1. Lorem ipsum dolor sit amet, consectetur adipiscing elit. " msgsha = sha1(msgtxt.encode("utf-8")).hexdigest() url = "/api/mailbox/media/DemoMailbox/%s" % (msgsha) @@ -50,8 +50,8 @@ async def test_get_media_from_mailbox(mock_http_client): async def test_delete_from_mailbox(mock_http_client): """Get audio from mailbox.""" - msgtxt1 = "Message 1. " "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " - msgtxt2 = "Message 3. " "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + msgtxt1 = "Message 1. Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + msgtxt2 = "Message 3. Lorem ipsum dolor sit amet, consectetur adipiscing elit. " msgsha1 = sha1(msgtxt1.encode("utf-8")).hexdigest() msgsha2 = sha1(msgtxt2.encode("utf-8")).hexdigest() diff --git a/tests/components/manual/test_alarm_control_panel.py b/tests/components/manual/test_alarm_control_panel.py index 1b06477750b..f1596277e3c 100644 --- a/tests/components/manual/test_alarm_control_panel.py +++ b/tests/components/manual/test_alarm_control_panel.py @@ -111,7 +111,7 @@ async def test_arm_home_with_pending(hass): future = dt_util.utcnow() + timedelta(seconds=1) with patch( - ("homeassistant.components.manual.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -252,7 +252,7 @@ async def test_arm_away_with_pending(hass): future = dt_util.utcnow() + timedelta(seconds=1) with patch( - ("homeassistant.components.manual.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -367,7 +367,7 @@ async def test_arm_night_with_pending(hass): future = dt_util.utcnow() + timedelta(seconds=1) with patch( - ("homeassistant.components.manual.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -432,7 +432,7 @@ async def test_trigger_no_pending(hass): future = dt_util.utcnow() + timedelta(seconds=60) with patch( - ("homeassistant.components.manual.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -474,7 +474,7 @@ async def test_trigger_with_delay(hass): future = dt_util.utcnow() + timedelta(seconds=1) with patch( - ("homeassistant.components.manual.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -563,7 +563,7 @@ async def test_trigger_with_pending(hass): future = dt_util.utcnow() + timedelta(seconds=2) with patch( - ("homeassistant.components.manual.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -574,7 +574,7 @@ async def test_trigger_with_pending(hass): future = dt_util.utcnow() + timedelta(seconds=5) with patch( - ("homeassistant.components.manual.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -618,7 +618,7 @@ async def test_trigger_with_unused_specific_delay(hass): future = dt_util.utcnow() + timedelta(seconds=5) with patch( - ("homeassistant.components.manual.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -662,7 +662,7 @@ async def test_trigger_with_specific_delay(hass): future = dt_util.utcnow() + timedelta(seconds=1) with patch( - ("homeassistant.components.manual.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -706,7 +706,7 @@ async def test_trigger_with_pending_and_delay(hass): future = dt_util.utcnow() + timedelta(seconds=1) with patch( - ("homeassistant.components.manual.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -718,7 +718,7 @@ async def test_trigger_with_pending_and_delay(hass): future += timedelta(seconds=1) with patch( - ("homeassistant.components.manual.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -763,7 +763,7 @@ async def test_trigger_with_pending_and_specific_delay(hass): future = dt_util.utcnow() + timedelta(seconds=1) with patch( - ("homeassistant.components.manual.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -775,7 +775,7 @@ async def test_trigger_with_pending_and_specific_delay(hass): future += timedelta(seconds=1) with patch( - ("homeassistant.components.manual.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -808,7 +808,7 @@ async def test_armed_home_with_specific_pending(hass): future = dt_util.utcnow() + timedelta(seconds=2) with patch( - ("homeassistant.components.manual.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -840,7 +840,7 @@ async def test_armed_away_with_specific_pending(hass): future = dt_util.utcnow() + timedelta(seconds=2) with patch( - ("homeassistant.components.manual.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -872,7 +872,7 @@ async def test_armed_night_with_specific_pending(hass): future = dt_util.utcnow() + timedelta(seconds=2) with patch( - ("homeassistant.components.manual.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -906,7 +906,7 @@ async def test_trigger_with_specific_pending(hass): future = dt_util.utcnow() + timedelta(seconds=2) with patch( - ("homeassistant.components.manual.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -916,7 +916,7 @@ async def test_trigger_with_specific_pending(hass): future = dt_util.utcnow() + timedelta(seconds=5) with patch( - ("homeassistant.components.manual.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -951,7 +951,7 @@ async def test_trigger_with_disarm_after_trigger(hass): future = dt_util.utcnow() + timedelta(seconds=5) with patch( - ("homeassistant.components.manual.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -1013,7 +1013,7 @@ async def test_trigger_with_unused_zero_specific_trigger_time(hass): future = dt_util.utcnow() + timedelta(seconds=5) with patch( - ("homeassistant.components.manual.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -1048,7 +1048,7 @@ async def test_trigger_with_specific_trigger_time(hass): future = dt_util.utcnow() + timedelta(seconds=5) with patch( - ("homeassistant.components.manual.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -1087,7 +1087,7 @@ async def test_trigger_with_no_disarm_after_trigger(hass): future = dt_util.utcnow() + timedelta(seconds=5) with patch( - ("homeassistant.components.manual.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -1126,7 +1126,7 @@ async def test_back_to_back_trigger_with_no_disarm_after_trigger(hass): future = dt_util.utcnow() + timedelta(seconds=5) with patch( - ("homeassistant.components.manual.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -1140,7 +1140,7 @@ async def test_back_to_back_trigger_with_no_disarm_after_trigger(hass): future = dt_util.utcnow() + timedelta(seconds=5) with patch( - ("homeassistant.components.manual.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -1178,7 +1178,7 @@ async def test_disarm_while_pending_trigger(hass): future = dt_util.utcnow() + timedelta(seconds=5) with patch( - ("homeassistant.components.manual.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -1217,7 +1217,7 @@ async def test_disarm_during_trigger_with_invalid_code(hass): future = dt_util.utcnow() + timedelta(seconds=5) with patch( - ("homeassistant.components.manual.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -1342,7 +1342,7 @@ async def test_arm_custom_bypass_with_pending(hass): future = dt_util.utcnow() + timedelta(seconds=1) with patch( - ("homeassistant.components.manual.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -1400,7 +1400,7 @@ async def test_armed_custom_bypass_with_specific_pending(hass): future = dt_util.utcnow() + timedelta(seconds=2) with patch( - ("homeassistant.components.manual.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -1448,7 +1448,7 @@ async def test_arm_away_after_disabled_disarmed(hass): future = dt_util.utcnow() + timedelta(seconds=1) with patch( - ("homeassistant.components.manual.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -1466,7 +1466,7 @@ async def test_arm_away_after_disabled_disarmed(hass): future += timedelta(seconds=1) with patch( - ("homeassistant.components.manual.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) diff --git a/tests/components/microsoft_face/test_init.py b/tests/components/microsoft_face/test_init.py index 24d67f56fb5..3e2cdf0d530 100644 --- a/tests/components/microsoft_face/test_init.py +++ b/tests/components/microsoft_face/test_init.py @@ -96,7 +96,7 @@ class TestMicrosoftFaceSetup: self.hass.stop() @patch( - "homeassistant.components.microsoft_face." "MicrosoftFace.update_store", + "homeassistant.components.microsoft_face.MicrosoftFace.update_store", return_value=mock_coro(), ) def test_setup_component(self, mock_update): @@ -105,7 +105,7 @@ class TestMicrosoftFaceSetup: setup_component(self.hass, mf.DOMAIN, self.config) @patch( - "homeassistant.components.microsoft_face." "MicrosoftFace.update_store", + "homeassistant.components.microsoft_face.MicrosoftFace.update_store", return_value=mock_coro(), ) def test_setup_component_wrong_api_key(self, mock_update): @@ -114,7 +114,7 @@ class TestMicrosoftFaceSetup: setup_component(self.hass, mf.DOMAIN, {mf.DOMAIN: {}}) @patch( - "homeassistant.components.microsoft_face." "MicrosoftFace.update_store", + "homeassistant.components.microsoft_face.MicrosoftFace.update_store", return_value=mock_coro(), ) def test_setup_component_test_service(self, mock_update): @@ -170,7 +170,7 @@ class TestMicrosoftFaceSetup: ) @patch( - "homeassistant.components.microsoft_face." "MicrosoftFace.update_store", + "homeassistant.components.microsoft_face.MicrosoftFace.update_store", return_value=mock_coro(), ) def test_service_groups(self, mock_update, aioclient_mock): @@ -257,7 +257,7 @@ class TestMicrosoftFaceSetup: assert "Hans" not in entity_group1.attributes @patch( - "homeassistant.components.microsoft_face." "MicrosoftFace.update_store", + "homeassistant.components.microsoft_face.MicrosoftFace.update_store", return_value=mock_coro(), ) def test_service_train(self, mock_update, aioclient_mock): @@ -317,7 +317,7 @@ class TestMicrosoftFaceSetup: assert aioclient_mock.mock_calls[3][2] == b"Test" @patch( - "homeassistant.components.microsoft_face." "MicrosoftFace.update_store", + "homeassistant.components.microsoft_face.MicrosoftFace.update_store", return_value=mock_coro(), ) def test_service_status_400(self, mock_update, aioclient_mock): @@ -339,7 +339,7 @@ class TestMicrosoftFaceSetup: assert len(aioclient_mock.mock_calls) == 1 @patch( - "homeassistant.components.microsoft_face." "MicrosoftFace.update_store", + "homeassistant.components.microsoft_face.MicrosoftFace.update_store", return_value=mock_coro(), ) def test_service_status_timeout(self, mock_update, aioclient_mock): diff --git a/tests/components/microsoft_face_detect/test_image_processing.py b/tests/components/microsoft_face_detect/test_image_processing.py index 1b01ee7434c..384e0ba130f 100644 --- a/tests/components/microsoft_face_detect/test_image_processing.py +++ b/tests/components/microsoft_face_detect/test_image_processing.py @@ -28,7 +28,7 @@ class TestMicrosoftFaceDetectSetup: self.hass.stop() @patch( - "homeassistant.components.microsoft_face." "MicrosoftFace.update_store", + "homeassistant.components.microsoft_face.MicrosoftFace.update_store", return_value=mock_coro(), ) def test_setup_platform(self, store_mock): @@ -49,7 +49,7 @@ class TestMicrosoftFaceDetectSetup: assert self.hass.states.get("image_processing.microsoftface_demo_camera") @patch( - "homeassistant.components.microsoft_face." "MicrosoftFace.update_store", + "homeassistant.components.microsoft_face.MicrosoftFace.update_store", return_value=mock_coro(), ) def test_setup_platform_name(self, store_mock): diff --git a/tests/components/microsoft_face_identify/test_image_processing.py b/tests/components/microsoft_face_identify/test_image_processing.py index 311d463bc1d..d1054cf8dc4 100644 --- a/tests/components/microsoft_face_identify/test_image_processing.py +++ b/tests/components/microsoft_face_identify/test_image_processing.py @@ -28,7 +28,7 @@ class TestMicrosoftFaceIdentifySetup: self.hass.stop() @patch( - "homeassistant.components.microsoft_face." "MicrosoftFace.update_store", + "homeassistant.components.microsoft_face.MicrosoftFace.update_store", return_value=mock_coro(), ) def test_setup_platform(self, store_mock): @@ -49,7 +49,7 @@ class TestMicrosoftFaceIdentifySetup: assert self.hass.states.get("image_processing.microsoftface_demo_camera") @patch( - "homeassistant.components.microsoft_face." "MicrosoftFace.update_store", + "homeassistant.components.microsoft_face.MicrosoftFace.update_store", return_value=mock_coro(), ) def test_setup_platform_name(self, store_mock): diff --git a/tests/components/mqtt/test_binary_sensor.py b/tests/components/mqtt/test_binary_sensor.py index 3e8f342ea94..3bfe32633b3 100644 --- a/tests/components/mqtt/test_binary_sensor.py +++ b/tests/components/mqtt/test_binary_sensor.py @@ -78,7 +78,7 @@ async def expires_helper(hass, mqtt_mock, caplog): """Run the basic expiry code.""" now = datetime(2017, 1, 1, 1, tzinfo=dt_util.UTC) - with patch(("homeassistant.helpers.event." "dt_util.utcnow"), return_value=now): + with patch(("homeassistant.helpers.event.dt_util.utcnow"), return_value=now): async_fire_time_changed(hass, now) async_fire_mqtt_message(hass, "test-topic", "ON") await hass.async_block_till_done() @@ -97,7 +97,7 @@ async def expires_helper(hass, mqtt_mock, caplog): assert state.state == STATE_ON # Next message resets timer - with patch(("homeassistant.helpers.event." "dt_util.utcnow"), return_value=now): + with patch(("homeassistant.helpers.event.dt_util.utcnow"), return_value=now): async_fire_time_changed(hass, now) async_fire_mqtt_message(hass, "test-topic", "OFF") await hass.async_block_till_done() diff --git a/tests/components/mqtt/test_climate.py b/tests/components/mqtt/test_climate.py index 2db368d0311..29962287dd7 100644 --- a/tests/components/mqtt/test_climate.py +++ b/tests/components/mqtt/test_climate.py @@ -113,7 +113,7 @@ async def test_set_operation_bad_attr_and_state(hass, mqtt_mock, caplog): assert state.state == "off" with pytest.raises(vol.Invalid) as excinfo: await common.async_set_hvac_mode(hass, None, ENTITY_CLIMATE) - assert ("value is not allowed for dictionary value @ " "data['hvac_mode']") in str( + assert ("value is not allowed for dictionary value @ data['hvac_mode']") in str( excinfo.value ) state = hass.states.get(ENTITY_CLIMATE) diff --git a/tests/components/mqtt/test_device_tracker.py b/tests/components/mqtt/test_device_tracker.py index 71348fcf5cb..f4324bd8634 100644 --- a/tests/components/mqtt/test_device_tracker.py +++ b/tests/components/mqtt/test_device_tracker.py @@ -27,7 +27,7 @@ async def test_ensure_device_tracker_platform_validation(hass): assert "qos" in config with patch( - "homeassistant.components.mqtt.device_tracker." "async_setup_scanner", + "homeassistant.components.mqtt.device_tracker.async_setup_scanner", autospec=True, side_effect=mock_setup_scanner, ) as mock_sp: diff --git a/tests/components/mqtt/test_discovery.py b/tests/components/mqtt/test_discovery.py index 2b6c65b919e..6320be3b772 100644 --- a/tests/components/mqtt/test_discovery.py +++ b/tests/components/mqtt/test_discovery.py @@ -179,9 +179,7 @@ async def test_discovery_incl_nodeid(hass, mqtt_mock, caplog): await async_start(hass, "homeassistant", {}, entry) async_fire_mqtt_message( - hass, - "homeassistant/binary_sensor/my_node_id/bla" "/config", - '{ "name": "Beer" }', + hass, "homeassistant/binary_sensor/my_node_id/bla/config", '{ "name": "Beer" }', ) await hass.async_block_till_done() @@ -212,7 +210,7 @@ async def test_non_duplicate_discovery(hass, mqtt_mock, caplog): assert state is not None assert state.name == "Beer" assert state_duplicate is None - assert "Component has already been discovered: " "binary_sensor bla" in caplog.text + assert "Component has already been discovered: binary_sensor bla" in caplog.text async def test_discovery_expansion(hass, mqtt_mock, caplog): @@ -424,7 +422,7 @@ async def test_complex_discovery_topic_prefix(hass, mqtt_mock, caplog): async_fire_mqtt_message( hass, - ("my_home/homeassistant/register" "/binary_sensor/node1/object1/config"), + ("my_home/homeassistant/register/binary_sensor/node1/object1/config"), '{ "name": "Beer" }', ) await hass.async_block_till_done() diff --git a/tests/components/mqtt/test_light.py b/tests/components/mqtt/test_light.py index b03cd4b8d73..43ccaf6dea5 100644 --- a/tests/components/mqtt/test_light.py +++ b/tests/components/mqtt/test_light.py @@ -555,7 +555,7 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): }, ) with patch( - "homeassistant.helpers.restore_state.RestoreEntity" ".async_get_last_state", + "homeassistant.helpers.restore_state.RestoreEntity.async_get_last_state", return_value=mock_coro(fake_state), ): with assert_setup_component(1, light.DOMAIN): diff --git a/tests/components/mqtt/test_light_json.py b/tests/components/mqtt/test_light_json.py index 52adeb61514..355451f6469 100644 --- a/tests/components/mqtt/test_light_json.py +++ b/tests/components/mqtt/test_light_json.py @@ -299,7 +299,7 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): ) with patch( - "homeassistant.helpers.restore_state.RestoreEntity" ".async_get_last_state", + "homeassistant.helpers.restore_state.RestoreEntity.async_get_last_state", return_value=mock_coro(fake_state), ): assert await async_setup_component( diff --git a/tests/components/mqtt/test_light_template.py b/tests/components/mqtt/test_light_template.py index 5148f45e6e7..1d109af5930 100644 --- a/tests/components/mqtt/test_light_template.py +++ b/tests/components/mqtt/test_light_template.py @@ -261,7 +261,7 @@ async def test_optimistic(hass, mqtt_mock): ) with patch( - "homeassistant.helpers.restore_state.RestoreEntity" ".async_get_last_state", + "homeassistant.helpers.restore_state.RestoreEntity.async_get_last_state", return_value=mock_coro(fake_state), ): with assert_setup_component(1, light.DOMAIN): diff --git a/tests/components/mqtt/test_sensor.py b/tests/components/mqtt/test_sensor.py index 4e8e5f9bfd0..66f8996bc2e 100644 --- a/tests/components/mqtt/test_sensor.py +++ b/tests/components/mqtt/test_sensor.py @@ -63,7 +63,7 @@ async def test_setting_sensor_value_expires(hass, mqtt_mock, caplog): assert state.state == "unknown" now = datetime(2017, 1, 1, 1, tzinfo=dt_util.UTC) - with patch(("homeassistant.helpers.event." "dt_util.utcnow"), return_value=now): + with patch(("homeassistant.helpers.event.dt_util.utcnow"), return_value=now): async_fire_time_changed(hass, now) async_fire_mqtt_message(hass, "test-topic", "100") await hass.async_block_till_done() @@ -82,7 +82,7 @@ async def test_setting_sensor_value_expires(hass, mqtt_mock, caplog): assert state.state == "100" # Next message resets timer - with patch(("homeassistant.helpers.event." "dt_util.utcnow"), return_value=now): + with patch(("homeassistant.helpers.event.dt_util.utcnow"), return_value=now): async_fire_time_changed(hass, now) async_fire_mqtt_message(hass, "test-topic", "101") await hass.async_block_till_done() diff --git a/tests/components/mqtt/test_switch.py b/tests/components/mqtt/test_switch.py index 35cbea5a82b..25fc3212f05 100644 --- a/tests/components/mqtt/test_switch.py +++ b/tests/components/mqtt/test_switch.py @@ -69,7 +69,7 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mock_publish): fake_state = ha.State("switch.test", "on") with patch( - "homeassistant.helpers.restore_state.RestoreEntity" ".async_get_last_state", + "homeassistant.helpers.restore_state.RestoreEntity.async_get_last_state", return_value=mock_coro(fake_state), ): assert await async_setup_component( diff --git a/tests/components/mqtt_json/test_device_tracker.py b/tests/components/mqtt_json/test_device_tracker.py index 7f3f806da52..5af196c5bf2 100644 --- a/tests/components/mqtt_json/test_device_tracker.py +++ b/tests/components/mqtt_json/test_device_tracker.py @@ -46,7 +46,7 @@ async def test_ensure_device_tracker_platform_validation(hass): assert "qos" in config with patch( - "homeassistant.components.mqtt_json.device_tracker." "async_setup_scanner", + "homeassistant.components.mqtt_json.device_tracker.async_setup_scanner", autospec=True, side_effect=mock_setup_scanner, ) as mock_sp: diff --git a/tests/components/owntracks/test_config_flow.py b/tests/components/owntracks/test_config_flow.py index d48c3c43a25..21bb5bcf993 100644 --- a/tests/components/owntracks/test_config_flow.py +++ b/tests/components/owntracks/test_config_flow.py @@ -126,7 +126,7 @@ async def test_user_not_supports_encryption(hass, not_supports_encryption): async def test_unload(hass): """Test unloading a config flow.""" with patch( - "homeassistant.config_entries.ConfigEntries" ".async_forward_entry_setup" + "homeassistant.config_entries.ConfigEntries.async_forward_entry_setup" ) as mock_forward: result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": "import"}, data={} @@ -140,7 +140,7 @@ async def test_unload(hass): assert entry.data["webhook_id"] in hass.data["webhook"] with patch( - "homeassistant.config_entries.ConfigEntries" ".async_forward_entry_unload", + "homeassistant.config_entries.ConfigEntries.async_forward_entry_unload", return_value=mock_coro(), ) as mock_unload: assert await hass.config_entries.async_unload(entry.entry_id) diff --git a/tests/components/owntracks/test_device_tracker.py b/tests/components/owntracks/test_device_tracker.py index 3a7f3c030ed..730da4bc7b2 100644 --- a/tests/components/owntracks/test_device_tracker.py +++ b/tests/components/owntracks/test_device_tracker.py @@ -1300,7 +1300,7 @@ async def test_single_waypoint_import(hass, context): async def test_not_implemented_message(hass, context): """Handle not implemented message type.""" patch_handler = patch( - "homeassistant.components.owntracks." "messages.async_handle_not_impl_msg", + "homeassistant.components.owntracks.messages.async_handle_not_impl_msg", return_value=mock_coro(False), ) patch_handler.start() @@ -1311,7 +1311,7 @@ async def test_not_implemented_message(hass, context): async def test_unsupported_message(hass, context): """Handle not implemented message type.""" patch_handler = patch( - "homeassistant.components.owntracks." "messages.async_handle_unsupported_msg", + "homeassistant.components.owntracks.messages.async_handle_unsupported_msg", return_value=mock_coro(False), ) patch_handler.start() @@ -1396,7 +1396,7 @@ def config_context(hass, setup_comp): patch_load.start() patch_save = patch( - "homeassistant.components.device_tracker." "DeviceTracker.async_update_config" + "homeassistant.components.device_tracker.DeviceTracker.async_update_config" ) patch_save.start() diff --git a/tests/components/qld_bushfire/test_geo_location.py b/tests/components/qld_bushfire/test_geo_location.py index 86ab16c5f1b..ad9bdd7c536 100644 --- a/tests/components/qld_bushfire/test_geo_location.py +++ b/tests/components/qld_bushfire/test_geo_location.py @@ -89,7 +89,7 @@ async def test_setup(hass): # Patching 'utcnow' to gain more control over the timed update. utcnow = dt_util.utcnow() with patch("homeassistant.util.dt.utcnow", return_value=utcnow), patch( - "georss_qld_bushfire_alert_client." "QldBushfireAlertFeed" + "georss_qld_bushfire_alert_client.QldBushfireAlertFeed" ) as mock_feed: mock_feed.return_value.update.return_value = ( "OK", @@ -193,7 +193,7 @@ async def test_setup_with_custom_location(hass): "1234", "Title 1", 20.5, (38.1, -3.1), category="Category 1" ) - with patch("georss_qld_bushfire_alert_client." "QldBushfireAlertFeed") as mock_feed: + with patch("georss_qld_bushfire_alert_client.QldBushfireAlertFeed") as mock_feed: mock_feed.return_value.update.return_value = "OK", [mock_entry_1] with assert_setup_component(1, geo_location.DOMAIN): diff --git a/tests/components/radarr/test_sensor.py b/tests/components/radarr/test_sensor.py index ecd0c501ee8..114daa7d2f7 100644 --- a/tests/components/radarr/test_sensor.py +++ b/tests/components/radarr/test_sensor.py @@ -182,7 +182,7 @@ def mocked_requests_get(*args, **kwargs): "sqliteVersion": "3.16.2", "urlBase": "", "runtimeVersion": ( - "4.6.1 " "(Stable 4.6.1.3/abb06f1 " "Mon Oct 3 07:57:59 UTC 2016)" + "4.6.1 (Stable 4.6.1.3/abb06f1 Mon Oct 3 07:57:59 UTC 2016)" ), }, 200, diff --git a/tests/components/rflink/test_binary_sensor.py b/tests/components/rflink/test_binary_sensor.py index d1fdec579c9..18c4f946318 100644 --- a/tests/components/rflink/test_binary_sensor.py +++ b/tests/components/rflink/test_binary_sensor.py @@ -126,7 +126,7 @@ async def test_off_delay(hass, monkeypatch): now = dt_util.utcnow() # fake time and turn on sensor future = now + timedelta(seconds=0) - with patch(("homeassistant.helpers.event." "dt_util.utcnow"), return_value=future): + with patch(("homeassistant.helpers.event.dt_util.utcnow"), return_value=future): async_fire_time_changed(hass, future) event_callback(on_event) await hass.async_block_till_done() @@ -136,7 +136,7 @@ async def test_off_delay(hass, monkeypatch): # fake time and turn on sensor again future = now + timedelta(seconds=15) - with patch(("homeassistant.helpers.event." "dt_util.utcnow"), return_value=future): + with patch(("homeassistant.helpers.event.dt_util.utcnow"), return_value=future): async_fire_time_changed(hass, future) event_callback(on_event) await hass.async_block_till_done() @@ -146,7 +146,7 @@ async def test_off_delay(hass, monkeypatch): # fake time and verify sensor still on (de-bounce) future = now + timedelta(seconds=35) - with patch(("homeassistant.helpers.event." "dt_util.utcnow"), return_value=future): + with patch(("homeassistant.helpers.event.dt_util.utcnow"), return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() state = hass.states.get("binary_sensor.test2") @@ -155,7 +155,7 @@ async def test_off_delay(hass, monkeypatch): # fake time and verify sensor is off future = now + timedelta(seconds=45) - with patch(("homeassistant.helpers.event." "dt_util.utcnow"), return_value=future): + with patch(("homeassistant.helpers.event.dt_util.utcnow"), return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() state = hass.states.get("binary_sensor.test2") diff --git a/tests/components/shell_command/test_init.py b/tests/components/shell_command/test_init.py index a54bd9f7787..50c3c6bfb55 100644 --- a/tests/components/shell_command/test_init.py +++ b/tests/components/shell_command/test_init.py @@ -111,7 +111,7 @@ class TestShellCommand(unittest.TestCase): shell_command.DOMAIN, { shell_command.DOMAIN: { - "test_service": ("ls /bin {{ states.sensor" ".test_state.state }}") + "test_service": ("ls /bin {{ states.sensor.test_state.state }}") } }, ) diff --git a/tests/components/shopping_list/conftest.py b/tests/components/shopping_list/conftest.py index 5026f19302e..44c8000efa2 100644 --- a/tests/components/shopping_list/conftest.py +++ b/tests/components/shopping_list/conftest.py @@ -11,7 +11,7 @@ from homeassistant.setup import async_setup_component def mock_shopping_list_io(): """Stub out the persistence.""" with patch("homeassistant.components.shopping_list.ShoppingData.save"), patch( - "homeassistant.components.shopping_list." "ShoppingData.async_load" + "homeassistant.components.shopping_list.ShoppingData.async_load" ): yield diff --git a/tests/components/startca/test_sensor.py b/tests/components/startca/test_sensor.py index aa06e13a812..eac75a3b4e7 100644 --- a/tests/components/startca/test_sensor.py +++ b/tests/components/startca/test_sensor.py @@ -45,7 +45,7 @@ async def test_capped_setup(hass, aioclient_mock): "" ) aioclient_mock.get( - "https://www.start.ca/support/usage/api?key=" "NOTAKEY", text=result + "https://www.start.ca/support/usage/api?key=NOTAKEY", text=result ) await async_setup_component(hass, "sensor", {"sensor": config}) @@ -140,7 +140,7 @@ async def test_unlimited_setup(hass, aioclient_mock): "" ) aioclient_mock.get( - "https://www.start.ca/support/usage/api?key=" "NOTAKEY", text=result + "https://www.start.ca/support/usage/api?key=NOTAKEY", text=result ) await async_setup_component(hass, "sensor", {"sensor": config}) @@ -196,9 +196,7 @@ async def test_unlimited_setup(hass, aioclient_mock): async def test_bad_return_code(hass, aioclient_mock): """Test handling a return code that isn't HTTP OK.""" - aioclient_mock.get( - "https://www.start.ca/support/usage/api?key=" "NOTAKEY", status=404 - ) + aioclient_mock.get("https://www.start.ca/support/usage/api?key=NOTAKEY", status=404) scd = StartcaData(hass.loop, async_get_clientsession(hass), "NOTAKEY", 400) @@ -209,7 +207,7 @@ async def test_bad_return_code(hass, aioclient_mock): async def test_bad_json_decode(hass, aioclient_mock): """Test decoding invalid json result.""" aioclient_mock.get( - "https://www.start.ca/support/usage/api?key=" "NOTAKEY", text="this is not xml" + "https://www.start.ca/support/usage/api?key=NOTAKEY", text="this is not xml" ) scd = StartcaData(hass.loop, async_get_clientsession(hass), "NOTAKEY", 400) diff --git a/tests/components/tomato/test_device_tracker.py b/tests/components/tomato/test_device_tracker.py index 96957805466..cbc8316f7c8 100644 --- a/tests/components/tomato/test_device_tracker.py +++ b/tests/components/tomato/test_device_tracker.py @@ -51,7 +51,7 @@ def mock_session_response(*args, **kwargs): def mock_exception_logger(): """Mock pyunifi.""" with mock.patch( - "homeassistant.components.tomato.device_tracker" "._LOGGER.exception" + "homeassistant.components.tomato.device_tracker._LOGGER.exception" ) as mock_exception_logger: yield mock_exception_logger @@ -312,7 +312,7 @@ def test_config_bad_credentials(hass, mock_exception_logger): assert mock_exception_logger.call_count == 1 assert mock_exception_logger.mock_calls[0] == mock.call( - "Failed to authenticate, " "please check your username and password" + "Failed to authenticate, please check your username and password" ) @@ -382,7 +382,7 @@ def test_bad_connection(hass, mock_exception_logger): tomato.get_scanner(hass, config) assert mock_exception_logger.call_count == 1 assert mock_exception_logger.mock_calls[0] == mock.call( - "Failed to connect to the router " "or invalid http_id supplied" + "Failed to connect to the router or invalid http_id supplied" ) diff --git a/tests/components/tplink/test_init.py b/tests/components/tplink/test_init.py index 9428bf05483..97512dfc9bd 100644 --- a/tests/components/tplink/test_init.py +++ b/tests/components/tplink/test_init.py @@ -242,7 +242,7 @@ async def test_unload(hass, platform): with patch( "homeassistant.components.tplink.common.SmartDevice._query_helper" ), patch( - "homeassistant.components.tplink.{}" ".async_setup_entry".format(platform), + "homeassistant.components.tplink.{}.async_setup_entry".format(platform), return_value=mock_coro(True), ) as light_setup: config = { diff --git a/tests/components/tradfri/conftest.py b/tests/components/tradfri/conftest.py index 7ebd4bbcd7c..1c6e572b81f 100644 --- a/tests/components/tradfri/conftest.py +++ b/tests/components/tradfri/conftest.py @@ -8,6 +8,6 @@ import pytest def mock_gateway_info(): """Mock get_gateway_info.""" with patch( - "homeassistant.components.tradfri.config_flow." "get_gateway_info" + "homeassistant.components.tradfri.config_flow.get_gateway_info" ) as mock_gateway: yield mock_gateway diff --git a/tests/components/tradfri/test_config_flow.py b/tests/components/tradfri/test_config_flow.py index 151607b1ed8..ad7386c530f 100644 --- a/tests/components/tradfri/test_config_flow.py +++ b/tests/components/tradfri/test_config_flow.py @@ -13,7 +13,7 @@ from tests.common import MockConfigEntry, mock_coro def mock_auth(): """Mock authenticate.""" with patch( - "homeassistant.components.tradfri.config_flow." "authenticate" + "homeassistant.components.tradfri.config_flow.authenticate" ) as mock_auth: yield mock_auth @@ -21,7 +21,7 @@ def mock_auth(): @pytest.fixture def mock_entry_setup(): """Mock entry setup.""" - with patch("homeassistant.components.tradfri." "async_setup_entry") as mock_setup: + with patch("homeassistant.components.tradfri.async_setup_entry") as mock_setup: mock_setup.return_value = mock_coro(True) yield mock_setup diff --git a/tests/components/tts/test_init.py b/tests/components/tts/test_init.py index 389f9478ad3..f8dc11069d8 100644 --- a/tests/components/tts/test_init.py +++ b/tests/components/tts/test_init.py @@ -99,7 +99,7 @@ class TestTTS: assert calls[0].data[ATTR_MEDIA_CONTENT_TYPE] == MEDIA_TYPE_MUSIC assert calls[0].data[ ATTR_MEDIA_CONTENT_ID - ] == "{}/api/tts_proxy/265944c108cbb00b2a621be5930513e03a0bb2cd" "_en_-_demo.mp3".format( + ] == "{}/api/tts_proxy/265944c108cbb00b2a621be5930513e03a0bb2cd_en_-_demo.mp3".format( self.hass.config.api.base_url ) assert os.path.isfile( @@ -129,7 +129,7 @@ class TestTTS: assert calls[0].data[ATTR_MEDIA_CONTENT_TYPE] == MEDIA_TYPE_MUSIC assert calls[0].data[ ATTR_MEDIA_CONTENT_ID - ] == "{}/api/tts_proxy/265944c108cbb00b2a621be5930513e03a0bb2cd" "_de_-_demo.mp3".format( + ] == "{}/api/tts_proxy/265944c108cbb00b2a621be5930513e03a0bb2cd_de_-_demo.mp3".format( self.hass.config.api.base_url ) assert os.path.isfile( @@ -169,7 +169,7 @@ class TestTTS: assert calls[0].data[ATTR_MEDIA_CONTENT_TYPE] == MEDIA_TYPE_MUSIC assert calls[0].data[ ATTR_MEDIA_CONTENT_ID - ] == "{}/api/tts_proxy/265944c108cbb00b2a621be5930513e03a0bb2cd" "_de_-_demo.mp3".format( + ] == "{}/api/tts_proxy/265944c108cbb00b2a621be5930513e03a0bb2cd_de_-_demo.mp3".format( self.hass.config.api.base_url ) assert os.path.isfile( @@ -232,7 +232,7 @@ class TestTTS: assert calls[0].data[ATTR_MEDIA_CONTENT_TYPE] == MEDIA_TYPE_MUSIC assert calls[0].data[ ATTR_MEDIA_CONTENT_ID - ] == "{}/api/tts_proxy/265944c108cbb00b2a621be5930513e03a0bb2cd" "_de_{}_demo.mp3".format( + ] == "{}/api/tts_proxy/265944c108cbb00b2a621be5930513e03a0bb2cd_de_{}_demo.mp3".format( self.hass.config.api.base_url, opt_hash ) assert os.path.isfile( @@ -273,7 +273,7 @@ class TestTTS: assert calls[0].data[ATTR_MEDIA_CONTENT_TYPE] == MEDIA_TYPE_MUSIC assert calls[0].data[ ATTR_MEDIA_CONTENT_ID - ] == "{}/api/tts_proxy/265944c108cbb00b2a621be5930513e03a0bb2cd" "_de_{}_demo.mp3".format( + ] == "{}/api/tts_proxy/265944c108cbb00b2a621be5930513e03a0bb2cd_de_{}_demo.mp3".format( self.hass.config.api.base_url, opt_hash ) assert os.path.isfile( @@ -449,7 +449,7 @@ class TestTTS: self.hass.start() url = ( - "{}/api/tts_proxy/265944c108cbb00b2a621be5930513e03a0bb2cd" "_en_-_demo.mp3" + "{}/api/tts_proxy/265944c108cbb00b2a621be5930513e03a0bb2cd_en_-_demo.mp3" ).format(self.hass.config.api.base_url) req = requests.get(url) @@ -465,7 +465,7 @@ class TestTTS: self.hass.start() url = ( - "{}/api/tts_proxy/265944dsk32c1b2a621be5930510bb2cd" "_en_-_demo.mp3" + "{}/api/tts_proxy/265944dsk32c1b2a621be5930510bb2cd_en_-_demo.mp3" ).format(self.hass.config.api.base_url) req = requests.get(url) @@ -542,7 +542,7 @@ class TestTTS: setup_component(self.hass, tts.DOMAIN, config) with patch( - "homeassistant.components.demo.tts.DemoProvider." "get_tts_audio", + "homeassistant.components.demo.tts.DemoProvider.get_tts_audio", return_value=(None, None), ): self.hass.services.call( @@ -555,7 +555,7 @@ class TestTTS: assert len(calls) == 1 assert calls[0].data[ ATTR_MEDIA_CONTENT_ID - ] == "{}/api/tts_proxy/265944c108cbb00b2a621be5930513e03a0bb2cd" "_en_-_demo.mp3".format( + ] == "{}/api/tts_proxy/265944c108cbb00b2a621be5930513e03a0bb2cd_en_-_demo.mp3".format( self.hass.config.api.base_url ) @@ -601,7 +601,7 @@ class TestTTS: self.hass.start() url = ( - "{}/api/tts_proxy/265944c108cbb00b2a621be5930513e03a0bb2cd" "_en_-_demo.mp3" + "{}/api/tts_proxy/265944c108cbb00b2a621be5930513e03a0bb2cd_en_-_demo.mp3" ).format(self.hass.config.api.base_url) req = requests.get(url) diff --git a/tests/components/websocket_api/test_auth.py b/tests/components/websocket_api/test_auth.py index ccc033ccc72..2a0bc9f8c5a 100644 --- a/tests/components/websocket_api/test_auth.py +++ b/tests/components/websocket_api/test_auth.py @@ -44,7 +44,7 @@ async def test_auth_events( async def test_auth_via_msg_incorrect_pass(no_auth_websocket_client): """Test authenticating.""" with patch( - "homeassistant.components.websocket_api.auth." "process_wrong_login", + "homeassistant.components.websocket_api.auth.process_wrong_login", return_value=mock_coro(), ) as mock_process_wrong_login: await no_auth_websocket_client.send_json( diff --git a/tests/components/wunderground/test_sensor.py b/tests/components/wunderground/test_sensor.py index bfe9f83fbc3..5f74a837cf3 100644 --- a/tests/components/wunderground/test_sensor.py +++ b/tests/components/wunderground/test_sensor.py @@ -49,11 +49,9 @@ URL = ( "http://api.wunderground.com/api/foo/alerts/conditions/forecast/lang" ":EN/q/32.87336,-117.22743.json" ) -PWS_URL = ( - "http://api.wunderground.com/api/foo/alerts/conditions/" "lang:EN/q/pws:bar.json" -) +PWS_URL = "http://api.wunderground.com/api/foo/alerts/conditions/lang:EN/q/pws:bar.json" INVALID_URL = ( - "http://api.wunderground.com/api/BOB/alerts/conditions/" "lang:foo/q/pws:bar.json" + "http://api.wunderground.com/api/BOB/alerts/conditions/lang:foo/q/pws:bar.json" ) diff --git a/tests/components/yr/test_sensor.py b/tests/components/yr/test_sensor.py index 7e2e8543f77..161a7cef66b 100644 --- a/tests/components/yr/test_sensor.py +++ b/tests/components/yr/test_sensor.py @@ -13,7 +13,7 @@ NOW = datetime(2016, 6, 9, 1, tzinfo=dt_util.UTC) async def test_default_setup(hass, aioclient_mock): """Test the default setup.""" aioclient_mock.get( - "https://aa015h6buqvih86i1.api.met.no/" "weatherapi/locationforecast/1.9/", + "https://aa015h6buqvih86i1.api.met.no/weatherapi/locationforecast/1.9/", text=load_fixture("yr.no.xml"), ) config = {"platform": "yr", "elevation": 0} @@ -32,7 +32,7 @@ async def test_default_setup(hass, aioclient_mock): async def test_custom_setup(hass, aioclient_mock): """Test a custom setup.""" aioclient_mock.get( - "https://aa015h6buqvih86i1.api.met.no/" "weatherapi/locationforecast/1.9/", + "https://aa015h6buqvih86i1.api.met.no/weatherapi/locationforecast/1.9/", text=load_fixture("yr.no.xml"), ) @@ -77,7 +77,7 @@ async def test_custom_setup(hass, aioclient_mock): async def test_forecast_setup(hass, aioclient_mock): """Test a custom setup with 24h forecast.""" aioclient_mock.get( - "https://aa015h6buqvih86i1.api.met.no/" "weatherapi/locationforecast/1.9/", + "https://aa015h6buqvih86i1.api.met.no/weatherapi/locationforecast/1.9/", text=load_fixture("yr.no.xml"), ) diff --git a/tests/components/zha/test_config_flow.py b/tests/components/zha/test_config_flow.py index fdff064a1c5..7e0b89f8d70 100644 --- a/tests/components/zha/test_config_flow.py +++ b/tests/components/zha/test_config_flow.py @@ -16,7 +16,7 @@ async def test_user_flow(hass): flow.hass = hass with asynctest.patch( - "homeassistant.components.zha.config_flow" ".check_zigpy_connection", + "homeassistant.components.zha.config_flow.check_zigpy_connection", return_value=False, ): result = await flow.async_step_user( @@ -26,7 +26,7 @@ async def test_user_flow(hass): assert result["errors"] == {"base": "cannot_connect"} with asynctest.patch( - "homeassistant.components.zha.config_flow" ".check_zigpy_connection", + "homeassistant.components.zha.config_flow.check_zigpy_connection", return_value=True, ): result = await flow.async_step_user( diff --git a/tests/conftest.py b/tests/conftest.py index 262acda6314..558da48a7c1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -60,7 +60,7 @@ def verify_cleanup(): for inst in INSTANCES: inst.stop() pytest.exit( - "Detected non stopped instances " "({}), aborting test run".format(count) + "Detected non stopped instances ({}), aborting test run".format(count) ) diff --git a/tests/helpers/test_check_config.py b/tests/helpers/test_check_config.py index 54c835895ca..0182d830e4c 100644 --- a/tests/helpers/test_check_config.py +++ b/tests/helpers/test_check_config.py @@ -23,7 +23,7 @@ BASE_CONFIG = ( "\n\n" ) -BAD_CORE_CONFIG = "homeassistant:\n" " unit_system: bad\n" "\n\n" +BAD_CORE_CONFIG = "homeassistant:\n unit_system: bad\n\n\n" def log_ha_config(conf): @@ -106,8 +106,7 @@ async def test_component_platform_not_found_2(hass, loop): async def test_package_invalid(hass, loop): """Test a valid platform setup.""" files = { - YAML_CONFIG_FILE: BASE_CONFIG - + (" packages:\n" " p1:\n" ' group: ["a"]') + YAML_CONFIG_FILE: BASE_CONFIG + (" packages:\n p1:\n" ' group: ["a"]') } with patch("os.path.isfile", return_value=True), patch_yaml_files(files): res = await async_check_ha_config_file(hass) diff --git a/tests/helpers/test_device_registry.py b/tests/helpers/test_device_registry.py index 3846230e6d3..7f31c32cde3 100644 --- a/tests/helpers/test_device_registry.py +++ b/tests/helpers/test_device_registry.py @@ -340,7 +340,7 @@ async def test_no_unnecessary_changes(registry): identifiers={("hue", "456"), ("bla", "123")}, ) with patch( - "homeassistant.helpers.device_registry" ".DeviceRegistry.async_schedule_save" + "homeassistant.helpers.device_registry.DeviceRegistry.async_schedule_save" ) as mock_save: entry2 = registry.async_get_or_create( config_entry_id="1234", identifiers={("hue", "456")} diff --git a/tests/helpers/test_entity_component.py b/tests/helpers/test_entity_component.py index a069c050cf4..7284a5d9b67 100644 --- a/tests/helpers/test_entity_component.py +++ b/tests/helpers/test_entity_component.py @@ -115,7 +115,7 @@ async def test_setup_recovers_when_setup_raises(hass): @asynctest.patch( - "homeassistant.helpers.entity_component.EntityComponent" ".async_setup_platform", + "homeassistant.helpers.entity_component.EntityComponent.async_setup_platform", return_value=mock_coro(), ) @asynctest.patch( @@ -137,7 +137,7 @@ async def test_setup_does_discovery(mock_setup_component, mock_setup, hass): assert ("platform_test", {}, {"msg": "discovery_info"}) == mock_setup.call_args[0] -@asynctest.patch("homeassistant.helpers.entity_platform." "async_track_time_interval") +@asynctest.patch("homeassistant.helpers.entity_platform.async_track_time_interval") async def test_set_scan_interval_via_config(mock_track, hass): """Test the setting of the scan interval via configuration.""" @@ -460,5 +460,5 @@ async def test_extract_all_use_match_all(hass, caplog): ent.entity_id for ent in await component.async_extract_from_service(call) ) assert ( - "Not passing an entity ID to a service to target all entities is " "deprecated" + "Not passing an entity ID to a service to target all entities is deprecated" ) not in caplog.text diff --git a/tests/helpers/test_entity_platform.py b/tests/helpers/test_entity_platform.py index 0f73699c896..592cc24df8e 100644 --- a/tests/helpers/test_entity_platform.py +++ b/tests/helpers/test_entity_platform.py @@ -134,7 +134,7 @@ async def test_update_state_adds_entities_with_update_before_add_false(hass): assert not ent.update.called -@asynctest.patch("homeassistant.helpers.entity_platform." "async_track_time_interval") +@asynctest.patch("homeassistant.helpers.entity_platform.async_track_time_interval") async def test_set_scan_interval_via_platform(mock_track, hass): """Test the setting of the scan interval via platform.""" diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py index d331da5d92d..cef8baec70e 100644 --- a/tests/helpers/test_event.py +++ b/tests/helpers/test_event.py @@ -805,7 +805,7 @@ async def test_call_later(hass): now = datetime(2017, 12, 19, 15, 40, 0, tzinfo=dt_util.UTC) with patch( - "homeassistant.helpers.event" ".async_track_point_in_utc_time" + "homeassistant.helpers.event.async_track_point_in_utc_time" ) as mock, patch("homeassistant.util.dt.utcnow", return_value=now): async_call_later(hass, 3, action) @@ -825,7 +825,7 @@ async def test_async_call_later(hass): now = datetime(2017, 12, 19, 15, 40, 0, tzinfo=dt_util.UTC) with patch( - "homeassistant.helpers.event" ".async_track_point_in_utc_time" + "homeassistant.helpers.event.async_track_point_in_utc_time" ) as mock, patch("homeassistant.util.dt.utcnow", return_value=now): remove = async_call_later(hass, 3, action) diff --git a/tests/helpers/test_service.py b/tests/helpers/test_service.py index 2697c59b787..b42b30a836a 100644 --- a/tests/helpers/test_service.py +++ b/tests/helpers/test_service.py @@ -459,7 +459,7 @@ async def test_call_with_match_all( mock_entities["light.living_room"], ] assert ( - "Not passing an entity ID to a service to target " "all entities is deprecated" + "Not passing an entity ID to a service to target all entities is deprecated" ) not in caplog.text diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index cbd530d0b4c..1c3afa472f2 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -153,7 +153,7 @@ def test_iterating_all_states(hass): def test_iterating_domain_states(hass): """Test iterating domain states.""" - tmpl_str = "{% for state in states.sensor %}" "{{ state.state }}{% endfor %}" + tmpl_str = "{% for state in states.sensor %}{{ state.state }}{% endfor %}" info = render_to_info(hass, tmpl_str) assert_result_info(info, "", domains=["sensor"]) @@ -818,7 +818,7 @@ def test_states_function(hass): @patch( - "homeassistant.helpers.template.TemplateEnvironment." "is_safe_callable", + "homeassistant.helpers.template.TemplateEnvironment.is_safe_callable", return_value=True, ) def test_now(mock_is_safe, hass): @@ -832,7 +832,7 @@ def test_now(mock_is_safe, hass): @patch( - "homeassistant.helpers.template.TemplateEnvironment." "is_safe_callable", + "homeassistant.helpers.template.TemplateEnvironment.is_safe_callable", return_value=True, ) def test_utcnow(mock_is_safe, hass): @@ -1260,18 +1260,18 @@ async def test_expand(hass): hass.states.async_set("test.object", "happy") info = render_to_info( - hass, "{{ expand('test.object') | map(attribute='entity_id')" " | join(', ') }}" + hass, "{{ expand('test.object') | map(attribute='entity_id') | join(', ') }}" ) assert_result_info(info, "test.object", []) info = render_to_info( hass, - "{{ expand('group.new_group') | map(attribute='entity_id')" " | join(', ') }}", + "{{ expand('group.new_group') | map(attribute='entity_id') | join(', ') }}", ) assert_result_info(info, "", ["group.new_group"]) info = render_to_info( - hass, "{{ expand(states.group) | map(attribute='entity_id')" " | join(', ') }}" + hass, "{{ expand(states.group) | map(attribute='entity_id') | join(', ') }}" ) assert_result_info(info, "", [], ["group"]) @@ -1279,12 +1279,12 @@ async def test_expand(hass): info = render_to_info( hass, - "{{ expand('group.new_group') | map(attribute='entity_id')" " | join(', ') }}", + "{{ expand('group.new_group') | map(attribute='entity_id') | join(', ') }}", ) assert_result_info(info, "test.object", ["group.new_group"]) info = render_to_info( - hass, "{{ expand(states.group) | map(attribute='entity_id')" " | join(', ') }}" + hass, "{{ expand(states.group) | map(attribute='entity_id') | join(', ') }}" ) assert_result_info(info, "test.object", ["group.new_group"], ["group"]) @@ -1437,7 +1437,7 @@ def test_closest_function_to_state(hass): assert ( template.Template( - "{{ closest(states.zone.far_away, " "states.test_domain).entity_id }}", hass + "{{ closest(states.zone.far_away, states.test_domain).entity_id }}", hass ).async_render() == "test_domain.closest_zone" ) @@ -1471,7 +1471,7 @@ def test_closest_function_state_with_invalid_location(hass): assert ( template.Template( - "{{ closest(states.test_domain.closest_home, " "states) }}", hass + "{{ closest(states.test_domain.closest_home, states) }}", hass ).async_render() == "None" ) @@ -1517,7 +1517,7 @@ def test_extract_entities_none_exclude_stuff(hass): assert ( template.extract_entities( - "{{ closest(states.zone.far_away, " "states.test_domain).entity_id }}" + "{{ closest(states.zone.far_away, states.test_domain).entity_id }}" ) == MATCH_ALL ) diff --git a/tests/scripts/test_check_config.py b/tests/scripts/test_check_config.py index 481efc6fb30..ea7ae03b5db 100644 --- a/tests/scripts/test_check_config.py +++ b/tests/scripts/test_check_config.py @@ -20,7 +20,7 @@ BASE_CONFIG = ( "\n\n" ) -BAD_CORE_CONFIG = "homeassistant:\n" " unit_system: bad\n" "\n\n" +BAD_CORE_CONFIG = "homeassistant:\n unit_system: bad\n\n\n" def normalize_yaml_files(check_dict): @@ -92,8 +92,8 @@ def test_secrets(isfile_patch, loop): files = { get_test_config_dir(YAML_CONFIG_FILE): BASE_CONFIG - + ("http:\n" " cors_allowed_origins: !secret http_pw"), - secrets_path: ("logger: debug\n" "http_pw: http://google.com"), + + ("http:\n cors_allowed_origins: !secret http_pw"), + secrets_path: ("logger: debug\nhttp_pw: http://google.com"), } with patch_yaml_files(files): @@ -122,8 +122,7 @@ def test_secrets(isfile_patch, loop): def test_package_invalid(isfile_patch, loop): """Test a valid platform setup.""" files = { - YAML_CONFIG_FILE: BASE_CONFIG - + (" packages:\n" " p1:\n" ' group: ["a"]') + YAML_CONFIG_FILE: BASE_CONFIG + (" packages:\n p1:\n" ' group: ["a"]') } with patch_yaml_files(files): res = check_config.check(get_test_config_dir()) diff --git a/tests/util/test_yaml.py b/tests/util/test_yaml.py index ba31ce57010..622d87d1a27 100644 --- a/tests/util/test_yaml.py +++ b/tests/util/test_yaml.py @@ -392,7 +392,7 @@ class TestSecrets(unittest.TestCase): with pytest.raises(HomeAssistantError): load_yaml( os.path.join(self._sub_folder_path, "sub.yaml"), - "http:\n" " api_password: !secret test", + "http:\n api_password: !secret test", ) def test_secrets_keyring(self): @@ -431,9 +431,9 @@ class TestSecrets(unittest.TestCase): def test_secrets_are_not_dict(self): """Did secrets handle non-dict file.""" - FILES[self._secret_path] = ( - "- http_pw: pwhttp\n" " comp1_un: un1\n" " comp1_pw: pw1\n" - ) + FILES[ + self._secret_path + ] = "- http_pw: pwhttp\n comp1_un: un1\n comp1_pw: pw1\n" yaml.clear_secret_cache() with pytest.raises(HomeAssistantError): load_yaml( From 24d5e54eedc40c08204107366c3d411590ad3ca1 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 2 Jan 2020 21:16:58 +0100 Subject: [PATCH 497/677] Bump pre-commit to 1.21.0 (#30406) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 37268e70726..f4fb13a417c 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -7,7 +7,7 @@ asynctest==0.13.0 codecov==2.0.15 mock-open==1.3.1 mypy==0.761 -pre-commit==1.20.0 +pre-commit==1.21.0 pylint==2.4.4 astroid==2.3.3 pytest-aiohttp==0.3.0 From 5edf72c9ea717e526375aa9339812187e3dc5c5e Mon Sep 17 00:00:00 2001 From: Aaron David Schneider Date: Thu, 2 Jan 2020 21:17:37 +0100 Subject: [PATCH 498/677] fix issuecomment-570284682 (#30405) --- homeassistant/components/fritz/device_tracker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/fritz/device_tracker.py b/homeassistant/components/fritz/device_tracker.py index e2382490cde..f27e409a28d 100644 --- a/homeassistant/components/fritz/device_tracker.py +++ b/homeassistant/components/fritz/device_tracker.py @@ -84,7 +84,7 @@ class FritzBoxScanner(DeviceScanner): ip_device = self.fritz_box.get_specific_host_entry(device).get("NewIPAddress") if not ip_device: - return None + return {} return {"ip": ip_device, "mac": device} def _update_info(self): From 7b00e941849f6ef68f994c113bbbf19a66536f9c Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 2 Jan 2020 21:21:54 +0100 Subject: [PATCH 499/677] Migrate local_file tests from coroutine to async/await (#30392) --- tests/components/local_file/test_camera.py | 40 ++++++++++------------ 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/tests/components/local_file/test_camera.py b/tests/components/local_file/test_camera.py index 042b0f76400..92650715e5d 100644 --- a/tests/components/local_file/test_camera.py +++ b/tests/components/local_file/test_camera.py @@ -1,5 +1,4 @@ """The tests for local file camera component.""" -import asyncio from unittest import mock from homeassistant.components.local_file.const import DOMAIN, SERVICE_UPDATE_FILE_PATH @@ -8,15 +7,14 @@ from homeassistant.setup import async_setup_component from tests.common import mock_registry -@asyncio.coroutine -def test_loading_file(hass, hass_client): +async def test_loading_file(hass, hass_client): """Test that it loads image from disk.""" mock_registry(hass) with mock.patch("os.path.isfile", mock.Mock(return_value=True)), mock.patch( "os.access", mock.Mock(return_value=True) ): - yield from async_setup_component( + await async_setup_component( hass, "camera", { @@ -28,26 +26,25 @@ def test_loading_file(hass, hass_client): }, ) - client = yield from hass_client() + client = await hass_client() m_open = mock.mock_open(read_data=b"hello") with mock.patch( "homeassistant.components.local_file.camera.open", m_open, create=True ): - resp = yield from client.get("/api/camera_proxy/camera.config_test") + resp = await client.get("/api/camera_proxy/camera.config_test") assert resp.status == 200 - body = yield from resp.text() + body = await resp.text() assert body == "hello" -@asyncio.coroutine -def test_file_not_readable(hass, caplog): +async def test_file_not_readable(hass, caplog): """Test a warning is shown setup when file is not readable.""" with mock.patch("os.path.isfile", mock.Mock(return_value=True)), mock.patch( "os.access", mock.Mock(return_value=False) ): - yield from async_setup_component( + await async_setup_component( hass, "camera", { @@ -64,8 +61,7 @@ def test_file_not_readable(hass, caplog): assert "mock.file" in caplog.text -@asyncio.coroutine -def test_camera_content_type(hass, hass_client): +async def test_camera_content_type(hass, hass_client): """Test local_file camera content_type.""" cam_config_jpg = { "name": "test_jpg", @@ -88,43 +84,43 @@ def test_camera_content_type(hass, hass_client): "file_path": "/path/to/image", } - yield from async_setup_component( + await async_setup_component( hass, "camera", {"camera": [cam_config_jpg, cam_config_png, cam_config_svg, cam_config_noext]}, ) - client = yield from hass_client() + client = await hass_client() image = "hello" m_open = mock.mock_open(read_data=image.encode()) with mock.patch( "homeassistant.components.local_file.camera.open", m_open, create=True ): - resp_1 = yield from client.get("/api/camera_proxy/camera.test_jpg") - resp_2 = yield from client.get("/api/camera_proxy/camera.test_png") - resp_3 = yield from client.get("/api/camera_proxy/camera.test_svg") - resp_4 = yield from client.get("/api/camera_proxy/camera.test_no_ext") + resp_1 = await client.get("/api/camera_proxy/camera.test_jpg") + resp_2 = await client.get("/api/camera_proxy/camera.test_png") + resp_3 = await client.get("/api/camera_proxy/camera.test_svg") + resp_4 = await client.get("/api/camera_proxy/camera.test_no_ext") assert resp_1.status == 200 assert resp_1.content_type == "image/jpeg" - body = yield from resp_1.text() + body = await resp_1.text() assert body == image assert resp_2.status == 200 assert resp_2.content_type == "image/png" - body = yield from resp_2.text() + body = await resp_2.text() assert body == image assert resp_3.status == 200 assert resp_3.content_type == "image/svg+xml" - body = yield from resp_3.text() + body = await resp_3.text() assert body == image # default mime type assert resp_4.status == 200 assert resp_4.content_type == "image/jpeg" - body = yield from resp_4.text() + body = await resp_4.text() assert body == image From 5b2b86987b0ce114e644614a44413a2d45ad5423 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 2 Jan 2020 21:22:13 +0100 Subject: [PATCH 500/677] Migrate fido tests from coroutine to async/await (#30391) --- tests/components/fido/test_sensor.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/tests/components/fido/test_sensor.py b/tests/components/fido/test_sensor.py index 1f67a1e2e11..9a316a85735 100644 --- a/tests/components/fido/test_sensor.py +++ b/tests/components/fido/test_sensor.py @@ -1,5 +1,4 @@ """The test for the fido sensor platform.""" -import asyncio import logging import sys from unittest.mock import MagicMock, patch @@ -27,8 +26,7 @@ class FidoClientMock: """Return fake fido data.""" return {"balance": 160.12, "1112223344": {"data_remaining": 100.33}} - @asyncio.coroutine - def fetch_data(self): + async def fetch_data(self): """Return fake fetching data.""" pass @@ -36,8 +34,7 @@ class FidoClientMock: class FidoClientMockError(FidoClientMock): """Fake Fido client error.""" - @asyncio.coroutine - def fetch_data(self): + async def fetch_data(self): """Return fake fetching data.""" raise PyFidoErrorMock("Fake Error") @@ -63,8 +60,7 @@ def fake_async_add_entities(component, update_before_add=False): pass -@asyncio.coroutine -def test_fido_sensor(loop, hass): +async def test_fido_sensor(loop, hass): """Test the Fido number sensor.""" with patch( "homeassistant.components.fido.sensor.FidoClient", new=FidoClientMock @@ -79,7 +75,7 @@ def test_fido_sensor(loop, hass): } } with assert_setup_component(1): - yield from async_setup_component(hass, "sensor", config) + await async_setup_component(hass, "sensor", config) state = hass.states.get("sensor.fido_1112223344_balance") assert state.state == "160.12" assert state.attributes.get("number") == "1112223344" @@ -87,8 +83,7 @@ def test_fido_sensor(loop, hass): assert state.state == "100.33" -@asyncio.coroutine -def test_error(hass, caplog): +async def test_error(hass, caplog): """Test the Fido sensor errors.""" caplog.set_level(logging.ERROR) sys.modules["pyfido"] = PyFidoFakeModule() @@ -96,5 +91,5 @@ def test_error(hass, caplog): config = {} fake_async_add_entities = MagicMock() - yield from fido.async_setup_platform(hass, config, fake_async_add_entities) + await fido.async_setup_platform(hass, config, fake_async_add_entities) assert fake_async_add_entities.called is False From 332cbbd8b1be773593037d293c5dabbf6c100199 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 2 Jan 2020 21:22:30 +0100 Subject: [PATCH 501/677] Migrate freedns tests from coroutine to async/await (#30390) --- tests/components/freedns/test_init.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/tests/components/freedns/test_init.py b/tests/components/freedns/test_init.py index b9e59de9ff1..1a64aa498e2 100644 --- a/tests/components/freedns/test_init.py +++ b/tests/components/freedns/test_init.py @@ -1,6 +1,4 @@ """Test the FreeDNS component.""" -import asyncio - import pytest from homeassistant.components import freedns @@ -37,8 +35,7 @@ def setup_freedns(hass, aioclient_mock): ) -@asyncio.coroutine -def test_setup(hass, aioclient_mock): +async def test_setup(hass, aioclient_mock): """Test setup works if update passes.""" params = {} params[ACCESS_TOKEN] = "" @@ -46,7 +43,7 @@ def test_setup(hass, aioclient_mock): UPDATE_URL, params=params, text="ERROR: Address has not changed." ) - result = yield from async_setup_component( + result = await async_setup_component( hass, freedns.DOMAIN, { @@ -60,18 +57,17 @@ def test_setup(hass, aioclient_mock): assert aioclient_mock.call_count == 1 async_fire_time_changed(hass, utcnow() + UPDATE_INTERVAL) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert aioclient_mock.call_count == 2 -@asyncio.coroutine -def test_setup_fails_if_wrong_token(hass, aioclient_mock): +async def test_setup_fails_if_wrong_token(hass, aioclient_mock): """Test setup fails if first update fails through wrong token.""" params = {} params[ACCESS_TOKEN] = "" aioclient_mock.get(UPDATE_URL, params=params, text="ERROR: Invalid update URL (2)") - result = yield from async_setup_component( + result = await async_setup_component( hass, freedns.DOMAIN, { From 1e3822bdd7744a0193b24fd805487319ae08989a Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 2 Jan 2020 21:22:49 +0100 Subject: [PATCH 502/677] Migrate group tests from coroutine to async/await (#30389) --- tests/components/group/test_init.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/tests/components/group/test_init.py b/tests/components/group/test_init.py index 5c826dbe85d..ee52a551cb8 100644 --- a/tests/components/group/test_init.py +++ b/tests/components/group/test_init.py @@ -1,6 +1,5 @@ """The tests for the Group components.""" # pylint: disable=protected-access -import asyncio from collections import OrderedDict import unittest from unittest.mock import patch @@ -481,25 +480,23 @@ class TestComponentsGroup(unittest.TestCase): assert group_state.attributes.get(ATTR_FRIENDLY_NAME) == "friendly_name" -@asyncio.coroutine -def test_service_group_services(hass): +async def test_service_group_services(hass): """Check if service are available.""" with assert_setup_component(0, "group"): - yield from async_setup_component(hass, "group", {"group": {}}) + await async_setup_component(hass, "group", {"group": {}}) assert hass.services.has_service("group", group.SERVICE_SET) assert hass.services.has_service("group", group.SERVICE_REMOVE) # pylint: disable=invalid-name -@asyncio.coroutine -def test_service_group_set_group_remove_group(hass): +async def test_service_group_set_group_remove_group(hass): """Check if service are available.""" with assert_setup_component(0, "group"): - yield from async_setup_component(hass, "group", {"group": {}}) + await async_setup_component(hass, "group", {"group": {}}) common.async_set_group(hass, "user_test_group", name="Test") - yield from hass.async_block_till_done() + await hass.async_block_till_done() group_state = hass.states.get("group.user_test_group") assert group_state @@ -513,7 +510,7 @@ def test_service_group_set_group_remove_group(hass): visible=False, entity_ids=["test.entity_bla1"], ) - yield from hass.async_block_till_done() + await hass.async_block_till_done() group_state = hass.states.get("group.user_test_group") assert group_state @@ -531,7 +528,7 @@ def test_service_group_set_group_remove_group(hass): control="hidden", add=["test.entity_id2"], ) - yield from hass.async_block_till_done() + await hass.async_block_till_done() group_state = hass.states.get("group.user_test_group") assert group_state @@ -546,7 +543,7 @@ def test_service_group_set_group_remove_group(hass): ) common.async_remove(hass, "user_test_group") - yield from hass.async_block_till_done() + await hass.async_block_till_done() group_state = hass.states.get("group.user_test_group") assert group_state is None From b9fcb87d2c0982d0f8c9eae986ab42634545447f Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 2 Jan 2020 21:23:16 +0100 Subject: [PATCH 503/677] Migrate generic tests from coroutine to async/await (#30388) --- tests/components/generic/test_camera.py | 73 ++++++++++++------------- 1 file changed, 34 insertions(+), 39 deletions(-) diff --git a/tests/components/generic/test_camera.py b/tests/components/generic/test_camera.py index c25b4ce9f3d..7f49d1b8979 100644 --- a/tests/components/generic/test_camera.py +++ b/tests/components/generic/test_camera.py @@ -5,12 +5,11 @@ from unittest import mock from homeassistant.setup import async_setup_component -@asyncio.coroutine -def test_fetching_url(aioclient_mock, hass, hass_client): +async def test_fetching_url(aioclient_mock, hass, hass_client): """Test that it fetches the given url.""" aioclient_mock.get("http://example.com", text="hello world") - yield from async_setup_component( + await async_setup_component( hass, "camera", { @@ -24,25 +23,24 @@ def test_fetching_url(aioclient_mock, hass, hass_client): }, ) - client = yield from hass_client() + client = await hass_client() - resp = yield from client.get("/api/camera_proxy/camera.config_test") + resp = await client.get("/api/camera_proxy/camera.config_test") assert resp.status == 200 assert aioclient_mock.call_count == 1 - body = yield from resp.text() + body = await resp.text() assert body == "hello world" - resp = yield from client.get("/api/camera_proxy/camera.config_test") + resp = await client.get("/api/camera_proxy/camera.config_test") assert aioclient_mock.call_count == 2 -@asyncio.coroutine -def test_fetching_without_verify_ssl(aioclient_mock, hass, hass_client): +async def test_fetching_without_verify_ssl(aioclient_mock, hass, hass_client): """Test that it fetches the given url when ssl verify is off.""" aioclient_mock.get("https://example.com", text="hello world") - yield from async_setup_component( + await async_setup_component( hass, "camera", { @@ -57,19 +55,18 @@ def test_fetching_without_verify_ssl(aioclient_mock, hass, hass_client): }, ) - client = yield from hass_client() + client = await hass_client() - resp = yield from client.get("/api/camera_proxy/camera.config_test") + resp = await client.get("/api/camera_proxy/camera.config_test") assert resp.status == 200 -@asyncio.coroutine -def test_fetching_url_with_verify_ssl(aioclient_mock, hass, hass_client): +async def test_fetching_url_with_verify_ssl(aioclient_mock, hass, hass_client): """Test that it fetches the given url when ssl verify is explicitly on.""" aioclient_mock.get("https://example.com", text="hello world") - yield from async_setup_component( + await async_setup_component( hass, "camera", { @@ -84,22 +81,21 @@ def test_fetching_url_with_verify_ssl(aioclient_mock, hass, hass_client): }, ) - client = yield from hass_client() + client = await hass_client() - resp = yield from client.get("/api/camera_proxy/camera.config_test") + resp = await client.get("/api/camera_proxy/camera.config_test") assert resp.status == 200 -@asyncio.coroutine -def test_limit_refetch(aioclient_mock, hass, hass_client): +async def test_limit_refetch(aioclient_mock, hass, hass_client): """Test that it fetches the given url.""" aioclient_mock.get("http://example.com/5a", text="hello world") aioclient_mock.get("http://example.com/10a", text="hello world") aioclient_mock.get("http://example.com/15a", text="hello planet") aioclient_mock.get("http://example.com/20a", status=404) - yield from async_setup_component( + await async_setup_component( hass, "camera", { @@ -112,51 +108,50 @@ def test_limit_refetch(aioclient_mock, hass, hass_client): }, ) - client = yield from hass_client() + client = await hass_client() - resp = yield from client.get("/api/camera_proxy/camera.config_test") + resp = await client.get("/api/camera_proxy/camera.config_test") hass.states.async_set("sensor.temp", "5") with mock.patch("async_timeout.timeout", side_effect=asyncio.TimeoutError()): - resp = yield from client.get("/api/camera_proxy/camera.config_test") + resp = await client.get("/api/camera_proxy/camera.config_test") assert aioclient_mock.call_count == 0 assert resp.status == 500 hass.states.async_set("sensor.temp", "10") - resp = yield from client.get("/api/camera_proxy/camera.config_test") + resp = await client.get("/api/camera_proxy/camera.config_test") assert aioclient_mock.call_count == 1 assert resp.status == 200 - body = yield from resp.text() + body = await resp.text() assert body == "hello world" - resp = yield from client.get("/api/camera_proxy/camera.config_test") + resp = await client.get("/api/camera_proxy/camera.config_test") assert aioclient_mock.call_count == 1 assert resp.status == 200 - body = yield from resp.text() + body = await resp.text() assert body == "hello world" hass.states.async_set("sensor.temp", "15") # Url change = fetch new image - resp = yield from client.get("/api/camera_proxy/camera.config_test") + resp = await client.get("/api/camera_proxy/camera.config_test") assert aioclient_mock.call_count == 2 assert resp.status == 200 - body = yield from resp.text() + body = await resp.text() assert body == "hello planet" # Cause a template render error hass.states.async_remove("sensor.temp") - resp = yield from client.get("/api/camera_proxy/camera.config_test") + resp = await client.get("/api/camera_proxy/camera.config_test") assert aioclient_mock.call_count == 2 assert resp.status == 200 - body = yield from resp.text() + body = await resp.text() assert body == "hello planet" -@asyncio.coroutine -def test_camera_content_type(aioclient_mock, hass, hass_client): +async def test_camera_content_type(aioclient_mock, hass, hass_client): """Test generic camera with custom content_type.""" svg_image = "" urlsvg = "https://upload.wikimedia.org/wikipedia/commons/0/02/SVG_logo.svg" @@ -172,22 +167,22 @@ def test_camera_content_type(aioclient_mock, hass, hass_client): cam_config_normal.pop("content_type") cam_config_normal["name"] = "config_test_jpg" - yield from async_setup_component( + await async_setup_component( hass, "camera", {"camera": [cam_config_svg, cam_config_normal]} ) - client = yield from hass_client() + client = await hass_client() - resp_1 = yield from client.get("/api/camera_proxy/camera.config_test_svg") + resp_1 = await client.get("/api/camera_proxy/camera.config_test_svg") assert aioclient_mock.call_count == 1 assert resp_1.status == 200 assert resp_1.content_type == "image/svg+xml" - body = yield from resp_1.text() + body = await resp_1.text() assert body == svg_image - resp_2 = yield from client.get("/api/camera_proxy/camera.config_test_jpg") + resp_2 = await client.get("/api/camera_proxy/camera.config_test_jpg") assert aioclient_mock.call_count == 2 assert resp_2.status == 200 assert resp_2.content_type == "image/jpeg" - body = yield from resp_2.text() + body = await resp_2.text() assert body == svg_image From 4e6d41554127f52999be99de656e5f7d24d27928 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 2 Jan 2020 21:23:35 +0100 Subject: [PATCH 504/677] Migrate media_player tests from coroutine to async/await (#30387) --- .../media_player/test_async_helpers.py | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/tests/components/media_player/test_async_helpers.py b/tests/components/media_player/test_async_helpers.py index 2e1ded3f084..2cbca449ed6 100644 --- a/tests/components/media_player/test_async_helpers.py +++ b/tests/components/media_player/test_async_helpers.py @@ -44,28 +44,23 @@ class AsyncMediaPlayer(mp.MediaPlayerDevice): | mp.const.SUPPORT_TURN_ON ) - @asyncio.coroutine - def async_set_volume_level(self, volume): + async def async_set_volume_level(self, volume): """Set volume level, range 0..1.""" self._volume = volume - @asyncio.coroutine - def async_media_play(self): + async def async_media_play(self): """Send play command.""" self._state = STATE_PLAYING - @asyncio.coroutine - def async_media_pause(self): + async def async_media_pause(self): """Send pause command.""" self._state = STATE_PAUSED - @asyncio.coroutine - def async_turn_on(self): + async def async_turn_on(self): """Turn the media player on.""" self._state = STATE_ON - @asyncio.coroutine - def async_turn_off(self): + async def async_turn_off(self): """Turn the media player off.""" self._state = STATE_OFF @@ -129,21 +124,19 @@ class SyncMediaPlayer(mp.MediaPlayerDevice): else: self._state = STATE_OFF - @asyncio.coroutine - def async_media_play_pause(self): + async def async_media_play_pause(self): """Create a coroutine to wrap the future returned by ABC. This allows the run_coroutine_threadsafe helper to be used. """ - yield from super().async_media_play_pause() + await super().async_media_play_pause() - @asyncio.coroutine - def async_toggle(self): + async def async_toggle(self): """Create a coroutine to wrap the future returned by ABC. This allows the run_coroutine_threadsafe helper to be used. """ - yield from super().async_toggle() + await super().async_toggle() class TestAsyncMediaPlayer(unittest.TestCase): From 4f8663846b3b692ebf54f42875aa4fb218e8f5a5 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 2 Jan 2020 21:23:56 +0100 Subject: [PATCH 505/677] Migrate frontend tests from coroutine to async/await (#30386) --- tests/components/frontend/test_init.py | 39 +++++++++++--------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/tests/components/frontend/test_init.py b/tests/components/frontend/test_init.py index f8fd5f1d7e3..56a945f62df 100644 --- a/tests/components/frontend/test_init.py +++ b/tests/components/frontend/test_init.py @@ -1,5 +1,4 @@ """The tests for Home Assistant frontend.""" -import asyncio import re from unittest.mock import patch @@ -71,53 +70,48 @@ def mock_onboarded(): yield -@asyncio.coroutine -def test_frontend_and_static(mock_http_client, mock_onboarded): +async def test_frontend_and_static(mock_http_client, mock_onboarded): """Test if we can get the frontend.""" - resp = yield from mock_http_client.get("") + resp = await mock_http_client.get("") assert resp.status == 200 assert "cache-control" not in resp.headers - text = yield from resp.text() + text = await resp.text() # Test we can retrieve frontend.js frontendjs = re.search(r"(?P\/frontend_es5\/app.[A-Za-z0-9]{8}.js)", text) assert frontendjs is not None, text - resp = yield from mock_http_client.get(frontendjs.groups(0)[0]) + resp = await mock_http_client.get(frontendjs.groups(0)[0]) assert resp.status == 200 assert "public" in resp.headers.get("cache-control") -@asyncio.coroutine -def test_dont_cache_service_worker(mock_http_client): +async def test_dont_cache_service_worker(mock_http_client): """Test that we don't cache the service worker.""" - resp = yield from mock_http_client.get("/service_worker.js") + resp = await mock_http_client.get("/service_worker.js") assert resp.status == 200 assert "cache-control" not in resp.headers -@asyncio.coroutine -def test_404(mock_http_client): +async def test_404(mock_http_client): """Test for HTTP 404 error.""" - resp = yield from mock_http_client.get("/not-existing") + resp = await mock_http_client.get("/not-existing") assert resp.status == 404 -@asyncio.coroutine -def test_we_cannot_POST_to_root(mock_http_client): +async def test_we_cannot_POST_to_root(mock_http_client): """Test that POST is not allow to root.""" - resp = yield from mock_http_client.post("/") + resp = await mock_http_client.post("/") assert resp.status == 405 -@asyncio.coroutine -def test_states_routes(mock_http_client): +async def test_states_routes(mock_http_client): """All served by index.""" - resp = yield from mock_http_client.get("/states") + resp = await mock_http_client.get("/states") assert resp.status == 200 - resp = yield from mock_http_client.get("/states/group.existing") + resp = await mock_http_client.get("/states/group.existing") assert resp.status == 200 @@ -211,12 +205,11 @@ async def test_missing_themes(hass, hass_ws_client): assert msg["result"]["themes"] == {} -@asyncio.coroutine -def test_extra_urls(mock_http_client_with_urls, mock_onboarded): +async def test_extra_urls(mock_http_client_with_urls, mock_onboarded): """Test that extra urls are loaded.""" - resp = yield from mock_http_client_with_urls.get("/states?latest") + resp = await mock_http_client_with_urls.get("/states?latest") assert resp.status == 200 - text = yield from resp.text() + text = await resp.text() assert text.find('href="https://domain.com/my_extra_url.html"') >= 0 From 8e5ccfcb8a1f8260395cf3e2d79eca66a2cc0e75 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Thu, 2 Jan 2020 22:24:29 +0100 Subject: [PATCH 506/677] Bump miflora and bluepy (#30411) --- homeassistant/components/decora/manifest.json | 5 +---- homeassistant/components/miflora/manifest.json | 10 ++-------- requirements_all.txt | 4 ++-- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/decora/manifest.json b/homeassistant/components/decora/manifest.json index 5142b5fb2e2..270b0ca763d 100644 --- a/homeassistant/components/decora/manifest.json +++ b/homeassistant/components/decora/manifest.json @@ -2,10 +2,7 @@ "domain": "decora", "name": "Decora", "documentation": "https://www.home-assistant.io/integrations/decora", - "requirements": [ - "bluepy==1.1.4", - "decora==0.6" - ], + "requirements": ["bluepy==1.3.0", "decora==0.6"], "dependencies": [], "codeowners": [] } diff --git a/homeassistant/components/miflora/manifest.json b/homeassistant/components/miflora/manifest.json index 54fa59135b3..0cce06935af 100644 --- a/homeassistant/components/miflora/manifest.json +++ b/homeassistant/components/miflora/manifest.json @@ -2,13 +2,7 @@ "domain": "miflora", "name": "Miflora", "documentation": "https://www.home-assistant.io/integrations/miflora", - "requirements": [ - "bluepy==1.1.4", - "miflora==0.4.0" - ], + "requirements": ["bluepy==1.3.0", "miflora==0.6.0"], "dependencies": [], - "codeowners": [ - "@danielhiversen", - "@ChristianKuehnel" - ] + "codeowners": ["@danielhiversen", "@ChristianKuehnel"] } diff --git a/requirements_all.txt b/requirements_all.txt index d2ed4e0e1e2..c3550718fb6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -318,7 +318,7 @@ blockchain==1.4.4 # homeassistant.components.decora # homeassistant.components.miflora -# bluepy==1.1.4 +# bluepy==1.3.0 # homeassistant.components.bme680 # bme680==1.0.5 @@ -846,7 +846,7 @@ meteofrance==0.3.7 mficlient==0.3.0 # homeassistant.components.miflora -miflora==0.4.0 +miflora==0.6.0 # homeassistant.components.mill millheater==0.3.4 From b91cbb50c86008f1f81c8d2248c4b3d90fd77ccd Mon Sep 17 00:00:00 2001 From: michaeldavie Date: Thu, 2 Jan 2020 16:24:43 -0500 Subject: [PATCH 507/677] Bump env_canada to 0.0.31 (#30409) --- homeassistant/components/environment_canada/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/environment_canada/manifest.json b/homeassistant/components/environment_canada/manifest.json index 8ad13b39251..bfe0aa5d2cb 100644 --- a/homeassistant/components/environment_canada/manifest.json +++ b/homeassistant/components/environment_canada/manifest.json @@ -3,7 +3,7 @@ "name": "Environment Canada", "documentation": "https://www.home-assistant.io/integrations/environment_canada", "requirements": [ - "env_canada==0.0.30" + "env_canada==0.0.31" ], "dependencies": [], "codeowners": [ diff --git a/requirements_all.txt b/requirements_all.txt index c3550718fb6..dd19553b961 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -480,7 +480,7 @@ enocean==0.50 enturclient==0.2.1 # homeassistant.components.environment_canada -env_canada==0.0.30 +env_canada==0.0.31 # homeassistant.components.envirophat # envirophat==0.0.6 From 9064058a030efd002ac78954c09e2f3122a038d7 Mon Sep 17 00:00:00 2001 From: Josh Bendavid Date: Thu, 2 Jan 2020 16:30:20 -0500 Subject: [PATCH 508/677] Add generic command functionality to denonavr (#29295) * Add generic command functionality to denonavr * add minimal unit tests for denonavr * fix import order * simplify denonavr unit test * handle domain specific service calls with dispatcher * update unit tests * update unit tests * remove unnecessary return value * fix handling of mock instances in unit tests --- homeassistant/components/denonavr/__init__.py | 34 +++++++++++ .../components/denonavr/media_player.py | 24 ++++++++ .../components/denonavr/services.yaml | 11 ++++ requirements_test_all.txt | 3 + tests/components/denonavr/__init__.py | 1 + .../components/denonavr/test_media_player.py | 57 +++++++++++++++++++ 6 files changed, 130 insertions(+) create mode 100644 homeassistant/components/denonavr/services.yaml create mode 100644 tests/components/denonavr/__init__.py create mode 100644 tests/components/denonavr/test_media_player.py diff --git a/homeassistant/components/denonavr/__init__.py b/homeassistant/components/denonavr/__init__.py index dee84449d13..8877a7dfb3b 100644 --- a/homeassistant/components/denonavr/__init__.py +++ b/homeassistant/components/denonavr/__init__.py @@ -1 +1,35 @@ """The denonavr component.""" +import voluptuous as vol + +from homeassistant.const import ATTR_ENTITY_ID +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import dispatcher_send + +DOMAIN = "denonavr" + +SERVICE_GET_COMMAND = "get_command" +ATTR_COMMAND = "command" + +CALL_SCHEMA = vol.Schema({vol.Required(ATTR_ENTITY_ID): cv.comp_entity_ids}) + +GET_COMMAND_SCHEMA = CALL_SCHEMA.extend({vol.Required(ATTR_COMMAND): cv.string}) + +SERVICE_TO_METHOD = { + SERVICE_GET_COMMAND: {"method": "get_command", "schema": GET_COMMAND_SCHEMA} +} + + +def setup(hass, config): + """Set up the denonavr platform.""" + + def service_handler(service): + method = SERVICE_TO_METHOD.get(service.service) + data = service.data.copy() + data["method"] = method["method"] + dispatcher_send(hass, DOMAIN, data) + + for service in SERVICE_TO_METHOD: + schema = SERVICE_TO_METHOD[service]["schema"] + hass.services.register(DOMAIN, service, service_handler, schema=schema) + + return True diff --git a/homeassistant/components/denonavr/media_player.py b/homeassistant/components/denonavr/media_player.py index 1725b2d105c..46d22187ce1 100644 --- a/homeassistant/components/denonavr/media_player.py +++ b/homeassistant/components/denonavr/media_player.py @@ -24,16 +24,21 @@ from homeassistant.components.media_player.const import ( SUPPORT_VOLUME_STEP, ) from homeassistant.const import ( + ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_TIMEOUT, CONF_ZONE, + ENTITY_MATCH_ALL, STATE_OFF, STATE_ON, STATE_PAUSED, STATE_PLAYING, ) import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from . import DOMAIN _LOGGER = logging.getLogger(__name__) @@ -190,6 +195,21 @@ class DenonDevice(MediaPlayerDevice): self._sound_mode_support and SUPPORT_SELECT_SOUND_MODE ) + async def async_added_to_hass(self): + """Register signal handler.""" + async_dispatcher_connect(self.hass, DOMAIN, self.signal_handler) + + def signal_handler(self, data): + """Handle domain-specific signal by calling appropriate method.""" + entity_ids = data[ATTR_ENTITY_ID] + if entity_ids == ENTITY_MATCH_ALL or self.entity_id in entity_ids: + params = { + key: value + for key, value in data.items() + if key not in ["entity_id", "method"] + } + getattr(self, data["method"])(**params) + def update(self): """Get the latest status information from device.""" self._receiver.update() @@ -398,3 +418,7 @@ class DenonDevice(MediaPlayerDevice): def mute_volume(self, mute): """Send mute command.""" return self._receiver.mute(mute) + + def get_command(self, command, **kwargs): + """Send generic command.""" + self._receiver.send_get_command(command) diff --git a/homeassistant/components/denonavr/services.yaml b/homeassistant/components/denonavr/services.yaml new file mode 100644 index 00000000000..889adc3af05 --- /dev/null +++ b/homeassistant/components/denonavr/services.yaml @@ -0,0 +1,11 @@ +# Describes the format for available webostv services + +get_command: + description: 'Send a generic http get command.' + fields: + entity_id: + description: Name(s) of the denonavr entities where to run the API method. + example: 'media_player.living_room_receiver' + command: + description: Endpoint of the command, including associated parameters. + example: '/goform/formiPhoneAppDirect.xml?RCKSK0410370' diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ff1ded4434c..373abe92ec8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -149,6 +149,9 @@ datadog==0.15.0 # homeassistant.components.ssdp defusedxml==0.6.0 +# homeassistant.components.denonavr +denonavr==0.7.10 + # homeassistant.components.directv directpy==0.5 diff --git a/tests/components/denonavr/__init__.py b/tests/components/denonavr/__init__.py new file mode 100644 index 00000000000..5ad16068f2a --- /dev/null +++ b/tests/components/denonavr/__init__.py @@ -0,0 +1 @@ +"""Tests for the denonavr integration.""" diff --git a/tests/components/denonavr/test_media_player.py b/tests/components/denonavr/test_media_player.py new file mode 100644 index 00000000000..91bc2abf94d --- /dev/null +++ b/tests/components/denonavr/test_media_player.py @@ -0,0 +1,57 @@ +"""The tests for the denonavr media player platform.""" +from unittest.mock import patch + +import pytest + +from homeassistant.components import media_player +from homeassistant.components.denonavr import ATTR_COMMAND, DOMAIN, SERVICE_GET_COMMAND +from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_PLATFORM +from homeassistant.setup import async_setup_component + +NAME = "fake" +ENTITY_ID = f"{media_player.DOMAIN}.{NAME}" + + +@pytest.fixture(name="client") +def client_fixture(): + """Patch of client library for tests.""" + with patch( + "homeassistant.components.denonavr.media_player.denonavr.DenonAVR", + autospec=True, + ) as mock_client_class, patch( + "homeassistant.components.denonavr.media_player.denonavr.discover" + ): + mock_client_class.return_value.name = NAME + mock_client_class.return_value.zones = {"Main": mock_client_class.return_value} + yield mock_client_class.return_value + + +async def setup_denonavr(hass): + """Initialize webostv and media_player for tests.""" + assert await async_setup_component( + hass, + media_player.DOMAIN, + { + media_player.DOMAIN: { + CONF_PLATFORM: "denonavr", + CONF_HOST: "fake", + CONF_NAME: NAME, + } + }, + ) + await hass.async_block_till_done() + + +async def test_get_command(hass, client): + """Test generic command functionality.""" + + await setup_denonavr(hass) + + data = { + ATTR_ENTITY_ID: ENTITY_ID, + ATTR_COMMAND: "test", + } + await hass.services.async_call(DOMAIN, SERVICE_GET_COMMAND, data) + await hass.async_block_till_done() + + client.send_get_command.assert_called_with("test") From c1936f6fe4b28a3899c31524027c06e7b31a050b Mon Sep 17 00:00:00 2001 From: Josh Bendavid Date: Thu, 2 Jan 2020 16:32:57 -0500 Subject: [PATCH 509/677] Add generic command/button functionality to webostv (#30379) * add generic command/button functionality to webostv * update codeowners --- CODEOWNERS | 1 + homeassistant/components/webostv/__init__.py | 31 +++++++++++++++ .../components/webostv/manifest.json | 2 +- .../components/webostv/media_player.py | 28 ++++++++++++- .../components/webostv/services.yaml | 26 +++++++++++++ tests/components/webostv/test_media_player.py | 39 ++++++++++++++++++- 6 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 homeassistant/components/webostv/services.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 23005cb5273..04918e979ee 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -365,6 +365,7 @@ homeassistant/components/waqi/* @andrey-git homeassistant/components/watson_tts/* @rutkai homeassistant/components/weather/* @fabaff homeassistant/components/weblink/* @home-assistant/core +homeassistant/components/webostv/* @bendavid homeassistant/components/websocket_api/* @home-assistant/core homeassistant/components/wemo/* @sqldiablo homeassistant/components/withings/* @vangorra diff --git a/homeassistant/components/webostv/__init__.py b/homeassistant/components/webostv/__init__.py index b34dba3ad94..e03fea68fd7 100644 --- a/homeassistant/components/webostv/__init__.py +++ b/homeassistant/components/webostv/__init__.py @@ -7,6 +7,7 @@ import voluptuous as vol from websockets.exceptions import ConnectionClosed from homeassistant.const import ( + ATTR_ENTITY_ID, CONF_CUSTOMIZE, CONF_HOST, CONF_ICON, @@ -14,6 +15,7 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP, ) import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import async_dispatcher_send DOMAIN = "webostv" @@ -23,6 +25,12 @@ CONF_STANDBY_CONNECTION = "standby_connection" DEFAULT_NAME = "LG webOS Smart TV" WEBOSTV_CONFIG_FILE = "webostv.conf" +SERVICE_BUTTON = "button" +ATTR_BUTTON = "button" + +SERVICE_COMMAND = "command" +ATTR_COMMAND = "command" + CUSTOMIZE_SCHEMA = vol.Schema( {vol.Optional(CONF_SOURCES, default=[]): vol.All(cv.ensure_list, [cv.string])} ) @@ -50,6 +58,17 @@ CONFIG_SCHEMA = vol.Schema( extra=vol.ALLOW_EXTRA, ) +CALL_SCHEMA = vol.Schema({vol.Required(ATTR_ENTITY_ID): cv.comp_entity_ids}) + +BUTTON_SCHEMA = CALL_SCHEMA.extend({vol.Required(ATTR_BUTTON): cv.string}) + +COMMAND_SCHEMA = CALL_SCHEMA.extend({vol.Required(ATTR_COMMAND): cv.string}) + +SERVICE_TO_METHOD = { + SERVICE_BUTTON: {"method": "async_button", "schema": BUTTON_SCHEMA}, + SERVICE_COMMAND: {"method": "async_command", "schema": COMMAND_SCHEMA}, +} + _LOGGER = logging.getLogger(__name__) @@ -57,6 +76,18 @@ async def async_setup(hass, config): """Set up the LG WebOS TV platform.""" hass.data[DOMAIN] = {} + async def async_service_handler(service): + method = SERVICE_TO_METHOD.get(service.service) + data = service.data.copy() + data["method"] = method["method"] + async_dispatcher_send(hass, DOMAIN, data) + + for service in SERVICE_TO_METHOD: + schema = SERVICE_TO_METHOD[service]["schema"] + hass.services.async_register( + DOMAIN, service, async_service_handler, schema=schema + ) + tasks = [async_setup_tv(hass, config, conf) for conf in config[DOMAIN]] if tasks: await asyncio.gather(*tasks) diff --git a/homeassistant/components/webostv/manifest.json b/homeassistant/components/webostv/manifest.json index 016f14f0f94..82d4942f83c 100644 --- a/homeassistant/components/webostv/manifest.json +++ b/homeassistant/components/webostv/manifest.json @@ -6,5 +6,5 @@ "aiopylgtv==0.2.4" ], "dependencies": ["configurator"], - "codeowners": [] + "codeowners": ["@bendavid"] } diff --git a/homeassistant/components/webostv/media_player.py b/homeassistant/components/webostv/media_player.py index fd47cf0a114..5e58cdf7a2f 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -24,13 +24,16 @@ from homeassistant.components.media_player.const import ( SUPPORT_VOLUME_STEP, ) from homeassistant.const import ( + ATTR_ENTITY_ID, CONF_CUSTOMIZE, CONF_HOST, CONF_NAME, + ENTITY_MATCH_ALL, STATE_OFF, STATE_PAUSED, STATE_PLAYING, ) +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.script import Script from . import CONF_ON_ACTION, CONF_SOURCES, DOMAIN @@ -131,7 +134,9 @@ class LgWebOSMediaPlayerEntity(MediaPlayerDevice): self._last_icon = None async def async_added_to_hass(self): - """Connect and subscribe to state updates.""" + """Connect and subscribe to dispatcher signals and state updates.""" + async_dispatcher_connect(self.hass, DOMAIN, self.async_signal_handler) + await self._client.register_state_update_callback( self.async_handle_state_update ) @@ -144,6 +149,17 @@ class LgWebOSMediaPlayerEntity(MediaPlayerDevice): """Call disconnect on removal.""" self._client.unregister_state_update_callback(self.async_handle_state_update) + async def async_signal_handler(self, data): + """Handle domain-specific signal by calling appropriate method.""" + entity_ids = data[ATTR_ENTITY_ID] + if entity_ids == ENTITY_MATCH_ALL or self.entity_id in entity_ids: + params = { + key: value + for key, value in data.items() + if key not in ["entity_id", "method"] + } + await getattr(self, data["method"])(**params) + async def async_handle_state_update(self): """Update state from WebOsClient.""" self._current_source_id = self._client.current_appId @@ -406,3 +422,13 @@ class LgWebOSMediaPlayerEntity(MediaPlayerDevice): await self._client.channel_down() else: await self._client.rewind() + + @cmd + async def async_button(self, button): + """Send a button press.""" + await self._client.button(button) + + @cmd + async def async_command(self, command): + """Send a command.""" + await self._client.request(command) diff --git a/homeassistant/components/webostv/services.yaml b/homeassistant/components/webostv/services.yaml new file mode 100644 index 00000000000..137a6026eda --- /dev/null +++ b/homeassistant/components/webostv/services.yaml @@ -0,0 +1,26 @@ +# Describes the format for available webostv services + +button: + description: 'Send a button press command.' + fields: + entity_id: + description: Name(s) of the webostv entities where to run the API method. + example: 'media_player.living_room_tv' + button: + description: Name of the button to press. Known possible values are + LEFT, RIGHT, DOWN, UP, HOME, BACK, ENTER, DASH, INFO, ASTERISK, CC, EXIT, + MUTE, RED, GREEN, BLUE, VOLUMEUP, VOLUMEDOWN, CHANNELUP, CHANNELDOWN, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + example: 'LEFT' + +command: + description: 'Send a command.' + fields: + entity_id: + description: Name(s) of the webostv entities where to run the API method. + example: 'media_player.living_room_tv' + command: + description: Endpoint of the command. Known valid endpoints are listed in + https://github.com/TheRealLink/pylgtv/blob/master/pylgtv/endpoints.py + example: 'media.controls/rewind' + diff --git a/tests/components/webostv/test_media_player.py b/tests/components/webostv/test_media_player.py index 023e0e2dc07..4dcda9eb908 100644 --- a/tests/components/webostv/test_media_player.py +++ b/tests/components/webostv/test_media_player.py @@ -9,7 +9,13 @@ from homeassistant.components.media_player.const import ( ATTR_MEDIA_VOLUME_MUTED, SERVICE_SELECT_SOURCE, ) -from homeassistant.components.webostv import DOMAIN +from homeassistant.components.webostv import ( + ATTR_BUTTON, + ATTR_COMMAND, + DOMAIN, + SERVICE_BUTTON, + SERVICE_COMMAND, +) from homeassistant.const import ( ATTR_ENTITY_ID, CONF_HOST, @@ -75,3 +81,34 @@ async def test_select_source_with_empty_source_list(hass, client): assert hass.states.is_state(ENTITY_ID, "playing") client.launch_app.assert_not_called() client.set_input.assert_not_called() + + +async def test_button(hass, client): + """Test generic button functionality.""" + + await setup_webostv(hass) + + data = { + ATTR_ENTITY_ID: ENTITY_ID, + ATTR_BUTTON: "test", + } + await hass.services.async_call(DOMAIN, SERVICE_BUTTON, data) + await hass.async_block_till_done() + + client.button.assert_called_once() + client.button.assert_called_with("test") + + +async def test_command(hass, client): + """Test generic button functionality.""" + + await setup_webostv(hass) + + data = { + ATTR_ENTITY_ID: ENTITY_ID, + ATTR_COMMAND: "test", + } + await hass.services.async_call(DOMAIN, SERVICE_COMMAND, data) + await hass.async_block_till_done() + + client.request.assert_called_with("test") From 9b961632af62fddcd1936d1ce0debda438438064 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 3 Jan 2020 00:02:59 +0100 Subject: [PATCH 510/677] Axis - Improve tests (#30415) --- tests/components/axis/test_binary_sensor.py | 67 +---- tests/components/axis/test_camera.py | 59 +--- tests/components/axis/test_config_flow.py | 2 +- tests/components/axis/test_device.py | 309 ++++++++++---------- tests/components/axis/test_switch.py | 68 +---- 5 files changed, 179 insertions(+), 326 deletions(-) diff --git a/tests/components/axis/test_binary_sensor.py b/tests/components/axis/test_binary_sensor.py index ca3e984c993..d70d55e0d1e 100644 --- a/tests/components/axis/test_binary_sensor.py +++ b/tests/components/axis/test_binary_sensor.py @@ -1,12 +1,11 @@ """Axis binary sensor platform tests.""" -from unittest.mock import Mock - -from homeassistant import config_entries from homeassistant.components import axis import homeassistant.components.binary_sensor as binary_sensor from homeassistant.setup import async_setup_component +from .test_device import NAME, setup_axis_integration + EVENTS = [ { "operation": "Initialized", @@ -24,52 +23,6 @@ EVENTS = [ }, ] -ENTRY_CONFIG = { - axis.CONF_DEVICE: { - axis.config_flow.CONF_HOST: "1.2.3.4", - axis.config_flow.CONF_USERNAME: "user", - axis.config_flow.CONF_PASSWORD: "pass", - axis.config_flow.CONF_PORT: 80, - }, - axis.config_flow.CONF_MAC: "1234ABCD", - axis.config_flow.CONF_MODEL: "model", - axis.config_flow.CONF_NAME: "model 0", -} - -ENTRY_OPTIONS = { - axis.CONF_CAMERA: False, - axis.CONF_EVENTS: True, - axis.CONF_TRIGGER_TIME: 0, -} - - -async def setup_device(hass): - """Load the Axis binary sensor platform.""" - from axis import AxisDevice - - loop = Mock() - - config_entry = config_entries.ConfigEntry( - 1, - axis.DOMAIN, - "Mock Title", - ENTRY_CONFIG, - "test", - config_entries.CONN_CLASS_LOCAL_PUSH, - system_options={}, - options=ENTRY_OPTIONS, - ) - device = axis.AxisNetworkDevice(hass, config_entry) - device.api = AxisDevice(loop=loop, **config_entry.data[axis.CONF_DEVICE]) - hass.data[axis.DOMAIN] = {device.serial: device} - device.api.enable_events(event_callback=device.async_event_callback) - - await hass.config_entries.async_forward_entry_setup(config_entry, "binary_sensor") - # To flush out the service call to update the group - await hass.async_block_till_done() - - return device - async def test_platform_manually_configured(hass): """Test that nothing happens when platform is manually configured.""" @@ -85,25 +38,25 @@ async def test_platform_manually_configured(hass): async def test_no_binary_sensors(hass): """Test that no sensors in Axis results in no sensor entities.""" - await setup_device(hass) + await setup_axis_integration(hass) - assert len(hass.states.async_all()) == 0 + assert not hass.states.async_entity_ids("binary_sensor") async def test_binary_sensors(hass): """Test that sensors are loaded properly.""" - device = await setup_device(hass) + device = await setup_axis_integration(hass) for event in EVENTS: device.api.stream.event.manage_event(event) await hass.async_block_till_done() - assert len(hass.states.async_all()) == 2 + assert len(hass.states.async_entity_ids("binary_sensor")) == 2 - pir = hass.states.get("binary_sensor.model_0_pir_0") + pir = hass.states.get(f"binary_sensor.{NAME}_pir_0") assert pir.state == "off" - assert pir.name == "model 0 PIR 0" + assert pir.name == f"{NAME} PIR 0" - vmd4 = hass.states.get("binary_sensor.model_0_vmd4_camera1profile1") + vmd4 = hass.states.get(f"binary_sensor.{NAME}_vmd4_camera1profile1") assert vmd4.state == "on" - assert vmd4.name == "model 0 VMD4 Camera1Profile1" + assert vmd4.name == f"{NAME} VMD4 Camera1Profile1" diff --git a/tests/components/axis/test_camera.py b/tests/components/axis/test_camera.py index 67ca7e3690a..5cbc5e993ca 100644 --- a/tests/components/axis/test_camera.py +++ b/tests/components/axis/test_camera.py @@ -1,57 +1,10 @@ """Axis camera platform tests.""" -from unittest.mock import Mock - -from homeassistant import config_entries from homeassistant.components import axis import homeassistant.components.camera as camera from homeassistant.setup import async_setup_component -ENTRY_CONFIG = { - axis.CONF_DEVICE: { - axis.config_flow.CONF_HOST: "1.2.3.4", - axis.config_flow.CONF_USERNAME: "user", - axis.config_flow.CONF_PASSWORD: "pass", - axis.config_flow.CONF_PORT: 80, - }, - axis.config_flow.CONF_MAC: "1234ABCD", - axis.config_flow.CONF_MODEL: "model", - axis.config_flow.CONF_NAME: "model 0", -} - -ENTRY_OPTIONS = { - axis.CONF_CAMERA: False, - axis.CONF_EVENTS: True, - axis.CONF_TRIGGER_TIME: 0, -} - - -async def setup_device(hass): - """Load the Axis binary sensor platform.""" - from axis import AxisDevice - - loop = Mock() - - config_entry = config_entries.ConfigEntry( - 1, - axis.DOMAIN, - "Mock Title", - ENTRY_CONFIG, - "test", - config_entries.CONN_CLASS_LOCAL_PUSH, - system_options={}, - options=ENTRY_OPTIONS, - ) - device = axis.AxisNetworkDevice(hass, config_entry) - device.api = AxisDevice(loop=loop, **config_entry.data[axis.CONF_DEVICE]) - hass.data[axis.DOMAIN] = {device.serial: device} - device.api.enable_events(event_callback=device.async_event_callback) - - await hass.config_entries.async_forward_entry_setup(config_entry, "camera") - # To flush out the service call to update the group - await hass.async_block_till_done() - - return device +from .test_device import NAME, setup_axis_integration async def test_platform_manually_configured(hass): @@ -68,12 +21,10 @@ async def test_platform_manually_configured(hass): async def test_camera(hass): """Test that Axis camera platform is loaded properly.""" - await setup_device(hass) + await setup_axis_integration(hass) - await hass.async_block_till_done() + assert len(hass.states.async_entity_ids("camera")) == 1 - assert len(hass.states.async_all()) == 1 - - cam = hass.states.get("camera.model_0") + cam = hass.states.get(f"camera.{NAME}") assert cam.state == "idle" - assert cam.name == "model 0" + assert cam.name == NAME diff --git a/tests/components/axis/test_config_flow.py b/tests/components/axis/test_config_flow.py index a29c270e0b8..e542ef0534f 100644 --- a/tests/components/axis/test_config_flow.py +++ b/tests/components/axis/test_config_flow.py @@ -60,7 +60,7 @@ async def test_flow_works(hass): ) assert result["type"] == "create_entry" - assert result["title"] == "{} - {}".format("prodnbr", "serialnumber") + assert result["title"] == f"prodnbr - serialnumber" assert result["data"] == { axis.CONF_DEVICE: { config_flow.CONF_HOST: "1.2.3.4", diff --git a/tests/components/axis/test_device.py b/tests/components/axis/test_device.py index a9f38cc4f3a..58ebed60681 100644 --- a/tests/components/axis/test_device.py +++ b/tests/components/axis/test_device.py @@ -1,173 +1,180 @@ """Test Axis device.""" -from unittest.mock import Mock, patch +from copy import deepcopy +from asynctest import Mock, patch +import axis as axislib import pytest -from homeassistant.components.axis import device, errors -from homeassistant.components.axis.camera import AxisCamera +from homeassistant import config_entries +from homeassistant.components import axis -from tests.common import MockConfigEntry, mock_coro +MAC = "00408C12345" +MODEL = "model" +NAME = "name" DEVICE_DATA = { - device.CONF_HOST: "1.2.3.4", - device.CONF_USERNAME: "username", - device.CONF_PASSWORD: "password", - device.CONF_PORT: 1234, + axis.device.CONF_HOST: "1.2.3.4", + axis.device.CONF_USERNAME: "username", + axis.device.CONF_PASSWORD: "password", + axis.device.CONF_PORT: 1234, } -ENTRY_OPTIONS = {device.CONF_CAMERA: True, device.CONF_EVENTS: True} +ENTRY_OPTIONS = {axis.device.CONF_CAMERA: True, axis.device.CONF_EVENTS: True} ENTRY_CONFIG = { - device.CONF_DEVICE: DEVICE_DATA, - device.CONF_MAC: "mac", - device.CONF_MODEL: "model", - device.CONF_NAME: "name", + axis.device.CONF_DEVICE: DEVICE_DATA, + axis.device.CONF_MAC: MAC, + axis.device.CONF_MODEL: MODEL, + axis.device.CONF_NAME: NAME, } +DEFAULT_BRAND = """root.Brand.Brand=AXIS +root.Brand.ProdFullName=AXIS M1065-LW Network Camera +root.Brand.ProdNbr=M1065-LW +root.Brand.ProdShortName=AXIS M1065-LW +root.Brand.ProdType=Network Camera +root.Brand.ProdVariant= +root.Brand.WebURL=http://www.axis.com +""" -async def test_device_setup(): - """Successful setup.""" - hass = Mock() - entry = Mock() - entry.data = ENTRY_CONFIG - entry.options = ENTRY_OPTIONS - api = Mock() +DEFAULT_PORTS = """root.Input.NbrOfInputs=1 +root.IOPort.I0.Configurable=no +root.IOPort.I0.Direction=input +root.IOPort.I0.Input.Name=PIR sensor +root.IOPort.I0.Input.Trig=closed +root.Output.NbrOfOutputs=0 +""" - axis_device = device.AxisNetworkDevice(hass, entry) - axis_device.start = Mock() +DEFAULT_PROPERTIES = """root.Properties.API.HTTP.Version=3 +root.Properties.API.Metadata.Metadata=yes +root.Properties.API.Metadata.Version=1.0 +root.Properties.Firmware.BuildDate=Feb 15 2019 09:42 +root.Properties.Firmware.BuildNumber=26 +root.Properties.Firmware.Version=9.10.1 +root.Properties.Image.Format=jpeg,mjpeg,h264 +root.Properties.Image.NbrOfViews=2 +root.Properties.Image.Resolution=1920x1080,1280x960,1280x720,1024x768,1024x576,800x600,640x480,640x360,352x240,320x240 +root.Properties.Image.Rotation=0,180 +root.Properties.System.SerialNumber=ACCC12345678 +""" - assert axis_device.host == DEVICE_DATA[device.CONF_HOST] - assert axis_device.model == ENTRY_CONFIG[device.CONF_MODEL] - assert axis_device.name == ENTRY_CONFIG[device.CONF_NAME] - assert axis_device.serial == ENTRY_CONFIG[device.CONF_MAC] - with patch.object(device, "get_device", return_value=mock_coro(api)): - assert await axis_device.async_setup() is True - - assert axis_device.api is api - assert len(hass.config_entries.async_forward_entry_setup.mock_calls) == 3 - assert hass.config_entries.async_forward_entry_setup.mock_calls[0][1] == ( - entry, - "camera", - ) - assert hass.config_entries.async_forward_entry_setup.mock_calls[1][1] == ( - entry, - "binary_sensor", - ) - assert hass.config_entries.async_forward_entry_setup.mock_calls[2][1] == ( - entry, - "switch", +async def setup_axis_integration( + hass, + config=ENTRY_CONFIG, + options=ENTRY_OPTIONS, + brand=DEFAULT_BRAND, + ports=DEFAULT_PORTS, + properties=DEFAULT_PROPERTIES, +): + """Create the Axis device.""" + config_entry = config_entries.ConfigEntry( + version=1, + domain=axis.DOMAIN, + title="Mock Title", + data=deepcopy(config), + source="test", + connection_class=config_entries.CONN_CLASS_LOCAL_PUSH, + system_options={}, + options=deepcopy(options), + entry_id="1", ) + def mock_request(self, method, path, json=None): + if method == "get": + if path == "/axis-cgi/param.cgi?action=list&group=root.Brand": + return brand + if path in [ + "/axis-cgi/param.cgi?action=list&group=root.Input", + "/axis-cgi/param.cgi?action=list&group=root.IOPort", + "/axis-cgi/param.cgi?action=list&group=root.Output", + ]: + return ports + if path == "/axis-cgi/param.cgi?action=list&group=root.Properties": + return properties -async def test_device_signal_new_address(hass): - """Successful setup.""" - entry = MockConfigEntry( - domain=device.DOMAIN, data=ENTRY_CONFIG, options=ENTRY_OPTIONS - ) + return None - api = Mock() - api.vapix.get_param.return_value = "1234" - - axis_device = device.AxisNetworkDevice(hass, entry) - hass.data[device.DOMAIN] = {axis_device.serial: axis_device} - - with patch.object(device, "get_device", return_value=mock_coro(api)), patch.object( - AxisCamera, "_new_address" - ) as new_address_mock: - await axis_device.async_setup() - await hass.async_block_till_done() - - assert len(hass.states.async_all()) == 1 - assert len(axis_device.listeners) == 2 - - entry.data[device.CONF_DEVICE][device.CONF_HOST] = "2.3.4.5" - hass.config_entries.async_update_entry(entry, data=entry.data) + with patch("axis.vapix.Vapix.request", new=mock_request), patch( + "axis.AxisDevice.start", return_value=True + ): + await axis.async_setup_entry(hass, config_entry) await hass.async_block_till_done() - assert axis_device.host == "2.3.4.5" - assert axis_device.api.config.host == "2.3.4.5" - assert len(new_address_mock.mock_calls) == 1 + hass.config_entries._entries.append(config_entry) + + return hass.data[axis.DOMAIN].get(config[axis.CONF_MAC]) + + +async def test_device_setup(hass): + """Successful setup.""" + with patch( + "homeassistant.config_entries.ConfigEntries.async_forward_entry_setup", + return_value=True, + ) as forward_entry_setup: + device = await setup_axis_integration(hass) + + entry = device.config_entry + + assert len(forward_entry_setup.mock_calls) == 3 + assert forward_entry_setup.mock_calls[0][1] == (entry, "camera") + assert forward_entry_setup.mock_calls[1][1] == (entry, "binary_sensor") + assert forward_entry_setup.mock_calls[2][1] == (entry, "switch") + + assert device.host == DEVICE_DATA[axis.device.CONF_HOST] + assert device.model == ENTRY_CONFIG[axis.device.CONF_MODEL] + assert device.name == ENTRY_CONFIG[axis.device.CONF_NAME] + assert device.serial == ENTRY_CONFIG[axis.device.CONF_MAC] + + +async def test_update_address(hass): + """Test update address works.""" + device = await setup_axis_integration(hass) + assert device.api.config.host == "1.2.3.4" + + await hass.config_entries.flow.async_init( + axis.DOMAIN, + data={ + "host": "2.3.4.5", + "port": 80, + "hostname": "name", + "properties": {"macaddress": MAC}, + }, + context={"source": "zeroconf"}, + ) + await hass.async_block_till_done() + + assert device.api.config.host == "2.3.4.5" async def test_device_unavailable(hass): """Successful setup.""" - entry = MockConfigEntry( - domain=device.DOMAIN, data=ENTRY_CONFIG, options=ENTRY_OPTIONS - ) - - api = Mock() - api.vapix.get_param.return_value = "1234" - - axis_device = device.AxisNetworkDevice(hass, entry) - hass.data[device.DOMAIN] = {axis_device.serial: axis_device} - - with patch.object(device, "get_device", return_value=mock_coro(api)), patch.object( - device, "async_dispatcher_send" - ) as mock_dispatcher: - await axis_device.async_setup() - await hass.async_block_till_done() - - axis_device.async_connection_status_callback(status=False) - - assert not axis_device.available - assert len(mock_dispatcher.mock_calls) == 1 + device = await setup_axis_integration(hass) + device.async_connection_status_callback(status=False) + assert not device.available async def test_device_reset(hass): """Successfully reset device.""" - entry = MockConfigEntry( - domain=device.DOMAIN, data=ENTRY_CONFIG, options=ENTRY_OPTIONS - ) - - api = Mock() - api.vapix.get_param.return_value = "1234" - - axis_device = device.AxisNetworkDevice(hass, entry) - hass.data[device.DOMAIN] = {axis_device.serial: axis_device} - - with patch.object(device, "get_device", return_value=mock_coro(api)): - await axis_device.async_setup() - await hass.async_block_till_done() - - await axis_device.async_reset() - - assert len(api.stop.mock_calls) == 1 - assert len(hass.states.async_all()) == 0 - assert len(axis_device.listeners) == 0 + device = await setup_axis_integration(hass) + result = await device.async_reset() + assert result is True -async def test_device_not_accessible(): +async def test_device_not_accessible(hass): """Failed setup schedules a retry of setup.""" - hass = Mock() - hass.data = dict() - entry = Mock() - entry.data = ENTRY_CONFIG - entry.options = ENTRY_OPTIONS - - axis_device = device.AxisNetworkDevice(hass, entry) - with patch.object( - device, "get_device", side_effect=errors.CannotConnect - ), pytest.raises(device.ConfigEntryNotReady): - await axis_device.async_setup() - - assert not hass.helpers.event.async_call_later.mock_calls + axis.device, "get_device", side_effect=axis.errors.CannotConnect + ), pytest.raises(axis.device.ConfigEntryNotReady): + await setup_axis_integration(hass) + assert hass.data[axis.DOMAIN] == {} -async def test_device_unknown_error(): +async def test_device_unknown_error(hass): """Unknown errors are handled.""" - hass = Mock() - entry = Mock() - entry.data = ENTRY_CONFIG - entry.options = ENTRY_OPTIONS - - axis_device = device.AxisNetworkDevice(hass, entry) - - with patch.object(device, "get_device", side_effect=Exception): - assert await axis_device.async_setup() is False - - assert not hass.helpers.event.async_call_later.mock_calls + with patch.object(axis.device, "get_device", side_effect=Exception): + await setup_axis_integration(hass) + assert hass.data[axis.DOMAIN] == {} async def test_new_event_sends_signal(hass): @@ -175,9 +182,9 @@ async def test_new_event_sends_signal(hass): entry = Mock() entry.data = ENTRY_CONFIG - axis_device = device.AxisNetworkDevice(hass, entry) + axis_device = axis.device.AxisNetworkDevice(hass, entry) - with patch.object(device, "async_dispatcher_send") as mock_dispatch_send: + with patch.object(axis.device, "async_dispatcher_send") as mock_dispatch_send: axis_device.async_event_callback(action="add", event_id="event") await hass.async_block_till_done() @@ -191,7 +198,7 @@ async def test_shutdown(): entry = Mock() entry.data = ENTRY_CONFIG - axis_device = device.AxisNetworkDevice(hass, entry) + axis_device = axis.device.AxisNetworkDevice(hass, entry) axis_device.api = Mock() axis_device.shutdown(None) @@ -199,39 +206,25 @@ async def test_shutdown(): assert len(axis_device.api.stop.mock_calls) == 1 -async def test_get_device(hass): - """Successful call.""" - with patch("axis.param_cgi.Params.update_brand", return_value=mock_coro()), patch( - "axis.param_cgi.Params.update_properties", return_value=mock_coro() - ), patch("axis.port_cgi.Ports.update", return_value=mock_coro()): - assert await device.get_device(hass, DEVICE_DATA) - - async def test_get_device_fails(hass): """Device unauthorized yields authentication required error.""" - import axis - with patch( - "axis.param_cgi.Params.update_brand", side_effect=axis.Unauthorized - ), pytest.raises(errors.AuthenticationRequired): - await device.get_device(hass, DEVICE_DATA) + "axis.param_cgi.Params.update_brand", side_effect=axislib.Unauthorized + ), pytest.raises(axis.errors.AuthenticationRequired): + await axis.device.get_device(hass, DEVICE_DATA) async def test_get_device_device_unavailable(hass): """Device unavailable yields cannot connect error.""" - import axis - with patch( - "axis.param_cgi.Params.update_brand", side_effect=axis.RequestError - ), pytest.raises(errors.CannotConnect): - await device.get_device(hass, DEVICE_DATA) + "axis.param_cgi.Params.update_brand", side_effect=axislib.RequestError + ), pytest.raises(axis.errors.CannotConnect): + await axis.device.get_device(hass, DEVICE_DATA) async def test_get_device_unknown_error(hass): """Device yield unknown error.""" - import axis - with patch( - "axis.param_cgi.Params.update_brand", side_effect=axis.AxisException - ), pytest.raises(errors.AuthenticationRequired): - await device.get_device(hass, DEVICE_DATA) + "axis.param_cgi.Params.update_brand", side_effect=axislib.AxisException + ), pytest.raises(axis.errors.AuthenticationRequired): + await axis.device.get_device(hass, DEVICE_DATA) diff --git a/tests/components/axis/test_switch.py b/tests/components/axis/test_switch.py index 406e3170ab2..844cfedf7fe 100644 --- a/tests/components/axis/test_switch.py +++ b/tests/components/axis/test_switch.py @@ -2,11 +2,12 @@ from unittest.mock import Mock, call as mock_call -from homeassistant import config_entries from homeassistant.components import axis import homeassistant.components.switch as switch from homeassistant.setup import async_setup_component +from .test_device import NAME, setup_axis_integration + EVENTS = [ { "operation": "Initialized", @@ -26,52 +27,6 @@ EVENTS = [ }, ] -ENTRY_CONFIG = { - axis.CONF_DEVICE: { - axis.config_flow.CONF_HOST: "1.2.3.4", - axis.config_flow.CONF_USERNAME: "user", - axis.config_flow.CONF_PASSWORD: "pass", - axis.config_flow.CONF_PORT: 80, - }, - axis.config_flow.CONF_MAC: "1234ABCD", - axis.config_flow.CONF_MODEL: "model", - axis.config_flow.CONF_NAME: "model 0", -} - -ENTRY_OPTIONS = { - axis.CONF_CAMERA: False, - axis.CONF_EVENTS: True, - axis.CONF_TRIGGER_TIME: 0, -} - - -async def setup_device(hass): - """Load the Axis switch platform.""" - from axis import AxisDevice - - loop = Mock() - - config_entry = config_entries.ConfigEntry( - 1, - axis.DOMAIN, - "Mock Title", - ENTRY_CONFIG, - "test", - config_entries.CONN_CLASS_LOCAL_PUSH, - system_options={}, - options=ENTRY_OPTIONS, - ) - device = axis.AxisNetworkDevice(hass, config_entry) - device.api = AxisDevice(loop=loop, **config_entry.data[axis.CONF_DEVICE]) - hass.data[axis.DOMAIN] = {device.serial: device} - device.api.enable_events(event_callback=device.async_event_callback) - - await hass.config_entries.async_forward_entry_setup(config_entry, "switch") - # To flush out the service call to update the group - await hass.async_block_till_done() - - return device - async def test_platform_manually_configured(hass): """Test that nothing happens when platform is manually configured.""" @@ -84,14 +39,15 @@ async def test_platform_manually_configured(hass): async def test_no_switches(hass): """Test that no output events in Axis results in no switch entities.""" - await setup_device(hass) + await setup_axis_integration(hass) assert not hass.states.async_entity_ids("switch") async def test_switches(hass): """Test that switches are loaded properly.""" - device = await setup_device(hass) + device = await setup_axis_integration(hass) + device.api.vapix.ports = {"0": Mock(), "1": Mock()} device.api.vapix.ports["0"].name = "Doorbell" device.api.vapix.ports["1"].name = "" @@ -100,24 +56,24 @@ async def test_switches(hass): device.api.stream.event.manage_event(event) await hass.async_block_till_done() - assert len(hass.states.async_all()) == 3 + assert len(hass.states.async_entity_ids("switch")) == 2 - relay_0 = hass.states.get("switch.model_0_doorbell") + relay_0 = hass.states.get(f"switch.{NAME}_doorbell") assert relay_0.state == "off" - assert relay_0.name == "model 0 Doorbell" + assert relay_0.name == f"{NAME} Doorbell" - relay_1 = hass.states.get("switch.model_0_relay_1") + relay_1 = hass.states.get(f"switch.{NAME}_relay_1") assert relay_1.state == "on" - assert relay_1.name == "model 0 Relay 1" + assert relay_1.name == f"{NAME} Relay 1" device.api.vapix.ports["0"].action = Mock() await hass.services.async_call( - "switch", "turn_on", {"entity_id": "switch.model_0_doorbell"}, blocking=True + "switch", "turn_on", {"entity_id": f"switch.{NAME}_doorbell"}, blocking=True ) await hass.services.async_call( - "switch", "turn_off", {"entity_id": "switch.model_0_doorbell"}, blocking=True + "switch", "turn_off", {"entity_id": f"switch.{NAME}_doorbell"}, blocking=True ) assert device.api.vapix.ports["0"].action.call_args_list == [ From 4bf15a07a34c24e9c4939cd05ea2c86d8a013b2f Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Fri, 3 Jan 2020 00:32:16 +0000 Subject: [PATCH 511/677] [ci skip] Translation update --- .../components/gios/.translations/fr.json | 16 ++++++++++++ .../gios/.translations/zh-Hant.json | 20 ++++++++++++++ .../components/local_ip/.translations/fr.json | 16 ++++++++++++ .../components/local_ip/.translations/no.json | 16 ++++++++++++ .../local_ip/.translations/zh-Hant.json | 16 ++++++++++++ .../components/sensor/.translations/fr.json | 26 +++++++++---------- .../components/tesla/.translations/hu.json | 9 +++++++ 7 files changed, 106 insertions(+), 13 deletions(-) create mode 100644 homeassistant/components/gios/.translations/fr.json create mode 100644 homeassistant/components/gios/.translations/zh-Hant.json create mode 100644 homeassistant/components/local_ip/.translations/fr.json create mode 100644 homeassistant/components/local_ip/.translations/no.json create mode 100644 homeassistant/components/local_ip/.translations/zh-Hant.json diff --git a/homeassistant/components/gios/.translations/fr.json b/homeassistant/components/gios/.translations/fr.json new file mode 100644 index 00000000000..f943adfe1c3 --- /dev/null +++ b/homeassistant/components/gios/.translations/fr.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "invalid_sensors_data": "Donn\u00e9es des capteurs non valides pour cette station de mesure.", + "wrong_station_id": "L'identifiant de la station de mesure n'est pas correct." + }, + "step": { + "user": { + "data": { + "name": "Nom de l'int\u00e9gration", + "station_id": "Identifiant de la station de mesure" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gios/.translations/zh-Hant.json b/homeassistant/components/gios/.translations/zh-Hant.json new file mode 100644 index 00000000000..19d13572c72 --- /dev/null +++ b/homeassistant/components/gios/.translations/zh-Hant.json @@ -0,0 +1,20 @@ +{ + "config": { + "error": { + "cannot_connect": "\u7121\u6cd5\u9023\u7dda\u81f3 GIO\u015a \u4f3a\u670d\u5668\u3002", + "invalid_sensors_data": "\u6b64\u76e3\u6e2c\u7ad9\u50b3\u611f\u5668\u8cc7\u6599\u7121\u6548\u3002", + "wrong_station_id": "\u76e3\u6e2c\u7ad9 ID \u4e0d\u6b63\u78ba\u3002" + }, + "step": { + "user": { + "data": { + "name": "\u6574\u5408\u540d\u7a31", + "station_id": "\u76e3\u6e2c\u7ad9 ID" + }, + "description": "\u8a2d\u5b9a GIO\u015a\uff08\u6ce2\u862d\u7e3d\u74b0\u5883\u4fdd\u8b77\u7763\u5bdf\u8655\uff09\u7a7a\u6c23\u54c1\u8cea\u6574\u5408\u3002\u5047\u5982\u9700\u8981\u5354\u52a9\uff0c\u8acb\u53c3\u8003\uff1ahttps://www.home-assistant.io/integrations/gios", + "title": "GIO\u015a\uff08\u6ce2\u862d\u7e3d\u74b0\u5883\u4fdd\u8b77\u7763\u5bdf\u8655\uff09" + } + }, + "title": "GIO\u015a" + } +} \ No newline at end of file diff --git a/homeassistant/components/local_ip/.translations/fr.json b/homeassistant/components/local_ip/.translations/fr.json new file mode 100644 index 00000000000..0d3c61c385b --- /dev/null +++ b/homeassistant/components/local_ip/.translations/fr.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "L'int\u00e9gration est d\u00e9j\u00e0 configur\u00e9e avec un capteur existant portant ce nom" + }, + "step": { + "user": { + "data": { + "name": "Nom du capteur" + }, + "title": "Adresse IP locale" + } + }, + "title": "Adresse IP locale" + } +} \ No newline at end of file diff --git a/homeassistant/components/local_ip/.translations/no.json b/homeassistant/components/local_ip/.translations/no.json new file mode 100644 index 00000000000..cb3b96a8940 --- /dev/null +++ b/homeassistant/components/local_ip/.translations/no.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "Integrasjonen er allerede konfigurert med en eksisterende sensor med det navnet" + }, + "step": { + "user": { + "data": { + "name": "Sensor navn" + }, + "title": "Lokal IP adresse" + } + }, + "title": "Lokal IP adresse" + } +} \ No newline at end of file diff --git a/homeassistant/components/local_ip/.translations/zh-Hant.json b/homeassistant/components/local_ip/.translations/zh-Hant.json new file mode 100644 index 00000000000..ec50980b6ea --- /dev/null +++ b/homeassistant/components/local_ip/.translations/zh-Hant.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "\u6574\u5408\u5df2\u7d93\u8a2d\u5b9a\u4e26\u6709\u73fe\u6709\u50b3\u611f\u5668\u4f7f\u7528\u76f8\u540c\u540d\u7a31" + }, + "step": { + "user": { + "data": { + "name": "\u50b3\u611f\u5668\u540d\u7a31" + }, + "title": "\u672c\u5730 IP \u4f4d\u5740" + } + }, + "title": "\u672c\u5730 IP \u4f4d\u5740" + } +} \ No newline at end of file diff --git a/homeassistant/components/sensor/.translations/fr.json b/homeassistant/components/sensor/.translations/fr.json index 1ce2592410d..800f44a3fd6 100644 --- a/homeassistant/components/sensor/.translations/fr.json +++ b/homeassistant/components/sensor/.translations/fr.json @@ -4,22 +4,22 @@ "is_battery_level": "Niveau de la batterie de {entity_name}", "is_humidity": "Humidit\u00e9 de {entity_name}", "is_illuminance": "\u00c9clairement de {entity_name}", - "is_power": "{entity_name} puissance", - "is_pressure": "{entity_name} pression", - "is_signal_strength": "{entity_name} force du signal", - "is_temperature": "La temp\u00e9rature de {entity_name}", - "is_timestamp": "{entity_name} horodatage", + "is_power": "Puissance de {entity_name}", + "is_pressure": "Pression de {entity_name}", + "is_signal_strength": "Force du signal de {entity_name}", + "is_temperature": "Temp\u00e9rature de {entity_name}", + "is_timestamp": "Horodatage de {entity_name}", "is_value": "La valeur actuelle de {entity_name} " }, "trigger_type": { - "battery_level": "Le niveau de la batterie de {entity_name}", - "humidity": "L'humidit\u00e9 de {entity_name}", - "illuminance": "L'\u00e9clairement de {entity_name}", - "power": "{entity_name} puissance", - "pressure": "{entity_name} pression", - "signal_strength": "{entity_name} force du signal", - "temperature": "La temp\u00e9rature de {entity_name}", - "timestamp": "{entity_name} horodatage", + "battery_level": "{entity_name} modification du niveau de batterie", + "humidity": "{entity_name} modification de l'humidit\u00e9", + "illuminance": "{entity_name} modification de l'\u00e9clairement", + "power": "{entity_name} modification de la puissance", + "pressure": "{entity_name} modification de la pression", + "signal_strength": "{entity_name} modification de la force du signal", + "temperature": "{entity_name} modification de temp\u00e9rature", + "timestamp": "{entity_name} modification d'horodatage", "value": "Changements de valeur de {entity_name} " } } diff --git a/homeassistant/components/tesla/.translations/hu.json b/homeassistant/components/tesla/.translations/hu.json index 7a9a3deff49..01090bbfa9e 100644 --- a/homeassistant/components/tesla/.translations/hu.json +++ b/homeassistant/components/tesla/.translations/hu.json @@ -17,5 +17,14 @@ } }, "title": "Tesla" + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Szkennel\u00e9sek k\u00f6z\u00f6tti m\u00e1sodpercek" + } + } + } } } \ No newline at end of file From 4c6e10a988b4059fe45f400ce246f30ff1cd20f7 Mon Sep 17 00:00:00 2001 From: Josh Bendavid Date: Thu, 2 Jan 2020 20:46:32 -0500 Subject: [PATCH 512/677] Cleanup of state handling in webostv (#30416) * cleanup unnecessary manipulation of state variables * update unit test --- .../components/webostv/media_player.py | 22 ++++--------------- tests/components/webostv/test_media_player.py | 3 ++- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/webostv/media_player.py b/homeassistant/components/webostv/media_player.py index 5e58cdf7a2f..4652d6385c1 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -30,8 +30,7 @@ from homeassistant.const import ( CONF_NAME, ENTITY_MATCH_ALL, STATE_OFF, - STATE_PAUSED, - STATE_PLAYING, + STATE_ON, ) from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.script import Script @@ -121,8 +120,6 @@ class LgWebOSMediaPlayerEntity(MediaPlayerDevice): # Assume that the TV is not muted self._muted = False - # Assume that the TV is in Play mode - self._playing = True self._volume = 0 self._current_source = None self._current_source_id = None @@ -172,7 +169,7 @@ class LgWebOSMediaPlayerEntity(MediaPlayerDevice): if self._current_source_id == "": self._state = STATE_OFF else: - self._state = STATE_PLAYING + self._state = STATE_ON self.update_sources() @@ -325,16 +322,12 @@ class LgWebOSMediaPlayerEntity(MediaPlayerDevice): @cmd async def async_mute_volume(self, mute): """Send mute command.""" - self._muted = mute await self._client.set_mute(mute) @cmd async def async_media_play_pause(self): - """Simulate play pause media player.""" - if self._playing: - await self.media_pause() - else: - await self.media_play() + """Client pause command acts as a play-pause toggle.""" + await self._client.pause() @cmd async def async_select_source(self, source): @@ -343,12 +336,9 @@ class LgWebOSMediaPlayerEntity(MediaPlayerDevice): if source_dict is None: _LOGGER.warning("Source %s not found for %s", source, self.name) return - self._current_source_id = source_dict["id"] if source_dict.get("title"): - self._current_source = source_dict["title"] await self._client.launch_app(source_dict["id"]) elif source_dict.get("label"): - self._current_source = source_dict["label"] await self._client.set_input(source_dict["id"]) @cmd @@ -389,15 +379,11 @@ class LgWebOSMediaPlayerEntity(MediaPlayerDevice): @cmd async def async_media_play(self): """Send play command.""" - self._playing = True - self._state = STATE_PLAYING await self._client.play() @cmd async def async_media_pause(self): """Send media pause command to media player.""" - self._playing = False - self._state = STATE_PAUSED await self._client.pause() @cmd diff --git a/tests/components/webostv/test_media_player.py b/tests/components/webostv/test_media_player.py index 4dcda9eb908..b0be238f971 100644 --- a/tests/components/webostv/test_media_player.py +++ b/tests/components/webostv/test_media_player.py @@ -21,6 +21,7 @@ from homeassistant.const import ( CONF_HOST, CONF_NAME, SERVICE_VOLUME_MUTE, + STATE_ON, ) from homeassistant.setup import async_setup_component @@ -78,7 +79,7 @@ async def test_select_source_with_empty_source_list(hass, client): await hass.services.async_call(media_player.DOMAIN, SERVICE_SELECT_SOURCE, data) await hass.async_block_till_done() - assert hass.states.is_state(ENTITY_ID, "playing") + assert hass.states.is_state(ENTITY_ID, STATE_ON) client.launch_app.assert_not_called() client.set_input.assert_not_called() From ef8eefc7a0fe53ce3d2ceb9efd8f4dfb2380c0e2 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Thu, 2 Jan 2020 21:21:09 -0500 Subject: [PATCH 513/677] Remove ZHA establish device mappings function (#30423) * remove establish_device_mappings * inline init --- homeassistant/components/zha/__init__.py | 2 - .../components/zha/core/registries.py | 220 ++++++++---------- tests/components/zha/conftest.py | 2 - 3 files changed, 102 insertions(+), 122 deletions(-) diff --git a/homeassistant/components/zha/__init__.py b/homeassistant/components/zha/__init__.py index 7303367d485..377c77bf601 100644 --- a/homeassistant/components/zha/__init__.py +++ b/homeassistant/components/zha/__init__.py @@ -27,7 +27,6 @@ from .core.const import ( DOMAIN, RadioType, ) -from .core.registries import establish_device_mappings DEVICE_CONFIG_SCHEMA_ENTRY = vol.Schema({vol.Optional(ha_const.CONF_TYPE): cv.string}) @@ -87,7 +86,6 @@ async def async_setup_entry(hass, config_entry): Will automatically load components to support devices found on the network. """ - establish_device_mappings() for component in COMPONENTS: hass.data[DATA_ZHA][component] = hass.data[DATA_ZHA].get(component, {}) diff --git a/homeassistant/components/zha/core/registries.py b/homeassistant/components/zha/core/registries.py index d2ba0243a5c..37acffd39d0 100644 --- a/homeassistant/components/zha/core/registries.py +++ b/homeassistant/components/zha/core/registries.py @@ -33,24 +33,117 @@ from . import channels # noqa: F401 pylint: disable=unused-import from .const import CONTROLLER, ZHA_GW_RADIO, ZHA_GW_RADIO_DESCRIPTION, RadioType from .decorators import CALLABLE_T, DictRegistry, SetRegistry +SMARTTHINGS_ACCELERATION_CLUSTER = 0xFC02 +SMARTTHINGS_ARRIVAL_SENSOR_DEVICE_TYPE = 0x8000 +SMARTTHINGS_HUMIDITY_CLUSTER = 0xFC45 + +REMOTE_DEVICE_TYPES = { + zigpy.profiles.zha.PROFILE_ID: [ + zigpy.profiles.zha.DeviceType.COLOR_CONTROLLER, + zigpy.profiles.zha.DeviceType.COLOR_DIMMER_SWITCH, + zigpy.profiles.zha.DeviceType.COLOR_SCENE_CONTROLLER, + zigpy.profiles.zha.DeviceType.DIMMER_SWITCH, + zigpy.profiles.zha.DeviceType.NON_COLOR_CONTROLLER, + zigpy.profiles.zha.DeviceType.NON_COLOR_SCENE_CONTROLLER, + zigpy.profiles.zha.DeviceType.REMOTE_CONTROL, + zigpy.profiles.zha.DeviceType.SCENE_SELECTOR, + ], + zigpy.profiles.zll.PROFILE_ID: [ + zigpy.profiles.zll.DeviceType.COLOR_CONTROLLER, + zigpy.profiles.zll.DeviceType.COLOR_SCENE_CONTROLLER, + zigpy.profiles.zll.DeviceType.CONTROL_BRIDGE, + zigpy.profiles.zll.DeviceType.CONTROLLER, + zigpy.profiles.zll.DeviceType.SCENE_CONTROLLER, + ], +} + +SINGLE_INPUT_CLUSTER_DEVICE_CLASS = { + # this works for now but if we hit conflicts we can break it out to + # a different dict that is keyed by manufacturer + SMARTTHINGS_ACCELERATION_CLUSTER: BINARY_SENSOR, + SMARTTHINGS_HUMIDITY_CLUSTER: SENSOR, + zcl.clusters.closures.DoorLock: LOCK, + zcl.clusters.general.AnalogInput.cluster_id: SENSOR, + zcl.clusters.general.MultistateInput.cluster_id: SENSOR, + zcl.clusters.general.OnOff: SWITCH, + zcl.clusters.general.PowerConfiguration: SENSOR, + zcl.clusters.homeautomation.ElectricalMeasurement: SENSOR, + zcl.clusters.hvac.Fan: FAN, + zcl.clusters.measurement.IlluminanceMeasurement: SENSOR, + zcl.clusters.measurement.OccupancySensing: BINARY_SENSOR, + zcl.clusters.measurement.PressureMeasurement: SENSOR, + zcl.clusters.measurement.RelativeHumidity: SENSOR, + zcl.clusters.measurement.TemperatureMeasurement: SENSOR, + zcl.clusters.security.IasZone: BINARY_SENSOR, + zcl.clusters.smartenergy.Metering: SENSOR, +} + +SINGLE_OUTPUT_CLUSTER_DEVICE_CLASS = {zcl.clusters.general.OnOff: BINARY_SENSOR} + +SWITCH_CLUSTERS = SetRegistry() + BINARY_SENSOR_CLUSTERS = SetRegistry() +BINARY_SENSOR_CLUSTERS.add(SMARTTHINGS_ACCELERATION_CLUSTER) + BINDABLE_CLUSTERS = SetRegistry() CHANNEL_ONLY_CLUSTERS = SetRegistry() CLUSTER_REPORT_CONFIGS = {} CUSTOM_CLUSTER_MAPPINGS = {} -DEVICE_CLASS = collections.defaultdict(dict) + +DEVICE_CLASS = { + zigpy.profiles.zha.PROFILE_ID: { + SMARTTHINGS_ARRIVAL_SENSOR_DEVICE_TYPE: DEVICE_TRACKER, + zigpy.profiles.zha.DeviceType.COLOR_DIMMABLE_LIGHT: LIGHT, + zigpy.profiles.zha.DeviceType.COLOR_TEMPERATURE_LIGHT: LIGHT, + zigpy.profiles.zha.DeviceType.DIMMABLE_BALLAST: LIGHT, + zigpy.profiles.zha.DeviceType.DIMMABLE_LIGHT: LIGHT, + zigpy.profiles.zha.DeviceType.DIMMABLE_PLUG_IN_UNIT: LIGHT, + zigpy.profiles.zha.DeviceType.EXTENDED_COLOR_LIGHT: LIGHT, + zigpy.profiles.zha.DeviceType.LEVEL_CONTROLLABLE_OUTPUT: LIGHT, + zigpy.profiles.zha.DeviceType.ON_OFF_BALLAST: SWITCH, + zigpy.profiles.zha.DeviceType.ON_OFF_LIGHT: LIGHT, + zigpy.profiles.zha.DeviceType.ON_OFF_LIGHT_SWITCH: SWITCH, + zigpy.profiles.zha.DeviceType.ON_OFF_PLUG_IN_UNIT: SWITCH, + zigpy.profiles.zha.DeviceType.SMART_PLUG: SWITCH, + }, + zigpy.profiles.zll.PROFILE_ID: { + zigpy.profiles.zll.DeviceType.COLOR_LIGHT: LIGHT, + zigpy.profiles.zll.DeviceType.COLOR_TEMPERATURE_LIGHT: LIGHT, + zigpy.profiles.zll.DeviceType.DIMMABLE_LIGHT: LIGHT, + zigpy.profiles.zll.DeviceType.DIMMABLE_PLUGIN_UNIT: LIGHT, + zigpy.profiles.zll.DeviceType.EXTENDED_COLOR_LIGHT: LIGHT, + zigpy.profiles.zll.DeviceType.ON_OFF_LIGHT: LIGHT, + zigpy.profiles.zll.DeviceType.ON_OFF_PLUGIN_UNIT: SWITCH, + }, +} + DEVICE_TRACKER_CLUSTERS = SetRegistry() EVENT_RELAY_CLUSTERS = SetRegistry() LIGHT_CLUSTERS = SetRegistry() OUTPUT_CHANNEL_ONLY_CLUSTERS = SetRegistry() -RADIO_TYPES = {} -REMOTE_DEVICE_TYPES = collections.defaultdict(list) -SINGLE_INPUT_CLUSTER_DEVICE_CLASS = {} -SINGLE_OUTPUT_CLUSTER_DEVICE_CLASS = {} -SWITCH_CLUSTERS = SetRegistry() -SMARTTHINGS_ACCELERATION_CLUSTER = 0xFC02 -SMARTTHINGS_ARRIVAL_SENSOR_DEVICE_TYPE = 0x8000 -SMARTTHINGS_HUMIDITY_CLUSTER = 0xFC45 + +RADIO_TYPES = { + RadioType.ezsp.name: { + ZHA_GW_RADIO: bellows.ezsp.EZSP, + CONTROLLER: bellows.zigbee.application.ControllerApplication, + ZHA_GW_RADIO_DESCRIPTION: "EZSP", + }, + RadioType.deconz.name: { + ZHA_GW_RADIO: zigpy_deconz.api.Deconz, + CONTROLLER: zigpy_deconz.zigbee.application.ControllerApplication, + ZHA_GW_RADIO_DESCRIPTION: "Deconz", + }, + RadioType.xbee.name: { + ZHA_GW_RADIO: zigpy_xbee.api.XBee, + CONTROLLER: zigpy_xbee.zigbee.application.ControllerApplication, + ZHA_GW_RADIO_DESCRIPTION: "XBee", + }, + RadioType.zigate.name: { + ZHA_GW_RADIO: zigpy_zigate.api.ZiGate, + CONTROLLER: zigpy_zigate.zigbee.application.ControllerApplication, + ZHA_GW_RADIO_DESCRIPTION: "ZiGate", + }, +} COMPONENT_CLUSTERS = { BINARY_SENSOR: BINARY_SENSOR_CLUSTERS, @@ -62,115 +155,6 @@ COMPONENT_CLUSTERS = { ZIGBEE_CHANNEL_REGISTRY = DictRegistry() -def establish_device_mappings(): - """Establish mappings between ZCL objects and HA ZHA objects. - - These cannot be module level, as importing bellows must be done in a - in a function. - """ - RADIO_TYPES[RadioType.ezsp.name] = { - ZHA_GW_RADIO: bellows.ezsp.EZSP, - CONTROLLER: bellows.zigbee.application.ControllerApplication, - ZHA_GW_RADIO_DESCRIPTION: "EZSP", - } - - RADIO_TYPES[RadioType.deconz.name] = { - ZHA_GW_RADIO: zigpy_deconz.api.Deconz, - CONTROLLER: zigpy_deconz.zigbee.application.ControllerApplication, - ZHA_GW_RADIO_DESCRIPTION: "Deconz", - } - - RADIO_TYPES[RadioType.xbee.name] = { - ZHA_GW_RADIO: zigpy_xbee.api.XBee, - CONTROLLER: zigpy_xbee.zigbee.application.ControllerApplication, - ZHA_GW_RADIO_DESCRIPTION: "XBee", - } - - RADIO_TYPES[RadioType.zigate.name] = { - ZHA_GW_RADIO: zigpy_zigate.api.ZiGate, - CONTROLLER: zigpy_zigate.zigbee.application.ControllerApplication, - ZHA_GW_RADIO_DESCRIPTION: "ZiGate", - } - - BINARY_SENSOR_CLUSTERS.add(SMARTTHINGS_ACCELERATION_CLUSTER) - - DEVICE_CLASS[zigpy.profiles.zha.PROFILE_ID].update( - { - SMARTTHINGS_ARRIVAL_SENSOR_DEVICE_TYPE: DEVICE_TRACKER, - zigpy.profiles.zha.DeviceType.COLOR_DIMMABLE_LIGHT: LIGHT, - zigpy.profiles.zha.DeviceType.COLOR_TEMPERATURE_LIGHT: LIGHT, - zigpy.profiles.zha.DeviceType.DIMMABLE_BALLAST: LIGHT, - zigpy.profiles.zha.DeviceType.DIMMABLE_LIGHT: LIGHT, - zigpy.profiles.zha.DeviceType.DIMMABLE_PLUG_IN_UNIT: LIGHT, - zigpy.profiles.zha.DeviceType.EXTENDED_COLOR_LIGHT: LIGHT, - zigpy.profiles.zha.DeviceType.LEVEL_CONTROLLABLE_OUTPUT: LIGHT, - zigpy.profiles.zha.DeviceType.ON_OFF_BALLAST: SWITCH, - zigpy.profiles.zha.DeviceType.ON_OFF_LIGHT: LIGHT, - zigpy.profiles.zha.DeviceType.ON_OFF_LIGHT_SWITCH: SWITCH, - zigpy.profiles.zha.DeviceType.ON_OFF_PLUG_IN_UNIT: SWITCH, - zigpy.profiles.zha.DeviceType.SMART_PLUG: SWITCH, - } - ) - - DEVICE_CLASS[zigpy.profiles.zll.PROFILE_ID].update( - { - zigpy.profiles.zll.DeviceType.COLOR_LIGHT: LIGHT, - zigpy.profiles.zll.DeviceType.COLOR_TEMPERATURE_LIGHT: LIGHT, - zigpy.profiles.zll.DeviceType.DIMMABLE_LIGHT: LIGHT, - zigpy.profiles.zll.DeviceType.DIMMABLE_PLUGIN_UNIT: LIGHT, - zigpy.profiles.zll.DeviceType.EXTENDED_COLOR_LIGHT: LIGHT, - zigpy.profiles.zll.DeviceType.ON_OFF_LIGHT: LIGHT, - zigpy.profiles.zll.DeviceType.ON_OFF_PLUGIN_UNIT: SWITCH, - } - ) - - SINGLE_INPUT_CLUSTER_DEVICE_CLASS.update( - { - # this works for now but if we hit conflicts we can break it out to - # a different dict that is keyed by manufacturer - SMARTTHINGS_ACCELERATION_CLUSTER: BINARY_SENSOR, - SMARTTHINGS_HUMIDITY_CLUSTER: SENSOR, - zcl.clusters.closures.DoorLock: LOCK, - zcl.clusters.general.AnalogInput.cluster_id: SENSOR, - zcl.clusters.general.MultistateInput.cluster_id: SENSOR, - zcl.clusters.general.OnOff: SWITCH, - zcl.clusters.general.PowerConfiguration: SENSOR, - zcl.clusters.homeautomation.ElectricalMeasurement: SENSOR, - zcl.clusters.hvac.Fan: FAN, - zcl.clusters.measurement.IlluminanceMeasurement: SENSOR, - zcl.clusters.measurement.OccupancySensing: BINARY_SENSOR, - zcl.clusters.measurement.PressureMeasurement: SENSOR, - zcl.clusters.measurement.RelativeHumidity: SENSOR, - zcl.clusters.measurement.TemperatureMeasurement: SENSOR, - zcl.clusters.security.IasZone: BINARY_SENSOR, - zcl.clusters.smartenergy.Metering: SENSOR, - } - ) - - SINGLE_OUTPUT_CLUSTER_DEVICE_CLASS.update( - {zcl.clusters.general.OnOff: BINARY_SENSOR} - ) - - zha = zigpy.profiles.zha - REMOTE_DEVICE_TYPES[zha.PROFILE_ID].append(zha.DeviceType.COLOR_CONTROLLER) - REMOTE_DEVICE_TYPES[zha.PROFILE_ID].append(zha.DeviceType.COLOR_DIMMER_SWITCH) - REMOTE_DEVICE_TYPES[zha.PROFILE_ID].append(zha.DeviceType.COLOR_SCENE_CONTROLLER) - REMOTE_DEVICE_TYPES[zha.PROFILE_ID].append(zha.DeviceType.DIMMER_SWITCH) - REMOTE_DEVICE_TYPES[zha.PROFILE_ID].append(zha.DeviceType.NON_COLOR_CONTROLLER) - REMOTE_DEVICE_TYPES[zha.PROFILE_ID].append( - zha.DeviceType.NON_COLOR_SCENE_CONTROLLER - ) - REMOTE_DEVICE_TYPES[zha.PROFILE_ID].append(zha.DeviceType.REMOTE_CONTROL) - REMOTE_DEVICE_TYPES[zha.PROFILE_ID].append(zha.DeviceType.SCENE_SELECTOR) - - zll = zigpy.profiles.zll - REMOTE_DEVICE_TYPES[zll.PROFILE_ID].append(zll.DeviceType.COLOR_CONTROLLER) - REMOTE_DEVICE_TYPES[zll.PROFILE_ID].append(zll.DeviceType.COLOR_SCENE_CONTROLLER) - REMOTE_DEVICE_TYPES[zll.PROFILE_ID].append(zll.DeviceType.CONTROL_BRIDGE) - REMOTE_DEVICE_TYPES[zll.PROFILE_ID].append(zll.DeviceType.CONTROLLER) - REMOTE_DEVICE_TYPES[zll.PROFILE_ID].append(zll.DeviceType.SCENE_CONTROLLER) - - def set_or_callable(value): """Convert single str or None to a set. Pass through callables and sets.""" if value is None: diff --git a/tests/components/zha/conftest.py b/tests/components/zha/conftest.py index cc8f9366ecb..d8abfb8f227 100644 --- a/tests/components/zha/conftest.py +++ b/tests/components/zha/conftest.py @@ -9,7 +9,6 @@ from zigpy.application import ControllerApplication from homeassistant import config_entries from homeassistant.components.zha.core.const import COMPONENTS, DATA_ZHA, DOMAIN from homeassistant.components.zha.core.gateway import ZHAGateway -from homeassistant.components.zha.core.registries import establish_device_mappings from homeassistant.components.zha.core.store import async_get_registry from homeassistant.helpers.device_registry import async_get_registry as get_dev_reg @@ -41,7 +40,6 @@ async def zha_gateway_fixture(hass, config_entry): Create a ZHAGateway object that can be used to interact with as if we had a real zigbee network running. """ - establish_device_mappings() for component in COMPONENTS: hass.data[DATA_ZHA][component] = hass.data[DATA_ZHA].get(component, {}) zha_storage = await async_get_registry(hass) From b097a64010a02fffbff8ca79199f0c53f8a1dc55 Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 3 Jan 2020 05:06:33 +0100 Subject: [PATCH 514/677] Implemented media_play & media_pause / push to version 0.7.11 of denonavr (#30421) * Implement media_play & media_pause / push to version 0.7.11 of denonavr * fix denonavr version in requirements_test_all.txt --- homeassistant/components/denonavr/manifest.json | 2 +- homeassistant/components/denonavr/media_player.py | 11 +++++++++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/denonavr/manifest.json b/homeassistant/components/denonavr/manifest.json index 9e084c78e21..a80c7d6ea5d 100644 --- a/homeassistant/components/denonavr/manifest.json +++ b/homeassistant/components/denonavr/manifest.json @@ -3,7 +3,7 @@ "name": "Denonavr", "documentation": "https://www.home-assistant.io/integrations/denonavr", "requirements": [ - "denonavr==0.7.10" + "denonavr==0.7.11" ], "dependencies": [], "codeowners": [] diff --git a/homeassistant/components/denonavr/media_player.py b/homeassistant/components/denonavr/media_player.py index 46d22187ce1..350d065f9d9 100644 --- a/homeassistant/components/denonavr/media_player.py +++ b/homeassistant/components/denonavr/media_player.py @@ -94,7 +94,6 @@ NewHost = namedtuple("NewHost", ["host", "name"]) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Denon platform.""" - # Initialize list with receivers to be started receivers = [] @@ -365,9 +364,17 @@ class DenonDevice(MediaPlayerDevice): return attributes def media_play_pause(self): - """Simulate play pause media player.""" + """Play or pause the media player.""" return self._receiver.toggle_play_pause() + def media_play(self): + """Send play command.""" + return self._receiver.play() + + def media_pause(self): + """Send pause command.""" + return self._receiver.pause() + def media_previous_track(self): """Send previous track command.""" return self._receiver.previous_track() diff --git a/requirements_all.txt b/requirements_all.txt index dd19553b961..8c85b023b02 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -420,7 +420,7 @@ defusedxml==0.6.0 deluge-client==1.7.1 # homeassistant.components.denonavr -denonavr==0.7.10 +denonavr==0.7.11 # homeassistant.components.directv directpy==0.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 373abe92ec8..fb0a7d76513 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -150,7 +150,7 @@ datadog==0.15.0 defusedxml==0.6.0 # homeassistant.components.denonavr -denonavr==0.7.10 +denonavr==0.7.11 # homeassistant.components.directv directpy==0.5 From 3a18ef219b1f4b906f9c0c18955df32bd36cd9c8 Mon Sep 17 00:00:00 2001 From: ochlocracy <5885236+ochlocracy@users.noreply.github.com> Date: Fri, 3 Jan 2020 05:30:26 -0500 Subject: [PATCH 515/677] Add RTSP stream support for UVC (Unifi Video Client) integration (#30297) * Add SUPPORT_STREAM to supported_features. * Implement stream_source with channel RTSP URIs. * Add Tests for Stream Support. * Make stream_source async. * Removed unused import. * Re-wrote test to remove warning, and lint error. --- homeassistant/components/uvc/camera.py | 23 +++++++++++++++++++++- tests/components/uvc/test_camera.py | 27 ++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/uvc/camera.py b/homeassistant/components/uvc/camera.py index b9a6262cd4f..cd6875cdcdc 100644 --- a/homeassistant/components/uvc/camera.py +++ b/homeassistant/components/uvc/camera.py @@ -6,7 +6,7 @@ import requests from uvcclient import camera as uvc_camera, nvr import voluptuous as vol -from homeassistant.components.camera import PLATFORM_SCHEMA, Camera +from homeassistant.components.camera import PLATFORM_SCHEMA, SUPPORT_STREAM, Camera from homeassistant.const import CONF_PORT, CONF_SSL from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv @@ -92,6 +92,17 @@ class UnifiVideoCamera(Camera): """Return the name of this camera.""" return self._name + @property + def supported_features(self): + """Return supported features.""" + caminfo = self._nvr.get_camera(self._uuid) + channels = caminfo["channels"] + for channel in channels: + if channel["isRtspEnabled"]: + return SUPPORT_STREAM + + return 0 + @property def is_recording(self): """Return true if the camera is recording.""" @@ -199,3 +210,13 @@ class UnifiVideoCamera(Camera): def disable_motion_detection(self): """Disable motion detection in camera.""" self.set_motion_detection(False) + + async def stream_source(self): + """Return the source of the stream.""" + caminfo = self._nvr.get_camera(self._uuid) + channels = caminfo["channels"] + for channel in channels: + if channel["isRtspEnabled"]: + return channel["rtspUris"][0] + + return None diff --git a/tests/components/uvc/test_camera.py b/tests/components/uvc/test_camera.py index c77b5d83749..b95d940bda4 100644 --- a/tests/components/uvc/test_camera.py +++ b/tests/components/uvc/test_camera.py @@ -7,6 +7,7 @@ import pytest import requests from uvcclient import camera, nvr +from homeassistant.components.camera import SUPPORT_STREAM from homeassistant.components.uvc import camera as uvc from homeassistant.exceptions import PlatformNotReady from homeassistant.setup import setup_component @@ -190,6 +191,26 @@ class TestUVC(unittest.TestCase): "host": "host-a", "internalHost": "host-b", "username": "admin", + "channels": [ + { + "id": "0", + "width": 1920, + "height": 1080, + "fps": 25, + "bitrate": 6000000, + "isRtspEnabled": True, + "rtspUris": ["rtsp://host-a:7447/uuid_rtspchannel_0"], + }, + { + "id": "1", + "width": 1024, + "height": 576, + "fps": 15, + "bitrate": 1200000, + "isRtspEnabled": False, + "rtspUris": ["rtsp://host-a:7447/uuid_rtspchannel_1"], + }, + ], } self.nvr.server_version = (3, 2, 0) @@ -199,6 +220,12 @@ class TestUVC(unittest.TestCase): assert self.uvc.is_recording assert "Ubiquiti" == self.uvc.brand assert "UVC Fake" == self.uvc.model + assert SUPPORT_STREAM == self.uvc.supported_features + + def test_stream(self): + """Test the RTSP stream URI.""" + stream_source = yield from self.uvc.stream_source() + assert stream_source == "rtsp://host-a:7447/uuid_rtspchannel_0" @mock.patch("uvcclient.store.get_info_store") @mock.patch("uvcclient.camera.UVCCameraClientV320") From f5aa89dd060cf804364865fd637ca7934968478e Mon Sep 17 00:00:00 2001 From: tetienne Date: Fri, 3 Jan 2020 11:31:05 +0100 Subject: [PATCH 516/677] Fix set tilt position (#30428) --- homeassistant/components/somfy/cover.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/somfy/cover.py b/homeassistant/components/somfy/cover.py index 5eabe7ee07a..b48e326162d 100644 --- a/homeassistant/components/somfy/cover.py +++ b/homeassistant/components/somfy/cover.py @@ -91,15 +91,15 @@ class SomfyCover(SomfyEntity, CoverDevice): def set_cover_tilt_position(self, **kwargs): """Move the cover tilt to a specific position.""" - self.cover.orientation = kwargs[ATTR_TILT_POSITION] + self.cover.orientation = 100 - kwargs[ATTR_TILT_POSITION] def open_cover_tilt(self, **kwargs): """Open the cover tilt.""" - self.cover.orientation = 100 + self.cover.orientation = 0 def close_cover_tilt(self, **kwargs): """Close the cover tilt.""" - self.cover.orientation = 0 + self.cover.orientation = 100 def stop_cover_tilt(self, **kwargs): """Stop the cover.""" From c130e81638cc39f520fa056ea5af0997eff7586d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Fri, 3 Jan 2020 12:31:50 +0200 Subject: [PATCH 517/677] Fix number of times seen in debug message (#30429) --- homeassistant/components/bluetooth_le_tracker/device_tracker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/bluetooth_le_tracker/device_tracker.py b/homeassistant/components/bluetooth_le_tracker/device_tracker.py index 9c64232c6e9..523c40c8cfe 100644 --- a/homeassistant/components/bluetooth_le_tracker/device_tracker.py +++ b/homeassistant/components/bluetooth_le_tracker/device_tracker.py @@ -46,8 +46,8 @@ def setup_scanner(hass, config, see, discovery_info=None): """Mark a device as seen.""" if new_device: if address in new_devices: - _LOGGER.debug("Seen %s %s times", address, new_devices[address]) new_devices[address] += 1 + _LOGGER.debug("Seen %s %s times", address, new_devices[address]) if new_devices[address] >= MIN_SEEN_NEW: _LOGGER.debug("Adding %s to tracked devices", address) devs_to_track.append(address) From 0a4f3ec1ec83f882c4fe7abea624b9d6db4b2007 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 3 Jan 2020 11:50:53 +0100 Subject: [PATCH 518/677] Use config entry unique id for deCONZ (#30122) * Use config entry unique id * Clean up * Backwards compatiblity note * Fix some of Balloobs comments * Bump dependency to v66 * Black somehow missed config flow tests... * Move set unique ID til after possibility to update existing entry --- homeassistant/components/deconz/__init__.py | 25 ++- .../components/deconz/config_flow.py | 67 ++++----- homeassistant/components/deconz/const.py | 1 - .../components/deconz/device_trigger.py | 6 +- homeassistant/components/deconz/gateway.py | 13 +- homeassistant/components/deconz/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/deconz/test_config_flow.py | 90 +++++------ tests/components/deconz/test_gateway.py | 7 +- tests/components/deconz/test_init.py | 142 ++++++------------ tests/components/deconz/test_services.py | 5 +- 12 files changed, 136 insertions(+), 226 deletions(-) diff --git a/homeassistant/components/deconz/__init__.py b/homeassistant/components/deconz/__init__.py index 0ea91d10b19..096bc6c2904 100644 --- a/homeassistant/components/deconz/__init__.py +++ b/homeassistant/components/deconz/__init__.py @@ -4,8 +4,8 @@ import voluptuous as vol from homeassistant.const import EVENT_HOMEASSISTANT_STOP from .config_flow import get_master_gateway -from .const import CONF_BRIDGEID, CONF_MASTER_GATEWAY, CONF_UUID, DOMAIN -from .gateway import DeconzGateway, get_gateway_from_config_entry +from .const import CONF_MASTER_GATEWAY, DOMAIN +from .gateway import DeconzGateway from .services import async_setup_services, async_unload_services CONFIG_SCHEMA = vol.Schema( @@ -35,13 +35,16 @@ async def async_setup_entry(hass, config_entry): if not await gateway.async_setup(): return False - hass.data[DOMAIN][gateway.bridgeid] = gateway + # 0.104 introduced config entry unique id, this makes upgrading possible + if config_entry.unique_id is None: + hass.config_entries.async_update_entry( + config_entry, unique_id=gateway.api.config.bridgeid + ) + + hass.data[DOMAIN][config_entry.unique_id] = gateway await gateway.async_update_device_registry() - if CONF_UUID not in config_entry.data: - await async_add_uuid_to_config_entry(hass, config_entry) - await async_setup_services(hass) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, gateway.shutdown) @@ -51,7 +54,7 @@ async def async_setup_entry(hass, config_entry): async def async_unload_entry(hass, config_entry): """Unload deCONZ config entry.""" - gateway = hass.data[DOMAIN].pop(config_entry.data[CONF_BRIDGEID]) + gateway = hass.data[DOMAIN].pop(config_entry.unique_id) if not hass.data[DOMAIN]: await async_unload_services(hass) @@ -74,11 +77,3 @@ async def async_update_master_gateway(hass, config_entry): options = {**config_entry.options, CONF_MASTER_GATEWAY: master} hass.config_entries.async_update_entry(config_entry, options=options) - - -async def async_add_uuid_to_config_entry(hass, config_entry): - """Add UUID to config entry to help discovery identify entries.""" - gateway = get_gateway_from_config_entry(hass, config_entry) - config = {**config_entry.data, CONF_UUID: gateway.api.config.uuid} - - hass.config_entries.async_update_entry(config_entry, data=config) diff --git a/homeassistant/components/deconz/config_flow.py b/homeassistant/components/deconz/config_flow.py index c84192456d1..0cec6add28c 100644 --- a/homeassistant/components/deconz/config_flow.py +++ b/homeassistant/components/deconz/config_flow.py @@ -4,7 +4,12 @@ from urllib.parse import urlparse import async_timeout from pydeconz.errors import RequestError, ResponseError -from pydeconz.utils import async_discovery, async_get_api_key, async_get_gateway_config +from pydeconz.utils import ( + async_discovery, + async_get_api_key, + async_get_bridge_id, + normalize_bridge_id, +) import voluptuous as vol from homeassistant import config_entries @@ -14,11 +19,9 @@ from homeassistant.core import callback from homeassistant.helpers import aiohttp_client from .const import ( - _LOGGER, CONF_ALLOW_CLIP_SENSOR, CONF_ALLOW_DECONZ_GROUPS, CONF_BRIDGEID, - CONF_UUID, DEFAULT_ALLOW_CLIP_SENSOR, DEFAULT_ALLOW_DECONZ_GROUPS, DEFAULT_PORT, @@ -29,15 +32,6 @@ DECONZ_MANUFACTURERURL = "http://www.dresden-elektronik.de" CONF_SERIAL = "serial" -@callback -def configured_gateways(hass): - """Return a set of all configured gateways.""" - return { - entry.data[CONF_BRIDGEID]: entry - for entry in hass.config_entries.async_entries(DOMAIN) - } - - @callback def get_master_gateway(hass): """Return the gateway which is marked as master.""" @@ -62,6 +56,7 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self): """Initialize the deCONZ config flow.""" + self.bridge_id = None self.bridges = [] self.deconz_config = {} @@ -79,7 +74,11 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): if user_input is not None: for bridge in self.bridges: if bridge[CONF_HOST] == user_input[CONF_HOST]: - self.deconz_config = bridge + self.bridge_id = bridge[CONF_BRIDGEID] + self.deconz_config = { + CONF_HOST: bridge[CONF_HOST], + CONF_PORT: bridge[CONF_PORT], + } return await self.async_step_link() self.deconz_config = user_input @@ -95,8 +94,7 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self.bridges = [] if len(self.bridges) == 1: - self.deconz_config = self.bridges[0] - return await self.async_step_link() + return await self.async_step_user(self.bridges[0]) if len(self.bridges) > 1: hosts = [] @@ -141,23 +139,21 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def _create_entry(self): """Create entry for gateway.""" - if CONF_BRIDGEID not in self.deconz_config: + if not self.bridge_id: session = aiohttp_client.async_get_clientsession(self.hass) try: with async_timeout.timeout(10): - gateway_config = await async_get_gateway_config( + self.bridge_id = await async_get_bridge_id( session, **self.deconz_config ) - self.deconz_config[CONF_BRIDGEID] = gateway_config.bridgeid - self.deconz_config[CONF_UUID] = gateway_config.uuid + await self.async_set_unique_id(self.bridge_id) + self._abort_if_unique_id_configured() except asyncio.TimeoutError: return self.async_abort(reason="no_bridges") - return self.async_create_entry( - title="deCONZ-" + self.deconz_config[CONF_BRIDGEID], data=self.deconz_config - ) + return self.async_create_entry(title=self.bridge_id, data=self.deconz_config) def _update_entry(self, entry, host, port, api_key=None): """Update existing entry.""" @@ -182,27 +178,17 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): if discovery_info[ssdp.ATTR_UPNP_MANUFACTURER_URL] != DECONZ_MANUFACTURERURL: return self.async_abort(reason="not_deconz_bridge") - uuid = discovery_info[ssdp.ATTR_UPNP_UDN].replace("uuid:", "") - - _LOGGER.debug("deCONZ gateway discovered (%s)", uuid) - + self.bridge_id = normalize_bridge_id(discovery_info[ssdp.ATTR_UPNP_SERIAL]) parsed_url = urlparse(discovery_info[ssdp.ATTR_SSDP_LOCATION]) for entry in self.hass.config_entries.async_entries(DOMAIN): - if uuid == entry.data.get(CONF_UUID): + if self.bridge_id == entry.unique_id: if entry.source == "hassio": return self.async_abort(reason="already_configured") return self._update_entry(entry, parsed_url.hostname, parsed_url.port) - bridgeid = discovery_info[ssdp.ATTR_UPNP_SERIAL] - if any( - bridgeid == flow["context"][CONF_BRIDGEID] - for flow in self._async_in_progress() - ): - return self.async_abort(reason="already_in_progress") - + await self.async_set_unique_id(self.bridge_id) # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 - self.context[CONF_BRIDGEID] = bridgeid self.context["title_placeholders"] = {"host": parsed_url.hostname} self.deconz_config = { @@ -217,18 +203,18 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): This flow is triggered by the discovery component. """ - bridgeid = user_input[CONF_SERIAL] - gateway_entries = configured_gateways(self.hass) + self.bridge_id = normalize_bridge_id(user_input[CONF_SERIAL]) + gateway = self.hass.data.get(DOMAIN, {}).get(self.bridge_id) - if bridgeid in gateway_entries: - entry = gateway_entries[bridgeid] + if gateway: return self._update_entry( - entry, + gateway.config_entry, user_input[CONF_HOST], user_input[CONF_PORT], user_input[CONF_API_KEY], ) + await self.async_set_unique_id(self.bridge_id) self._hassio_discovery = user_input return await self.async_step_hassio_confirm() @@ -239,7 +225,6 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self.deconz_config = { CONF_HOST: self._hassio_discovery[CONF_HOST], CONF_PORT: self._hassio_discovery[CONF_PORT], - CONF_BRIDGEID: self._hassio_discovery[CONF_SERIAL], CONF_API_KEY: self._hassio_discovery[CONF_API_KEY], } diff --git a/homeassistant/components/deconz/const.py b/homeassistant/components/deconz/const.py index a663f99bf73..47975750de9 100644 --- a/homeassistant/components/deconz/const.py +++ b/homeassistant/components/deconz/const.py @@ -6,7 +6,6 @@ _LOGGER = logging.getLogger(__package__) DOMAIN = "deconz" CONF_BRIDGEID = "bridgeid" -CONF_UUID = "uuid" DEFAULT_PORT = 80 DEFAULT_ALLOW_CLIP_SENSOR = False diff --git a/homeassistant/components/deconz/device_trigger.py b/homeassistant/components/deconz/device_trigger.py index 9c8a41453aa..9bdfa70b7de 100644 --- a/homeassistant/components/deconz/device_trigger.py +++ b/homeassistant/components/deconz/device_trigger.py @@ -15,9 +15,7 @@ from homeassistant.const import ( ) from . import DOMAIN -from .config_flow import configured_gateways from .deconz_event import CONF_DECONZ_EVENT, CONF_UNIQUE_ID -from .gateway import get_gateway_from_config_entry CONF_SUBTYPE = "subtype" @@ -287,10 +285,8 @@ TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend( def _get_deconz_event_from_device_id(hass, device_id): """Resolve deconz event from device id.""" - deconz_config_entries = configured_gateways(hass) - for config_entry in deconz_config_entries.values(): + for gateway in hass.data.get(DOMAIN): - gateway = get_gateway_from_config_entry(hass, config_entry) for deconz_event in gateway.events: if device_id == deconz_event.device_id: diff --git a/homeassistant/components/deconz/gateway.py b/homeassistant/components/deconz/gateway.py index 083af2dca6f..04452cc313c 100644 --- a/homeassistant/components/deconz/gateway.py +++ b/homeassistant/components/deconz/gateway.py @@ -22,7 +22,6 @@ from .const import ( _LOGGER, CONF_ALLOW_CLIP_SENSOR, CONF_ALLOW_DECONZ_GROUPS, - CONF_BRIDGEID, CONF_MASTER_GATEWAY, DEFAULT_ALLOW_CLIP_SENSOR, DEFAULT_ALLOW_DECONZ_GROUPS, @@ -36,7 +35,7 @@ from .errors import AuthenticationRequired, CannotConnect @callback def get_gateway_from_config_entry(hass, config_entry): """Return gateway with a matching bridge id.""" - return hass.data[DOMAIN][config_entry.data[CONF_BRIDGEID]] + return hass.data[DOMAIN][config_entry.unique_id] class DeconzGateway: @@ -56,7 +55,7 @@ class DeconzGateway: @property def bridgeid(self) -> str: """Return the unique identifier of the gateway.""" - return self.config_entry.data[CONF_BRIDGEID] + return self.config_entry.unique_id @property def master(self) -> bool: @@ -92,11 +91,9 @@ class DeconzGateway: async def async_setup(self) -> bool: """Set up a deCONZ gateway.""" - hass = self.hass - try: self.api = await get_gateway( - hass, + self.hass, self.config_entry.data, self.async_add_device_callback, self.async_connection_status_callback, @@ -110,8 +107,8 @@ class DeconzGateway: return False for component in SUPPORTED_PLATFORMS: - hass.async_create_task( - hass.config_entries.async_forward_entry_setup( + self.hass.async_create_task( + self.hass.config_entries.async_forward_entry_setup( self.config_entry, component ) ) diff --git a/homeassistant/components/deconz/manifest.json b/homeassistant/components/deconz/manifest.json index 30b00600331..402244fbc13 100644 --- a/homeassistant/components/deconz/manifest.json +++ b/homeassistant/components/deconz/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/deconz", "requirements": [ - "pydeconz==65" + "pydeconz==66" ], "ssdp": [ { diff --git a/requirements_all.txt b/requirements_all.txt index 8c85b023b02..a6cba26235b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1187,7 +1187,7 @@ pydaikin==1.6.1 pydanfossair==0.1.0 # homeassistant.components.deconz -pydeconz==65 +pydeconz==66 # homeassistant.components.delijn pydelijn==0.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fb0a7d76513..93eedd2ccff 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -410,7 +410,7 @@ pycoolmasternet==0.0.4 pydaikin==1.6.1 # homeassistant.components.deconz -pydeconz==65 +pydeconz==66 # homeassistant.components.zwave pydispatcher==2.0.5 diff --git a/tests/components/deconz/test_config_flow.py b/tests/components/deconz/test_config_flow.py index da8b0a8a7f4..f8fe42d10d8 100644 --- a/tests/components/deconz/test_config_flow.py +++ b/tests/components/deconz/test_config_flow.py @@ -1,11 +1,20 @@ """Tests for deCONZ config flow.""" import asyncio -from unittest.mock import Mock, patch +from copy import deepcopy +from asynctest import Mock, patch import pydeconz from homeassistant.components import ssdp from homeassistant.components.deconz import config_flow +from homeassistant.components.deconz.const import CONF_BRIDGEID + +from .test_gateway import ( + BRIDGEID, + DECONZ_WEB_REQUEST, + ENTRY_CONFIG, + setup_deconz_integration, +) from tests.common import MockConfigEntry @@ -14,7 +23,7 @@ async def test_flow_works(hass, aioclient_mock): """Test that config flow works.""" aioclient_mock.get( pydeconz.utils.URL_DISCOVER, - json=[{"id": "id", "internalipaddress": "1.2.3.4", "internalport": 80}], + json=[{"id": BRIDGEID, "internalipaddress": "1.2.3.4", "internalport": 80}], headers={"content-type": "application/json"}, ) aioclient_mock.post( @@ -35,9 +44,8 @@ async def test_flow_works(hass, aioclient_mock): ) assert result["type"] == "create_entry" - assert result["title"] == "deCONZ-id" + assert result["title"] == BRIDGEID assert result["data"] == { - config_flow.CONF_BRIDGEID: "id", config_flow.CONF_HOST: "1.2.3.4", config_flow.CONF_PORT: 80, config_flow.CONF_API_KEY: "1234567890ABCDEF", @@ -117,12 +125,12 @@ async def test_user_step_two_bridges_selection(hass, aioclient_mock): flow.hass = hass flow.bridges = [ { - config_flow.CONF_BRIDGEID: "id1", + CONF_BRIDGEID: "id1", config_flow.CONF_HOST: "1.2.3.4", config_flow.CONF_PORT: 80, }, { - config_flow.CONF_BRIDGEID: "id2", + CONF_BRIDGEID: "id2", config_flow.CONF_HOST: "5.6.7.8", config_flow.CONF_PORT: 80, }, @@ -241,25 +249,22 @@ async def test_bridge_discovery_update_existing_entry(hass): """Test if a discovered bridge has already been configured.""" entry = MockConfigEntry( domain=config_flow.DOMAIN, - data={ - config_flow.CONF_HOST: "1.2.3.4", - config_flow.CONF_BRIDGEID: "123ABC", - config_flow.CONF_UUID: "456DEF", - }, + source="user", + data={config_flow.CONF_HOST: "1.2.3.4", config_flow.CONF_PORT: 80}, + unique_id=BRIDGEID, ) entry.add_to_hass(hass) gateway = Mock() gateway.config_entry = entry - hass.data[config_flow.DOMAIN] = {"123ABC": gateway} + hass.data[config_flow.DOMAIN] = {BRIDGEID: gateway} result = await hass.config_entries.flow.async_init( config_flow.DOMAIN, data={ ssdp.ATTR_SSDP_LOCATION: "http://mock-deconz/", ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.DECONZ_MANUFACTURERURL, - ssdp.ATTR_UPNP_SERIAL: "123ABC", - ssdp.ATTR_UPNP_UDN: "uuid:456DEF", + ssdp.ATTR_UPNP_SERIAL: BRIDGEID, }, context={"source": "ssdp"}, ) @@ -277,8 +282,8 @@ async def test_bridge_discovery_dont_update_existing_hassio_entry(hass): data={ config_flow.CONF_HOST: "core-deconz", config_flow.CONF_BRIDGEID: "123ABC", - config_flow.CONF_UUID: "456DEF", }, + unique_id="123ABC", ) entry.add_to_hass(hass) @@ -292,7 +297,6 @@ async def test_bridge_discovery_dont_update_existing_hassio_entry(hass): ssdp.ATTR_SSDP_LOCATION: "http://mock-deconz/", ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.DECONZ_MANUFACTURERURL, ssdp.ATTR_UPNP_SERIAL: "123ABC", - ssdp.ATTR_UPNP_UDN: "uuid:456DEF", }, context={"source": "ssdp"}, ) @@ -306,11 +310,12 @@ async def test_create_entry(hass, aioclient_mock): """Test that _create_entry work and that bridgeid can be requested.""" aioclient_mock.get( "http://1.2.3.4:80/api/1234567890ABCDEF/config", - json={"bridgeid": "123ABC", "uuid": "456DEF"}, + json={"bridgeid": BRIDGEID, "uuid": "456DEF"}, headers={"content-type": "application/json"}, ) flow = config_flow.DeconzFlowHandler() + flow.context = {} flow.hass = hass flow.deconz_config = { config_flow.CONF_HOST: "1.2.3.4", @@ -321,13 +326,11 @@ async def test_create_entry(hass, aioclient_mock): result = await flow._create_entry() assert result["type"] == "create_entry" - assert result["title"] == "deCONZ-123ABC" + assert result["title"] == BRIDGEID assert result["data"] == { - config_flow.CONF_BRIDGEID: "123ABC", config_flow.CONF_HOST: "1.2.3.4", config_flow.CONF_PORT: 80, config_flow.CONF_API_KEY: "1234567890ABCDEF", - config_flow.CONF_UUID: "456DEF", } @@ -342,7 +345,7 @@ async def test_create_entry_timeout(hass, aioclient_mock): } with patch( - "homeassistant.components.deconz.config_flow.async_get_gateway_config", + "homeassistant.components.deconz.config_flow.async_get_bridge_id", side_effect=asyncio.TimeoutError, ): result = await flow._create_entry() @@ -353,54 +356,44 @@ async def test_create_entry_timeout(hass, aioclient_mock): async def test_hassio_update_instance(hass): """Test we can update an existing config entry.""" - entry = MockConfigEntry( - domain=config_flow.DOMAIN, - data={ - config_flow.CONF_BRIDGEID: "id", - config_flow.CONF_HOST: "1.2.3.4", - config_flow.CONF_PORT: 40850, - config_flow.CONF_API_KEY: "secret", - }, + data = deepcopy(DECONZ_WEB_REQUEST) + entry_config = deepcopy(ENTRY_CONFIG) + gateway = await setup_deconz_integration( + hass, entry_config, options={}, get_state_response=data ) - entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( config_flow.DOMAIN, data={ - config_flow.CONF_HOST: "mock-deconz", + config_flow.CONF_HOST: "2.3.4.5", config_flow.CONF_PORT: 8080, config_flow.CONF_API_KEY: "updated", - config_flow.CONF_SERIAL: "id", + config_flow.CONF_SERIAL: BRIDGEID, }, context={"source": "hassio"}, ) assert result["type"] == "abort" assert result["reason"] == "updated_instance" - assert entry.data[config_flow.CONF_HOST] == "mock-deconz" - assert entry.data[config_flow.CONF_PORT] == 8080 - assert entry.data[config_flow.CONF_API_KEY] == "updated" + assert gateway.config_entry.data[config_flow.CONF_HOST] == "2.3.4.5" + assert gateway.config_entry.data[config_flow.CONF_PORT] == 8080 + assert gateway.config_entry.data[config_flow.CONF_API_KEY] == "updated" async def test_hassio_dont_update_instance(hass): """Test we can update an existing config entry.""" - entry = MockConfigEntry( - domain=config_flow.DOMAIN, - data={ - config_flow.CONF_BRIDGEID: "id", - config_flow.CONF_HOST: "1.2.3.4", - config_flow.CONF_PORT: 8080, - config_flow.CONF_API_KEY: "secret", - }, + data = deepcopy(DECONZ_WEB_REQUEST) + await setup_deconz_integration( + hass, ENTRY_CONFIG, options={}, get_state_response=data ) - entry.add_to_hass(hass) + result = await hass.config_entries.flow.async_init( config_flow.DOMAIN, data={ config_flow.CONF_HOST: "1.2.3.4", - config_flow.CONF_PORT: 8080, - config_flow.CONF_API_KEY: "secret", - config_flow.CONF_SERIAL: "id", + config_flow.CONF_PORT: 80, + config_flow.CONF_API_KEY: "ABCDEF", + config_flow.CONF_SERIAL: BRIDGEID, }, context={"source": "hassio"}, ) @@ -417,7 +410,7 @@ async def test_hassio_confirm(hass): "addon": "Mock Addon", config_flow.CONF_HOST: "mock-deconz", config_flow.CONF_PORT: 80, - config_flow.CONF_SERIAL: "id", + config_flow.CONF_SERIAL: BRIDGEID, config_flow.CONF_API_KEY: "1234567890ABCDEF", }, context={"source": "hassio"}, @@ -434,7 +427,6 @@ async def test_hassio_confirm(hass): assert result["result"].data == { config_flow.CONF_HOST: "mock-deconz", config_flow.CONF_PORT: 80, - config_flow.CONF_BRIDGEID: "id", config_flow.CONF_API_KEY: "1234567890ABCDEF", } diff --git a/tests/components/deconz/test_gateway.py b/tests/components/deconz/test_gateway.py index 288868f1bec..656b610f4a2 100644 --- a/tests/components/deconz/test_gateway.py +++ b/tests/components/deconz/test_gateway.py @@ -10,14 +10,12 @@ from homeassistant.components import deconz, ssdp from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.dispatcher import async_dispatcher_connect -BRIDGEID = "0123456789" +BRIDGEID = "01234E56789A" ENTRY_CONFIG = { deconz.config_flow.CONF_API_KEY: "ABCDEF", - deconz.config_flow.CONF_BRIDGEID: BRIDGEID, deconz.config_flow.CONF_HOST: "1.2.3.4", deconz.config_flow.CONF_PORT: 80, - deconz.config_flow.CONF_UUID: "456DEF", } DECONZ_CONFIG = { @@ -60,7 +58,8 @@ async def setup_deconz_integration(hass, config, options, get_state_response): hass.config_entries._entries.append(config_entry) - return hass.data[deconz.DOMAIN].get(config[deconz.CONF_BRIDGEID]) + bridgeid = get_state_response["config"]["bridgeid"] + return hass.data[deconz.DOMAIN].get(bridgeid) async def test_gateway_setup(hass): diff --git a/tests/components/deconz/test_init.py b/tests/components/deconz/test_init.py index 3c6e02ab41c..806fd7ed4aa 100644 --- a/tests/components/deconz/test_init.py +++ b/tests/components/deconz/test_init.py @@ -1,13 +1,14 @@ """Test deCONZ component setup process.""" import asyncio +from copy import deepcopy -from asynctest import Mock, patch +from asynctest import patch import pytest from homeassistant.components import deconz from homeassistant.exceptions import ConfigEntryNotReady -from tests.common import MockConfigEntry +from .test_gateway import DECONZ_WEB_REQUEST, ENTRY_CONFIG, setup_deconz_integration ENTRY1_HOST = "1.2.3.4" ENTRY1_PORT = 80 @@ -34,138 +35,83 @@ async def setup_entry(hass, entry): async def test_setup_entry_fails(hass): """Test setup entry fails if deCONZ is not available.""" - entry = Mock() - entry.data = { - deconz.config_flow.CONF_HOST: ENTRY1_HOST, - deconz.config_flow.CONF_PORT: ENTRY1_PORT, - deconz.config_flow.CONF_API_KEY: ENTRY1_API_KEY, - } + data = deepcopy(DECONZ_WEB_REQUEST) with patch("pydeconz.DeconzSession.initialize", side_effect=Exception): - await deconz.async_setup_entry(hass, entry) + await setup_deconz_integration( + hass, ENTRY_CONFIG, options={}, get_state_response=data + ) + assert not hass.data[deconz.DOMAIN] async def test_setup_entry_no_available_bridge(hass): """Test setup entry fails if deCONZ is not available.""" - entry = Mock() - entry.data = { - deconz.config_flow.CONF_HOST: ENTRY1_HOST, - deconz.config_flow.CONF_PORT: ENTRY1_PORT, - deconz.config_flow.CONF_API_KEY: ENTRY1_API_KEY, - } + data = deepcopy(DECONZ_WEB_REQUEST) with patch( "pydeconz.DeconzSession.initialize", side_effect=asyncio.TimeoutError ), pytest.raises(ConfigEntryNotReady): - await deconz.async_setup_entry(hass, entry) + await setup_deconz_integration( + hass, ENTRY_CONFIG, options={}, get_state_response=data + ) async def test_setup_entry_successful(hass): """Test setup entry is successful.""" - entry = MockConfigEntry( - domain=deconz.DOMAIN, - data={ - deconz.config_flow.CONF_HOST: ENTRY1_HOST, - deconz.config_flow.CONF_PORT: ENTRY1_PORT, - deconz.config_flow.CONF_API_KEY: ENTRY1_API_KEY, - deconz.CONF_BRIDGEID: ENTRY1_BRIDGEID, - deconz.CONF_UUID: ENTRY1_UUID, - }, + data = deepcopy(DECONZ_WEB_REQUEST) + gateway = await setup_deconz_integration( + hass, ENTRY_CONFIG, options={}, get_state_response=data ) - entry.add_to_hass(hass) - await setup_entry(hass, entry) - - assert ENTRY1_BRIDGEID in hass.data[deconz.DOMAIN] - assert hass.data[deconz.DOMAIN][ENTRY1_BRIDGEID].master + assert hass.data[deconz.DOMAIN] + assert gateway.bridgeid in hass.data[deconz.DOMAIN] + assert hass.data[deconz.DOMAIN][gateway.bridgeid].master async def test_setup_entry_multiple_gateways(hass): """Test setup entry is successful with multiple gateways.""" - entry = MockConfigEntry( - domain=deconz.DOMAIN, - data={ - deconz.config_flow.CONF_HOST: ENTRY1_HOST, - deconz.config_flow.CONF_PORT: ENTRY1_PORT, - deconz.config_flow.CONF_API_KEY: ENTRY1_API_KEY, - deconz.CONF_BRIDGEID: ENTRY1_BRIDGEID, - deconz.CONF_UUID: ENTRY1_UUID, - }, + data = deepcopy(DECONZ_WEB_REQUEST) + gateway = await setup_deconz_integration( + hass, ENTRY_CONFIG, options={}, get_state_response=data ) - entry.add_to_hass(hass) - entry2 = MockConfigEntry( - domain=deconz.DOMAIN, - data={ - deconz.config_flow.CONF_HOST: ENTRY2_HOST, - deconz.config_flow.CONF_PORT: ENTRY2_PORT, - deconz.config_flow.CONF_API_KEY: ENTRY2_API_KEY, - deconz.CONF_BRIDGEID: ENTRY2_BRIDGEID, - deconz.CONF_UUID: ENTRY2_UUID, - }, + data2 = deepcopy(DECONZ_WEB_REQUEST) + data2["config"]["bridgeid"] = "01234E56789B" + gateway2 = await setup_deconz_integration( + hass, ENTRY_CONFIG, options={}, get_state_response=data2 ) - entry2.add_to_hass(hass) - await setup_entry(hass, entry) - await setup_entry(hass, entry2) - - assert ENTRY1_BRIDGEID in hass.data[deconz.DOMAIN] - assert hass.data[deconz.DOMAIN][ENTRY1_BRIDGEID].master - assert ENTRY2_BRIDGEID in hass.data[deconz.DOMAIN] - assert not hass.data[deconz.DOMAIN][ENTRY2_BRIDGEID].master + assert len(hass.data[deconz.DOMAIN]) == 2 + assert hass.data[deconz.DOMAIN][gateway.bridgeid].master + assert not hass.data[deconz.DOMAIN][gateway2.bridgeid].master async def test_unload_entry(hass): """Test being able to unload an entry.""" - entry = MockConfigEntry( - domain=deconz.DOMAIN, - data={ - deconz.config_flow.CONF_HOST: ENTRY1_HOST, - deconz.config_flow.CONF_PORT: ENTRY1_PORT, - deconz.config_flow.CONF_API_KEY: ENTRY1_API_KEY, - deconz.CONF_BRIDGEID: ENTRY1_BRIDGEID, - deconz.CONF_UUID: ENTRY1_UUID, - }, + data = deepcopy(DECONZ_WEB_REQUEST) + gateway = await setup_deconz_integration( + hass, ENTRY_CONFIG, options={}, get_state_response=data ) - entry.add_to_hass(hass) - - await setup_entry(hass, entry) - - with patch.object(deconz.DeconzGateway, "async_reset", return_value=True): - assert await deconz.async_unload_entry(hass, entry) + assert hass.data[deconz.DOMAIN] + assert await deconz.async_unload_entry(hass, gateway.config_entry) assert not hass.data[deconz.DOMAIN] async def test_unload_entry_multiple_gateways(hass): """Test being able to unload an entry and master gateway gets moved.""" - entry = MockConfigEntry( - domain=deconz.DOMAIN, - data={ - deconz.config_flow.CONF_HOST: ENTRY1_HOST, - deconz.config_flow.CONF_PORT: ENTRY1_PORT, - deconz.config_flow.CONF_API_KEY: ENTRY1_API_KEY, - deconz.CONF_BRIDGEID: ENTRY1_BRIDGEID, - deconz.CONF_UUID: ENTRY1_UUID, - }, + data = deepcopy(DECONZ_WEB_REQUEST) + gateway = await setup_deconz_integration( + hass, ENTRY_CONFIG, options={}, get_state_response=data ) - entry.add_to_hass(hass) - entry2 = MockConfigEntry( - domain=deconz.DOMAIN, - data={ - deconz.config_flow.CONF_HOST: ENTRY2_HOST, - deconz.config_flow.CONF_PORT: ENTRY2_PORT, - deconz.config_flow.CONF_API_KEY: ENTRY2_API_KEY, - deconz.CONF_BRIDGEID: ENTRY2_BRIDGEID, - deconz.CONF_UUID: ENTRY2_UUID, - }, + data2 = deepcopy(DECONZ_WEB_REQUEST) + data2["config"]["bridgeid"] = "01234E56789B" + gateway2 = await setup_deconz_integration( + hass, ENTRY_CONFIG, options={}, get_state_response=data2 ) - entry2.add_to_hass(hass) - await setup_entry(hass, entry) - await setup_entry(hass, entry2) + assert len(hass.data[deconz.DOMAIN]) == 2 - with patch.object(deconz.DeconzGateway, "async_reset", return_value=True): - assert await deconz.async_unload_entry(hass, entry) + assert await deconz.async_unload_entry(hass, gateway.config_entry) - assert ENTRY2_BRIDGEID in hass.data[deconz.DOMAIN] - assert hass.data[deconz.DOMAIN][ENTRY2_BRIDGEID].master + assert len(hass.data[deconz.DOMAIN]) == 1 + assert hass.data[deconz.DOMAIN][gateway2.bridgeid].master diff --git a/tests/components/deconz/test_services.py b/tests/components/deconz/test_services.py index fad5444aa00..32065c60f83 100644 --- a/tests/components/deconz/test_services.py +++ b/tests/components/deconz/test_services.py @@ -6,6 +6,7 @@ import pytest import voluptuous as vol from homeassistant.components import deconz +from homeassistant.components.deconz.const import CONF_BRIDGEID from .test_gateway import ( BRIDGEID, @@ -99,7 +100,7 @@ async def test_configure_service_with_field(hass): data = { deconz.services.SERVICE_FIELD: "/light/2", - deconz.CONF_BRIDGEID: BRIDGEID, + CONF_BRIDGEID: BRIDGEID, deconz.services.SERVICE_DATA: {"on": True, "attr1": 10, "attr2": 20}, } @@ -203,7 +204,7 @@ async def test_service_refresh_devices(hass): hass, ENTRY_CONFIG, options={}, get_state_response=data ) - data = {deconz.CONF_BRIDGEID: BRIDGEID} + data = {CONF_BRIDGEID: BRIDGEID} with patch( "pydeconz.DeconzSession.request", From fdfedd086bcc6f19bf8e3023381f5c0d12f30624 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Fri, 3 Jan 2020 10:52:01 +0000 Subject: [PATCH 519/677] Rework FlowManager to use inheritance (#30133) * Pull async_finish_flow/async_create_flow out of ConfigEntries * Towards refactoring * mypy fixes * Mark Flow manager with abc.* annotations * Flake8 fixes * Mypy fixes * Blacken data_entry_flow * Blacken longer signatures caused by mypy changes * test fixes * Test fixes * Fix typo * Avoid protected member lint (W0212) in config_entries * More protected member fixes * Missing await --- homeassistant/auth/__init__.py | 111 ++++--- .../components/auth/mfa_setup_flow.py | 24 +- .../components/config/config_entries.py | 8 +- homeassistant/config_entries.py | 291 +++++++++--------- homeassistant/data_entry_flow.py | 41 ++- tests/components/deconz/test_config_flow.py | 2 +- .../opentherm_gw/test_config_flow.py | 8 +- tests/components/plex/test_config_flow.py | 4 +- tests/components/tesla/test_config_flow.py | 8 +- tests/components/unifi/test_config_flow.py | 2 +- tests/test_config_entries.py | 4 +- tests/test_data_entry_flow.py | 68 ++-- 12 files changed, 313 insertions(+), 258 deletions(-) diff --git a/homeassistant/auth/__init__.py b/homeassistant/auth/__init__.py index e4437bea840..9b3cf49fa22 100644 --- a/homeassistant/auth/__init__.py +++ b/homeassistant/auth/__init__.py @@ -67,6 +67,69 @@ async def auth_manager_from_config( return manager +class AuthManagerFlowManager(data_entry_flow.FlowManager): + """Manage authentication flows.""" + + def __init__(self, hass: HomeAssistant, auth_manager: "AuthManager"): + """Init auth manager flows.""" + super().__init__(hass) + self.auth_manager = auth_manager + + async def async_create_flow( + self, + handler_key: Any, + *, + context: Optional[Dict[str, Any]] = None, + data: Optional[Dict[str, Any]] = None, + ) -> data_entry_flow.FlowHandler: + """Create a login flow.""" + auth_provider = self.auth_manager.get_auth_provider(*handler_key) + if not auth_provider: + raise KeyError(f"Unknown auth provider {handler_key}") + return await auth_provider.async_login_flow(context) + + async def async_finish_flow( + self, flow: data_entry_flow.FlowHandler, result: Dict[str, Any] + ) -> Dict[str, Any]: + """Return a user as result of login flow.""" + flow = cast(LoginFlow, flow) + + if result["type"] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY: + return result + + # we got final result + if isinstance(result["data"], models.User): + result["result"] = result["data"] + return result + + auth_provider = self.auth_manager.get_auth_provider(*result["handler"]) + if not auth_provider: + raise KeyError(f"Unknown auth provider {result['handler']}") + + credentials = await auth_provider.async_get_or_create_credentials( + result["data"] + ) + + if flow.context.get("credential_only"): + result["result"] = credentials + return result + + # multi-factor module cannot enabled for new credential + # which has not linked to a user yet + if auth_provider.support_mfa and not credentials.is_new: + user = await self.auth_manager.async_get_user_by_credentials(credentials) + if user is not None: + modules = await self.auth_manager.async_get_enabled_mfa(user) + + if modules: + flow.user = user + flow.available_mfa_modules = modules + return await flow.async_step_select_mfa_module() + + result["result"] = await self.auth_manager.async_get_or_create_user(credentials) + return result + + class AuthManager: """Manage the authentication for Home Assistant.""" @@ -82,9 +145,7 @@ class AuthManager: self._store = store self._providers = providers self._mfa_modules = mfa_modules - self.login_flow = data_entry_flow.FlowManager( - hass, self._async_create_login_flow, self._async_finish_login_flow - ) + self.login_flow = AuthManagerFlowManager(hass, self) @property def auth_providers(self) -> List[AuthProvider]: @@ -417,50 +478,6 @@ class AuthManager: return refresh_token - async def _async_create_login_flow( - self, handler: _ProviderKey, *, context: Optional[Dict], data: Optional[Any] - ) -> data_entry_flow.FlowHandler: - """Create a login flow.""" - auth_provider = self._providers[handler] - - return await auth_provider.async_login_flow(context) - - async def _async_finish_login_flow( - self, flow: LoginFlow, result: Dict[str, Any] - ) -> Dict[str, Any]: - """Return a user as result of login flow.""" - if result["type"] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY: - return result - - # we got final result - if isinstance(result["data"], models.User): - result["result"] = result["data"] - return result - - auth_provider = self._providers[result["handler"]] - credentials = await auth_provider.async_get_or_create_credentials( - result["data"] - ) - - if flow.context.get("credential_only"): - result["result"] = credentials - return result - - # multi-factor module cannot enabled for new credential - # which has not linked to a user yet - if auth_provider.support_mfa and not credentials.is_new: - user = await self.async_get_user_by_credentials(credentials) - if user is not None: - modules = await self.async_get_enabled_mfa(user) - - if modules: - flow.user = user - flow.available_mfa_modules = modules - return await flow.async_step_select_mfa_module() - - result["result"] = await self.async_get_or_create_user(credentials) - return result - @callback def _async_get_auth_provider( self, credentials: models.Credentials diff --git a/homeassistant/components/auth/mfa_setup_flow.py b/homeassistant/components/auth/mfa_setup_flow.py index 92926e2e7c5..1b199551a14 100644 --- a/homeassistant/components/auth/mfa_setup_flow.py +++ b/homeassistant/components/auth/mfa_setup_flow.py @@ -28,25 +28,27 @@ DATA_SETUP_FLOW_MGR = "auth_mfa_setup_flow_manager" _LOGGER = logging.getLogger(__name__) -async def async_setup(hass): - """Init mfa setup flow manager.""" +class MfaFlowManager(data_entry_flow.FlowManager): + """Manage multi factor authentication flows.""" - async def _async_create_setup_flow(handler, context, data): + async def async_create_flow(self, handler_key, *, context, data): """Create a setup flow. handler is a mfa module.""" - mfa_module = hass.auth.get_auth_mfa_module(handler) + mfa_module = self.hass.auth.get_auth_mfa_module(handler_key) if mfa_module is None: - raise ValueError(f"Mfa module {handler} is not found") + raise ValueError(f"Mfa module {handler_key} is not found") user_id = data.pop("user_id") return await mfa_module.async_setup_flow(user_id) - async def _async_finish_setup_flow(flow, flow_result): - _LOGGER.debug("flow_result: %s", flow_result) - return flow_result + async def async_finish_flow(self, flow, result): + """Complete an mfs setup flow.""" + _LOGGER.debug("flow_result: %s", result) + return result - hass.data[DATA_SETUP_FLOW_MGR] = data_entry_flow.FlowManager( - hass, _async_create_setup_flow, _async_finish_setup_flow - ) + +async def async_setup(hass): + """Init mfa setup flow manager.""" + hass.data[DATA_SETUP_FLOW_MGR] = MfaFlowManager(hass) hass.components.websocket_api.async_register_command( WS_TYPE_SETUP_MFA, websocket_setup_mfa, SCHEMA_WS_SETUP_MFA diff --git a/homeassistant/components/config/config_entries.py b/homeassistant/components/config/config_entries.py index dbf0ee8f283..22df26cce4e 100644 --- a/homeassistant/components/config/config_entries.py +++ b/homeassistant/components/config/config_entries.py @@ -23,12 +23,8 @@ async def async_setup(hass): hass.http.register_view(ConfigManagerFlowResourceView(hass.config_entries.flow)) hass.http.register_view(ConfigManagerAvailableFlowView) - hass.http.register_view( - OptionManagerFlowIndexView(hass.config_entries.options.flow) - ) - hass.http.register_view( - OptionManagerFlowResourceView(hass.config_entries.options.flow) - ) + hass.http.register_view(OptionManagerFlowIndexView(hass.config_entries.options)) + hass.http.register_view(OptionManagerFlowResourceView(hass.config_entries.options)) hass.components.websocket_api.async_register_command(config_entries_progress) hass.components.websocket_api.async_register_command(system_options_list) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 942998767a1..d1b5c927a2b 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -399,6 +399,137 @@ class ConfigEntry: } +class ConfigEntriesFlowManager(data_entry_flow.FlowManager): + """Manage all the config entry flows that are in progress.""" + + def __init__( + self, hass: HomeAssistant, config_entries: "ConfigEntries", hass_config: dict + ): + """Initialize the config entry flow manager.""" + super().__init__(hass) + self.config_entries = config_entries + self._hass_config = hass_config + + async def async_finish_flow( + self, flow: data_entry_flow.FlowHandler, result: Dict[str, Any] + ) -> Dict[str, Any]: + """Finish a config flow and add an entry.""" + flow = cast(ConfigFlow, flow) + + # Remove notification if no other discovery config entries in progress + if not any( + ent["context"]["source"] in DISCOVERY_SOURCES + for ent in self.hass.config_entries.flow.async_progress() + if ent["flow_id"] != flow.flow_id + ): + self.hass.components.persistent_notification.async_dismiss( + DISCOVERY_NOTIFICATION_ID + ) + + if result["type"] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY: + return result + + # Check if config entry exists with unique ID. Unload it. + existing_entry = None + + if flow.unique_id is not None: + # Abort all flows in progress with same unique ID. + for progress_flow in self.async_progress(): + if ( + progress_flow["handler"] == flow.handler + and progress_flow["flow_id"] != flow.flow_id + and progress_flow["context"].get("unique_id") == flow.unique_id + ): + self.async_abort(progress_flow["flow_id"]) + + # Find existing entry. + for check_entry in self.config_entries.async_entries(result["handler"]): + if check_entry.unique_id == flow.unique_id: + existing_entry = check_entry + break + + # Unload the entry before setting up the new one. + # We will remove it only after the other one is set up, + # so that device customizations are not getting lost. + if ( + existing_entry is not None + and existing_entry.state not in UNRECOVERABLE_STATES + ): + await self.config_entries.async_unload(existing_entry.entry_id) + + entry = ConfigEntry( + version=result["version"], + domain=result["handler"], + title=result["title"], + data=result["data"], + options={}, + system_options={}, + source=flow.context["source"], + connection_class=flow.CONNECTION_CLASS, + unique_id=flow.unique_id, + ) + + await self.config_entries.async_add(entry) + + if existing_entry is not None: + await self.config_entries.async_remove(existing_entry.entry_id) + + result["result"] = entry + return result + + async def async_create_flow( + self, handler_key: Any, *, context: Optional[Dict] = None, data: Any = None + ) -> "ConfigFlow": + """Create a flow for specified handler. + + Handler key is the domain of the component that we want to set up. + """ + try: + integration = await loader.async_get_integration(self.hass, handler_key) + except loader.IntegrationNotFound: + _LOGGER.error("Cannot find integration %s", handler_key) + raise data_entry_flow.UnknownHandler + + # Make sure requirements and dependencies of component are resolved + await async_process_deps_reqs(self.hass, self._hass_config, integration) + + try: + integration.get_platform("config_flow") + except ImportError as err: + _LOGGER.error( + "Error occurred loading config flow for integration %s: %s", + handler_key, + err, + ) + raise data_entry_flow.UnknownHandler + + handler = HANDLERS.get(handler_key) + + if handler is None: + raise data_entry_flow.UnknownHandler + + if not context or "source" not in context: + raise KeyError("Context not set or doesn't have a source set") + + source = context["source"] + + # Create notification. + if source in DISCOVERY_SOURCES: + self.hass.bus.async_fire(EVENT_FLOW_DISCOVERED) + self.hass.components.persistent_notification.async_create( + title="New devices discovered", + message=( + "We have discovered new devices on your network. " + "[Check it out](/config/integrations)" + ), + notification_id=DISCOVERY_NOTIFICATION_ID, + ) + + flow = cast(ConfigFlow, handler()) + flow.init_step = source + return flow + + class ConfigEntries: """Manage the configuration entries. @@ -408,9 +539,7 @@ class ConfigEntries: def __init__(self, hass: HomeAssistant, hass_config: dict) -> None: """Initialize the entry manager.""" self.hass = hass - self.flow = data_entry_flow.FlowManager( - hass, self._async_create_flow, self._async_finish_flow - ) + self.flow = ConfigEntriesFlowManager(hass, self, hass_config) self.options = OptionsFlowManager(hass) self._hass_config = hass_config self._entries: List[ConfigEntry] = [] @@ -445,6 +574,12 @@ class ConfigEntries: return list(self._entries) return [entry for entry in self._entries if entry.domain == domain] + async def async_add(self, entry: ConfigEntry) -> None: + """Add and setup an entry.""" + self._entries.append(entry) + await self.async_setup(entry.entry_id) + self._async_schedule_save() + async def async_remove(self, entry_id: str) -> Dict[str, Any]: """Remove an entry.""" entry = self.async_get_entry(entry_id) @@ -630,123 +765,6 @@ class ConfigEntries: return await entry.async_unload(self.hass, integration=integration) - async def _async_finish_flow( - self, flow: "ConfigFlow", result: Dict[str, Any] - ) -> Dict[str, Any]: - """Finish a config flow and add an entry.""" - # Remove notification if no other discovery config entries in progress - if not any( - ent["context"]["source"] in DISCOVERY_SOURCES - for ent in self.hass.config_entries.flow.async_progress() - if ent["flow_id"] != flow.flow_id - ): - self.hass.components.persistent_notification.async_dismiss( - DISCOVERY_NOTIFICATION_ID - ) - - if result["type"] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY: - return result - - # Check if config entry exists with unique ID. Unload it. - existing_entry = None - - if flow.unique_id is not None: - # Abort all flows in progress with same unique ID. - for progress_flow in self.flow.async_progress(): - if ( - progress_flow["handler"] == flow.handler - and progress_flow["flow_id"] != flow.flow_id - and progress_flow["context"].get("unique_id") == flow.unique_id - ): - self.flow.async_abort(progress_flow["flow_id"]) - - # Find existing entry. - for check_entry in self.async_entries(result["handler"]): - if check_entry.unique_id == flow.unique_id: - existing_entry = check_entry - break - - # Unload the entry before setting up the new one. - # We will remove it only after the other one is set up, - # so that device customizations are not getting lost. - if ( - existing_entry is not None - and existing_entry.state not in UNRECOVERABLE_STATES - ): - await self.async_unload(existing_entry.entry_id) - - entry = ConfigEntry( - version=result["version"], - domain=result["handler"], - title=result["title"], - data=result["data"], - options={}, - system_options={}, - source=flow.context["source"], - connection_class=flow.CONNECTION_CLASS, - unique_id=flow.unique_id, - ) - self._entries.append(entry) - - await self.async_setup(entry.entry_id) - - if existing_entry is not None: - await self.async_remove(existing_entry.entry_id) - - self._async_schedule_save() - - result["result"] = entry - return result - - async def _async_create_flow( - self, handler_key: str, *, context: Dict[str, Any], data: Dict[str, Any] - ) -> "ConfigFlow": - """Create a flow for specified handler. - - Handler key is the domain of the component that we want to set up. - """ - try: - integration = await loader.async_get_integration(self.hass, handler_key) - except loader.IntegrationNotFound: - _LOGGER.error("Cannot find integration %s", handler_key) - raise data_entry_flow.UnknownHandler - - # Make sure requirements and dependencies of component are resolved - await async_process_deps_reqs(self.hass, self._hass_config, integration) - - try: - integration.get_platform("config_flow") - except ImportError as err: - _LOGGER.error( - "Error occurred loading config flow for integration %s: %s", - handler_key, - err, - ) - raise data_entry_flow.UnknownHandler - - handler = HANDLERS.get(handler_key) - - if handler is None: - raise data_entry_flow.UnknownHandler - - source = context["source"] - - # Create notification. - if source in DISCOVERY_SOURCES: - self.hass.bus.async_fire(EVENT_FLOW_DISCOVERED) - self.hass.components.persistent_notification.async_create( - title="New devices discovered", - message=( - "We have discovered new devices on your network. " - "[Check it out](/config/integrations)" - ), - notification_id=DISCOVERY_NOTIFICATION_ID, - ) - - flow = cast(ConfigFlow, handler()) - flow.init_step = source - return flow - def _async_schedule_save(self) -> None: """Save the entity registry to a file.""" self._store.async_delay_save(self._data_to_save, SAVE_DELAY) @@ -854,26 +872,23 @@ class ConfigFlow(data_entry_flow.FlowHandler): return self.async_abort(reason="not_implemented") -class OptionsFlowManager: +class OptionsFlowManager(data_entry_flow.FlowManager): """Flow to set options for a configuration entry.""" - def __init__(self, hass: HomeAssistant) -> None: - """Initialize the options manager.""" - self.hass = hass - self.flow = data_entry_flow.FlowManager( - hass, self._async_create_flow, self._async_finish_flow - ) - - async def _async_create_flow( - self, entry_id: str, *, context: Dict[str, Any], data: Dict[str, Any] - ) -> Optional["OptionsFlow"]: + async def async_create_flow( + self, + handler_key: Any, + *, + context: Optional[Dict[str, Any]] = None, + data: Optional[Dict[str, Any]] = None, + ) -> "OptionsFlow": """Create an options flow for a config entry. Entry_id and flow.handler is the same thing to map entry with flow. """ - entry = self.hass.config_entries.async_get_entry(entry_id) + entry = self.hass.config_entries.async_get_entry(handler_key) if entry is None: - return None + raise UnknownEntry(handler_key) if entry.domain not in HANDLERS: raise data_entry_flow.UnknownHandler @@ -881,16 +896,18 @@ class OptionsFlowManager: flow = cast(OptionsFlow, HANDLERS[entry.domain].async_get_options_flow(entry)) return flow - async def _async_finish_flow( - self, flow: "OptionsFlow", result: Dict[str, Any] - ) -> Optional[Dict[str, Any]]: + async def async_finish_flow( + self, flow: data_entry_flow.FlowHandler, result: Dict[str, Any] + ) -> Dict[str, Any]: """Finish an options flow and update options for configuration entry. Flow.handler and entry_id is the same thing to map flow with entry. """ + flow = cast(OptionsFlow, flow) + entry = self.hass.config_entries.async_get_entry(flow.handler) if entry is None: - return None + raise UnknownEntry(flow.handler) self.hass.config_entries.async_update_entry(entry, options=result["data"]) result["result"] = True diff --git a/homeassistant/data_entry_flow.py b/homeassistant/data_entry_flow.py index 7c2b4ab6ddc..6a9f5b1dc5a 100644 --- a/homeassistant/data_entry_flow.py +++ b/homeassistant/data_entry_flow.py @@ -1,6 +1,7 @@ """Classes to help gather user submissions.""" +import abc import logging -from typing import Any, Callable, Dict, List, Optional, cast +from typing import Any, Dict, List, Optional, cast import uuid import voluptuous as vol @@ -46,20 +47,34 @@ class AbortFlow(FlowError): self.description_placeholders = description_placeholders -class FlowManager: +class FlowManager(abc.ABC): """Manage all the flows that are in progress.""" - def __init__( - self, - hass: HomeAssistant, - async_create_flow: Callable, - async_finish_flow: Callable, - ) -> None: + def __init__(self, hass: HomeAssistant,) -> None: """Initialize the flow manager.""" self.hass = hass self._progress: Dict[str, Any] = {} - self._async_create_flow = async_create_flow - self._async_finish_flow = async_finish_flow + + @abc.abstractmethod + async def async_create_flow( + self, + handler_key: Any, + *, + context: Optional[Dict[str, Any]] = None, + data: Optional[Dict[str, Any]] = None, + ) -> "FlowHandler": + """Create a flow for specified handler. + + Handler key is the domain of the component that we want to set up. + """ + pass + + @abc.abstractmethod + async def async_finish_flow( + self, flow: "FlowHandler", result: Dict[str, Any] + ) -> Dict[str, Any]: + """Finish a config flow and add an entry.""" + pass @callback def async_progress(self) -> List[Dict]: @@ -75,7 +90,9 @@ class FlowManager: """Start a configuration flow.""" if context is None: context = {} - flow = await self._async_create_flow(handler, context=context, data=data) + flow = await self.async_create_flow(handler, context=context, data=data) + if not flow: + raise UnknownFlow("Flow was not created") flow.hass = self.hass flow.handler = handler flow.flow_id = uuid.uuid4().hex @@ -168,7 +185,7 @@ class FlowManager: return result # We pass a copy of the result because we're mutating our version - result = await self._async_finish_flow(flow, dict(result)) + result = await self.async_finish_flow(flow, dict(result)) # _async_finish_flow may change result type, check it again if result["type"] == RESULT_TYPE_FORM: diff --git a/tests/components/deconz/test_config_flow.py b/tests/components/deconz/test_config_flow.py index f8fe42d10d8..3fef85611c8 100644 --- a/tests/components/deconz/test_config_flow.py +++ b/tests/components/deconz/test_config_flow.py @@ -436,7 +436,7 @@ async def test_option_flow(hass): entry = MockConfigEntry(domain=config_flow.DOMAIN, data={}, options=None) hass.config_entries._entries.append(entry) - flow = await hass.config_entries.options._async_create_flow( + flow = await hass.config_entries.options.async_create_flow( entry.entry_id, context={"source": "test"}, data=None ) diff --git a/tests/components/opentherm_gw/test_config_flow.py b/tests/components/opentherm_gw/test_config_flow.py index 26048543a22..0adcdb188d0 100644 --- a/tests/components/opentherm_gw/test_config_flow.py +++ b/tests/components/opentherm_gw/test_config_flow.py @@ -182,13 +182,13 @@ async def test_options_form(hass): ) entry.add_to_hass(hass) - result = await hass.config_entries.options.flow.async_init( + result = await hass.config_entries.options.async_init( entry.entry_id, context={"source": "test"}, data=None ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "init" - result = await hass.config_entries.options.flow.async_configure( + result = await hass.config_entries.options.async_configure( result["flow_id"], user_input={CONF_FLOOR_TEMP: True, CONF_PRECISION: PRECISION_HALVES}, ) @@ -197,11 +197,11 @@ async def test_options_form(hass): assert result["data"][CONF_PRECISION] == PRECISION_HALVES assert result["data"][CONF_FLOOR_TEMP] is True - result = await hass.config_entries.options.flow.async_init( + result = await hass.config_entries.options.async_init( entry.entry_id, context={"source": "test"}, data=None ) - result = await hass.config_entries.options.flow.async_configure( + result = await hass.config_entries.options.async_configure( result["flow_id"], user_input={CONF_PRECISION: 0} ) diff --git a/tests/components/plex/test_config_flow.py b/tests/components/plex/test_config_flow.py index 0fb1f850809..8f9342c4f72 100644 --- a/tests/components/plex/test_config_flow.py +++ b/tests/components/plex/test_config_flow.py @@ -462,13 +462,13 @@ async def test_option_flow(hass): entry = MockConfigEntry(domain=config_flow.DOMAIN, data={}, options=DEFAULT_OPTIONS) entry.add_to_hass(hass) - result = await hass.config_entries.options.flow.async_init( + result = await hass.config_entries.options.async_init( entry.entry_id, context={"source": "test"}, data=None ) assert result["type"] == "form" assert result["step_id"] == "plex_mp_settings" - result = await hass.config_entries.options.flow.async_configure( + result = await hass.config_entries.options.async_configure( result["flow_id"], user_input={ config_flow.CONF_USE_EPISODE_ART: True, diff --git a/tests/components/tesla/test_config_flow.py b/tests/components/tesla/test_config_flow.py index b6eeff54a50..7b7e822ce58 100644 --- a/tests/components/tesla/test_config_flow.py +++ b/tests/components/tesla/test_config_flow.py @@ -131,12 +131,12 @@ async def test_option_flow(hass): entry = MockConfigEntry(domain=DOMAIN, data={}, options=None) entry.add_to_hass(hass) - result = await hass.config_entries.options.flow.async_init(entry.entry_id) + result = await hass.config_entries.options.async_init(entry.entry_id) assert result["type"] == "form" assert result["step_id"] == "init" - result = await hass.config_entries.options.flow.async_configure( + result = await hass.config_entries.options.async_configure( result["flow_id"], user_input={CONF_SCAN_INTERVAL: 350} ) assert result["type"] == "create_entry" @@ -148,12 +148,12 @@ async def test_option_flow_input_floor(hass): entry = MockConfigEntry(domain=DOMAIN, data={}, options=None) entry.add_to_hass(hass) - result = await hass.config_entries.options.flow.async_init(entry.entry_id) + result = await hass.config_entries.options.async_init(entry.entry_id) assert result["type"] == "form" assert result["step_id"] == "init" - result = await hass.config_entries.options.flow.async_configure( + result = await hass.config_entries.options.async_configure( result["flow_id"], user_input={CONF_SCAN_INTERVAL: 1} ) assert result["type"] == "create_entry" diff --git a/tests/components/unifi/test_config_flow.py b/tests/components/unifi/test_config_flow.py index 1b973aee9a5..cc8896d55ce 100644 --- a/tests/components/unifi/test_config_flow.py +++ b/tests/components/unifi/test_config_flow.py @@ -231,7 +231,7 @@ async def test_option_flow(hass): entry = MockConfigEntry(domain=config_flow.DOMAIN, data={}, options=None) hass.config_entries._entries.append(entry) - flow = await hass.config_entries.options._async_create_flow( + flow = await hass.config_entries.options.async_create_flow( entry.entry_id, context={"source": "test"}, data=None ) diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 5b694b2de87..c3a87bcf3a0 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -692,13 +692,13 @@ async def test_entry_options(hass, manager): return OptionsFlowHandler() config_entries.HANDLERS["test"] = TestFlow() - flow = await manager.options._async_create_flow( + flow = await manager.options.async_create_flow( entry.entry_id, context={"source": "test"}, data=None ) flow.handler = entry.entry_id # Used to keep reference to config entry - await manager.options._async_finish_flow(flow, {"data": {"second": True}}) + await manager.options.async_finish_flow(flow, {"data": {"second": True}}) assert entry.data == {"first": True} diff --git a/tests/test_data_entry_flow.py b/tests/test_data_entry_flow.py index a6bdd2b5cb6..664304c9ef6 100644 --- a/tests/test_data_entry_flow.py +++ b/tests/test_data_entry_flow.py @@ -14,27 +14,32 @@ def manager(): handlers = Registry() entries = [] - async def async_create_flow(handler_name, *, context, data): - handler = handlers.get(handler_name) + class FlowManager(data_entry_flow.FlowManager): + """Test flow manager.""" - if handler is None: - raise data_entry_flow.UnknownHandler + async def async_create_flow(self, handler_key, *, context, data): + """Test create flow.""" + handler = handlers.get(handler_key) - flow = handler() - flow.init_step = context.get("init_step", "init") - flow.source = context.get("source") - return flow + if handler is None: + raise data_entry_flow.UnknownHandler - async def async_add_entry(flow, result): - if result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY: - result["source"] = flow.context.get("source") - entries.append(result) - return result + flow = handler() + flow.init_step = context.get("init_step", "init") + flow.source = context.get("source") + return flow - manager = data_entry_flow.FlowManager(None, async_create_flow, async_add_entry) - manager.mock_created_entries = entries - manager.mock_reg_handler = handlers.register - return manager + async def async_finish_flow(self, flow, result): + """Test finish flow.""" + if result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY: + result["source"] = flow.context.get("source") + entries.append(result) + return result + + mgr = FlowManager(None) + mgr.mock_created_entries = entries + mgr.mock_reg_handler = handlers.register + return mgr async def test_configure_reuses_handler_instance(manager): @@ -194,22 +199,23 @@ async def test_finish_callback_change_result_type(hass): step_id="init", data_schema=vol.Schema({"count": int}) ) - async def async_create_flow(handler_name, *, context, data): - """Create a test flow.""" - return TestFlow() + class FlowManager(data_entry_flow.FlowManager): + async def async_create_flow(self, handler_name, *, context, data): + """Create a test flow.""" + return TestFlow() - async def async_finish_flow(flow, result): - """Redirect to init form if count <= 1.""" - if result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY: - if result["data"] is None or result["data"].get("count", 0) <= 1: - return flow.async_show_form( - step_id="init", data_schema=vol.Schema({"count": int}) - ) - else: - result["result"] = result["data"]["count"] - return result + async def async_finish_flow(self, flow, result): + """Redirect to init form if count <= 1.""" + if result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY: + if result["data"] is None or result["data"].get("count", 0) <= 1: + return flow.async_show_form( + step_id="init", data_schema=vol.Schema({"count": int}) + ) + else: + result["result"] = result["data"]["count"] + return result - manager = data_entry_flow.FlowManager(hass, async_create_flow, async_finish_flow) + manager = FlowManager(hass) result = await manager.async_init("test") assert result["type"] == data_entry_flow.RESULT_TYPE_FORM From 3f33fc6122d6a38aabcc80cb137e7ef3ae3284d7 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Fri, 3 Jan 2020 11:54:19 +0100 Subject: [PATCH 520/677] convert to integer if rounding precision is zero (#30226) Convert values to integer if rounding precision is zero. With that a value which is an integer before filtering can be configured to stay integer when using precision = 0. This also aligns behavior of filters to how rounding behaves in tempaltes (homeassistant/helpers/template.py, function forgiving_round). --- homeassistant/components/filter/sensor.py | 3 ++- tests/components/filter/test_sensor.py | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/filter/sensor.py b/homeassistant/components/filter/sensor.py index eeb0d32f51c..baa4f90af3f 100644 --- a/homeassistant/components/filter/sensor.py +++ b/homeassistant/components/filter/sensor.py @@ -324,7 +324,8 @@ class FilterState: def set_precision(self, precision): """Set precision of Number based states.""" if isinstance(self.state, Number): - self.state = round(float(self.state), precision) + value = round(float(self.state), precision) + self.state = int(value) if precision == 0 else value def __str__(self): """Return state as the string representation of FilterState.""" diff --git a/tests/components/filter/test_sensor.py b/tests/components/filter/test_sensor.py index 9ae4245ed70..d46fa4eab68 100644 --- a/tests/components/filter/test_sensor.py +++ b/tests/components/filter/test_sensor.py @@ -208,6 +208,13 @@ class TestFilterSensor(unittest.TestCase): filtered = filt.filter_state(state) assert 21 == filtered.state + def test_precision_zero(self): + """Test if precision of zero returns an integer.""" + filt = LowPassFilter(window_size=10, precision=0, entity=None, time_constant=10) + for state in self.values: + filtered = filt.filter_state(state) + assert isinstance(filtered.state, int) + def test_lowpass(self): """Test if lowpass filter works.""" filt = LowPassFilter(window_size=10, precision=2, entity=None, time_constant=10) From 5580ee3fa184af736e85bd831b22119eac6e54e4 Mon Sep 17 00:00:00 2001 From: Ian Duffy <1243435+imduffy15@users.noreply.github.com> Date: Fri, 3 Jan 2020 11:08:40 +0000 Subject: [PATCH 521/677] Don't allow badly formed upnp devices to kill auto discovery (#30342) Prevent the following from occurring: ``` 2019-12-31 22:38:41 ERROR (MainThread) [homeassistant.core] Error doing job: Task exception was never retrieved Traceback (most recent call last): File "/usr/src/homeassistant/homeassistant/data_entry_flow.py", line 85, in async_init return await self._async_handle_step(flow, flow.init_step, data) File "/usr/src/homeassistant/homeassistant/data_entry_flow.py", line 145, in _async_handle_step result: Dict = await getattr(flow, method)(user_input) File "/usr/src/homeassistant/homeassistant/components/deconz/config_flow.py", line 182, in async_step_ssdp if discovery_info[ssdp.ATTR_UPNP_MANUFACTURER_URL] != DECONZ_MANUFACTURERURL: KeyError: 'manufacturerURL' 2019-12-31 22:38:41 ERROR (MainThread) [homeassistant.core] Error doing job: Task exception was never retrieved Traceback (most recent call last): File "/usr/src/homeassistant/homeassistant/data_entry_flow.py", line 85, in async_init return await self._async_handle_step(flow, flow.init_step, data) File "/usr/src/homeassistant/homeassistant/data_entry_flow.py", line 145, in _async_handle_step result: Dict = await getattr(flow, method)(user_input) File "/usr/src/homeassistant/homeassistant/components/deconz/config_flow.py", line 182, in async_step_ssdp if discovery_info[ssdp.ATTR_UPNP_MANUFACTURER_URL] != DECONZ_MANUFACTURERURL: KeyError: 'manufacturerURL' ``` --- homeassistant/components/deconz/config_flow.py | 5 ++++- homeassistant/components/hue/config_flow.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/deconz/config_flow.py b/homeassistant/components/deconz/config_flow.py index 0cec6add28c..95b200bba13 100644 --- a/homeassistant/components/deconz/config_flow.py +++ b/homeassistant/components/deconz/config_flow.py @@ -175,7 +175,10 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_ssdp(self, discovery_info): """Handle a discovered deCONZ bridge.""" - if discovery_info[ssdp.ATTR_UPNP_MANUFACTURER_URL] != DECONZ_MANUFACTURERURL: + if ( + discovery_info.get(ssdp.ATTR_UPNP_MANUFACTURER_URL) + != DECONZ_MANUFACTURERURL + ): return self.async_abort(reason="not_deconz_bridge") self.bridge_id = normalize_bridge_id(discovery_info[ssdp.ATTR_UPNP_SERIAL]) diff --git a/homeassistant/components/hue/config_flow.py b/homeassistant/components/hue/config_flow.py index 60000a68fb7..cb3d63eec20 100644 --- a/homeassistant/components/hue/config_flow.py +++ b/homeassistant/components/hue/config_flow.py @@ -148,7 +148,7 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): host is already configured and delegate to the import step if not. """ # Filter out non-Hue bridges #1 - if discovery_info[ssdp.ATTR_UPNP_MANUFACTURER_URL] != HUE_MANUFACTURERURL: + if discovery_info.get(ssdp.ATTR_UPNP_MANUFACTURER_URL) != HUE_MANUFACTURERURL: return self.async_abort(reason="not_hue_bridge") # Filter out non-Hue bridges #2 From 0944d022628fea532cc1f6dc0f2ad8d6306e22ec Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Fri, 3 Jan 2020 12:12:29 +0100 Subject: [PATCH 522/677] Mark API key as deprecated (#30402) Service account should be used instead --- homeassistant/components/google_assistant/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/google_assistant/__init__.py b/homeassistant/components/google_assistant/__init__.py index 107003db583..2d848101def 100644 --- a/homeassistant/components/google_assistant/__init__.py +++ b/homeassistant/components/google_assistant/__init__.py @@ -65,6 +65,7 @@ def _check_report_state(data): GOOGLE_ASSISTANT_SCHEMA = vol.All( cv.deprecated(CONF_ALLOW_UNLOCK, invalidation_version="0.95"), + cv.deprecated(CONF_API_KEY, invalidation_version="0.105"), vol.Schema( { vol.Required(CONF_PROJECT_ID): cv.string, From b57da2f86277ce2eb9fff7da955addc5cf1fce0d Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 3 Jan 2020 13:27:14 +0100 Subject: [PATCH 523/677] Axis - Improve tests based on feedback (#30430) * Use MockConfigEntry, add_to_hass and hass.config_entries.async_setup * Mock method instead of handling paths --- tests/components/axis/test_device.py | 45 ++++++++++++---------------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/tests/components/axis/test_device.py b/tests/components/axis/test_device.py index 58ebed60681..7bcb1350fe8 100644 --- a/tests/components/axis/test_device.py +++ b/tests/components/axis/test_device.py @@ -8,6 +8,8 @@ import pytest from homeassistant import config_entries from homeassistant.components import axis +from tests.common import MockConfigEntry + MAC = "00408C12345" MODEL = "model" NAME = "name" @@ -55,7 +57,7 @@ root.Properties.Image.Format=jpeg,mjpeg,h264 root.Properties.Image.NbrOfViews=2 root.Properties.Image.Resolution=1920x1080,1280x960,1280x720,1024x768,1024x576,800x600,640x480,640x360,352x240,320x240 root.Properties.Image.Rotation=0,180 -root.Properties.System.SerialNumber=ACCC12345678 +root.Properties.System.SerialNumber=00408C12345 """ @@ -68,41 +70,34 @@ async def setup_axis_integration( properties=DEFAULT_PROPERTIES, ): """Create the Axis device.""" - config_entry = config_entries.ConfigEntry( - version=1, + config_entry = MockConfigEntry( domain=axis.DOMAIN, - title="Mock Title", data=deepcopy(config), - source="test", connection_class=config_entries.CONN_CLASS_LOCAL_PUSH, - system_options={}, options=deepcopy(options), entry_id="1", ) + config_entry.add_to_hass(hass) - def mock_request(self, method, path, json=None): - if method == "get": - if path == "/axis-cgi/param.cgi?action=list&group=root.Brand": - return brand - if path in [ - "/axis-cgi/param.cgi?action=list&group=root.Input", - "/axis-cgi/param.cgi?action=list&group=root.IOPort", - "/axis-cgi/param.cgi?action=list&group=root.Output", - ]: - return ports - if path == "/axis-cgi/param.cgi?action=list&group=root.Properties": - return properties + def mock_update_brand(self): + self.process_raw(brand) - return None + def mock_update_ports(self): + self.process_raw(ports) - with patch("axis.vapix.Vapix.request", new=mock_request), patch( + def mock_update_properties(self): + self.process_raw(properties) + + with patch("axis.param_cgi.Brand.update_brand", new=mock_update_brand), patch( + "axis.param_cgi.Ports.update_ports", new=mock_update_ports + ), patch( + "axis.param_cgi.Properties.update_properties", new=mock_update_properties + ), patch( "axis.AxisDevice.start", return_value=True ): - await axis.async_setup_entry(hass, config_entry) + await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - hass.config_entries._entries.append(config_entry) - return hass.data[axis.DOMAIN].get(config[axis.CONF_MAC]) @@ -163,9 +158,7 @@ async def test_device_reset(hass): async def test_device_not_accessible(hass): """Failed setup schedules a retry of setup.""" - with patch.object( - axis.device, "get_device", side_effect=axis.errors.CannotConnect - ), pytest.raises(axis.device.ConfigEntryNotReady): + with patch.object(axis.device, "get_device", side_effect=axis.errors.CannotConnect): await setup_axis_integration(hass) assert hass.data[axis.DOMAIN] == {} From 859935e8bc1d421d6540f5dc83a032f943400e87 Mon Sep 17 00:00:00 2001 From: David K <142583+neffs@users.noreply.github.com> Date: Fri, 3 Jan 2020 14:19:03 +0100 Subject: [PATCH 524/677] Update HomeKit position state characteristic for covers (#27867) * HomeKit: update position state characteristic for covers position state is a mandatory characteristic for HK window coverings * Test position state characteristic --- .../components/homekit/type_covers.py | 17 ++++++++++- tests/components/homekit/test_type_covers.py | 28 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/homekit/type_covers.py b/homeassistant/components/homekit/type_covers.py index 3a33207e70e..8ad095faa13 100644 --- a/homeassistant/components/homekit/type_covers.py +++ b/homeassistant/components/homekit/type_covers.py @@ -18,6 +18,8 @@ from homeassistant.const import ( SERVICE_STOP_COVER, STATE_CLOSED, STATE_OPEN, + STATE_OPENING, + STATE_CLOSING, ) from . import TYPES @@ -101,6 +103,9 @@ class WindowCovering(HomeAccessory): self.char_target_position = serv_cover.configure_char( CHAR_TARGET_POSITION, value=0, setter_callback=self.move_cover ) + self.char_position_state = serv_cover.configure_char( + CHAR_POSITION_STATE, value=2 + ) @debounce def move_cover(self, value): @@ -122,6 +127,12 @@ class WindowCovering(HomeAccessory): ): self.char_target_position.set_value(current_position) self._homekit_target = None + if new_state.state == STATE_OPENING: + self.char_position_state.set_value(1) + elif new_state.state == STATE_CLOSING: + self.char_position_state.set_value(0) + else: + self.char_position_state.set_value(2) @TYPES.register("WindowCoveringBasic") @@ -175,7 +186,6 @@ class WindowCoveringBasic(HomeAccessory): # Snap the current/target position to the expected final position. self.char_current_position.set_value(position) self.char_target_position.set_value(position) - self.char_position_state.set_value(2) def update_state(self, new_state): """Update cover position after state changed.""" @@ -184,4 +194,9 @@ class WindowCoveringBasic(HomeAccessory): if hk_position is not None: self.char_current_position.set_value(hk_position) self.char_target_position.set_value(hk_position) + if new_state.state == STATE_OPENING: + self.char_position_state.set_value(1) + elif new_state.state == STATE_CLOSING: + self.char_position_state.set_value(0) + else: self.char_position_state.set_value(2) diff --git a/tests/components/homekit/test_type_covers.py b/tests/components/homekit/test_type_covers.py index d3cf24971ff..7ff65619bb6 100644 --- a/tests/components/homekit/test_type_covers.py +++ b/tests/components/homekit/test_type_covers.py @@ -15,6 +15,8 @@ from homeassistant.const import ( ATTR_SUPPORTED_FEATURES, STATE_CLOSED, STATE_OPEN, + STATE_OPENING, + STATE_CLOSING, STATE_UNAVAILABLE, STATE_UNKNOWN, ) @@ -138,11 +140,25 @@ async def test_window_set_cover_position(hass, hk_driver, cls, events): await hass.async_block_till_done() assert acc.char_current_position.value == 0 assert acc.char_target_position.value == 0 + assert acc.char_position_state.value == 2 + + hass.states.async_set(entity_id, STATE_OPENING, {ATTR_CURRENT_POSITION: 60}) + await hass.async_block_till_done() + assert acc.char_current_position.value == 60 + assert acc.char_target_position.value == 60 + assert acc.char_position_state.value == 1 + + hass.states.async_set(entity_id, STATE_CLOSING, {ATTR_CURRENT_POSITION: 50}) + await hass.async_block_till_done() + assert acc.char_current_position.value == 50 + assert acc.char_target_position.value == 50 + assert acc.char_position_state.value == 0 hass.states.async_set(entity_id, STATE_OPEN, {ATTR_CURRENT_POSITION: 50}) await hass.async_block_till_done() assert acc.char_current_position.value == 50 assert acc.char_target_position.value == 50 + assert acc.char_position_state.value == 2 # Set from HomeKit call_set_cover_position = async_mock_service(hass, DOMAIN, "set_cover_position") @@ -189,12 +205,24 @@ async def test_window_open_close(hass, hk_driver, cls, events): assert acc.char_target_position.value == 0 assert acc.char_position_state.value == 2 + hass.states.async_set(entity_id, STATE_OPENING) + await hass.async_block_till_done() + assert acc.char_current_position.value == 0 + assert acc.char_target_position.value == 0 + assert acc.char_position_state.value == 1 + hass.states.async_set(entity_id, STATE_OPEN) await hass.async_block_till_done() assert acc.char_current_position.value == 100 assert acc.char_target_position.value == 100 assert acc.char_position_state.value == 2 + hass.states.async_set(entity_id, STATE_CLOSING) + await hass.async_block_till_done() + assert acc.char_current_position.value == 100 + assert acc.char_target_position.value == 100 + assert acc.char_position_state.value == 0 + hass.states.async_set(entity_id, STATE_CLOSED) await hass.async_block_till_done() assert acc.char_current_position.value == 0 From 5ad209c6fd8adb1cf4df640e361dcb7481753b25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio?= Date: Fri, 3 Jan 2020 15:22:14 +0200 Subject: [PATCH 525/677] Handle telegram event commands with args (#30254) * Handle telegram event commands with args * Parse message regargless of command * Lint * Use multiple assignment --- homeassistant/components/telegram_bot/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/telegram_bot/__init__.py b/homeassistant/components/telegram_bot/__init__.py index fc37121f3f9..9b56201f8c7 100644 --- a/homeassistant/components/telegram_bot/__init__.py +++ b/homeassistant/components/telegram_bot/__init__.py @@ -782,7 +782,13 @@ class BaseTelegramBotEntity: if event_data is None: return message_ok - event_data[ATTR_DATA] = data[ATTR_DATA] + query_data = event_data[ATTR_DATA] = data[ATTR_DATA] + + if query_data[0] == "/": + pieces = query_data.split(" ") + event_data[ATTR_COMMAND] = pieces[0] + event_data[ATTR_ARGS] = pieces[1:] + event_data[ATTR_MSG] = data[ATTR_MSG] event_data[ATTR_CHAT_INSTANCE] = data[ATTR_CHAT_INSTANCE] event_data[ATTR_MSGID] = data[ATTR_MSGID] From fa4fa304617153cb7ec320662c07a7555575ff63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Fri, 3 Jan 2020 15:47:06 +0200 Subject: [PATCH 526/677] Various string cleanups (#30435) * Remove some unnecessary string concatenations * Replace some simple str.formats with f-strings * Replace some string concatenations with f-strings --- homeassistant/__main__.py | 14 ++--- homeassistant/components/apns/notify.py | 2 +- .../components/braviatv/media_player.py | 2 +- homeassistant/components/cloud/__init__.py | 2 +- homeassistant/components/darksky/sensor.py | 2 +- homeassistant/components/ebusd/__init__.py | 2 +- .../components/ecoal_boiler/__init__.py | 2 +- .../components/ecoal_boiler/switch.py | 2 +- homeassistant/components/egardia/__init__.py | 2 +- homeassistant/components/fibaro/__init__.py | 2 +- .../components/hangouts/hangouts_bot.py | 8 +-- homeassistant/components/hook/switch.py | 6 +- .../huawei_router/device_tracker.py | 2 +- homeassistant/components/icloud/__init__.py | 4 +- homeassistant/components/insteon/__init__.py | 2 +- .../components/itunes/media_player.py | 12 ++-- homeassistant/components/kodi/media_player.py | 2 +- .../components/konnected/__init__.py | 2 +- .../components/life360/device_tracker.py | 2 +- homeassistant/components/lifx/light.py | 2 +- .../components/netatmo/binary_sensor.py | 4 +- homeassistant/components/netatmo/camera.py | 4 +- .../components/netgear/device_tracker.py | 4 +- .../components/octoprint/__init__.py | 12 ++-- homeassistant/components/onewire/sensor.py | 2 +- homeassistant/components/plant/__init__.py | 18 +++--- homeassistant/components/plex/media_player.py | 2 +- .../components/raspihats/__init__.py | 2 +- homeassistant/components/raspihats/switch.py | 6 +- .../components/samsungtv/media_player.py | 2 +- homeassistant/components/sensibo/climate.py | 2 +- .../components/seventeentrack/sensor.py | 2 +- .../components/sky_hub/device_tracker.py | 7 +-- homeassistant/components/smappee/__init__.py | 2 +- .../components/smartthings/__init__.py | 2 +- .../components/smartthings/smartapp.py | 2 +- .../components/soundtouch/media_player.py | 2 +- homeassistant/components/startca/sensor.py | 2 +- .../components/tank_utility/sensor.py | 2 +- homeassistant/components/temper/sensor.py | 2 +- .../components/wunderground/sensor.py | 8 +-- homeassistant/components/x10/light.py | 10 ++-- homeassistant/components/yeelight/light.py | 2 +- homeassistant/components/zha/api.py | 2 +- .../components/zha/core/channels/__init__.py | 4 +- homeassistant/components/zha/core/device.py | 2 +- homeassistant/components/zha/entity.py | 2 +- .../components/zhong_hong/climate.py | 4 +- homeassistant/components/zwave/cover.py | 2 +- homeassistant/config.py | 4 +- homeassistant/const.py | 4 +- homeassistant/core.py | 16 ++---- homeassistant/data_entry_flow.py | 4 +- homeassistant/helpers/__init__.py | 2 +- homeassistant/helpers/check_config.py | 4 +- homeassistant/helpers/config_validation.py | 42 +++++++------- homeassistant/helpers/discovery.py | 4 +- homeassistant/helpers/entity.py | 2 +- homeassistant/helpers/entity_platform.py | 12 +--- homeassistant/helpers/entity_registry.py | 5 +- homeassistant/helpers/icon.py | 2 +- homeassistant/helpers/intent.py | 2 +- homeassistant/helpers/script.py | 6 +- homeassistant/helpers/service.py | 2 +- homeassistant/helpers/template.py | 4 +- homeassistant/helpers/translation.py | 2 +- homeassistant/scripts/__init__.py | 2 +- homeassistant/scripts/check_config.py | 2 +- homeassistant/scripts/macos/__init__.py | 6 +- homeassistant/setup.py | 4 +- homeassistant/util/dt.py | 4 +- homeassistant/util/ruamel_yaml.py | 2 +- homeassistant/util/unit_system.py | 8 +-- script/gen_requirements_all.py | 6 +- script/hassfest/dependencies.py | 5 +- script/hassfest/model.py | 4 +- script/inspect_schemas.py | 5 +- script/lazytox.py | 5 +- tests/common.py | 55 ++++++------------- tests/components/hddtemp/test_sensor.py | 2 +- tests/components/light/test_init.py | 2 +- tests/components/tradfri/test_light.py | 10 ++-- tests/components/uk_transport/test_sensor.py | 4 +- .../unifi_direct/test_device_tracker.py | 4 +- .../components/vacuum/test_device_trigger.py | 8 +-- tests/components/webhook/test_init.py | 14 ++--- tests/components/withings/common.py | 9 ++- tests/components/xiaomi_miio/test_vacuum.py | 4 +- tests/components/yessssms/test_notify.py | 4 +- tests/components/yweather/test_sensor.py | 2 +- tests/components/zeroconf/test_init.py | 4 +- tests/components/zha/test_sensor.py | 6 +- tests/components/zwave/test_init.py | 18 +++--- tests/conftest.py | 7 +-- tests/helpers/test_config_entry_flow.py | 4 +- tests/helpers/test_entity_component.py | 4 +- tests/helpers/test_entity_platform.py | 4 +- tests/helpers/test_storage.py | 2 +- tests/helpers/test_temperature.py | 2 +- tests/helpers/test_template.py | 6 +- tests/test_bootstrap.py | 2 +- tests/test_config.py | 4 +- tests/test_core.py | 9 ++- tests/test_loader.py | 2 +- tests/test_setup.py | 2 +- 105 files changed, 241 insertions(+), 314 deletions(-) diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index a0243e2dd8c..5398c3d5c55 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -55,10 +55,8 @@ def ensure_config_path(config_dir: str) -> None: if not os.path.isdir(config_dir): if config_dir != config_util.get_default_config_dir(): print( - ( - "Fatal Error: Specified configuration directory does " - "not exist {} " - ).format(config_dir) + f"Fatal Error: Specified configuration directory {config_dir} " + "does not exist" ) sys.exit(1) @@ -66,10 +64,8 @@ def ensure_config_path(config_dir: str) -> None: os.mkdir(config_dir) except OSError: print( - ( - "Fatal Error: Unable to create default configuration " - "directory {} " - ).format(config_dir) + "Fatal Error: Unable to create default configuration " + f"directory {config_dir}" ) sys.exit(1) @@ -78,7 +74,7 @@ def ensure_config_path(config_dir: str) -> None: try: os.mkdir(lib_dir) except OSError: - print("Fatal Error: Unable to create library directory {}".format(lib_dir)) + print(f"Fatal Error: Unable to create library directory {lib_dir}") sys.exit(1) diff --git a/homeassistant/components/apns/notify.py b/homeassistant/components/apns/notify.py index 990598508af..febe344a9c4 100644 --- a/homeassistant/components/apns/notify.py +++ b/homeassistant/components/apns/notify.py @@ -150,7 +150,7 @@ class ApnsNotificationService(BaseNotificationService): self.app_name = app_name self.sandbox = sandbox self.certificate = cert_file - self.yaml_path = hass.config.path(app_name + "_" + APNS_DEVICES) + self.yaml_path = hass.config.path(f"{app_name}_{APNS_DEVICES}") self.devices = {} self.device_states = {} self.topic = topic diff --git a/homeassistant/components/braviatv/media_player.py b/homeassistant/components/braviatv/media_player.py index d0458541f7b..ef0640c8e87 100644 --- a/homeassistant/components/braviatv/media_player.py +++ b/homeassistant/components/braviatv/media_player.py @@ -291,7 +291,7 @@ class BraviaTVDevice(MediaPlayerDevice): if self._channel_name is not None: return_value = self._channel_name if self._program_name is not None: - return_value = return_value + ": " + self._program_name + return_value = f"{return_value}: {self._program_name}" return return_value @property diff --git a/homeassistant/components/cloud/__init__.py b/homeassistant/components/cloud/__init__.py index 6d9b70051f5..4799a82979f 100644 --- a/homeassistant/components/cloud/__init__.py +++ b/homeassistant/components/cloud/__init__.py @@ -158,7 +158,7 @@ def async_remote_ui_url(hass) -> str: if not hass.data[DOMAIN].remote.instance_domain: raise CloudNotAvailable - return "https://" + hass.data[DOMAIN].remote.instance_domain + return f"https://{hass.data[DOMAIN].remote.instance_domain}" def is_cloudhook_request(request): diff --git a/homeassistant/components/darksky/sensor.py b/homeassistant/components/darksky/sensor.py index 5b6da5d11bb..9f99b37a201 100644 --- a/homeassistant/components/darksky/sensor.py +++ b/homeassistant/components/darksky/sensor.py @@ -750,7 +750,7 @@ class DarkSkyAlertSensor(Entity): for i, alert in enumerate(data): for attr in ALERTS_ATTRS: if multiple_alerts: - dkey = attr + "_" + str(i) + dkey = f"{attr}_{i!s}" else: dkey = attr alerts[dkey] = getattr(alert, attr) diff --git a/homeassistant/components/ebusd/__init__.py b/homeassistant/components/ebusd/__init__.py index e4d0bdbcdb1..eafa42ba22a 100644 --- a/homeassistant/components/ebusd/__init__.py +++ b/homeassistant/components/ebusd/__init__.py @@ -34,7 +34,7 @@ def verify_ebusd_config(config): circuit = config[CONF_CIRCUIT] for condition in config[CONF_MONITORED_CONDITIONS]: if condition not in SENSOR_TYPES[circuit]: - raise vol.Invalid("Condition '" + condition + "' not in '" + circuit + "'.") + raise vol.Invalid(f"Condition '{condition}' not in '{circuit}'.") return config diff --git a/homeassistant/components/ecoal_boiler/__init__.py b/homeassistant/components/ecoal_boiler/__init__.py index 608e4a59a3f..b0ca7aec5cc 100644 --- a/homeassistant/components/ecoal_boiler/__init__.py +++ b/homeassistant/components/ecoal_boiler/__init__.py @@ -18,7 +18,7 @@ from homeassistant.helpers.discovery import load_platform _LOGGER = logging.getLogger(__name__) DOMAIN = "ecoal_boiler" -DATA_ECOAL_BOILER = "data_" + DOMAIN +DATA_ECOAL_BOILER = f"data_{DOMAIN}" DEFAULT_USERNAME = "admin" DEFAULT_PASSWORD = "admin" diff --git a/homeassistant/components/ecoal_boiler/switch.py b/homeassistant/components/ecoal_boiler/switch.py index 9f286e625a5..00bfd7f3e5b 100644 --- a/homeassistant/components/ecoal_boiler/switch.py +++ b/homeassistant/components/ecoal_boiler/switch.py @@ -38,7 +38,7 @@ class EcoalSwitch(SwitchDevice): # set_() # as attribute name in status instance: # status. - self._contr_set_fun = getattr(self._ecoal_contr, "set_" + state_attr) + self._contr_set_fun = getattr(self._ecoal_contr, f"set_{state_attr}") # No value set, will be read from controller instead self._state = None diff --git a/homeassistant/components/egardia/__init__.py b/homeassistant/components/egardia/__init__.py index efe47736479..770db1d236b 100644 --- a/homeassistant/components/egardia/__init__.py +++ b/homeassistant/components/egardia/__init__.py @@ -110,7 +110,7 @@ def setup(hass, config): bound = server.bind() if not bound: raise OSError( - "Binding error occurred while " + "starting EgardiaServer." + "Binding error occurred while starting EgardiaServer." ) hass.data[EGARDIA_SERVER] = server server.start() diff --git a/homeassistant/components/fibaro/__init__.py b/homeassistant/components/fibaro/__init__.py index aeb7c0879e0..32d8f328ef8 100644 --- a/homeassistant/components/fibaro/__init__.py +++ b/homeassistant/components/fibaro/__init__.py @@ -268,7 +268,7 @@ class FibaroController: else: room_name = self._room_map[device.roomID].name device.room_name = room_name - device.friendly_name = room_name + " " + device.name + device.friendly_name = f"{room_name} {device.name}" device.ha_id = "{}_{}_{}".format( slugify(room_name), slugify(device.name), device.id ) diff --git a/homeassistant/components/hangouts/hangouts_bot.py b/homeassistant/components/hangouts/hangouts_bot.py index 8575a547a9c..fd14ec0b094 100644 --- a/homeassistant/components/hangouts/hangouts_bot.py +++ b/homeassistant/components/hangouts/hangouts_bot.py @@ -86,15 +86,15 @@ class HangoutsBot: conv_id = self._resolve_conversation_id(conversation) if conv_id is not None: conversations.append(conv_id) - data["_" + CONF_CONVERSATIONS] = conversations + data[f"_{CONF_CONVERSATIONS}"] = conversations elif self._default_conv_ids: - data["_" + CONF_CONVERSATIONS] = self._default_conv_ids + data[f"_{CONF_CONVERSATIONS}"] = self._default_conv_ids else: - data["_" + CONF_CONVERSATIONS] = [ + data[f"_{CONF_CONVERSATIONS}"] = [ conv.id_ for conv in self._conversation_list.get_all() ] - for conv_id in data["_" + CONF_CONVERSATIONS]: + for conv_id in data[f"_{CONF_CONVERSATIONS}"]: if conv_id not in self._conversation_intents: self._conversation_intents[conv_id] = {} diff --git a/homeassistant/components/hook/switch.py b/homeassistant/components/hook/switch.py index 14c4d4ba662..582dc61af14 100644 --- a/homeassistant/components/hook/switch.py +++ b/homeassistant/components/hook/switch.py @@ -21,12 +21,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( vol.Exclusive( CONF_PASSWORD, "hook_secret", - msg="hook: provide " + "username/password OR token", + msg="hook: provide username/password OR token", ): cv.string, vol.Exclusive( - CONF_TOKEN, - "hook_secret", - msg="hook: provide " + "username/password OR token", + CONF_TOKEN, "hook_secret", msg="hook: provide username/password OR token", ): cv.string, vol.Inclusive(CONF_USERNAME, "hook_auth"): cv.string, vol.Inclusive(CONF_PASSWORD, "hook_auth"): cv.string, diff --git a/homeassistant/components/huawei_router/device_tracker.py b/homeassistant/components/huawei_router/device_tracker.py index 4b52060e425..be34b26be0d 100644 --- a/homeassistant/components/huawei_router/device_tracker.py +++ b/homeassistant/components/huawei_router/device_tracker.py @@ -88,7 +88,7 @@ class HuaweiDeviceScanner(DeviceScanner): _LOGGER.debug( "Active clients: %s", - "\n".join((client.mac + " " + client.name) for client in active_clients), + "\n".join(f"{client.mac} {client.name}" for client in active_clients), ) return True diff --git a/homeassistant/components/icloud/__init__.py b/homeassistant/components/icloud/__init__.py index c59f4098951..e983f5fac22 100644 --- a/homeassistant/components/icloud/__init__.py +++ b/homeassistant/components/icloud/__init__.py @@ -216,7 +216,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool if icloud_account is None: raise Exception( - "No iCloud account with username or name " + account_identifier + f"No iCloud account with username or name {account_identifier}" ) return icloud_account @@ -430,7 +430,7 @@ class IcloudAccount: if slugify(device.name.replace(" ", "", 99)) == name_slug: result.append(device) if not result: - raise Exception("No device with name " + name) + raise Exception(f"No device with name {name}") return result @property diff --git a/homeassistant/components/insteon/__init__.py b/homeassistant/components/insteon/__init__.py index 11f224dbfcc..df6fa626a4f 100644 --- a/homeassistant/components/insteon/__init__.py +++ b/homeassistant/components/insteon/__init__.py @@ -614,7 +614,7 @@ class InsteonEntity(Entity): # Get an extension label if there is one extension = self._get_label() if extension: - extension = " " + extension + extension = f" {extension}" name = "{:s} {:s}{:s}".format( description, self._insteon_device.address.human, extension ) diff --git a/homeassistant/components/itunes/media_player.py b/homeassistant/components/itunes/media_player.py index 112a9c609d8..327cbf5e9ac 100644 --- a/homeassistant/components/itunes/media_player.py +++ b/homeassistant/components/itunes/media_player.py @@ -110,7 +110,7 @@ class Itunes: def _command(self, named_command): """Make a request for a controlling command.""" - return self._request("PUT", "/" + named_command) + return self._request("PUT", f"/{named_command}") def now_playing(self): """Return the current state.""" @@ -168,7 +168,7 @@ class Itunes: def artwork_url(self): """Return a URL of the current track's album art.""" - return self._base_url + "/artwork" + return f"{self._base_url}/artwork" def airplay_devices(self): """Return a list of AirPlay devices.""" @@ -176,17 +176,17 @@ class Itunes: def airplay_device(self, device_id): """Return an AirPlay device.""" - return self._request("GET", "/airplay_devices/" + device_id) + return self._request("GET", f"/airplay_devices/{device_id}") def toggle_airplay_device(self, device_id, toggle): """Toggle airplay device on or off, id, toggle True or False.""" command = "on" if toggle else "off" - path = "/airplay_devices/" + device_id + "/" + command + path = f"/airplay_devices/{device_id}/{command}" return self._request("PUT", path) def set_volume_airplay_device(self, device_id, level): """Set volume, returns current state of device, id,level 0-100.""" - path = "/airplay_devices/" + device_id + "/volume" + path = f"/airplay_devices/{device_id}/volume" return self._request("PUT", path, {"level": level}) @@ -431,7 +431,7 @@ class AirPlayDevice(MediaPlayerDevice): if "name" in state_hash: name = state_hash.get("name", "") - self.device_name = (name + " AirTunes Speaker").strip() + self.device_name = f"{name} AirTunes Speaker".strip() if "kind" in state_hash: self.kind = state_hash.get("kind", None) diff --git a/homeassistant/components/kodi/media_player.py b/homeassistant/components/kodi/media_player.py index 71418927ed2..13aa18d01ad 100644 --- a/homeassistant/components/kodi/media_player.py +++ b/homeassistant/components/kodi/media_player.py @@ -963,7 +963,7 @@ class KodiDevice(MediaPlayerDevice): @staticmethod def _find(key_word, words): key_word = key_word.split(" ") - patt = [re.compile("(^| )" + k + "( |$)", re.IGNORECASE) for k in key_word] + patt = [re.compile(f"(^| ){k}( |$)", re.IGNORECASE) for k in key_word] out = [[i, 0] for i in range(len(words))] for i in range(len(words)): diff --git a/homeassistant/components/konnected/__init__.py b/homeassistant/components/konnected/__init__.py index 624d359e154..28e62c322ad 100644 --- a/homeassistant/components/konnected/__init__.py +++ b/homeassistant/components/konnected/__init__.py @@ -488,7 +488,7 @@ class KonnectedView(HomeAssistantView): device = data[CONF_DEVICES][device_id] if not device: return self.json_message( - "Device " + device_id + " not configured", status_code=HTTP_NOT_FOUND + f"Device {device_id} not configured", status_code=HTTP_NOT_FOUND ) try: diff --git a/homeassistant/components/life360/device_tracker.py b/homeassistant/components/life360/device_tracker.py index ddd562ebfac..b2ba1ca3164 100644 --- a/homeassistant/components/life360/device_tracker.py +++ b/homeassistant/components/life360/device_tracker.py @@ -162,7 +162,7 @@ class Life360Scanner: msg = f"{key}: {err_msg}" if _errs >= self._error_threshold: if _errs == self._max_errs: - msg = "Suppressing further errors until OK: " + msg + msg = f"Suppressing further errors until OK: {msg}" _LOGGER.error(msg) elif _errs >= self._warning_threshold: _LOGGER.warning(msg) diff --git a/homeassistant/components/lifx/light.py b/homeassistant/components/lifx/light.py index aa63be04f0d..4e845a07854 100644 --- a/homeassistant/components/lifx/light.py +++ b/homeassistant/components/lifx/light.py @@ -562,7 +562,7 @@ class LIFXLight(Light): """Return the name of the currently running effect.""" effect = self.effects_conductor.effect(self.bulb) if effect: - return "lifx_effect_" + effect.name + return f"lifx_effect_{effect.name}" return None async def update_hass(self, now=None): diff --git a/homeassistant/components/netatmo/binary_sensor.py b/homeassistant/components/netatmo/binary_sensor.py index 06097ed852d..a449b7bb43d 100644 --- a/homeassistant/components/netatmo/binary_sensor.py +++ b/homeassistant/components/netatmo/binary_sensor.py @@ -155,9 +155,9 @@ class NetatmoBinarySensor(BinarySensorDevice): else: self._name = camera_name if module_name: - self._name += " / " + module_name + self._name += f" / {module_name}" self._sensor_name = sensor - self._name += " " + sensor + self._name += f" {sensor}" self._cameratype = camera_type self._state = None diff --git a/homeassistant/components/netatmo/camera.py b/homeassistant/components/netatmo/camera.py index 1713265a014..546a5da3c15 100644 --- a/homeassistant/components/netatmo/camera.py +++ b/homeassistant/components/netatmo/camera.py @@ -106,7 +106,7 @@ class NetatmoCamera(Camera): self._camera_name = camera_name self._home = home if home: - self._name = home + " / " + camera_name + self._name = f"{home} / {camera_name}" else: self._name = camera_name self._cameratype = camera_type @@ -383,7 +383,7 @@ class NetatmoCamera(Camera): """Set light mode ('auto', 'on', 'off').""" if self.model == "Presence": try: - config = '{"mode":"' + mode + '"}' + config = f'{{"mode":"{mode}"}}' if self._localurl: requests.get( f"{self._localurl}/command/floodlight_set_config?config={config}", diff --git a/homeassistant/components/netgear/device_tracker.py b/homeassistant/components/netgear/device_tracker.py index d556e83ca13..3e87bcac53c 100644 --- a/homeassistant/components/netgear/device_tracker.py +++ b/homeassistant/components/netgear/device_tracker.py @@ -116,7 +116,7 @@ class NetgearDeviceScanner(DeviceScanner): self.tracked_accesspoints and dev.conn_ap_mac in self.tracked_accesspoints ): - devices.append(dev.mac + "_" + dev.conn_ap_mac) + devices.append(f"{dev.mac}_{dev.conn_ap_mac}") return devices @@ -144,7 +144,7 @@ class NetgearDeviceScanner(DeviceScanner): ap_name = dev.name break - return name + " on " + ap_name + return f"{name} on {ap_name}" return name diff --git a/homeassistant/components/octoprint/__init__.py b/homeassistant/components/octoprint/__init__.py index 7564330e499..f73e525efe3 100644 --- a/homeassistant/components/octoprint/__init__.py +++ b/homeassistant/components/octoprint/__init__.py @@ -45,7 +45,7 @@ def ensure_valid_path(value): """Validate the path, ensuring it starts and ends with a /.""" vol.Schema(cv.string)(value) if value[0] != "/": - value = "/" + value + value = f"/{value}" if value[-1] != "/": value += "/" return value @@ -189,7 +189,7 @@ class OctoPrintAPI: tools = [] if self.number_of_tools > 0: for tool_number in range(0, self.number_of_tools): - tools.append("tool" + str(tool_number)) + tools.append(f"tool{tool_number!s}") if self.bed: tools.append("bed") if not self.bed and self.number_of_tools == 0: @@ -231,18 +231,16 @@ class OctoPrintAPI: self.printer_error_logged = False return response.json() except Exception as conn_exc: # pylint: disable=broad-except - log_string = "Failed to update OctoPrint status. " + " Error: %s" % ( - conn_exc - ) + log_string = "Failed to update OctoPrint status. Error: %s" % conn_exc # Only log the first failure if endpoint == "job": - log_string = "Endpoint: job " + log_string + log_string = f"Endpoint: job {log_string}" if not self.job_error_logged: _LOGGER.error(log_string) self.job_error_logged = True self.job_available = False elif endpoint == "printer": - log_string = "Endpoint: printer " + log_string + log_string = f"Endpoint: printer {log_string}" if not self.printer_error_logged: _LOGGER.error(log_string) self.printer_error_logged = True diff --git a/homeassistant/components/onewire/sensor.py b/homeassistant/components/onewire/sensor.py index 6405cb05adc..936bf9f751b 100644 --- a/homeassistant/components/onewire/sensor.py +++ b/homeassistant/components/onewire/sensor.py @@ -148,7 +148,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): # We have a raw GPIO ow sensor on a Pi elif base_dir == DEFAULT_MOUNT_DIR: for device_family in DEVICE_SENSORS: - for device_folder in glob(os.path.join(base_dir, device_family + "[.-]*")): + for device_folder in glob(os.path.join(base_dir, f"{device_family}[.-]*")): sensor_id = os.path.split(device_folder)[1] device_file = os.path.join(device_folder, "w1_slave") devs.append( diff --git a/homeassistant/components/plant/__init__.py b/homeassistant/components/plant/__init__.py index cc405dcad1f..1f440eb36dc 100644 --- a/homeassistant/components/plant/__init__.py +++ b/homeassistant/components/plant/__init__.py @@ -44,15 +44,15 @@ ATTR_MAX_BRIGHTNESS_HISTORY = "max_brightness" # to have a separate literal for it to avoid confusion. ATTR_DICT_OF_UNITS_OF_MEASUREMENT = "unit_of_measurement_dict" -CONF_MIN_BATTERY_LEVEL = "min_" + READING_BATTERY -CONF_MIN_TEMPERATURE = "min_" + READING_TEMPERATURE -CONF_MAX_TEMPERATURE = "max_" + READING_TEMPERATURE -CONF_MIN_MOISTURE = "min_" + READING_MOISTURE -CONF_MAX_MOISTURE = "max_" + READING_MOISTURE -CONF_MIN_CONDUCTIVITY = "min_" + READING_CONDUCTIVITY -CONF_MAX_CONDUCTIVITY = "max_" + READING_CONDUCTIVITY -CONF_MIN_BRIGHTNESS = "min_" + READING_BRIGHTNESS -CONF_MAX_BRIGHTNESS = "max_" + READING_BRIGHTNESS +CONF_MIN_BATTERY_LEVEL = f"min_{READING_BATTERY}" +CONF_MIN_TEMPERATURE = f"min_{READING_TEMPERATURE}" +CONF_MAX_TEMPERATURE = f"max_{READING_TEMPERATURE}" +CONF_MIN_MOISTURE = f"min_{READING_MOISTURE}" +CONF_MAX_MOISTURE = f"max_{READING_MOISTURE}" +CONF_MIN_CONDUCTIVITY = f"min_{READING_CONDUCTIVITY}" +CONF_MAX_CONDUCTIVITY = f"max_{READING_CONDUCTIVITY}" +CONF_MIN_BRIGHTNESS = f"min_{READING_BRIGHTNESS}" +CONF_MAX_BRIGHTNESS = f"max_{READING_BRIGHTNESS}" CONF_CHECK_DAYS = "check_days" CONF_SENSOR_BATTERY_LEVEL = READING_BATTERY diff --git a/homeassistant/components/plex/media_player.py b/homeassistant/components/plex/media_player.py index ad5fb2f73f1..46b797976ab 100644 --- a/homeassistant/components/plex/media_player.py +++ b/homeassistant/components/plex/media_player.py @@ -300,7 +300,7 @@ class PlexMediaPlayer(MediaPlayerDevice): elif self._session_type == "movie": self._media_content_type = MEDIA_TYPE_MOVIE if self.session.year is not None and self._media_title is not None: - self._media_title += " (" + str(self.session.year) + ")" + self._media_title += f" ({self.session.year!s})" elif self._session_type == "track": self._media_content_type = MEDIA_TYPE_MUSIC diff --git a/homeassistant/components/raspihats/__init__.py b/homeassistant/components/raspihats/__init__.py index 8b7ea0a38d7..fb544d3ebcc 100644 --- a/homeassistant/components/raspihats/__init__.py +++ b/homeassistant/components/raspihats/__init__.py @@ -50,7 +50,7 @@ def log_message(source, *parts): """Build log message.""" message = source.__class__.__name__ for part in parts: - message += ": " + str(part) + message += f": {part!s}" return message diff --git a/homeassistant/components/raspihats/switch.py b/homeassistant/components/raspihats/switch.py index b99a84bdae9..8a083dbe2c9 100644 --- a/homeassistant/components/raspihats/switch.py +++ b/homeassistant/components/raspihats/switch.py @@ -127,7 +127,7 @@ class I2CHatSwitch(ToggleEntity): state = self.I2C_HATS_MANAGER.read_dq(self._address, self._channel) return state != self._invert_logic except I2CHatsException as ex: - _LOGGER.error(self._log_message("Is ON check failed, " + str(ex))) + _LOGGER.error(self._log_message(f"Is ON check failed, {ex!s}")) return False def turn_on(self, **kwargs): @@ -137,7 +137,7 @@ class I2CHatSwitch(ToggleEntity): self.I2C_HATS_MANAGER.write_dq(self._address, self._channel, state) self.schedule_update_ha_state() except I2CHatsException as ex: - _LOGGER.error(self._log_message("Turn ON failed, " + str(ex))) + _LOGGER.error(self._log_message(f"Turn ON failed, {ex!s}")) def turn_off(self, **kwargs): """Turn the device off.""" @@ -146,4 +146,4 @@ class I2CHatSwitch(ToggleEntity): self.I2C_HATS_MANAGER.write_dq(self._address, self._channel, state) self.schedule_update_ha_state() except I2CHatsException as ex: - _LOGGER.error(self._log_message("Turn OFF failed:, " + str(ex))) + _LOGGER.error(self._log_message(f"Turn OFF failed, {ex!s}")) diff --git a/homeassistant/components/samsungtv/media_player.py b/homeassistant/components/samsungtv/media_player.py index 56b947ba9ad..fd900fedec1 100644 --- a/homeassistant/components/samsungtv/media_player.py +++ b/homeassistant/components/samsungtv/media_player.py @@ -344,7 +344,7 @@ class SamsungTVDevice(MediaPlayerDevice): return for digit in media_id: - await self.hass.async_add_job(self.send_key, "KEY_" + digit) + await self.hass.async_add_job(self.send_key, f"KEY_{digit}") await asyncio.sleep(KEY_PRESS_TIMEOUT, self.hass.loop) await self.hass.async_add_job(self.send_key, "KEY_ENTER") diff --git a/homeassistant/components/sensibo/climate.py b/homeassistant/components/sensibo/climate.py index 2431b223f09..08e2212e2a2 100644 --- a/homeassistant/components/sensibo/climate.py +++ b/homeassistant/components/sensibo/climate.py @@ -65,7 +65,7 @@ _FETCH_FIELDS = ",".join( "temperatureUnit", ] ) -_INITIAL_FETCH_FIELDS = "id," + _FETCH_FIELDS +_INITIAL_FETCH_FIELDS = f"id,{_FETCH_FIELDS}" FIELD_TO_FLAG = { "fanLevel": SUPPORT_FAN_MODE, diff --git a/homeassistant/components/seventeentrack/sensor.py b/homeassistant/components/seventeentrack/sensor.py index 167f4347c0c..43bc1b41c8f 100644 --- a/homeassistant/components/seventeentrack/sensor.py +++ b/homeassistant/components/seventeentrack/sensor.py @@ -45,7 +45,7 @@ ENTITY_ID_TEMPLATE = "sensor.seventeentrack_package_{0}" NOTIFICATION_DELIVERED_ID = "package_delivered_{0}" NOTIFICATION_DELIVERED_TITLE = "Package {0} delivered" NOTIFICATION_DELIVERED_MESSAGE = ( - "Package Delivered: {0}
" + "Visit 17.track for more information: " + "Package Delivered: {0}
Visit 17.track for more information: " "https://t.17track.net/track#nums={1}" ) diff --git a/homeassistant/components/sky_hub/device_tracker.py b/homeassistant/components/sky_hub/device_tracker.py index f7760a59eed..c7dc1092b73 100644 --- a/homeassistant/components/sky_hub/device_tracker.py +++ b/homeassistant/components/sky_hub/device_tracker.py @@ -95,8 +95,7 @@ def _parse_skyhub_response(data_str): pattmatch = re.search("attach_dev = '(.*)'", data_str) if pattmatch is None: raise OSError( - "Error: Impossible to fetch data from" - + " Sky Hub. Try to reboot the router." + "Error: Impossible to fetch data from Sky Hub. Try to reboot the router." ) patt = pattmatch.group(1) @@ -107,8 +106,6 @@ def _parse_skyhub_response(data_str): if _MAC_REGEX.match(dvc[1]): devices[dvc[1]] = dvc[0] else: - raise RuntimeError( - "Error: MAC address " + dvc[1] + " not in correct format." - ) + raise RuntimeError(f"Error: MAC address {dvc[1]} not in correct format.") return devices diff --git a/homeassistant/components/smappee/__init__.py b/homeassistant/components/smappee/__init__.py index ecab09f6ff9..d34653e60e7 100644 --- a/homeassistant/components/smappee/__init__.py +++ b/homeassistant/components/smappee/__init__.py @@ -24,7 +24,7 @@ CONF_HOST_PASSWORD = "host_password" DOMAIN = "smappee" DATA_SMAPPEE = "SMAPPEE" -_SENSOR_REGEX = re.compile(r"(?P([A-Za-z]+))\=" + r"(?P([0-9\.]+))") +_SENSOR_REGEX = re.compile(r"(?P([A-Za-z]+))\=(?P([0-9\.]+))") CONFIG_SCHEMA = vol.Schema( { diff --git a/homeassistant/components/smartthings/__init__.py b/homeassistant/components/smartthings/__init__.py index 9787fb53917..33f9558023d 100644 --- a/homeassistant/components/smartthings/__init__.py +++ b/homeassistant/components/smartthings/__init__.py @@ -279,7 +279,7 @@ class DeviceBroker: capabilities = device.capabilities.copy() slots = {} for platform_name in SUPPORTED_PLATFORMS: - platform = importlib.import_module("." + platform_name, self.__module__) + platform = importlib.import_module(f".{platform_name}", self.__module__) if not hasattr(platform, "get_capabilities"): continue assigned = platform.get_capabilities(capabilities) diff --git a/homeassistant/components/smartthings/smartapp.py b/homeassistant/components/smartthings/smartapp.py index d0487290926..d17f6061101 100644 --- a/homeassistant/components/smartthings/smartapp.py +++ b/homeassistant/components/smartthings/smartapp.py @@ -109,7 +109,7 @@ def get_webhook_url(hass: HomeAssistantType) -> str: def _get_app_template(hass: HomeAssistantType): - endpoint = "at " + hass.config.api.base_url + endpoint = f"at {hass.config.api.base_url}" cloudhook_url = hass.data[DOMAIN][CONF_CLOUDHOOK_URL] if cloudhook_url is not None: endpoint = "via Nabu Casa" diff --git a/homeassistant/components/soundtouch/media_player.py b/homeassistant/components/soundtouch/media_player.py index 4a0f6b55b22..72677995a9d 100644 --- a/homeassistant/components/soundtouch/media_player.py +++ b/homeassistant/components/soundtouch/media_player.py @@ -304,7 +304,7 @@ class SoundTouchDevice(MediaPlayerDevice): if self._status.station_name is not None: return self._status.station_name if self._status.artist is not None: - return self._status.artist + " - " + self._status.track + return f"{self._status.artist} - {self._status.track}" return None diff --git a/homeassistant/components/startca/sensor.py b/homeassistant/components/startca/sensor.py index 55ae15cede7..e07f21e5d60 100644 --- a/homeassistant/components/startca/sensor.py +++ b/homeassistant/components/startca/sensor.py @@ -140,7 +140,7 @@ class StartcaData: async def async_update(self): """Get the Start.ca bandwidth data from the web service.""" _LOGGER.debug("Updating Start.ca usage data") - url = "https://www.start.ca/support/usage/api?key=" + self.api_key + url = f"https://www.start.ca/support/usage/api?key={self.api_key}" with async_timeout.timeout(REQUEST_TIMEOUT): req = await self.websession.get(url) if req.status != 200: diff --git a/homeassistant/components/tank_utility/sensor.py b/homeassistant/components/tank_utility/sensor.py index 23446257eab..61a3d7367bf 100644 --- a/homeassistant/components/tank_utility/sensor.py +++ b/homeassistant/components/tank_utility/sensor.py @@ -73,7 +73,7 @@ class TankUtilitySensor(Entity): self._token = token self._device = device self._state = None - self._name = "Tank Utility " + self.device + self._name = f"Tank Utility {self.device}" self._unit_of_measurement = SENSOR_UNIT_OF_MEASUREMENT self._attributes = {} diff --git a/homeassistant/components/temper/sensor.py b/homeassistant/components/temper/sensor.py index 8b782ae4d79..fd26b1702dc 100644 --- a/homeassistant/components/temper/sensor.py +++ b/homeassistant/components/temper/sensor.py @@ -39,7 +39,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for idx, dev in enumerate(temper_devices): if idx != 0: - name = name + "_" + str(idx) + name = f"{name}_{idx!s}" TEMPER_SENSORS.append(TemperSensor(dev, temp_unit, name, scaling)) add_entities(TEMPER_SENSORS) diff --git a/homeassistant/components/wunderground/sensor.py b/homeassistant/components/wunderground/sensor.py index 5d3bf1f74b8..0a566259a88 100644 --- a/homeassistant/components/wunderground/sensor.py +++ b/homeassistant/components/wunderground/sensor.py @@ -998,7 +998,7 @@ class WUndergroundSensor(Entity): self.rest.request_feature(SENSOR_TYPES[condition].feature) # This is only the suggested entity id, it might get changed by # the entity registry later. - self.entity_id = sensor.ENTITY_ID_FORMAT.format("pws_" + condition) + self.entity_id = sensor.ENTITY_ID_FORMAT.format(f"pws_{condition}") self._unique_id = f"{unique_id_base},{condition}" self._device_class = self._cfg_expand("device_class") @@ -1122,11 +1122,11 @@ class WUndergroundData: self._api_key, "/".join(sorted(self._features)), self._lang ) if self._pws_id: - url = url + f"pws:{self._pws_id}" + url = f"{url}pws:{self._pws_id}" else: - url = url + f"{self._latitude},{self._longitude}" + url = f"{url}{self._latitude},{self._longitude}" - return url + ".json" + return f"{url}.json" @Throttle(MIN_TIME_BETWEEN_UPDATES) async def async_update(self): diff --git a/homeassistant/components/x10/light.py b/homeassistant/components/x10/light.py index 1f74326d544..7be2f12d949 100644 --- a/homeassistant/components/x10/light.py +++ b/homeassistant/components/x10/light.py @@ -34,7 +34,7 @@ def x10_command(command): def get_unit_status(code): """Get on/off status for given unit.""" - output = check_output("heyu onstate " + code, shell=True) + output = check_output(f"heyu onstate {code}", shell=True) return int(output.decode("utf-8")[0]) @@ -84,18 +84,18 @@ class X10Light(Light): def turn_on(self, **kwargs): """Instruct the light to turn on.""" if self._is_cm11a: - x10_command("on " + self._id) + x10_command(f"on {self._id}") else: - x10_command("fon " + self._id) + x10_command(f"fon {self._id}") self._brightness = kwargs.get(ATTR_BRIGHTNESS, 255) self._state = True def turn_off(self, **kwargs): """Instruct the light to turn off.""" if self._is_cm11a: - x10_command("off " + self._id) + x10_command(f"off {self._id}") else: - x10_command("foff " + self._id) + x10_command(f"foff {self._id}") self._state = False def update(self): diff --git a/homeassistant/components/yeelight/light.py b/homeassistant/components/yeelight/light.py index c40ead2a892..61de12eafbf 100644 --- a/homeassistant/components/yeelight/light.py +++ b/homeassistant/components/yeelight/light.py @@ -935,6 +935,6 @@ class YeelightAmbientLight(YeelightColorLight): bg_prop = self.PROPERTIES_MAPPING.get(prop) if not bg_prop: - bg_prop = "bg_" + prop + bg_prop = f"bg_{prop}" return super()._get_property(bg_prop, default) diff --git a/homeassistant/components/zha/api.py b/homeassistant/components/zha/api.py index 1294fcaedbd..6228a2bc0c8 100644 --- a/homeassistant/components/zha/api.py +++ b/homeassistant/components/zha/api.py @@ -797,7 +797,7 @@ async def async_binding_operation(zha_gateway, source_ieee, target_ieee, operati operation.name, target_ieee, ) - zdo.debug("processing " + op_msg, *op_params) + zdo.debug(f"processing {op_msg}", *op_params) bind_tasks.append( ( diff --git a/homeassistant/components/zha/core/channels/__init__.py b/homeassistant/components/zha/core/channels/__init__.py index 5a337b2a537..a5ecf21e0c3 100644 --- a/homeassistant/components/zha/core/channels/__init__.py +++ b/homeassistant/components/zha/core/channels/__init__.py @@ -263,7 +263,7 @@ class ZigbeeChannel(LogMixin): def log(self, level, msg, *args): """Log a message.""" - msg = "[%s:%s]: " + msg + msg = f"[%s:%s]: {msg}" args = (self.device.nwk, self._id,) + args _LOGGER.log(level, msg, *args) @@ -357,7 +357,7 @@ class ZDOChannel(LogMixin): def log(self, level, msg, *args): """Log a message.""" - msg = "[%s:ZDO](%s): " + msg + msg = f"[%s:ZDO](%s): {msg}" args = (self._zha_device.nwk, self._zha_device.model) + args _LOGGER.log(level, msg, *args) diff --git a/homeassistant/components/zha/core/device.py b/homeassistant/components/zha/core/device.py index 77e0263c06c..dbaf3fd4435 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -528,6 +528,6 @@ class ZHADevice(LogMixin): def log(self, level, msg, *args): """Log a message.""" - msg = "[%s](%s): " + msg + msg = f"[%s](%s): {msg}" args = (self.nwk, self.model) + args _LOGGER.log(level, msg, *args) diff --git a/homeassistant/components/zha/entity.py b/homeassistant/components/zha/entity.py index 102472d25b0..0b001bdedbc 100644 --- a/homeassistant/components/zha/entity.py +++ b/homeassistant/components/zha/entity.py @@ -185,6 +185,6 @@ class ZhaEntity(RestoreEntity, LogMixin, entity.Entity): def log(self, level, msg, *args): """Log a message.""" - msg = "%s: " + msg + msg = f"%s: {msg}" args = (self.entity_id,) + args _LOGGER.log(level, msg, *args) diff --git a/homeassistant/components/zhong_hong/climate.py b/homeassistant/components/zhong_hong/climate.py index b94e19d6dbd..203131afdb1 100644 --- a/homeassistant/components/zhong_hong/climate.py +++ b/homeassistant/components/zhong_hong/climate.py @@ -161,9 +161,7 @@ class ZhongHongClimate(ClimateDevice): @property def unique_id(self): """Return the unique ID of the HVAC.""" - return "zhong_hong_hvac_{}_{}".format( - self._device.addr_out, self._device.addr_in - ) + return f"zhong_hong_hvac_{self._device.addr_out}_{self._device.addr_in}" @property def supported_features(self): diff --git a/homeassistant/components/zwave/cover.py b/homeassistant/components/zwave/cover.py index 5b4fb0c9934..724977a4220 100644 --- a/homeassistant/components/zwave/cover.py +++ b/homeassistant/components/zwave/cover.py @@ -37,7 +37,7 @@ def _to_hex_str(id_in_bytes): Example: 0x1234 --> '0x1234' """ - return "0x{:04x}".format(id_in_bytes) + return f"0x{id_in_bytes:04x}" # For some reason node.manufacturer_id is of type string. So we need to convert diff --git a/homeassistant/config.py b/homeassistant/config.py index ee3ccc15f81..6777c1ef5a5 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -572,9 +572,7 @@ async def async_process_ha_core_config(hass: HomeAssistant, config: Dict) -> Non def _log_pkg_error(package: str, component: str, config: Dict, message: str) -> None: """Log an error while merging packages.""" - message = "Package {} setup failed. Integration {} {}".format( - package, component, message - ) + message = f"Package {package} setup failed. Integration {component} {message}" pack_config = config[CONF_CORE][CONF_PACKAGES].get(package, config) message += " (See {}:{}). ".format( diff --git a/homeassistant/const.py b/homeassistant/const.py index 15dc5a099bc..ae055c52f10 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,8 +2,8 @@ MAJOR_VERSION = 0 MINOR_VERSION = 104 PATCH_VERSION = "0.dev0" -__short_version__ = "{}.{}".format(MAJOR_VERSION, MINOR_VERSION) -__version__ = "{}.{}".format(__short_version__, PATCH_VERSION) +__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" +__version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0) # Truthy date string triggers showing related deprecation warning messages. REQUIRED_NEXT_PYTHON_VER = (3, 8, 0) diff --git a/homeassistant/core.py b/homeassistant/core.py index e76673f5727..af27110dc9d 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -712,18 +712,14 @@ class State: if not valid_entity_id(entity_id) and not temp_invalid_id_bypass: raise InvalidEntityFormatError( - ( - "Invalid entity id encountered: {}. " - "Format should be ." - ).format(entity_id) + f"Invalid entity id encountered: {entity_id}. " + "Format should be ." ) if not valid_state(state): raise InvalidStateError( - ( - "Invalid state encountered for entity id: {}. " - "State max length is 255 characters." - ).format(entity_id) + f"Invalid state encountered for entity id: {entity_id}. " + "State max length is 255 characters." ) self.entity_id = entity_id.lower() @@ -1034,9 +1030,7 @@ class ServiceCall: self.domain, self.service, self.context.id, util.repr_helper(self.data) ) - return "".format( - self.domain, self.service, self.context.id - ) + return f"" class ServiceRegistry: diff --git a/homeassistant/data_entry_flow.py b/homeassistant/data_entry_flow.py index 6a9f5b1dc5a..5e72dea9273 100644 --- a/homeassistant/data_entry_flow.py +++ b/homeassistant/data_entry_flow.py @@ -153,9 +153,7 @@ class FlowManager(abc.ABC): if not hasattr(flow, method): self._progress.pop(flow.flow_id) raise UnknownStep( - "Handler {} doesn't support step {}".format( - flow.__class__.__name__, step_id - ) + f"Handler {flow.__class__.__name__} doesn't support step {step_id}" ) try: diff --git a/homeassistant/helpers/__init__.py b/homeassistant/helpers/__init__.py index 125d90e1162..ad97456968b 100644 --- a/homeassistant/helpers/__init__.py +++ b/homeassistant/helpers/__init__.py @@ -36,5 +36,5 @@ def extract_domain_configs(config: ConfigType, domain: str) -> Sequence[str]: Async friendly. """ - pattern = re.compile(r"^{}(| .+)$".format(domain)) + pattern = re.compile(fr"^{domain}(| .+)$") return [key for key in config.keys() if pattern.match(key)] diff --git a/homeassistant/helpers/check_config.py b/homeassistant/helpers/check_config.py index 1b1e136ed89..6ac1326545a 100644 --- a/homeassistant/helpers/check_config.py +++ b/homeassistant/helpers/check_config.py @@ -69,9 +69,7 @@ async def async_check_ha_config_file(hass: HomeAssistant) -> HomeAssistantConfig package: str, component: str, config: ConfigType, message: str ) -> None: """Handle errors from packages: _log_pkg_error.""" - message = "Package {} setup failed. Component {} {}".format( - package, component, message - ) + message = f"Package {package} setup failed. Component {component} {message}" domain = f"homeassistant.packages.{package}.{component}" pack_config = core_config[CONF_PACKAGES].get(package, config) result.add_error(message, domain, pack_config) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index bcf0d42df70..e357a2ba622 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -135,7 +135,7 @@ def boolean(value: Any) -> bool: elif isinstance(value, Number): # type ignore: https://github.com/python/mypy/issues/3186 return value != 0 # type: ignore - raise vol.Invalid("invalid boolean value {}".format(value)) + raise vol.Invalid(f"invalid boolean value {value}") def isdevice(value: Any) -> str: @@ -144,7 +144,7 @@ def isdevice(value: Any) -> str: os.stat(value) return str(value) except OSError: - raise vol.Invalid("No device at {} found".format(value)) + raise vol.Invalid(f"No device at {value} found") def matches_regex(regex: str) -> Callable[[Any], str]: @@ -154,13 +154,11 @@ def matches_regex(regex: str) -> Callable[[Any], str]: def validator(value: Any) -> str: """Validate that value matches the given regex.""" if not isinstance(value, str): - raise vol.Invalid("not a string value: {}".format(value)) + raise vol.Invalid(f"not a string value: {value}") if not compiled.match(value): raise vol.Invalid( - "value {} does not match regular expression {}".format( - value, compiled.pattern - ) + f"value {value} does not match regular expression {compiled.pattern}" ) return value @@ -175,10 +173,10 @@ def is_regex(value: Any) -> Pattern[Any]: return r except TypeError: raise vol.Invalid( - "value {} is of the wrong type for a regular expression".format(value) + f"value {value} is of the wrong type for a regular expression" ) except re.error: - raise vol.Invalid("value {} is not a valid regular expression".format(value)) + raise vol.Invalid(f"value {value} is not a valid regular expression") def isfile(value: Any) -> str: @@ -220,7 +218,7 @@ def entity_id(value: Any) -> str: if valid_entity_id(str_value): return str_value - raise vol.Invalid("Entity ID {} is an invalid entity id".format(value)) + raise vol.Invalid(f"Entity ID {value} is an invalid entity id") def entity_ids(value: Union[str, List]) -> List[str]: @@ -256,9 +254,7 @@ def entities_domain(domain: str) -> Callable[[Union[str, List]], List[str]]: for ent_id in values: if split_entity_id(ent_id)[0] != domain: raise vol.Invalid( - "Entity ID '{}' does not belong to domain '{}'".format( - ent_id, domain - ) + f"Entity ID '{ent_id}' does not belong to domain '{domain}'" ) return values @@ -307,7 +303,7 @@ def time(value: Any) -> time_sys: raise vol.Invalid("Not a parseable type") if time_val is None: - raise vol.Invalid("Invalid time specified: {}".format(value)) + raise vol.Invalid(f"Invalid time specified: {value}") return time_val @@ -368,7 +364,7 @@ def time_period_seconds(value: Union[int, str]) -> timedelta: try: return timedelta(seconds=int(value)) except (ValueError, TypeError): - raise vol.Invalid("Expected seconds, got {}".format(value)) + raise vol.Invalid(f"Expected seconds, got {value}") time_period = vol.Any(time_period_str, time_period_seconds, timedelta, time_period_dict) @@ -400,7 +396,7 @@ def service(value: Any) -> str: str_value = string(value).lower() if valid_entity_id(str_value): return str_value - raise vol.Invalid("Service {} does not match format .".format(value)) + raise vol.Invalid(f"Service {value} does not match format .") def schema_with_slug_keys(value_schema: Union[T, Callable]) -> Callable: @@ -432,7 +428,7 @@ def slug(value: Any) -> str: slg = util_slugify(str_value) if str_value == slg: return str_value - raise vol.Invalid("invalid slug {} (try {})".format(value, slg)) + raise vol.Invalid(f"invalid slug {value} (try {slg})") def slugify(value: Any) -> str: @@ -442,7 +438,7 @@ def slugify(value: Any) -> str: slg = util_slugify(str(value)) if slg: return slg - raise vol.Invalid("Unable to slugify {}".format(value)) + raise vol.Invalid(f"Unable to slugify {value}") def string(value: Any) -> str: @@ -484,7 +480,7 @@ def template(value: Optional[Any]) -> template_helper.Template: template_value.ensure_valid() return cast(template_helper.Template, template_value) except TemplateError as ex: - raise vol.Invalid("invalid template ({})".format(ex)) + raise vol.Invalid(f"invalid template ({ex})") def template_complex(value: Any) -> Any: @@ -515,7 +511,7 @@ def datetime(value: Any) -> datetime_sys: date_val = None if date_val is None: - raise vol.Invalid("Invalid datetime specified: {}".format(value)) + raise vol.Invalid(f"Invalid datetime specified: {value}") return date_val @@ -545,8 +541,8 @@ def socket_timeout(value: Optional[Any]) -> object: 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=_)) + except Exception as err: + raise vol.Invalid(f"Invalid socket timeout: {err}") # pylint: disable=no-value-for-parameter @@ -700,8 +696,8 @@ def key_dependency( raise vol.Invalid("key dependencies require a dict") if key in value and dependency not in value: raise vol.Invalid( - 'dependency violation - key "{}" requires ' - 'key "{}" to exist'.format(key, dependency) + f'dependency violation - key "{key}" requires ' + f'key "{dependency}" to exist' ) return value diff --git a/homeassistant/helpers/discovery.py b/homeassistant/helpers/discovery.py index a6162dbde55..806540e57ce 100644 --- a/homeassistant/helpers/discovery.py +++ b/homeassistant/helpers/discovery.py @@ -65,7 +65,7 @@ def discover(hass, service, discovered, component, hass_config): async def async_discover(hass, service, discovered, component, hass_config): """Fire discovery event. Can ensure a component is loaded.""" if component in DEPENDENCY_BLACKLIST: - raise HomeAssistantError("Cannot discover the {} component.".format(component)) + raise HomeAssistantError(f"Cannot discover the {component} component.") if component is not None and component not in hass.config.components: await setup.async_setup_component(hass, component, hass_config) @@ -151,7 +151,7 @@ async def async_load_platform(hass, component, platform, discovered, hass_config assert hass_config, "You need to pass in the real hass config" if component in DEPENDENCY_BLACKLIST: - raise HomeAssistantError("Cannot discover the {} component.".format(component)) + raise HomeAssistantError(f"Cannot discover the {component} component.") setup_success = True diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index b1786130b58..b7c806950a0 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -562,7 +562,7 @@ class Entity(ABC): def __repr__(self) -> str: """Return the representation.""" - return "".format(self.name, self.state) + return f"" # call an requests async def async_request_call(self, coro): diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 9b82eb76dec..82236875ea4 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -304,9 +304,7 @@ class EntityPlatform: suggested_object_id = entity.name if self.entity_namespace is not None: - suggested_object_id = "{} {}".format( - self.entity_namespace, suggested_object_id - ) + suggested_object_id = f"{self.entity_namespace} {suggested_object_id}" if self.config_entry is not None: config_entry_id = self.config_entry.entry_id @@ -380,9 +378,7 @@ class EntityPlatform: ) if self.entity_namespace is not None: - suggested_object_id = "{} {}".format( - self.entity_namespace, suggested_object_id - ) + suggested_object_id = f"{self.entity_namespace} {suggested_object_id}" entity.entity_id = entity_registry.async_generate_entity_id( self.domain, suggested_object_id, self.entities.keys() ) @@ -402,9 +398,7 @@ class EntityPlatform: if already_exists: msg = f"Entity id already exists: {entity.entity_id}" if entity.unique_id is not None: - msg += ". Platform {} does not generate unique IDs".format( - self.platform_name - ) + msg += f". Platform {self.platform_name} does not generate unique IDs" raise HomeAssistantError(msg) entity_id = entity.entity_id diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 77d8ccc00e0..2673162a841 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -323,9 +323,8 @@ class EntityRegistry: ) if conflict: raise ValueError( - "Unique id '{}' is already in use by '{}'".format( - new_unique_id, conflict.entity_id - ) + f"Unique id '{new_unique_id}' is already in use by " + f"'{conflict.entity_id}'" ) changes["unique_id"] = new_unique_id diff --git a/homeassistant/helpers/icon.py b/homeassistant/helpers/icon.py index b2a1d58717b..dd64e9c92f1 100644 --- a/homeassistant/helpers/icon.py +++ b/homeassistant/helpers/icon.py @@ -8,7 +8,7 @@ def icon_for_battery_level( """Return a battery icon valid identifier.""" icon = "mdi:battery" if battery_level is None: - return icon + "-unknown" + return f"{icon}-unknown" if charging and battery_level > 10: icon += "-charging-{}".format(int(round(battery_level / 20 - 0.01)) * 20) elif charging: diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index 181d1baebc0..8fdf617e3f6 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -158,7 +158,7 @@ class IntentHandler: def __repr__(self) -> str: """Represent a string of an intent handler.""" - return "<{} - {}>".format(self.__class__.__name__, self.intent_type) + return f"<{self.__class__.__name__} - {self.intent_type}>" def _fuzzymatch(name: str, items: Iterable[T], key: Callable[[T], str]) -> Optional[T]: diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 8e0faa2ce4d..837a561181d 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -299,7 +299,7 @@ class Script: _LOGGER.error("Error rendering '%s' delay template: %s", self.name, ex) raise _StopScript - self.last_action = action.get(CONF_ALIAS, "delay {}".format(delay)) + self.last_action = action.get(CONF_ALIAS, f"delay {delay}") self._log("Executing step %s" % self.last_action) unsub = async_track_point_in_utc_time( @@ -408,7 +408,7 @@ class Script: self.last_action = action.get(CONF_ALIAS, action[CONF_CONDITION]) check = config(self.hass, variables) - self._log("Test condition {}: {}".format(self.last_action, check)) + self._log(f"Test condition {self.last_action}: {check}") if not check: raise _StopScript @@ -446,6 +446,6 @@ class Script: def _log(self, msg): """Logger helper.""" if self.name is not None: - msg = "Script {}: {}".format(self.name, msg) + msg = f"Script {self.name}: {msg}" _LOGGER.info(msg) diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index 5381f765993..16fabe251af 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -240,7 +240,7 @@ def async_set_service_schema(hass, domain, service, schema): "fields": schema.get("fields") or {}, } - hass.data[SERVICE_DESCRIPTION_CACHE]["{}.{}".format(domain, service)] = description + hass.data[SERVICE_DESCRIPTION_CACHE][f"{domain}.{service}"] = description @bind_hass diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index b27120e1825..8565315f87f 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -328,7 +328,7 @@ class AllStates: if not valid_entity_id(name): raise TemplateError(f"Invalid entity ID '{name}'") return _get_state(self._hass, name) - if not valid_entity_id(name + ".entity"): + if not valid_entity_id(f"{name}.entity"): raise TemplateError(f"Invalid domain name '{name}'") return DomainStates(self._hass, name) @@ -451,7 +451,7 @@ class TemplateState(State): """Representation of Template State.""" state = object.__getattribute__(self, "_access_state")() rep = state.__repr__() - return "