Add config entry for IQVIA (#23765)
* Add config entry for IQVIA * Updated tests and requirements * Removed unnecessary dependency * Fixed tests * Reverted unintended changepull/23784/head
parent
4004867eda
commit
45adb5c9c7
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"config": {
|
||||
"error": {
|
||||
"identifier_exists": "ZIP code already registered",
|
||||
"invalid_zip_code": "ZIP code is invalid"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"zip_code": "ZIP Code"
|
||||
},
|
||||
"description": "Fill out your U.S. or Canadian ZIP code.",
|
||||
"title": "IQVIA"
|
||||
}
|
||||
},
|
||||
"title": "IQVIA"
|
||||
}
|
||||
}
|
|
@ -8,28 +8,27 @@ from pyiqvia.errors import IQVIAError, InvalidZipError
|
|||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import SOURCE_IMPORT
|
||||
from homeassistant.const import ATTR_ATTRIBUTION, CONF_MONITORED_CONDITIONS
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
||||
from homeassistant.helpers.discovery import async_load_platform
|
||||
from homeassistant.helpers.dispatcher import (
|
||||
async_dispatcher_connect, async_dispatcher_send)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.event import async_track_time_interval
|
||||
from homeassistant.util.decorator import Registry
|
||||
|
||||
from .config_flow import configured_instances
|
||||
from .const import (
|
||||
DATA_CLIENT, DATA_LISTENER, DOMAIN, SENSORS, TOPIC_DATA_UPDATE,
|
||||
TYPE_ALLERGY_FORECAST, TYPE_ALLERGY_INDEX, TYPE_ALLERGY_OUTLOOK,
|
||||
TYPE_ALLERGY_TODAY, TYPE_ALLERGY_TOMORROW, TYPE_ASTHMA_FORECAST,
|
||||
TYPE_ASTHMA_INDEX, TYPE_ASTHMA_TODAY, TYPE_ASTHMA_TOMORROW,
|
||||
TYPE_DISEASE_FORECAST, TYPE_DISEASE_INDEX, TYPE_DISEASE_TODAY)
|
||||
CONF_ZIP_CODE, DATA_CLIENT, DATA_LISTENER, DOMAIN, SENSORS,
|
||||
TOPIC_DATA_UPDATE, TYPE_ALLERGY_FORECAST, TYPE_ALLERGY_INDEX,
|
||||
TYPE_ALLERGY_OUTLOOK, TYPE_ALLERGY_TODAY, TYPE_ALLERGY_TOMORROW,
|
||||
TYPE_ASTHMA_FORECAST, TYPE_ASTHMA_INDEX, TYPE_ASTHMA_TODAY,
|
||||
TYPE_ASTHMA_TOMORROW, TYPE_DISEASE_FORECAST, TYPE_DISEASE_INDEX,
|
||||
TYPE_DISEASE_TODAY)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
CONF_ZIP_CODE = 'zip_code'
|
||||
|
||||
DATA_CONFIG = 'config'
|
||||
|
||||
DEFAULT_ATTRIBUTION = 'Data provided by IQVIA™'
|
||||
|
@ -59,23 +58,39 @@ async def async_setup(hass, config):
|
|||
hass.data[DOMAIN][DATA_CLIENT] = {}
|
||||
hass.data[DOMAIN][DATA_LISTENER] = {}
|
||||
|
||||
if DOMAIN not in config:
|
||||
return True
|
||||
|
||||
conf = config[DOMAIN]
|
||||
|
||||
if conf[CONF_ZIP_CODE] in configured_instances(hass):
|
||||
return True
|
||||
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={'source': SOURCE_IMPORT}, data=conf))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry):
|
||||
"""Set up IQVIA as config entry."""
|
||||
websession = aiohttp_client.async_get_clientsession(hass)
|
||||
|
||||
try:
|
||||
iqvia = IQVIAData(
|
||||
Client(conf[CONF_ZIP_CODE], websession),
|
||||
conf[CONF_MONITORED_CONDITIONS])
|
||||
Client(config_entry.data[CONF_ZIP_CODE], websession),
|
||||
config_entry.data.get(CONF_MONITORED_CONDITIONS, list(SENSORS)))
|
||||
await iqvia.async_update()
|
||||
except IQVIAError as err:
|
||||
_LOGGER.error('Unable to set up IQVIA: %s', err)
|
||||
return False
|
||||
|
||||
hass.data[DOMAIN][DATA_CLIENT] = iqvia
|
||||
hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = iqvia
|
||||
|
||||
hass.async_create_task(
|
||||
async_load_platform(hass, 'sensor', DOMAIN, {}, config))
|
||||
hass.config_entries.async_forward_entry_setup(
|
||||
config_entry, 'sensor'))
|
||||
|
||||
async def refresh(event_time):
|
||||
"""Refresh IQVIA data."""
|
||||
|
@ -83,8 +98,23 @@ async def async_setup(hass, config):
|
|||
await iqvia.async_update()
|
||||
async_dispatcher_send(hass, TOPIC_DATA_UPDATE)
|
||||
|
||||
hass.data[DOMAIN][DATA_LISTENER] = async_track_time_interval(
|
||||
hass, refresh, DEFAULT_SCAN_INTERVAL)
|
||||
hass.data[DOMAIN][DATA_LISTENER][
|
||||
config_entry.entry_id] = async_track_time_interval(
|
||||
hass, refresh, DEFAULT_SCAN_INTERVAL)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass, config_entry):
|
||||
"""Unload an OpenUV config entry."""
|
||||
hass.data[DOMAIN][DATA_CLIENT].pop(config_entry.entry_id)
|
||||
|
||||
remove_listener = hass.data[DOMAIN][DATA_LISTENER].pop(
|
||||
config_entry.entry_id)
|
||||
remove_listener()
|
||||
|
||||
await hass.config_entries.async_forward_entry_unload(
|
||||
config_entry, 'sensor')
|
||||
|
||||
return True
|
||||
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
"""Config flow to configure the IQVIA component."""
|
||||
|
||||
from collections import OrderedDict
|
||||
import voluptuous as vol
|
||||
|
||||
from pyiqvia import Client
|
||||
from pyiqvia.errors import IQVIAError
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import aiohttp_client
|
||||
|
||||
from .const import CONF_ZIP_CODE, DOMAIN
|
||||
|
||||
|
||||
@callback
|
||||
def configured_instances(hass):
|
||||
"""Return a set of configured IQVIA instances."""
|
||||
return set(
|
||||
entry.data[CONF_ZIP_CODE]
|
||||
for entry in hass.config_entries.async_entries(DOMAIN))
|
||||
|
||||
|
||||
@config_entries.HANDLERS.register(DOMAIN)
|
||||
class IQVIAFlowHandler(config_entries.ConfigFlow):
|
||||
"""Handle an IQVIA config flow."""
|
||||
|
||||
VERSION = 1
|
||||
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the config flow."""
|
||||
self.data_schema = OrderedDict()
|
||||
self.data_schema[vol.Required(CONF_ZIP_CODE)] = str
|
||||
|
||||
async def _show_form(self, errors=None):
|
||||
"""Show the form to the user."""
|
||||
return self.async_show_form(
|
||||
step_id='user',
|
||||
data_schema=vol.Schema(self.data_schema),
|
||||
errors=errors if errors else {},
|
||||
)
|
||||
|
||||
async def async_step_import(self, import_config):
|
||||
"""Import a config entry from configuration.yaml."""
|
||||
return await self.async_step_user(import_config)
|
||||
|
||||
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()
|
||||
|
||||
if user_input[CONF_ZIP_CODE] in configured_instances(self.hass):
|
||||
return await self._show_form({CONF_ZIP_CODE: 'identifier_exists'})
|
||||
|
||||
websession = aiohttp_client.async_get_clientsession(self.hass)
|
||||
client = Client(user_input[CONF_ZIP_CODE], websession)
|
||||
|
||||
try:
|
||||
await client.allergens.current()
|
||||
except IQVIAError:
|
||||
return await self._show_form({CONF_ZIP_CODE: 'invalid_zip_code'})
|
||||
|
||||
return self.async_create_entry(
|
||||
title=user_input[CONF_ZIP_CODE], data=user_input)
|
|
@ -1,6 +1,8 @@
|
|||
"""Define IQVIA constants."""
|
||||
DOMAIN = 'iqvia'
|
||||
|
||||
CONF_ZIP_CODE = 'zip_code'
|
||||
|
||||
DATA_CLIENT = 'client'
|
||||
DATA_LISTENER = 'listener'
|
||||
|
||||
|
|
|
@ -54,8 +54,13 @@ TREND_SUBSIDING = 'Subsiding'
|
|||
|
||||
async def async_setup_platform(
|
||||
hass, config, async_add_entities, discovery_info=None):
|
||||
"""Configure the platform and add the sensors."""
|
||||
iqvia = hass.data[DOMAIN][DATA_CLIENT]
|
||||
"""Set up IQVIA sensors based on the old way."""
|
||||
pass
|
||||
|
||||
|
||||
async def async_setup_entry(hass, entry, async_add_entities):
|
||||
"""Set up IQVIA sensors based on a config entry."""
|
||||
iqvia = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id]
|
||||
|
||||
sensor_class_mapping = {
|
||||
TYPE_ALLERGY_FORECAST: ForecastSensor,
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"config": {
|
||||
"title": "IQVIA",
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "IQVIA",
|
||||
"description": "Fill out your U.S. or Canadian ZIP code.",
|
||||
"data": {
|
||||
"zip_code": "ZIP Code"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"identifier_exists": "ZIP code already registered",
|
||||
"invalid_zip_code": "ZIP code is invalid"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -160,6 +160,7 @@ FLOWS = [
|
|||
'ifttt',
|
||||
'ios',
|
||||
'ipma',
|
||||
'iqvia',
|
||||
'lifx',
|
||||
'locative',
|
||||
'logi_circle',
|
||||
|
|
|
@ -234,6 +234,9 @@ pyheos==0.5.2
|
|||
# homeassistant.components.homematic
|
||||
pyhomematic==0.1.58
|
||||
|
||||
# homeassistant.components.iqvia
|
||||
pyiqvia==0.2.0
|
||||
|
||||
# homeassistant.components.litejet
|
||||
pylitejet==0.1
|
||||
|
||||
|
|
|
@ -103,6 +103,7 @@ TEST_REQUIREMENTS = (
|
|||
'pydispatcher',
|
||||
'pyheos',
|
||||
'pyhomematic',
|
||||
'pyiqvia',
|
||||
'pylitejet',
|
||||
'pymonoprice',
|
||||
'pynx584',
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
"""Define tests for IQVIA."""
|
|
@ -0,0 +1,97 @@
|
|||
"""Define tests for the IQVIA config flow."""
|
||||
from pyiqvia.errors import IQVIAError
|
||||
import pytest
|
||||
|
||||
from homeassistant import data_entry_flow
|
||||
from homeassistant.components.iqvia import CONF_ZIP_CODE, DOMAIN, config_flow
|
||||
|
||||
from tests.common import MockConfigEntry, MockDependency, mock_coro
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def allergens_current_response():
|
||||
"""Define a fixture for a successful allergens.current response."""
|
||||
return mock_coro()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_pyiqvia(allergens_current_response):
|
||||
"""Mock the pyiqvia library."""
|
||||
with MockDependency('pyiqvia') as mock_pyiqvia_:
|
||||
mock_pyiqvia_.Client().allergens.current.return_value = (
|
||||
allergens_current_response)
|
||||
yield mock_pyiqvia_
|
||||
|
||||
|
||||
async def test_duplicate_error(hass):
|
||||
"""Test that errors are shown when duplicates are added."""
|
||||
conf = {
|
||||
CONF_ZIP_CODE: '12345',
|
||||
}
|
||||
|
||||
MockConfigEntry(domain=DOMAIN, data=conf).add_to_hass(hass)
|
||||
flow = config_flow.IQVIAFlowHandler()
|
||||
flow.hass = hass
|
||||
|
||||
result = await flow.async_step_user(user_input=conf)
|
||||
assert result['errors'] == {CONF_ZIP_CODE: 'identifier_exists'}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'allergens_current_response', [mock_coro(exception=IQVIAError)])
|
||||
async def test_invalid_zip_code(hass, mock_pyiqvia):
|
||||
"""Test that an invalid ZIP code key throws an error."""
|
||||
conf = {
|
||||
CONF_ZIP_CODE: 'abcde',
|
||||
}
|
||||
|
||||
flow = config_flow.IQVIAFlowHandler()
|
||||
flow.hass = hass
|
||||
|
||||
result = await flow.async_step_user(user_input=conf)
|
||||
assert result['errors'] == {CONF_ZIP_CODE: 'invalid_zip_code'}
|
||||
|
||||
|
||||
async def test_show_form(hass):
|
||||
"""Test that the form is served with no input."""
|
||||
flow = config_flow.IQVIAFlowHandler()
|
||||
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_pyiqvia):
|
||||
"""Test that the import step works."""
|
||||
conf = {
|
||||
CONF_ZIP_CODE: '12345',
|
||||
}
|
||||
|
||||
flow = config_flow.IQVIAFlowHandler()
|
||||
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'] == '12345'
|
||||
assert result['data'] == {
|
||||
CONF_ZIP_CODE: '12345',
|
||||
}
|
||||
|
||||
|
||||
async def test_step_user(hass, mock_pyiqvia):
|
||||
"""Test that the user step works."""
|
||||
conf = {
|
||||
CONF_ZIP_CODE: '12345',
|
||||
}
|
||||
|
||||
flow = config_flow.IQVIAFlowHandler()
|
||||
flow.hass = hass
|
||||
|
||||
result = await flow.async_step_user(user_input=conf)
|
||||
assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result['title'] == '12345'
|
||||
assert result['data'] == {
|
||||
CONF_ZIP_CODE: '12345',
|
||||
}
|
Loading…
Reference in New Issue