mirror of
https://github.com/home-assistant/core.git
synced 2025-04-25 09:47:52 +00:00
Add Avri config flow (#34288)
* Add config flow to Avri integration * Add config flow validation * Update .coveragerc * Start adding config flow tests * Fix failing test * Fix pylint * Update homeassistant/components/avri/config_flow.py Co-authored-by: J. Nick Koston <nick@koston.org> * Update homeassistant/components/avri/config_flow.py Co-authored-by: J. Nick Koston <nick@koston.org> * Fix import order * Code review comments * Update homeassistant/components/avri/sensor.py Co-authored-by: J. Nick Koston <nick@koston.org> * Remove device information Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
14f5cab71d
commit
d73a4e1ed5
@ -68,6 +68,7 @@ omit =
|
||||
homeassistant/components/aurora_abb_powerone/sensor.py
|
||||
homeassistant/components/avea/light.py
|
||||
homeassistant/components/avion/light.py
|
||||
homeassistant/components/avri/const.py
|
||||
homeassistant/components/avri/sensor.py
|
||||
homeassistant/components/azure_event_hub/*
|
||||
homeassistant/components/azure_service_bus/*
|
||||
|
24
homeassistant/components/avri/.translations/en.json
Normal file
24
homeassistant/components/avri/.translations/en.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "This address is already configured."
|
||||
},
|
||||
"error": {
|
||||
"invalid_country_code": "Unknown 2 letter country code.",
|
||||
"invalid_house_number": "Invalid house number."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"country_code": "2 Letter country code",
|
||||
"house_number": "House number",
|
||||
"house_number_extension": "House number extension",
|
||||
"zip_code": "Zip code"
|
||||
},
|
||||
"description": "Enter your address",
|
||||
"title": "Avri"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Avri"
|
||||
}
|
24
homeassistant/components/avri/.translations/nl.json
Normal file
24
homeassistant/components/avri/.translations/nl.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Dit adres is reeds geconfigureerd."
|
||||
},
|
||||
"error": {
|
||||
"invalid_country_code": "Onbekende landcode",
|
||||
"invalid_house_number": "Ongeldig huisnummer."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"country_code": "2 Letter landcode",
|
||||
"house_number": "Huisnummer",
|
||||
"house_number_extension": "Huisnummer toevoeging",
|
||||
"zip_code": "Postcode"
|
||||
},
|
||||
"description": "Vul je adres in.",
|
||||
"title": "Avri"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Avri"
|
||||
}
|
@ -1 +1,63 @@
|
||||
"""The avri component."""
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from avri.api import Avri
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import (
|
||||
CONF_COUNTRY_CODE,
|
||||
CONF_HOUSE_NUMBER,
|
||||
CONF_HOUSE_NUMBER_EXTENSION,
|
||||
CONF_ZIP_CODE,
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORMS = ["sensor"]
|
||||
SCAN_INTERVAL = timedelta(hours=4)
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: dict):
|
||||
"""Set up the Avri component."""
|
||||
hass.data[DOMAIN] = {}
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
"""Set up Avri from a config entry."""
|
||||
client = Avri(
|
||||
postal_code=entry.data[CONF_ZIP_CODE],
|
||||
house_nr=entry.data[CONF_HOUSE_NUMBER],
|
||||
house_nr_extension=entry.data.get(CONF_HOUSE_NUMBER_EXTENSION),
|
||||
country_code=entry.data[CONF_COUNTRY_CODE],
|
||||
)
|
||||
|
||||
hass.data[DOMAIN][entry.entry_id] = client
|
||||
|
||||
for component in PLATFORMS:
|
||||
hass.async_create_task(
|
||||
hass.config_entries.async_forward_entry_setup(entry, component)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
"""Unload a config entry."""
|
||||
unload_ok = all(
|
||||
await asyncio.gather(
|
||||
*[
|
||||
hass.config_entries.async_forward_entry_unload(entry, component)
|
||||
for component in PLATFORMS
|
||||
]
|
||||
)
|
||||
)
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
|
74
homeassistant/components/avri/config_flow.py
Normal file
74
homeassistant/components/avri/config_flow.py
Normal file
@ -0,0 +1,74 @@
|
||||
"""Config flow for Avri component."""
|
||||
import pycountry
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_ID
|
||||
|
||||
from .const import (
|
||||
CONF_COUNTRY_CODE,
|
||||
CONF_HOUSE_NUMBER,
|
||||
CONF_HOUSE_NUMBER_EXTENSION,
|
||||
CONF_ZIP_CODE,
|
||||
DEFAULT_COUNTRY_CODE,
|
||||
)
|
||||
from .const import DOMAIN # pylint:disable=unused-import
|
||||
|
||||
DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_ZIP_CODE): str,
|
||||
vol.Required(CONF_HOUSE_NUMBER): int,
|
||||
vol.Optional(CONF_HOUSE_NUMBER_EXTENSION): str,
|
||||
vol.Optional(CONF_COUNTRY_CODE, default=DEFAULT_COUNTRY_CODE): str,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class AvriConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Avri config flow."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
async def _show_setup_form(self, errors=None):
|
||||
"""Show the setup form to the user."""
|
||||
return self.async_show_form(
|
||||
step_id="user", data_schema=DATA_SCHEMA, errors=errors or {},
|
||||
)
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle the initial step."""
|
||||
if user_input is None:
|
||||
return await self._show_setup_form()
|
||||
|
||||
zip_code = user_input[CONF_ZIP_CODE].replace(" ", "").upper()
|
||||
|
||||
errors = {}
|
||||
if user_input[CONF_HOUSE_NUMBER] <= 0:
|
||||
errors[CONF_HOUSE_NUMBER] = "invalid_house_number"
|
||||
return await self._show_setup_form(errors)
|
||||
if not pycountry.countries.get(alpha_2=user_input[CONF_COUNTRY_CODE]):
|
||||
errors[CONF_COUNTRY_CODE] = "invalid_country_code"
|
||||
return await self._show_setup_form(errors)
|
||||
|
||||
unique_id = (
|
||||
f"{zip_code}"
|
||||
f" "
|
||||
f"{user_input[CONF_HOUSE_NUMBER]}"
|
||||
f'{user_input.get(CONF_HOUSE_NUMBER_EXTENSION, "")}'
|
||||
)
|
||||
|
||||
await self.async_set_unique_id(unique_id)
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
return self.async_create_entry(
|
||||
title=unique_id,
|
||||
data={
|
||||
CONF_ID: unique_id,
|
||||
CONF_ZIP_CODE: zip_code,
|
||||
CONF_HOUSE_NUMBER: user_input[CONF_HOUSE_NUMBER],
|
||||
CONF_HOUSE_NUMBER_EXTENSION: user_input.get(
|
||||
CONF_HOUSE_NUMBER_EXTENSION, ""
|
||||
),
|
||||
CONF_COUNTRY_CODE: user_input[CONF_COUNTRY_CODE],
|
||||
},
|
||||
)
|
8
homeassistant/components/avri/const.py
Normal file
8
homeassistant/components/avri/const.py
Normal file
@ -0,0 +1,8 @@
|
||||
"""Constants for the Avri integration."""
|
||||
CONF_COUNTRY_CODE = "country_code"
|
||||
CONF_ZIP_CODE = "zip_code"
|
||||
CONF_HOUSE_NUMBER = "house_number"
|
||||
CONF_HOUSE_NUMBER_EXTENSION = "house_number_extension"
|
||||
DOMAIN = "avri"
|
||||
ICON = "mdi:trash-can-outline"
|
||||
DEFAULT_COUNTRY_CODE = "NL"
|
@ -2,6 +2,12 @@
|
||||
"domain": "avri",
|
||||
"name": "Avri",
|
||||
"documentation": "https://www.home-assistant.io/integrations/avri",
|
||||
"requirements": ["avri-api==0.1.7"],
|
||||
"codeowners": ["@timvancann"]
|
||||
}
|
||||
"requirements": [
|
||||
"avri-api==0.1.7",
|
||||
"pycountry==19.8.18"
|
||||
],
|
||||
"codeowners": [
|
||||
"@timvancann"
|
||||
],
|
||||
"config_flow": true
|
||||
}
|
@ -1,45 +1,25 @@
|
||||
"""Support for Avri waste curbside collection pickup."""
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from avri.api import Avri, AvriException
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||
from homeassistant.const import CONF_NAME
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_ID, DEVICE_CLASS_TIMESTAMP
|
||||
from homeassistant.exceptions import PlatformNotReady
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
|
||||
from .const import DOMAIN, ICON
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
CONF_COUNTRY_CODE = "country_code"
|
||||
CONF_ZIP_CODE = "zip_code"
|
||||
CONF_HOUSE_NUMBER = "house_number"
|
||||
CONF_HOUSE_NUMBER_EXTENSION = "house_number_extension"
|
||||
DEFAULT_NAME = "avri"
|
||||
ICON = "mdi:trash-can-outline"
|
||||
SCAN_INTERVAL = timedelta(hours=4)
|
||||
DEFAULT_COUNTRY_CODE = "NL"
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_ZIP_CODE): cv.string,
|
||||
vol.Required(CONF_HOUSE_NUMBER): cv.positive_int,
|
||||
vol.Optional(CONF_HOUSE_NUMBER_EXTENSION): cv.string,
|
||||
vol.Optional(CONF_COUNTRY_CODE, default=DEFAULT_COUNTRY_CODE): cv.string,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistantType, entry: ConfigEntry, async_add_entities
|
||||
) -> None:
|
||||
"""Set up the Avri Waste platform."""
|
||||
client = Avri(
|
||||
postal_code=config[CONF_ZIP_CODE],
|
||||
house_nr=config[CONF_HOUSE_NUMBER],
|
||||
house_nr_extension=config.get(CONF_HOUSE_NUMBER_EXTENSION),
|
||||
country_code=config[CONF_COUNTRY_CODE],
|
||||
)
|
||||
client = hass.data[DOMAIN][entry.entry_id]
|
||||
integration_id = entry.data[CONF_ID]
|
||||
|
||||
try:
|
||||
each_upcoming = client.upcoming_of_each()
|
||||
@ -47,22 +27,23 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
raise PlatformNotReady from ex
|
||||
else:
|
||||
entities = [
|
||||
AvriWasteUpcoming(config[CONF_NAME], client, upcoming.name)
|
||||
AvriWasteUpcoming(client, upcoming.name, integration_id)
|
||||
for upcoming in each_upcoming
|
||||
]
|
||||
add_entities(entities, True)
|
||||
async_add_entities(entities, True)
|
||||
|
||||
|
||||
class AvriWasteUpcoming(Entity):
|
||||
"""Avri Waste Sensor."""
|
||||
|
||||
def __init__(self, name: str, client: Avri, waste_type: str):
|
||||
def __init__(self, client: Avri, waste_type: str, integration_id: str):
|
||||
"""Initialize the sensor."""
|
||||
self._waste_type = waste_type
|
||||
self._name = f"{name}_{self._waste_type}"
|
||||
self._name = f"{self._waste_type}".title()
|
||||
self._state = None
|
||||
self._client = client
|
||||
self._state_available = False
|
||||
self._integration_id = integration_id
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@ -72,13 +53,7 @@ class AvriWasteUpcoming(Entity):
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return a unique ID."""
|
||||
return (
|
||||
f"{self._waste_type}"
|
||||
f"-{self._client.country_code}"
|
||||
f"-{self._client.postal_code}"
|
||||
f"-{self._client.house_nr}"
|
||||
f"-{self._client.house_nr_extension}"
|
||||
)
|
||||
return (f"{self._integration_id}" f"-{self._waste_type}").replace(" ", "")
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
@ -90,13 +65,21 @@ class AvriWasteUpcoming(Entity):
|
||||
"""Return True if entity is available."""
|
||||
return self._state_available
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the device class of the sensor."""
|
||||
return DEVICE_CLASS_TIMESTAMP
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Icon to use in the frontend."""
|
||||
return ICON
|
||||
|
||||
def update(self):
|
||||
"""Update device state."""
|
||||
async def async_update(self):
|
||||
"""Update the data."""
|
||||
if not self.enabled:
|
||||
return
|
||||
|
||||
try:
|
||||
pickup_events = self._client.upcoming_of_each()
|
||||
except AvriException as ex:
|
||||
|
24
homeassistant/components/avri/strings.json
Normal file
24
homeassistant/components/avri/strings.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"title": "Avri",
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "This address is already configured."
|
||||
},
|
||||
"error": {
|
||||
"invalid_house_number": "Invalid house number.",
|
||||
"invalid_country_code": "Unknown 2 letter country code."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"zip_code": "Zip code",
|
||||
"house_number": "House number",
|
||||
"house_number_extension": "House number extension",
|
||||
"country_code": "2 Letter country code"
|
||||
},
|
||||
"description": "Enter your address",
|
||||
"title": "Avri"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@ FLOWS = [
|
||||
"ambient_station",
|
||||
"atag",
|
||||
"august",
|
||||
"avri",
|
||||
"axis",
|
||||
"blebox",
|
||||
"blink",
|
||||
|
@ -1259,6 +1259,9 @@ pycomfoconnect==0.3
|
||||
# homeassistant.components.coolmaster
|
||||
pycoolmasternet==0.0.4
|
||||
|
||||
# homeassistant.components.avri
|
||||
pycountry==19.8.18
|
||||
|
||||
# homeassistant.components.microsoft
|
||||
pycsspeechtts==1.0.3
|
||||
|
||||
|
@ -146,6 +146,9 @@ async-upnp-client==0.14.13
|
||||
# homeassistant.components.stream
|
||||
av==8.0.2
|
||||
|
||||
# homeassistant.components.avri
|
||||
avri-api==0.1.7
|
||||
|
||||
# homeassistant.components.axis
|
||||
axis==29
|
||||
|
||||
@ -544,6 +547,9 @@ pychromecast==6.0.0
|
||||
# homeassistant.components.coolmaster
|
||||
pycoolmasternet==0.0.4
|
||||
|
||||
# homeassistant.components.avri
|
||||
pycountry==19.8.18
|
||||
|
||||
# homeassistant.components.daikin
|
||||
pydaikin==2.1.1
|
||||
|
||||
|
@ -8,4 +8,4 @@ cd "$(dirname "$0")/.."
|
||||
script/bootstrap
|
||||
|
||||
pre-commit install
|
||||
pip3 install -e .
|
||||
pip install -e .
|
||||
|
1
tests/components/avri/__init__.py
Normal file
1
tests/components/avri/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Tests for the Avri integration."""
|
80
tests/components/avri/test_config_flow.py
Normal file
80
tests/components/avri/test_config_flow.py
Normal file
@ -0,0 +1,80 @@
|
||||
"""Test the Avri config flow."""
|
||||
from asynctest import patch
|
||||
|
||||
from homeassistant import config_entries, setup
|
||||
from homeassistant.components.avri.const import DOMAIN
|
||||
|
||||
|
||||
async def test_form(hass):
|
||||
"""Test we get the form."""
|
||||
await setup.async_setup_component(hass, "avri", {})
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == "form"
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.avri.async_setup_entry", return_value=True,
|
||||
) as mock_setup_entry:
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
"zip_code": "1234AB",
|
||||
"house_number": 42,
|
||||
"house_number_extension": "",
|
||||
"country_code": "NL",
|
||||
},
|
||||
)
|
||||
|
||||
assert result2["type"] == "create_entry"
|
||||
assert result2["title"] == "1234AB 42"
|
||||
assert result2["data"] == {
|
||||
"id": "1234AB 42",
|
||||
"zip_code": "1234AB",
|
||||
"house_number": 42,
|
||||
"house_number_extension": "",
|
||||
"country_code": "NL",
|
||||
}
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_form_invalid_house_number(hass):
|
||||
"""Test we handle invalid house number."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
"zip_code": "1234AB",
|
||||
"house_number": -1,
|
||||
"house_number_extension": "",
|
||||
"country_code": "NL",
|
||||
},
|
||||
)
|
||||
|
||||
assert result2["type"] == "form"
|
||||
assert result2["errors"] == {"house_number": "invalid_house_number"}
|
||||
|
||||
|
||||
async def test_form_invalid_country_code(hass):
|
||||
"""Test we handle invalid county code."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
"zip_code": "1234AB",
|
||||
"house_number": 42,
|
||||
"house_number_extension": "",
|
||||
"country_code": "foo",
|
||||
},
|
||||
)
|
||||
|
||||
assert result2["type"] == "form"
|
||||
assert result2["errors"] == {"country_code": "invalid_country_code"}
|
Loading…
x
Reference in New Issue
Block a user