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:
Aaron Bach 2020-12-19 10:29:37 -07:00 committed by GitHub
parent fbc695e5cf
commit 60ecc8282c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 121 additions and 11 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -14,5 +14,15 @@
} }
} }
} }
},
"options": {
"step": {
"init": {
"data": {
"friendly_name": "Use friendly names for pickup types (when possible)"
},
"title": "Configure Recollect Waste"
}
}
} }
} }

View file

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