diff --git a/homeassistant/components/openuv/__init__.py b/homeassistant/components/openuv/__init__.py index e6a302975a5..1403778aae2 100644 --- a/homeassistant/components/openuv/__init__.py +++ b/homeassistant/components/openuv/__init__.py @@ -4,9 +4,7 @@ import logging from pyopenuv import Client from pyopenuv.errors import OpenUvError -import voluptuous as vol -from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_API_KEY, @@ -26,7 +24,6 @@ from homeassistant.helpers.dispatcher import ( from homeassistant.helpers.entity import Entity from homeassistant.helpers.service import verify_domain_control -from .config_flow import configured_instances from .const import DOMAIN _LOGGER = logging.getLogger(__name__) @@ -55,54 +52,14 @@ TYPE_SAFE_EXPOSURE_TIME_4 = "safe_exposure_time_type_4" TYPE_SAFE_EXPOSURE_TIME_5 = "safe_exposure_time_type_5" TYPE_SAFE_EXPOSURE_TIME_6 = "safe_exposure_time_type_6" +PLATFORMS = ["binary_sensor", "sensor"] -CONFIG_SCHEMA = vol.Schema( - { - DOMAIN: vol.Schema( - { - vol.Required(CONF_API_KEY): cv.string, - vol.Optional(CONF_ELEVATION): float, - vol.Optional(CONF_LATITUDE): cv.latitude, - vol.Optional(CONF_LONGITUDE): cv.longitude, - } - ) - }, - extra=vol.ALLOW_EXTRA, -) +CONFIG_SCHEMA = cv.deprecated(DOMAIN, invalidation_version="0.115") async def async_setup(hass, config): """Set up the OpenUV component.""" - hass.data[DOMAIN] = {} - hass.data[DOMAIN][DATA_OPENUV_CLIENT] = {} - hass.data[DOMAIN][DATA_OPENUV_LISTENER] = {} - - if DOMAIN not in config: - return True - - conf = config[DOMAIN] - - identifier = ( - f"{conf.get(CONF_LATITUDE, hass.config.latitude)}, " - f"{conf.get(CONF_LONGITUDE, hass.config.longitude)}" - ) - if identifier in configured_instances(hass): - return True - - data = {CONF_API_KEY: conf[CONF_API_KEY]} - if CONF_LATITUDE in conf: - data[CONF_LATITUDE] = conf[CONF_LATITUDE] - if CONF_LONGITUDE in conf: - data[CONF_LONGITUDE] = conf[CONF_LONGITUDE] - if CONF_ELEVATION in conf: - data[CONF_ELEVATION] = conf[CONF_ELEVATION] - - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_IMPORT}, data=data - ) - ) - + hass.data[DOMAIN] = {DATA_OPENUV_CLIENT: {}, DATA_OPENUV_LISTENER: {}} return True @@ -127,7 +84,7 @@ async def async_setup_entry(hass, config_entry): _LOGGER.error("Config entry failed: %s", err) raise ConfigEntryNotReady - for component in ("binary_sensor", "sensor"): + for component in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(config_entry, component) ) @@ -139,8 +96,6 @@ async def async_setup_entry(hass, config_entry): await openuv.async_update() async_dispatcher_send(hass, TOPIC_UPDATE) - hass.services.async_register(DOMAIN, "update_data", update_data) - @_verify_domain_control async def update_uv_index_data(service): """Refresh OpenUV UV index data.""" @@ -148,8 +103,6 @@ async def async_setup_entry(hass, config_entry): await openuv.async_update_uv_index_data() async_dispatcher_send(hass, TOPIC_UPDATE) - hass.services.async_register(DOMAIN, "update_uv_index_data", update_uv_index_data) - @_verify_domain_control async def update_protection_data(service): """Refresh OpenUV protection window data.""" @@ -157,25 +110,30 @@ async def async_setup_entry(hass, config_entry): await openuv.async_update_protection_data() async_dispatcher_send(hass, TOPIC_UPDATE) - hass.services.async_register( - DOMAIN, "update_protection_data", update_protection_data - ) + for service, method in [ + ("update_data", update_data), + ("update_uv_index_data", update_uv_index_data), + ("update_protection_data", update_protection_data), + ]: + hass.services.async_register(DOMAIN, service, method) return True async def async_unload_entry(hass, config_entry): """Unload an OpenUV config entry.""" - hass.data[DOMAIN][DATA_OPENUV_CLIENT].pop(config_entry.entry_id) + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(config_entry, component) + for component in PLATFORMS + ] + ) + ) + if unload_ok: + hass.data[DOMAIN][DATA_OPENUV_CLIENT].pop(config_entry.entry_id) - tasks = [ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in ("binary_sensor", "sensor") - ] - - await asyncio.gather(*tasks) - - return True + return unload_ok async def async_migrate_entry(hass, config_entry): diff --git a/homeassistant/components/openuv/config_flow.py b/homeassistant/components/openuv/config_flow.py index 821c9624c58..bf359ef1b30 100644 --- a/homeassistant/components/openuv/config_flow.py +++ b/homeassistant/components/openuv/config_flow.py @@ -10,24 +10,21 @@ from homeassistant.const import ( CONF_LATITUDE, CONF_LONGITUDE, ) -from homeassistant.core import callback from homeassistant.helpers import aiohttp_client, config_validation as cv -from .const import DOMAIN +from .const import DOMAIN # pylint: disable=unused-import - -@callback -def configured_instances(hass): - """Return a set of configured OpenUV instances.""" - return { - f"{entry.data.get(CONF_LATITUDE, hass.config.latitude)}, " - f"{entry.data.get(CONF_LONGITUDE, hass.config.longitude)}" - for entry in hass.config_entries.async_entries(DOMAIN) +CONFIG_SCHEMA = vol.Schema( + { + vol.Required(CONF_API_KEY): str, + vol.Inclusive(CONF_LATITUDE, "coords"): cv.latitude, + vol.Inclusive(CONF_LONGITUDE, "coords"): cv.longitude, + vol.Optional(CONF_ELEVATION): vol.Coerce(float), } +) -@config_entries.HANDLERS.register(DOMAIN) -class OpenUvFlowHandler(config_entries.ConfigFlow): +class OpenUvFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Handle an OpenUV config flow.""" VERSION = 2 @@ -35,17 +32,8 @@ class OpenUvFlowHandler(config_entries.ConfigFlow): async def _show_form(self, errors=None): """Show the form to the user.""" - data_schema = vol.Schema( - { - vol.Required(CONF_API_KEY): str, - vol.Optional(CONF_LATITUDE): cv.latitude, - vol.Optional(CONF_LONGITUDE): cv.longitude, - vol.Optional(CONF_ELEVATION): vol.Coerce(float), - } - ) - return self.async_show_form( - step_id="user", data_schema=data_schema, errors=errors if errors else {} + step_id="user", data_schema=CONFIG_SCHEMA, errors=errors if errors else {}, ) async def async_step_import(self, import_config): @@ -54,16 +42,16 @@ class OpenUvFlowHandler(config_entries.ConfigFlow): async def async_step_user(self, user_input=None): """Handle the start of the config flow.""" - if not user_input: return await self._show_form() - identifier = ( - f"{user_input.get(CONF_LATITUDE, self.hass.config.latitude)}, " - f"{user_input.get(CONF_LONGITUDE, self.hass.config.longitude)}" - ) - if identifier in configured_instances(self.hass): - return await self._show_form({CONF_LATITUDE: "identifier_exists"}) + if user_input.get(CONF_LATITUDE): + identifier = f"{user_input[CONF_LATITUDE]}, {user_input[CONF_LONGITUDE]}" + else: + identifier = "Default Coordinates" + + await self.async_set_unique_id(identifier) + self._abort_if_unique_id_configured() websession = aiohttp_client.async_get_clientsession(self.hass) client = Client(user_input[CONF_API_KEY], 0, 0, websession) diff --git a/homeassistant/components/openuv/strings.json b/homeassistant/components/openuv/strings.json index 0c4913cf6c1..0777b139cf9 100644 --- a/homeassistant/components/openuv/strings.json +++ b/homeassistant/components/openuv/strings.json @@ -13,7 +13,10 @@ }, "error": { "identifier_exists": "Coordinates already registered", - "invalid_api_key": "Invalid API key" + "invalid_api_key": "[%key:common::config_flow::error::invalid_api_key%]" + }, + "abort": { + "already_configured": "These coordinates are already registered." } } -} \ No newline at end of file +} diff --git a/homeassistant/components/openuv/translations/en.json b/homeassistant/components/openuv/translations/en.json index 4c59a587fcd..0d2eec6341d 100644 --- a/homeassistant/components/openuv/translations/en.json +++ b/homeassistant/components/openuv/translations/en.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "These coordinates are already registered." + }, "error": { "identifier_exists": "Coordinates already registered", "invalid_api_key": "Invalid API key" @@ -7,7 +10,7 @@ "step": { "user": { "data": { - "api_key": "API Key", + "api_key": "[%key:common::config_flow::data::api_key%]", "elevation": "Elevation", "latitude": "Latitude", "longitude": "Longitude" diff --git a/tests/components/openuv/test_config_flow.py b/tests/components/openuv/test_config_flow.py index 3aa67abdc4f..b85805efa94 100644 --- a/tests/components/openuv/test_config_flow.py +++ b/tests/components/openuv/test_config_flow.py @@ -1,11 +1,9 @@ """Define tests for the OpenUV config flow.""" -from unittest.mock import patch - -from pyopenuv.errors import OpenUvError -import pytest +from pyopenuv.errors import InvalidApiKeyError from homeassistant import data_entry_flow -from homeassistant.components.openuv import DOMAIN, config_flow +from homeassistant.components.openuv import DOMAIN +from homeassistant.config_entries import SOURCE_USER from homeassistant.const import ( CONF_API_KEY, CONF_ELEVATION, @@ -13,21 +11,8 @@ from homeassistant.const import ( CONF_LONGITUDE, ) -from tests.common import MockConfigEntry, mock_coro - - -@pytest.fixture -def uv_index_response(): - """Define a fixture for a successful /uv response.""" - return mock_coro() - - -@pytest.fixture -def mock_pyopenuv(uv_index_response): - """Mock the pyopenuv library.""" - with patch("homeassistant.components.openuv.config_flow.Client") as MockClient: - MockClient().uv_index.return_value = uv_index_response - yield MockClient +from tests.async_mock import patch +from tests.common import MockConfigEntry async def test_duplicate_error(hass): @@ -39,16 +24,19 @@ async def test_duplicate_error(hass): CONF_LONGITUDE: -104.9812612, } - MockConfigEntry(domain=DOMAIN, data=conf).add_to_hass(hass) - flow = config_flow.OpenUvFlowHandler() - flow.hass = hass + MockConfigEntry( + domain=DOMAIN, unique_id="39.128712, -104.9812612", data=conf + ).add_to_hass(hass) - result = await flow.async_step_user(user_input=conf) - assert result["errors"] == {CONF_LATITUDE: "identifier_exists"} + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=conf + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" -@pytest.mark.parametrize("uv_index_response", [mock_coro(exception=OpenUvError)]) -async def test_invalid_api_key(hass, mock_pyopenuv): +async def test_invalid_api_key(hass): """Test that an invalid API key throws an error.""" conf = { CONF_API_KEY: "12345abcde", @@ -57,48 +45,17 @@ async def test_invalid_api_key(hass, mock_pyopenuv): CONF_LONGITUDE: -104.9812612, } - flow = config_flow.OpenUvFlowHandler() - flow.hass = hass - - result = await flow.async_step_user(user_input=conf) - assert result["errors"] == {CONF_API_KEY: "invalid_api_key"} + with patch( + "pyopenuv.client.Client.uv_index", side_effect=InvalidApiKeyError, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=conf + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {CONF_API_KEY: "invalid_api_key"} -async def test_show_form(hass): - """Test that the form is served with no input.""" - flow = config_flow.OpenUvFlowHandler() - flow.hass = hass - - result = await flow.async_step_user(user_input=None) - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "user" - - -async def test_step_import(hass, mock_pyopenuv): - """Test that the import step works.""" - conf = { - CONF_API_KEY: "12345abcde", - CONF_ELEVATION: 59.1234, - CONF_LATITUDE: 39.128712, - CONF_LONGITUDE: -104.9812612, - } - - flow = config_flow.OpenUvFlowHandler() - flow.hass = hass - - result = await flow.async_step_import(import_config=conf) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["title"] == "39.128712, -104.9812612" - assert result["data"] == { - CONF_API_KEY: "12345abcde", - CONF_ELEVATION: 59.1234, - CONF_LATITUDE: 39.128712, - CONF_LONGITUDE: -104.9812612, - } - - -async def test_step_user(hass, mock_pyopenuv): +async def test_step_user(hass): """Test that the user step works.""" conf = { CONF_API_KEY: "12345abcde", @@ -107,15 +64,23 @@ async def test_step_user(hass, mock_pyopenuv): CONF_LONGITUDE: -104.9812612, } - flow = config_flow.OpenUvFlowHandler() - flow.hass = hass + with patch( + "homeassistant.components.airvisual.async_setup_entry", return_value=True + ), patch("pyopenuv.client.Client.uv_index"): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" - result = await flow.async_step_user(user_input=conf) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["title"] == "39.128712, -104.9812612" - assert result["data"] == { - CONF_API_KEY: "12345abcde", - CONF_ELEVATION: 59.1234, - CONF_LATITUDE: 39.128712, - CONF_LONGITUDE: -104.9812612, - } + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=conf + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "39.128712, -104.9812612" + assert result["data"] == { + CONF_API_KEY: "12345abcde", + CONF_ELEVATION: 59.1234, + CONF_LATITUDE: 39.128712, + CONF_LONGITUDE: -104.9812612, + }