Add subentry support to kitchen sink

pull/132049/head
Erik 2024-12-02 10:07:54 +01:00
parent a4653bb8dc
commit a12a42710f
7 changed files with 233 additions and 6 deletions

View File

@ -70,11 +70,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
return True
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set the config entry up."""
# Set up demo platforms with config entry
await hass.config_entries.async_forward_entry_setups(
config_entry, COMPONENTS_WITH_DEMO_PLATFORM
entry, COMPONENTS_WITH_DEMO_PLATFORM
)
# Create issues
@ -85,7 +85,9 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
await _insert_statistics(hass)
# Start a reauth flow
config_entry.async_start_reauth(hass)
entry.async_start_reauth(hass)
entry.async_on_unload(entry.add_update_listener(_async_update_listener))
# Notify backup listeners
hass.async_create_task(_notify_backup_listeners(hass), eager_start=False)
@ -93,6 +95,11 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
return True
async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Handle update."""
await hass.config_entries.async_reload(entry.entry_id)
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload config entry."""
# Notify backup listeners

View File

@ -12,7 +12,9 @@ from homeassistant.config_entries import (
ConfigEntry,
ConfigFlow,
ConfigFlowResult,
ConfigSubentryFlow,
OptionsFlow,
SubentryFlowResult,
)
from homeassistant.core import callback
@ -35,6 +37,21 @@ class KitchenSinkConfigFlow(ConfigFlow, domain=DOMAIN):
"""Get the options flow for this handler."""
return OptionsFlowHandler()
@staticmethod
@callback
def async_get_subentry_flow(
config_entry: ConfigEntry, subentry_type: str
) -> ConfigSubentryFlow:
"""Get the subentry flow for this handler."""
return SubentryFlowHandler()
@classmethod
@callback
def async_supported_subentries(cls, config_entry: ConfigEntry) -> tuple[str, ...]:
"""Return subentries supported by this handler."""
return ("add_entity",)
async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResult:
"""Set the config entry up from yaml."""
return self.async_create_entry(title="Kitchen Sink", data=import_data)
@ -94,3 +111,31 @@ class OptionsFlowHandler(OptionsFlow):
}
),
)
class SubentryFlowHandler(ConfigSubentryFlow):
"""Handle subentry flow."""
async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> SubentryFlowResult:
"""Manage the options."""
return await self.async_step_add_sensor()
async def async_step_add_sensor(
self, user_input: dict[str, Any] | None = None
) -> SubentryFlowResult:
"""Add a new sensor."""
if user_input is not None:
title = user_input.pop("name")
return self.async_create_entry(data=user_input, title=title)
return self.async_show_form(
step_id="add_sensor",
data_schema=vol.Schema(
{
vol.Required("name"): str,
vol.Required("state"): int,
}
),
)

View File

@ -11,7 +11,7 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import UnitOfPower
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import UNDEFINED, StateType, UndefinedType
from . import DOMAIN
@ -21,7 +21,8 @@ from .device import async_create_device
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
# pylint: disable-next=hass-argument-type
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Everything but the Kitchen Sink config entry."""
async_create_device(
@ -90,6 +91,23 @@ async def async_setup_entry(
]
)
for subentry_id, subentry in config_entry.subentries.items():
async_add_entities(
[
DemoSensor(
device_unique_id=subentry_id,
unique_id=subentry_id,
device_name=subentry.title,
entity_name=None,
state=subentry.data["state"],
device_class=None,
state_class=None,
unit_of_measurement=None,
)
],
subentry_id=subentry_id,
)
class DemoSensor(SensorEntity):
"""Representation of a Demo sensor."""

View File

@ -9,6 +9,20 @@
}
}
},
"config_subentries": {
"add_entity": {
"title": "Add entity",
"step": {
"add_sensor": {
"description": "Configure the new sensor",
"data": {
"name": "[%key:common::config_flow::data::name%]",
"state": "Initial state"
}
}
}
}
},
"options": {
"step": {
"init": {

View File

@ -69,3 +69,84 @@
}),
})
# ---
# name: test_states_with_subentry
set({
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'power',
'friendly_name': 'Outlet 1 Power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
'entity_id': 'sensor.outlet_1_power',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '50',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'power',
'friendly_name': 'Outlet 2 Power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
'entity_id': 'sensor.outlet_2_power',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '1500',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Sensor test',
}),
'context': <ANY>,
'entity_id': 'sensor.sensor_test',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '15',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Statistics issues Issue 1',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
'entity_id': 'sensor.statistics_issues_issue_1',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '100',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Statistics issues Issue 2',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': 'dogs',
}),
'context': <ANY>,
'entity_id': 'sensor.statistics_issues_issue_2',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '100',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Statistics issues Issue 3',
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
'entity_id': 'sensor.statistics_issues_issue_3',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '100',
}),
})
# ---

View File

@ -104,3 +104,36 @@ async def test_options_flow(hass: HomeAssistant) -> None:
assert config_entry.options == {"section_1": {"bool": True, "int": 15}}
await hass.async_block_till_done()
@pytest.mark.usefixtures("no_platforms")
async def test_subentry_flow(hass: HomeAssistant) -> None:
"""Test config flow options."""
config_entry = MockConfigEntry(domain=DOMAIN)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
result = await hass.config_entries.subentries.async_init(
(config_entry.entry_id, "add_entity")
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "add_sensor"
result = await hass.config_entries.subentries.async_configure(
result["flow_id"],
user_input={"name": "Sensor 1", "state": 15},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
subentry_id = list(config_entry.subentries.keys())[0]
assert config_entry.subentries == {
subentry_id: config_entries.ConfigSubentry(
data={"state": 15},
subentry_id=subentry_id,
title="Sensor 1",
unique_id=None,
)
}
await hass.async_block_till_done()

View File

@ -5,11 +5,14 @@ from unittest.mock import patch
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant import config_entries
from homeassistant.components.kitchen_sink import DOMAIN
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry
@pytest.fixture
async def sensor_only() -> None:
@ -21,14 +24,40 @@ async def sensor_only() -> None:
yield
@pytest.fixture(autouse=True)
@pytest.fixture
async def setup_comp(hass: HomeAssistant, sensor_only):
"""Set up demo component."""
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
await hass.async_block_till_done()
@pytest.mark.usefixtures("setup_comp")
async def test_states(hass: HomeAssistant, snapshot: SnapshotAssertion) -> None:
"""Test the expected sensor entities are added."""
states = hass.states.async_all()
assert set(states) == snapshot
@pytest.mark.usefixtures("sensor_only")
async def test_states_with_subentry(
hass: HomeAssistant, snapshot: SnapshotAssertion
) -> None:
"""Test the expected sensor entities are added."""
config_entry = MockConfigEntry(
domain=DOMAIN,
subentries_data=[
config_entries.ConfigSubentryData(
data={"state": 15},
subentry_id="blabla",
title="Sensor test",
unique_id=None,
)
],
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
states = hass.states.async_all()
assert set(states) == snapshot