Add subentry support to kitchen sink
parent
a4653bb8dc
commit
a12a42710f
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
),
|
||||
)
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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',
|
||||
}),
|
||||
})
|
||||
# ---
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue