mirror of
https://github.com/home-assistant/core.git
synced 2025-07-27 07:07:28 +00:00
Scrape sub config entry
This commit is contained in:
parent
0aa09a2d51
commit
b27ef88be7
@ -5,26 +5,29 @@ from __future__ import annotations
|
|||||||
import asyncio
|
import asyncio
|
||||||
from collections.abc import Coroutine
|
from collections.abc import Coroutine
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.rest import RESOURCE_SCHEMA, create_rest_data_from_config
|
from homeassistant.components.rest import RESOURCE_SCHEMA, create_rest_data_from_config
|
||||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry, ConfigSubentry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_ATTRIBUTE,
|
CONF_ATTRIBUTE,
|
||||||
|
CONF_NAME,
|
||||||
CONF_SCAN_INTERVAL,
|
CONF_SCAN_INTERVAL,
|
||||||
|
CONF_UNIQUE_ID,
|
||||||
CONF_VALUE_TEMPLATE,
|
CONF_VALUE_TEMPLATE,
|
||||||
Platform,
|
Platform,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import (
|
from homeassistant.helpers import (
|
||||||
config_validation as cv,
|
config_validation as cv,
|
||||||
|
device_registry as dr,
|
||||||
discovery,
|
discovery,
|
||||||
entity_registry as er,
|
entity_registry as er,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers.device_registry import DeviceEntry
|
|
||||||
from homeassistant.helpers.trigger_template_entity import (
|
from homeassistant.helpers.trigger_template_entity import (
|
||||||
CONF_AVAILABILITY,
|
CONF_AVAILABILITY,
|
||||||
TEMPLATE_SENSOR_BASE_SCHEMA,
|
TEMPLATE_SENSOR_BASE_SCHEMA,
|
||||||
@ -36,6 +39,8 @@ from .coordinator import ScrapeCoordinator
|
|||||||
|
|
||||||
type ScrapeConfigEntry = ConfigEntry[ScrapeCoordinator]
|
type ScrapeConfigEntry = ConfigEntry[ScrapeCoordinator]
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
SENSOR_SCHEMA = vol.Schema(
|
SENSOR_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
**TEMPLATE_SENSOR_BASE_SCHEMA.schema,
|
**TEMPLATE_SENSOR_BASE_SCHEMA.schema,
|
||||||
@ -116,6 +121,62 @@ async def async_setup_entry(hass: HomeAssistant, entry: ScrapeConfigEntry) -> bo
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_migrate_entry(hass: HomeAssistant, entry: ScrapeConfigEntry) -> bool:
|
||||||
|
"""Migrate old entry."""
|
||||||
|
|
||||||
|
if entry.version > 2:
|
||||||
|
# Don't migrate from future version
|
||||||
|
return False
|
||||||
|
|
||||||
|
if entry.version == 1:
|
||||||
|
old_to_new_sensor_id = {}
|
||||||
|
for sensor in entry.options[SENSOR_DOMAIN]:
|
||||||
|
# Create a new sub config entry per sensor
|
||||||
|
sensor_config = dict(sensor)
|
||||||
|
title = sensor_config.pop(CONF_NAME)
|
||||||
|
old_unique_id = sensor_config.pop(CONF_UNIQUE_ID)
|
||||||
|
new_sub_entry = ConfigSubentry(
|
||||||
|
data=sensor, subentry_type="entity", title=title, unique_id=None
|
||||||
|
)
|
||||||
|
old_to_new_sensor_id[old_unique_id] = new_sub_entry.subentry_id
|
||||||
|
hass.config_entries.async_add_subentry(entry, new_sub_entry)
|
||||||
|
|
||||||
|
# Use the new sub config entry id as the unique id for the sensor entity
|
||||||
|
entity_reg = er.async_get(hass)
|
||||||
|
entities = er.async_entries_for_config_entry(entity_reg, entry.entry_id)
|
||||||
|
for entity in entities:
|
||||||
|
if entity.unique_id in old_to_new_sensor_id:
|
||||||
|
entity_reg.async_update_entity(
|
||||||
|
entity.entity_id,
|
||||||
|
new_unique_id=old_to_new_sensor_id[entity.unique_id],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Use the new sub config entry id as the unique id for the sensor device
|
||||||
|
device_reg = dr.async_get(hass)
|
||||||
|
devices = dr.async_entries_for_config_entry(device_reg, entry.entry_id)
|
||||||
|
for device in devices:
|
||||||
|
for identifier in device.identifiers:
|
||||||
|
device_unique_id = identifier[1]
|
||||||
|
if device_unique_id in old_to_new_sensor_id:
|
||||||
|
device_reg.async_update_device(
|
||||||
|
device.id,
|
||||||
|
add_config_subentry_id=old_to_new_sensor_id[device_unique_id],
|
||||||
|
new_identifiers={
|
||||||
|
(DOMAIN, old_to_new_sensor_id[device_unique_id])
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Remove the sensors as they are now subentries
|
||||||
|
new_config_entry_options = dict(entry.options)
|
||||||
|
new_config_entry_options.pop(SENSOR_DOMAIN)
|
||||||
|
|
||||||
|
hass.config_entries.async_update_entry(
|
||||||
|
entry, version=2, options=new_config_entry_options
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Unload Scrape config entry."""
|
"""Unload Scrape config entry."""
|
||||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||||
@ -127,7 +188,7 @@ async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
|||||||
|
|
||||||
|
|
||||||
async def async_remove_config_entry_device(
|
async def async_remove_config_entry_device(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, device: DeviceEntry
|
hass: HomeAssistant, entry: ConfigEntry, device: dr.DeviceEntry
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Remove Scrape config entry from a device."""
|
"""Remove Scrape config entry from a device."""
|
||||||
entity_registry = er.async_get(hass)
|
entity_registry = er.async_get(hass)
|
||||||
|
@ -2,21 +2,28 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Mapping
|
import logging
|
||||||
from typing import Any, cast
|
from typing import Any
|
||||||
import uuid
|
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import data_entry_flow
|
||||||
from homeassistant.components.rest import create_rest_data_from_config
|
from homeassistant.components.rest import create_rest_data_from_config
|
||||||
from homeassistant.components.rest.data import DEFAULT_TIMEOUT
|
from homeassistant.components.rest.data import DEFAULT_TIMEOUT
|
||||||
from homeassistant.components.rest.schema import DEFAULT_METHOD, METHODS
|
from homeassistant.components.rest.schema import DEFAULT_METHOD, METHODS
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
CONF_STATE_CLASS,
|
CONF_STATE_CLASS,
|
||||||
DOMAIN as SENSOR_DOMAIN,
|
|
||||||
SensorDeviceClass,
|
SensorDeviceClass,
|
||||||
SensorStateClass,
|
SensorStateClass,
|
||||||
)
|
)
|
||||||
|
from homeassistant.config_entries import (
|
||||||
|
ConfigEntry,
|
||||||
|
ConfigFlow,
|
||||||
|
ConfigFlowResult,
|
||||||
|
ConfigSubentryFlow,
|
||||||
|
OptionsFlow,
|
||||||
|
SubentryFlowResult,
|
||||||
|
)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_ATTRIBUTE,
|
CONF_ATTRIBUTE,
|
||||||
CONF_AUTHENTICATION,
|
CONF_AUTHENTICATION,
|
||||||
@ -28,7 +35,6 @@ from homeassistant.const import (
|
|||||||
CONF_PAYLOAD,
|
CONF_PAYLOAD,
|
||||||
CONF_RESOURCE,
|
CONF_RESOURCE,
|
||||||
CONF_TIMEOUT,
|
CONF_TIMEOUT,
|
||||||
CONF_UNIQUE_ID,
|
|
||||||
CONF_UNIT_OF_MEASUREMENT,
|
CONF_UNIT_OF_MEASUREMENT,
|
||||||
CONF_USERNAME,
|
CONF_USERNAME,
|
||||||
CONF_VALUE_TEMPLATE,
|
CONF_VALUE_TEMPLATE,
|
||||||
@ -37,15 +43,7 @@ from homeassistant.const import (
|
|||||||
HTTP_DIGEST_AUTHENTICATION,
|
HTTP_DIGEST_AUTHENTICATION,
|
||||||
UnitOfTemperature,
|
UnitOfTemperature,
|
||||||
)
|
)
|
||||||
from homeassistant.core import async_get_hass
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers import config_validation as cv, entity_registry as er
|
|
||||||
from homeassistant.helpers.schema_config_entry_flow import (
|
|
||||||
SchemaCommonFlowHandler,
|
|
||||||
SchemaConfigFlowHandler,
|
|
||||||
SchemaFlowError,
|
|
||||||
SchemaFlowFormStep,
|
|
||||||
SchemaFlowMenuStep,
|
|
||||||
)
|
|
||||||
from homeassistant.helpers.selector import (
|
from homeassistant.helpers.selector import (
|
||||||
BooleanSelector,
|
BooleanSelector,
|
||||||
NumberSelector,
|
NumberSelector,
|
||||||
@ -73,7 +71,10 @@ from .const import (
|
|||||||
DOMAIN,
|
DOMAIN,
|
||||||
)
|
)
|
||||||
|
|
||||||
RESOURCE_SETUP = {
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
RESOURCE_SETUP = vol.Schema(
|
||||||
|
{
|
||||||
vol.Required(CONF_RESOURCE): TextSelector(
|
vol.Required(CONF_RESOURCE): TextSelector(
|
||||||
TextSelectorConfig(type=TextSelectorType.URL)
|
TextSelectorConfig(type=TextSelectorType.URL)
|
||||||
),
|
),
|
||||||
@ -81,36 +82,73 @@ RESOURCE_SETUP = {
|
|||||||
SelectSelectorConfig(options=METHODS, mode=SelectSelectorMode.DROPDOWN)
|
SelectSelectorConfig(options=METHODS, mode=SelectSelectorMode.DROPDOWN)
|
||||||
),
|
),
|
||||||
vol.Optional(CONF_PAYLOAD): ObjectSelector(),
|
vol.Optional(CONF_PAYLOAD): ObjectSelector(),
|
||||||
|
vol.Required("auth"): data_entry_flow.section(
|
||||||
|
vol.Schema(
|
||||||
|
{
|
||||||
vol.Optional(CONF_AUTHENTICATION): SelectSelector(
|
vol.Optional(CONF_AUTHENTICATION): SelectSelector(
|
||||||
SelectSelectorConfig(
|
SelectSelectorConfig(
|
||||||
options=[HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION],
|
options=[
|
||||||
|
HTTP_BASIC_AUTHENTICATION,
|
||||||
|
HTTP_DIGEST_AUTHENTICATION,
|
||||||
|
],
|
||||||
mode=SelectSelectorMode.DROPDOWN,
|
mode=SelectSelectorMode.DROPDOWN,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
vol.Optional(CONF_USERNAME): TextSelector(),
|
vol.Optional(CONF_USERNAME): TextSelector(
|
||||||
vol.Optional(CONF_PASSWORD): TextSelector(
|
TextSelectorConfig(
|
||||||
TextSelectorConfig(type=TextSelectorType.PASSWORD)
|
type=TextSelectorType.TEXT, autocomplete="username"
|
||||||
|
)
|
||||||
),
|
),
|
||||||
|
vol.Optional(CONF_PASSWORD): TextSelector(
|
||||||
|
TextSelectorConfig(
|
||||||
|
type=TextSelectorType.PASSWORD,
|
||||||
|
autocomplete="current-password",
|
||||||
|
)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
vol.Required("advanced"): data_entry_flow.section(
|
||||||
|
vol.Schema(
|
||||||
|
{
|
||||||
vol.Optional(CONF_HEADERS): ObjectSelector(),
|
vol.Optional(CONF_HEADERS): ObjectSelector(),
|
||||||
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): BooleanSelector(),
|
vol.Optional(
|
||||||
|
CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL
|
||||||
|
): BooleanSelector(),
|
||||||
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): NumberSelector(
|
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): NumberSelector(
|
||||||
NumberSelectorConfig(min=0, step=1, mode=NumberSelectorMode.BOX)
|
NumberSelectorConfig(min=0, step=1, mode=NumberSelectorMode.BOX)
|
||||||
),
|
),
|
||||||
vol.Optional(CONF_ENCODING, default=DEFAULT_ENCODING): TextSelector(),
|
vol.Optional(
|
||||||
|
CONF_ENCODING, default=DEFAULT_ENCODING
|
||||||
|
): TextSelector(),
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
SENSOR_SETUP = {
|
SENSOR_SETUP = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): TextSelector(),
|
||||||
vol.Required(CONF_SELECT): TextSelector(),
|
vol.Required(CONF_SELECT): TextSelector(),
|
||||||
vol.Optional(CONF_INDEX, default=0): NumberSelector(
|
vol.Optional(CONF_INDEX, default=0): vol.All(
|
||||||
|
NumberSelector(
|
||||||
NumberSelectorConfig(min=0, step=1, mode=NumberSelectorMode.BOX)
|
NumberSelectorConfig(min=0, step=1, mode=NumberSelectorMode.BOX)
|
||||||
),
|
),
|
||||||
|
vol.Coerce(int),
|
||||||
|
),
|
||||||
|
vol.Required("advanced"): data_entry_flow.section(
|
||||||
|
vol.Schema(
|
||||||
|
{
|
||||||
vol.Optional(CONF_ATTRIBUTE): TextSelector(),
|
vol.Optional(CONF_ATTRIBUTE): TextSelector(),
|
||||||
vol.Optional(CONF_VALUE_TEMPLATE): TemplateSelector(),
|
vol.Optional(CONF_VALUE_TEMPLATE): TemplateSelector(),
|
||||||
vol.Optional(CONF_AVAILABILITY): TemplateSelector(),
|
vol.Optional(CONF_AVAILABILITY): TemplateSelector(),
|
||||||
vol.Optional(CONF_DEVICE_CLASS): SelectSelector(
|
vol.Optional(CONF_DEVICE_CLASS): SelectSelector(
|
||||||
SelectSelectorConfig(
|
SelectSelectorConfig(
|
||||||
options=[
|
options=[
|
||||||
cls.value for cls in SensorDeviceClass if cls != SensorDeviceClass.ENUM
|
cls.value
|
||||||
|
for cls in SensorDeviceClass
|
||||||
|
if cls != SensorDeviceClass.ENUM
|
||||||
],
|
],
|
||||||
mode=SelectSelectorMode.DROPDOWN,
|
mode=SelectSelectorMode.DROPDOWN,
|
||||||
translation_key="device_class",
|
translation_key="device_class",
|
||||||
@ -135,180 +173,93 @@ SENSOR_SETUP = {
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def validate_rest_setup(
|
async def validate_rest_setup(
|
||||||
handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
|
hass: HomeAssistant, user_input: dict[str, Any]
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Validate rest setup."""
|
"""Validate rest setup."""
|
||||||
hass = async_get_hass()
|
|
||||||
rest_config: dict[str, Any] = COMBINED_SCHEMA(user_input)
|
rest_config: dict[str, Any] = COMBINED_SCHEMA(user_input)
|
||||||
try:
|
try:
|
||||||
rest = create_rest_data_from_config(hass, rest_config)
|
rest = create_rest_data_from_config(hass, rest_config)
|
||||||
await rest.async_update()
|
await rest.async_update()
|
||||||
except Exception as err:
|
except Exception:
|
||||||
raise SchemaFlowError("resource_error") from err
|
_LOGGER.exception("Error when getting resource %s", user_input[CONF_RESOURCE])
|
||||||
|
return {"base": "resource_error"}
|
||||||
if rest.data is None:
|
if rest.data is None:
|
||||||
raise SchemaFlowError("resource_error")
|
return {"base": "no_data"}
|
||||||
return user_input
|
|
||||||
|
|
||||||
|
|
||||||
async def validate_sensor_setup(
|
|
||||||
handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
"""Validate sensor input."""
|
|
||||||
user_input[CONF_INDEX] = int(user_input[CONF_INDEX])
|
|
||||||
user_input[CONF_UNIQUE_ID] = str(uuid.uuid1())
|
|
||||||
|
|
||||||
# Standard behavior is to merge the result with the options.
|
|
||||||
# In this case, we want to add a sub-item so we update the options directly.
|
|
||||||
sensors: list[dict[str, Any]] = handler.options.setdefault(SENSOR_DOMAIN, [])
|
|
||||||
sensors.append(user_input)
|
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
async def validate_select_sensor(
|
class ScrapeConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||||
handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
|
"""Scrape configuration flow."""
|
||||||
) -> dict[str, Any]:
|
|
||||||
"""Store sensor index in flow state."""
|
|
||||||
handler.flow_state["_idx"] = int(user_input[CONF_INDEX])
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
VERSION = 2
|
||||||
|
|
||||||
async def get_select_sensor_schema(handler: SchemaCommonFlowHandler) -> vol.Schema:
|
@staticmethod
|
||||||
"""Return schema for selecting a sensor."""
|
@callback
|
||||||
return vol.Schema(
|
def async_get_options_flow(config_entry: ConfigEntry) -> ScrapeOptionFlow:
|
||||||
{
|
"""Get the options flow for this handler."""
|
||||||
vol.Required(CONF_INDEX): vol.In(
|
return ScrapeOptionFlow()
|
||||||
{
|
|
||||||
str(index): config[CONF_NAME]
|
@classmethod
|
||||||
for index, config in enumerate(handler.options[SENSOR_DOMAIN])
|
@callback
|
||||||
},
|
def async_get_supported_subentry_types(
|
||||||
)
|
cls, config_entry: ConfigEntry
|
||||||
}
|
) -> dict[str, type[ConfigSubentryFlow]]:
|
||||||
|
"""Return subentries supported by this handler."""
|
||||||
|
return {"entity": ScrapeSubentryFlowHandler}
|
||||||
|
|
||||||
|
async def async_step_user(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> ConfigFlowResult:
|
||||||
|
"""User flow to create a sensor subentry."""
|
||||||
|
if user_input is not None:
|
||||||
|
errors = await validate_rest_setup(self.hass, user_input)
|
||||||
|
title = user_input[CONF_RESOURCE]
|
||||||
|
if not errors:
|
||||||
|
return self.async_create_entry(data=user_input, title=title)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user", data_schema=RESOURCE_SETUP, errors=errors
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def get_edit_sensor_suggested_values(
|
class ScrapeOptionFlow(OptionsFlow):
|
||||||
handler: SchemaCommonFlowHandler,
|
"""Scrape Options flow."""
|
||||||
) -> dict[str, Any]:
|
|
||||||
"""Return suggested values for sensor editing."""
|
|
||||||
idx: int = handler.flow_state["_idx"]
|
|
||||||
return dict(handler.options[SENSOR_DOMAIN][idx])
|
|
||||||
|
|
||||||
|
async def async_step_init(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> ConfigFlowResult:
|
||||||
|
"""Manage Scrape options."""
|
||||||
|
|
||||||
async def validate_sensor_edit(
|
if user_input is not None:
|
||||||
handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
|
errors = await validate_rest_setup(self.hass, user_input)
|
||||||
) -> dict[str, Any]:
|
if not errors:
|
||||||
"""Update edited sensor."""
|
return self.async_create_entry(data=user_input)
|
||||||
user_input[CONF_INDEX] = int(user_input[CONF_INDEX])
|
|
||||||
|
|
||||||
# Standard behavior is to merge the result with the options.
|
return self.async_show_form(
|
||||||
# In this case, we want to add a sub-item so we update the options directly,
|
step_id="init",
|
||||||
# including popping omitted optional schema items.
|
data_schema=self.add_suggested_values_to_schema(
|
||||||
idx: int = handler.flow_state["_idx"]
|
RESOURCE_SETUP,
|
||||||
handler.options[SENSOR_DOMAIN][idx].update(user_input)
|
self.config_entry.options,
|
||||||
for key in DATA_SCHEMA_EDIT_SENSOR.schema:
|
),
|
||||||
if isinstance(key, vol.Optional) and key not in user_input:
|
|
||||||
# Key not present, delete keys old value (if present) too
|
|
||||||
handler.options[SENSOR_DOMAIN][idx].pop(key, None)
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
async def get_remove_sensor_schema(handler: SchemaCommonFlowHandler) -> vol.Schema:
|
|
||||||
"""Return schema for sensor removal."""
|
|
||||||
return vol.Schema(
|
|
||||||
{
|
|
||||||
vol.Required(CONF_INDEX): cv.multi_select(
|
|
||||||
{
|
|
||||||
str(index): config[CONF_NAME]
|
|
||||||
for index, config in enumerate(handler.options[SENSOR_DOMAIN])
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def validate_remove_sensor(
|
class ScrapeSubentryFlowHandler(ConfigSubentryFlow):
|
||||||
handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
|
"""Handle subentry flow."""
|
||||||
) -> dict[str, Any]:
|
|
||||||
"""Validate remove sensor."""
|
|
||||||
removed_indexes: set[str] = set(user_input[CONF_INDEX])
|
|
||||||
|
|
||||||
# Standard behavior is to merge the result with the options.
|
async def async_step_user(
|
||||||
# In this case, we want to remove sub-items so we update the options directly.
|
self, user_input: dict[str, Any] | None = None
|
||||||
entity_registry = er.async_get(handler.parent_handler.hass)
|
) -> SubentryFlowResult:
|
||||||
sensors: list[dict[str, Any]] = []
|
"""User flow to create a sensor subentry."""
|
||||||
sensor: dict[str, Any]
|
if user_input is not None:
|
||||||
for index, sensor in enumerate(handler.options[SENSOR_DOMAIN]):
|
title = user_input.pop("name")
|
||||||
if str(index) not in removed_indexes:
|
return self.async_create_entry(data=user_input, title=title)
|
||||||
sensors.append(sensor)
|
|
||||||
elif entity_id := entity_registry.async_get_entity_id(
|
|
||||||
SENSOR_DOMAIN, DOMAIN, sensor[CONF_UNIQUE_ID]
|
|
||||||
):
|
|
||||||
entity_registry.async_remove(entity_id)
|
|
||||||
handler.options[SENSOR_DOMAIN] = sensors
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
return self.async_show_form(step_id="user", data_schema=SENSOR_SETUP)
|
||||||
DATA_SCHEMA_RESOURCE = vol.Schema(RESOURCE_SETUP)
|
|
||||||
DATA_SCHEMA_EDIT_SENSOR = vol.Schema(SENSOR_SETUP)
|
|
||||||
DATA_SCHEMA_SENSOR = vol.Schema(
|
|
||||||
{
|
|
||||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): TextSelector(),
|
|
||||||
**SENSOR_SETUP,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
CONFIG_FLOW = {
|
|
||||||
"user": SchemaFlowFormStep(
|
|
||||||
schema=DATA_SCHEMA_RESOURCE,
|
|
||||||
next_step="sensor",
|
|
||||||
validate_user_input=validate_rest_setup,
|
|
||||||
),
|
|
||||||
"sensor": SchemaFlowFormStep(
|
|
||||||
schema=DATA_SCHEMA_SENSOR,
|
|
||||||
validate_user_input=validate_sensor_setup,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
OPTIONS_FLOW = {
|
|
||||||
"init": SchemaFlowMenuStep(
|
|
||||||
["resource", "add_sensor", "select_edit_sensor", "remove_sensor"]
|
|
||||||
),
|
|
||||||
"resource": SchemaFlowFormStep(
|
|
||||||
DATA_SCHEMA_RESOURCE,
|
|
||||||
validate_user_input=validate_rest_setup,
|
|
||||||
),
|
|
||||||
"add_sensor": SchemaFlowFormStep(
|
|
||||||
DATA_SCHEMA_SENSOR,
|
|
||||||
suggested_values=None,
|
|
||||||
validate_user_input=validate_sensor_setup,
|
|
||||||
),
|
|
||||||
"select_edit_sensor": SchemaFlowFormStep(
|
|
||||||
get_select_sensor_schema,
|
|
||||||
suggested_values=None,
|
|
||||||
validate_user_input=validate_select_sensor,
|
|
||||||
next_step="edit_sensor",
|
|
||||||
),
|
|
||||||
"edit_sensor": SchemaFlowFormStep(
|
|
||||||
DATA_SCHEMA_EDIT_SENSOR,
|
|
||||||
suggested_values=get_edit_sensor_suggested_values,
|
|
||||||
validate_user_input=validate_sensor_edit,
|
|
||||||
),
|
|
||||||
"remove_sensor": SchemaFlowFormStep(
|
|
||||||
get_remove_sensor_schema,
|
|
||||||
suggested_values=None,
|
|
||||||
validate_user_input=validate_remove_sensor,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class ScrapeConfigFlowHandler(SchemaConfigFlowHandler, domain=DOMAIN):
|
|
||||||
"""Handle a config flow for Scrape."""
|
|
||||||
|
|
||||||
config_flow = CONFIG_FLOW
|
|
||||||
options_flow = OPTIONS_FLOW
|
|
||||||
|
|
||||||
def async_config_entry_title(self, options: Mapping[str, Any]) -> str:
|
|
||||||
"""Return config entry title."""
|
|
||||||
return cast(str, options[CONF_RESOURCE])
|
|
||||||
|
@ -101,7 +101,8 @@ async def async_setup_entry(
|
|||||||
entities: list = []
|
entities: list = []
|
||||||
|
|
||||||
coordinator = entry.runtime_data
|
coordinator = entry.runtime_data
|
||||||
config = dict(entry.options)
|
for subentry in entry.subentries.values():
|
||||||
|
config = dict(subentry.data)
|
||||||
for sensor in config["sensor"]:
|
for sensor in config["sensor"]:
|
||||||
sensor_config: ConfigType = vol.Schema(
|
sensor_config: ConfigType = vol.Schema(
|
||||||
TEMPLATE_SENSOR_BASE_SCHEMA.schema, extra=vol.ALLOW_EXTRA
|
TEMPLATE_SENSOR_BASE_SCHEMA.schema, extra=vol.ALLOW_EXTRA
|
||||||
|
Loading…
x
Reference in New Issue
Block a user