Add options flow for Recollect Waste (#44234)
* Add options flow for Recollect Waste * Add test * Typing * Typing * Typing AGAIN * Add missing type hints * Code review * Code review * Don't need to block until done
This commit is contained in:
parent
fbc695e5cf
commit
60ecc8282c
6 changed files with 121 additions and 11 deletions
|
@ -14,6 +14,8 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
|
||||||
|
|
||||||
from .const import CONF_PLACE_ID, CONF_SERVICE_ID, DATA_COORDINATOR, DOMAIN, LOGGER
|
from .const import CONF_PLACE_ID, CONF_SERVICE_ID, DATA_COORDINATOR, DOMAIN, LOGGER
|
||||||
|
|
||||||
|
DATA_LISTENER = "listener"
|
||||||
|
|
||||||
DEFAULT_NAME = "recollect_waste"
|
DEFAULT_NAME = "recollect_waste"
|
||||||
DEFAULT_UPDATE_INTERVAL = timedelta(days=1)
|
DEFAULT_UPDATE_INTERVAL = timedelta(days=1)
|
||||||
|
|
||||||
|
@ -22,7 +24,7 @@ PLATFORMS = ["sensor"]
|
||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config: dict) -> bool:
|
async def async_setup(hass: HomeAssistant, config: dict) -> bool:
|
||||||
"""Set up the RainMachine component."""
|
"""Set up the RainMachine component."""
|
||||||
hass.data[DOMAIN] = {DATA_COORDINATOR: {}}
|
hass.data[DOMAIN] = {DATA_COORDINATOR: {}, DATA_LISTENER: {}}
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@ -64,9 +66,18 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
hass.config_entries.async_forward_entry_setup(entry, component)
|
hass.config_entries.async_forward_entry_setup(entry, component)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
hass.data[DOMAIN][DATA_LISTENER][entry.entry_id] = entry.add_update_listener(
|
||||||
|
async_reload_entry
|
||||||
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
|
"""Handle an options update."""
|
||||||
|
await hass.config_entries.async_reload(entry.entry_id)
|
||||||
|
|
||||||
|
|
||||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Unload an RainMachine config entry."""
|
"""Unload an RainMachine config entry."""
|
||||||
unload_ok = all(
|
unload_ok = all(
|
||||||
|
@ -79,5 +90,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
)
|
)
|
||||||
if unload_ok:
|
if unload_ok:
|
||||||
hass.data[DOMAIN][DATA_COORDINATOR].pop(entry.entry_id)
|
hass.data[DOMAIN][DATA_COORDINATOR].pop(entry.entry_id)
|
||||||
|
cancel_listener = hass.data[DOMAIN][DATA_LISTENER].pop(entry.entry_id)
|
||||||
|
cancel_listener()
|
||||||
|
|
||||||
return unload_ok
|
return unload_ok
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
"""Config flow for ReCollect Waste integration."""
|
"""Config flow for ReCollect Waste integration."""
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from aiorecollect.client import Client
|
from aiorecollect.client import Client
|
||||||
from aiorecollect.errors import RecollectError
|
from aiorecollect.errors import RecollectError
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.const import CONF_FRIENDLY_NAME
|
||||||
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers import aiohttp_client
|
from homeassistant.helpers import aiohttp_client
|
||||||
|
|
||||||
from .const import ( # pylint:disable=unused-import
|
from .const import ( # pylint:disable=unused-import
|
||||||
|
@ -24,6 +28,14 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
VERSION = 1
|
VERSION = 1
|
||||||
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
|
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@callback
|
||||||
|
def async_get_options_flow(
|
||||||
|
config_entry: config_entries.ConfigEntry,
|
||||||
|
) -> config_entries.OptionsFlow:
|
||||||
|
"""Define the config flow to handle options."""
|
||||||
|
return RecollectWasteOptionsFlowHandler(config_entry)
|
||||||
|
|
||||||
async def async_step_import(self, import_config: dict = None) -> dict:
|
async def async_step_import(self, import_config: dict = None) -> dict:
|
||||||
"""Handle configuration via YAML import."""
|
"""Handle configuration via YAML import."""
|
||||||
return await self.async_step_user(import_config)
|
return await self.async_step_user(import_config)
|
||||||
|
@ -62,3 +74,28 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
CONF_SERVICE_ID: user_input[CONF_SERVICE_ID],
|
CONF_SERVICE_ID: user_input[CONF_SERVICE_ID],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class RecollectWasteOptionsFlowHandler(config_entries.OptionsFlow):
|
||||||
|
"""Handle a Recollect Waste options flow."""
|
||||||
|
|
||||||
|
def __init__(self, entry: config_entries.ConfigEntry):
|
||||||
|
"""Initialize."""
|
||||||
|
self._entry = entry
|
||||||
|
|
||||||
|
async def async_step_init(self, user_input: Optional[dict] = None):
|
||||||
|
"""Manage the options."""
|
||||||
|
if user_input is not None:
|
||||||
|
return self.async_create_entry(title="", data=user_input)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="init",
|
||||||
|
data_schema=vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Optional(
|
||||||
|
CONF_FRIENDLY_NAME,
|
||||||
|
default=self._entry.options.get(CONF_FRIENDLY_NAME),
|
||||||
|
): bool
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
"""Support for ReCollect Waste sensors."""
|
"""Support for ReCollect Waste sensors."""
|
||||||
from typing import Callable
|
from typing import Callable, List
|
||||||
|
|
||||||
|
from aiorecollect.client import PickupType
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||||
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||||
from homeassistant.const import ATTR_ATTRIBUTION
|
from homeassistant.const import ATTR_ATTRIBUTION, CONF_FRIENDLY_NAME
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.helpers.update_coordinator import (
|
from homeassistant.helpers.update_coordinator import (
|
||||||
|
@ -35,13 +36,26 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_get_pickup_type_names(
|
||||||
|
entry: ConfigEntry, pickup_types: List[PickupType]
|
||||||
|
) -> List[str]:
|
||||||
|
"""Return proper pickup type names from their associated objects."""
|
||||||
|
return [
|
||||||
|
t.friendly_name
|
||||||
|
if entry.options.get(CONF_FRIENDLY_NAME) and t.friendly_name
|
||||||
|
else t.name
|
||||||
|
for t in pickup_types
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(
|
async def async_setup_platform(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config: dict,
|
config: dict,
|
||||||
async_add_entities: Callable,
|
async_add_entities: Callable,
|
||||||
discovery_info: dict = None,
|
discovery_info: dict = None,
|
||||||
):
|
):
|
||||||
"""Import Awair configuration from YAML."""
|
"""Import Recollect Waste configuration from YAML."""
|
||||||
LOGGER.warning(
|
LOGGER.warning(
|
||||||
"Loading ReCollect Waste via platform setup is deprecated. "
|
"Loading ReCollect Waste via platform setup is deprecated. "
|
||||||
"Please remove it from your configuration."
|
"Please remove it from your configuration."
|
||||||
|
@ -70,8 +84,7 @@ class ReCollectWasteSensor(CoordinatorEntity):
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
self._attributes = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION}
|
self._attributes = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION}
|
||||||
self._place_id = entry.data[CONF_PLACE_ID]
|
self._entry = entry
|
||||||
self._service_id = entry.data[CONF_SERVICE_ID]
|
|
||||||
self._state = None
|
self._state = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -97,7 +110,7 @@ class ReCollectWasteSensor(CoordinatorEntity):
|
||||||
@property
|
@property
|
||||||
def unique_id(self) -> str:
|
def unique_id(self) -> str:
|
||||||
"""Return a unique ID."""
|
"""Return a unique ID."""
|
||||||
return f"{self._place_id}{self._service_id}"
|
return f"{self._entry.data[CONF_PLACE_ID]}{self._entry.data[CONF_SERVICE_ID]}"
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _handle_coordinator_update(self) -> None:
|
def _handle_coordinator_update(self) -> None:
|
||||||
|
@ -120,11 +133,13 @@ class ReCollectWasteSensor(CoordinatorEntity):
|
||||||
self._state = pickup_event.date
|
self._state = pickup_event.date
|
||||||
self._attributes.update(
|
self._attributes.update(
|
||||||
{
|
{
|
||||||
ATTR_PICKUP_TYPES: [t.name for t in pickup_event.pickup_types],
|
ATTR_PICKUP_TYPES: async_get_pickup_type_names(
|
||||||
|
self._entry, pickup_event.pickup_types
|
||||||
|
),
|
||||||
ATTR_AREA_NAME: pickup_event.area_name,
|
ATTR_AREA_NAME: pickup_event.area_name,
|
||||||
ATTR_NEXT_PICKUP_TYPES: [
|
ATTR_NEXT_PICKUP_TYPES: async_get_pickup_type_names(
|
||||||
t.name for t in next_pickup_event.pickup_types
|
self._entry, next_pickup_event.pickup_types
|
||||||
],
|
),
|
||||||
ATTR_NEXT_PICKUP_DATE: next_date,
|
ATTR_NEXT_PICKUP_DATE: next_date,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -14,5 +14,15 @@
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"title": "Configure Recollect Waste",
|
||||||
|
"data": {
|
||||||
|
"friendly_name": "Use friendly names for pickup types (when possible)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,5 +14,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"data": {
|
||||||
|
"friendly_name": "Use friendly names for pickup types (when possible)"
|
||||||
|
},
|
||||||
|
"title": "Configure Recollect Waste"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -8,6 +8,7 @@ from homeassistant.components.recollect_waste import (
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER
|
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER
|
||||||
|
from homeassistant.const import CONF_FRIENDLY_NAME
|
||||||
|
|
||||||
from tests.async_mock import patch
|
from tests.async_mock import patch
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
@ -45,6 +46,30 @@ async def test_invalid_place_or_service_id(hass):
|
||||||
assert result["errors"] == {"base": "invalid_place_or_service_id"}
|
assert result["errors"] == {"base": "invalid_place_or_service_id"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_options_flow(hass):
|
||||||
|
"""Test config flow options."""
|
||||||
|
conf = {CONF_PLACE_ID: "12345", CONF_SERVICE_ID: "12345"}
|
||||||
|
|
||||||
|
config_entry = MockConfigEntry(domain=DOMAIN, unique_id="12345, 12345", data=conf)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.recollect_waste.async_setup_entry", return_value=True
|
||||||
|
):
|
||||||
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
result = await hass.config_entries.options.async_init(config_entry.entry_id)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result["step_id"] == "init"
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.async_configure(
|
||||||
|
result["flow_id"], user_input={CONF_FRIENDLY_NAME: True}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
assert config_entry.options == {CONF_FRIENDLY_NAME: True}
|
||||||
|
|
||||||
|
|
||||||
async def test_show_form(hass):
|
async def test_show_form(hass):
|
||||||
"""Test that the form is served with no input."""
|
"""Test that the form is served with no input."""
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue