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:
parent
fff3cb0b46
commit
ef79566864
5 changed files with 60 additions and 71 deletions
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
|
||||||
|
|
5
homeassistant/components/openuv/services.yaml
Normal file
5
homeassistant/components/openuv/services.yaml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# Describes the format for available OpenUV services
|
||||||
|
|
||||||
|
---
|
||||||
|
update_data:
|
||||||
|
description: Request new data from OpenUV.
|
|
@ -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,
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue