From 505efca704d6afaf80e4d0920cbe2bee8bd30367 Mon Sep 17 00:00:00 2001 From: Quentin POLLET Date: Thu, 5 Dec 2019 13:20:24 +0100 Subject: [PATCH 01/17] Add config flow to Meteo-France --- .coveragerc | 5 +- .../meteo_france/.translations/en.json | 21 +++ .../components/meteo_france/__init__.py | 129 ++++++++---------- .../components/meteo_france/config_flow.py | 80 +++++++++++ .../components/meteo_france/const.py | 1 - .../components/meteo_france/manifest.json | 1 + .../components/meteo_france/sensor.py | 43 ++++-- .../components/meteo_france/strings.json | 21 +++ .../components/meteo_france/weather.py | 31 +++-- homeassistant/generated/config_flows.py | 1 + requirements_test_all.txt | 6 + tests/components/meteo_france/__init__.py | 1 + .../meteo_france/test_config_flow.py | 96 +++++++++++++ 13 files changed, 343 insertions(+), 93 deletions(-) create mode 100644 homeassistant/components/meteo_france/.translations/en.json create mode 100644 homeassistant/components/meteo_france/config_flow.py create mode 100644 homeassistant/components/meteo_france/strings.json create mode 100644 tests/components/meteo_france/__init__.py create mode 100644 tests/components/meteo_france/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index cc2402e480b82d..8fe53ec828226f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -413,7 +413,10 @@ omit = homeassistant/components/mediaroom/media_player.py homeassistant/components/message_bird/notify.py homeassistant/components/met/weather.py - homeassistant/components/meteo_france/* + homeassistant/components/meteo_france/__init__.py + homeassistant/components/meteo_france/const.py + homeassistant/components/meteo_france/sensor.py + homeassistant/components/meteo_france/weather.py homeassistant/components/meteoalarm/* homeassistant/components/metoffice/sensor.py homeassistant/components/metoffice/weather.py diff --git a/homeassistant/components/meteo_france/.translations/en.json b/homeassistant/components/meteo_france/.translations/en.json new file mode 100644 index 00000000000000..259db84d4d19b0 --- /dev/null +++ b/homeassistant/components/meteo_france/.translations/en.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "city_exists": "City already configured" + }, + "error": { + "city_exists": "City already configured", + "unknown": "Unknown error: please retry later" + }, + "step": { + "user": { + "data": { + "city": "City" + }, + "description": "Enter your city", + "title": "M\u00e9t\u00e9o-France" + } + }, + "title": "M\u00e9t\u00e9o-France" + } +} \ No newline at end of file diff --git a/homeassistant/components/meteo_france/__init__.py b/homeassistant/components/meteo_france/__init__.py index 73b8dbb0e3963d..885a78048df514 100644 --- a/homeassistant/components/meteo_france/__init__.py +++ b/homeassistant/components/meteo_france/__init__.py @@ -6,62 +6,61 @@ from vigilancemeteo import VigilanceMeteoError, VigilanceMeteoFranceProxy import voluptuous as vol +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.discovery import load_platform +from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.util import Throttle -from .const import CONF_CITY, DATA_METEO_FRANCE, DOMAIN, SENSOR_TYPES +from .const import CONF_CITY, DOMAIN, SENSOR_TYPES _LOGGER = logging.getLogger(__name__) SCAN_INTERVAL = datetime.timedelta(minutes=5) -def has_all_unique_cities(value): - """Validate that all cities are unique.""" - cities = [location[CONF_CITY] for location in value] - vol.Schema(vol.Unique())(cities) - return value - +CITY_SCHEMA = vol.Schema( + { + vol.Required(CONF_CITY): cv.string, + vol.Optional(CONF_MONITORED_CONDITIONS): vol.All( + cv.ensure_list, [vol.In(SENSOR_TYPES)] + ), + } +) CONFIG_SCHEMA = vol.Schema( - { - DOMAIN: vol.All( - cv.ensure_list, - [ - vol.Schema( - { - vol.Required(CONF_CITY): cv.string, - vol.Optional(CONF_MONITORED_CONDITIONS): vol.All( - cv.ensure_list, [vol.In(SENSOR_TYPES)] - ), - } - ) - ], - has_all_unique_cities, - ) - }, - extra=vol.ALLOW_EXTRA, + {DOMAIN: vol.Schema(vol.All(cv.ensure_list, [CITY_SCHEMA]))}, extra=vol.ALLOW_EXTRA, ) -def setup(hass, config): - """Set up the Meteo-France component.""" - hass.data[DATA_METEO_FRANCE] = {} +async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: + """Set up Meteo-France from legacy config file.""" + + confs = config.get(DOMAIN) + if confs is None: + return True + + for city_conf in confs: + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=city_conf.copy() + ) + ) + + return True + - # Check if at least weather alert have to be monitored for one location. - need_weather_alert_watcher = False - for location in config[DOMAIN]: - if ( - CONF_MONITORED_CONDITIONS in location - and "weather_alert" in location[CONF_MONITORED_CONDITIONS] - ): - need_weather_alert_watcher = True +async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: + """Set up an Meteo-France account from a config entry.""" + hass.data.setdefault(DOMAIN, {}) - # If weather alert monitoring is expected initiate a client to be used by - # all weather_alert entities. - if need_weather_alert_watcher: + # Check if at least weather alert have to be monitored for one location/entry. + # If weather alert monitoring is expected initiate a client to be used by all weather_alert entities. + if ( + CONF_MONITORED_CONDITIONS in entry.data + and "weather_alert" in entry.data[CONF_MONITORED_CONDITIONS] + and hass.data[DOMAIN].get("weather_alert_client") is not None + ): _LOGGER.debug("Weather Alert monitoring expected. Loading vigilancemeteo") weather_alert_client = VigilanceMeteoFranceProxy() @@ -74,48 +73,40 @@ def setup(hass, config): ) else: weather_alert_client = None - hass.data[DATA_METEO_FRANCE]["weather_alert_client"] = weather_alert_client - - for location in config[DOMAIN]: + hass.data[DOMAIN]["weather_alert_client"] = weather_alert_client - city = location[CONF_CITY] + city = entry.data[CONF_CITY] - try: - client = meteofranceClient(city) - except meteofranceError as exp: - _LOGGER.error( - "Unexpected error when creating the meteofrance proxy: %s", exp - ) - return + try: + client = meteofranceClient(city) + except meteofranceError as exp: + _LOGGER.error("Unexpected error when creating the meteofrance proxy: %s", exp) + return - client.need_rain_forecast = bool( - CONF_MONITORED_CONDITIONS in location - and "next_rain" in location[CONF_MONITORED_CONDITIONS] - ) + client.need_rain_forecast = bool( + CONF_MONITORED_CONDITIONS in entry.data + and "next_rain" in entry.data[CONF_MONITORED_CONDITIONS] + ) - hass.data[DATA_METEO_FRANCE][city] = MeteoFranceUpdater(client) - hass.data[DATA_METEO_FRANCE][city].update() - - if CONF_MONITORED_CONDITIONS in location: - monitored_conditions = location[CONF_MONITORED_CONDITIONS] - _LOGGER.debug("meteo_france sensor platform loaded for %s", city) - load_platform( - hass, - "sensor", - DOMAIN, - {CONF_CITY: city, CONF_MONITORED_CONDITIONS: monitored_conditions}, - config, - ) + hass.data[DOMAIN][city] = MeteoFranceUpdater(client) + hass.data[DOMAIN][city].update() - load_platform(hass, "weather", DOMAIN, {CONF_CITY: city}, config) + if CONF_MONITORED_CONDITIONS in entry.data: + _LOGGER.debug("meteo_france sensor platform loaded for %s", city) + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, "sensor") + ) + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, "weather") + ) return True class MeteoFranceUpdater: """Update data from Meteo-France.""" - def __init__(self, client): + def __init__(self, client: meteofranceClient): """Initialize the data object.""" self._client = client diff --git a/homeassistant/components/meteo_france/config_flow.py b/homeassistant/components/meteo_france/config_flow.py new file mode 100644 index 00000000000000..63fcf3b1c0babd --- /dev/null +++ b/homeassistant/components/meteo_france/config_flow.py @@ -0,0 +1,80 @@ +"""Config flow to configure the Meteo-France integration.""" +import logging + +from meteofrance.client import meteofranceClient, meteofranceError +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_MONITORED_CONDITIONS + +from .const import CONF_CITY, SENSOR_TYPES +from .const import DOMAIN # pylint: disable=unused-import + +_LOGGER = logging.getLogger(__name__) + + +class MeteoFranceFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a Meteo-France config flow.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + + def __init__(self): + """Initialize Meteo-France config flow.""" + + def _configuration_exists(self, city_name: str) -> bool: + """Return True if city_name exists in configuration.""" + for entry in self._async_current_entries(): + if entry.data[CONF_CITY] == city_name: + return True + return False + + async def _show_setup_form(self, user_input=None, errors=None): + """Show the setup form to the user.""" + + if user_input is None: + user_input = {} + + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + {vol.Required(CONF_CITY, default=user_input.get(CONF_CITY, "")): str} + ), + errors=errors or {}, + ) + + async def async_step_user(self, user_input=None): + """Handle a flow initiated by the user.""" + errors = {} + + if user_input is None: + return await self._show_setup_form(user_input, errors) + + city = user_input[CONF_CITY] + monitored_conditions = user_input.get( + CONF_MONITORED_CONDITIONS, list(SENSOR_TYPES) + ) + if self._configuration_exists(city): + errors[CONF_CITY] = "city_exists" + return await self._show_setup_form(user_input, errors) + + try: + await self.hass.async_add_executor_job(meteofranceClient, city) + except meteofranceError as exp: + _LOGGER.error( + "Unexpected error when creating the meteofrance proxy: %s", exp + ) + errors["base"] = "unknown" + return await self._show_setup_form(user_input, errors) + + return self.async_create_entry( + title=city, + data={CONF_CITY: city, CONF_MONITORED_CONDITIONS: monitored_conditions}, + ) + + async def async_step_import(self, user_input): + """Import a config entry.""" + if self._configuration_exists(user_input[CONF_CITY]): + return self.async_abort(reason="city_exists") + + return await self.async_step_user(user_input) diff --git a/homeassistant/components/meteo_france/const.py b/homeassistant/components/meteo_france/const.py index 223aca20bac240..8db32ccd4cd1f5 100644 --- a/homeassistant/components/meteo_france/const.py +++ b/homeassistant/components/meteo_france/const.py @@ -3,7 +3,6 @@ from homeassistant.const import TEMP_CELSIUS DOMAIN = "meteo_france" -DATA_METEO_FRANCE = "data_meteo_france" ATTRIBUTION = "Data provided by Météo-France" CONF_CITY = "city" diff --git a/homeassistant/components/meteo_france/manifest.json b/homeassistant/components/meteo_france/manifest.json index 41a003ea4f758d..0293b71197260b 100644 --- a/homeassistant/components/meteo_france/manifest.json +++ b/homeassistant/components/meteo_france/manifest.json @@ -1,6 +1,7 @@ { "domain": "meteo_france", "name": "Météo-France", + "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/meteo_france", "requirements": ["meteofrance==0.3.7", "vigilancemeteo==3.0.0"], "dependencies": [], diff --git a/homeassistant/components/meteo_france/sensor.py b/homeassistant/components/meteo_france/sensor.py index f0c08ac18220a3..dcd9592ef54572 100644 --- a/homeassistant/components/meteo_france/sensor.py +++ b/homeassistant/components/meteo_france/sensor.py @@ -1,15 +1,18 @@ """Support for Meteo-France raining forecast sensor.""" import logging -from vigilancemeteo import DepartmentWeatherAlert +from meteofrance.client import meteofranceClient +from vigilancemeteo import DepartmentWeatherAlert, VigilanceMeteoFranceProxy +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_ATTRIBUTION, CONF_MONITORED_CONDITIONS from homeassistant.helpers.entity import Entity +from homeassistant.helpers.typing import HomeAssistantType from .const import ( ATTRIBUTION, CONF_CITY, - DATA_METEO_FRANCE, + DOMAIN, SENSOR_TYPE_CLASS, SENSOR_TYPE_ICON, SENSOR_TYPE_NAME, @@ -23,19 +26,23 @@ STATE_ATTR_BULLETIN_TIME = "Bulletin date" -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the Meteo-France sensor.""" - if discovery_info is None: - return +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Old way of setting up the Meteo-France platform.""" + pass - city = discovery_info[CONF_CITY] - monitored_conditions = discovery_info[CONF_MONITORED_CONDITIONS] - client = hass.data[DATA_METEO_FRANCE][city] - weather_alert_client = hass.data[DATA_METEO_FRANCE]["weather_alert_client"] + +async def async_setup_entry( + hass: HomeAssistantType, entry: ConfigEntry, async_add_entities +) -> None: + """Set up the Meteo-France sensor platform.""" + city = entry.data[CONF_CITY] + monitored_conditions = entry.data[CONF_MONITORED_CONDITIONS] + client = hass.data[DOMAIN][city] + weather_alert_client = hass.data[DOMAIN]["weather_alert_client"] alert_watcher = None if "weather_alert" in monitored_conditions: - datas = hass.data[DATA_METEO_FRANCE][city].get_data() + datas = hass.data[DOMAIN][city].get_data() # Check if a department code is available for this city. if "dept" in datas: try: @@ -65,7 +72,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): # Exit and don't create the sensor if no department code available. return - add_entities( + async_add_entities( [ MeteoFranceSensor(variable, client, alert_watcher) for variable in monitored_conditions @@ -77,7 +84,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class MeteoFranceSensor(Entity): """Representation of a Meteo-France sensor.""" - def __init__(self, condition, client, alert_watcher): + def __init__( + self, + condition, + client: meteofranceClient, + alert_watcher: VigilanceMeteoFranceProxy, + ): """Initialize the Meteo-France sensor.""" self._condition = condition self._client = client @@ -90,6 +102,11 @@ def name(self): """Return the name of the sensor.""" return f"{self._data['name']} {SENSOR_TYPES[self._condition][SENSOR_TYPE_NAME]}" + @property + def unique_id(self): + """Return the unique id of the sensor.""" + return self.name + @property def state(self): """Return the state of the sensor.""" diff --git a/homeassistant/components/meteo_france/strings.json b/homeassistant/components/meteo_france/strings.json new file mode 100644 index 00000000000000..b220589f35b53f --- /dev/null +++ b/homeassistant/components/meteo_france/strings.json @@ -0,0 +1,21 @@ +{ + "config": { + "title": "Météo-France", + "step": { + "user": { + "title": "Météo-France", + "description": "Enter your city", + "data": { + "city": "City" + } + } + }, + "error":{ + "city_exists": "City already configured", + "unknown": "Unknown error: please retry later" + }, + "abort":{ + "city_exists": "City already configured" + } + } +} diff --git a/homeassistant/components/meteo_france/weather.py b/homeassistant/components/meteo_france/weather.py index c96080808e97e8..fe0ea4d5f234d3 100644 --- a/homeassistant/components/meteo_france/weather.py +++ b/homeassistant/components/meteo_france/weather.py @@ -2,6 +2,8 @@ from datetime import timedelta import logging +from meteofrance.client import meteofranceClient + from homeassistant.components.weather import ( ATTR_FORECAST_CONDITION, ATTR_FORECAST_TEMP, @@ -9,29 +11,35 @@ ATTR_FORECAST_TIME, WeatherEntity, ) +from homeassistant.config_entries import ConfigEntry from homeassistant.const import TEMP_CELSIUS +from homeassistant.helpers.typing import HomeAssistantType import homeassistant.util.dt as dt_util -from .const import ATTRIBUTION, CONDITION_CLASSES, CONF_CITY, DATA_METEO_FRANCE +from .const import ATTRIBUTION, CONDITION_CLASSES, CONF_CITY, DOMAIN _LOGGER = logging.getLogger(__name__) -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the Meteo-France weather platform.""" - if discovery_info is None: - return +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Old way of setting up the Meteo-France platform.""" + pass + - city = discovery_info[CONF_CITY] - client = hass.data[DATA_METEO_FRANCE][city] +async def async_setup_entry( + hass: HomeAssistantType, entry: ConfigEntry, async_add_entities +) -> None: + """Set up the Meteo-France weather platform.""" + city = entry.data[CONF_CITY] + client = hass.data[DOMAIN][city] - add_entities([MeteoFranceWeather(client)], True) + async_add_entities([MeteoFranceWeather(client)], True) class MeteoFranceWeather(WeatherEntity): """Representation of a weather condition.""" - def __init__(self, client): + def __init__(self, client: meteofranceClient): """Initialise the platform with a data instance and station name.""" self._client = client self._data = {} @@ -46,6 +54,11 @@ def name(self): """Return the name of the sensor.""" return self._data["name"] + @property + def unique_id(self): + """Return the unique id of the sensor.""" + return self.name + @property def condition(self): """Return the current condition.""" diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index cf77dae7fb2267..ea8a0a4e82d4cd 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -54,6 +54,7 @@ "luftdaten", "mailgun", "met", + "meteo_france", "mikrotik", "mobile_app", "mqtt", diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4f0e6a21a2ae7a..355567cdf57f57 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -298,6 +298,9 @@ luftdaten==0.6.3 # homeassistant.components.mythicbeastsdns mbddns==0.1.2 +# homeassistant.components.meteo_france +meteofrance==0.3.7 + # homeassistant.components.mfi mficlient==0.3.0 @@ -663,6 +666,9 @@ url-normalize==1.4.1 # homeassistant.components.uvc uvcclient==0.11.0 +# homeassistant.components.meteo_france +vigilancemeteo==3.0.0 + # homeassistant.components.verisure vsure==1.5.4 diff --git a/tests/components/meteo_france/__init__.py b/tests/components/meteo_france/__init__.py new file mode 100644 index 00000000000000..c4d4c446574381 --- /dev/null +++ b/tests/components/meteo_france/__init__.py @@ -0,0 +1 @@ +"""Tests for the Meteo-France component.""" diff --git a/tests/components/meteo_france/test_config_flow.py b/tests/components/meteo_france/test_config_flow.py new file mode 100644 index 00000000000000..66d2e943c20806 --- /dev/null +++ b/tests/components/meteo_france/test_config_flow.py @@ -0,0 +1,96 @@ +"""Tests for the Meteo-France config flow.""" +from unittest.mock import patch + +from meteofrance.client import meteofranceError +import pytest + +from homeassistant import data_entry_flow +from homeassistant.components.meteo_france import config_flow +from homeassistant.components.meteo_france.const import CONF_CITY, DOMAIN, SENSOR_TYPES +from homeassistant.const import CONF_MONITORED_CONDITIONS + +from tests.common import MockConfigEntry + +CITY = "74220" +MONITORED_CONDITIONS = ["temperature", "weather"] +DEFAULT_MONITORED_CONDITIONS = list(SENSOR_TYPES) + + +@pytest.fixture(name="client") +def mock_controller_client(): + """Mock a successful client.""" + with patch("meteofrance.client.meteofranceClient", update=False): + yield + + +def init_config_flow(hass): + """Init a configuration flow.""" + flow = config_flow.MeteoFranceFlowHandler() + flow.hass = hass + return flow + + +async def test_user(hass, client): + """Test user config.""" + flow = init_config_flow(hass) + + result = await flow.async_step_user() + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + + # test with all provided + result = await flow.async_step_user({CONF_CITY: CITY}) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == CITY + assert result["data"][CONF_CITY] == CITY + assert result["data"][CONF_MONITORED_CONDITIONS] == DEFAULT_MONITORED_CONDITIONS + + +async def test_import(hass, client): + """Test import step.""" + flow = init_config_flow(hass) + + # import with city + result = await flow.async_step_import({CONF_CITY: CITY}) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == CITY + assert result["data"][CONF_CITY] == CITY + assert result["data"][CONF_MONITORED_CONDITIONS] == DEFAULT_MONITORED_CONDITIONS + + # import with all + result = await flow.async_step_import( + {CONF_CITY: CITY, CONF_MONITORED_CONDITIONS: MONITORED_CONDITIONS} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == CITY + assert result["data"][CONF_CITY] == CITY + assert result["data"][CONF_MONITORED_CONDITIONS] == MONITORED_CONDITIONS + + +async def test_abort_if_already_setup(hass, client): + """Test we abort if already setup.""" + flow = init_config_flow(hass) + MockConfigEntry(domain=DOMAIN, data={CONF_CITY: CITY}).add_to_hass(hass) + + # Should fail, same CITY (import) + result = await flow.async_step_import({CONF_CITY: CITY}) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "city_exists" + + # Should fail, same CITY (flow) + result = await flow.async_step_user({CONF_CITY: CITY}) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {CONF_CITY: "city_exists"} + + +async def test_on_client_failed(hass): + """Test when we have errors during client fetch.""" + flow = init_config_flow(hass) + + with patch( + "meteofrance.client.meteofranceClient._init_codes", + side_effect=meteofranceError(), + ): + result = await flow.async_step_user({CONF_CITY: CITY}) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {"base": "unknown"} From 309ede5c80b6983966f635a393d65830c5585129 Mon Sep 17 00:00:00 2001 From: Quentin POLLET Date: Sat, 11 Jan 2020 19:18:28 +0100 Subject: [PATCH 02/17] Review 1 --- homeassistant/components/meteo_france/__init__.py | 6 +++--- homeassistant/components/meteo_france/config_flow.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/meteo_france/__init__.py b/homeassistant/components/meteo_france/__init__.py index 885a78048df514..5358c284f7b92a 100644 --- a/homeassistant/components/meteo_france/__init__.py +++ b/homeassistant/components/meteo_france/__init__.py @@ -36,11 +36,11 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: """Set up Meteo-France from legacy config file.""" - confs = config.get(DOMAIN) - if confs is None: + conf = config.get(DOMAIN) + if conf is None: return True - for city_conf in confs: + for city_conf in conf: hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data=city_conf.copy() diff --git a/homeassistant/components/meteo_france/config_flow.py b/homeassistant/components/meteo_france/config_flow.py index 63fcf3b1c0babd..f1776752ebd07c 100644 --- a/homeassistant/components/meteo_france/config_flow.py +++ b/homeassistant/components/meteo_france/config_flow.py @@ -48,7 +48,7 @@ async def async_step_user(self, user_input=None): errors = {} if user_input is None: - return await self._show_setup_form(user_input, errors) + return await self._show_setup_form(user_input, None) city = user_input[CONF_CITY] monitored_conditions = user_input.get( From dde52eb3af231bc71b974078dd630897553b68ea Mon Sep 17 00:00:00 2001 From: Quentin POLLET Date: Mon, 20 Jan 2020 00:08:47 +0100 Subject: [PATCH 03/17] Use config_entry.unique_id --- .../meteo_france/.translations/en.json | 3 +- .../components/meteo_france/config_flow.py | 17 ++--- .../components/meteo_france/strings.json | 3 +- .../meteo_france/test_config_flow.py | 66 ++++++++++--------- 4 files changed, 42 insertions(+), 47 deletions(-) diff --git a/homeassistant/components/meteo_france/.translations/en.json b/homeassistant/components/meteo_france/.translations/en.json index 259db84d4d19b0..0b54a7a8d229d1 100644 --- a/homeassistant/components/meteo_france/.translations/en.json +++ b/homeassistant/components/meteo_france/.translations/en.json @@ -1,10 +1,9 @@ { "config": { "abort": { - "city_exists": "City already configured" + "already_configured": "City already configured" }, "error": { - "city_exists": "City already configured", "unknown": "Unknown error: please retry later" }, "step": { diff --git a/homeassistant/components/meteo_france/config_flow.py b/homeassistant/components/meteo_france/config_flow.py index f1776752ebd07c..8ebfa5dc32f6c2 100644 --- a/homeassistant/components/meteo_france/config_flow.py +++ b/homeassistant/components/meteo_france/config_flow.py @@ -22,13 +22,6 @@ class MeteoFranceFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self): """Initialize Meteo-France config flow.""" - def _configuration_exists(self, city_name: str) -> bool: - """Return True if city_name exists in configuration.""" - for entry in self._async_current_entries(): - if entry.data[CONF_CITY] == city_name: - return True - return False - async def _show_setup_form(self, user_input=None, errors=None): """Show the setup form to the user.""" @@ -54,9 +47,10 @@ async def async_step_user(self, user_input=None): monitored_conditions = user_input.get( CONF_MONITORED_CONDITIONS, list(SENSOR_TYPES) ) - if self._configuration_exists(city): - errors[CONF_CITY] = "city_exists" - return await self._show_setup_form(user_input, errors) + + # Check if already configured + await self.async_set_unique_id(city) + self._abort_if_unique_id_configured() try: await self.hass.async_add_executor_job(meteofranceClient, city) @@ -74,7 +68,4 @@ async def async_step_user(self, user_input=None): async def async_step_import(self, user_input): """Import a config entry.""" - if self._configuration_exists(user_input[CONF_CITY]): - return self.async_abort(reason="city_exists") - return await self.async_step_user(user_input) diff --git a/homeassistant/components/meteo_france/strings.json b/homeassistant/components/meteo_france/strings.json index b220589f35b53f..0d9991b6ab2a9f 100644 --- a/homeassistant/components/meteo_france/strings.json +++ b/homeassistant/components/meteo_france/strings.json @@ -11,11 +11,10 @@ } }, "error":{ - "city_exists": "City already configured", "unknown": "Unknown error: please retry later" }, "abort":{ - "city_exists": "City already configured" + "already_configured": "City already configured" } } } diff --git a/tests/components/meteo_france/test_config_flow.py b/tests/components/meteo_france/test_config_flow.py index 66d2e943c20806..823d2b584f353f 100644 --- a/tests/components/meteo_france/test_config_flow.py +++ b/tests/components/meteo_france/test_config_flow.py @@ -5,13 +5,14 @@ import pytest from homeassistant import data_entry_flow -from homeassistant.components.meteo_france import config_flow from homeassistant.components.meteo_france.const import CONF_CITY, DOMAIN, SENSOR_TYPES +from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER from homeassistant.const import CONF_MONITORED_CONDITIONS from tests.common import MockConfigEntry CITY = "74220" +CITY_2 = "69004" MONITORED_CONDITIONS = ["temperature", "weather"] DEFAULT_MONITORED_CONDITIONS = list(SENSOR_TYPES) @@ -23,24 +24,20 @@ def mock_controller_client(): yield -def init_config_flow(hass): - """Init a configuration flow.""" - flow = config_flow.MeteoFranceFlowHandler() - flow.hass = hass - return flow - - async def test_user(hass, client): """Test user config.""" - flow = init_config_flow(hass) - - result = await flow.async_step_user() + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=None, + ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "user" # test with all provided - result = await flow.async_step_user({CONF_CITY: CITY}) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data={CONF_CITY: CITY}, + ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["result"].unique_id == CITY assert result["title"] == CITY assert result["data"][CONF_CITY] == CITY assert result["data"][CONF_MONITORED_CONDITIONS] == DEFAULT_MONITORED_CONDITIONS @@ -48,49 +45,58 @@ async def test_user(hass, client): async def test_import(hass, client): """Test import step.""" - flow = init_config_flow(hass) - # import with city - result = await flow.async_step_import({CONF_CITY: CITY}) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data={CONF_CITY: CITY}, + ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["result"].unique_id == CITY assert result["title"] == CITY assert result["data"][CONF_CITY] == CITY assert result["data"][CONF_MONITORED_CONDITIONS] == DEFAULT_MONITORED_CONDITIONS # import with all - result = await flow.async_step_import( - {CONF_CITY: CITY, CONF_MONITORED_CONDITIONS: MONITORED_CONDITIONS} + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data={CONF_CITY: CITY_2, CONF_MONITORED_CONDITIONS: MONITORED_CONDITIONS}, ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["title"] == CITY - assert result["data"][CONF_CITY] == CITY + assert result["result"].unique_id == CITY_2 + assert result["title"] == CITY_2 + assert result["data"][CONF_CITY] == CITY_2 assert result["data"][CONF_MONITORED_CONDITIONS] == MONITORED_CONDITIONS async def test_abort_if_already_setup(hass, client): """Test we abort if already setup.""" - flow = init_config_flow(hass) - MockConfigEntry(domain=DOMAIN, data={CONF_CITY: CITY}).add_to_hass(hass) + MockConfigEntry(domain=DOMAIN, data={CONF_CITY: CITY}, unique_id=CITY).add_to_hass( + hass + ) # Should fail, same CITY (import) - result = await flow.async_step_import({CONF_CITY: CITY}) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data={CONF_CITY: CITY}, + ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "city_exists" + assert result["reason"] == "already_configured" # Should fail, same CITY (flow) - result = await flow.async_step_user({CONF_CITY: CITY}) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {CONF_CITY: "city_exists"} + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data={CONF_CITY: CITY}, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" async def test_on_client_failed(hass): """Test when we have errors during client fetch.""" - flow = init_config_flow(hass) - with patch( - "meteofrance.client.meteofranceClient._init_codes", + "homeassistant.components.meteo_france.config_flow.meteofranceClient", side_effect=meteofranceError(), ): - result = await flow.async_step_user({CONF_CITY: CITY}) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data={CONF_CITY: CITY}, + ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["errors"] == {"base": "unknown"} From 4133f06ccfc097a487579747caceb2e97c82549b Mon Sep 17 00:00:00 2001 From: Quentin POLLET Date: Mon, 27 Jan 2020 14:02:13 +0100 Subject: [PATCH 04/17] Fix config_flow _show_setup_form + init --- homeassistant/components/meteo_france/config_flow.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/meteo_france/config_flow.py b/homeassistant/components/meteo_france/config_flow.py index 8ebfa5dc32f6c2..08399a08eaa682 100644 --- a/homeassistant/components/meteo_france/config_flow.py +++ b/homeassistant/components/meteo_france/config_flow.py @@ -19,10 +19,7 @@ class MeteoFranceFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL - def __init__(self): - """Initialize Meteo-France config flow.""" - - async def _show_setup_form(self, user_input=None, errors=None): + def _show_setup_form(self, user_input=None, errors=None): """Show the setup form to the user.""" if user_input is None: @@ -41,7 +38,7 @@ async def async_step_user(self, user_input=None): errors = {} if user_input is None: - return await self._show_setup_form(user_input, None) + return self._show_setup_form(user_input, None) city = user_input[CONF_CITY] monitored_conditions = user_input.get( @@ -59,7 +56,7 @@ async def async_step_user(self, user_input=None): "Unexpected error when creating the meteofrance proxy: %s", exp ) errors["base"] = "unknown" - return await self._show_setup_form(user_input, errors) + return self._show_setup_form(user_input, errors) return self.async_create_entry( title=city, From a321cad37fec533490ca4d7fb4fe3653ab3789a4 Mon Sep 17 00:00:00 2001 From: Quentin POLLET Date: Fri, 31 Jan 2020 13:05:20 +0100 Subject: [PATCH 05/17] Remove empty *_setup_platform() --- homeassistant/components/meteo_france/sensor.py | 5 ----- homeassistant/components/meteo_france/weather.py | 5 ----- 2 files changed, 10 deletions(-) diff --git a/homeassistant/components/meteo_france/sensor.py b/homeassistant/components/meteo_france/sensor.py index dcd9592ef54572..ec040557a5c45b 100644 --- a/homeassistant/components/meteo_france/sensor.py +++ b/homeassistant/components/meteo_france/sensor.py @@ -26,11 +26,6 @@ STATE_ATTR_BULLETIN_TIME = "Bulletin date" -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Old way of setting up the Meteo-France platform.""" - pass - - async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, async_add_entities ) -> None: diff --git a/homeassistant/components/meteo_france/weather.py b/homeassistant/components/meteo_france/weather.py index fe0ea4d5f234d3..1bdea073aae0f0 100644 --- a/homeassistant/components/meteo_france/weather.py +++ b/homeassistant/components/meteo_france/weather.py @@ -21,11 +21,6 @@ _LOGGER = logging.getLogger(__name__) -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Old way of setting up the Meteo-France platform.""" - pass - - async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, async_add_entities ) -> None: From 227a5e4878644ff33269084d1b2482cbeac8df04 Mon Sep 17 00:00:00 2001 From: Quentin POLLET Date: Fri, 31 Jan 2020 13:26:36 +0100 Subject: [PATCH 06/17] Avoid HomeAssistantError: Entity id already exists: sensor.[city_name]_[sensor_type]. Platform meteo_france does not generate unique IDs - when multiple district in one city --- .../meteo_france/.translations/en.json | 3 +- .../components/meteo_france/config_flow.py | 21 ++-- .../components/meteo_france/strings.json | 3 +- .../meteo_france/test_config_flow.py | 107 +++++++++++++----- 4 files changed, 93 insertions(+), 41 deletions(-) diff --git a/homeassistant/components/meteo_france/.translations/en.json b/homeassistant/components/meteo_france/.translations/en.json index 0b54a7a8d229d1..1c5fed38a43223 100644 --- a/homeassistant/components/meteo_france/.translations/en.json +++ b/homeassistant/components/meteo_france/.translations/en.json @@ -9,9 +9,8 @@ "step": { "user": { "data": { - "city": "City" + "city": "City or postal code" }, - "description": "Enter your city", "title": "M\u00e9t\u00e9o-France" } }, diff --git a/homeassistant/components/meteo_france/config_flow.py b/homeassistant/components/meteo_france/config_flow.py index 08399a08eaa682..318bed053933d3 100644 --- a/homeassistant/components/meteo_france/config_flow.py +++ b/homeassistant/components/meteo_france/config_flow.py @@ -40,17 +40,15 @@ async def async_step_user(self, user_input=None): if user_input is None: return self._show_setup_form(user_input, None) - city = user_input[CONF_CITY] + city = user_input[CONF_CITY] # Might be a city name or a postal code monitored_conditions = user_input.get( CONF_MONITORED_CONDITIONS, list(SENSOR_TYPES) ) - - # Check if already configured - await self.async_set_unique_id(city) - self._abort_if_unique_id_configured() + city_name = None try: - await self.hass.async_add_executor_job(meteofranceClient, city) + client = await self.hass.async_add_executor_job(meteofranceClient, city) + city_name = client.get_data()["name"] except meteofranceError as exp: _LOGGER.error( "Unexpected error when creating the meteofrance proxy: %s", exp @@ -58,9 +56,16 @@ async def async_step_user(self, user_input=None): errors["base"] = "unknown" return self._show_setup_form(user_input, errors) + # Check if already configured + await self.async_set_unique_id(city_name) + self._abort_if_unique_id_configured() + return self.async_create_entry( - title=city, - data={CONF_CITY: city, CONF_MONITORED_CONDITIONS: monitored_conditions}, + title=city_name, + data={ + CONF_CITY: city_name, + CONF_MONITORED_CONDITIONS: monitored_conditions, + }, ) async def async_step_import(self, user_input): diff --git a/homeassistant/components/meteo_france/strings.json b/homeassistant/components/meteo_france/strings.json index 0d9991b6ab2a9f..a6721b1e7e1a85 100644 --- a/homeassistant/components/meteo_france/strings.json +++ b/homeassistant/components/meteo_france/strings.json @@ -4,9 +4,8 @@ "step": { "user": { "title": "Météo-France", - "description": "Enter your city", "data": { - "city": "City" + "city": "City or postal code" } } }, diff --git a/tests/components/meteo_france/test_config_flow.py b/tests/components/meteo_france/test_config_flow.py index 823d2b584f353f..09ce8de81298a6 100644 --- a/tests/components/meteo_france/test_config_flow.py +++ b/tests/components/meteo_france/test_config_flow.py @@ -11,20 +11,38 @@ from tests.common import MockConfigEntry -CITY = "74220" -CITY_2 = "69004" +CITY_1_POSTAL = "74220" +CITY_1_NAME = "La Clusaz" +CITY_2_POSTAL_DISTRICT_1 = "69001" +CITY_2_POSTAL_DISTRICT_4 = "69004" +CITY_2_NAME = "Lyon" MONITORED_CONDITIONS = ["temperature", "weather"] DEFAULT_MONITORED_CONDITIONS = list(SENSOR_TYPES) -@pytest.fixture(name="client") -def mock_controller_client(): +@pytest.fixture(name="client_1") +def mock_controller_client_1(): """Mock a successful client.""" - with patch("meteofrance.client.meteofranceClient", update=False): - yield + with patch( + "homeassistant.components.meteo_france.config_flow.meteofranceClient", + update=False, + ) as service_mock: + service_mock.return_value.get_data.return_value = {"name": CITY_1_NAME} + yield service_mock + + +@pytest.fixture(name="client_2") +def mock_controller_client_2(): + """Mock a successful client.""" + with patch( + "homeassistant.components.meteo_france.config_flow.meteofranceClient", + update=False, + ) as service_mock: + service_mock.return_value.get_data.return_value = {"name": CITY_2_NAME} + yield service_mock -async def test_user(hass, client): +async def test_user(hass, client_1): """Test user config.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, data=None, @@ -34,69 +52,100 @@ async def test_user(hass, client): # test with all provided result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data={CONF_CITY: CITY}, + DOMAIN, context={"source": SOURCE_USER}, data={CONF_CITY: CITY_1_POSTAL}, ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["result"].unique_id == CITY - assert result["title"] == CITY - assert result["data"][CONF_CITY] == CITY + assert result["result"].unique_id == CITY_1_NAME + assert result["title"] == CITY_1_NAME + assert result["data"][CONF_CITY] == CITY_1_NAME assert result["data"][CONF_MONITORED_CONDITIONS] == DEFAULT_MONITORED_CONDITIONS -async def test_import(hass, client): +async def test_import(hass, client_1): """Test import step.""" # import with city result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_IMPORT}, data={CONF_CITY: CITY}, + DOMAIN, context={"source": SOURCE_IMPORT}, data={CONF_CITY: CITY_1_POSTAL}, ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["result"].unique_id == CITY - assert result["title"] == CITY - assert result["data"][CONF_CITY] == CITY + assert result["result"].unique_id == CITY_1_NAME + assert result["title"] == CITY_1_NAME + assert result["data"][CONF_CITY] == CITY_1_NAME assert result["data"][CONF_MONITORED_CONDITIONS] == DEFAULT_MONITORED_CONDITIONS + +async def test_import_all(hass, client_2): + """Test import step.""" # import with all result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, - data={CONF_CITY: CITY_2, CONF_MONITORED_CONDITIONS: MONITORED_CONDITIONS}, + data={ + CONF_CITY: CITY_2_POSTAL_DISTRICT_4, + CONF_MONITORED_CONDITIONS: MONITORED_CONDITIONS, + }, ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["result"].unique_id == CITY_2 - assert result["title"] == CITY_2 - assert result["data"][CONF_CITY] == CITY_2 + assert result["result"].unique_id == CITY_2_NAME + assert result["title"] == CITY_2_NAME + assert result["data"][CONF_CITY] == CITY_2_NAME assert result["data"][CONF_MONITORED_CONDITIONS] == MONITORED_CONDITIONS -async def test_abort_if_already_setup(hass, client): +async def test_abort_if_already_setup(hass, client_1): """Test we abort if already setup.""" - MockConfigEntry(domain=DOMAIN, data={CONF_CITY: CITY}, unique_id=CITY).add_to_hass( - hass + MockConfigEntry( + domain=DOMAIN, data={CONF_CITY: CITY_1_POSTAL}, unique_id=CITY_1_NAME + ).add_to_hass(hass) + + # Should fail, same CITY same postal code (import) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data={CONF_CITY: CITY_1_POSTAL}, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + + # Should fail, same CITY same postal code (flow) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data={CONF_CITY: CITY_1_POSTAL}, ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" - # Should fail, same CITY (import) + +async def test_abort_if_already_setup_district(hass, client_2): + """Test we abort if already setup.""" + MockConfigEntry( + domain=DOMAIN, data={CONF_CITY: CITY_2_POSTAL_DISTRICT_1}, unique_id=CITY_2_NAME + ).add_to_hass(hass) + + # Should fail, same CITY different postal code (import) result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_IMPORT}, data={CONF_CITY: CITY}, + DOMAIN, + context={"source": SOURCE_IMPORT}, + data={CONF_CITY: CITY_2_POSTAL_DISTRICT_4}, ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "already_configured" - # Should fail, same CITY (flow) + # Should fail, same CITY different postal code (flow) result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data={CONF_CITY: CITY}, + DOMAIN, + context={"source": SOURCE_USER}, + data={CONF_CITY: CITY_2_POSTAL_DISTRICT_4}, ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "already_configured" -async def test_on_client_failed(hass): +async def test_client_failed(hass): """Test when we have errors during client fetch.""" with patch( "homeassistant.components.meteo_france.config_flow.meteofranceClient", side_effect=meteofranceError(), ): result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data={CONF_CITY: CITY}, + DOMAIN, context={"source": SOURCE_USER}, data={CONF_CITY: CITY_1_POSTAL}, ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["errors"] == {"base": "unknown"} From 59e10c559827ee642b66b863baf6235428894eaa Mon Sep 17 00:00:00 2001 From: Quentin POLLET Date: Mon, 3 Feb 2020 15:20:44 +0100 Subject: [PATCH 07/17] Review + abort when API error --- homeassistant/components/meteo_france/.translations/en.json | 4 +--- homeassistant/components/meteo_france/__init__.py | 2 +- homeassistant/components/meteo_france/config_flow.py | 6 +++--- homeassistant/components/meteo_france/strings.json | 6 ++---- tests/components/meteo_france/test_config_flow.py | 4 ++-- 5 files changed, 9 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/meteo_france/.translations/en.json b/homeassistant/components/meteo_france/.translations/en.json index 1c5fed38a43223..fdcc7de83ed96d 100644 --- a/homeassistant/components/meteo_france/.translations/en.json +++ b/homeassistant/components/meteo_france/.translations/en.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "City already configured" - }, - "error": { + "already_configured": "City already configured", "unknown": "Unknown error: please retry later" }, "step": { diff --git a/homeassistant/components/meteo_france/__init__.py b/homeassistant/components/meteo_france/__init__.py index 5358c284f7b92a..bb435040dcb883 100644 --- a/homeassistant/components/meteo_france/__init__.py +++ b/homeassistant/components/meteo_france/__init__.py @@ -81,7 +81,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool client = meteofranceClient(city) except meteofranceError as exp: _LOGGER.error("Unexpected error when creating the meteofrance proxy: %s", exp) - return + return False client.need_rain_forecast = bool( CONF_MONITORED_CONDITIONS in entry.data diff --git a/homeassistant/components/meteo_france/config_flow.py b/homeassistant/components/meteo_france/config_flow.py index 318bed053933d3..485abc851d3471 100644 --- a/homeassistant/components/meteo_france/config_flow.py +++ b/homeassistant/components/meteo_france/config_flow.py @@ -6,6 +6,7 @@ from homeassistant import config_entries from homeassistant.const import CONF_MONITORED_CONDITIONS +from homeassistant.data_entry_flow import AbortFlow from .const import CONF_CITY, SENSOR_TYPES from .const import DOMAIN # pylint: disable=unused-import @@ -38,7 +39,7 @@ async def async_step_user(self, user_input=None): errors = {} if user_input is None: - return self._show_setup_form(user_input, None) + return self._show_setup_form(user_input, errors) city = user_input[CONF_CITY] # Might be a city name or a postal code monitored_conditions = user_input.get( @@ -53,8 +54,7 @@ async def async_step_user(self, user_input=None): _LOGGER.error( "Unexpected error when creating the meteofrance proxy: %s", exp ) - errors["base"] = "unknown" - return self._show_setup_form(user_input, errors) + raise AbortFlow("unknown") # Check if already configured await self.async_set_unique_id(city_name) diff --git a/homeassistant/components/meteo_france/strings.json b/homeassistant/components/meteo_france/strings.json index a6721b1e7e1a85..ecd7953aa62ce1 100644 --- a/homeassistant/components/meteo_france/strings.json +++ b/homeassistant/components/meteo_france/strings.json @@ -9,11 +9,9 @@ } } }, - "error":{ - "unknown": "Unknown error: please retry later" - }, "abort":{ - "already_configured": "City already configured" + "already_configured": "City already configured", + "unknown": "Unknown error: please retry later" } } } diff --git a/tests/components/meteo_france/test_config_flow.py b/tests/components/meteo_france/test_config_flow.py index 09ce8de81298a6..f6872d5b90c0ac 100644 --- a/tests/components/meteo_france/test_config_flow.py +++ b/tests/components/meteo_france/test_config_flow.py @@ -147,5 +147,5 @@ async def test_client_failed(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, data={CONF_CITY: CITY_1_POSTAL}, ) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {"base": "unknown"} + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "unknown" From bd7ffb4e0bda0a80589768ae2287261dbb7f8d2b Mon Sep 17 00:00:00 2001 From: Quentin POLLET Date: Mon, 3 Feb 2020 15:39:26 +0100 Subject: [PATCH 08/17] Fix I/O --- homeassistant/components/meteo_france/__init__.py | 6 ++++-- homeassistant/components/meteo_france/sensor.py | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/meteo_france/__init__.py b/homeassistant/components/meteo_france/__init__.py index bb435040dcb883..1e6c500ec10810 100644 --- a/homeassistant/components/meteo_france/__init__.py +++ b/homeassistant/components/meteo_france/__init__.py @@ -63,7 +63,9 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool ): _LOGGER.debug("Weather Alert monitoring expected. Loading vigilancemeteo") - weather_alert_client = VigilanceMeteoFranceProxy() + weather_alert_client = await hass.async_add_executor_job( + VigilanceMeteoFranceProxy + ) try: weather_alert_client.update_data() except VigilanceMeteoError as exp: @@ -78,7 +80,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool city = entry.data[CONF_CITY] try: - client = meteofranceClient(city) + client = await hass.async_add_executor_job(meteofranceClient, city) except meteofranceError as exp: _LOGGER.error("Unexpected error when creating the meteofrance proxy: %s", exp) return False diff --git a/homeassistant/components/meteo_france/sensor.py b/homeassistant/components/meteo_france/sensor.py index ec040557a5c45b..bb0150729c2b2d 100644 --- a/homeassistant/components/meteo_france/sensor.py +++ b/homeassistant/components/meteo_france/sensor.py @@ -37,13 +37,13 @@ async def async_setup_entry( alert_watcher = None if "weather_alert" in monitored_conditions: - datas = hass.data[DOMAIN][city].get_data() + datas = client.get_data() # Check if a department code is available for this city. if "dept" in datas: try: # If yes create the watcher DepartmentWeatherAlert object. - alert_watcher = DepartmentWeatherAlert( - datas["dept"], weather_alert_client + alert_watcher = await hass.async_add_executor_job( + DepartmentWeatherAlert, datas["dept"], weather_alert_client ) except ValueError as exp: _LOGGER.error( From e74ad0cf4e1c61222076164079405451e4473043 Mon Sep 17 00:00:00 2001 From: Quentin POLLET Date: Mon, 3 Feb 2020 16:07:29 +0100 Subject: [PATCH 09/17] Remove monitored_conditions --- .../components/meteo_france/__init__.py | 57 ++++--------- .../components/meteo_france/config_flow.py | 14 +--- .../components/meteo_france/const.py | 1 + .../components/meteo_france/sensor.py | 82 +++++++++---------- 4 files changed, 57 insertions(+), 97 deletions(-) diff --git a/homeassistant/components/meteo_france/__init__.py b/homeassistant/components/meteo_france/__init__.py index 1e6c500ec10810..ff263a621d339f 100644 --- a/homeassistant/components/meteo_france/__init__.py +++ b/homeassistant/components/meteo_france/__init__.py @@ -7,26 +7,18 @@ import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.const import CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.util import Throttle -from .const import CONF_CITY, DOMAIN, SENSOR_TYPES +from .const import CONF_CITY, DOMAIN, PLATFORMS _LOGGER = logging.getLogger(__name__) SCAN_INTERVAL = datetime.timedelta(minutes=5) -CITY_SCHEMA = vol.Schema( - { - vol.Required(CONF_CITY): cv.string, - vol.Optional(CONF_MONITORED_CONDITIONS): vol.All( - cv.ensure_list, [vol.In(SENSOR_TYPES)] - ), - } -) +CITY_SCHEMA = vol.Schema({vol.Required(CONF_CITY): cv.string}) CONFIG_SCHEMA = vol.Schema( {DOMAIN: vol.Schema(vol.All(cv.ensure_list, [CITY_SCHEMA]))}, extra=vol.ALLOW_EXTRA, @@ -54,54 +46,33 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool """Set up an Meteo-France account from a config entry.""" hass.data.setdefault(DOMAIN, {}) - # Check if at least weather alert have to be monitored for one location/entry. - # If weather alert monitoring is expected initiate a client to be used by all weather_alert entities. - if ( - CONF_MONITORED_CONDITIONS in entry.data - and "weather_alert" in entry.data[CONF_MONITORED_CONDITIONS] - and hass.data[DOMAIN].get("weather_alert_client") is not None - ): - _LOGGER.debug("Weather Alert monitoring expected. Loading vigilancemeteo") - - weather_alert_client = await hass.async_add_executor_job( - VigilanceMeteoFranceProxy + # Weather alert + weather_alert_client = await hass.async_add_executor_job(VigilanceMeteoFranceProxy) + try: + weather_alert_client.update_data() + except VigilanceMeteoError as exp: + _LOGGER.error( + "Unexpected error when creating the vigilance_meteoFrance proxy: %s ", exp ) - try: - weather_alert_client.update_data() - except VigilanceMeteoError as exp: - _LOGGER.error( - "Unexpected error when creating the vigilance_meteoFrance proxy: %s ", - exp, - ) - else: - weather_alert_client = None + return False hass.data[DOMAIN]["weather_alert_client"] = weather_alert_client + # Weather city = entry.data[CONF_CITY] - try: client = await hass.async_add_executor_job(meteofranceClient, city) except meteofranceError as exp: _LOGGER.error("Unexpected error when creating the meteofrance proxy: %s", exp) return False - client.need_rain_forecast = bool( - CONF_MONITORED_CONDITIONS in entry.data - and "next_rain" in entry.data[CONF_MONITORED_CONDITIONS] - ) - hass.data[DOMAIN][city] = MeteoFranceUpdater(client) hass.data[DOMAIN][city].update() - if CONF_MONITORED_CONDITIONS in entry.data: - _LOGGER.debug("meteo_france sensor platform loaded for %s", city) + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, "sensor") + hass.config_entries.async_forward_entry_setup(entry, platform) ) - - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, "weather") - ) + _LOGGER.debug("meteo_france sensor platform loaded for %s", city) return True diff --git a/homeassistant/components/meteo_france/config_flow.py b/homeassistant/components/meteo_france/config_flow.py index 485abc851d3471..a884f03a080f5a 100644 --- a/homeassistant/components/meteo_france/config_flow.py +++ b/homeassistant/components/meteo_france/config_flow.py @@ -5,10 +5,9 @@ import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import CONF_MONITORED_CONDITIONS from homeassistant.data_entry_flow import AbortFlow -from .const import CONF_CITY, SENSOR_TYPES +from .const import CONF_CITY from .const import DOMAIN # pylint: disable=unused-import _LOGGER = logging.getLogger(__name__) @@ -42,9 +41,6 @@ async def async_step_user(self, user_input=None): return self._show_setup_form(user_input, errors) city = user_input[CONF_CITY] # Might be a city name or a postal code - monitored_conditions = user_input.get( - CONF_MONITORED_CONDITIONS, list(SENSOR_TYPES) - ) city_name = None try: @@ -60,13 +56,7 @@ async def async_step_user(self, user_input=None): await self.async_set_unique_id(city_name) self._abort_if_unique_id_configured() - return self.async_create_entry( - title=city_name, - data={ - CONF_CITY: city_name, - CONF_MONITORED_CONDITIONS: monitored_conditions, - }, - ) + return self.async_create_entry(title=city_name, data={CONF_CITY: city_name}) async def async_step_import(self, user_input): """Import a config entry.""" diff --git a/homeassistant/components/meteo_france/const.py b/homeassistant/components/meteo_france/const.py index 8db32ccd4cd1f5..fae2000b19a10a 100644 --- a/homeassistant/components/meteo_france/const.py +++ b/homeassistant/components/meteo_france/const.py @@ -3,6 +3,7 @@ from homeassistant.const import TEMP_CELSIUS DOMAIN = "meteo_france" +PLATFORMS = ["sensor", "weather"] ATTRIBUTION = "Data provided by Météo-France" CONF_CITY = "city" diff --git a/homeassistant/components/meteo_france/sensor.py b/homeassistant/components/meteo_france/sensor.py index bb0150729c2b2d..c9d37324a4c538 100644 --- a/homeassistant/components/meteo_france/sensor.py +++ b/homeassistant/components/meteo_france/sensor.py @@ -5,7 +5,7 @@ from vigilancemeteo import DepartmentWeatherAlert, VigilanceMeteoFranceProxy from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ATTR_ATTRIBUTION, CONF_MONITORED_CONDITIONS +from homeassistant.const import ATTR_ATTRIBUTION from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import HomeAssistantType @@ -31,46 +31,44 @@ async def async_setup_entry( ) -> None: """Set up the Meteo-France sensor platform.""" city = entry.data[CONF_CITY] - monitored_conditions = entry.data[CONF_MONITORED_CONDITIONS] client = hass.data[DOMAIN][city] weather_alert_client = hass.data[DOMAIN]["weather_alert_client"] alert_watcher = None - if "weather_alert" in monitored_conditions: - datas = client.get_data() - # Check if a department code is available for this city. - if "dept" in datas: - try: - # If yes create the watcher DepartmentWeatherAlert object. - alert_watcher = await hass.async_add_executor_job( - DepartmentWeatherAlert, datas["dept"], weather_alert_client - ) - except ValueError as exp: - _LOGGER.error( - "Unexpected error when creating the weather alert sensor for %s in department %s: %s", - city, - datas["dept"], - exp, - ) - alert_watcher = None - else: - _LOGGER.info( - "Weather alert watcher added for %s in department %s", - city, - datas["dept"], - ) + datas = client.get_data() + # Check if a department code is available for this city. + if "dept" in datas: + try: + # If yes create the watcher DepartmentWeatherAlert object. + alert_watcher = await hass.async_add_executor_job( + DepartmentWeatherAlert, datas["dept"], weather_alert_client + ) + except ValueError as exp: + _LOGGER.error( + "Unexpected error when creating the weather alert sensor for %s in department %s: %s", + city, + datas["dept"], + exp, + ) + alert_watcher = None else: - _LOGGER.warning( - "No 'dept' key found for '%s'. So weather alert information won't be available", + _LOGGER.info( + "Weather alert watcher added for %s in department %s", city, + datas["dept"], ) - # Exit and don't create the sensor if no department code available. - return + else: + _LOGGER.warning( + "No 'dept' key found for '%s'. So weather alert information won't be available", + city, + ) + # Exit and don't create the sensor if no department code available. + return async_add_entities( [ - MeteoFranceSensor(variable, client, alert_watcher) - for variable in monitored_conditions + MeteoFranceSensor(sensor_type, client, alert_watcher) + for sensor_type in SENSOR_TYPES ], True, ) @@ -81,12 +79,12 @@ class MeteoFranceSensor(Entity): def __init__( self, - condition, + type: str, client: meteofranceClient, alert_watcher: VigilanceMeteoFranceProxy, ): """Initialize the Meteo-France sensor.""" - self._condition = condition + self._type = type self._client = client self._alert_watcher = alert_watcher self._state = None @@ -95,7 +93,7 @@ def __init__( @property def name(self): """Return the name of the sensor.""" - return f"{self._data['name']} {SENSOR_TYPES[self._condition][SENSOR_TYPE_NAME]}" + return f"{self._data['name']} {SENSOR_TYPES[self._type][SENSOR_TYPE_NAME]}" @property def unique_id(self): @@ -111,7 +109,7 @@ def state(self): def device_state_attributes(self): """Return the state attributes of the sensor.""" # Attributes for next_rain sensor. - if self._condition == "next_rain" and "rain_forecast" in self._data: + if self._type == "next_rain" and "rain_forecast" in self._data: return { **{STATE_ATTR_FORECAST: self._data["rain_forecast"]}, **self._data["next_rain_intervals"], @@ -119,7 +117,7 @@ def device_state_attributes(self): } # Attributes for weather_alert sensor. - if self._condition == "weather_alert" and self._alert_watcher is not None: + if self._type == "weather_alert" and self._alert_watcher is not None: return { **{STATE_ATTR_BULLETIN_TIME: self._alert_watcher.bulletin_date}, **self._alert_watcher.alerts_list, @@ -132,17 +130,17 @@ def device_state_attributes(self): @property def unit_of_measurement(self): """Return the unit of measurement.""" - return SENSOR_TYPES[self._condition][SENSOR_TYPE_UNIT] + return SENSOR_TYPES[self._type][SENSOR_TYPE_UNIT] @property def icon(self): """Return the icon.""" - return SENSOR_TYPES[self._condition][SENSOR_TYPE_ICON] + return SENSOR_TYPES[self._type][SENSOR_TYPE_ICON] @property def device_class(self): """Return the device class of the sensor.""" - return SENSOR_TYPES[self._condition][SENSOR_TYPE_CLASS] + return SENSOR_TYPES[self._type][SENSOR_TYPE_CLASS] def update(self): """Fetch new state data for the sensor.""" @@ -150,7 +148,7 @@ def update(self): self._client.update() self._data = self._client.get_data() - if self._condition == "weather_alert": + if self._type == "weather_alert": if self._alert_watcher is not None: self._alert_watcher.update_department_status() self._state = self._alert_watcher.department_color @@ -165,9 +163,9 @@ def update(self): "No weather alert data for location %s", self._data["name"] ) else: - self._state = self._data[self._condition] + self._state = self._data[self._type] except KeyError: _LOGGER.error( - "No condition %s for location %s", self._condition, self._data["name"] + "No condition %s for location %s", self._type, self._data["name"] ) self._state = None From b02122e3ca6511472dc91ebbace075b79c79d78f Mon Sep 17 00:00:00 2001 From: Quentin POLLET Date: Mon, 3 Feb 2020 16:14:17 +0100 Subject: [PATCH 10/17] Add async_unload_entry --- .../components/meteo_france/__init__.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/homeassistant/components/meteo_france/__init__.py b/homeassistant/components/meteo_france/__init__.py index ff263a621d339f..736a1d2db294a5 100644 --- a/homeassistant/components/meteo_france/__init__.py +++ b/homeassistant/components/meteo_france/__init__.py @@ -1,4 +1,5 @@ """Support for Meteo-France weather data.""" +import asyncio import datetime import logging @@ -76,6 +77,22 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool return True +async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): + """Unload a config entry.""" + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS + ] + ) + ) + if unload_ok: + hass.data[DOMAIN].pop(entry.data[CONF_CITY]) + + return unload_ok + + class MeteoFranceUpdater: """Update data from Meteo-France.""" From b6d6a8641a56691266e2ad05f889390263d6da81 Mon Sep 17 00:00:00 2001 From: Quentin POLLET Date: Mon, 3 Feb 2020 19:40:29 +0100 Subject: [PATCH 11/17] Review 3 --- homeassistant/components/meteo_france/__init__.py | 6 +++--- .../components/meteo_france/config_flow.py | 3 +-- homeassistant/components/meteo_france/sensor.py | 14 ++++++-------- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/meteo_france/__init__.py b/homeassistant/components/meteo_france/__init__.py index 736a1d2db294a5..b7eda51b95560a 100644 --- a/homeassistant/components/meteo_france/__init__.py +++ b/homeassistant/components/meteo_france/__init__.py @@ -48,9 +48,9 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool hass.data.setdefault(DOMAIN, {}) # Weather alert - weather_alert_client = await hass.async_add_executor_job(VigilanceMeteoFranceProxy) + weather_alert_client = VigilanceMeteoFranceProxy() try: - weather_alert_client.update_data() + await hass.async_add_executor_job(weather_alert_client.update_data) except VigilanceMeteoError as exp: _LOGGER.error( "Unexpected error when creating the vigilance_meteoFrance proxy: %s ", exp @@ -67,7 +67,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool return False hass.data[DOMAIN][city] = MeteoFranceUpdater(client) - hass.data[DOMAIN][city].update() + await hass.async_add_executor_job(hass.data[DOMAIN][city].update) for platform in PLATFORMS: hass.async_create_task( diff --git a/homeassistant/components/meteo_france/config_flow.py b/homeassistant/components/meteo_france/config_flow.py index a884f03a080f5a..b82067f708d4af 100644 --- a/homeassistant/components/meteo_france/config_flow.py +++ b/homeassistant/components/meteo_france/config_flow.py @@ -5,7 +5,6 @@ import voluptuous as vol from homeassistant import config_entries -from homeassistant.data_entry_flow import AbortFlow from .const import CONF_CITY from .const import DOMAIN # pylint: disable=unused-import @@ -50,7 +49,7 @@ async def async_step_user(self, user_input=None): _LOGGER.error( "Unexpected error when creating the meteofrance proxy: %s", exp ) - raise AbortFlow("unknown") + return self.async_abort(reason="unknown") # Check if already configured await self.async_set_unique_id(city_name) diff --git a/homeassistant/components/meteo_france/sensor.py b/homeassistant/components/meteo_france/sensor.py index c9d37324a4c538..bc90dbb7611271 100644 --- a/homeassistant/components/meteo_france/sensor.py +++ b/homeassistant/components/meteo_france/sensor.py @@ -43,6 +43,11 @@ async def async_setup_entry( alert_watcher = await hass.async_add_executor_job( DepartmentWeatherAlert, datas["dept"], weather_alert_client ) + _LOGGER.info( + "Weather alert watcher added for %s in department %s", + city, + datas["dept"], + ) except ValueError as exp: _LOGGER.error( "Unexpected error when creating the weather alert sensor for %s in department %s: %s", @@ -51,12 +56,6 @@ async def async_setup_entry( exp, ) alert_watcher = None - else: - _LOGGER.info( - "Weather alert watcher added for %s in department %s", - city, - datas["dept"], - ) else: _LOGGER.warning( "No 'dept' key found for '%s'. So weather alert information won't be available", @@ -153,8 +152,7 @@ def update(self): self._alert_watcher.update_department_status() self._state = self._alert_watcher.department_color _LOGGER.debug( - "weather alert watcher for %s updated. Proxy" - " have the status: %s", + "weather alert watcher for %s updated. Proxy have the status: %s", self._data["name"], self._alert_watcher.proxy.status, ) From d1c314d0d338be0c14224d2d66fafdde6a923d0b Mon Sep 17 00:00:00 2001 From: Quentin POLLET Date: Mon, 3 Feb 2020 19:47:07 +0100 Subject: [PATCH 12/17] Fix pipe --- .../components/meteo_france/sensor.py | 4 +-- .../meteo_france/test_config_flow.py | 27 ++----------------- 2 files changed, 4 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/meteo_france/sensor.py b/homeassistant/components/meteo_france/sensor.py index bc90dbb7611271..c275ac4f76d1be 100644 --- a/homeassistant/components/meteo_france/sensor.py +++ b/homeassistant/components/meteo_france/sensor.py @@ -78,12 +78,12 @@ class MeteoFranceSensor(Entity): def __init__( self, - type: str, + sensor_type: str, client: meteofranceClient, alert_watcher: VigilanceMeteoFranceProxy, ): """Initialize the Meteo-France sensor.""" - self._type = type + self._type = sensor_type self._client = client self._alert_watcher = alert_watcher self._state = None diff --git a/tests/components/meteo_france/test_config_flow.py b/tests/components/meteo_france/test_config_flow.py index f6872d5b90c0ac..c807886cbbd5c6 100644 --- a/tests/components/meteo_france/test_config_flow.py +++ b/tests/components/meteo_france/test_config_flow.py @@ -5,9 +5,8 @@ import pytest from homeassistant import data_entry_flow -from homeassistant.components.meteo_france.const import CONF_CITY, DOMAIN, SENSOR_TYPES +from homeassistant.components.meteo_france.const import CONF_CITY, DOMAIN from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER -from homeassistant.const import CONF_MONITORED_CONDITIONS from tests.common import MockConfigEntry @@ -16,8 +15,6 @@ CITY_2_POSTAL_DISTRICT_1 = "69001" CITY_2_POSTAL_DISTRICT_4 = "69004" CITY_2_NAME = "Lyon" -MONITORED_CONDITIONS = ["temperature", "weather"] -DEFAULT_MONITORED_CONDITIONS = list(SENSOR_TYPES) @pytest.fixture(name="client_1") @@ -58,12 +55,11 @@ async def test_user(hass, client_1): assert result["result"].unique_id == CITY_1_NAME assert result["title"] == CITY_1_NAME assert result["data"][CONF_CITY] == CITY_1_NAME - assert result["data"][CONF_MONITORED_CONDITIONS] == DEFAULT_MONITORED_CONDITIONS async def test_import(hass, client_1): """Test import step.""" - # import with city + # import with all result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data={CONF_CITY: CITY_1_POSTAL}, ) @@ -71,25 +67,6 @@ async def test_import(hass, client_1): assert result["result"].unique_id == CITY_1_NAME assert result["title"] == CITY_1_NAME assert result["data"][CONF_CITY] == CITY_1_NAME - assert result["data"][CONF_MONITORED_CONDITIONS] == DEFAULT_MONITORED_CONDITIONS - - -async def test_import_all(hass, client_2): - """Test import step.""" - # import with all - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data={ - CONF_CITY: CITY_2_POSTAL_DISTRICT_4, - CONF_MONITORED_CONDITIONS: MONITORED_CONDITIONS, - }, - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["result"].unique_id == CITY_2_NAME - assert result["title"] == CITY_2_NAME - assert result["data"][CONF_CITY] == CITY_2_NAME - assert result["data"][CONF_MONITORED_CONDITIONS] == MONITORED_CONDITIONS async def test_abort_if_already_setup(hass, client_1): From 5c703415ba449bd095c89da87031d591cc867f05 Mon Sep 17 00:00:00 2001 From: Quentin POLLET Date: Mon, 3 Feb 2020 19:48:20 +0100 Subject: [PATCH 13/17] alert_watcher is already None --- homeassistant/components/meteo_france/sensor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/meteo_france/sensor.py b/homeassistant/components/meteo_france/sensor.py index c275ac4f76d1be..cf28b9ea558d20 100644 --- a/homeassistant/components/meteo_france/sensor.py +++ b/homeassistant/components/meteo_france/sensor.py @@ -55,7 +55,6 @@ async def async_setup_entry( datas["dept"], exp, ) - alert_watcher = None else: _LOGGER.warning( "No 'dept' key found for '%s'. So weather alert information won't be available", From 8297fdc77c7aebfa4c27e79ee2d0b5a330420602 Mon Sep 17 00:00:00 2001 From: Quentin POLLET Date: Tue, 4 Feb 2020 12:47:25 +0100 Subject: [PATCH 14/17] Review 4 --- CODEOWNERS | 2 +- homeassistant/components/meteo_france/manifest.json | 2 +- tests/components/meteo_france/test_config_flow.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 5a66f80a1d0956..4df7f250d606b6 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -206,7 +206,7 @@ homeassistant/components/mcp23017/* @jardiamj homeassistant/components/mediaroom/* @dgomes homeassistant/components/melissa/* @kennedyshead homeassistant/components/met/* @danielhiversen -homeassistant/components/meteo_france/* @victorcerutti @oncleben31 +homeassistant/components/meteo_france/* @victorcerutti @oncleben31 @Quentame homeassistant/components/meteoalarm/* @rolfberkenbosch homeassistant/components/miflora/* @danielhiversen @ChristianKuehnel homeassistant/components/mikrotik/* @engrbm87 diff --git a/homeassistant/components/meteo_france/manifest.json b/homeassistant/components/meteo_france/manifest.json index 0293b71197260b..77f8fca984d6fc 100644 --- a/homeassistant/components/meteo_france/manifest.json +++ b/homeassistant/components/meteo_france/manifest.json @@ -5,5 +5,5 @@ "documentation": "https://www.home-assistant.io/integrations/meteo_france", "requirements": ["meteofrance==0.3.7", "vigilancemeteo==3.0.0"], "dependencies": [], - "codeowners": ["@victorcerutti", "@oncleben31"] + "codeowners": ["@victorcerutti", "@oncleben31", "@Quentame"] } diff --git a/tests/components/meteo_france/test_config_flow.py b/tests/components/meteo_france/test_config_flow.py index c807886cbbd5c6..52fa337da58f07 100644 --- a/tests/components/meteo_france/test_config_flow.py +++ b/tests/components/meteo_france/test_config_flow.py @@ -42,7 +42,7 @@ def mock_controller_client_2(): async def test_user(hass, client_1): """Test user config.""" result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data=None, + DOMAIN, context={"source": SOURCE_USER} ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "user" From 4249ac0654ef2c9ec7ff7035020489b6f005b8ed Mon Sep 17 00:00:00 2001 From: Quentin POLLET Date: Tue, 4 Feb 2020 13:04:40 +0100 Subject: [PATCH 15/17] Better fix for "Entity id already exists" --- homeassistant/components/meteo_france/.translations/en.json | 3 ++- homeassistant/components/meteo_france/config_flow.py | 2 +- homeassistant/components/meteo_france/strings.json | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/meteo_france/.translations/en.json b/homeassistant/components/meteo_france/.translations/en.json index fdcc7de83ed96d..f9c4a0e9dc4a1d 100644 --- a/homeassistant/components/meteo_france/.translations/en.json +++ b/homeassistant/components/meteo_france/.translations/en.json @@ -7,8 +7,9 @@ "step": { "user": { "data": { - "city": "City or postal code" + "city": "City" }, + "description": "Enter the postal code (only for France, recommanded) or city name", "title": "M\u00e9t\u00e9o-France" } }, diff --git a/homeassistant/components/meteo_france/config_flow.py b/homeassistant/components/meteo_france/config_flow.py index b82067f708d4af..c7673020360095 100644 --- a/homeassistant/components/meteo_france/config_flow.py +++ b/homeassistant/components/meteo_france/config_flow.py @@ -55,7 +55,7 @@ async def async_step_user(self, user_input=None): await self.async_set_unique_id(city_name) self._abort_if_unique_id_configured() - return self.async_create_entry(title=city_name, data={CONF_CITY: city_name}) + return self.async_create_entry(title=city_name, data={CONF_CITY: city}) async def async_step_import(self, user_input): """Import a config entry.""" diff --git a/homeassistant/components/meteo_france/strings.json b/homeassistant/components/meteo_france/strings.json index ecd7953aa62ce1..74c258d1874bb5 100644 --- a/homeassistant/components/meteo_france/strings.json +++ b/homeassistant/components/meteo_france/strings.json @@ -4,8 +4,9 @@ "step": { "user": { "title": "Météo-France", + "description": "Enter the postal code (only for France, recommanded) or city name", "data": { - "city": "City or postal code" + "city": "City" } } }, From 90c63f9744aacb1feb9f7d72886e72bc9c5cdf88 Mon Sep 17 00:00:00 2001 From: Quentin POLLET Date: Tue, 4 Feb 2020 13:52:01 +0100 Subject: [PATCH 16/17] Whoops, fix tests --- tests/components/meteo_france/test_config_flow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/components/meteo_france/test_config_flow.py b/tests/components/meteo_france/test_config_flow.py index 52fa337da58f07..f9ead2c1ef394f 100644 --- a/tests/components/meteo_france/test_config_flow.py +++ b/tests/components/meteo_france/test_config_flow.py @@ -54,7 +54,7 @@ async def test_user(hass, client_1): assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["result"].unique_id == CITY_1_NAME assert result["title"] == CITY_1_NAME - assert result["data"][CONF_CITY] == CITY_1_NAME + assert result["data"][CONF_CITY] == CITY_1_POSTAL async def test_import(hass, client_1): @@ -66,7 +66,7 @@ async def test_import(hass, client_1): assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["result"].unique_id == CITY_1_NAME assert result["title"] == CITY_1_NAME - assert result["data"][CONF_CITY] == CITY_1_NAME + assert result["data"][CONF_CITY] == CITY_1_POSTAL async def test_abort_if_already_setup(hass, client_1): From fc159c452b1d1fb93e7d65d72b64a8ee4f5c16dc Mon Sep 17 00:00:00 2001 From: Quentin POLLET Date: Tue, 4 Feb 2020 18:04:59 +0100 Subject: [PATCH 17/17] Fix string --- homeassistant/components/meteo_france/.translations/en.json | 2 +- homeassistant/components/meteo_france/strings.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/meteo_france/.translations/en.json b/homeassistant/components/meteo_france/.translations/en.json index f9c4a0e9dc4a1d..804ad9d67b1bbb 100644 --- a/homeassistant/components/meteo_france/.translations/en.json +++ b/homeassistant/components/meteo_france/.translations/en.json @@ -9,7 +9,7 @@ "data": { "city": "City" }, - "description": "Enter the postal code (only for France, recommanded) or city name", + "description": "Enter the postal code (only for France, recommended) or city name", "title": "M\u00e9t\u00e9o-France" } }, diff --git a/homeassistant/components/meteo_france/strings.json b/homeassistant/components/meteo_france/strings.json index 74c258d1874bb5..8bb02f28bd06c4 100644 --- a/homeassistant/components/meteo_france/strings.json +++ b/homeassistant/components/meteo_france/strings.json @@ -4,7 +4,7 @@ "step": { "user": { "title": "Météo-France", - "description": "Enter the postal code (only for France, recommanded) or city name", + "description": "Enter the postal code (only for France, recommended) or city name", "data": { "city": "City" }