Adjust OpenUV integration for upcoming API limit changes (#19949)

* Adjust OpenUV integration for upcoming API limit changes

* Added fix for "Invalid API Key"

* Bugfix

* Add initial nighttime check

* Move from polling to a service-based model

* Fixed test

* Removed unnecessary scan interval

* Fixed test

* Moving test imports

* Member comments

* Hound

* Removed unused import
This commit is contained in:
Aaron Bach 2019-01-14 05:12:06 -07:00 committed by Fabian Affolter
parent fff3cb0b46
commit ef79566864
5 changed files with 60 additions and 71 deletions

View file

@ -5,7 +5,6 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/openuv/ https://home-assistant.io/components/openuv/
""" """
import logging import logging
from datetime import timedelta
import voluptuous as vol import voluptuous as vol
@ -13,15 +12,14 @@ from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import ( from homeassistant.const import (
ATTR_ATTRIBUTION, CONF_API_KEY, CONF_BINARY_SENSORS, CONF_ELEVATION, ATTR_ATTRIBUTION, CONF_API_KEY, CONF_BINARY_SENSORS, CONF_ELEVATION,
CONF_LATITUDE, CONF_LONGITUDE, CONF_MONITORED_CONDITIONS, CONF_LATITUDE, CONF_LONGITUDE, CONF_MONITORED_CONDITIONS,
CONF_SCAN_INTERVAL, CONF_SENSORS) CONF_SENSORS)
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import aiohttp_client, config_validation as cv from homeassistant.helpers import aiohttp_client, config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_track_time_interval
from .config_flow import configured_instances from .config_flow import configured_instances
from .const import DEFAULT_SCAN_INTERVAL, DOMAIN from .const import DOMAIN
REQUIREMENTS = ['pyopenuv==1.0.4'] REQUIREMENTS = ['pyopenuv==1.0.4']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -93,8 +91,6 @@ CONFIG_SCHEMA = vol.Schema({
vol.Optional(CONF_BINARY_SENSORS, default={}): vol.Optional(CONF_BINARY_SENSORS, default={}):
BINARY_SENSOR_SCHEMA, BINARY_SENSOR_SCHEMA,
vol.Optional(CONF_SENSORS, default={}): SENSOR_SCHEMA, vol.Optional(CONF_SENSORS, default={}): SENSOR_SCHEMA,
vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL):
cv.time_period,
}) })
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
@ -120,7 +116,6 @@ async def async_setup(hass, config):
CONF_API_KEY: conf[CONF_API_KEY], CONF_API_KEY: conf[CONF_API_KEY],
CONF_BINARY_SENSORS: conf[CONF_BINARY_SENSORS], CONF_BINARY_SENSORS: conf[CONF_BINARY_SENSORS],
CONF_SENSORS: conf[CONF_SENSORS], CONF_SENSORS: conf[CONF_SENSORS],
CONF_SCAN_INTERVAL: conf[CONF_SCAN_INTERVAL],
} }
if CONF_LATITUDE in conf: if CONF_LATITUDE in conf:
@ -167,17 +162,13 @@ async def async_setup_entry(hass, config_entry):
hass.config_entries.async_forward_entry_setup( hass.config_entries.async_forward_entry_setup(
config_entry, component)) config_entry, component))
async def refresh(event_time): async def update_data(service):
"""Refresh OpenUV data.""" """Refresh OpenUV data."""
_LOGGER.debug('Refreshing OpenUV data') _LOGGER.debug('Refreshing OpenUV data')
await openuv.async_update() await openuv.async_update()
async_dispatcher_send(hass, TOPIC_UPDATE) async_dispatcher_send(hass, TOPIC_UPDATE)
hass.data[DOMAIN][DATA_OPENUV_LISTENER][ hass.services.async_register(DOMAIN, 'update_data', update_data)
config_entry.entry_id] = async_track_time_interval(
hass,
refresh,
timedelta(seconds=config_entry.data[CONF_SCAN_INTERVAL]))
return True return True
@ -186,10 +177,6 @@ async def async_unload_entry(hass, config_entry):
"""Unload an OpenUV config entry.""" """Unload an OpenUV config entry."""
hass.data[DOMAIN][DATA_OPENUV_CLIENT].pop(config_entry.entry_id) hass.data[DOMAIN][DATA_OPENUV_CLIENT].pop(config_entry.entry_id)
remove_listener = hass.data[DOMAIN][DATA_OPENUV_LISTENER].pop(
config_entry.entry_id)
remove_listener()
for component in ('binary_sensor', 'sensor'): for component in ('binary_sensor', 'sensor'):
await hass.config_entries.async_forward_entry_unload( await hass.config_entries.async_forward_entry_unload(
config_entry, component) config_entry, component)

