Add config entry for IQVIA (#23765)

* Add config entry for IQVIA

* Updated tests and requirements

* Removed unnecessary dependency

* Fixed tests

* Reverted unintended change
pull/23784/head
Aaron Bach 2019-05-09 10:11:51 -06:00 committed by Paulus Schoutsen
parent 4004867eda
commit 45adb5c9c7
11 changed files with 258 additions and 17 deletions

View File

@ -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"
}
}

View File

@ -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

View File

@ -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)

View File

@ -1,6 +1,8 @@
"""Define IQVIA constants."""
DOMAIN = 'iqvia'
CONF_ZIP_CODE = 'zip_code'
DATA_CLIENT = 'client'
DATA_LISTENER = 'listener'

View File

@ -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,

View File

@ -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"
}
}
}

View File

@ -160,6 +160,7 @@ FLOWS = [
'ifttt',
'ios',
'ipma',
'iqvia',
'lifx',
'locative',
'logi_circle',

View File

@ -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

View File

@ -103,6 +103,7 @@ TEST_REQUIREMENTS = (
'pydispatcher',
'pyheos',
'pyhomematic',
'pyiqvia',
'pylitejet',
'pymonoprice',
'pynx584',

View File

@ -0,0 +1 @@
"""Define tests for IQVIA."""

View File

@ -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',
}