From 69f942d7dea538be1d443a1383efe31b11b4c608 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Sun, 12 Jul 2020 20:49:30 +0000 Subject: [PATCH 01/57] Add configuration flow for Buienradar integration --- CODEOWNERS | 2 +- .../components/buienradar/__init__.py | 45 +++- homeassistant/components/buienradar/camera.py | 41 ++-- .../components/buienradar/config_flow.py | 203 ++++++++++++++++++ homeassistant/components/buienradar/const.py | 20 ++ .../components/buienradar/manifest.json | 3 +- homeassistant/components/buienradar/sensor.py | 40 ++-- .../components/buienradar/strings.json | 36 ++++ .../buienradar/translations/en.json | 42 ++++ .../components/buienradar/weather.py | 24 +-- homeassistant/generated/config_flows.py | 1 + .../components/buienradar/test_config_flow.py | 90 ++++++++ 12 files changed, 472 insertions(+), 75 deletions(-) create mode 100644 homeassistant/components/buienradar/config_flow.py create mode 100644 homeassistant/components/buienradar/strings.json create mode 100644 homeassistant/components/buienradar/translations/en.json create mode 100644 tests/components/buienradar/test_config_flow.py diff --git a/CODEOWNERS b/CODEOWNERS index 6bb6221ec3267..a8ecbcdd595ee 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -76,7 +76,7 @@ homeassistant/components/brother/* @bieniu homeassistant/components/brunt/* @eavanvalkenburg homeassistant/components/bsblan/* @liudger homeassistant/components/bt_smarthub/* @jxwolstenholme -homeassistant/components/buienradar/* @mjj4791 @ties +homeassistant/components/buienradar/* @mjj4791 @ties @Robbie1221 homeassistant/components/cast/* @emontnemery homeassistant/components/cert_expiry/* @Cereal2nd @jjlawren homeassistant/components/circuit/* @braam diff --git a/homeassistant/components/buienradar/__init__.py b/homeassistant/components/buienradar/__init__.py index 680351f9b817b..0ae9702b8efe9 100644 --- a/homeassistant/components/buienradar/__init__.py +++ b/homeassistant/components/buienradar/__init__.py @@ -1 +1,44 @@ -"""The buienradar component.""" +"""The buienradar integration.""" +import asyncio + +import voluptuous as vol + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_INCLUDE +from homeassistant.core import HomeAssistant + +from .const import CONF_CAMERA, CONF_SENSOR, CONF_WEATHER, DOMAIN + +CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA) + +PLATFORMS = [CONF_WEATHER, CONF_CAMERA, CONF_SENSOR] + + +async def async_setup(hass: HomeAssistant, config: dict): + """Set up the buienradar component.""" + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): + """Set up buienradar2 from a config entry.""" + for component in PLATFORMS: + if entry.data[component][CONF_INCLUDE]: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, component) + ) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): + """Unload a config entry.""" + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, component) + for component in PLATFORMS + ] + ) + ) + + return unload_ok diff --git a/homeassistant/components/buienradar/camera.py b/homeassistant/components/buienradar/camera.py index 92f25b7ffc679..50e7778120c0c 100644 --- a/homeassistant/components/buienradar/camera.py +++ b/homeassistant/components/buienradar/camera.py @@ -6,46 +6,29 @@ import logging import aiohttp -import voluptuous as vol -from homeassistant.components.camera import PLATFORM_SCHEMA, Camera +from homeassistant.components.camera import Camera +from homeassistant.config_entries import ConfigEntry 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.helpers.typing import HomeAssistantType from homeassistant.util import dt as dt_util -CONF_DIMENSION = "dimension" -CONF_DELTA = "delta" -CONF_COUNTRY = "country_code" +from .const import CONF_CAMERA, CONF_COUNTRY, CONF_DELTA, CONF_DIMENSION _LOGGER = logging.getLogger(__name__) -# Maximum range according to docs -DIM_RANGE = vol.All(vol.Coerce(int), vol.Range(min=120, max=700)) -# Multiple choice for available Radar Map URL -SUPPORTED_COUNTRY_CODES = ["NL", "BE"] - -PLATFORM_SCHEMA = vol.All( - PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_DIMENSION, default=512): DIM_RANGE, - vol.Optional(CONF_DELTA, default=600.0): cv.positive_float, - vol.Optional(CONF_NAME, default="Buienradar loop"): cv.string, - vol.Optional(CONF_COUNTRY, default="NL"): vol.All( - vol.Coerce(str), vol.In(SUPPORTED_COUNTRY_CODES) - ), - } - ) -) - - -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): +async def async_setup_entry( + hass: HomeAssistantType, entry: ConfigEntry, async_add_entities +) -> None: """Set up buienradar radar-loop camera component.""" - dimension = config[CONF_DIMENSION] - delta = config[CONF_DELTA] + config = entry.data + + dimension = config[CONF_CAMERA][CONF_DIMENSION] + delta = config[CONF_CAMERA][CONF_DELTA] name = config[CONF_NAME] - country = config[CONF_COUNTRY] + country = config[CONF_CAMERA][CONF_COUNTRY] async_add_entities([BuienradarCam(name, dimension, delta, country)]) diff --git a/homeassistant/components/buienradar/config_flow.py b/homeassistant/components/buienradar/config_flow.py new file mode 100644 index 0000000000000..489d1503501f9 --- /dev/null +++ b/homeassistant/components/buienradar/config_flow.py @@ -0,0 +1,203 @@ +"""Config flow for buienradar2 integration.""" +import logging + +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_INCLUDE, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME +from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv + +from .const import ( + CAMERA_DIM_MAX, + CAMERA_DIM_MIN, + CONF_CAMERA, + CONF_COUNTRY, + CONF_DELTA, + CONF_DIMENSION, + CONF_FORECAST, + CONF_SENSOR, + CONF_TIMEFRAME, + CONF_WEATHER, + DOMAIN, + HOME_LOCATION_NAME, + SUPPORTED_COUNTRY_CODES, +) + +_LOGGER = logging.getLogger(__name__) + + +@callback +def configured_instances(hass): + """Return a set of configured SimpliSafe instances.""" + entries = [] + for entry in hass.config_entries.async_entries(DOMAIN): + entries.append( + f"{entry.data.get(CONF_LATITUDE)}-{entry.data.get(CONF_LONGITUDE)}" + ) + return set(entries) + + +class BuienradarFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for buienradar2.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + + def __init__(self): + """Init MetFlowHandler.""" + self._errors = {} + + self._weather = False + self._camera = False + self._sensor = False + + self._name = None + self._latitude = None + self._longitude = None + + self._weatherdata = {} + self._cameradata = {} + self._sensordata = {} + + async def async_step_user(self, user_input=None): + """Handle a flow initialized by the user.""" + self._errors = {} + + if user_input is not None: + if ( + f"{user_input.get(CONF_LATITUDE)}-{user_input.get(CONF_LONGITUDE)}" + not in configured_instances(self.hass) + ): + self._name = user_input[CONF_NAME] + + self._weather = user_input[CONF_WEATHER] + self._camera = user_input[CONF_CAMERA] + self._sensor = user_input[CONF_SENSOR] + + self._latitude = user_input[CONF_LATITUDE] + self._longitude = user_input[CONF_LONGITUDE] + + if self._weather: + return await self.async_step_setup_weather() + if self._camera: + return await self.async_step_setup_camera() + if self._sensor: + return await self.async_step_setup_sensor() + + self._errors["base"] = "empty_selection" + else: + self._errors[CONF_NAME] = "name_exists" + + return await self._show_config_form( + name=HOME_LOCATION_NAME, + latitude=self.hass.config.latitude, + longitude=self.hass.config.longitude, + ) + + async def async_step_setup_weather(self, user_input=None): + """Handle step to configure weather platform.""" + self._errors = {} + + if user_input is not None: + self._weatherdata = user_input + + if self._camera: + return await self.async_step_setup_camera() + if self._sensor: + return await self.async_step_setup_sensor() + + return await self._configure() + + return self.async_show_form( + step_id="setup_weather", + data_schema=vol.Schema({vol.Required(CONF_FORECAST, default=True): bool}), + errors=self._errors, + ) + + async def async_step_setup_camera(self, user_input=None): + """Handle step to configure camera platform.""" + self._errors = {} + + if user_input is not None: + self._cameradata = user_input + + if self._sensor: + return await self.async_step_setup_sensor() + + return await self._configure() + + return self.async_show_form( + step_id="setup_camera", + data_schema=vol.Schema( + { + vol.Required(CONF_DIMENSION, default=512): vol.All( + vol.Coerce(int), + vol.Range(min=CAMERA_DIM_MIN, max=CAMERA_DIM_MAX), + ), + vol.Required(CONF_DELTA, default=600.0): vol.All( + vol.Coerce(int), vol.Range(min=0) + ), + vol.Required(CONF_COUNTRY, default="NL"): vol.All( + vol.Coerce(str), vol.In(SUPPORTED_COUNTRY_CODES) + ), + } + ), + errors=self._errors, + ) + + async def async_step_setup_sensor(self, user_input=None): + """Handle step to configure sensor platform.""" + self._errors = {} + + if user_input is not None: + self._sensordata = user_input + + return await self._configure() + + return self.async_show_form( + step_id="setup_sensor", + data_schema=vol.Schema( + { + vol.Required(CONF_TIMEFRAME, default=60): vol.All( + vol.Coerce(int), vol.Range(min=5, max=120) + ), + } + ), + errors=self._errors, + ) + + async def _show_config_form( + self, name=None, latitude=None, longitude=None, elevation=None + ): + """Show the configuration form to edit location data.""" + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Required(CONF_NAME, default=name): str, + vol.Required(CONF_LATITUDE, default=latitude): cv.latitude, + vol.Required(CONF_LONGITUDE, default=longitude): cv.longitude, + vol.Required(CONF_WEATHER, default=False): bool, + vol.Required(CONF_CAMERA, default=False): bool, + vol.Required(CONF_SENSOR, default=False): bool, + } + ), + errors=self._errors, + ) + + async def _configure(self): + data = { + CONF_NAME: self._name, + CONF_LATITUDE: self._latitude, + CONF_LONGITUDE: self._longitude, + CONF_WEATHER: self._weatherdata, + CONF_CAMERA: self._cameradata, + CONF_SENSOR: self._sensordata, + } + + data[CONF_WEATHER][CONF_INCLUDE] = self._weather + data[CONF_CAMERA][CONF_INCLUDE] = self._camera + data[CONF_SENSOR][CONF_INCLUDE] = self._sensor + + return self.async_create_entry(title=self._name, data=data) diff --git a/homeassistant/components/buienradar/const.py b/homeassistant/components/buienradar/const.py index b91d2497d77e3..9364a16b283a1 100644 --- a/homeassistant/components/buienradar/const.py +++ b/homeassistant/components/buienradar/const.py @@ -1,6 +1,26 @@ """Constants for buienradar component.""" + +DOMAIN = "buienradar" + DEFAULT_TIMEFRAME = 60 +HOME_LOCATION_NAME = "Home" + +CONF_WEATHER = "weather" +CONF_CAMERA = "camera" +CONF_SENSOR = "sensor" +CONF_FORECAST = "forecast" +CONF_DIMENSION = "dimension" +CONF_DELTA = "delta" +CONF_COUNTRY = "country_code" +CONF_TIMEFRAME = "timeframe" + +"""Range according to the docs""" +CAMERA_DIM_MIN = 120 +CAMERA_DIM_MAX = 700 + +SUPPORTED_COUNTRY_CODES = ["NL", "BE"] + """Schedule next call after (minutes).""" SCHEDULE_OK = 10 """When an error occurred, new call after (minutes).""" diff --git a/homeassistant/components/buienradar/manifest.json b/homeassistant/components/buienradar/manifest.json index bdaa4e166ee62..d7759aa9b8d61 100644 --- a/homeassistant/components/buienradar/manifest.json +++ b/homeassistant/components/buienradar/manifest.json @@ -1,8 +1,9 @@ { "domain": "buienradar", "name": "Buienradar", + "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/buienradar", "requirements": ["buienradar==1.0.4"], - "codeowners": ["@mjj4791", "@ties"], + "codeowners": ["@mjj4791", "@ties", "@Robbie1221"], "iot_class": "cloud_polling" } diff --git a/homeassistant/components/buienradar/sensor.py b/homeassistant/components/buienradar/sensor.py index 5ff15a509785f..4c3f897c50d6a 100644 --- a/homeassistant/components/buienradar/sensor.py +++ b/homeassistant/components/buienradar/sensor.py @@ -18,14 +18,13 @@ WINDGUST, WINDSPEED, ) -import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity +from homeassistant.components.sensor import SensorEntity +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_LATITUDE, CONF_LONGITUDE, - CONF_MONITORED_CONDITIONS, CONF_NAME, DEGREE, IRRADIATION_WATTS_PER_SQUARE_METER, @@ -37,11 +36,12 @@ SPEED_KILOMETERS_PER_HOUR, TEMP_CELSIUS, ) + from homeassistant.core import callback -import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import dt as dt_util -from .const import DEFAULT_TIMEFRAME +from .const import CONF_SENSOR, CONF_TIMEFRAME from .util import BrData _LOGGER = logging.getLogger(__name__) @@ -186,32 +186,16 @@ "symbol_5d": ["Symbol 5d", None, None], } -CONF_TIMEFRAME = "timeframe" - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Optional( - CONF_MONITORED_CONDITIONS, default=["symbol", "temperature"] - ): vol.All(cv.ensure_list, vol.Length(min=1), [vol.In(SENSOR_TYPES.keys())]), - vol.Inclusive( - CONF_LATITUDE, "coordinates", "Latitude and longitude must exist together" - ): cv.latitude, - vol.Inclusive( - CONF_LONGITUDE, "coordinates", "Latitude and longitude must exist together" - ): cv.longitude, - vol.Optional(CONF_TIMEFRAME, default=DEFAULT_TIMEFRAME): vol.All( - vol.Coerce(int), vol.Range(min=5, max=120) - ), - vol.Optional(CONF_NAME, default="br"): cv.string, - } -) - -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): +async def async_setup_entry( + hass: HomeAssistantType, entry: ConfigEntry, async_add_entities +) -> None: """Create the buienradar sensor.""" + config = entry.data + latitude = config.get(CONF_LATITUDE, hass.config.latitude) longitude = config.get(CONF_LONGITUDE, hass.config.longitude) - timeframe = config[CONF_TIMEFRAME] + timeframe = config[CONF_SENSOR][CONF_TIMEFRAME] if None in (latitude, longitude): _LOGGER.error("Latitude or longitude not set in Home Assistant config") @@ -226,7 +210,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) dev = [] - for sensor_type in config[CONF_MONITORED_CONDITIONS]: + for sensor_type in SENSOR_TYPES: dev.append(BrSensor(sensor_type, config.get(CONF_NAME), coordinates)) async_add_entities(dev) diff --git a/homeassistant/components/buienradar/strings.json b/homeassistant/components/buienradar/strings.json new file mode 100644 index 0000000000000..d2f7042504fbf --- /dev/null +++ b/homeassistant/components/buienradar/strings.json @@ -0,0 +1,36 @@ +{ + "config": { + "step": { + "user": { + "title": "Buienradar", + "data": { + "name": "Name", + "latitude": "Latitude", + "longitude": "Longitude" + } + }, + "setup_weather": { + "title": "Buienradar Weather", + "data": { + "forecast": "Include temperature forecast" + } + }, + "setup_camera": { + "title": "Buienradar Camera", + "data": { + "dimension": "Image Size", + "delta": "Interval between image updates", + "country_code": "Country" + } + }, + "setup_sensor": { + "title": "Buienradar Sensors", + "data": { + "timeframe": "Minutes to look ahead for precipitation forecast" + } + } + }, + "error": { "name_exists": "Location already exists" } + }, + "title": "Buienradar" +} diff --git a/homeassistant/components/buienradar/translations/en.json b/homeassistant/components/buienradar/translations/en.json new file mode 100644 index 0000000000000..11e09a0b48e01 --- /dev/null +++ b/homeassistant/components/buienradar/translations/en.json @@ -0,0 +1,42 @@ +{ + "title": "Buienradar", + "config": { + "error": { + "name_exists": "Location already exists", + "empty_selection": "Select at least one platform to setup" + }, + "step": { + "user": { + "data": { + "latitude": "Latitude", + "longitude": "Longitude", + "name": "Name", + "weather": "Setup buienradar weather", + "camera": "Setup buienradar camera", + "sensor": "Setup buienradar sensors" + }, + "title": "Buienradar" + }, + "setup_weather": { + "title": "Buienradar Weather", + "data": { + "forecast": "Include temperature forecast" + } + }, + "setup_camera": { + "data": { + "dimension": "Image Size [px]", + "delta": "Interval between image updates [s]", + "country_code": "Country" + }, + "title": "Buienradar Camera" + }, + "setup_sensor": { + "title": "Buienradar Sensors", + "data": { + "timeframe": "Minutes to look ahead for precipitation forecast" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/buienradar/weather.py b/homeassistant/components/buienradar/weather.py index 2ff638a255091..1b5a433027363 100644 --- a/homeassistant/components/buienradar/weather.py +++ b/homeassistant/components/buienradar/weather.py @@ -11,7 +11,6 @@ WINDAZIMUTH, WINDSPEED, ) -import voluptuous as vol from homeassistant.components.weather import ( ATTR_CONDITION_CLOUDY, @@ -35,14 +34,14 @@ ATTR_FORECAST_TIME, ATTR_FORECAST_WIND_BEARING, ATTR_FORECAST_WIND_SPEED, - PLATFORM_SCHEMA, WeatherEntity, ) +from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, TEMP_CELSIUS -from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.typing import HomeAssistantType # Reuse data and API logic from the sensor implementation -from .const import DEFAULT_TIMEFRAME +from .const import CONF_WEATHER, DEFAULT_TIMEFRAME from .util import BrData _LOGGER = logging.getLogger(__name__) @@ -70,18 +69,13 @@ ATTR_CONDITION_EXCEPTIONAL: [], } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_LATITUDE): cv.latitude, - vol.Optional(CONF_LONGITUDE): cv.longitude, - vol.Optional(CONF_FORECAST, default=True): cv.boolean, - } -) - -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): +async def async_setup_entry( + hass: HomeAssistantType, entry: ConfigEntry, async_add_entities +) -> None: """Set up the buienradar platform.""" + config = entry.data + latitude = config.get(CONF_LATITUDE, hass.config.latitude) longitude = config.get(CONF_LONGITUDE, hass.config.longitude) @@ -116,7 +110,7 @@ class BrWeather(WeatherEntity): def __init__(self, data, config, coordinates): """Initialise the platform with a data instance and station name.""" self._stationname = config.get(CONF_NAME) - self._forecast = config[CONF_FORECAST] + self._forecast = config[CONF_WEATHER][CONF_FORECAST] self._data = data self._unique_id = "{:2.6f}{:2.6f}".format( diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 3b408860d59a2..83360295c480b 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -38,6 +38,7 @@ "brother", "bsblan", "canary", + "buienradar", "cast", "cert_expiry", "climacell", diff --git a/tests/components/buienradar/test_config_flow.py b/tests/components/buienradar/test_config_flow.py new file mode 100644 index 0000000000000..963876c42a1b7 --- /dev/null +++ b/tests/components/buienradar/test_config_flow.py @@ -0,0 +1,90 @@ +"""Test the buienradar2 config flow.""" +from homeassistant import config_entries, setup +from homeassistant.components.buienradar2.config_flow import CannotConnect, InvalidAuth +from homeassistant.components.buienradar2.const import DOMAIN + +from tests.async_mock import patch + + +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.buienradar2.config_flow.PlaceholderHub.authenticate", + return_value=True, + ), patch( + "homeassistant.components.buienradar2.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.buienradar2.async_setup_entry", return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "host": "1.1.1.1", + "username": "test-username", + "password": "test-password", + }, + ) + + assert result2["type"] == "create_entry" + assert result2["title"] == "Name of the device" + assert result2["data"] == { + "host": "1.1.1.1", + "username": "test-username", + "password": "test-password", + } + 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.buienradar2.config_flow.PlaceholderHub.authenticate", + side_effect=InvalidAuth, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "host": "1.1.1.1", + "username": "test-username", + "password": "test-password", + }, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "invalid_auth"} + + +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.buienradar2.config_flow.PlaceholderHub.authenticate", + side_effect=CannotConnect, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "host": "1.1.1.1", + "username": "test-username", + "password": "test-password", + }, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "cannot_connect"} From 28d1df7be8f53e859288a7ccf6bbe2f21e27b570 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Mon, 13 Jul 2020 18:22:34 +0000 Subject: [PATCH 02/57] Update buienradar camera tests to work with config flow --- tests/components/buienradar/test_camera.py | 153 ++++++++++++++------- 1 file changed, 101 insertions(+), 52 deletions(-) diff --git a/tests/components/buienradar/test_camera.py b/tests/components/buienradar/test_camera.py index c9c6d7b479397..c9cb3e26179dd 100644 --- a/tests/components/buienradar/test_camera.py +++ b/tests/components/buienradar/test_camera.py @@ -1,16 +1,39 @@ """The tests for generic camera component.""" import asyncio from contextlib import suppress +import copy from aiohttp.client_exceptions import ClientResponseError -from homeassistant.const import HTTP_INTERNAL_SERVER_ERROR -from homeassistant.setup import async_setup_component +from homeassistant.components.buienradar.const import ( + CONF_CAMERA, + CONF_COUNTRY, + CONF_DELTA, + CONF_DIMENSION, + CONF_SENSOR, + CONF_WEATHER, + DOMAIN, +) +from homeassistant.const import CONF_INCLUDE, CONF_NAME, HTTP_INTERNAL_SERVER_ERROR from homeassistant.util import dt as dt_util +from tests.common import MockConfigEntry + # An infinitesimally small time-delta. EPSILON_DELTA = 0.0000000001 +TEST_CFG_DATA = { + CONF_NAME: "config_test", + CONF_CAMERA: { + CONF_INCLUDE: True, + CONF_DIMENSION: 512, + CONF_DELTA: 600, + CONF_COUNTRY: "NL", + }, + CONF_SENSOR: {CONF_INCLUDE: False}, + CONF_WEATHER: {CONF_INCLUDE: False}, +} + def radar_map_url(dim: int = 512, country_code: str = "NL") -> str: """Build map url, defaulting to 512 wide (as in component).""" @@ -21,9 +44,11 @@ async def test_fetching_url_and_caching(aioclient_mock, hass, hass_client): """Test that it fetches the given url.""" aioclient_mock.get(radar_map_url(), text="hello world") - await async_setup_component( - hass, "camera", {"camera": {"name": "config_test", "platform": "buienradar"}} - ) + mock_entry = MockConfigEntry(domain=DOMAIN, unique_id="TEST_ID", data=TEST_CFG_DATA) + + mock_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_entry.entry_id) await hass.async_block_till_done() client = await hass_client() @@ -41,22 +66,22 @@ async def test_fetching_url_and_caching(aioclient_mock, hass, hass_client): resp = await client.get("/api/camera_proxy/camera.config_test") assert aioclient_mock.call_count == 1 + await hass.config_entries.async_unload(mock_entry.entry_id) + await hass.async_block_till_done() + async def test_expire_delta(aioclient_mock, hass, hass_client): """Test that the cache expires after delta.""" aioclient_mock.get(radar_map_url(), text="hello world") - await async_setup_component( - hass, - "camera", - { - "camera": { - "name": "config_test", - "platform": "buienradar", - "delta": EPSILON_DELTA, - } - }, - ) + data = copy.deepcopy(TEST_CFG_DATA) + data[CONF_CAMERA][CONF_DELTA] = EPSILON_DELTA + + mock_entry = MockConfigEntry(domain=DOMAIN, unique_id="TEST_ID", data=data) + + mock_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_entry.entry_id) await hass.async_block_till_done() client = await hass_client() @@ -73,14 +98,19 @@ async def test_expire_delta(aioclient_mock, hass, hass_client): resp = await client.get("/api/camera_proxy/camera.config_test") assert aioclient_mock.call_count == 2 + await hass.config_entries.async_unload(mock_entry.entry_id) + await hass.async_block_till_done() + async def test_only_one_fetch_at_a_time(aioclient_mock, hass, hass_client): """Test that it fetches with only one request at the same time.""" aioclient_mock.get(radar_map_url(), text="hello world") - await async_setup_component( - hass, "camera", {"camera": {"name": "config_test", "platform": "buienradar"}} - ) + mock_entry = MockConfigEntry(domain=DOMAIN, unique_id="TEST_ID", data=TEST_CFG_DATA) + + mock_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_entry.entry_id) await hass.async_block_till_done() client = await hass_client() @@ -95,16 +125,22 @@ async def test_only_one_fetch_at_a_time(aioclient_mock, hass, hass_client): assert aioclient_mock.call_count == 1 + await hass.config_entries.async_unload(mock_entry.entry_id) + await hass.async_block_till_done() + async def test_dimension(aioclient_mock, hass, hass_client): """Test that it actually adheres to the dimension.""" aioclient_mock.get(radar_map_url(700), text="hello world") - await async_setup_component( - hass, - "camera", - {"camera": {"name": "config_test", "platform": "buienradar", "dimension": 700}}, - ) + data = copy.deepcopy(TEST_CFG_DATA) + data[CONF_CAMERA][CONF_DIMENSION] = 700 + + mock_entry = MockConfigEntry(domain=DOMAIN, unique_id="TEST_ID", data=data) + + mock_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_entry.entry_id) await hass.async_block_till_done() client = await hass_client() @@ -113,22 +149,22 @@ async def test_dimension(aioclient_mock, hass, hass_client): assert aioclient_mock.call_count == 1 + await hass.config_entries.async_unload(mock_entry.entry_id) + await hass.async_block_till_done() + async def test_belgium_country(aioclient_mock, hass, hass_client): """Test that it actually adheres to another country like Belgium.""" aioclient_mock.get(radar_map_url(country_code="BE"), text="hello world") - await async_setup_component( - hass, - "camera", - { - "camera": { - "name": "config_test", - "platform": "buienradar", - "country_code": "BE", - } - }, - ) + data = copy.deepcopy(TEST_CFG_DATA) + data[CONF_CAMERA][CONF_COUNTRY] = "BE" + + mock_entry = MockConfigEntry(domain=DOMAIN, unique_id="TEST_ID", data=data) + + mock_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_entry.entry_id) await hass.async_block_till_done() client = await hass_client() @@ -137,14 +173,19 @@ async def test_belgium_country(aioclient_mock, hass, hass_client): assert aioclient_mock.call_count == 1 + await hass.config_entries.async_unload(mock_entry.entry_id) + await hass.async_block_till_done() + async def test_failure_response_not_cached(aioclient_mock, hass, hass_client): """Test that it does not cache a failure response.""" aioclient_mock.get(radar_map_url(), text="hello world", status=401) - await async_setup_component( - hass, "camera", {"camera": {"name": "config_test", "platform": "buienradar"}} - ) + mock_entry = MockConfigEntry(domain=DOMAIN, unique_id="TEST_ID", data=TEST_CFG_DATA) + + mock_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_entry.entry_id) await hass.async_block_till_done() client = await hass_client() @@ -154,6 +195,9 @@ async def test_failure_response_not_cached(aioclient_mock, hass, hass_client): assert aioclient_mock.call_count == 2 + await hass.config_entries.async_unload(mock_entry.entry_id) + await hass.async_block_till_done() + async def test_last_modified_updates(aioclient_mock, hass, hass_client): """Test that it does respect HTTP not modified.""" @@ -168,17 +212,14 @@ async def test_last_modified_updates(aioclient_mock, hass, hass_client): headers={"Last-Modified": last_modified}, ) - await async_setup_component( - hass, - "camera", - { - "camera": { - "name": "config_test", - "platform": "buienradar", - "delta": EPSILON_DELTA, - } - }, - ) + data = copy.deepcopy(TEST_CFG_DATA) + data[CONF_CAMERA][CONF_DELTA] = EPSILON_DELTA + + mock_entry = MockConfigEntry(domain=DOMAIN, unique_id="TEST_ID", data=data) + + mock_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_entry.entry_id) await hass.async_block_till_done() client = await hass_client() @@ -202,12 +243,17 @@ async def test_last_modified_updates(aioclient_mock, hass, hass_client): assert (await resp_1.read()) == (await resp_2.read()) + await hass.config_entries.async_unload(mock_entry.entry_id) + await hass.async_block_till_done() + async def test_retries_after_error(aioclient_mock, hass, hass_client): """Test that it does retry after an error instead of caching.""" - await async_setup_component( - hass, "camera", {"camera": {"name": "config_test", "platform": "buienradar"}} - ) + mock_entry = MockConfigEntry(domain=DOMAIN, unique_id="TEST_ID", data=TEST_CFG_DATA) + + mock_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_entry.entry_id) await hass.async_block_till_done() client = await hass_client() @@ -233,3 +279,6 @@ async def test_retries_after_error(aioclient_mock, hass, hass_client): # Binary text can not be added as body to `aioclient_mock.get(text=...)`, # while `resp.read()` returns bytes, encode the value. assert (await resp_2.read()) == b"DEADBEEF" + + await hass.config_entries.async_unload(mock_entry.entry_id) + await hass.async_block_till_done() From e7404dd181f2bf131961e9f58a53575ac68adfcd Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Mon, 13 Jul 2020 18:30:02 +0000 Subject: [PATCH 03/57] Update buienradar weather tests to work with config flow --- tests/components/buienradar/test_weather.py | 41 +++++++++++++-------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/tests/components/buienradar/test_weather.py b/tests/components/buienradar/test_weather.py index db0a6ce398449..3d6b27172ec14 100644 --- a/tests/components/buienradar/test_weather.py +++ b/tests/components/buienradar/test_weather.py @@ -1,25 +1,36 @@ """The tests for the buienradar weather component.""" -from homeassistant.components import weather -from homeassistant.setup import async_setup_component - -# Example config snippet from documentation. -BASE_CONFIG = { - "weather": [ - { - "platform": "buienradar", - "name": "volkel", - "latitude": 51.65, - "longitude": 5.7, - "forecast": True, - } - ] +from homeassistant.components.buienradar.const import ( + CONF_CAMERA, + CONF_FORECAST, + CONF_SENSOR, + CONF_WEATHER, + DOMAIN, +) +from homeassistant.const import CONF_INCLUDE, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME + +from tests.common import MockConfigEntry + +TEST_CFG_DATA = { + CONF_NAME: "volkel", + CONF_LATITUDE: 51.65, + CONF_LONGITUDE: 5.7, + CONF_CAMERA: {CONF_INCLUDE: False}, + CONF_SENSOR: {CONF_INCLUDE: False}, + CONF_WEATHER: {CONF_INCLUDE: True, CONF_FORECAST: True}, } async def test_smoke_test_setup_component(hass): """Smoke test for successfully set-up with default config.""" - assert await async_setup_component(hass, weather.DOMAIN, BASE_CONFIG) + mock_entry = MockConfigEntry(domain=DOMAIN, unique_id="TEST_ID", data=TEST_CFG_DATA) + + mock_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_entry.entry_id) await hass.async_block_till_done() state = hass.states.get("weather.volkel") assert state.state == "unknown" + + await hass.config_entries.async_unload(mock_entry.entry_id) + await hass.async_block_till_done() From 6cb7b82af215ed0c5d73a5c325d86182afb34fcc Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Mon, 13 Jul 2020 18:36:50 +0000 Subject: [PATCH 04/57] Update buienradar sensor tests to work with config flow --- tests/components/buienradar/test_sensor.py | 38 ++++++++++++++-------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/tests/components/buienradar/test_sensor.py b/tests/components/buienradar/test_sensor.py index 801f5706a0804..80ad167dfc34a 100644 --- a/tests/components/buienradar/test_sensor.py +++ b/tests/components/buienradar/test_sensor.py @@ -1,26 +1,38 @@ """The tests for the Buienradar sensor platform.""" -from homeassistant.components import sensor -from homeassistant.setup import async_setup_component +from homeassistant.components.buienradar.const import ( + CONF_CAMERA, + CONF_SENSOR, + CONF_TIMEFRAME, + CONF_WEATHER, + DOMAIN, +) +from homeassistant.const import CONF_INCLUDE, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME + +from tests.common import MockConfigEntry CONDITIONS = ["stationname", "temperature"] -BASE_CONFIG = { - "sensor": [ - { - "platform": "buienradar", - "name": "volkel", - "latitude": 51.65, - "longitude": 5.7, - "monitored_conditions": CONDITIONS, - } - ] +TEST_CFG_DATA = { + CONF_NAME: "volkel", + CONF_LATITUDE: 51.65, + CONF_LONGITUDE: 5.7, + CONF_CAMERA: {CONF_INCLUDE: False}, + CONF_SENSOR: {CONF_INCLUDE: True, CONF_TIMEFRAME: 60}, + CONF_WEATHER: {CONF_INCLUDE: False}, } async def test_smoke_test_setup_component(hass): """Smoke test for successfully set-up with default config.""" - assert await async_setup_component(hass, sensor.DOMAIN, BASE_CONFIG) + mock_entry = MockConfigEntry(domain=DOMAIN, unique_id="TEST_ID", data=TEST_CFG_DATA) + + mock_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_entry.entry_id) await hass.async_block_till_done() for cond in CONDITIONS: state = hass.states.get(f"sensor.volkel_{cond}") assert state.state == "unknown" + + await hass.config_entries.async_unload(mock_entry.entry_id) + await hass.async_block_till_done() From e140cb9d0f8fe2ebf38cde6f2968a4519aa9be75 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Mon, 13 Jul 2020 18:38:17 +0000 Subject: [PATCH 05/57] Remove buienradar config_flow tests to pass tests --- .../components/buienradar/test_config_flow.py | 89 ------------------- 1 file changed, 89 deletions(-) diff --git a/tests/components/buienradar/test_config_flow.py b/tests/components/buienradar/test_config_flow.py index 963876c42a1b7..de1c4931478d4 100644 --- a/tests/components/buienradar/test_config_flow.py +++ b/tests/components/buienradar/test_config_flow.py @@ -1,90 +1 @@ """Test the buienradar2 config flow.""" -from homeassistant import config_entries, setup -from homeassistant.components.buienradar2.config_flow import CannotConnect, InvalidAuth -from homeassistant.components.buienradar2.const import DOMAIN - -from tests.async_mock import patch - - -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.buienradar2.config_flow.PlaceholderHub.authenticate", - return_value=True, - ), patch( - "homeassistant.components.buienradar2.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.buienradar2.async_setup_entry", return_value=True, - ) as mock_setup_entry: - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - "host": "1.1.1.1", - "username": "test-username", - "password": "test-password", - }, - ) - - assert result2["type"] == "create_entry" - assert result2["title"] == "Name of the device" - assert result2["data"] == { - "host": "1.1.1.1", - "username": "test-username", - "password": "test-password", - } - 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.buienradar2.config_flow.PlaceholderHub.authenticate", - side_effect=InvalidAuth, - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - "host": "1.1.1.1", - "username": "test-username", - "password": "test-password", - }, - ) - - assert result2["type"] == "form" - assert result2["errors"] == {"base": "invalid_auth"} - - -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.buienradar2.config_flow.PlaceholderHub.authenticate", - side_effect=CannotConnect, - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - "host": "1.1.1.1", - "username": "test-username", - "password": "test-password", - }, - ) - - assert result2["type"] == "form" - assert result2["errors"] == {"base": "cannot_connect"} From 5faefce21edef93b7bf9fba0fc965f22419baa4c Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Mon, 13 Jul 2020 19:16:44 +0000 Subject: [PATCH 06/57] Add config flow tests for buienradar integration --- .../components/buienradar/test_config_flow.py | 326 ++++++++++++++++++ 1 file changed, 326 insertions(+) diff --git a/tests/components/buienradar/test_config_flow.py b/tests/components/buienradar/test_config_flow.py index de1c4931478d4..d5c4d1d49595d 100644 --- a/tests/components/buienradar/test_config_flow.py +++ b/tests/components/buienradar/test_config_flow.py @@ -1 +1,327 @@ """Test the buienradar2 config flow.""" +from homeassistant import config_entries +from homeassistant.components.buienradar.const import ( + CONF_CAMERA, + CONF_COUNTRY, + CONF_DELTA, + CONF_DIMENSION, + CONF_FORECAST, + CONF_SENSOR, + CONF_TIMEFRAME, + CONF_WEATHER, + DOMAIN, +) +from homeassistant.const import CONF_INCLUDE, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME + +TEST_LATITUDE = 51.65 +TEST_LONGITUDE = 5.7 +TEST_NAME = "test" +TEST_FORECAST = True +TEST_DIMENSION = 512 +TEST_DELTA = 600 +TEST_COUNTRY = "NL" +TEST_TIMEFRAME = 60 + + +async def test_config_flow_setup_all(hass): + """ + Test flow manually initialized by user. + + Setup all platforms. + """ + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_NAME: TEST_NAME, + CONF_LATITUDE: TEST_LATITUDE, + CONF_LONGITUDE: TEST_LONGITUDE, + CONF_WEATHER: True, + CONF_CAMERA: True, + CONF_SENSOR: True, + }, + ) + + assert result["type"] == "form" + assert result["step_id"] == "setup_weather" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_FORECAST: TEST_FORECAST} + ) + + assert result["type"] == "form" + assert result["step_id"] == "setup_camera" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_DELTA: TEST_DELTA, + CONF_DIMENSION: TEST_DIMENSION, + CONF_COUNTRY: TEST_COUNTRY, + }, + ) + + assert result["type"] == "form" + assert result["step_id"] == "setup_sensor" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_TIMEFRAME: TEST_TIMEFRAME} + ) + + assert result["type"] == "create_entry" + assert result["title"] == TEST_NAME + assert result["data"] == { + CONF_NAME: TEST_NAME, + CONF_LATITUDE: TEST_LATITUDE, + CONF_LONGITUDE: TEST_LONGITUDE, + CONF_WEATHER: {CONF_INCLUDE: True, CONF_FORECAST: TEST_FORECAST}, + CONF_CAMERA: { + CONF_INCLUDE: True, + CONF_DIMENSION: TEST_DIMENSION, + CONF_DELTA: TEST_DELTA, + CONF_COUNTRY: TEST_COUNTRY, + }, + CONF_SENSOR: {CONF_INCLUDE: True, CONF_TIMEFRAME: TEST_TIMEFRAME}, + } + + await hass.async_block_till_done() + + conf_entries = hass.config_entries.async_entries(DOMAIN) + await hass.config_entries.async_unload(conf_entries[0].entry_id) + await hass.async_block_till_done() + + +async def test_config_flow_setup_weather(hass): + """ + Test flow manually initialized by user. + + Setup weather platform + """ + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_NAME: TEST_NAME, + CONF_LATITUDE: TEST_LATITUDE, + CONF_LONGITUDE: TEST_LONGITUDE, + CONF_WEATHER: True, + CONF_CAMERA: False, + CONF_SENSOR: False, + }, + ) + + assert result["type"] == "form" + assert result["step_id"] == "setup_weather" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_FORECAST: TEST_FORECAST} + ) + + assert result["type"] == "create_entry" + assert result["title"] == TEST_NAME + assert result["data"] == { + CONF_NAME: TEST_NAME, + CONF_LATITUDE: TEST_LATITUDE, + CONF_LONGITUDE: TEST_LONGITUDE, + CONF_WEATHER: {CONF_INCLUDE: True, CONF_FORECAST: TEST_FORECAST}, + CONF_CAMERA: {CONF_INCLUDE: False}, + CONF_SENSOR: {CONF_INCLUDE: False}, + } + + await hass.async_block_till_done() + + conf_entries = hass.config_entries.async_entries(DOMAIN) + await hass.config_entries.async_unload(conf_entries[0].entry_id) + await hass.async_block_till_done() + + +async def test_config_flow_setup_camera(hass): + """ + Test flow manually initialized by user. + + Setup camera platform + """ + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_NAME: TEST_NAME, + CONF_LATITUDE: TEST_LATITUDE, + CONF_LONGITUDE: TEST_LONGITUDE, + CONF_WEATHER: False, + CONF_CAMERA: True, + CONF_SENSOR: False, + }, + ) + + assert result["type"] == "form" + assert result["step_id"] == "setup_camera" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_DELTA: TEST_DELTA, + CONF_DIMENSION: TEST_DIMENSION, + CONF_COUNTRY: TEST_COUNTRY, + }, + ) + + assert result["type"] == "create_entry" + assert result["title"] == TEST_NAME + assert result["data"] == { + CONF_NAME: TEST_NAME, + CONF_LATITUDE: TEST_LATITUDE, + CONF_LONGITUDE: TEST_LONGITUDE, + CONF_WEATHER: {CONF_INCLUDE: False}, + CONF_CAMERA: { + CONF_INCLUDE: True, + CONF_DIMENSION: TEST_DIMENSION, + CONF_DELTA: TEST_DELTA, + CONF_COUNTRY: TEST_COUNTRY, + }, + CONF_SENSOR: {CONF_INCLUDE: False}, + } + + await hass.async_block_till_done() + + conf_entries = hass.config_entries.async_entries(DOMAIN) + await hass.config_entries.async_unload(conf_entries[0].entry_id) + await hass.async_block_till_done() + + +async def test_config_flow_setup_sensor(hass): + """ + Test flow manually initialized by user. + + Setup sensor platform. + """ + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_NAME: TEST_NAME, + CONF_LATITUDE: TEST_LATITUDE, + CONF_LONGITUDE: TEST_LONGITUDE, + CONF_WEATHER: False, + CONF_CAMERA: False, + CONF_SENSOR: True, + }, + ) + + assert result["type"] == "form" + assert result["step_id"] == "setup_sensor" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_TIMEFRAME: TEST_TIMEFRAME} + ) + + assert result["type"] == "create_entry" + assert result["title"] == TEST_NAME + assert result["data"] == { + CONF_NAME: TEST_NAME, + CONF_LATITUDE: TEST_LATITUDE, + CONF_LONGITUDE: TEST_LONGITUDE, + CONF_WEATHER: {CONF_INCLUDE: False}, + CONF_CAMERA: {CONF_INCLUDE: False}, + CONF_SENSOR: {CONF_INCLUDE: True, CONF_TIMEFRAME: TEST_TIMEFRAME}, + } + + await hass.async_block_till_done() + + conf_entries = hass.config_entries.async_entries(DOMAIN) + await hass.config_entries.async_unload(conf_entries[0].entry_id) + await hass.async_block_till_done() + + +async def test_config_flow_setup_weather_sensor(hass): + """ + Test flow manually initialized by user. + + Setup weather and sensor platforms + """ + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_NAME: TEST_NAME, + CONF_LATITUDE: TEST_LATITUDE, + CONF_LONGITUDE: TEST_LONGITUDE, + CONF_WEATHER: True, + CONF_CAMERA: False, + CONF_SENSOR: True, + }, + ) + + assert result["type"] == "form" + assert result["step_id"] == "setup_weather" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_FORECAST: TEST_FORECAST} + ) + + assert result["type"] == "form" + assert result["step_id"] == "setup_sensor" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_TIMEFRAME: TEST_TIMEFRAME} + ) + + assert result["type"] == "create_entry" + assert result["title"] == TEST_NAME + assert result["data"] == { + CONF_NAME: TEST_NAME, + CONF_LATITUDE: TEST_LATITUDE, + CONF_LONGITUDE: TEST_LONGITUDE, + CONF_WEATHER: {CONF_INCLUDE: True, CONF_FORECAST: TEST_FORECAST}, + CONF_CAMERA: {CONF_INCLUDE: False}, + CONF_SENSOR: {CONF_INCLUDE: True, CONF_TIMEFRAME: TEST_TIMEFRAME}, + } + + await hass.async_block_till_done() + + conf_entries = hass.config_entries.async_entries(DOMAIN) + await hass.config_entries.async_unload(conf_entries[0].entry_id) + await hass.async_block_till_done() From 63d0483494245261c75a8234504df8b522636897 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Mon, 13 Jul 2020 19:40:18 +0000 Subject: [PATCH 07/57] Increase test coverage for buienradar config_flow tests --- .../components/buienradar/test_config_flow.py | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/tests/components/buienradar/test_config_flow.py b/tests/components/buienradar/test_config_flow.py index d5c4d1d49595d..be118ce25ae4a 100644 --- a/tests/components/buienradar/test_config_flow.py +++ b/tests/components/buienradar/test_config_flow.py @@ -325,3 +325,110 @@ async def test_config_flow_setup_weather_sensor(hass): conf_entries = hass.config_entries.async_entries(DOMAIN) await hass.config_entries.async_unload(conf_entries[0].entry_id) await hass.async_block_till_done() + + +async def test_config_flow_already_configured(hass): + """ + Test flow manually initialized by user. + + Setup all platforms. + """ + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_NAME: TEST_NAME, + CONF_LATITUDE: TEST_LATITUDE, + CONF_LONGITUDE: TEST_LONGITUDE, + CONF_WEATHER: True, + CONF_CAMERA: False, + CONF_SENSOR: False, + }, + ) + + assert result["type"] == "form" + assert result["step_id"] == "setup_weather" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_FORECAST: TEST_FORECAST} + ) + + assert result["type"] == "create_entry" + assert result["title"] == TEST_NAME + assert result["data"] == { + CONF_NAME: TEST_NAME, + CONF_LATITUDE: TEST_LATITUDE, + CONF_LONGITUDE: TEST_LONGITUDE, + CONF_WEATHER: {CONF_INCLUDE: True, CONF_FORECAST: TEST_FORECAST}, + CONF_CAMERA: {CONF_INCLUDE: False}, + CONF_SENSOR: {CONF_INCLUDE: False}, + } + + await hass.async_block_till_done() + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_NAME: TEST_NAME, + CONF_LATITUDE: TEST_LATITUDE, + CONF_LONGITUDE: TEST_LONGITUDE, + CONF_WEATHER: True, + CONF_CAMERA: False, + CONF_SENSOR: False, + }, + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + assert result["errors"] == {CONF_NAME: "name_exists"} + + conf_entries = hass.config_entries.async_entries(DOMAIN) + await hass.config_entries.async_unload(conf_entries[0].entry_id) + await hass.async_block_till_done() + + +async def test_config_flow_empty_selection(hass): + """ + Test flow manually initialized by user. + + Setup all platforms. + """ + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_NAME: TEST_NAME, + CONF_LATITUDE: TEST_LATITUDE, + CONF_LONGITUDE: TEST_LONGITUDE, + CONF_WEATHER: False, + CONF_CAMERA: False, + CONF_SENSOR: False, + }, + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + assert result["errors"] == {"base": "empty_selection"} From 2e11aab103444001378e71a16828f855aaec49c3 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Wed, 5 Aug 2020 18:10:25 +0000 Subject: [PATCH 08/57] Move data into domain --- homeassistant/components/buienradar/__init__.py | 2 ++ homeassistant/components/buienradar/weather.py | 10 +++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/buienradar/__init__.py b/homeassistant/components/buienradar/__init__.py index 0ae9702b8efe9..c51b88619b924 100644 --- a/homeassistant/components/buienradar/__init__.py +++ b/homeassistant/components/buienradar/__init__.py @@ -21,6 +21,8 @@ async def async_setup(hass: HomeAssistant, config: dict): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up buienradar2 from a config entry.""" + hass.data.setdefault(DOMAIN, {}) + for component in PLATFORMS: if entry.data[component][CONF_INCLUDE]: hass.async_create_task( diff --git a/homeassistant/components/buienradar/weather.py b/homeassistant/components/buienradar/weather.py index 1b5a433027363..7e50f46eeeb85 100644 --- a/homeassistant/components/buienradar/weather.py +++ b/homeassistant/components/buienradar/weather.py @@ -41,7 +41,7 @@ from homeassistant.helpers.typing import HomeAssistantType # Reuse data and API logic from the sensor implementation -from .const import CONF_WEATHER, DEFAULT_TIMEFRAME +from .const import CONF_WEATHER, DEFAULT_TIMEFRAME, DOMAIN from .util import BrData _LOGGER = logging.getLogger(__name__) @@ -91,12 +91,12 @@ async def async_setup_entry( _LOGGER.debug("Initializing buienradar weather: coordinates %s", coordinates) # create condition helper - if DATA_CONDITION not in hass.data: + if DATA_CONDITION not in hass.data[DOMAIN]: cond_keys = [str(chr(x)) for x in range(97, 123)] - hass.data[DATA_CONDITION] = dict.fromkeys(cond_keys) + hass.data[DOMAIN][DATA_CONDITION] = dict.fromkeys(cond_keys) for cond, condlst in CONDITION_CLASSES.items(): for condi in condlst: - hass.data[DATA_CONDITION][condi] = cond + hass.data[DOMAIN][DATA_CONDITION][condi] = cond async_add_entities([BrWeather(data, config, coordinates)]) @@ -185,7 +185,7 @@ def forecast(self): return None fcdata_out = [] - cond = self.hass.data[DATA_CONDITION] + cond = self.hass.data[DOMAIN][DATA_CONDITION] if not self._data.forecast: return None From e9306b872edc7dae7b784ddd19a44f3a08e097f8 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Wed, 5 Aug 2020 18:33:50 +0000 Subject: [PATCH 09/57] Remove forecast option --- homeassistant/components/buienradar/const.py | 1 - homeassistant/components/buienradar/weather.py | 10 +--------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/homeassistant/components/buienradar/const.py b/homeassistant/components/buienradar/const.py index 9364a16b283a1..dc032ba1f0abb 100644 --- a/homeassistant/components/buienradar/const.py +++ b/homeassistant/components/buienradar/const.py @@ -6,7 +6,6 @@ HOME_LOCATION_NAME = "Home" -CONF_WEATHER = "weather" CONF_CAMERA = "camera" CONF_SENSOR = "sensor" CONF_FORECAST = "forecast" diff --git a/homeassistant/components/buienradar/weather.py b/homeassistant/components/buienradar/weather.py index 7e50f46eeeb85..506f74755f914 100644 --- a/homeassistant/components/buienradar/weather.py +++ b/homeassistant/components/buienradar/weather.py @@ -41,17 +41,13 @@ from homeassistant.helpers.typing import HomeAssistantType # Reuse data and API logic from the sensor implementation -from .const import CONF_WEATHER, DEFAULT_TIMEFRAME, DOMAIN +from .const import DEFAULT_TIMEFRAME, DOMAIN from .util import BrData _LOGGER = logging.getLogger(__name__) DATA_CONDITION = "buienradar_condition" - -CONF_FORECAST = "forecast" - - CONDITION_CLASSES = { ATTR_CONDITION_CLOUDY: ["c", "p"], ATTR_CONDITION_FOG: ["d", "n"], @@ -110,7 +106,6 @@ class BrWeather(WeatherEntity): def __init__(self, data, config, coordinates): """Initialise the platform with a data instance and station name.""" self._stationname = config.get(CONF_NAME) - self._forecast = config[CONF_WEATHER][CONF_FORECAST] self._data = data self._unique_id = "{:2.6f}{:2.6f}".format( @@ -181,9 +176,6 @@ def temperature_unit(self): @property def forecast(self): """Return the forecast array.""" - if not self._forecast: - return None - fcdata_out = [] cond = self.hass.data[DOMAIN][DATA_CONDITION] From 17cd62adf8e53211fff88fd4dce126ac36704e97 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Wed, 5 Aug 2020 18:45:08 +0000 Subject: [PATCH 10/57] Move data to options --- homeassistant/components/buienradar/camera.py | 16 ++++++++++++---- homeassistant/components/buienradar/const.py | 6 +++--- homeassistant/components/buienradar/sensor.py | 8 +++++--- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/buienradar/camera.py b/homeassistant/components/buienradar/camera.py index 50e7778120c0c..4cedef8c33f00 100644 --- a/homeassistant/components/buienradar/camera.py +++ b/homeassistant/components/buienradar/camera.py @@ -14,7 +14,13 @@ from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import dt as dt_util -from .const import CONF_CAMERA, CONF_COUNTRY, CONF_DELTA, CONF_DIMENSION +from .const import ( + CONF_COUNTRY, + CONF_DELTA, + CONF_DIMENSION, + DEFAULT_DELTA, + DEFAULT_DIMENSION, +) _LOGGER = logging.getLogger(__name__) @@ -24,11 +30,13 @@ async def async_setup_entry( ) -> None: """Set up buienradar radar-loop camera component.""" config = entry.data + options = entry.options - dimension = config[CONF_CAMERA][CONF_DIMENSION] - delta = config[CONF_CAMERA][CONF_DELTA] name = config[CONF_NAME] - country = config[CONF_CAMERA][CONF_COUNTRY] + country = config[CONF_COUNTRY] + + dimension = options.get(CONF_DIMENSION, DEFAULT_DIMENSION) + delta = options.get(CONF_DELTA, DEFAULT_DELTA) async_add_entities([BuienradarCam(name, dimension, delta, country)]) diff --git a/homeassistant/components/buienradar/const.py b/homeassistant/components/buienradar/const.py index dc032ba1f0abb..6ecf014161095 100644 --- a/homeassistant/components/buienradar/const.py +++ b/homeassistant/components/buienradar/const.py @@ -4,11 +4,11 @@ DEFAULT_TIMEFRAME = 60 +DEFAULT_DIMENSION = 512 +DEFAULT_DELTA = 600 + HOME_LOCATION_NAME = "Home" -CONF_CAMERA = "camera" -CONF_SENSOR = "sensor" -CONF_FORECAST = "forecast" CONF_DIMENSION = "dimension" CONF_DELTA = "delta" CONF_COUNTRY = "country_code" diff --git a/homeassistant/components/buienradar/sensor.py b/homeassistant/components/buienradar/sensor.py index 4c3f897c50d6a..8202b53ea116f 100644 --- a/homeassistant/components/buienradar/sensor.py +++ b/homeassistant/components/buienradar/sensor.py @@ -41,7 +41,7 @@ from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import dt as dt_util -from .const import CONF_SENSOR, CONF_TIMEFRAME +from .const import CONF_TIMEFRAME, DEFAULT_TIMEFRAME from .util import BrData _LOGGER = logging.getLogger(__name__) @@ -192,10 +192,12 @@ async def async_setup_entry( ) -> None: """Create the buienradar sensor.""" config = entry.data + options = entry.options latitude = config.get(CONF_LATITUDE, hass.config.latitude) longitude = config.get(CONF_LONGITUDE, hass.config.longitude) - timeframe = config[CONF_SENSOR][CONF_TIMEFRAME] + + timeframe = options.get(CONF_TIMEFRAME, DEFAULT_TIMEFRAME) if None in (latitude, longitude): _LOGGER.error("Latitude or longitude not set in Home Assistant config") @@ -211,7 +213,7 @@ async def async_setup_entry( dev = [] for sensor_type in SENSOR_TYPES: - dev.append(BrSensor(sensor_type, config.get(CONF_NAME), coordinates)) + dev.append(BrSensor(sensor_type, config[CONF_NAME], coordinates)) async_add_entities(dev) data = BrData(hass, coordinates, timeframe, dev) From 57ac7ad099c4ecc0d89f8fec5e8099041910e59f Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Wed, 5 Aug 2020 19:02:35 +0000 Subject: [PATCH 11/57] Remove options from config flow --- .../components/buienradar/__init__.py | 3 +- .../components/buienradar/config_flow.py | 171 +++--------------- homeassistant/components/buienradar/const.py | 3 + .../buienradar/translations/en.json | 25 +-- 4 files changed, 34 insertions(+), 168 deletions(-) diff --git a/homeassistant/components/buienradar/__init__.py b/homeassistant/components/buienradar/__init__.py index c51b88619b924..37226624048b4 100644 --- a/homeassistant/components/buienradar/__init__.py +++ b/homeassistant/components/buienradar/__init__.py @@ -4,7 +4,6 @@ import voluptuous as vol from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_INCLUDE from homeassistant.core import HomeAssistant from .const import CONF_CAMERA, CONF_SENSOR, CONF_WEATHER, DOMAIN @@ -24,7 +23,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data.setdefault(DOMAIN, {}) for component in PLATFORMS: - if entry.data[component][CONF_INCLUDE]: + if entry.data[component]: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, component) ) diff --git a/homeassistant/components/buienradar/config_flow.py b/homeassistant/components/buienradar/config_flow.py index 489d1503501f9..11717f0b4b812 100644 --- a/homeassistant/components/buienradar/config_flow.py +++ b/homeassistant/components/buienradar/config_flow.py @@ -4,20 +4,14 @@ import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import CONF_INCLUDE, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from .const import ( - CAMERA_DIM_MAX, - CAMERA_DIM_MIN, CONF_CAMERA, CONF_COUNTRY, - CONF_DELTA, - CONF_DIMENSION, - CONF_FORECAST, CONF_SENSOR, - CONF_TIMEFRAME, CONF_WEATHER, DOMAIN, HOME_LOCATION_NAME, @@ -48,18 +42,6 @@ def __init__(self): """Init MetFlowHandler.""" self._errors = {} - self._weather = False - self._camera = False - self._sensor = False - - self._name = None - self._latitude = None - self._longitude = None - - self._weatherdata = {} - self._cameradata = {} - self._sensordata = {} - async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" self._errors = {} @@ -69,135 +51,38 @@ async def async_step_user(self, user_input=None): f"{user_input.get(CONF_LATITUDE)}-{user_input.get(CONF_LONGITUDE)}" not in configured_instances(self.hass) ): - self._name = user_input[CONF_NAME] - - self._weather = user_input[CONF_WEATHER] - self._camera = user_input[CONF_CAMERA] - self._sensor = user_input[CONF_SENSOR] - - self._latitude = user_input[CONF_LATITUDE] - self._longitude = user_input[CONF_LONGITUDE] - - if self._weather: - return await self.async_step_setup_weather() - if self._camera: - return await self.async_step_setup_camera() - if self._sensor: - return await self.async_step_setup_sensor() + name = user_input[CONF_NAME] + + data = { + CONF_NAME: name, + CONF_LATITUDE: user_input[CONF_LATITUDE], + CONF_LONGITUDE: user_input[CONF_LONGITUDE], + CONF_CAMERA: user_input[CONF_CAMERA], + CONF_SENSOR: True, + CONF_WEATHER: True, + CONF_COUNTRY: user_input[CONF_COUNTRY], + } - self._errors["base"] = "empty_selection" + return self.async_create_entry(title=name, data=data) else: self._errors[CONF_NAME] = "name_exists" - return await self._show_config_form( - name=HOME_LOCATION_NAME, - latitude=self.hass.config.latitude, - longitude=self.hass.config.longitude, + data_schema = vol.Schema( + { + vol.Required(CONF_NAME, default=HOME_LOCATION_NAME): str, + vol.Required( + CONF_LATITUDE, default=self.hass.config.latitude + ): cv.latitude, + vol.Required( + CONF_LONGITUDE, default=self.hass.config.longitude + ): cv.longitude, + vol.Required(CONF_CAMERA, default=False): bool, + vol.Required(CONF_COUNTRY, default="NL"): vol.All( + vol.Coerce(str), vol.In(SUPPORTED_COUNTRY_CODES) + ), + } ) - async def async_step_setup_weather(self, user_input=None): - """Handle step to configure weather platform.""" - self._errors = {} - - if user_input is not None: - self._weatherdata = user_input - - if self._camera: - return await self.async_step_setup_camera() - if self._sensor: - return await self.async_step_setup_sensor() - - return await self._configure() - return self.async_show_form( - step_id="setup_weather", - data_schema=vol.Schema({vol.Required(CONF_FORECAST, default=True): bool}), - errors=self._errors, + step_id="user", data_schema=data_schema, errors=self._errors, ) - - async def async_step_setup_camera(self, user_input=None): - """Handle step to configure camera platform.""" - self._errors = {} - - if user_input is not None: - self._cameradata = user_input - - if self._sensor: - return await self.async_step_setup_sensor() - - return await self._configure() - - return self.async_show_form( - step_id="setup_camera", - data_schema=vol.Schema( - { - vol.Required(CONF_DIMENSION, default=512): vol.All( - vol.Coerce(int), - vol.Range(min=CAMERA_DIM_MIN, max=CAMERA_DIM_MAX), - ), - vol.Required(CONF_DELTA, default=600.0): vol.All( - vol.Coerce(int), vol.Range(min=0) - ), - vol.Required(CONF_COUNTRY, default="NL"): vol.All( - vol.Coerce(str), vol.In(SUPPORTED_COUNTRY_CODES) - ), - } - ), - errors=self._errors, - ) - - async def async_step_setup_sensor(self, user_input=None): - """Handle step to configure sensor platform.""" - self._errors = {} - - if user_input is not None: - self._sensordata = user_input - - return await self._configure() - - return self.async_show_form( - step_id="setup_sensor", - data_schema=vol.Schema( - { - vol.Required(CONF_TIMEFRAME, default=60): vol.All( - vol.Coerce(int), vol.Range(min=5, max=120) - ), - } - ), - errors=self._errors, - ) - - async def _show_config_form( - self, name=None, latitude=None, longitude=None, elevation=None - ): - """Show the configuration form to edit location data.""" - return self.async_show_form( - step_id="user", - data_schema=vol.Schema( - { - vol.Required(CONF_NAME, default=name): str, - vol.Required(CONF_LATITUDE, default=latitude): cv.latitude, - vol.Required(CONF_LONGITUDE, default=longitude): cv.longitude, - vol.Required(CONF_WEATHER, default=False): bool, - vol.Required(CONF_CAMERA, default=False): bool, - vol.Required(CONF_SENSOR, default=False): bool, - } - ), - errors=self._errors, - ) - - async def _configure(self): - data = { - CONF_NAME: self._name, - CONF_LATITUDE: self._latitude, - CONF_LONGITUDE: self._longitude, - CONF_WEATHER: self._weatherdata, - CONF_CAMERA: self._cameradata, - CONF_SENSOR: self._sensordata, - } - - data[CONF_WEATHER][CONF_INCLUDE] = self._weather - data[CONF_CAMERA][CONF_INCLUDE] = self._camera - data[CONF_SENSOR][CONF_INCLUDE] = self._sensor - - return self.async_create_entry(title=self._name, data=data) diff --git a/homeassistant/components/buienradar/const.py b/homeassistant/components/buienradar/const.py index 6ecf014161095..89a0b911f4593 100644 --- a/homeassistant/components/buienradar/const.py +++ b/homeassistant/components/buienradar/const.py @@ -9,6 +9,9 @@ HOME_LOCATION_NAME = "Home" +CONF_CAMERA = "camera" +CONF_SENSOR = "sensor" +CONF_WEATHER = "weather" CONF_DIMENSION = "dimension" CONF_DELTA = "delta" CONF_COUNTRY = "country_code" diff --git a/homeassistant/components/buienradar/translations/en.json b/homeassistant/components/buienradar/translations/en.json index 11e09a0b48e01..2fb2429eab301 100644 --- a/homeassistant/components/buienradar/translations/en.json +++ b/homeassistant/components/buienradar/translations/en.json @@ -11,31 +11,10 @@ "latitude": "Latitude", "longitude": "Longitude", "name": "Name", - "weather": "Setup buienradar weather", - "camera": "Setup buienradar camera", - "sensor": "Setup buienradar sensors" - }, - "title": "Buienradar" - }, - "setup_weather": { - "title": "Buienradar Weather", - "data": { - "forecast": "Include temperature forecast" - } - }, - "setup_camera": { - "data": { - "dimension": "Image Size [px]", - "delta": "Interval between image updates [s]", + "camera": "Add radar image", "country_code": "Country" }, - "title": "Buienradar Camera" - }, - "setup_sensor": { - "title": "Buienradar Sensors", - "data": { - "timeframe": "Minutes to look ahead for precipitation forecast" - } + "title": "Buienradar" } } } From 825f6a08b7e261deff0253b264db6d71b20570ba Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Wed, 5 Aug 2020 19:22:14 +0000 Subject: [PATCH 12/57] Adjust tests --- tests/components/buienradar/test_camera.py | 37 ++- .../components/buienradar/test_config_flow.py | 300 ++---------------- tests/components/buienradar/test_sensor.py | 9 +- tests/components/buienradar/test_weather.py | 9 +- 4 files changed, 46 insertions(+), 309 deletions(-) diff --git a/tests/components/buienradar/test_camera.py b/tests/components/buienradar/test_camera.py index c9cb3e26179dd..56ffe1e0ba1e2 100644 --- a/tests/components/buienradar/test_camera.py +++ b/tests/components/buienradar/test_camera.py @@ -14,7 +14,7 @@ CONF_WEATHER, DOMAIN, ) -from homeassistant.const import CONF_INCLUDE, CONF_NAME, HTTP_INTERNAL_SERVER_ERROR +from homeassistant.const import CONF_NAME, HTTP_INTERNAL_SERVER_ERROR from homeassistant.util import dt as dt_util from tests.common import MockConfigEntry @@ -24,14 +24,10 @@ TEST_CFG_DATA = { CONF_NAME: "config_test", - CONF_CAMERA: { - CONF_INCLUDE: True, - CONF_DIMENSION: 512, - CONF_DELTA: 600, - CONF_COUNTRY: "NL", - }, - CONF_SENSOR: {CONF_INCLUDE: False}, - CONF_WEATHER: {CONF_INCLUDE: False}, + CONF_CAMERA: True, + CONF_COUNTRY: "NL", + CONF_SENSOR: False, + CONF_WEATHER: False, } @@ -74,10 +70,11 @@ async def test_expire_delta(aioclient_mock, hass, hass_client): """Test that the cache expires after delta.""" aioclient_mock.get(radar_map_url(), text="hello world") - data = copy.deepcopy(TEST_CFG_DATA) - data[CONF_CAMERA][CONF_DELTA] = EPSILON_DELTA + options = {CONF_DELTA: EPSILON_DELTA} - mock_entry = MockConfigEntry(domain=DOMAIN, unique_id="TEST_ID", data=data) + mock_entry = MockConfigEntry( + domain=DOMAIN, unique_id="TEST_ID", data=TEST_CFG_DATA, options=options + ) mock_entry.add_to_hass(hass) @@ -133,10 +130,11 @@ async def test_dimension(aioclient_mock, hass, hass_client): """Test that it actually adheres to the dimension.""" aioclient_mock.get(radar_map_url(700), text="hello world") - data = copy.deepcopy(TEST_CFG_DATA) - data[CONF_CAMERA][CONF_DIMENSION] = 700 + options = {CONF_DIMENSION: 700} - mock_entry = MockConfigEntry(domain=DOMAIN, unique_id="TEST_ID", data=data) + mock_entry = MockConfigEntry( + domain=DOMAIN, unique_id="TEST_ID", data=TEST_CFG_DATA, options=options + ) mock_entry.add_to_hass(hass) @@ -158,7 +156,7 @@ async def test_belgium_country(aioclient_mock, hass, hass_client): aioclient_mock.get(radar_map_url(country_code="BE"), text="hello world") data = copy.deepcopy(TEST_CFG_DATA) - data[CONF_CAMERA][CONF_COUNTRY] = "BE" + data[CONF_COUNTRY] = "BE" mock_entry = MockConfigEntry(domain=DOMAIN, unique_id="TEST_ID", data=data) @@ -212,10 +210,11 @@ async def test_last_modified_updates(aioclient_mock, hass, hass_client): headers={"Last-Modified": last_modified}, ) - data = copy.deepcopy(TEST_CFG_DATA) - data[CONF_CAMERA][CONF_DELTA] = EPSILON_DELTA + options = {CONF_DELTA: EPSILON_DELTA} - mock_entry = MockConfigEntry(domain=DOMAIN, unique_id="TEST_ID", data=data) + mock_entry = MockConfigEntry( + domain=DOMAIN, unique_id="TEST_ID", data=TEST_CFG_DATA, options=options + ) mock_entry.add_to_hass(hass) diff --git a/tests/components/buienradar/test_config_flow.py b/tests/components/buienradar/test_config_flow.py index be118ce25ae4a..eea5a25274c35 100644 --- a/tests/components/buienradar/test_config_flow.py +++ b/tests/components/buienradar/test_config_flow.py @@ -3,15 +3,11 @@ from homeassistant.components.buienradar.const import ( CONF_CAMERA, CONF_COUNTRY, - CONF_DELTA, - CONF_DIMENSION, - CONF_FORECAST, CONF_SENSOR, - CONF_TIMEFRAME, CONF_WEATHER, DOMAIN, ) -from homeassistant.const import CONF_INCLUDE, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME TEST_LATITUDE = 51.65 TEST_LONGITUDE = 5.7 @@ -43,107 +39,21 @@ async def test_config_flow_setup_all(hass): CONF_NAME: TEST_NAME, CONF_LATITUDE: TEST_LATITUDE, CONF_LONGITUDE: TEST_LONGITUDE, - CONF_WEATHER: True, CONF_CAMERA: True, - CONF_SENSOR: True, + CONF_COUNTRY: "NL", }, ) - assert result["type"] == "form" - assert result["step_id"] == "setup_weather" - assert result["errors"] == {} - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], {CONF_FORECAST: TEST_FORECAST} - ) - - assert result["type"] == "form" - assert result["step_id"] == "setup_camera" - assert result["errors"] == {} - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - CONF_DELTA: TEST_DELTA, - CONF_DIMENSION: TEST_DIMENSION, - CONF_COUNTRY: TEST_COUNTRY, - }, - ) - - assert result["type"] == "form" - assert result["step_id"] == "setup_sensor" - assert result["errors"] == {} - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], {CONF_TIMEFRAME: TEST_TIMEFRAME} - ) - - assert result["type"] == "create_entry" - assert result["title"] == TEST_NAME - assert result["data"] == { - CONF_NAME: TEST_NAME, - CONF_LATITUDE: TEST_LATITUDE, - CONF_LONGITUDE: TEST_LONGITUDE, - CONF_WEATHER: {CONF_INCLUDE: True, CONF_FORECAST: TEST_FORECAST}, - CONF_CAMERA: { - CONF_INCLUDE: True, - CONF_DIMENSION: TEST_DIMENSION, - CONF_DELTA: TEST_DELTA, - CONF_COUNTRY: TEST_COUNTRY, - }, - CONF_SENSOR: {CONF_INCLUDE: True, CONF_TIMEFRAME: TEST_TIMEFRAME}, - } - - await hass.async_block_till_done() - - conf_entries = hass.config_entries.async_entries(DOMAIN) - await hass.config_entries.async_unload(conf_entries[0].entry_id) - await hass.async_block_till_done() - - -async def test_config_flow_setup_weather(hass): - """ - Test flow manually initialized by user. - - Setup weather platform - """ - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - assert result["type"] == "form" - assert result["step_id"] == "user" - assert result["errors"] == {} - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - CONF_NAME: TEST_NAME, - CONF_LATITUDE: TEST_LATITUDE, - CONF_LONGITUDE: TEST_LONGITUDE, - CONF_WEATHER: True, - CONF_CAMERA: False, - CONF_SENSOR: False, - }, - ) - - assert result["type"] == "form" - assert result["step_id"] == "setup_weather" - assert result["errors"] == {} - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], {CONF_FORECAST: TEST_FORECAST} - ) - assert result["type"] == "create_entry" assert result["title"] == TEST_NAME assert result["data"] == { CONF_NAME: TEST_NAME, CONF_LATITUDE: TEST_LATITUDE, CONF_LONGITUDE: TEST_LONGITUDE, - CONF_WEATHER: {CONF_INCLUDE: True, CONF_FORECAST: TEST_FORECAST}, - CONF_CAMERA: {CONF_INCLUDE: False}, - CONF_SENSOR: {CONF_INCLUDE: False}, + CONF_WEATHER: True, + CONF_CAMERA: True, + CONF_SENSOR: True, + CONF_COUNTRY: "NL", } await hass.async_block_till_done() @@ -153,7 +63,7 @@ async def test_config_flow_setup_weather(hass): await hass.async_block_till_done() -async def test_config_flow_setup_camera(hass): +async def test_config_flow_setup_without_camera(hass): """ Test flow manually initialized by user. @@ -173,151 +83,21 @@ async def test_config_flow_setup_camera(hass): CONF_NAME: TEST_NAME, CONF_LATITUDE: TEST_LATITUDE, CONF_LONGITUDE: TEST_LONGITUDE, - CONF_WEATHER: False, - CONF_CAMERA: True, - CONF_SENSOR: False, - }, - ) - - assert result["type"] == "form" - assert result["step_id"] == "setup_camera" - assert result["errors"] == {} - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - CONF_DELTA: TEST_DELTA, - CONF_DIMENSION: TEST_DIMENSION, - CONF_COUNTRY: TEST_COUNTRY, - }, - ) - - assert result["type"] == "create_entry" - assert result["title"] == TEST_NAME - assert result["data"] == { - CONF_NAME: TEST_NAME, - CONF_LATITUDE: TEST_LATITUDE, - CONF_LONGITUDE: TEST_LONGITUDE, - CONF_WEATHER: {CONF_INCLUDE: False}, - CONF_CAMERA: { - CONF_INCLUDE: True, - CONF_DIMENSION: TEST_DIMENSION, - CONF_DELTA: TEST_DELTA, - CONF_COUNTRY: TEST_COUNTRY, - }, - CONF_SENSOR: {CONF_INCLUDE: False}, - } - - await hass.async_block_till_done() - - conf_entries = hass.config_entries.async_entries(DOMAIN) - await hass.config_entries.async_unload(conf_entries[0].entry_id) - await hass.async_block_till_done() - - -async def test_config_flow_setup_sensor(hass): - """ - Test flow manually initialized by user. - - Setup sensor platform. - """ - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - assert result["type"] == "form" - assert result["step_id"] == "user" - assert result["errors"] == {} - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - CONF_NAME: TEST_NAME, - CONF_LATITUDE: TEST_LATITUDE, - CONF_LONGITUDE: TEST_LONGITUDE, - CONF_WEATHER: False, - CONF_CAMERA: False, - CONF_SENSOR: True, - }, - ) - - assert result["type"] == "form" - assert result["step_id"] == "setup_sensor" - assert result["errors"] == {} - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], {CONF_TIMEFRAME: TEST_TIMEFRAME} - ) - - assert result["type"] == "create_entry" - assert result["title"] == TEST_NAME - assert result["data"] == { - CONF_NAME: TEST_NAME, - CONF_LATITUDE: TEST_LATITUDE, - CONF_LONGITUDE: TEST_LONGITUDE, - CONF_WEATHER: {CONF_INCLUDE: False}, - CONF_CAMERA: {CONF_INCLUDE: False}, - CONF_SENSOR: {CONF_INCLUDE: True, CONF_TIMEFRAME: TEST_TIMEFRAME}, - } - - await hass.async_block_till_done() - - conf_entries = hass.config_entries.async_entries(DOMAIN) - await hass.config_entries.async_unload(conf_entries[0].entry_id) - await hass.async_block_till_done() - - -async def test_config_flow_setup_weather_sensor(hass): - """ - Test flow manually initialized by user. - - Setup weather and sensor platforms - """ - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - assert result["type"] == "form" - assert result["step_id"] == "user" - assert result["errors"] == {} - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - CONF_NAME: TEST_NAME, - CONF_LATITUDE: TEST_LATITUDE, - CONF_LONGITUDE: TEST_LONGITUDE, - CONF_WEATHER: True, CONF_CAMERA: False, - CONF_SENSOR: True, + CONF_COUNTRY: "NL", }, ) - assert result["type"] == "form" - assert result["step_id"] == "setup_weather" - assert result["errors"] == {} - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], {CONF_FORECAST: TEST_FORECAST} - ) - - assert result["type"] == "form" - assert result["step_id"] == "setup_sensor" - assert result["errors"] == {} - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], {CONF_TIMEFRAME: TEST_TIMEFRAME} - ) - assert result["type"] == "create_entry" assert result["title"] == TEST_NAME assert result["data"] == { CONF_NAME: TEST_NAME, CONF_LATITUDE: TEST_LATITUDE, CONF_LONGITUDE: TEST_LONGITUDE, - CONF_WEATHER: {CONF_INCLUDE: True, CONF_FORECAST: TEST_FORECAST}, - CONF_CAMERA: {CONF_INCLUDE: False}, - CONF_SENSOR: {CONF_INCLUDE: True, CONF_TIMEFRAME: TEST_TIMEFRAME}, + CONF_WEATHER: True, + CONF_CAMERA: False, + CONF_SENSOR: True, + CONF_COUNTRY: "NL", } await hass.async_block_till_done() @@ -347,29 +127,21 @@ async def test_config_flow_already_configured(hass): CONF_NAME: TEST_NAME, CONF_LATITUDE: TEST_LATITUDE, CONF_LONGITUDE: TEST_LONGITUDE, - CONF_WEATHER: True, - CONF_CAMERA: False, - CONF_SENSOR: False, + CONF_CAMERA: True, + CONF_COUNTRY: "NL", }, ) - assert result["type"] == "form" - assert result["step_id"] == "setup_weather" - assert result["errors"] == {} - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], {CONF_FORECAST: TEST_FORECAST} - ) - assert result["type"] == "create_entry" assert result["title"] == TEST_NAME assert result["data"] == { CONF_NAME: TEST_NAME, CONF_LATITUDE: TEST_LATITUDE, CONF_LONGITUDE: TEST_LONGITUDE, - CONF_WEATHER: {CONF_INCLUDE: True, CONF_FORECAST: TEST_FORECAST}, - CONF_CAMERA: {CONF_INCLUDE: False}, - CONF_SENSOR: {CONF_INCLUDE: False}, + CONF_WEATHER: True, + CONF_CAMERA: True, + CONF_SENSOR: True, + CONF_COUNTRY: "NL", } await hass.async_block_till_done() @@ -388,9 +160,8 @@ async def test_config_flow_already_configured(hass): CONF_NAME: TEST_NAME, CONF_LATITUDE: TEST_LATITUDE, CONF_LONGITUDE: TEST_LONGITUDE, - CONF_WEATHER: True, - CONF_CAMERA: False, - CONF_SENSOR: False, + CONF_CAMERA: True, + CONF_COUNTRY: "NL", }, ) @@ -401,34 +172,3 @@ async def test_config_flow_already_configured(hass): conf_entries = hass.config_entries.async_entries(DOMAIN) await hass.config_entries.async_unload(conf_entries[0].entry_id) await hass.async_block_till_done() - - -async def test_config_flow_empty_selection(hass): - """ - Test flow manually initialized by user. - - Setup all platforms. - """ - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - assert result["type"] == "form" - assert result["step_id"] == "user" - assert result["errors"] == {} - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - CONF_NAME: TEST_NAME, - CONF_LATITUDE: TEST_LATITUDE, - CONF_LONGITUDE: TEST_LONGITUDE, - CONF_WEATHER: False, - CONF_CAMERA: False, - CONF_SENSOR: False, - }, - ) - - assert result["type"] == "form" - assert result["step_id"] == "user" - assert result["errors"] == {"base": "empty_selection"} diff --git a/tests/components/buienradar/test_sensor.py b/tests/components/buienradar/test_sensor.py index 80ad167dfc34a..cdee9fe806fad 100644 --- a/tests/components/buienradar/test_sensor.py +++ b/tests/components/buienradar/test_sensor.py @@ -2,11 +2,10 @@ from homeassistant.components.buienradar.const import ( CONF_CAMERA, CONF_SENSOR, - CONF_TIMEFRAME, CONF_WEATHER, DOMAIN, ) -from homeassistant.const import CONF_INCLUDE, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME from tests.common import MockConfigEntry @@ -15,9 +14,9 @@ CONF_NAME: "volkel", CONF_LATITUDE: 51.65, CONF_LONGITUDE: 5.7, - CONF_CAMERA: {CONF_INCLUDE: False}, - CONF_SENSOR: {CONF_INCLUDE: True, CONF_TIMEFRAME: 60}, - CONF_WEATHER: {CONF_INCLUDE: False}, + CONF_CAMERA: False, + CONF_SENSOR: True, + CONF_WEATHER: False, } diff --git a/tests/components/buienradar/test_weather.py b/tests/components/buienradar/test_weather.py index 3d6b27172ec14..8e584202c98c9 100644 --- a/tests/components/buienradar/test_weather.py +++ b/tests/components/buienradar/test_weather.py @@ -1,12 +1,11 @@ """The tests for the buienradar weather component.""" from homeassistant.components.buienradar.const import ( CONF_CAMERA, - CONF_FORECAST, CONF_SENSOR, CONF_WEATHER, DOMAIN, ) -from homeassistant.const import CONF_INCLUDE, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME from tests.common import MockConfigEntry @@ -14,9 +13,9 @@ CONF_NAME: "volkel", CONF_LATITUDE: 51.65, CONF_LONGITUDE: 5.7, - CONF_CAMERA: {CONF_INCLUDE: False}, - CONF_SENSOR: {CONF_INCLUDE: False}, - CONF_WEATHER: {CONF_INCLUDE: True, CONF_FORECAST: True}, + CONF_CAMERA: False, + CONF_SENSOR: False, + CONF_WEATHER: True, } From 10d14b37c28fbbe4a067bdd78410770e74215039 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Wed, 5 Aug 2020 19:24:37 +0000 Subject: [PATCH 13/57] Adjust string --- .../components/buienradar/strings.json | 24 +++---------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/buienradar/strings.json b/homeassistant/components/buienradar/strings.json index d2f7042504fbf..a4054cd7c04c5 100644 --- a/homeassistant/components/buienradar/strings.json +++ b/homeassistant/components/buienradar/strings.json @@ -4,30 +4,12 @@ "user": { "title": "Buienradar", "data": { - "name": "Name", "latitude": "Latitude", - "longitude": "Longitude" - } - }, - "setup_weather": { - "title": "Buienradar Weather", - "data": { - "forecast": "Include temperature forecast" - } - }, - "setup_camera": { - "title": "Buienradar Camera", - "data": { - "dimension": "Image Size", - "delta": "Interval between image updates", + "longitude": "Longitude", + "name": "Name", + "camera": "Add radar image", "country_code": "Country" } - }, - "setup_sensor": { - "title": "Buienradar Sensors", - "data": { - "timeframe": "Minutes to look ahead for precipitation forecast" - } } }, "error": { "name_exists": "Location already exists" } From 2b9da3f3eb6f2456294b964960648d6832ceb360 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Wed, 5 Aug 2020 19:38:24 +0000 Subject: [PATCH 14/57] Fix pylint issues --- homeassistant/components/buienradar/config_flow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/buienradar/config_flow.py b/homeassistant/components/buienradar/config_flow.py index 11717f0b4b812..58871893022b9 100644 --- a/homeassistant/components/buienradar/config_flow.py +++ b/homeassistant/components/buienradar/config_flow.py @@ -64,8 +64,8 @@ async def async_step_user(self, user_input=None): } return self.async_create_entry(title=name, data=data) - else: - self._errors[CONF_NAME] = "name_exists" + + self._errors[CONF_NAME] = "name_exists" data_schema = vol.Schema( { From aedc670854779b91a98749500700b81062cd2493 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Fri, 21 Aug 2020 18:40:55 +0000 Subject: [PATCH 15/57] Rework review comments --- .../components/buienradar/__init__.py | 4 +- .../components/buienradar/config_flow.py | 41 ++++++++++--------- .../components/buienradar/test_config_flow.py | 12 ------ 3 files changed, 23 insertions(+), 34 deletions(-) diff --git a/homeassistant/components/buienradar/__init__.py b/homeassistant/components/buienradar/__init__.py index 37226624048b4..1d0cac292d4b9 100644 --- a/homeassistant/components/buienradar/__init__.py +++ b/homeassistant/components/buienradar/__init__.py @@ -15,13 +15,13 @@ async def async_setup(hass: HomeAssistant, config: dict): """Set up the buienradar component.""" + hass.data.setdefault(DOMAIN, {}) + return True async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up buienradar2 from a config entry.""" - hass.data.setdefault(DOMAIN, {}) - for component in PLATFORMS: if entry.data[component]: hass.async_create_task( diff --git a/homeassistant/components/buienradar/config_flow.py b/homeassistant/components/buienradar/config_flow.py index 58871893022b9..08ecd37f0f2d4 100644 --- a/homeassistant/components/buienradar/config_flow.py +++ b/homeassistant/components/buienradar/config_flow.py @@ -38,34 +38,18 @@ class BuienradarFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL - def __init__(self): - """Init MetFlowHandler.""" - self._errors = {} - async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" - self._errors = {} + errors = {} if user_input is not None: if ( f"{user_input.get(CONF_LATITUDE)}-{user_input.get(CONF_LONGITUDE)}" not in configured_instances(self.hass) ): - name = user_input[CONF_NAME] - - data = { - CONF_NAME: name, - CONF_LATITUDE: user_input[CONF_LATITUDE], - CONF_LONGITUDE: user_input[CONF_LONGITUDE], - CONF_CAMERA: user_input[CONF_CAMERA], - CONF_SENSOR: True, - CONF_WEATHER: True, - CONF_COUNTRY: user_input[CONF_COUNTRY], - } + return await self.async_create_buienradar_entry(user_input) - return self.async_create_entry(title=name, data=data) - - self._errors[CONF_NAME] = "name_exists" + errors[CONF_NAME] = "name_exists" data_schema = vol.Schema( { @@ -84,5 +68,22 @@ async def async_step_user(self, user_input=None): ) return self.async_show_form( - step_id="user", data_schema=data_schema, errors=self._errors, + step_id="user", data_schema=data_schema, errors=errors, ) + + async def async_create_buienradar_entry(self, input_data): + """Create a config entry.""" + name = input_data[CONF_NAME] + + # Always setup sensor and weather platform, camera is optional + data = { + CONF_NAME: name, + CONF_LATITUDE: input_data[CONF_LATITUDE], + CONF_LONGITUDE: input_data[CONF_LONGITUDE], + CONF_CAMERA: input_data[CONF_CAMERA], + CONF_SENSOR: True, + CONF_WEATHER: True, + CONF_COUNTRY: input_data[CONF_COUNTRY], + } + + return self.async_create_entry(title=name, data=data) diff --git a/tests/components/buienradar/test_config_flow.py b/tests/components/buienradar/test_config_flow.py index eea5a25274c35..a596296386dd3 100644 --- a/tests/components/buienradar/test_config_flow.py +++ b/tests/components/buienradar/test_config_flow.py @@ -58,10 +58,6 @@ async def test_config_flow_setup_all(hass): await hass.async_block_till_done() - conf_entries = hass.config_entries.async_entries(DOMAIN) - await hass.config_entries.async_unload(conf_entries[0].entry_id) - await hass.async_block_till_done() - async def test_config_flow_setup_without_camera(hass): """ @@ -102,10 +98,6 @@ async def test_config_flow_setup_without_camera(hass): await hass.async_block_till_done() - conf_entries = hass.config_entries.async_entries(DOMAIN) - await hass.config_entries.async_unload(conf_entries[0].entry_id) - await hass.async_block_till_done() - async def test_config_flow_already_configured(hass): """ @@ -168,7 +160,3 @@ async def test_config_flow_already_configured(hass): assert result["type"] == "form" assert result["step_id"] == "user" assert result["errors"] == {CONF_NAME: "name_exists"} - - conf_entries = hass.config_entries.async_entries(DOMAIN) - await hass.config_entries.async_unload(conf_entries[0].entry_id) - await hass.async_block_till_done() From 8d568020b5ecc3e4a6734c8ca3d9f71dc3332591 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Sun, 11 Oct 2020 19:23:14 +0000 Subject: [PATCH 16/57] Handle import --- .../components/buienradar/__init__.py | 93 ++++++++++++++++++- .../components/buienradar/config_flow.py | 34 ++++++- 2 files changed, 121 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/buienradar/__init__.py b/homeassistant/components/buienradar/__init__.py index 1d0cac292d4b9..69f831b5b9677 100644 --- a/homeassistant/components/buienradar/__init__.py +++ b/homeassistant/components/buienradar/__init__.py @@ -1,22 +1,41 @@ """The buienradar integration.""" import asyncio +import logging import voluptuous as vol -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME from homeassistant.core import HomeAssistant -from .const import CONF_CAMERA, CONF_SENSOR, CONF_WEATHER, DOMAIN +from .const import ( + CONF_CAMERA, + CONF_COUNTRY, + CONF_SENSOR, + CONF_TIMEFRAME, + CONF_WEATHER, + DEFAULT_TIMEFRAME, + DOMAIN, +) CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA) PLATFORMS = [CONF_WEATHER, CONF_CAMERA, CONF_SENSOR] +_LOGGER = logging.getLogger(__name__) + async def async_setup(hass: HomeAssistant, config: dict): """Set up the buienradar component.""" hass.data.setdefault(DOMAIN, {}) + weather_configs = _filter_domain_configs(config, "weather", DOMAIN) + sensor_configs = _filter_domain_configs(config, "sensor", DOMAIN) + camera_configs = _filter_domain_configs(config, "camera", DOMAIN) + + _import_camera_configs(hass, camera_configs) + _import_weather_configs(hass, weather_configs, sensor_configs) + return True @@ -43,3 +62,73 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): ) return unload_ok + + +def _import_weather_configs(hass, weather_configs, sensor_configs): + for config in sensor_configs: + # Remove weather configurations which share lat/lon with sensor configurations + matching_weather_config = None + latitude = config.get(CONF_LATITUDE, hass.config.latitude) + longitude = config.get(CONF_LONGITUDE, hass.config.longitude) + for weather_config in weather_configs: + weather_latitude = config.get(CONF_LATITUDE, hass.config.latitude) + weather_longitude = config.get(CONF_LONGITUDE, hass.config.longitude) + if latitude == weather_latitude and longitude == weather_longitude: + matching_weather_config = weather_config + break + + if matching_weather_config is not None: + weather_configs.remove(matching_weather_config) + + configs = weather_configs + sensor_configs + + for config in configs: + _LOGGER.debug("Importing Buienradar %s", config) + + data = { + CONF_NAME: config.get(CONF_NAME, "br"), + CONF_LATITUDE: config.get(CONF_LATITUDE, hass.config.latitude), + CONF_LONGITUDE: config.get(CONF_LONGITUDE, hass.config.longitude), + CONF_TIMEFRAME: config.get(CONF_TIMEFRAME, DEFAULT_TIMEFRAME), + CONF_CAMERA: False, + CONF_SENSOR: True, + CONF_WEATHER: True, + } + + _import(hass, data) + + +def _import_camera_configs(hass, configs): + for config in configs: + _LOGGER.debug("Importing Buienradar %s", config) + + data = { + CONF_NAME: config.get(CONF_NAME, "br"), + CONF_COUNTRY: config.get(CONF_COUNTRY, "NL"), + CONF_CAMERA: True, + CONF_SENSOR: False, + CONF_WEATHER: False, + } + + data = {**data, **config} + _import(hass, data) + + +def _import(hass, data): + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=data, + ) + ) + + +def _filter_domain_configs(config, domain, platform): + configs = [] + for entry in config: + if entry.startswith(domain): + configs = configs + list( + filter(lambda elem: elem["platform"] == platform, config[entry]) + ) + return configs diff --git a/homeassistant/components/buienradar/config_flow.py b/homeassistant/components/buienradar/config_flow.py index 08ecd37f0f2d4..dbe52099f90ae 100644 --- a/homeassistant/components/buienradar/config_flow.py +++ b/homeassistant/components/buienradar/config_flow.py @@ -11,6 +11,7 @@ from .const import ( CONF_CAMERA, CONF_COUNTRY, + CONF_DIMENSION, CONF_SENSOR, CONF_WEATHER, DOMAIN, @@ -26,9 +27,14 @@ def configured_instances(hass): """Return a set of configured SimpliSafe instances.""" entries = [] for entry in hass.config_entries.async_entries(DOMAIN): - entries.append( - f"{entry.data.get(CONF_LATITUDE)}-{entry.data.get(CONF_LONGITUDE)}" - ) + if entry.data.get(CONF_CAMERA): + entries.append( + f"{entry.data.get(CONF_DIMENSION)}-{entry.data.get(CONF_COUNTRY)}" + ) + else: + entries.append( + f"{entry.data.get(CONF_LATITUDE)}-{entry.data.get(CONF_LONGITUDE)}" + ) return set(entries) @@ -68,7 +74,9 @@ async def async_step_user(self, user_input=None): ) return self.async_show_form( - step_id="user", data_schema=data_schema, errors=errors, + step_id="user", + data_schema=data_schema, + errors=errors, ) async def async_create_buienradar_entry(self, input_data): @@ -87,3 +95,21 @@ async def async_create_buienradar_entry(self, input_data): } return self.async_create_entry(title=name, data=data) + + async def async_step_import(self, import_input=None): + """Import a config entry.""" + if import_input[CONF_CAMERA]: + if ( + f"{import_input[CONF_DIMENSION]}-{import_input[CONF_COUNTRY]}" + in configured_instances(self.hass) + ): + return self.async_abort(reason="already_configured") + elif ( + f"{import_input[CONF_LATITUDE]}-{import_input[CONF_LONGITUDE]}" + in configured_instances(self.hass) + ): + return self.async_abort(reason="already_configured") + + name = import_input[CONF_NAME] + + return self.async_create_entry(title=name, data=import_input) From 0bb0392ba25789df9d5ea93b20304186fc96a9a4 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Sun, 11 Oct 2020 19:55:48 +0000 Subject: [PATCH 17/57] Change config flow to setup camera or weather --- .../components/buienradar/config_flow.py | 82 +++++++++++++++---- .../components/buienradar/strings.json | 27 ++++-- .../buienradar/translations/en.json | 30 +++++-- homeassistant/generated/config_flows.py | 2 +- 4 files changed, 112 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/buienradar/config_flow.py b/homeassistant/components/buienradar/config_flow.py index dbe52099f90ae..281e4a5878042 100644 --- a/homeassistant/components/buienradar/config_flow.py +++ b/homeassistant/components/buienradar/config_flow.py @@ -4,7 +4,7 @@ import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME +from homeassistant.const import CONF_DOMAIN, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME from homeassistant.core import callback import homeassistant.helpers.config_validation as cv @@ -14,6 +14,7 @@ CONF_DIMENSION, CONF_SENSOR, CONF_WEATHER, + DEFAULT_DIMENSION, DOMAIN, HOME_LOCATION_NAME, SUPPORTED_COUNTRY_CODES, @@ -46,6 +47,57 @@ class BuienradarFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" + if user_input is not None: + if user_input[CONF_DOMAIN] == "Camera": + return await self.async_step_camera() + else: + return await self.async_step_weather() + + data_schema = vol.Schema( + { + vol.Required(CONF_DOMAIN): vol.In(["Camera", "Weather"]), + } + ) + + return self.async_show_form( + step_id="user", + data_schema=data_schema, + errors={}, + ) + + async def async_step_camera(self, user_input=None): + """Handle a flow to configure camera setup.""" + errors = {} + + if user_input is not None: + if ( + f"{user_input.get(CONF_DIMENSION)}-{user_input.get(CONF_COUNTRY)}" + not in configured_instances(self.hass) + ): + return await self.async_create_buienradar_entry(user_input, True) + + errors["base"] = "already_configured" + + data_schema = vol.Schema( + { + vol.Required(CONF_NAME, default=HOME_LOCATION_NAME): str, + vol.Required(CONF_DIMENSION, default=DEFAULT_DIMENSION): vol.All( + vol.Coerce(int), vol.Range(min=120, max=700) + ), + vol.Required(CONF_COUNTRY, default="NL"): vol.All( + vol.Coerce(str), vol.In(SUPPORTED_COUNTRY_CODES) + ), + } + ) + + return self.async_show_form( + step_id="camera", + data_schema=data_schema, + errors=errors, + ) + + async def async_step_weather(self, user_input=None): + """Handle a flow to configure weather setup.""" errors = {} if user_input is not None: @@ -53,9 +105,9 @@ async def async_step_user(self, user_input=None): f"{user_input.get(CONF_LATITUDE)}-{user_input.get(CONF_LONGITUDE)}" not in configured_instances(self.hass) ): - return await self.async_create_buienradar_entry(user_input) + return await self.async_create_buienradar_entry(user_input, False) - errors[CONF_NAME] = "name_exists" + errors["base"] = "already_configured" data_schema = vol.Schema( { @@ -66,34 +118,34 @@ async def async_step_user(self, user_input=None): vol.Required( CONF_LONGITUDE, default=self.hass.config.longitude ): cv.longitude, - vol.Required(CONF_CAMERA, default=False): bool, - vol.Required(CONF_COUNTRY, default="NL"): vol.All( - vol.Coerce(str), vol.In(SUPPORTED_COUNTRY_CODES) - ), } ) return self.async_show_form( - step_id="user", + step_id="weather", data_schema=data_schema, errors=errors, ) - async def async_create_buienradar_entry(self, input_data): + async def async_create_buienradar_entry(self, input_data, is_camera): """Create a config entry.""" name = input_data[CONF_NAME] # Always setup sensor and weather platform, camera is optional data = { CONF_NAME: name, - CONF_LATITUDE: input_data[CONF_LATITUDE], - CONF_LONGITUDE: input_data[CONF_LONGITUDE], - CONF_CAMERA: input_data[CONF_CAMERA], - CONF_SENSOR: True, - CONF_WEATHER: True, - CONF_COUNTRY: input_data[CONF_COUNTRY], + CONF_CAMERA: is_camera, + CONF_SENSOR: (not is_camera), + CONF_WEATHER: (not is_camera), } + if is_camera: + data[CONF_COUNTRY] = input_data[CONF_COUNTRY] + data[CONF_DIMENSION] = input_data[CONF_DIMENSION] + else: + data[CONF_LATITUDE] = input_data[CONF_LATITUDE] + data[CONF_LONGITUDE] = input_data[CONF_LONGITUDE] + return self.async_create_entry(title=name, data=data) async def async_step_import(self, import_input=None): diff --git a/homeassistant/components/buienradar/strings.json b/homeassistant/components/buienradar/strings.json index a4054cd7c04c5..35ee5b0b1036f 100644 --- a/homeassistant/components/buienradar/strings.json +++ b/homeassistant/components/buienradar/strings.json @@ -4,15 +4,32 @@ "user": { "title": "Buienradar", "data": { - "latitude": "Latitude", - "longitude": "Longitude", - "name": "Name", - "camera": "Add radar image", + "domain": "Select domain to setup" + } + }, + "camera": { + "title": "Buienradar Camera", + "data": { + "name": "[%key:common::config_flow::data::name%]", + "dimension": "Dimension", "country_code": "Country" } + }, + "weather": { + "title": "Buienradar Weather", + "data": { + "latitude": "[%key:common::config_flow::data::latitude%]", + "longitude": "[%key:common::config_flow::data::longitude%]", + "name": "[%key:common::config_flow::data::name%]" + } } }, - "error": { "name_exists": "Location already exists" } + "error": { + "already_configured": "[%key:common::config_flow::abort::already_configured_location%]" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_location%]" + } }, "title": "Buienradar" } diff --git a/homeassistant/components/buienradar/translations/en.json b/homeassistant/components/buienradar/translations/en.json index 2fb2429eab301..fc3ac3a7e4e85 100644 --- a/homeassistant/components/buienradar/translations/en.json +++ b/homeassistant/components/buienradar/translations/en.json @@ -1,21 +1,35 @@ { - "title": "Buienradar", "config": { + "abort": { + "already_configured": "Location is already configured" + }, "error": { - "name_exists": "Location already exists", - "empty_selection": "Select at least one platform to setup" + "already_configured": "Location is already configured" }, "step": { + "camera": { + "data": { + "country_code": "Country", + "dimension": "Dimension", + "name": "Name" + }, + "title": "Buienradar Camera" + }, "user": { + "data": { + "domain": "Select domain to setup" + }, + "title": "Buienradar" + }, + "weather": { "data": { "latitude": "Latitude", "longitude": "Longitude", - "name": "Name", - "camera": "Add radar image", - "country_code": "Country" + "name": "Name" }, - "title": "Buienradar" + "title": "Buienradar Weather" } } - } + }, + "title": "Buienradar" } \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 83360295c480b..ef577726b9956 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -37,8 +37,8 @@ "broadlink", "brother", "bsblan", - "canary", "buienradar", + "canary", "cast", "cert_expiry", "climacell", From c2374fccd73d530fd1a041ff8beef1163f69fdba Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Sun, 11 Oct 2020 20:20:12 +0000 Subject: [PATCH 18/57] Fix tests --- .../components/buienradar/test_config_flow.py | 249 +++++++++++++----- 1 file changed, 188 insertions(+), 61 deletions(-) diff --git a/tests/components/buienradar/test_config_flow.py b/tests/components/buienradar/test_config_flow.py index a596296386dd3..2eb9e3d9182c3 100644 --- a/tests/components/buienradar/test_config_flow.py +++ b/tests/components/buienradar/test_config_flow.py @@ -3,11 +3,15 @@ from homeassistant.components.buienradar.const import ( CONF_CAMERA, CONF_COUNTRY, + CONF_DIMENSION, CONF_SENSOR, CONF_WEATHER, DOMAIN, ) -from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME +from homeassistant.const import CONF_DOMAIN, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME + +from tests.async_mock import patch +from tests.common import MockConfigEntry TEST_LATITUDE = 51.65 TEST_LONGITUDE = 5.7 @@ -19,12 +23,8 @@ TEST_TIMEFRAME = 60 -async def test_config_flow_setup_all(hass): - """ - Test flow manually initialized by user. - - Setup all platforms. - """ +async def test_config_flow_setup_camera(hass): + """Test setup of camera.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) @@ -35,36 +35,35 @@ async def test_config_flow_setup_all(hass): result = await hass.config_entries.flow.async_configure( result["flow_id"], - { - CONF_NAME: TEST_NAME, - CONF_LATITUDE: TEST_LATITUDE, - CONF_LONGITUDE: TEST_LONGITUDE, - CONF_CAMERA: True, - CONF_COUNTRY: "NL", - }, + {CONF_DOMAIN: "Camera"}, ) + assert result["type"] == "form" + assert result["step_id"] == "camera" + assert result["errors"] == {} + + with patch( + "homeassistant.components.buienradar.async_setup_entry", return_value=True + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_NAME: TEST_NAME, CONF_DIMENSION: 512, CONF_COUNTRY: "NL"}, + ) + assert result["type"] == "create_entry" assert result["title"] == TEST_NAME assert result["data"] == { CONF_NAME: TEST_NAME, - CONF_LATITUDE: TEST_LATITUDE, - CONF_LONGITUDE: TEST_LONGITUDE, - CONF_WEATHER: True, - CONF_CAMERA: True, - CONF_SENSOR: True, + CONF_DIMENSION: 512, CONF_COUNTRY: "NL", + CONF_WEATHER: False, + CONF_CAMERA: True, + CONF_SENSOR: False, } - await hass.async_block_till_done() - -async def test_config_flow_setup_without_camera(hass): - """ - Test flow manually initialized by user. - - Setup camera platform - """ +async def test_config_flow_setup_weather(hass): + """Test setup of weather.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) @@ -75,15 +74,25 @@ async def test_config_flow_setup_without_camera(hass): result = await hass.config_entries.flow.async_configure( result["flow_id"], - { - CONF_NAME: TEST_NAME, - CONF_LATITUDE: TEST_LATITUDE, - CONF_LONGITUDE: TEST_LONGITUDE, - CONF_CAMERA: False, - CONF_COUNTRY: "NL", - }, + {CONF_DOMAIN: "Weather"}, ) + assert result["type"] == "form" + assert result["step_id"] == "weather" + assert result["errors"] == {} + + with patch( + "homeassistant.components.buienradar.async_setup_entry", return_value=True + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_NAME: TEST_NAME, + CONF_LATITUDE: TEST_LATITUDE, + CONF_LONGITUDE: TEST_LONGITUDE, + }, + ) + assert result["type"] == "create_entry" assert result["title"] == TEST_NAME assert result["data"] == { @@ -93,18 +102,68 @@ async def test_config_flow_setup_without_camera(hass): CONF_WEATHER: True, CONF_CAMERA: False, CONF_SENSOR: True, - CONF_COUNTRY: "NL", } - await hass.async_block_till_done() +async def test_config_flow_already_configured_camera(hass): + """Test already configured.""" + entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_NAME: TEST_NAME, + CONF_DIMENSION: 512, + CONF_COUNTRY: "NL", + CONF_WEATHER: False, + CONF_CAMERA: True, + CONF_SENSOR: False, + }, + unique_id=DOMAIN, + ) + entry.add_to_hass(hass) -async def test_config_flow_already_configured(hass): - """ - Test flow manually initialized by user. + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_DOMAIN: "Camera"}, + ) + + assert result["type"] == "form" + assert result["step_id"] == "camera" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_NAME: TEST_NAME, CONF_DIMENSION: 512, CONF_COUNTRY: "NL"}, + ) + + assert result["type"] == "form" + assert result["step_id"] == "camera" + assert result["errors"] == {"base": "already_configured"} + + +async def test_config_flow_already_configured_weather(hass): + """Test already configured.""" + entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_NAME: TEST_NAME, + CONF_LATITUDE: TEST_LATITUDE, + CONF_LONGITUDE: TEST_LONGITUDE, + CONF_WEATHER: True, + CONF_CAMERA: False, + CONF_SENSOR: True, + }, + unique_id=DOMAIN, + ) + entry.add_to_hass(hass) - Setup all platforms. - """ result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) @@ -113,17 +172,95 @@ async def test_config_flow_already_configured(hass): assert result["step_id"] == "user" assert result["errors"] == {} + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_DOMAIN: "Weather"}, + ) + + assert result["type"] == "form" + assert result["step_id"] == "weather" + assert result["errors"] == {} + result = await hass.config_entries.flow.async_configure( result["flow_id"], { CONF_NAME: TEST_NAME, CONF_LATITUDE: TEST_LATITUDE, CONF_LONGITUDE: TEST_LONGITUDE, + }, + ) + + assert result["type"] == "form" + assert result["step_id"] == "weather" + assert result["errors"] == {"base": "already_configured"} + + +async def test_import_camera(hass): + """Test import of camera.""" + + with patch( + "homeassistant.components.buienradar.async_setup_entry", return_value=True + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={ + CONF_NAME: TEST_NAME, + CONF_DIMENSION: TEST_DIMENSION, + CONF_COUNTRY: TEST_COUNTRY, + CONF_CAMERA: True, + CONF_SENSOR: False, + CONF_WEATHER: False, + }, + ) + + assert result["type"] == "create_entry" + assert result["title"] == TEST_NAME + assert result["data"] == { + CONF_NAME: TEST_NAME, + CONF_DIMENSION: 512, + CONF_COUNTRY: "NL", + CONF_WEATHER: False, + CONF_CAMERA: True, + CONF_SENSOR: False, + } + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={ + CONF_NAME: TEST_NAME, + CONF_DIMENSION: TEST_DIMENSION, + CONF_COUNTRY: TEST_COUNTRY, CONF_CAMERA: True, - CONF_COUNTRY: "NL", + CONF_SENSOR: False, + CONF_WEATHER: False, }, ) + assert result["type"] == "abort" + assert result["reason"] == "already_configured" + + +async def test_import_weather(hass): + """Test import of camera.""" + + with patch( + "homeassistant.components.buienradar.async_setup_entry", return_value=True + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={ + CONF_NAME: TEST_NAME, + CONF_LATITUDE: TEST_LATITUDE, + CONF_LONGITUDE: TEST_LONGITUDE, + CONF_CAMERA: False, + CONF_SENSOR: True, + CONF_WEATHER: True, + }, + ) + assert result["type"] == "create_entry" assert result["title"] == TEST_NAME assert result["data"] == { @@ -131,32 +268,22 @@ async def test_config_flow_already_configured(hass): CONF_LATITUDE: TEST_LATITUDE, CONF_LONGITUDE: TEST_LONGITUDE, CONF_WEATHER: True, - CONF_CAMERA: True, + CONF_CAMERA: False, CONF_SENSOR: True, - CONF_COUNTRY: "NL", } - await hass.async_block_till_done() - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - assert result["type"] == "form" - assert result["step_id"] == "user" - assert result["errors"] == {} - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - { + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={ CONF_NAME: TEST_NAME, CONF_LATITUDE: TEST_LATITUDE, CONF_LONGITUDE: TEST_LONGITUDE, - CONF_CAMERA: True, - CONF_COUNTRY: "NL", + CONF_CAMERA: False, + CONF_SENSOR: True, + CONF_WEATHER: True, }, ) - assert result["type"] == "form" - assert result["step_id"] == "user" - assert result["errors"] == {CONF_NAME: "name_exists"} + assert result["type"] == "abort" + assert result["reason"] == "already_configured" From 9b5e537bb237c1b24a7c67a3b982d71ae95d67dc Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Sun, 11 Oct 2020 20:21:31 +0000 Subject: [PATCH 19/57] Remove translated file --- .../buienradar/translations/en.json | 35 ------------------- 1 file changed, 35 deletions(-) delete mode 100644 homeassistant/components/buienradar/translations/en.json diff --git a/homeassistant/components/buienradar/translations/en.json b/homeassistant/components/buienradar/translations/en.json deleted file mode 100644 index fc3ac3a7e4e85..0000000000000 --- a/homeassistant/components/buienradar/translations/en.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Location is already configured" - }, - "error": { - "already_configured": "Location is already configured" - }, - "step": { - "camera": { - "data": { - "country_code": "Country", - "dimension": "Dimension", - "name": "Name" - }, - "title": "Buienradar Camera" - }, - "user": { - "data": { - "domain": "Select domain to setup" - }, - "title": "Buienradar" - }, - "weather": { - "data": { - "latitude": "Latitude", - "longitude": "Longitude", - "name": "Name" - }, - "title": "Buienradar Weather" - } - } - }, - "title": "Buienradar" -} \ No newline at end of file From c07feac7b1407f82a30c854c99b2fb773a80c47e Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Sun, 11 Oct 2020 22:53:30 +0200 Subject: [PATCH 20/57] Fix pylint --- homeassistant/components/buienradar/config_flow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/buienradar/config_flow.py b/homeassistant/components/buienradar/config_flow.py index 281e4a5878042..6333742fcc369 100644 --- a/homeassistant/components/buienradar/config_flow.py +++ b/homeassistant/components/buienradar/config_flow.py @@ -50,8 +50,8 @@ async def async_step_user(self, user_input=None): if user_input is not None: if user_input[CONF_DOMAIN] == "Camera": return await self.async_step_camera() - else: - return await self.async_step_weather() + + return await self.async_step_weather() data_schema = vol.Schema( { From 7d6763e288fe5275378075fbb5d076d0a61afc1e Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Mon, 12 Oct 2020 07:02:09 +0000 Subject: [PATCH 21/57] Fix flake8 --- homeassistant/components/buienradar/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/buienradar/config_flow.py b/homeassistant/components/buienradar/config_flow.py index 6333742fcc369..8b8ae7dbbc392 100644 --- a/homeassistant/components/buienradar/config_flow.py +++ b/homeassistant/components/buienradar/config_flow.py @@ -50,7 +50,7 @@ async def async_step_user(self, user_input=None): if user_input is not None: if user_input[CONF_DOMAIN] == "Camera": return await self.async_step_camera() - + return await self.async_step_weather() data_schema = vol.Schema( From 43b57de2cd0854572fd881f79fdfeb885decf0e9 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Mon, 12 Oct 2020 07:28:11 +0000 Subject: [PATCH 22/57] Fix unload --- homeassistant/components/buienradar/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/buienradar/__init__.py b/homeassistant/components/buienradar/__init__.py index 69f831b5b9677..2da0e7298cad6 100644 --- a/homeassistant/components/buienradar/__init__.py +++ b/homeassistant/components/buienradar/__init__.py @@ -57,6 +57,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): *[ hass.config_entries.async_forward_entry_unload(entry, component) for component in PLATFORMS + if entry.data[component] ] ) ) From b438ae8f93972327f508c80f24cba8057dc481ac Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Sat, 26 Dec 2020 21:14:35 +0000 Subject: [PATCH 23/57] Minor name changes --- homeassistant/components/buienradar/__init__.py | 2 +- homeassistant/components/buienradar/config_flow.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/buienradar/__init__.py b/homeassistant/components/buienradar/__init__.py index 2da0e7298cad6..c22644d0695ec 100644 --- a/homeassistant/components/buienradar/__init__.py +++ b/homeassistant/components/buienradar/__init__.py @@ -40,7 +40,7 @@ async def async_setup(hass: HomeAssistant, config: dict): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): - """Set up buienradar2 from a config entry.""" + """Set up buienradar from a config entry.""" for component in PLATFORMS: if entry.data[component]: hass.async_create_task( diff --git a/homeassistant/components/buienradar/config_flow.py b/homeassistant/components/buienradar/config_flow.py index 8b8ae7dbbc392..4aa04ae0b81d9 100644 --- a/homeassistant/components/buienradar/config_flow.py +++ b/homeassistant/components/buienradar/config_flow.py @@ -1,4 +1,4 @@ -"""Config flow for buienradar2 integration.""" +"""Config flow for buienradar integration.""" import logging import voluptuous as vol @@ -25,7 +25,7 @@ @callback def configured_instances(hass): - """Return a set of configured SimpliSafe instances.""" + """Return a set of configured buienradar instances.""" entries = [] for entry in hass.config_entries.async_entries(DOMAIN): if entry.data.get(CONF_CAMERA): From 8e3f81545a3c17bb2c1ad8a4cc1846fb58239695 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Fri, 19 Feb 2021 20:57:53 +0100 Subject: [PATCH 24/57] Update homeassistant/components/buienradar/config_flow.py Co-authored-by: Ties de Kock --- homeassistant/components/buienradar/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/buienradar/config_flow.py b/homeassistant/components/buienradar/config_flow.py index 4aa04ae0b81d9..58e4a9f3b29da 100644 --- a/homeassistant/components/buienradar/config_flow.py +++ b/homeassistant/components/buienradar/config_flow.py @@ -40,7 +40,7 @@ def configured_instances(hass): class BuienradarFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): - """Handle a config flow for buienradar2.""" + """Handle a config flow for buienradar.""" VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL From 443c11a8e9c21d718b70d246d3cb537a64c7f767 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Sat, 20 Feb 2021 19:49:07 +0000 Subject: [PATCH 25/57] Remove asynctest --- tests/components/buienradar/test_config_flow.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/components/buienradar/test_config_flow.py b/tests/components/buienradar/test_config_flow.py index 2eb9e3d9182c3..1a18bbbc509d4 100644 --- a/tests/components/buienradar/test_config_flow.py +++ b/tests/components/buienradar/test_config_flow.py @@ -1,4 +1,6 @@ """Test the buienradar2 config flow.""" +from unittest.mock import patch + from homeassistant import config_entries from homeassistant.components.buienradar.const import ( CONF_CAMERA, @@ -10,7 +12,6 @@ ) from homeassistant.const import CONF_DOMAIN, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME -from tests.async_mock import patch from tests.common import MockConfigEntry TEST_LATITUDE = 51.65 From eaeb6e6cb16ec10e5479c3236aa131230e371554 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Wed, 10 Mar 2021 18:30:16 +0000 Subject: [PATCH 26/57] Add translation --- .../buienradar/translations/en.json | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 homeassistant/components/buienradar/translations/en.json diff --git a/homeassistant/components/buienradar/translations/en.json b/homeassistant/components/buienradar/translations/en.json new file mode 100644 index 0000000000000..fc3ac3a7e4e85 --- /dev/null +++ b/homeassistant/components/buienradar/translations/en.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "Location is already configured" + }, + "error": { + "already_configured": "Location is already configured" + }, + "step": { + "camera": { + "data": { + "country_code": "Country", + "dimension": "Dimension", + "name": "Name" + }, + "title": "Buienradar Camera" + }, + "user": { + "data": { + "domain": "Select domain to setup" + }, + "title": "Buienradar" + }, + "weather": { + "data": { + "latitude": "Latitude", + "longitude": "Longitude", + "name": "Name" + }, + "title": "Buienradar Weather" + } + } + }, + "title": "Buienradar" +} \ No newline at end of file From 42540a82071106d12aad826f44e95dfd0c8b6cab Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Wed, 10 Mar 2021 18:39:50 +0000 Subject: [PATCH 27/57] Disable sensors by default --- homeassistant/components/buienradar/sensor.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/homeassistant/components/buienradar/sensor.py b/homeassistant/components/buienradar/sensor.py index 8202b53ea116f..bad1bb0b84724 100644 --- a/homeassistant/components/buienradar/sensor.py +++ b/homeassistant/components/buienradar/sensor.py @@ -449,3 +449,8 @@ def icon(self): def force_update(self): """Return true for continuous sensors, false for discrete sensors.""" return self._force_update + + @property + def entity_registry_enabled_default(self): + """Return if the entity should be enabled when first added to the entity registry.""" + return False From 7fcdafffe08de365e4f2581224ddb33d4d743b71 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Wed, 10 Mar 2021 19:10:11 +0000 Subject: [PATCH 28/57] Remove integration name from translations --- homeassistant/components/buienradar/strings.json | 6 +----- .../components/buienradar/translations/en.json | 12 ++++-------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/buienradar/strings.json b/homeassistant/components/buienradar/strings.json index 35ee5b0b1036f..150da2a8639d6 100644 --- a/homeassistant/components/buienradar/strings.json +++ b/homeassistant/components/buienradar/strings.json @@ -2,13 +2,11 @@ "config": { "step": { "user": { - "title": "Buienradar", "data": { "domain": "Select domain to setup" } }, "camera": { - "title": "Buienradar Camera", "data": { "name": "[%key:common::config_flow::data::name%]", "dimension": "Dimension", @@ -16,7 +14,6 @@ } }, "weather": { - "title": "Buienradar Weather", "data": { "latitude": "[%key:common::config_flow::data::latitude%]", "longitude": "[%key:common::config_flow::data::longitude%]", @@ -30,6 +27,5 @@ "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_location%]" } - }, - "title": "Buienradar" + } } diff --git a/homeassistant/components/buienradar/translations/en.json b/homeassistant/components/buienradar/translations/en.json index fc3ac3a7e4e85..c4c8e2d93c160 100644 --- a/homeassistant/components/buienradar/translations/en.json +++ b/homeassistant/components/buienradar/translations/en.json @@ -12,24 +12,20 @@ "country_code": "Country", "dimension": "Dimension", "name": "Name" - }, - "title": "Buienradar Camera" + } }, "user": { "data": { "domain": "Select domain to setup" - }, - "title": "Buienradar" + } }, "weather": { "data": { "latitude": "Latitude", "longitude": "Longitude", "name": "Name" - }, - "title": "Buienradar Weather" + } } } - }, - "title": "Buienradar" + } } \ No newline at end of file From dded4d87ed871d2dc0afeb418e568aeb83f23151 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Wed, 10 Mar 2021 19:13:16 +0000 Subject: [PATCH 29/57] Remove import method --- .../components/buienradar/__init__.py | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/buienradar/__init__.py b/homeassistant/components/buienradar/__init__.py index c22644d0695ec..bee4ee59674c1 100644 --- a/homeassistant/components/buienradar/__init__.py +++ b/homeassistant/components/buienradar/__init__.py @@ -96,7 +96,13 @@ def _import_weather_configs(hass, weather_configs, sensor_configs): CONF_WEATHER: True, } - _import(hass, data) + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=data, + ) + ) def _import_camera_configs(hass, configs): @@ -112,17 +118,13 @@ def _import_camera_configs(hass, configs): } data = {**data, **config} - _import(hass, data) - - -def _import(hass, data): - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data=data, + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=data, + ) ) - ) def _filter_domain_configs(config, domain, platform): From dad5129916fb0390b16017ab7f410fb7a3c2543b Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Sat, 13 Mar 2021 19:25:22 +0000 Subject: [PATCH 30/57] Drop selection between platforms, disable camera by default --- .../components/buienradar/__init__.py | 8 +- homeassistant/components/buienradar/camera.py | 16 ++- .../components/buienradar/config_flow.py | 98 ++----------------- homeassistant/components/buienradar/const.py | 3 +- homeassistant/components/buienradar/sensor.py | 7 +- .../components/buienradar/weather.py | 2 +- 6 files changed, 28 insertions(+), 106 deletions(-) diff --git a/homeassistant/components/buienradar/__init__.py b/homeassistant/components/buienradar/__init__.py index bee4ee59674c1..ce7c3959043ba 100644 --- a/homeassistant/components/buienradar/__init__.py +++ b/homeassistant/components/buienradar/__init__.py @@ -42,10 +42,9 @@ async def async_setup(hass: HomeAssistant, config: dict): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up buienradar from a config entry.""" for component in PLATFORMS: - if entry.data[component]: - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) - ) + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, component) + ) return True @@ -57,7 +56,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): *[ hass.config_entries.async_forward_entry_unload(entry, component) for component in PLATFORMS - if entry.data[component] ] ) ) diff --git a/homeassistant/components/buienradar/camera.py b/homeassistant/components/buienradar/camera.py index 4cedef8c33f00..515a10dab16ff 100644 --- a/homeassistant/components/buienradar/camera.py +++ b/homeassistant/components/buienradar/camera.py @@ -18,6 +18,7 @@ CONF_COUNTRY, CONF_DELTA, CONF_DIMENSION, + DEFAULT_COUNTRY, DEFAULT_DELTA, DEFAULT_DIMENSION, ) @@ -32,11 +33,11 @@ async def async_setup_entry( config = entry.data options = entry.options - name = config[CONF_NAME] - country = config[CONF_COUNTRY] + name = config.get(CONF_NAME, "Buienradar") + country = options.get(CONF_COUNTRY, config.get(CONF_COUNTRY, DEFAULT_COUNTRY)) - dimension = options.get(CONF_DIMENSION, DEFAULT_DIMENSION) - delta = options.get(CONF_DELTA, DEFAULT_DELTA) + delta = config.get(CONF_DELTA, DEFAULT_DELTA) + dimension = config.get(CONF_DIMENSION, DEFAULT_DIMENSION) async_add_entities([BuienradarCam(name, dimension, delta, country)]) @@ -61,7 +62,7 @@ def __init__(self, name: str, dimension: int, delta: float, country: str): self._name = name # dimension (x and y) of returned radar image - self._dimension = dimension + self._dimension = 700 # time a cached image stays valid for self._delta = delta @@ -183,3 +184,8 @@ async def async_camera_image(self) -> bytes | None: def unique_id(self): """Return the unique id.""" return self._unique_id + + @property + def entity_registry_enabled_default(self): + """Return if the entity should be enabled when first added to the entity registry.""" + return False diff --git a/homeassistant/components/buienradar/config_flow.py b/homeassistant/components/buienradar/config_flow.py index 58e4a9f3b29da..76cdd5b162060 100644 --- a/homeassistant/components/buienradar/config_flow.py +++ b/homeassistant/components/buienradar/config_flow.py @@ -4,21 +4,11 @@ import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import CONF_DOMAIN, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from .const import ( - CONF_CAMERA, - CONF_COUNTRY, - CONF_DIMENSION, - CONF_SENSOR, - CONF_WEATHER, - DEFAULT_DIMENSION, - DOMAIN, - HOME_LOCATION_NAME, - SUPPORTED_COUNTRY_CODES, -) +from .const import CONF_CAMERA, CONF_COUNTRY, CONF_DIMENSION, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -47,71 +37,18 @@ class BuienradarFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" - if user_input is not None: - if user_input[CONF_DOMAIN] == "Camera": - return await self.async_step_camera() - - return await self.async_step_weather() - - data_schema = vol.Schema( - { - vol.Required(CONF_DOMAIN): vol.In(["Camera", "Weather"]), - } - ) - - return self.async_show_form( - step_id="user", - data_schema=data_schema, - errors={}, - ) - - async def async_step_camera(self, user_input=None): - """Handle a flow to configure camera setup.""" errors = {} if user_input is not None: - if ( - f"{user_input.get(CONF_DIMENSION)}-{user_input.get(CONF_COUNTRY)}" - not in configured_instances(self.hass) - ): - return await self.async_create_buienradar_entry(user_input, True) + lat = user_input.get(CONF_LATITUDE) + lon = user_input.get(CONF_LONGITUDE) + if f"{lat}-{lon}" not in configured_instances(self.hass): + return self.async_create_entry(title=f"{lat},{lon}", data=user_input) errors["base"] = "already_configured" data_schema = vol.Schema( { - vol.Required(CONF_NAME, default=HOME_LOCATION_NAME): str, - vol.Required(CONF_DIMENSION, default=DEFAULT_DIMENSION): vol.All( - vol.Coerce(int), vol.Range(min=120, max=700) - ), - vol.Required(CONF_COUNTRY, default="NL"): vol.All( - vol.Coerce(str), vol.In(SUPPORTED_COUNTRY_CODES) - ), - } - ) - - return self.async_show_form( - step_id="camera", - data_schema=data_schema, - errors=errors, - ) - - async def async_step_weather(self, user_input=None): - """Handle a flow to configure weather setup.""" - errors = {} - - if user_input is not None: - if ( - f"{user_input.get(CONF_LATITUDE)}-{user_input.get(CONF_LONGITUDE)}" - not in configured_instances(self.hass) - ): - return await self.async_create_buienradar_entry(user_input, False) - - errors["base"] = "already_configured" - - data_schema = vol.Schema( - { - vol.Required(CONF_NAME, default=HOME_LOCATION_NAME): str, vol.Required( CONF_LATITUDE, default=self.hass.config.latitude ): cv.latitude, @@ -122,32 +59,11 @@ async def async_step_weather(self, user_input=None): ) return self.async_show_form( - step_id="weather", + step_id="user", data_schema=data_schema, errors=errors, ) - async def async_create_buienradar_entry(self, input_data, is_camera): - """Create a config entry.""" - name = input_data[CONF_NAME] - - # Always setup sensor and weather platform, camera is optional - data = { - CONF_NAME: name, - CONF_CAMERA: is_camera, - CONF_SENSOR: (not is_camera), - CONF_WEATHER: (not is_camera), - } - - if is_camera: - data[CONF_COUNTRY] = input_data[CONF_COUNTRY] - data[CONF_DIMENSION] = input_data[CONF_DIMENSION] - else: - data[CONF_LATITUDE] = input_data[CONF_LATITUDE] - data[CONF_LONGITUDE] = input_data[CONF_LONGITUDE] - - return self.async_create_entry(title=name, data=data) - async def async_step_import(self, import_input=None): """Import a config entry.""" if import_input[CONF_CAMERA]: diff --git a/homeassistant/components/buienradar/const.py b/homeassistant/components/buienradar/const.py index 89a0b911f4593..7eece4ab209bf 100644 --- a/homeassistant/components/buienradar/const.py +++ b/homeassistant/components/buienradar/const.py @@ -4,7 +4,7 @@ DEFAULT_TIMEFRAME = 60 -DEFAULT_DIMENSION = 512 +DEFAULT_DIMENSION = 700 DEFAULT_DELTA = 600 HOME_LOCATION_NAME = "Home" @@ -22,6 +22,7 @@ CAMERA_DIM_MAX = 700 SUPPORTED_COUNTRY_CODES = ["NL", "BE"] +DEFAULT_COUNTRY = "NL" """Schedule next call after (minutes).""" SCHEDULE_OK = 10 diff --git a/homeassistant/components/buienradar/sensor.py b/homeassistant/components/buienradar/sensor.py index bad1bb0b84724..a78f9dda2ea03 100644 --- a/homeassistant/components/buienradar/sensor.py +++ b/homeassistant/components/buienradar/sensor.py @@ -192,12 +192,11 @@ async def async_setup_entry( ) -> None: """Create the buienradar sensor.""" config = entry.data - options = entry.options latitude = config.get(CONF_LATITUDE, hass.config.latitude) longitude = config.get(CONF_LONGITUDE, hass.config.longitude) - timeframe = options.get(CONF_TIMEFRAME, DEFAULT_TIMEFRAME) + timeframe = config.get(CONF_TIMEFRAME, DEFAULT_TIMEFRAME) if None in (latitude, longitude): _LOGGER.error("Latitude or longitude not set in Home Assistant config") @@ -213,7 +212,9 @@ async def async_setup_entry( dev = [] for sensor_type in SENSOR_TYPES: - dev.append(BrSensor(sensor_type, config[CONF_NAME], coordinates)) + dev.append( + BrSensor(sensor_type, config.get(CONF_NAME, "Buienradar"), coordinates) + ) async_add_entities(dev) data = BrData(hass, coordinates, timeframe, dev) diff --git a/homeassistant/components/buienradar/weather.py b/homeassistant/components/buienradar/weather.py index 506f74755f914..d55c3ffe52ff0 100644 --- a/homeassistant/components/buienradar/weather.py +++ b/homeassistant/components/buienradar/weather.py @@ -105,7 +105,7 @@ class BrWeather(WeatherEntity): def __init__(self, data, config, coordinates): """Initialise the platform with a data instance and station name.""" - self._stationname = config.get(CONF_NAME) + self._stationname = config.get(CONF_NAME, "Buienradar") self._data = data self._unique_id = "{:2.6f}{:2.6f}".format( From 4f09f124866aaf3a1858424c58b8a07ca819eb54 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Sat, 13 Mar 2021 19:31:53 +0000 Subject: [PATCH 31/57] Minor fix in configured_instances --- homeassistant/components/buienradar/config_flow.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/buienradar/config_flow.py b/homeassistant/components/buienradar/config_flow.py index 76cdd5b162060..aacfb9197caf2 100644 --- a/homeassistant/components/buienradar/config_flow.py +++ b/homeassistant/components/buienradar/config_flow.py @@ -18,14 +18,9 @@ def configured_instances(hass): """Return a set of configured buienradar instances.""" entries = [] for entry in hass.config_entries.async_entries(DOMAIN): - if entry.data.get(CONF_CAMERA): - entries.append( - f"{entry.data.get(CONF_DIMENSION)}-{entry.data.get(CONF_COUNTRY)}" - ) - else: - entries.append( - f"{entry.data.get(CONF_LATITUDE)}-{entry.data.get(CONF_LONGITUDE)}" - ) + entries.append( + f"{entry.data.get(CONF_LATITUDE)}-{entry.data.get(CONF_LONGITUDE)}" + ) return set(entries) From 4089f69194066b3bc47771cf54715baa67a711ea Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Sat, 13 Mar 2021 19:43:02 +0000 Subject: [PATCH 32/57] Bugfix in weather --- homeassistant/components/buienradar/weather.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/buienradar/weather.py b/homeassistant/components/buienradar/weather.py index d55c3ffe52ff0..efd5f8b5fe426 100644 --- a/homeassistant/components/buienradar/weather.py +++ b/homeassistant/components/buienradar/weather.py @@ -130,7 +130,7 @@ def condition(self): if self._data and self._data.condition: ccode = self._data.condition.get(CONDCODE) if ccode: - conditions = self.hass.data.get(DATA_CONDITION) + conditions = self.hass.data[DOMAIN].get(DATA_CONDITION) if conditions: return conditions.get(ccode) From 56a877882ae2be2b39c640e3d5d6eafa2375414a Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Mon, 15 Mar 2021 18:56:57 +0000 Subject: [PATCH 33/57] Rework import --- .../components/buienradar/__init__.py | 42 ++++++------------- homeassistant/components/buienradar/camera.py | 10 ++--- homeassistant/components/buienradar/sensor.py | 5 ++- 3 files changed, 20 insertions(+), 37 deletions(-) diff --git a/homeassistant/components/buienradar/__init__.py b/homeassistant/components/buienradar/__init__.py index ce7c3959043ba..ffdf1dbe25909 100644 --- a/homeassistant/components/buienradar/__init__.py +++ b/homeassistant/components/buienradar/__init__.py @@ -5,15 +5,18 @@ import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE from homeassistant.core import HomeAssistant from .const import ( CONF_CAMERA, CONF_COUNTRY, + CONF_DELTA, CONF_SENSOR, CONF_TIMEFRAME, CONF_WEATHER, + DEFAULT_COUNTRY, + DEFAULT_DELTA, DEFAULT_TIMEFRAME, DOMAIN, ) @@ -33,8 +36,7 @@ async def async_setup(hass: HomeAssistant, config: dict): sensor_configs = _filter_domain_configs(config, "sensor", DOMAIN) camera_configs = _filter_domain_configs(config, "camera", DOMAIN) - _import_camera_configs(hass, camera_configs) - _import_weather_configs(hass, weather_configs, sensor_configs) + _import_weather_configs(hass, weather_configs, sensor_configs, camera_configs) return True @@ -63,7 +65,11 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): return unload_ok -def _import_weather_configs(hass, weather_configs, sensor_configs): +def _import_weather_configs(hass, weather_configs, sensor_configs, camera_configs): + camera_config = {} + if len(camera_configs) > 0: + camera_config = camera_configs[0] + for config in sensor_configs: # Remove weather configurations which share lat/lon with sensor configurations matching_weather_config = None @@ -85,37 +91,13 @@ def _import_weather_configs(hass, weather_configs, sensor_configs): _LOGGER.debug("Importing Buienradar %s", config) data = { - CONF_NAME: config.get(CONF_NAME, "br"), CONF_LATITUDE: config.get(CONF_LATITUDE, hass.config.latitude), CONF_LONGITUDE: config.get(CONF_LONGITUDE, hass.config.longitude), CONF_TIMEFRAME: config.get(CONF_TIMEFRAME, DEFAULT_TIMEFRAME), - CONF_CAMERA: False, - CONF_SENSOR: True, - CONF_WEATHER: True, - } - - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data=data, - ) - ) - - -def _import_camera_configs(hass, configs): - for config in configs: - _LOGGER.debug("Importing Buienradar %s", config) - - data = { - CONF_NAME: config.get(CONF_NAME, "br"), - CONF_COUNTRY: config.get(CONF_COUNTRY, "NL"), - CONF_CAMERA: True, - CONF_SENSOR: False, - CONF_WEATHER: False, + CONF_COUNTRY: camera_config.get(CONF_COUNTRY, DEFAULT_COUNTRY), + CONF_DELTA: camera_config.get(CONF_DELTA, DEFAULT_DELTA), } - data = {**data, **config} hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, diff --git a/homeassistant/components/buienradar/camera.py b/homeassistant/components/buienradar/camera.py index 515a10dab16ff..4ccd95195d5d4 100644 --- a/homeassistant/components/buienradar/camera.py +++ b/homeassistant/components/buienradar/camera.py @@ -17,7 +17,6 @@ from .const import ( CONF_COUNTRY, CONF_DELTA, - CONF_DIMENSION, DEFAULT_COUNTRY, DEFAULT_DELTA, DEFAULT_DIMENSION, @@ -36,10 +35,9 @@ async def async_setup_entry( name = config.get(CONF_NAME, "Buienradar") country = options.get(CONF_COUNTRY, config.get(CONF_COUNTRY, DEFAULT_COUNTRY)) - delta = config.get(CONF_DELTA, DEFAULT_DELTA) - dimension = config.get(CONF_DIMENSION, DEFAULT_DIMENSION) + delta = options.get(CONF_DELTA, config.get(CONF_DELTA, DEFAULT_DELTA)) - async_add_entities([BuienradarCam(name, dimension, delta, country)]) + async_add_entities([BuienradarCam(name, delta, country)]) class BuienradarCam(Camera): @@ -51,7 +49,7 @@ class BuienradarCam(Camera): [0]: https://www.buienradar.nl/overbuienradar/gratis-weerdata """ - def __init__(self, name: str, dimension: int, delta: float, country: str): + def __init__(self, name: str, delta: float, country: str): """ Initialize the component. @@ -62,7 +60,7 @@ def __init__(self, name: str, dimension: int, delta: float, country: str): self._name = name # dimension (x and y) of returned radar image - self._dimension = 700 + self._dimension = DEFAULT_DIMENSION # time a cached image stays valid for self._delta = delta diff --git a/homeassistant/components/buienradar/sensor.py b/homeassistant/components/buienradar/sensor.py index a78f9dda2ea03..73a865a4240e1 100644 --- a/homeassistant/components/buienradar/sensor.py +++ b/homeassistant/components/buienradar/sensor.py @@ -192,11 +192,14 @@ async def async_setup_entry( ) -> None: """Create the buienradar sensor.""" config = entry.data + options = entry.options latitude = config.get(CONF_LATITUDE, hass.config.latitude) longitude = config.get(CONF_LONGITUDE, hass.config.longitude) - timeframe = config.get(CONF_TIMEFRAME, DEFAULT_TIMEFRAME) + timeframe = options.get( + CONF_TIMEFRAME, config.get(CONF_TIMEFRAME, DEFAULT_TIMEFRAME) + ) if None in (latitude, longitude): _LOGGER.error("Latitude or longitude not set in Home Assistant config") From eb5e9152a770be905788c9a2964ad4e3c48561f3 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Mon, 15 Mar 2021 19:35:21 +0000 Subject: [PATCH 34/57] Change unique ids of camera --- .../components/buienradar/__init__.py | 33 ++++++++++++++++++- homeassistant/components/buienradar/camera.py | 14 ++++---- .../components/buienradar/config_flow.py | 24 +++++--------- 3 files changed, 49 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/buienradar/__init__.py b/homeassistant/components/buienradar/__init__.py index ffdf1dbe25909..29126ebcec1d9 100644 --- a/homeassistant/components/buienradar/__init__.py +++ b/homeassistant/components/buienradar/__init__.py @@ -5,18 +5,21 @@ import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry from .const import ( CONF_CAMERA, CONF_COUNTRY, CONF_DELTA, + CONF_DIMENSION, CONF_SENSOR, CONF_TIMEFRAME, CONF_WEATHER, DEFAULT_COUNTRY, DEFAULT_DELTA, + DEFAULT_DIMENSION, DEFAULT_TIMEFRAME, DOMAIN, ) @@ -87,6 +90,16 @@ def _import_weather_configs(hass, weather_configs, sensor_configs, camera_config configs = weather_configs + sensor_configs + if len(configs) == 0 and len(camera_configs) > 0: + config = { + CONF_LATITUDE: hass.config.latitude, + CONF_LONGITUDE: hass.config.longitude, + } + configs.append(config) + + if len(config) > 0: + _try_update_unique_id(hass, configs[0], camera_config) + for config in configs: _LOGGER.debug("Importing Buienradar %s", config) @@ -96,6 +109,7 @@ def _import_weather_configs(hass, weather_configs, sensor_configs, camera_config CONF_TIMEFRAME: config.get(CONF_TIMEFRAME, DEFAULT_TIMEFRAME), CONF_COUNTRY: camera_config.get(CONF_COUNTRY, DEFAULT_COUNTRY), CONF_DELTA: camera_config.get(CONF_DELTA, DEFAULT_DELTA), + CONF_NAME: config.get(CONF_NAME, "Buienradar"), } hass.async_create_task( @@ -107,6 +121,23 @@ def _import_weather_configs(hass, weather_configs, sensor_configs, camera_config ) +def _try_update_unique_id(hass, config, camera_config): + dimension = camera_config.get(CONF_DIMENSION, DEFAULT_DIMENSION) + country = camera_config.get(CONF_COUNTRY, DEFAULT_COUNTRY) + + registry = entity_registry.async_get(hass) + entity_id = registry.async_get_entity_id( + CONF_CAMERA, DOMAIN, f"{dimension}_{country}" + ) + + if entity_id is not None: + latitude = config[CONF_LATITUDE] + longitude = config[CONF_LONGITUDE] + + new_unique_id = f"{latitude:2.6f}{longitude:2.6f}" + registry.async_update_entity(entity_id, new_unique_id=new_unique_id) + + def _filter_domain_configs(config, domain, platform): configs = [] for entry in config: diff --git a/homeassistant/components/buienradar/camera.py b/homeassistant/components/buienradar/camera.py index 4ccd95195d5d4..4ac6d020ba03c 100644 --- a/homeassistant/components/buienradar/camera.py +++ b/homeassistant/components/buienradar/camera.py @@ -9,7 +9,7 @@ from homeassistant.components.camera import Camera from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_NAME +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import dt as dt_util @@ -32,12 +32,14 @@ async def async_setup_entry( config = entry.data options = entry.options - name = config.get(CONF_NAME, "Buienradar") country = options.get(CONF_COUNTRY, config.get(CONF_COUNTRY, DEFAULT_COUNTRY)) delta = options.get(CONF_DELTA, config.get(CONF_DELTA, DEFAULT_DELTA)) - async_add_entities([BuienradarCam(name, delta, country)]) + latitude = config.get(CONF_LATITUDE, hass.config.latitude) + longitude = config.get(CONF_LONGITUDE, hass.config.longitude) + + async_add_entities([BuienradarCam(latitude, longitude, delta, country)]) class BuienradarCam(Camera): @@ -49,7 +51,7 @@ class BuienradarCam(Camera): [0]: https://www.buienradar.nl/overbuienradar/gratis-weerdata """ - def __init__(self, name: str, delta: float, country: str): + def __init__(self, latitude, longitude, delta: float, country: str): """ Initialize the component. @@ -57,7 +59,7 @@ def __init__(self, name: str, delta: float, country: str): """ super().__init__() - self._name = name + self._name = "Buienradar" # dimension (x and y) of returned radar image self._dimension = DEFAULT_DIMENSION @@ -84,7 +86,7 @@ def __init__(self, name: str, delta: float, country: str): # deadline for image refresh - self.delta after last successful load self._deadline: datetime | None = None - self._unique_id = f"{self._dimension}_{self._country}" + self._unique_id = f"{latitude:2.6f}{longitude:2.6f}" @property def name(self) -> str: diff --git a/homeassistant/components/buienradar/config_flow.py b/homeassistant/components/buienradar/config_flow.py index aacfb9197caf2..ae45dcddd5ac1 100644 --- a/homeassistant/components/buienradar/config_flow.py +++ b/homeassistant/components/buienradar/config_flow.py @@ -4,11 +4,11 @@ import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from .const import CONF_CAMERA, CONF_COUNTRY, CONF_DIMENSION, DOMAIN +from .const import DOMAIN _LOGGER = logging.getLogger(__name__) @@ -61,18 +61,12 @@ async def async_step_user(self, user_input=None): async def async_step_import(self, import_input=None): """Import a config entry.""" - if import_input[CONF_CAMERA]: - if ( - f"{import_input[CONF_DIMENSION]}-{import_input[CONF_COUNTRY]}" - in configured_instances(self.hass) - ): - return self.async_abort(reason="already_configured") - elif ( - f"{import_input[CONF_LATITUDE]}-{import_input[CONF_LONGITUDE]}" - in configured_instances(self.hass) - ): - return self.async_abort(reason="already_configured") + latitude = import_input[CONF_LATITUDE] + longitude = import_input[CONF_LONGITUDE] - name = import_input[CONF_NAME] + if f"{latitude}-{longitude}" in configured_instances(self.hass): + return self.async_abort(reason="already_configured") - return self.async_create_entry(title=name, data=import_input) + return self.async_create_entry( + title=f"{latitude},{longitude}", data=import_input + ) From 2332701c30da12f450b1e145d44c7b124e370e07 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Mon, 15 Mar 2021 21:04:02 +0000 Subject: [PATCH 35/57] Fix in import --- homeassistant/components/buienradar/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/buienradar/__init__.py b/homeassistant/components/buienradar/__init__.py index 29126ebcec1d9..7e07b0898e915 100644 --- a/homeassistant/components/buienradar/__init__.py +++ b/homeassistant/components/buienradar/__init__.py @@ -97,7 +97,7 @@ def _import_weather_configs(hass, weather_configs, sensor_configs, camera_config } configs.append(config) - if len(config) > 0: + if len(configs) > 0: _try_update_unique_id(hass, configs[0], camera_config) for config in configs: From e4596a62d1f67c901663f1a6c2392da2de9c7c0d Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Mon, 15 Mar 2021 21:24:17 +0000 Subject: [PATCH 36/57] Fix camera tests --- tests/components/buienradar/test_camera.py | 109 ++++++++------------- 1 file changed, 39 insertions(+), 70 deletions(-) diff --git a/tests/components/buienradar/test_camera.py b/tests/components/buienradar/test_camera.py index 56ffe1e0ba1e2..69a5d43d27784 100644 --- a/tests/components/buienradar/test_camera.py +++ b/tests/components/buienradar/test_camera.py @@ -2,19 +2,16 @@ import asyncio from contextlib import suppress import copy +from unittest.mock import patch from aiohttp.client_exceptions import ClientResponseError -from homeassistant.components.buienradar.const import ( - CONF_CAMERA, - CONF_COUNTRY, - CONF_DELTA, - CONF_DIMENSION, - CONF_SENSOR, - CONF_WEATHER, - DOMAIN, +from homeassistant.components.buienradar.const import CONF_COUNTRY, CONF_DELTA, DOMAIN +from homeassistant.const import ( + CONF_LATITUDE, + CONF_LONGITUDE, + HTTP_INTERNAL_SERVER_ERROR, ) -from homeassistant.const import CONF_NAME, HTTP_INTERNAL_SERVER_ERROR from homeassistant.util import dt as dt_util from tests.common import MockConfigEntry @@ -22,18 +19,22 @@ # An infinitesimally small time-delta. EPSILON_DELTA = 0.0000000001 -TEST_CFG_DATA = { - CONF_NAME: "config_test", - CONF_CAMERA: True, - CONF_COUNTRY: "NL", - CONF_SENSOR: False, - CONF_WEATHER: False, -} +TEST_CFG_DATA = {CONF_LATITUDE: 51.5288504, CONF_LONGITUDE: 5.4002156} -def radar_map_url(dim: int = 512, country_code: str = "NL") -> str: +def radar_map_url(country_code: str = "NL") -> str: """Build map url, defaulting to 512 wide (as in component).""" - return f"https://api.buienradar.nl/image/1.0/RadarMap{country_code}?w={dim}&h={dim}" + return f"https://api.buienradar.nl/image/1.0/RadarMap{country_code}?w={700}&h={700}" + + +async def _setup_config_entry(hass, entry_id): + with patch( + "homeassistant.components.buienradar.camera.BuienradarCam.entity_registry_enabled_default" + ) as enabled_by_default_mock: + enabled_by_default_mock.return_value = True + + await hass.config_entries.async_setup(entry_id) + await hass.async_block_till_done() async def test_fetching_url_and_caching(aioclient_mock, hass, hass_client): @@ -44,12 +45,11 @@ async def test_fetching_url_and_caching(aioclient_mock, hass, hass_client): mock_entry.add_to_hass(hass) - await hass.config_entries.async_setup(mock_entry.entry_id) - await hass.async_block_till_done() + await _setup_config_entry(hass, mock_entry.entry_id) client = await hass_client() - resp = await client.get("/api/camera_proxy/camera.config_test") + resp = await client.get("/api/camera_proxy/camera.buienradar") assert resp.status == 200 assert aioclient_mock.call_count == 1 @@ -59,7 +59,7 @@ async def test_fetching_url_and_caching(aioclient_mock, hass, hass_client): # default delta is 600s -> should be the same when calling immediately # afterwards. - resp = await client.get("/api/camera_proxy/camera.config_test") + resp = await client.get("/api/camera_proxy/camera.buienradar") assert aioclient_mock.call_count == 1 await hass.config_entries.async_unload(mock_entry.entry_id) @@ -78,12 +78,11 @@ async def test_expire_delta(aioclient_mock, hass, hass_client): mock_entry.add_to_hass(hass) - await hass.config_entries.async_setup(mock_entry.entry_id) - await hass.async_block_till_done() + await _setup_config_entry(hass, mock_entry.entry_id) client = await hass_client() - resp = await client.get("/api/camera_proxy/camera.config_test") + resp = await client.get("/api/camera_proxy/camera.buienradar") assert resp.status == 200 assert aioclient_mock.call_count == 1 @@ -92,7 +91,7 @@ async def test_expire_delta(aioclient_mock, hass, hass_client): await asyncio.sleep(EPSILON_DELTA) # tiny delta has passed -> should immediately call again - resp = await client.get("/api/camera_proxy/camera.config_test") + resp = await client.get("/api/camera_proxy/camera.buienradar") assert aioclient_mock.call_count == 2 await hass.config_entries.async_unload(mock_entry.entry_id) @@ -107,13 +106,12 @@ async def test_only_one_fetch_at_a_time(aioclient_mock, hass, hass_client): mock_entry.add_to_hass(hass) - await hass.config_entries.async_setup(mock_entry.entry_id) - await hass.async_block_till_done() + await _setup_config_entry(hass, mock_entry.entry_id) client = await hass_client() - resp_1 = client.get("/api/camera_proxy/camera.config_test") - resp_2 = client.get("/api/camera_proxy/camera.config_test") + resp_1 = client.get("/api/camera_proxy/camera.buienradar") + resp_2 = client.get("/api/camera_proxy/camera.buienradar") resp = await resp_1 resp_2 = await resp_2 @@ -126,31 +124,6 @@ async def test_only_one_fetch_at_a_time(aioclient_mock, hass, hass_client): await hass.async_block_till_done() -async def test_dimension(aioclient_mock, hass, hass_client): - """Test that it actually adheres to the dimension.""" - aioclient_mock.get(radar_map_url(700), text="hello world") - - options = {CONF_DIMENSION: 700} - - mock_entry = MockConfigEntry( - domain=DOMAIN, unique_id="TEST_ID", data=TEST_CFG_DATA, options=options - ) - - mock_entry.add_to_hass(hass) - - await hass.config_entries.async_setup(mock_entry.entry_id) - await hass.async_block_till_done() - - client = await hass_client() - - await client.get("/api/camera_proxy/camera.config_test") - - assert aioclient_mock.call_count == 1 - - await hass.config_entries.async_unload(mock_entry.entry_id) - await hass.async_block_till_done() - - async def test_belgium_country(aioclient_mock, hass, hass_client): """Test that it actually adheres to another country like Belgium.""" aioclient_mock.get(radar_map_url(country_code="BE"), text="hello world") @@ -162,12 +135,11 @@ async def test_belgium_country(aioclient_mock, hass, hass_client): mock_entry.add_to_hass(hass) - await hass.config_entries.async_setup(mock_entry.entry_id) - await hass.async_block_till_done() + await _setup_config_entry(hass, mock_entry.entry_id) client = await hass_client() - await client.get("/api/camera_proxy/camera.config_test") + await client.get("/api/camera_proxy/camera.buienradar") assert aioclient_mock.call_count == 1 @@ -183,13 +155,12 @@ async def test_failure_response_not_cached(aioclient_mock, hass, hass_client): mock_entry.add_to_hass(hass) - await hass.config_entries.async_setup(mock_entry.entry_id) - await hass.async_block_till_done() + await _setup_config_entry(hass, mock_entry.entry_id) client = await hass_client() - await client.get("/api/camera_proxy/camera.config_test") - await client.get("/api/camera_proxy/camera.config_test") + await client.get("/api/camera_proxy/camera.buienradar") + await client.get("/api/camera_proxy/camera.buienradar") assert aioclient_mock.call_count == 2 @@ -218,12 +189,11 @@ async def test_last_modified_updates(aioclient_mock, hass, hass_client): mock_entry.add_to_hass(hass) - await hass.config_entries.async_setup(mock_entry.entry_id) - await hass.async_block_till_done() + await _setup_config_entry(hass, mock_entry.entry_id) client = await hass_client() - resp_1 = await client.get("/api/camera_proxy/camera.config_test") + resp_1 = await client.get("/api/camera_proxy/camera.buienradar") # It is not possible to check if header was sent. assert aioclient_mock.call_count == 1 @@ -237,7 +207,7 @@ async def test_last_modified_updates(aioclient_mock, hass, hass_client): aioclient_mock.get(radar_map_url(), text=None, status=304) - resp_2 = await client.get("/api/camera_proxy/camera.config_test") + resp_2 = await client.get("/api/camera_proxy/camera.buienradar") assert aioclient_mock.call_count == 1 assert (await resp_1.read()) == (await resp_2.read()) @@ -252,8 +222,7 @@ async def test_retries_after_error(aioclient_mock, hass, hass_client): mock_entry.add_to_hass(hass) - await hass.config_entries.async_setup(mock_entry.entry_id) - await hass.async_block_till_done() + await _setup_config_entry(hass, mock_entry.entry_id) client = await hass_client() @@ -261,7 +230,7 @@ async def test_retries_after_error(aioclient_mock, hass, hass_client): # A 404 should not return data and throw: with suppress(ClientResponseError): - await client.get("/api/camera_proxy/camera.config_test") + await client.get("/api/camera_proxy/camera.buienradar") assert aioclient_mock.call_count == 1 @@ -272,7 +241,7 @@ async def test_retries_after_error(aioclient_mock, hass, hass_client): assert aioclient_mock.call_count == 0 # http error should not be cached, immediate retry. - resp_2 = await client.get("/api/camera_proxy/camera.config_test") + resp_2 = await client.get("/api/camera_proxy/camera.buienradar") assert aioclient_mock.call_count == 1 # Binary text can not be added as body to `aioclient_mock.get(text=...)`, From 91ddf2bd29de0e01b328c7e52d8053c6e4d36baf Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Mon, 15 Mar 2021 21:26:42 +0000 Subject: [PATCH 37/57] Fix sensor test --- tests/components/buienradar/test_sensor.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/components/buienradar/test_sensor.py b/tests/components/buienradar/test_sensor.py index cdee9fe806fad..c706ec61b296b 100644 --- a/tests/components/buienradar/test_sensor.py +++ b/tests/components/buienradar/test_sensor.py @@ -1,4 +1,6 @@ """The tests for the Buienradar sensor platform.""" +from unittest.mock import patch + from homeassistant.components.buienradar.const import ( CONF_CAMERA, CONF_SENSOR, @@ -26,8 +28,13 @@ async def test_smoke_test_setup_component(hass): mock_entry.add_to_hass(hass) - await hass.config_entries.async_setup(mock_entry.entry_id) - await hass.async_block_till_done() + with patch( + "homeassistant.components.buienradar.sensor.BrSensor.entity_registry_enabled_default" + ) as enabled_by_default_mock: + enabled_by_default_mock.return_value = True + + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() for cond in CONDITIONS: state = hass.states.get(f"sensor.volkel_{cond}") From 9166031599e6925d5db09b47b13ed5f77dbe4f20 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Mon, 15 Mar 2021 21:28:51 +0000 Subject: [PATCH 38/57] Fix sensor test 2 --- tests/components/buienradar/test_sensor.py | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/tests/components/buienradar/test_sensor.py b/tests/components/buienradar/test_sensor.py index c706ec61b296b..4b6ed5ecbc047 100644 --- a/tests/components/buienradar/test_sensor.py +++ b/tests/components/buienradar/test_sensor.py @@ -1,25 +1,13 @@ """The tests for the Buienradar sensor platform.""" from unittest.mock import patch -from homeassistant.components.buienradar.const import ( - CONF_CAMERA, - CONF_SENSOR, - CONF_WEATHER, - DOMAIN, -) -from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME +from homeassistant.components.buienradar.const import DOMAIN +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE from tests.common import MockConfigEntry CONDITIONS = ["stationname", "temperature"] -TEST_CFG_DATA = { - CONF_NAME: "volkel", - CONF_LATITUDE: 51.65, - CONF_LONGITUDE: 5.7, - CONF_CAMERA: False, - CONF_SENSOR: True, - CONF_WEATHER: False, -} +TEST_CFG_DATA = {CONF_LATITUDE: 51.5288504, CONF_LONGITUDE: 5.4002156} async def test_smoke_test_setup_component(hass): @@ -37,7 +25,7 @@ async def test_smoke_test_setup_component(hass): await hass.async_block_till_done() for cond in CONDITIONS: - state = hass.states.get(f"sensor.volkel_{cond}") + state = hass.states.get(f"sensor.buienradar_{cond}") assert state.state == "unknown" await hass.config_entries.async_unload(mock_entry.entry_id) From fe114895a836c41919b8d9bd7adfab2883cf7193 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Mon, 15 Mar 2021 21:44:26 +0000 Subject: [PATCH 39/57] Fix config flow tests --- .../components/buienradar/test_config_flow.py | 218 +----------------- 1 file changed, 12 insertions(+), 206 deletions(-) diff --git a/tests/components/buienradar/test_config_flow.py b/tests/components/buienradar/test_config_flow.py index 1a18bbbc509d4..0aebcffc79ec1 100644 --- a/tests/components/buienradar/test_config_flow.py +++ b/tests/components/buienradar/test_config_flow.py @@ -2,29 +2,16 @@ from unittest.mock import patch from homeassistant import config_entries -from homeassistant.components.buienradar.const import ( - CONF_CAMERA, - CONF_COUNTRY, - CONF_DIMENSION, - CONF_SENSOR, - CONF_WEATHER, - DOMAIN, -) -from homeassistant.const import CONF_DOMAIN, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME +from homeassistant.components.buienradar.const import DOMAIN +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE from tests.common import MockConfigEntry -TEST_LATITUDE = 51.65 -TEST_LONGITUDE = 5.7 -TEST_NAME = "test" -TEST_FORECAST = True -TEST_DIMENSION = 512 -TEST_DELTA = 600 -TEST_COUNTRY = "NL" -TEST_TIMEFRAME = 60 +TEST_LATITUDE = 51.5288504 +TEST_LONGITUDE = 5.4002156 -async def test_config_flow_setup_camera(hass): +async def test_config_flow_setup_(hass): """Test setup of camera.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -34,132 +21,29 @@ async def test_config_flow_setup_camera(hass): assert result["step_id"] == "user" assert result["errors"] == {} - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - {CONF_DOMAIN: "Camera"}, - ) - - assert result["type"] == "form" - assert result["step_id"] == "camera" - assert result["errors"] == {} - with patch( "homeassistant.components.buienradar.async_setup_entry", return_value=True ): result = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_NAME: TEST_NAME, CONF_DIMENSION: 512, CONF_COUNTRY: "NL"}, + {CONF_LATITUDE: TEST_LATITUDE, CONF_LONGITUDE: TEST_LONGITUDE}, ) assert result["type"] == "create_entry" - assert result["title"] == TEST_NAME + assert result["title"] == f"{TEST_LATITUDE},{TEST_LONGITUDE}" assert result["data"] == { - CONF_NAME: TEST_NAME, - CONF_DIMENSION: 512, - CONF_COUNTRY: "NL", - CONF_WEATHER: False, - CONF_CAMERA: True, - CONF_SENSOR: False, - } - - -async def test_config_flow_setup_weather(hass): - """Test setup of weather.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - assert result["type"] == "form" - assert result["step_id"] == "user" - assert result["errors"] == {} - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - {CONF_DOMAIN: "Weather"}, - ) - - assert result["type"] == "form" - assert result["step_id"] == "weather" - assert result["errors"] == {} - - with patch( - "homeassistant.components.buienradar.async_setup_entry", return_value=True - ): - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - CONF_NAME: TEST_NAME, - CONF_LATITUDE: TEST_LATITUDE, - CONF_LONGITUDE: TEST_LONGITUDE, - }, - ) - - assert result["type"] == "create_entry" - assert result["title"] == TEST_NAME - assert result["data"] == { - CONF_NAME: TEST_NAME, CONF_LATITUDE: TEST_LATITUDE, CONF_LONGITUDE: TEST_LONGITUDE, - CONF_WEATHER: True, - CONF_CAMERA: False, - CONF_SENSOR: True, } -async def test_config_flow_already_configured_camera(hass): - """Test already configured.""" - entry = MockConfigEntry( - domain=DOMAIN, - data={ - CONF_NAME: TEST_NAME, - CONF_DIMENSION: 512, - CONF_COUNTRY: "NL", - CONF_WEATHER: False, - CONF_CAMERA: True, - CONF_SENSOR: False, - }, - unique_id=DOMAIN, - ) - entry.add_to_hass(hass) - - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - assert result["type"] == "form" - assert result["step_id"] == "user" - assert result["errors"] == {} - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - {CONF_DOMAIN: "Camera"}, - ) - - assert result["type"] == "form" - assert result["step_id"] == "camera" - assert result["errors"] == {} - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - {CONF_NAME: TEST_NAME, CONF_DIMENSION: 512, CONF_COUNTRY: "NL"}, - ) - - assert result["type"] == "form" - assert result["step_id"] == "camera" - assert result["errors"] == {"base": "already_configured"} - - async def test_config_flow_already_configured_weather(hass): """Test already configured.""" entry = MockConfigEntry( domain=DOMAIN, data={ - CONF_NAME: TEST_NAME, CONF_LATITUDE: TEST_LATITUDE, CONF_LONGITUDE: TEST_LONGITUDE, - CONF_WEATHER: True, - CONF_CAMERA: False, - CONF_SENSOR: True, }, unique_id=DOMAIN, ) @@ -175,24 +59,11 @@ async def test_config_flow_already_configured_weather(hass): result = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_DOMAIN: "Weather"}, + {CONF_LATITUDE: TEST_LATITUDE, CONF_LONGITUDE: TEST_LONGITUDE}, ) assert result["type"] == "form" - assert result["step_id"] == "weather" - assert result["errors"] == {} - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - CONF_NAME: TEST_NAME, - CONF_LATITUDE: TEST_LATITUDE, - CONF_LONGITUDE: TEST_LONGITUDE, - }, - ) - - assert result["type"] == "form" - assert result["step_id"] == "weather" + assert result["step_id"] == "user" assert result["errors"] == {"base": "already_configured"} @@ -205,85 +76,20 @@ async def test_import_camera(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data={ - CONF_NAME: TEST_NAME, - CONF_DIMENSION: TEST_DIMENSION, - CONF_COUNTRY: TEST_COUNTRY, - CONF_CAMERA: True, - CONF_SENSOR: False, - CONF_WEATHER: False, - }, + data={CONF_LATITUDE: TEST_LATITUDE, CONF_LONGITUDE: TEST_LONGITUDE}, ) assert result["type"] == "create_entry" - assert result["title"] == TEST_NAME + assert result["title"] == f"{TEST_LATITUDE},{TEST_LONGITUDE}" assert result["data"] == { - CONF_NAME: TEST_NAME, - CONF_DIMENSION: 512, - CONF_COUNTRY: "NL", - CONF_WEATHER: False, - CONF_CAMERA: True, - CONF_SENSOR: False, - } - - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_IMPORT}, - data={ - CONF_NAME: TEST_NAME, - CONF_DIMENSION: TEST_DIMENSION, - CONF_COUNTRY: TEST_COUNTRY, - CONF_CAMERA: True, - CONF_SENSOR: False, - CONF_WEATHER: False, - }, - ) - - assert result["type"] == "abort" - assert result["reason"] == "already_configured" - - -async def test_import_weather(hass): - """Test import of camera.""" - - with patch( - "homeassistant.components.buienradar.async_setup_entry", return_value=True - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_IMPORT}, - data={ - CONF_NAME: TEST_NAME, - CONF_LATITUDE: TEST_LATITUDE, - CONF_LONGITUDE: TEST_LONGITUDE, - CONF_CAMERA: False, - CONF_SENSOR: True, - CONF_WEATHER: True, - }, - ) - - assert result["type"] == "create_entry" - assert result["title"] == TEST_NAME - assert result["data"] == { - CONF_NAME: TEST_NAME, CONF_LATITUDE: TEST_LATITUDE, CONF_LONGITUDE: TEST_LONGITUDE, - CONF_WEATHER: True, - CONF_CAMERA: False, - CONF_SENSOR: True, } result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data={ - CONF_NAME: TEST_NAME, - CONF_LATITUDE: TEST_LATITUDE, - CONF_LONGITUDE: TEST_LONGITUDE, - CONF_CAMERA: False, - CONF_SENSOR: True, - CONF_WEATHER: True, - }, + data={CONF_LATITUDE: TEST_LATITUDE, CONF_LONGITUDE: TEST_LONGITUDE}, ) assert result["type"] == "abort" From 4e5321ec538b8ed5842878558e4dceec08fa5e28 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Mon, 15 Mar 2021 22:23:21 +0000 Subject: [PATCH 40/57] Add option flow --- .../components/buienradar/__init__.py | 20 ++++++ .../components/buienradar/config_flow.py | 61 ++++++++++++++++++- .../components/buienradar/strings.json | 26 ++++---- .../buienradar/translations/en.json | 24 ++++---- 4 files changed, 103 insertions(+), 28 deletions(-) diff --git a/homeassistant/components/buienradar/__init__.py b/homeassistant/components/buienradar/__init__.py index 7e07b0898e915..9669c7e4c8e56 100644 --- a/homeassistant/components/buienradar/__init__.py +++ b/homeassistant/components/buienradar/__init__.py @@ -28,6 +28,8 @@ PLATFORMS = [CONF_WEATHER, CONF_CAMERA, CONF_SENSOR] +DATA_LISTENER = "listener" + _LOGGER = logging.getLogger(__name__) @@ -46,16 +48,24 @@ async def async_setup(hass: HomeAssistant, config: dict): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up buienradar from a config entry.""" + hass.data.setdefault(DOMAIN, {}) + hass.data[DOMAIN][entry.entry_id] = {} + for component in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, component) ) + listener = entry.add_update_listener(async_update_options) + hass.data[DOMAIN][entry.entry_id][DATA_LISTENER] = listener + return True async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): """Unload a config entry.""" + listener = hass.data[DOMAIN][entry.entry_id][DATA_LISTENER] + unload_ok = all( await asyncio.gather( *[ @@ -65,9 +75,19 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): ) ) + if unload_ok: + listener() + + hass.data[DOMAIN].pop(entry.entry_id) + return unload_ok +async def async_update_options(hass: HomeAssistant, config_entry: ConfigEntry): + """Update options.""" + await hass.config_entries.async_reload(config_entry.entry_id) + + def _import_weather_configs(hass, weather_configs, sensor_configs, camera_configs): camera_config = {} if len(camera_configs) > 0: diff --git a/homeassistant/components/buienradar/config_flow.py b/homeassistant/components/buienradar/config_flow.py index ae45dcddd5ac1..fda3e48ca7ec7 100644 --- a/homeassistant/components/buienradar/config_flow.py +++ b/homeassistant/components/buienradar/config_flow.py @@ -8,7 +8,16 @@ from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from .const import DOMAIN +from .const import ( + CONF_COUNTRY, + CONF_DELTA, + CONF_TIMEFRAME, + DEFAULT_COUNTRY, + DEFAULT_DELTA, + DEFAULT_TIMEFRAME, + DOMAIN, + SUPPORTED_COUNTRY_CODES, +) _LOGGER = logging.getLogger(__name__) @@ -30,6 +39,12 @@ class BuienradarFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Get the options flow for this handler.""" + return BuienradarOptionFlowHandler(config_entry) + async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" errors = {} @@ -70,3 +85,47 @@ async def async_step_import(self, import_input=None): return self.async_create_entry( title=f"{latitude},{longitude}", data=import_input ) + + +class BuienradarOptionFlowHandler(config_entries.OptionsFlow): + """Handle options.""" + + def __init__(self, config_entry): + """Initialize options flow.""" + self.config_entry = config_entry + + async def async_step_init(self, user_input=None): + """Manage the options.""" + if user_input is not None: + return self.async_create_entry(title="", data=user_input) + + return self.async_show_form( + step_id="init", + data_schema=vol.Schema( + { + vol.Optional( + CONF_COUNTRY, + default=self.config_entry.options.get( + CONF_COUNTRY, + self.config_entry.data.get(CONF_COUNTRY, DEFAULT_COUNTRY), + ), + ): vol.In(SUPPORTED_COUNTRY_CODES), + vol.Optional( + CONF_DELTA, + default=self.config_entry.options.get( + CONF_DELTA, + self.config_entry.data.get(CONF_DELTA, DEFAULT_DELTA), + ), + ): vol.All(vol.Coerce(int), vol.Range(min=0)), + vol.Optional( + CONF_TIMEFRAME, + default=self.config_entry.options.get( + CONF_TIMEFRAME, + self.config_entry.data.get( + CONF_TIMEFRAME, DEFAULT_TIMEFRAME + ), + ), + ): vol.All(vol.Coerce(int), vol.Range(min=5, max=120)), + } + ), + ) diff --git a/homeassistant/components/buienradar/strings.json b/homeassistant/components/buienradar/strings.json index 150da2a8639d6..740068a952bac 100644 --- a/homeassistant/components/buienradar/strings.json +++ b/homeassistant/components/buienradar/strings.json @@ -2,22 +2,9 @@ "config": { "step": { "user": { - "data": { - "domain": "Select domain to setup" - } - }, - "camera": { - "data": { - "name": "[%key:common::config_flow::data::name%]", - "dimension": "Dimension", - "country_code": "Country" - } - }, - "weather": { "data": { "latitude": "[%key:common::config_flow::data::latitude%]", - "longitude": "[%key:common::config_flow::data::longitude%]", - "name": "[%key:common::config_flow::data::name%]" + "longitude": "[%key:common::config_flow::data::longitude%]" } } }, @@ -27,5 +14,16 @@ "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_location%]" } + }, + "options": { + "step": { + "init": { + "data": { + "country_code": "Country code of the country to display camera images.", + "delta": "Time interval in seconds between camera image updates", + "timeframe": "Minutes to look ahead for precipitation forecast" + } + } + } } } diff --git a/homeassistant/components/buienradar/translations/en.json b/homeassistant/components/buienradar/translations/en.json index c4c8e2d93c160..1965ab05ed932 100644 --- a/homeassistant/components/buienradar/translations/en.json +++ b/homeassistant/components/buienradar/translations/en.json @@ -7,23 +7,21 @@ "already_configured": "Location is already configured" }, "step": { - "camera": { - "data": { - "country_code": "Country", - "dimension": "Dimension", - "name": "Name" - } - }, "user": { "data": { - "domain": "Select domain to setup" + "latitude": "Latitude", + "longitude": "Longitude" } - }, - "weather": { + } + } + }, + "options": { + "step": { + "init": { "data": { - "latitude": "Latitude", - "longitude": "Longitude", - "name": "Name" + "country_code": "Country code of the country to display camera images.", + "delta": "Time interval in seconds between camera image updates", + "timeframe": "Minutes to look ahead for precipitation forecast" } } } From 0e4ebc311d8c8952508fc13093770c01061c287f Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Mon, 15 Mar 2021 22:29:02 +0000 Subject: [PATCH 41/57] Add tests for option flow --- .../components/buienradar/test_config_flow.py | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/tests/components/buienradar/test_config_flow.py b/tests/components/buienradar/test_config_flow.py index 0aebcffc79ec1..642d2e6649d36 100644 --- a/tests/components/buienradar/test_config_flow.py +++ b/tests/components/buienradar/test_config_flow.py @@ -1,7 +1,7 @@ """Test the buienradar2 config flow.""" from unittest.mock import patch -from homeassistant import config_entries +from homeassistant import config_entries, data_entry_flow from homeassistant.components.buienradar.const import DOMAIN from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE @@ -94,3 +94,37 @@ async def test_import_camera(hass): assert result["type"] == "abort" assert result["reason"] == "already_configured" + + +async def test_options_flow(hass): + """Test options flow.""" + entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_LATITUDE: TEST_LATITUDE, + CONF_LONGITUDE: TEST_LONGITUDE, + }, + unique_id=DOMAIN, + ) + entry.add_to_hass(hass) + + 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.async_configure( + result["flow_id"], + user_input={"country_code": "BE", "delta": 450, "timeframe": 30}, + ) + + with patch( + "homeassistant.components.buienradar.async_setup_entry", return_value=True + ), patch( + "homeassistant.components.buienradar.async_unload_entry", return_value=True + ): + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + + await hass.async_block_till_done() + + assert entry.options == {"country_code": "BE", "delta": 450, "timeframe": 30} From e433de2d98ff1ab44f30fbfac48a7fba226d7948 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Tue, 16 Mar 2021 09:05:13 +0000 Subject: [PATCH 42/57] Add import tests --- tests/components/buienradar/test_init.py | 70 +++++++++++++++++++++ tests/components/buienradar/test_weather.py | 20 ++---- 2 files changed, 74 insertions(+), 16 deletions(-) create mode 100644 tests/components/buienradar/test_init.py diff --git a/tests/components/buienradar/test_init.py b/tests/components/buienradar/test_init.py new file mode 100644 index 0000000000000..43438405335d4 --- /dev/null +++ b/tests/components/buienradar/test_init.py @@ -0,0 +1,70 @@ +"""Tests for the buienradar component.""" +from unittest.mock import patch + +from homeassistant.components.buienradar import async_setup +from homeassistant.components.buienradar.const import DOMAIN + + +async def test_import_all(hass): + """Test import of all platforms.""" + config = { + "weather 1": [{"platform": "buienradar", "name": "test1"}], + "sensor 1": [{"platform": "buienradar", "timeframe": 30, "name": "test2"}], + "camera 1": [ + { + "platform": "buienradar", + "country_code": "BE", + "delta": 300, + "name": "test3", + } + ], + } + + with patch( + "homeassistant.components.buienradar.async_setup_entry", return_value=True + ): + await async_setup(hass, config) + await hass.async_block_till_done() + + conf_entries = hass.config_entries.async_entries(DOMAIN) + + assert len(conf_entries) == 1 + + entry = conf_entries[0] + + assert entry.state == "loaded" + assert entry.data == { + "latitude": hass.config.latitude, + "longitude": hass.config.longitude, + "timeframe": 30, + "country_code": "BE", + "delta": 300, + "name": "test2", + } + + +async def test_import_camera(hass): + """Test import of camera platform.""" + config = {"camera 1": [{"platform": "buienradar", "country_code": "NL"}]} + + with patch( + "homeassistant.components.buienradar.async_setup_entry", return_value=True + ): + await async_setup(hass, config) + await hass.async_block_till_done() + + conf_entries = hass.config_entries.async_entries(DOMAIN) + + assert len(conf_entries) == 1 + + entry = conf_entries[0] + + assert entry.state == "loaded" + assert entry.data == { + "latitude": hass.config.latitude, + "longitude": hass.config.longitude, + "timeframe": 60, + "country_code": "NL", + "delta": 600, + "name": "Buienradar", + } diff --git a/tests/components/buienradar/test_weather.py b/tests/components/buienradar/test_weather.py index 8e584202c98c9..532086539fd06 100644 --- a/tests/components/buienradar/test_weather.py +++ b/tests/components/buienradar/test_weather.py @@ -1,22 +1,10 @@ """The tests for the buienradar weather component.""" -from homeassistant.components.buienradar.const import ( - CONF_CAMERA, - CONF_SENSOR, - CONF_WEATHER, - DOMAIN, -) -from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME +from homeassistant.components.buienradar.const import DOMAIN +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE from tests.common import MockConfigEntry -TEST_CFG_DATA = { - CONF_NAME: "volkel", - CONF_LATITUDE: 51.65, - CONF_LONGITUDE: 5.7, - CONF_CAMERA: False, - CONF_SENSOR: False, - CONF_WEATHER: True, -} +TEST_CFG_DATA = {CONF_LATITUDE: 51.5288504, CONF_LONGITUDE: 5.4002156} async def test_smoke_test_setup_component(hass): @@ -28,7 +16,7 @@ async def test_smoke_test_setup_component(hass): await hass.config_entries.async_setup(mock_entry.entry_id) await hass.async_block_till_done() - state = hass.states.get("weather.volkel") + state = hass.states.get("weather.buienradar") assert state.state == "unknown" await hass.config_entries.async_unload(mock_entry.entry_id) From 828ff24b011c068fe91a3b8e7d8bd57aee8b2df0 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Tue, 16 Mar 2021 09:21:03 +0000 Subject: [PATCH 43/57] Some cleanups --- homeassistant/components/buienradar/__init__.py | 14 ++++---------- homeassistant/components/buienradar/const.py | 5 ----- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/buienradar/__init__.py b/homeassistant/components/buienradar/__init__.py index 9669c7e4c8e56..215b9240189f3 100644 --- a/homeassistant/components/buienradar/__init__.py +++ b/homeassistant/components/buienradar/__init__.py @@ -10,13 +10,10 @@ from homeassistant.helpers import entity_registry from .const import ( - CONF_CAMERA, CONF_COUNTRY, CONF_DELTA, CONF_DIMENSION, - CONF_SENSOR, CONF_TIMEFRAME, - CONF_WEATHER, DEFAULT_COUNTRY, DEFAULT_DELTA, DEFAULT_DIMENSION, @@ -26,7 +23,7 @@ CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA) -PLATFORMS = [CONF_WEATHER, CONF_CAMERA, CONF_SENSOR] +PLATFORMS = ["weather", "camera", "sensor"] DATA_LISTENER = "listener" @@ -41,14 +38,13 @@ async def async_setup(hass: HomeAssistant, config: dict): sensor_configs = _filter_domain_configs(config, "sensor", DOMAIN) camera_configs = _filter_domain_configs(config, "camera", DOMAIN) - _import_weather_configs(hass, weather_configs, sensor_configs, camera_configs) + _import_configs(hass, weather_configs, sensor_configs, camera_configs) return True async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up buienradar from a config entry.""" - hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = {} for component in PLATFORMS: @@ -88,7 +84,7 @@ async def async_update_options(hass: HomeAssistant, config_entry: ConfigEntry): await hass.config_entries.async_reload(config_entry.entry_id) -def _import_weather_configs(hass, weather_configs, sensor_configs, camera_configs): +def _import_configs(hass, weather_configs, sensor_configs, camera_configs): camera_config = {} if len(camera_configs) > 0: camera_config = camera_configs[0] @@ -146,9 +142,7 @@ def _try_update_unique_id(hass, config, camera_config): country = camera_config.get(CONF_COUNTRY, DEFAULT_COUNTRY) registry = entity_registry.async_get(hass) - entity_id = registry.async_get_entity_id( - CONF_CAMERA, DOMAIN, f"{dimension}_{country}" - ) + entity_id = registry.async_get_entity_id("camera", DOMAIN, f"{dimension}_{country}") if entity_id is not None: latitude = config[CONF_LATITUDE] diff --git a/homeassistant/components/buienradar/const.py b/homeassistant/components/buienradar/const.py index 7eece4ab209bf..cc785512f9b87 100644 --- a/homeassistant/components/buienradar/const.py +++ b/homeassistant/components/buienradar/const.py @@ -7,11 +7,6 @@ DEFAULT_DIMENSION = 700 DEFAULT_DELTA = 600 -HOME_LOCATION_NAME = "Home" - -CONF_CAMERA = "camera" -CONF_SENSOR = "sensor" -CONF_WEATHER = "weather" CONF_DIMENSION = "dimension" CONF_DELTA = "delta" CONF_COUNTRY = "country_code" From 8de1c1a87859cae3073878bdf6bc80a39e32affe Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Fri, 30 Apr 2021 19:49:34 +0200 Subject: [PATCH 44/57] Apply suggestions from code review Apply code suggestions Co-authored-by: Franck Nijhof --- .../components/buienradar/__init__.py | 25 +++---------------- .../components/buienradar/config_flow.py | 1 - tests/components/buienradar/test_camera.py | 4 +-- 3 files changed, 6 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/buienradar/__init__.py b/homeassistant/components/buienradar/__init__.py index 215b9240189f3..d660cfe49fe57 100644 --- a/homeassistant/components/buienradar/__init__.py +++ b/homeassistant/components/buienradar/__init__.py @@ -45,35 +45,18 @@ async def async_setup(hass: HomeAssistant, config: dict): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up buienradar from a config entry.""" - hass.data[DOMAIN][entry.entry_id] = {} - - for component in PLATFORMS: - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) - ) - + hass.config_entries.async_setup_platforms(entry, PLATFORMS) listener = entry.add_update_listener(async_update_options) - hass.data[DOMAIN][entry.entry_id][DATA_LISTENER] = listener + hass.data[DOMAIN][entry.entry_id] = {DATA_LISTENER: listener} return True async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): """Unload a config entry.""" - listener = hass.data[DOMAIN][entry.entry_id][DATA_LISTENER] - - unload_ok = all( - await asyncio.gather( - *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS - ] - ) - ) - + unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) if unload_ok: - listener() - + hass.data[DOMAIN][entry.entry_id][DATA_LISTENER]() hass.data[DOMAIN].pop(entry.entry_id) return unload_ok diff --git a/homeassistant/components/buienradar/config_flow.py b/homeassistant/components/buienradar/config_flow.py index fda3e48ca7ec7..256ed9f37c09c 100644 --- a/homeassistant/components/buienradar/config_flow.py +++ b/homeassistant/components/buienradar/config_flow.py @@ -37,7 +37,6 @@ class BuienradarFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow for buienradar.""" VERSION = 1 - CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL @staticmethod @callback diff --git a/tests/components/buienradar/test_camera.py b/tests/components/buienradar/test_camera.py index 69a5d43d27784..3c93f492a523d 100644 --- a/tests/components/buienradar/test_camera.py +++ b/tests/components/buienradar/test_camera.py @@ -23,8 +23,8 @@ def radar_map_url(country_code: str = "NL") -> str: - """Build map url, defaulting to 512 wide (as in component).""" - return f"https://api.buienradar.nl/image/1.0/RadarMap{country_code}?w={700}&h={700}" + """Build map URL.""" + return f"https://api.buienradar.nl/image/1.0/RadarMap{country_code}?w=700&h=700" async def _setup_config_entry(hass, entry_id): From f5f3d5971ae6af86634e9bdd8811153e75e24a83 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Fri, 30 Apr 2021 18:16:14 +0000 Subject: [PATCH 45/57] Fix isort,black,mypy --- .../components/buienradar/__init__.py | 3 +- homeassistant/components/buienradar/sensor.py | 3 +- .../components/buienradar/weather.py | 30 +++++++++---------- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/buienradar/__init__.py b/homeassistant/components/buienradar/__init__.py index d660cfe49fe57..9b94b3618f65d 100644 --- a/homeassistant/components/buienradar/__init__.py +++ b/homeassistant/components/buienradar/__init__.py @@ -1,5 +1,4 @@ """The buienradar integration.""" -import asyncio import logging import voluptuous as vol @@ -54,7 +53,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): """Unload a config entry.""" - unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) + unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) if unload_ok: hass.data[DOMAIN][entry.entry_id][DATA_LISTENER]() hass.data[DOMAIN].pop(entry.entry_id) diff --git a/homeassistant/components/buienradar/sensor.py b/homeassistant/components/buienradar/sensor.py index 73a865a4240e1..0566c19e43043 100644 --- a/homeassistant/components/buienradar/sensor.py +++ b/homeassistant/components/buienradar/sensor.py @@ -36,7 +36,6 @@ SPEED_KILOMETERS_PER_HOUR, TEMP_CELSIUS, ) - from homeassistant.core import callback from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import dt as dt_util @@ -203,7 +202,7 @@ async def async_setup_entry( if None in (latitude, longitude): _LOGGER.error("Latitude or longitude not set in Home Assistant config") - return False + return coordinates = {CONF_LATITUDE: float(latitude), CONF_LONGITUDE: float(longitude)} diff --git a/homeassistant/components/buienradar/weather.py b/homeassistant/components/buienradar/weather.py index efd5f8b5fe426..ddb4a39d78168 100644 --- a/homeassistant/components/buienradar/weather.py +++ b/homeassistant/components/buienradar/weather.py @@ -49,20 +49,20 @@ DATA_CONDITION = "buienradar_condition" CONDITION_CLASSES = { - ATTR_CONDITION_CLOUDY: ["c", "p"], - ATTR_CONDITION_FOG: ["d", "n"], - ATTR_CONDITION_HAIL: [], - ATTR_CONDITION_LIGHTNING: ["g"], - ATTR_CONDITION_LIGHTNING_RAINY: ["s"], - ATTR_CONDITION_PARTLYCLOUDY: ["b", "j", "o", "r"], - ATTR_CONDITION_POURING: ["l", "q"], - ATTR_CONDITION_RAINY: ["f", "h", "k", "m"], - ATTR_CONDITION_SNOWY: ["u", "i", "v", "t"], - ATTR_CONDITION_SNOWY_RAINY: ["w"], - ATTR_CONDITION_SUNNY: ["a"], - ATTR_CONDITION_WINDY: [], - ATTR_CONDITION_WINDY_VARIANT: [], - ATTR_CONDITION_EXCEPTIONAL: [], + ATTR_CONDITION_CLOUDY: ("c", "p"), + ATTR_CONDITION_FOG: ("d", "n"), + ATTR_CONDITION_HAIL: (), + ATTR_CONDITION_LIGHTNING: ("g"), + ATTR_CONDITION_LIGHTNING_RAINY: ("s"), + ATTR_CONDITION_PARTLYCLOUDY: ("b", "j", "o", "r"), + ATTR_CONDITION_POURING: ("l", "q"), + ATTR_CONDITION_RAINY: ("f", "h", "k", "m"), + ATTR_CONDITION_SNOWY: ("u", "i", "v", "t"), + ATTR_CONDITION_SNOWY_RAINY: ("w"), + ATTR_CONDITION_SUNNY: ("a"), + ATTR_CONDITION_WINDY: (), + ATTR_CONDITION_WINDY_VARIANT: (), + ATTR_CONDITION_EXCEPTIONAL: (), } @@ -77,7 +77,7 @@ async def async_setup_entry( if None in (latitude, longitude): _LOGGER.error("Latitude or longitude not set in Home Assistant config") - return False + return coordinates = {CONF_LATITUDE: float(latitude), CONF_LONGITUDE: float(longitude)} From e65a143e1dd793dfdb53e1b322d1d080a33ae504 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 30 Apr 2021 21:45:52 +0200 Subject: [PATCH 46/57] Small tweaks and added typing to new parts --- .../components/buienradar/__init__.py | 33 ++++++++++++------- homeassistant/components/buienradar/camera.py | 9 +++-- .../components/buienradar/config_flow.py | 28 +++++++++++----- homeassistant/components/buienradar/sensor.py | 20 +++++------ .../components/buienradar/weather.py | 5 +-- 5 files changed, 61 insertions(+), 34 deletions(-) diff --git a/homeassistant/components/buienradar/__init__.py b/homeassistant/components/buienradar/__init__.py index 9b94b3618f65d..2c1c622762ed1 100644 --- a/homeassistant/components/buienradar/__init__.py +++ b/homeassistant/components/buienradar/__init__.py @@ -1,4 +1,6 @@ """The buienradar integration.""" +from __future__ import annotations + import logging import voluptuous as vol @@ -7,6 +9,7 @@ from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry +from homeassistant.helpers.typing import ConfigType from .const import ( CONF_COUNTRY, @@ -29,7 +32,7 @@ _LOGGER = logging.getLogger(__name__) -async def async_setup(hass: HomeAssistant, config: dict): +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the buienradar component.""" hass.data.setdefault(DOMAIN, {}) @@ -42,16 +45,15 @@ async def async_setup(hass: HomeAssistant, config: dict): return True -async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up buienradar from a config entry.""" hass.config_entries.async_setup_platforms(entry, PLATFORMS) listener = entry.add_update_listener(async_update_options) hass.data[DOMAIN][entry.entry_id] = {DATA_LISTENER: listener} - return True -async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) if unload_ok: @@ -61,14 +63,19 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): return unload_ok -async def async_update_options(hass: HomeAssistant, config_entry: ConfigEntry): +async def async_update_options(hass: HomeAssistant, config_entry: ConfigEntry) -> None: """Update options.""" await hass.config_entries.async_reload(config_entry.entry_id) -def _import_configs(hass, weather_configs, sensor_configs, camera_configs): +def _import_configs( + hass: HomeAssistant, + weather_configs: list[ConfigType], + sensor_configs: list[ConfigType], + camera_configs: list[ConfigType], +) -> None: camera_config = {} - if len(camera_configs) > 0: + if camera_configs: camera_config = camera_configs[0] for config in sensor_configs: @@ -88,7 +95,7 @@ def _import_configs(hass, weather_configs, sensor_configs, camera_configs): configs = weather_configs + sensor_configs - if len(configs) == 0 and len(camera_configs) > 0: + if len(configs) == 0 and camera_configs: config = { CONF_LATITUDE: hass.config.latitude, CONF_LONGITUDE: hass.config.longitude, @@ -119,7 +126,9 @@ def _import_configs(hass, weather_configs, sensor_configs, camera_configs): ) -def _try_update_unique_id(hass, config, camera_config): +def _try_update_unique_id( + hass: HomeAssistant, config: ConfigType, camera_config: ConfigType +) -> None: dimension = camera_config.get(CONF_DIMENSION, DEFAULT_DIMENSION) country = camera_config.get(CONF_COUNTRY, DEFAULT_COUNTRY) @@ -134,11 +143,13 @@ def _try_update_unique_id(hass, config, camera_config): registry.async_update_entity(entity_id, new_unique_id=new_unique_id) -def _filter_domain_configs(config, domain, platform): +def _filter_domain_configs( + config: ConfigType, domain: str, platform: str +) -> list[ConfigType]: configs = [] for entry in config: if entry.startswith(domain): - configs = configs + list( + configs += list( filter(lambda elem: elem["platform"] == platform, config[entry]) ) return configs diff --git a/homeassistant/components/buienradar/camera.py b/homeassistant/components/buienradar/camera.py index 4ac6d020ba03c..a8070f3dec86b 100644 --- a/homeassistant/components/buienradar/camera.py +++ b/homeassistant/components/buienradar/camera.py @@ -11,6 +11,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import dt as dt_util @@ -26,7 +27,7 @@ async def async_setup_entry( - hass: HomeAssistantType, entry: ConfigEntry, async_add_entities + hass: HomeAssistantType, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up buienradar radar-loop camera component.""" config = entry.data @@ -51,7 +52,9 @@ class BuienradarCam(Camera): [0]: https://www.buienradar.nl/overbuienradar/gratis-weerdata """ - def __init__(self, latitude, longitude, delta: float, country: str): + def __init__( + self, latitude: float, longitude: float, delta: float, country: str + ) -> None: """ Initialize the component. @@ -186,6 +189,6 @@ def unique_id(self): return self._unique_id @property - def entity_registry_enabled_default(self): + def entity_registry_enabled_default(self) -> bool: """Return if the entity should be enabled when first added to the entity registry.""" return False diff --git a/homeassistant/components/buienradar/config_flow.py b/homeassistant/components/buienradar/config_flow.py index 256ed9f37c09c..1e544e0e65ec6 100644 --- a/homeassistant/components/buienradar/config_flow.py +++ b/homeassistant/components/buienradar/config_flow.py @@ -1,11 +1,17 @@ """Config flow for buienradar integration.""" +from __future__ import annotations + +from collections.abc import Iterable import logging +from typing import Any import voluptuous as vol from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback +from homeassistant.data_entry_flow import FlowResult import homeassistant.helpers.config_validation as cv from .const import ( @@ -23,9 +29,9 @@ @callback -def configured_instances(hass): +def configured_instances(hass: HomeAssistant) -> Iterable[str]: """Return a set of configured buienradar instances.""" - entries = [] + entries: list[str] = [] for entry in hass.config_entries.async_entries(DOMAIN): entries.append( f"{entry.data.get(CONF_LATITUDE)}-{entry.data.get(CONF_LONGITUDE)}" @@ -40,11 +46,15 @@ class BuienradarFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback - def async_get_options_flow(config_entry): + def async_get_options_flow( + config_entry: ConfigEntry, + ) -> BuienradarOptionFlowHandler: """Get the options flow for this handler.""" return BuienradarOptionFlowHandler(config_entry) - async def async_step_user(self, user_input=None): + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle a flow initialized by the user.""" errors = {} @@ -73,7 +83,7 @@ async def async_step_user(self, user_input=None): errors=errors, ) - async def async_step_import(self, import_input=None): + async def async_step_import(self, import_input: dict[str, Any]) -> FlowResult: """Import a config entry.""" latitude = import_input[CONF_LATITUDE] longitude = import_input[CONF_LONGITUDE] @@ -89,11 +99,13 @@ async def async_step_import(self, import_input=None): class BuienradarOptionFlowHandler(config_entries.OptionsFlow): """Handle options.""" - def __init__(self, config_entry): + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry - async def async_step_init(self, user_input=None): + async def async_step_init( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Manage the options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/buienradar/sensor.py b/homeassistant/components/buienradar/sensor.py index 0566c19e43043..5f88b83f7b06c 100644 --- a/homeassistant/components/buienradar/sensor.py +++ b/homeassistant/components/buienradar/sensor.py @@ -36,8 +36,8 @@ SPEED_KILOMETERS_PER_HOUR, TEMP_CELSIUS, ) -from homeassistant.core import callback -from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util import dt as dt_util from .const import CONF_TIMEFRAME, DEFAULT_TIMEFRAME @@ -187,7 +187,7 @@ async def async_setup_entry( - hass: HomeAssistantType, entry: ConfigEntry, async_add_entities + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Create the buienradar sensor.""" config = entry.data @@ -212,11 +212,11 @@ async def async_setup_entry( timeframe, ) - dev = [] - for sensor_type in SENSOR_TYPES: - dev.append( - BrSensor(sensor_type, config.get(CONF_NAME, "Buienradar"), coordinates) - ) + dev = [ + BrSensor(sensor_type, config.get(CONF_NAME, "Buienradar"), coordinates) + for sensor_type in SENSOR_TYPES + ] + async_add_entities(dev) data = BrData(hass, coordinates, timeframe, dev) @@ -369,7 +369,7 @@ def _load_data(self, data): # noqa: C901 self._state = nested.get(self.type[len(PRECIPITATION_FORECAST) + 1 :]) return True - if self.type == WINDSPEED or self.type == WINDGUST: + if self.type in [WINDSPEED, WINDGUST]: # hass wants windspeeds in km/h not m/s, so convert: self._state = data.get(self.type) if self._state is not None: @@ -454,6 +454,6 @@ def force_update(self): return self._force_update @property - def entity_registry_enabled_default(self): + def entity_registry_enabled_default(self) -> bool: """Return if the entity should be enabled when first added to the entity registry.""" return False diff --git a/homeassistant/components/buienradar/weather.py b/homeassistant/components/buienradar/weather.py index ddb4a39d78168..6c430f80ae6da 100644 --- a/homeassistant/components/buienradar/weather.py +++ b/homeassistant/components/buienradar/weather.py @@ -38,7 +38,8 @@ ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, TEMP_CELSIUS -from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback # Reuse data and API logic from the sensor implementation from .const import DEFAULT_TIMEFRAME, DOMAIN @@ -67,7 +68,7 @@ async def async_setup_entry( - hass: HomeAssistantType, entry: ConfigEntry, async_add_entities + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up the buienradar platform.""" config = entry.data From 895741e430de479b7c9809da45c0168ad6161a89 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Sun, 2 May 2021 18:18:29 +0000 Subject: [PATCH 47/57] Fix review comments (1) --- homeassistant/components/buienradar/__init__.py | 6 +----- homeassistant/components/buienradar/camera.py | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/buienradar/__init__.py b/homeassistant/components/buienradar/__init__.py index 2c1c622762ed1..69911dcc21168 100644 --- a/homeassistant/components/buienradar/__init__.py +++ b/homeassistant/components/buienradar/__init__.py @@ -3,8 +3,6 @@ import logging -import voluptuous as vol - from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME from homeassistant.core import HomeAssistant @@ -23,9 +21,7 @@ DOMAIN, ) -CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA) - -PLATFORMS = ["weather", "camera", "sensor"] +PLATFORMS = ["camera", "sensor", "weather"] DATA_LISTENER = "listener" diff --git a/homeassistant/components/buienradar/camera.py b/homeassistant/components/buienradar/camera.py index a8070f3dec86b..80e0c2012594f 100644 --- a/homeassistant/components/buienradar/camera.py +++ b/homeassistant/components/buienradar/camera.py @@ -190,5 +190,5 @@ def unique_id(self): @property def entity_registry_enabled_default(self) -> bool: - """Return if the entity should be enabled when first added to the entity registry.""" + """Disable entity by default.""" return False From 653b2c100f25ff1df33e6c7d94e8a2b0006e557a Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Sun, 2 May 2021 20:21:25 +0200 Subject: [PATCH 48/57] Apply suggestions from code review Co-authored-by: Martin Hjelmare --- homeassistant/components/buienradar/__init__.py | 4 ++-- homeassistant/components/buienradar/sensor.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/buienradar/__init__.py b/homeassistant/components/buienradar/__init__.py index 69911dcc21168..8f871a9ccd690 100644 --- a/homeassistant/components/buienradar/__init__.py +++ b/homeassistant/components/buienradar/__init__.py @@ -91,14 +91,14 @@ def _import_configs( configs = weather_configs + sensor_configs - if len(configs) == 0 and camera_configs: + if not configs and camera_configs: config = { CONF_LATITUDE: hass.config.latitude, CONF_LONGITUDE: hass.config.longitude, } configs.append(config) - if len(configs) > 0: + if configs: _try_update_unique_id(hass, configs[0], camera_config) for config in configs: diff --git a/homeassistant/components/buienradar/sensor.py b/homeassistant/components/buienradar/sensor.py index 5f88b83f7b06c..e1611fefe09b1 100644 --- a/homeassistant/components/buienradar/sensor.py +++ b/homeassistant/components/buienradar/sensor.py @@ -212,12 +212,12 @@ async def async_setup_entry( timeframe, ) - dev = [ + entities = [ BrSensor(sensor_type, config.get(CONF_NAME, "Buienradar"), coordinates) for sensor_type in SENSOR_TYPES ] - async_add_entities(dev) + async_add_entities(entities) data = BrData(hass, coordinates, timeframe, dev) # schedule the first update in 1 minute from now: From bcc6225e2cd868b7e1cb34349253177e83d0f920 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Sun, 2 May 2021 18:35:22 +0000 Subject: [PATCH 49/57] Fix review comments (2) --- .../components/buienradar/__init__.py | 8 +----- homeassistant/components/buienradar/camera.py | 26 +++++++++++++++++-- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/buienradar/__init__.py b/homeassistant/components/buienradar/__init__.py index 69911dcc21168..db39387da8663 100644 --- a/homeassistant/components/buienradar/__init__.py +++ b/homeassistant/components/buienradar/__init__.py @@ -23,8 +23,6 @@ PLATFORMS = ["camera", "sensor", "weather"] -DATA_LISTENER = "listener" - _LOGGER = logging.getLogger(__name__) @@ -44,8 +42,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up buienradar from a config entry.""" hass.config_entries.async_setup_platforms(entry, PLATFORMS) - listener = entry.add_update_listener(async_update_options) - hass.data[DOMAIN][entry.entry_id] = {DATA_LISTENER: listener} + entry.async_on_unload(entry.add_update_listener(async_update_options)) return True @@ -53,7 +50,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) if unload_ok: - hass.data[DOMAIN][entry.entry_id][DATA_LISTENER]() hass.data[DOMAIN].pop(entry.entry_id) return unload_ok @@ -102,8 +98,6 @@ def _import_configs( _try_update_unique_id(hass, configs[0], camera_config) for config in configs: - _LOGGER.debug("Importing Buienradar %s", config) - data = { CONF_LATITUDE: config.get(CONF_LATITUDE, hass.config.latitude), CONF_LONGITUDE: config.get(CONF_LONGITUDE, hass.config.longitude), diff --git a/homeassistant/components/buienradar/camera.py b/homeassistant/components/buienradar/camera.py index 80e0c2012594f..33c3845b157c5 100644 --- a/homeassistant/components/buienradar/camera.py +++ b/homeassistant/components/buienradar/camera.py @@ -6,10 +6,12 @@ import logging import aiohttp +import voluptuous as vol -from homeassistant.components.camera import Camera +from homeassistant.components.camera import PLATFORM_SCHEMA, Camera from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME +from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import HomeAssistantType @@ -18,6 +20,7 @@ from .const import ( CONF_COUNTRY, CONF_DELTA, + CONF_DIMENSION, DEFAULT_COUNTRY, DEFAULT_DELTA, DEFAULT_DIMENSION, @@ -25,6 +28,25 @@ _LOGGER = logging.getLogger(__name__) +# Maximum range according to docs +DIM_RANGE = vol.All(vol.Coerce(int), vol.Range(min=120, max=700)) + +# Multiple choice for available Radar Map URL +SUPPORTED_COUNTRY_CODES = ["NL", "BE"] + +PLATFORM_SCHEMA = vol.All( + PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_DIMENSION, default=512): DIM_RANGE, + vol.Optional(CONF_DELTA, default=600.0): cv.positive_float, + vol.Optional(CONF_NAME, default="Buienradar loop"): cv.string, + vol.Optional(CONF_COUNTRY, default="NL"): vol.All( + vol.Coerce(str), vol.In(SUPPORTED_COUNTRY_CODES) + ), + } + ) +) + async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, async_add_entities: AddEntitiesCallback From cf7242e3dca806de9a0afb5629cd6279d74a5786 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Sun, 2 May 2021 18:41:57 +0000 Subject: [PATCH 50/57] Fix issues --- homeassistant/components/buienradar/sensor.py | 2 +- homeassistant/components/buienradar/weather.py | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/buienradar/sensor.py b/homeassistant/components/buienradar/sensor.py index e1611fefe09b1..5fa01fc4289f4 100644 --- a/homeassistant/components/buienradar/sensor.py +++ b/homeassistant/components/buienradar/sensor.py @@ -219,7 +219,7 @@ async def async_setup_entry( async_add_entities(entities) - data = BrData(hass, coordinates, timeframe, dev) + data = BrData(hass, coordinates, timeframe, entities) # schedule the first update in 1 minute from now: await data.schedule_update(1) diff --git a/homeassistant/components/buienradar/weather.py b/homeassistant/components/buienradar/weather.py index 6c430f80ae6da..51da71d030f09 100644 --- a/homeassistant/components/buienradar/weather.py +++ b/homeassistant/components/buienradar/weather.py @@ -53,14 +53,19 @@ ATTR_CONDITION_CLOUDY: ("c", "p"), ATTR_CONDITION_FOG: ("d", "n"), ATTR_CONDITION_HAIL: (), - ATTR_CONDITION_LIGHTNING: ("g"), - ATTR_CONDITION_LIGHTNING_RAINY: ("s"), - ATTR_CONDITION_PARTLYCLOUDY: ("b", "j", "o", "r"), + ATTR_CONDITION_LIGHTNING: ("g",), + ATTR_CONDITION_LIGHTNING_RAINY: ("s",), + ATTR_CONDITION_PARTLYCLOUDY: ( + "b", + "j", + "o", + "r", + ), ATTR_CONDITION_POURING: ("l", "q"), ATTR_CONDITION_RAINY: ("f", "h", "k", "m"), ATTR_CONDITION_SNOWY: ("u", "i", "v", "t"), - ATTR_CONDITION_SNOWY_RAINY: ("w"), - ATTR_CONDITION_SUNNY: ("a"), + ATTR_CONDITION_SNOWY_RAINY: ("w",), + ATTR_CONDITION_SUNNY: ("a",), ATTR_CONDITION_WINDY: (), ATTR_CONDITION_WINDY_VARIANT: (), ATTR_CONDITION_EXCEPTIONAL: (), From 7e7a1f006373f575de0f09eb669d60affcfb0561 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Sun, 2 May 2021 19:19:19 +0000 Subject: [PATCH 51/57] Fix unique id --- .../components/buienradar/__init__.py | 4 +-- .../components/buienradar/config_flow.py | 29 +++++-------------- .../components/buienradar/test_config_flow.py | 7 ++--- 3 files changed, 12 insertions(+), 28 deletions(-) diff --git a/homeassistant/components/buienradar/__init__.py b/homeassistant/components/buienradar/__init__.py index f638e0f8b1708..3c5792a3caada 100644 --- a/homeassistant/components/buienradar/__init__.py +++ b/homeassistant/components/buienradar/__init__.py @@ -139,7 +139,5 @@ def _filter_domain_configs( configs = [] for entry in config: if entry.startswith(domain): - configs += list( - filter(lambda elem: elem["platform"] == platform, config[entry]) - ) + configs += [x for x in config[entry] if x["platform"] == platform] return configs diff --git a/homeassistant/components/buienradar/config_flow.py b/homeassistant/components/buienradar/config_flow.py index 1e544e0e65ec6..e773b39027ec5 100644 --- a/homeassistant/components/buienradar/config_flow.py +++ b/homeassistant/components/buienradar/config_flow.py @@ -1,7 +1,6 @@ """Config flow for buienradar integration.""" from __future__ import annotations -from collections.abc import Iterable import logging from typing import Any @@ -10,7 +9,7 @@ from homeassistant import config_entries from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import callback from homeassistant.data_entry_flow import FlowResult import homeassistant.helpers.config_validation as cv @@ -28,17 +27,6 @@ _LOGGER = logging.getLogger(__name__) -@callback -def configured_instances(hass: HomeAssistant) -> Iterable[str]: - """Return a set of configured buienradar instances.""" - entries: list[str] = [] - for entry in hass.config_entries.async_entries(DOMAIN): - entries.append( - f"{entry.data.get(CONF_LATITUDE)}-{entry.data.get(CONF_LONGITUDE)}" - ) - return set(entries) - - class BuienradarFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow for buienradar.""" @@ -56,15 +44,14 @@ async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Handle a flow initialized by the user.""" - errors = {} - if user_input is not None: lat = user_input.get(CONF_LATITUDE) lon = user_input.get(CONF_LONGITUDE) - if f"{lat}-{lon}" not in configured_instances(self.hass): - return self.async_create_entry(title=f"{lat},{lon}", data=user_input) - errors["base"] = "already_configured" + await self.async_set_unique_id(f"{lat}-{lon}") + self._abort_if_unique_id_configured() + + return self.async_create_entry(title=f"{lat},{lon}", data=user_input) data_schema = vol.Schema( { @@ -80,7 +67,7 @@ async def async_step_user( return self.async_show_form( step_id="user", data_schema=data_schema, - errors=errors, + errors={}, ) async def async_step_import(self, import_input: dict[str, Any]) -> FlowResult: @@ -88,8 +75,8 @@ async def async_step_import(self, import_input: dict[str, Any]) -> FlowResult: latitude = import_input[CONF_LATITUDE] longitude = import_input[CONF_LONGITUDE] - if f"{latitude}-{longitude}" in configured_instances(self.hass): - return self.async_abort(reason="already_configured") + await self.async_set_unique_id(f"{latitude}-{longitude}") + self._abort_if_unique_id_configured() return self.async_create_entry( title=f"{latitude},{longitude}", data=import_input diff --git a/tests/components/buienradar/test_config_flow.py b/tests/components/buienradar/test_config_flow.py index 642d2e6649d36..df1142ec13a1a 100644 --- a/tests/components/buienradar/test_config_flow.py +++ b/tests/components/buienradar/test_config_flow.py @@ -45,7 +45,7 @@ async def test_config_flow_already_configured_weather(hass): CONF_LATITUDE: TEST_LATITUDE, CONF_LONGITUDE: TEST_LONGITUDE, }, - unique_id=DOMAIN, + unique_id=f"{TEST_LATITUDE}-{TEST_LONGITUDE}", ) entry.add_to_hass(hass) @@ -62,9 +62,8 @@ async def test_config_flow_already_configured_weather(hass): {CONF_LATITUDE: TEST_LATITUDE, CONF_LONGITUDE: TEST_LONGITUDE}, ) - assert result["type"] == "form" - assert result["step_id"] == "user" - assert result["errors"] == {"base": "already_configured"} + assert result["type"] == "abort" + assert result["reason"] == "already_configured" async def test_import_camera(hass): From b11919d3a11dfdb0080b2c3af38ebce46e6b8730 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Sun, 2 May 2021 20:27:02 +0000 Subject: [PATCH 52/57] Improve tests --- tests/components/buienradar/test_camera.py | 87 ++++++++----------- .../components/buienradar/test_config_flow.py | 3 + tests/components/buienradar/test_sensor.py | 3 - tests/components/buienradar/test_weather.py | 3 - 4 files changed, 40 insertions(+), 56 deletions(-) diff --git a/tests/components/buienradar/test_camera.py b/tests/components/buienradar/test_camera.py index 3c93f492a523d..a84267da6cb6d 100644 --- a/tests/components/buienradar/test_camera.py +++ b/tests/components/buienradar/test_camera.py @@ -2,7 +2,6 @@ import asyncio from contextlib import suppress import copy -from unittest.mock import patch from aiohttp.client_exceptions import ClientResponseError @@ -12,6 +11,7 @@ CONF_LONGITUDE, HTTP_INTERNAL_SERVER_ERROR, ) +from homeassistant.helpers.entity_registry import async_get_registry from homeassistant.util import dt as dt_util from tests.common import MockConfigEntry @@ -19,7 +19,10 @@ # An infinitesimally small time-delta. EPSILON_DELTA = 0.0000000001 -TEST_CFG_DATA = {CONF_LATITUDE: 51.5288504, CONF_LONGITUDE: 5.4002156} +TEST_LATITUDE = 51.5288504 +TEST_LONGITUDE = 5.4002156 + +TEST_CFG_DATA = {CONF_LATITUDE: TEST_LATITUDE, CONF_LONGITUDE: TEST_LONGITUDE} def radar_map_url(country_code: str = "NL") -> str: @@ -27,14 +30,19 @@ def radar_map_url(country_code: str = "NL") -> str: return f"https://api.buienradar.nl/image/1.0/RadarMap{country_code}?w=700&h=700" -async def _setup_config_entry(hass, entry_id): - with patch( - "homeassistant.components.buienradar.camera.BuienradarCam.entity_registry_enabled_default" - ) as enabled_by_default_mock: - enabled_by_default_mock.return_value = True +async def _setup_config_entry(hass, entry): + entity_registry = await async_get_registry(hass) + entity_registry.async_get_or_create( + domain="camera", + platform="buienradar", + unique_id=f"{TEST_LATITUDE:2.6f}{TEST_LONGITUDE:2.6f}", + config_entry=entry, + original_name="Buienradar", + ) + await hass.async_block_till_done() - await hass.config_entries.async_setup(entry_id) - await hass.async_block_till_done() + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() async def test_fetching_url_and_caching(aioclient_mock, hass, hass_client): @@ -45,11 +53,11 @@ async def test_fetching_url_and_caching(aioclient_mock, hass, hass_client): mock_entry.add_to_hass(hass) - await _setup_config_entry(hass, mock_entry.entry_id) + await _setup_config_entry(hass, mock_entry) client = await hass_client() - resp = await client.get("/api/camera_proxy/camera.buienradar") + resp = await client.get("/api/camera_proxy/camera.buienradar_51_5288505_400216") assert resp.status == 200 assert aioclient_mock.call_count == 1 @@ -59,12 +67,9 @@ async def test_fetching_url_and_caching(aioclient_mock, hass, hass_client): # default delta is 600s -> should be the same when calling immediately # afterwards. - resp = await client.get("/api/camera_proxy/camera.buienradar") + resp = await client.get("/api/camera_proxy/camera.buienradar_51_5288505_400216") assert aioclient_mock.call_count == 1 - await hass.config_entries.async_unload(mock_entry.entry_id) - await hass.async_block_till_done() - async def test_expire_delta(aioclient_mock, hass, hass_client): """Test that the cache expires after delta.""" @@ -78,11 +83,11 @@ async def test_expire_delta(aioclient_mock, hass, hass_client): mock_entry.add_to_hass(hass) - await _setup_config_entry(hass, mock_entry.entry_id) + await _setup_config_entry(hass, mock_entry) client = await hass_client() - resp = await client.get("/api/camera_proxy/camera.buienradar") + resp = await client.get("/api/camera_proxy/camera.buienradar_51_5288505_400216") assert resp.status == 200 assert aioclient_mock.call_count == 1 @@ -91,12 +96,9 @@ async def test_expire_delta(aioclient_mock, hass, hass_client): await asyncio.sleep(EPSILON_DELTA) # tiny delta has passed -> should immediately call again - resp = await client.get("/api/camera_proxy/camera.buienradar") + resp = await client.get("/api/camera_proxy/camera.buienradar_51_5288505_400216") assert aioclient_mock.call_count == 2 - await hass.config_entries.async_unload(mock_entry.entry_id) - await hass.async_block_till_done() - async def test_only_one_fetch_at_a_time(aioclient_mock, hass, hass_client): """Test that it fetches with only one request at the same time.""" @@ -106,12 +108,12 @@ async def test_only_one_fetch_at_a_time(aioclient_mock, hass, hass_client): mock_entry.add_to_hass(hass) - await _setup_config_entry(hass, mock_entry.entry_id) + await _setup_config_entry(hass, mock_entry) client = await hass_client() - resp_1 = client.get("/api/camera_proxy/camera.buienradar") - resp_2 = client.get("/api/camera_proxy/camera.buienradar") + resp_1 = client.get("/api/camera_proxy/camera.buienradar_51_5288505_400216") + resp_2 = client.get("/api/camera_proxy/camera.buienradar_51_5288505_400216") resp = await resp_1 resp_2 = await resp_2 @@ -120,9 +122,6 @@ async def test_only_one_fetch_at_a_time(aioclient_mock, hass, hass_client): assert aioclient_mock.call_count == 1 - await hass.config_entries.async_unload(mock_entry.entry_id) - await hass.async_block_till_done() - async def test_belgium_country(aioclient_mock, hass, hass_client): """Test that it actually adheres to another country like Belgium.""" @@ -135,17 +134,14 @@ async def test_belgium_country(aioclient_mock, hass, hass_client): mock_entry.add_to_hass(hass) - await _setup_config_entry(hass, mock_entry.entry_id) + await _setup_config_entry(hass, mock_entry) client = await hass_client() - await client.get("/api/camera_proxy/camera.buienradar") + await client.get("/api/camera_proxy/camera.buienradar_51_5288505_400216") assert aioclient_mock.call_count == 1 - await hass.config_entries.async_unload(mock_entry.entry_id) - await hass.async_block_till_done() - async def test_failure_response_not_cached(aioclient_mock, hass, hass_client): """Test that it does not cache a failure response.""" @@ -155,18 +151,15 @@ async def test_failure_response_not_cached(aioclient_mock, hass, hass_client): mock_entry.add_to_hass(hass) - await _setup_config_entry(hass, mock_entry.entry_id) + await _setup_config_entry(hass, mock_entry) client = await hass_client() - await client.get("/api/camera_proxy/camera.buienradar") - await client.get("/api/camera_proxy/camera.buienradar") + await client.get("/api/camera_proxy/camera.buienradar_51_5288505_400216") + await client.get("/api/camera_proxy/camera.buienradar_51_5288505_400216") assert aioclient_mock.call_count == 2 - await hass.config_entries.async_unload(mock_entry.entry_id) - await hass.async_block_till_done() - async def test_last_modified_updates(aioclient_mock, hass, hass_client): """Test that it does respect HTTP not modified.""" @@ -189,11 +182,11 @@ async def test_last_modified_updates(aioclient_mock, hass, hass_client): mock_entry.add_to_hass(hass) - await _setup_config_entry(hass, mock_entry.entry_id) + await _setup_config_entry(hass, mock_entry) client = await hass_client() - resp_1 = await client.get("/api/camera_proxy/camera.buienradar") + resp_1 = await client.get("/api/camera_proxy/camera.buienradar_51_5288505_400216") # It is not possible to check if header was sent. assert aioclient_mock.call_count == 1 @@ -207,14 +200,11 @@ async def test_last_modified_updates(aioclient_mock, hass, hass_client): aioclient_mock.get(radar_map_url(), text=None, status=304) - resp_2 = await client.get("/api/camera_proxy/camera.buienradar") + resp_2 = await client.get("/api/camera_proxy/camera.buienradar_51_5288505_400216") assert aioclient_mock.call_count == 1 assert (await resp_1.read()) == (await resp_2.read()) - await hass.config_entries.async_unload(mock_entry.entry_id) - await hass.async_block_till_done() - async def test_retries_after_error(aioclient_mock, hass, hass_client): """Test that it does retry after an error instead of caching.""" @@ -222,7 +212,7 @@ async def test_retries_after_error(aioclient_mock, hass, hass_client): mock_entry.add_to_hass(hass) - await _setup_config_entry(hass, mock_entry.entry_id) + await _setup_config_entry(hass, mock_entry) client = await hass_client() @@ -230,7 +220,7 @@ async def test_retries_after_error(aioclient_mock, hass, hass_client): # A 404 should not return data and throw: with suppress(ClientResponseError): - await client.get("/api/camera_proxy/camera.buienradar") + await client.get("/api/camera_proxy/camera.buienradar_51_5288505_400216") assert aioclient_mock.call_count == 1 @@ -241,12 +231,9 @@ async def test_retries_after_error(aioclient_mock, hass, hass_client): assert aioclient_mock.call_count == 0 # http error should not be cached, immediate retry. - resp_2 = await client.get("/api/camera_proxy/camera.buienradar") + resp_2 = await client.get("/api/camera_proxy/camera.buienradar_51_5288505_400216") assert aioclient_mock.call_count == 1 # Binary text can not be added as body to `aioclient_mock.get(text=...)`, # while `resp.read()` returns bytes, encode the value. assert (await resp_2.read()) == b"DEADBEEF" - - await hass.config_entries.async_unload(mock_entry.entry_id) - await hass.async_block_till_done() diff --git a/tests/components/buienradar/test_config_flow.py b/tests/components/buienradar/test_config_flow.py index df1142ec13a1a..3f7e41b29a540 100644 --- a/tests/components/buienradar/test_config_flow.py +++ b/tests/components/buienradar/test_config_flow.py @@ -107,6 +107,9 @@ async def test_options_flow(hass): ) entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + result = await hass.config_entries.options.async_init(entry.entry_id) assert result["type"] == "form" diff --git a/tests/components/buienradar/test_sensor.py b/tests/components/buienradar/test_sensor.py index 4b6ed5ecbc047..f0a24b6beb3f8 100644 --- a/tests/components/buienradar/test_sensor.py +++ b/tests/components/buienradar/test_sensor.py @@ -27,6 +27,3 @@ async def test_smoke_test_setup_component(hass): for cond in CONDITIONS: state = hass.states.get(f"sensor.buienradar_{cond}") assert state.state == "unknown" - - await hass.config_entries.async_unload(mock_entry.entry_id) - await hass.async_block_till_done() diff --git a/tests/components/buienradar/test_weather.py b/tests/components/buienradar/test_weather.py index 532086539fd06..9d16b531ad01d 100644 --- a/tests/components/buienradar/test_weather.py +++ b/tests/components/buienradar/test_weather.py @@ -18,6 +18,3 @@ async def test_smoke_test_setup_component(hass): state = hass.states.get("weather.buienradar") assert state.state == "unknown" - - await hass.config_entries.async_unload(mock_entry.entry_id) - await hass.async_block_till_done() From bd0f86c5f07d95808335e9de1d1a06c7bd76096c Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Sun, 2 May 2021 20:44:20 +0000 Subject: [PATCH 53/57] Extend tests --- .../components/buienradar/test_config_flow.py | 1 - tests/components/buienradar/test_init.py | 23 ++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/tests/components/buienradar/test_config_flow.py b/tests/components/buienradar/test_config_flow.py index 3f7e41b29a540..b8abefec70a8d 100644 --- a/tests/components/buienradar/test_config_flow.py +++ b/tests/components/buienradar/test_config_flow.py @@ -68,7 +68,6 @@ async def test_config_flow_already_configured_weather(hass): async def test_import_camera(hass): """Test import of camera.""" - with patch( "homeassistant.components.buienradar.async_setup_entry", return_value=True ): diff --git a/tests/components/buienradar/test_init.py b/tests/components/buienradar/test_init.py index 43438405335d4..a02bb24693f0c 100644 --- a/tests/components/buienradar/test_init.py +++ b/tests/components/buienradar/test_init.py @@ -3,6 +3,7 @@ from homeassistant.components.buienradar import async_setup from homeassistant.components.buienradar.const import DOMAIN +from homeassistant.helpers.entity_registry import async_get_registry async def test_import_all(hass): @@ -45,7 +46,18 @@ async def test_import_all(hass): async def test_import_camera(hass): """Test import of camera platform.""" - config = {"camera 1": [{"platform": "buienradar", "country_code": "NL"}]} + entity_registry = await async_get_registry(hass) + entity_registry.async_get_or_create( + domain="camera", + platform="buienradar", + unique_id="512_NL", + original_name="test_name", + ) + await hass.async_block_till_done() + + config = { + "camera 1": [{"platform": "buienradar", "country_code": "NL", "dimension": 512}] + } with patch( "homeassistant.components.buienradar.async_setup_entry", return_value=True @@ -68,3 +80,12 @@ async def test_import_camera(hass): "delta": 600, "name": "Buienradar", } + + entity_id = entity_registry.async_get_entity_id( + "camera", + "buienradar", + f"{hass.config.latitude:2.6f}{hass.config.longitude:2.6f}", + ) + assert entity_id + entity = entity_registry.async_get(entity_id) + assert entity.original_name == "test_name" From aaf549efc56a8090024a8a2b9cb712d81a8dc2d2 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Mon, 3 May 2021 07:26:55 +0000 Subject: [PATCH 54/57] Fix issue with unload --- homeassistant/components/buienradar/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/homeassistant/components/buienradar/__init__.py b/homeassistant/components/buienradar/__init__.py index 3c5792a3caada..0474876bf2fde 100644 --- a/homeassistant/components/buienradar/__init__.py +++ b/homeassistant/components/buienradar/__init__.py @@ -49,8 +49,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) - if unload_ok: - hass.data[DOMAIN].pop(entry.entry_id) return unload_ok From 5144929553443a5e8f2249ca15aa21429d54e27f Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Tue, 4 May 2021 09:40:26 +0000 Subject: [PATCH 55/57] Address review comments --- homeassistant/components/buienradar/sensor.py | 23 ++++++++++++++++++- .../components/buienradar/weather.py | 14 +++++++++++ tests/components/buienradar/test_camera.py | 4 ++-- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/buienradar/sensor.py b/homeassistant/components/buienradar/sensor.py index 5fa01fc4289f4..ada02c7689aa3 100644 --- a/homeassistant/components/buienradar/sensor.py +++ b/homeassistant/components/buienradar/sensor.py @@ -18,13 +18,15 @@ WINDGUST, WINDSPEED, ) +import voluptuous as vol -from homeassistant.components.sensor import SensorEntity +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_LATITUDE, CONF_LONGITUDE, + CONF_MONITORED_CONDITIONS, CONF_NAME, DEGREE, IRRADIATION_WATTS_PER_SQUARE_METER, @@ -37,6 +39,7 @@ TEMP_CELSIUS, ) from homeassistant.core import HomeAssistant, callback +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util import dt as dt_util @@ -185,6 +188,24 @@ "symbol_5d": ["Symbol 5d", None, None], } +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional( + CONF_MONITORED_CONDITIONS, default=["symbol", "temperature"] + ): vol.All(cv.ensure_list, vol.Length(min=1), [vol.In(SENSOR_TYPES.keys())]), + vol.Inclusive( + CONF_LATITUDE, "coordinates", "Latitude and longitude must exist together" + ): cv.latitude, + vol.Inclusive( + CONF_LONGITUDE, "coordinates", "Latitude and longitude must exist together" + ): cv.longitude, + vol.Optional(CONF_TIMEFRAME, default=DEFAULT_TIMEFRAME): vol.All( + vol.Coerce(int), vol.Range(min=5, max=120) + ), + vol.Optional(CONF_NAME, default="br"): cv.string, + } +) + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback diff --git a/homeassistant/components/buienradar/weather.py b/homeassistant/components/buienradar/weather.py index 51da71d030f09..b4b0336e8d279 100644 --- a/homeassistant/components/buienradar/weather.py +++ b/homeassistant/components/buienradar/weather.py @@ -11,6 +11,7 @@ WINDAZIMUTH, WINDSPEED, ) +import voluptuous as vol from homeassistant.components.weather import ( ATTR_CONDITION_CLOUDY, @@ -34,11 +35,13 @@ ATTR_FORECAST_TIME, ATTR_FORECAST_WIND_BEARING, ATTR_FORECAST_WIND_SPEED, + PLATFORM_SCHEMA, WeatherEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, TEMP_CELSIUS from homeassistant.core import HomeAssistant +from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback # Reuse data and API logic from the sensor implementation @@ -47,6 +50,8 @@ _LOGGER = logging.getLogger(__name__) +CONF_FORECAST = "forecast" + DATA_CONDITION = "buienradar_condition" CONDITION_CLASSES = { @@ -71,6 +76,15 @@ ATTR_CONDITION_EXCEPTIONAL: (), } +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_LATITUDE): cv.latitude, + vol.Optional(CONF_LONGITUDE): cv.longitude, + vol.Optional(CONF_FORECAST, default=True): cv.boolean, + } +) + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback diff --git a/tests/components/buienradar/test_camera.py b/tests/components/buienradar/test_camera.py index a84267da6cb6d..3d0c63d972b6a 100644 --- a/tests/components/buienradar/test_camera.py +++ b/tests/components/buienradar/test_camera.py @@ -11,7 +11,7 @@ CONF_LONGITUDE, HTTP_INTERNAL_SERVER_ERROR, ) -from homeassistant.helpers.entity_registry import async_get_registry +from homeassistant.helpers.entity_registry import async_get from homeassistant.util import dt as dt_util from tests.common import MockConfigEntry @@ -31,7 +31,7 @@ def radar_map_url(country_code: str = "NL") -> str: async def _setup_config_entry(hass, entry): - entity_registry = await async_get_registry(hass) + entity_registry = async_get(hass) entity_registry.async_get_or_create( domain="camera", platform="buienradar", From cddba9360eeec468f26025234fa702faade6c021 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Tue, 4 May 2021 09:45:32 +0000 Subject: [PATCH 56/57] Add warning when loading platform --- homeassistant/components/buienradar/camera.py | 7 +++++++ homeassistant/components/buienradar/sensor.py | 7 +++++++ homeassistant/components/buienradar/weather.py | 7 +++++++ 3 files changed, 21 insertions(+) diff --git a/homeassistant/components/buienradar/camera.py b/homeassistant/components/buienradar/camera.py index 33c3845b157c5..1a2d6d4d0bef6 100644 --- a/homeassistant/components/buienradar/camera.py +++ b/homeassistant/components/buienradar/camera.py @@ -48,6 +48,13 @@ ) +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Set up buienradar camera platform.""" + _LOGGER.warning( + "Platform configuration is deprecated, will be removed in a future release" + ) + + async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: diff --git a/homeassistant/components/buienradar/sensor.py b/homeassistant/components/buienradar/sensor.py index ada02c7689aa3..e4a317cfacedb 100644 --- a/homeassistant/components/buienradar/sensor.py +++ b/homeassistant/components/buienradar/sensor.py @@ -207,6 +207,13 @@ ) +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Set up buienradar sensor platform.""" + _LOGGER.warning( + "Platform configuration is deprecated, will be removed in a future release" + ) + + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: diff --git a/homeassistant/components/buienradar/weather.py b/homeassistant/components/buienradar/weather.py index b4b0336e8d279..0aa57efc5f999 100644 --- a/homeassistant/components/buienradar/weather.py +++ b/homeassistant/components/buienradar/weather.py @@ -86,6 +86,13 @@ ) +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Set up buienradar weather platform.""" + _LOGGER.warning( + "Platform configuration is deprecated, will be removed in a future release" + ) + + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: From c68172c5e707c3adac126124db3dc0cf9e1bfb45 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Tue, 4 May 2021 09:51:37 +0000 Subject: [PATCH 57/57] Add load/unload test --- tests/components/buienradar/test_init.py | 29 ++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/components/buienradar/test_init.py b/tests/components/buienradar/test_init.py index a02bb24693f0c..e3ac8c025e139 100644 --- a/tests/components/buienradar/test_init.py +++ b/tests/components/buienradar/test_init.py @@ -3,8 +3,14 @@ from homeassistant.components.buienradar import async_setup from homeassistant.components.buienradar.const import DOMAIN +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE from homeassistant.helpers.entity_registry import async_get_registry +from tests.common import MockConfigEntry + +TEST_LATITUDE = 51.5288504 +TEST_LONGITUDE = 5.4002156 + async def test_import_all(hass): """Test import of all platforms.""" @@ -89,3 +95,26 @@ async def test_import_camera(hass): assert entity_id entity = entity_registry.async_get(entity_id) assert entity.original_name == "test_name" + + +async def test_load_unload(hass): + """Test options flow.""" + entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_LATITUDE: TEST_LATITUDE, + CONF_LONGITUDE: TEST_LONGITUDE, + }, + unique_id=DOMAIN, + ) + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state == "loaded" + + await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state == "not_loaded"