View file

@ -5,11 +5,10 @@ import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.const import ( from homeassistant.const import (
CONF_API_KEY, CONF_ELEVATION, CONF_LATITUDE, CONF_LONGITUDE, CONF_API_KEY, CONF_ELEVATION, CONF_LATITUDE, CONF_LONGITUDE)
CONF_SCAN_INTERVAL)
from homeassistant.helpers import aiohttp_client, config_validation as cv from homeassistant.helpers import aiohttp_client, config_validation as cv
from .const import DEFAULT_SCAN_INTERVAL, DOMAIN from .const import DOMAIN
@callback @callback
@ -54,7 +53,8 @@ class OpenUvFlowHandler(config_entries.ConfigFlow):
async def async_step_user(self, user_input=None): async def async_step_user(self, user_input=None):
"""Handle the start of the config flow.""" """Handle the start of the config flow."""
from pyopenuv.util import validate_api_key from pyopenuv import Client
from pyopenuv.errors import OpenUvError
if not user_input: if not user_input:
return await self._show_form() return await self._show_form()
@ -66,14 +66,11 @@ class OpenUvFlowHandler(config_entries.ConfigFlow):
return await self._show_form({CONF_LATITUDE: 'identifier_exists'}) return await self._show_form({CONF_LATITUDE: 'identifier_exists'})
websession = aiohttp_client.async_get_clientsession(self.hass) websession = aiohttp_client.async_get_clientsession(self.hass)
api_key_validation = await validate_api_key( client = Client(user_input[CONF_API_KEY], 0, 0, websession)
user_input[CONF_API_KEY], websession)
if not api_key_validation: try:
await client.uv_index()
except OpenUvError:
return await self._show_form({CONF_API_KEY: 'invalid_api_key'}) return await self._show_form({CONF_API_KEY: 'invalid_api_key'})
scan_interval = user_input.get(
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
user_input[CONF_SCAN_INTERVAL] = scan_interval.seconds
return self.async_create_entry(title=identifier, data=user_input) return self.async_create_entry(title=identifier, data=user_input)

View file

@ -1,6 +1,2 @@
"""Define constants for the OpenUV component.""" """Define constants for the OpenUV component."""
from datetime import timedelta
DOMAIN = 'openuv' DOMAIN = 'openuv'
DEFAULT_SCAN_INTERVAL = timedelta(minutes=30)

View file

@ -0,0 +1,5 @@
# Describes the format for available OpenUV services
---
update_data:
description: Request new data from OpenUV.

View file

@ -1,14 +1,27 @@
"""Define tests for the OpenUV config flow.""" """Define tests for the OpenUV config flow."""
from datetime import timedelta import pytest
from unittest.mock import patch from pyopenuv.errors import OpenUvError
from homeassistant import data_entry_flow from homeassistant import data_entry_flow
from homeassistant.components.openuv import DOMAIN, config_flow from homeassistant.components.openuv import DOMAIN, config_flow
from homeassistant.const import ( from homeassistant.const import (
CONF_API_KEY, CONF_ELEVATION, CONF_LATITUDE, CONF_LONGITUDE, CONF_API_KEY, CONF_ELEVATION, CONF_LATITUDE, CONF_LONGITUDE)
CONF_SCAN_INTERVAL)
from tests.common import MockConfigEntry, mock_coro from tests.common import MockConfigEntry, MockDependency, 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 MockDependency('pyopenuv') as mock_pyopenuv_:
mock_pyopenuv_.Client().uv_index.return_value = uv_index_response
yield mock_pyopenuv_
async def test_duplicate_error(hass): async def test_duplicate_error(hass):
@ -28,7 +41,9 @@ async def test_duplicate_error(hass):
assert result['errors'] == {CONF_LATITUDE: 'identifier_exists'} assert result['errors'] == {CONF_LATITUDE: 'identifier_exists'}
async def test_invalid_api_key(hass): @pytest.mark.parametrize(
'uv_index_response', [mock_coro(exception=OpenUvError)])
async def test_invalid_api_key(hass, mock_pyopenuv):
"""Test that an invalid API key throws an error.""" """Test that an invalid API key throws an error."""
conf = { conf = {
CONF_API_KEY: '12345abcde', CONF_API_KEY: '12345abcde',
@ -40,10 +55,8 @@ async def test_invalid_api_key(hass):
flow = config_flow.OpenUvFlowHandler() flow = config_flow.OpenUvFlowHandler()
flow.hass = hass flow.hass = hass
with patch('pyopenuv.util.validate_api_key', result = await flow.async_step_user(user_input=conf)
return_value=mock_coro(False)): assert result['errors'] == {CONF_API_KEY: 'invalid_api_key'}
result = await flow.async_step_user(user_input=conf)
assert result['errors'] == {CONF_API_KEY: 'invalid_api_key'}
async def test_show_form(hass): async def test_show_form(hass):
@ -57,7 +70,7 @@ async def test_show_form(hass):
assert result['step_id'] == 'user' assert result['step_id'] == 'user'
async def test_step_import(hass): async def test_step_import(hass, mock_pyopenuv):
"""Test that the import step works.""" """Test that the import step works."""
conf = { conf = {
CONF_API_KEY: '12345abcde', CONF_API_KEY: '12345abcde',
@ -69,44 +82,35 @@ async def test_step_import(hass):
flow = config_flow.OpenUvFlowHandler() flow = config_flow.OpenUvFlowHandler()
flow.hass = hass flow.hass = hass
with patch('pyopenuv.util.validate_api_key', result = await flow.async_step_import(import_config=conf)
return_value=mock_coro(True)): assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
result = await flow.async_step_import(import_config=conf) assert result['title'] == '39.128712, -104.9812612'
assert result['data'] == {
assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY CONF_API_KEY: '12345abcde',
assert result['title'] == '39.128712, -104.9812612' CONF_ELEVATION: 59.1234,
assert result['data'] == { CONF_LATITUDE: 39.128712,
CONF_API_KEY: '12345abcde', CONF_LONGITUDE: -104.9812612,
CONF_ELEVATION: 59.1234, }
CONF_LATITUDE: 39.128712,
CONF_LONGITUDE: -104.9812612,
CONF_SCAN_INTERVAL: 1800,
}
async def test_step_user(hass): async def test_step_user(hass, mock_pyopenuv):
"""Test that the user step works.""" """Test that the user step works."""
conf = { conf = {
CONF_API_KEY: '12345abcde', CONF_API_KEY: '12345abcde',
CONF_ELEVATION: 59.1234, CONF_ELEVATION: 59.1234,
CONF_LATITUDE: 39.128712, CONF_LATITUDE: 39.128712,
CONF_LONGITUDE: -104.9812612, CONF_LONGITUDE: -104.9812612,
CONF_SCAN_INTERVAL: timedelta(minutes=5)
} }
flow = config_flow.OpenUvFlowHandler() flow = config_flow.OpenUvFlowHandler()
flow.hass = hass flow.hass = hass
with patch('pyopenuv.util.validate_api_key', result = await flow.async_step_user(user_input=conf)
return_value=mock_coro(True)): assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
result = await flow.async_step_user(user_input=conf) assert result['title'] == '39.128712, -104.9812612'
assert result['data'] == {
assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY CONF_API_KEY: '12345abcde',
assert result['title'] == '39.128712, -104.9812612' CONF_ELEVATION: 59.1234,
assert result['data'] == { CONF_LATITUDE: 39.128712,
CONF_API_KEY: '12345abcde', CONF_LONGITUDE: -104.9812612,
CONF_ELEVATION: 59.1234, }
CONF_LATITUDE: 39.128712,
CONF_LONGITUDE: -104.9812612,
CONF_SCAN_INTERVAL: 300,
}