mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +00:00
Add config entry for IQVIA (#23765)
* Add config entry for IQVIA * Updated tests and requirements * Removed unnecessary dependency * Fixed tests * Reverted unintended change
This commit is contained in:
parent
4004867eda
commit
45adb5c9c7
18
homeassistant/components/iqvia/.translations/en.json
Normal file
18
homeassistant/components/iqvia/.translations/en.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
@ -8,28 +8,27 @@ from pyiqvia.errors import IQVIAError, InvalidZipError
|
|||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.config_entries import SOURCE_IMPORT
|
||||||
from homeassistant.const import ATTR_ATTRIBUTION, CONF_MONITORED_CONDITIONS
|
from homeassistant.const import ATTR_ATTRIBUTION, CONF_MONITORED_CONDITIONS
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
||||||
from homeassistant.helpers.discovery import async_load_platform
|
|
||||||
from homeassistant.helpers.dispatcher import (
|
from homeassistant.helpers.dispatcher import (
|
||||||
async_dispatcher_connect, async_dispatcher_send)
|
async_dispatcher_connect, 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 homeassistant.helpers.event import async_track_time_interval
|
||||||
from homeassistant.util.decorator import Registry
|
from homeassistant.util.decorator import Registry
|
||||||
|
|
||||||
|
from .config_flow import configured_instances
|
||||||
from .const import (
|
from .const import (
|
||||||
DATA_CLIENT, DATA_LISTENER, DOMAIN, SENSORS, TOPIC_DATA_UPDATE,
|
CONF_ZIP_CODE, DATA_CLIENT, DATA_LISTENER, DOMAIN, SENSORS,
|
||||||
TYPE_ALLERGY_FORECAST, TYPE_ALLERGY_INDEX, TYPE_ALLERGY_OUTLOOK,
|
TOPIC_DATA_UPDATE, TYPE_ALLERGY_FORECAST, TYPE_ALLERGY_INDEX,
|
||||||
TYPE_ALLERGY_TODAY, TYPE_ALLERGY_TOMORROW, TYPE_ASTHMA_FORECAST,
|
TYPE_ALLERGY_OUTLOOK, TYPE_ALLERGY_TODAY, TYPE_ALLERGY_TOMORROW,
|
||||||
TYPE_ASTHMA_INDEX, TYPE_ASTHMA_TODAY, TYPE_ASTHMA_TOMORROW,
|
TYPE_ASTHMA_FORECAST, TYPE_ASTHMA_INDEX, TYPE_ASTHMA_TODAY,
|
||||||
TYPE_DISEASE_FORECAST, TYPE_DISEASE_INDEX, TYPE_DISEASE_TODAY)
|
TYPE_ASTHMA_TOMORROW, TYPE_DISEASE_FORECAST, TYPE_DISEASE_INDEX,
|
||||||
|
TYPE_DISEASE_TODAY)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
CONF_ZIP_CODE = 'zip_code'
|
|
||||||
|
|
||||||
DATA_CONFIG = 'config'
|
DATA_CONFIG = 'config'
|
||||||
|
|
||||||
DEFAULT_ATTRIBUTION = 'Data provided by IQVIA™'
|
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_CLIENT] = {}
|
||||||
hass.data[DOMAIN][DATA_LISTENER] = {}
|
hass.data[DOMAIN][DATA_LISTENER] = {}
|
||||||
|
|
||||||
|
if DOMAIN not in config:
|
||||||
|
return True
|
||||||
|
|
||||||
conf = config[DOMAIN]
|
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)
|
websession = aiohttp_client.async_get_clientsession(hass)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
iqvia = IQVIAData(
|
iqvia = IQVIAData(
|
||||||
Client(conf[CONF_ZIP_CODE], websession),
|
Client(config_entry.data[CONF_ZIP_CODE], websession),
|
||||||
conf[CONF_MONITORED_CONDITIONS])
|
config_entry.data.get(CONF_MONITORED_CONDITIONS, list(SENSORS)))
|
||||||
await iqvia.async_update()
|
await iqvia.async_update()
|
||||||
except IQVIAError as err:
|
except IQVIAError as err:
|
||||||
_LOGGER.error('Unable to set up IQVIA: %s', err)
|
_LOGGER.error('Unable to set up IQVIA: %s', err)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
hass.data[DOMAIN][DATA_CLIENT] = iqvia
|
hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = iqvia
|
||||||
|
|
||||||
hass.async_create_task(
|
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):
|
async def refresh(event_time):
|
||||||
"""Refresh IQVIA data."""
|
"""Refresh IQVIA data."""
|
||||||
@ -83,8 +98,23 @@ async def async_setup(hass, config):
|
|||||||
await iqvia.async_update()
|
await iqvia.async_update()
|
||||||
async_dispatcher_send(hass, TOPIC_DATA_UPDATE)
|
async_dispatcher_send(hass, TOPIC_DATA_UPDATE)
|
||||||
|
|
||||||
hass.data[DOMAIN][DATA_LISTENER] = async_track_time_interval(
|
hass.data[DOMAIN][DATA_LISTENER][
|
||||||
hass, refresh, DEFAULT_SCAN_INTERVAL)
|
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
|
return True
|
||||||
|
|
||||||
|
65
homeassistant/components/iqvia/config_flow.py
Normal file
65
homeassistant/components/iqvia/config_flow.py
Normal 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)
|
@ -1,6 +1,8 @@
|
|||||||
"""Define IQVIA constants."""
|
"""Define IQVIA constants."""
|
||||||
DOMAIN = 'iqvia'
|
DOMAIN = 'iqvia'
|
||||||
|
|
||||||
|
CONF_ZIP_CODE = 'zip_code'
|
||||||
|
|
||||||
DATA_CLIENT = 'client'
|
DATA_CLIENT = 'client'
|
||||||
DATA_LISTENER = 'listener'
|
DATA_LISTENER = 'listener'
|
||||||
|
|
||||||
|
@ -54,8 +54,13 @@ TREND_SUBSIDING = 'Subsiding'
|
|||||||
|
|
||||||
async def async_setup_platform(
|
async def async_setup_platform(
|
||||||
hass, config, async_add_entities, discovery_info=None):
|
hass, config, async_add_entities, discovery_info=None):
|
||||||
"""Configure the platform and add the sensors."""
|
"""Set up IQVIA sensors based on the old way."""
|
||||||
iqvia = hass.data[DOMAIN][DATA_CLIENT]
|
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 = {
|
sensor_class_mapping = {
|
||||||
TYPE_ALLERGY_FORECAST: ForecastSensor,
|
TYPE_ALLERGY_FORECAST: ForecastSensor,
|
||||||
|
18
homeassistant/components/iqvia/strings.json
Normal file
18
homeassistant/components/iqvia/strings.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -160,6 +160,7 @@ FLOWS = [
|
|||||||
'ifttt',
|
'ifttt',
|
||||||
'ios',
|
'ios',
|
||||||
'ipma',
|
'ipma',
|
||||||
|
'iqvia',
|
||||||
'lifx',
|
'lifx',
|
||||||
'locative',
|
'locative',
|
||||||
'logi_circle',
|
'logi_circle',
|
||||||
|
@ -234,6 +234,9 @@ pyheos==0.5.2
|
|||||||
# homeassistant.components.homematic
|
# homeassistant.components.homematic
|
||||||
pyhomematic==0.1.58
|
pyhomematic==0.1.58
|
||||||
|
|
||||||
|
# homeassistant.components.iqvia
|
||||||
|
pyiqvia==0.2.0
|
||||||
|
|
||||||
# homeassistant.components.litejet
|
# homeassistant.components.litejet
|
||||||
pylitejet==0.1
|
pylitejet==0.1
|
||||||
|
|
||||||
|
@ -103,6 +103,7 @@ TEST_REQUIREMENTS = (
|
|||||||
'pydispatcher',
|
'pydispatcher',
|
||||||
'pyheos',
|
'pyheos',
|
||||||
'pyhomematic',
|
'pyhomematic',
|
||||||
|
'pyiqvia',
|
||||||
'pylitejet',
|
'pylitejet',
|
||||||
'pymonoprice',
|
'pymonoprice',
|
||||||
'pynx584',
|
'pynx584',
|
||||||
|
1
tests/components/iqvia/__init__.py
Normal file
1
tests/components/iqvia/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Define tests for IQVIA."""
|
97
tests/components/iqvia/test_config_flow.py
Normal file
97
tests/components/iqvia/test_config_flow.py
Normal 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',
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user