mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +00:00
Add subentry support to kitchen sink
This commit is contained in:
parent
a4653bb8dc
commit
a12a42710f
@ -70,11 +70,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
return True
|
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 the config entry up."""
|
||||||
# Set up demo platforms with config entry
|
# Set up demo platforms with config entry
|
||||||
await hass.config_entries.async_forward_entry_setups(
|
await hass.config_entries.async_forward_entry_setups(
|
||||||
config_entry, COMPONENTS_WITH_DEMO_PLATFORM
|
entry, COMPONENTS_WITH_DEMO_PLATFORM
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create issues
|
# Create issues
|
||||||
@ -85,7 +85,9 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
|||||||
await _insert_statistics(hass)
|
await _insert_statistics(hass)
|
||||||
|
|
||||||
# Start a reauth flow
|
# 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
|
# Notify backup listeners
|
||||||
hass.async_create_task(_notify_backup_listeners(hass), eager_start=False)
|
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
|
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:
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Unload config entry."""
|
"""Unload config entry."""
|
||||||
# Notify backup listeners
|
# Notify backup listeners
|
||||||
|
@ -12,7 +12,9 @@ from homeassistant.config_entries import (
|
|||||||
ConfigEntry,
|
ConfigEntry,
|
||||||
ConfigFlow,
|
ConfigFlow,
|
||||||
ConfigFlowResult,
|
ConfigFlowResult,
|
||||||
|
ConfigSubentryFlow,
|
||||||
OptionsFlow,
|
OptionsFlow,
|
||||||
|
SubentryFlowResult,
|
||||||
)
|
)
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
|
|
||||||
@ -35,6 +37,21 @@ class KitchenSinkConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
"""Get the options flow for this handler."""
|
"""Get the options flow for this handler."""
|
||||||
return OptionsFlowHandler()
|
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:
|
async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResult:
|
||||||
"""Set the config entry up from yaml."""
|
"""Set the config entry up from yaml."""
|
||||||
return self.async_create_entry(title="Kitchen Sink", data=import_data)
|
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.const import UnitOfPower
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
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 homeassistant.helpers.typing import UNDEFINED, StateType, UndefinedType
|
||||||
|
|
||||||
from . import DOMAIN
|
from . import DOMAIN
|
||||||
@ -21,7 +21,8 @@ from .device import async_create_device
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: ConfigEntry,
|
config_entry: ConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
# pylint: disable-next=hass-argument-type
|
||||||
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Everything but the Kitchen Sink config entry."""
|
"""Set up the Everything but the Kitchen Sink config entry."""
|
||||||
async_create_device(
|
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):
|
class DemoSensor(SensorEntity):
|
||||||
"""Representation of a Demo sensor."""
|
"""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": {
|
"options": {
|
||||||
"step": {
|
"step": {
|
||||||
"init": {
|
"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}}
|
assert config_entry.options == {"section_1": {"bool": True, "int": 15}}
|
||||||
|
|
||||||
await hass.async_block_till_done()
|
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
|
import pytest
|
||||||
from syrupy.assertion import SnapshotAssertion
|
from syrupy.assertion import SnapshotAssertion
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
from homeassistant.components.kitchen_sink import DOMAIN
|
from homeassistant.components.kitchen_sink import DOMAIN
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
async def sensor_only() -> None:
|
async def sensor_only() -> None:
|
||||||
@ -21,14 +24,40 @@ async def sensor_only() -> None:
|
|||||||
yield
|
yield
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture
|
||||||
async def setup_comp(hass: HomeAssistant, sensor_only):
|
async def setup_comp(hass: HomeAssistant, sensor_only):
|
||||||
"""Set up demo component."""
|
"""Set up demo component."""
|
||||||
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
|
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("setup_comp")
|
||||||
async def test_states(hass: HomeAssistant, snapshot: SnapshotAssertion) -> None:
|
async def test_states(hass: HomeAssistant, snapshot: SnapshotAssertion) -> None:
|
||||||
"""Test the expected sensor entities are added."""
|
"""Test the expected sensor entities are added."""
|
||||||
states = hass.states.async_all()
|
states = hass.states.async_all()
|
||||||
assert set(states) == snapshot
|
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…
x
Reference in New Issue
Block a user