Integrations v2.1: Differentiating hubs, devices and services (#80524)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
parent
0c8884fd51
commit
c4bbc439a5
11 changed files with 1376 additions and 105 deletions
|
@ -6,5 +6,6 @@
|
||||||
"requirements": ["adguardhome==0.5.1"],
|
"requirements": ["adguardhome==0.5.1"],
|
||||||
"codeowners": ["@frenck"],
|
"codeowners": ["@frenck"],
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
|
"integration_type": "service",
|
||||||
"loggers": ["adguardhome"]
|
"loggers": ["adguardhome"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ from homeassistant.components import websocket_api
|
||||||
from homeassistant.components.http import HomeAssistantView
|
from homeassistant.components.http import HomeAssistantView
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.exceptions import DependencyError, Unauthorized
|
from homeassistant.exceptions import DependencyError, Unauthorized
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.data_entry_flow import (
|
from homeassistant.helpers.data_entry_flow import (
|
||||||
FlowManagerIndexView,
|
FlowManagerIndexView,
|
||||||
FlowManagerResourceView,
|
FlowManagerResourceView,
|
||||||
|
@ -64,7 +65,7 @@ class ConfigManagerEntryIndexView(HomeAssistantView):
|
||||||
domain = request.query["domain"]
|
domain = request.query["domain"]
|
||||||
type_filter = None
|
type_filter = None
|
||||||
if "type" in request.query:
|
if "type" in request.query:
|
||||||
type_filter = request.query["type"]
|
type_filter = [request.query["type"]]
|
||||||
return self.json(await async_matching_config_entries(hass, type_filter, domain))
|
return self.json(await async_matching_config_entries(hass, type_filter, domain))
|
||||||
|
|
||||||
|
|
||||||
|
@ -405,7 +406,7 @@ async def ignore_config_flow(
|
||||||
@websocket_api.websocket_command(
|
@websocket_api.websocket_command(
|
||||||
{
|
{
|
||||||
vol.Required("type"): "config_entries/get",
|
vol.Required("type"): "config_entries/get",
|
||||||
vol.Optional("type_filter"): str,
|
vol.Optional("type_filter"): vol.All(cv.ensure_list, [str]),
|
||||||
vol.Optional("domain"): str,
|
vol.Optional("domain"): str,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -427,7 +428,7 @@ async def config_entries_get(
|
||||||
@websocket_api.websocket_command(
|
@websocket_api.websocket_command(
|
||||||
{
|
{
|
||||||
vol.Required("type"): "config_entries/subscribe",
|
vol.Required("type"): "config_entries/subscribe",
|
||||||
vol.Optional("type_filter"): str,
|
vol.Optional("type_filter"): vol.All(cv.ensure_list, [str]),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@websocket_api.async_response
|
@websocket_api.async_response
|
||||||
|
@ -445,7 +446,7 @@ async def config_entries_subscribe(
|
||||||
"""Forward config entry state events to websocket."""
|
"""Forward config entry state events to websocket."""
|
||||||
if type_filter:
|
if type_filter:
|
||||||
integration = await async_get_integration(hass, entry.domain)
|
integration = await async_get_integration(hass, entry.domain)
|
||||||
if integration.integration_type != type_filter:
|
if integration.integration_type not in type_filter:
|
||||||
return
|
return
|
||||||
|
|
||||||
connection.send_message(
|
connection.send_message(
|
||||||
|
@ -475,7 +476,7 @@ async def config_entries_subscribe(
|
||||||
|
|
||||||
|
|
||||||
async def async_matching_config_entries(
|
async def async_matching_config_entries(
|
||||||
hass: HomeAssistant, type_filter: str | None, domain: str | None
|
hass: HomeAssistant, type_filter: list[str] | None, domain: str | None
|
||||||
) -> list[dict[str, Any]]:
|
) -> list[dict[str, Any]]:
|
||||||
"""Return matching config entries by type and/or domain."""
|
"""Return matching config entries by type and/or domain."""
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
|
@ -483,7 +484,7 @@ async def async_matching_config_entries(
|
||||||
kwargs["domain"] = domain
|
kwargs["domain"] = domain
|
||||||
entries = hass.config_entries.async_entries(**kwargs)
|
entries = hass.config_entries.async_entries(**kwargs)
|
||||||
|
|
||||||
if type_filter is None:
|
if not type_filter:
|
||||||
return [entry_json(entry) for entry in entries]
|
return [entry_json(entry) for entry in entries]
|
||||||
|
|
||||||
integrations = {}
|
integrations = {}
|
||||||
|
@ -499,13 +500,17 @@ async def async_matching_config_entries(
|
||||||
elif not isinstance(integration_or_exc, IntegrationNotFound):
|
elif not isinstance(integration_or_exc, IntegrationNotFound):
|
||||||
raise integration_or_exc
|
raise integration_or_exc
|
||||||
|
|
||||||
|
# Filter out entries that don't match the type filter
|
||||||
|
# when only helpers are requested, also filter out entries
|
||||||
|
# from unknown integrations. This prevent them from showing
|
||||||
|
# up in the helpers UI.
|
||||||
entries = [
|
entries = [
|
||||||
entry
|
entry
|
||||||
for entry in entries
|
for entry in entries
|
||||||
if (type_filter != "helper" and entry.domain not in integrations)
|
if (type_filter != ["helper"] and entry.domain not in integrations)
|
||||||
or (
|
or (
|
||||||
entry.domain in integrations
|
entry.domain in integrations
|
||||||
and integrations[entry.domain].integration_type == type_filter
|
and integrations[entry.domain].integration_type in type_filter
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -9,5 +9,6 @@
|
||||||
"codeowners": ["@OttoWinter", "@jesserockz"],
|
"codeowners": ["@OttoWinter", "@jesserockz"],
|
||||||
"after_dependencies": ["bluetooth", "zeroconf", "tag"],
|
"after_dependencies": ["bluetooth", "zeroconf", "tag"],
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
|
"integration_type": "device",
|
||||||
"loggers": ["aioesphomeapi", "noiseprotocol"]
|
"loggers": ["aioesphomeapi", "noiseprotocol"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,5 +25,6 @@
|
||||||
"codeowners": ["@balloob", "@marcelveldt"],
|
"codeowners": ["@balloob", "@marcelveldt"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
|
"integration_type": "hub",
|
||||||
"loggers": ["aiohue"]
|
"loggers": ["aiohue"]
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -130,7 +130,9 @@ class Manifest(TypedDict, total=False):
|
||||||
name: str
|
name: str
|
||||||
disabled: str
|
disabled: str
|
||||||
domain: str
|
domain: str
|
||||||
integration_type: Literal["entity", "integration", "hardware", "helper", "system"]
|
integration_type: Literal[
|
||||||
|
"entity", "device", "hardware", "helper", "hub", "service", "system"
|
||||||
|
]
|
||||||
dependencies: list[str]
|
dependencies: list[str]
|
||||||
after_dependencies: list[str]
|
after_dependencies: list[str]
|
||||||
requirements: list[str]
|
requirements: list[str]
|
||||||
|
@ -224,7 +226,7 @@ async def async_get_custom_components(
|
||||||
|
|
||||||
async def async_get_config_flows(
|
async def async_get_config_flows(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
type_filter: Literal["helper", "integration"] | None = None,
|
type_filter: Literal["device", "helper", "hub", "service"] | None = None,
|
||||||
) -> set[str]:
|
) -> set[str]:
|
||||||
"""Return cached list of config flows."""
|
"""Return cached list of config flows."""
|
||||||
# pylint: disable=import-outside-toplevel
|
# pylint: disable=import-outside-toplevel
|
||||||
|
@ -262,9 +264,11 @@ async def async_get_integration_descriptions(
|
||||||
core_flows: dict[str, Any] = json_loads(flow)
|
core_flows: dict[str, Any] = json_loads(flow)
|
||||||
custom_integrations = await async_get_custom_components(hass)
|
custom_integrations = await async_get_custom_components(hass)
|
||||||
custom_flows: dict[str, Any] = {
|
custom_flows: dict[str, Any] = {
|
||||||
"integration": {},
|
"device": {},
|
||||||
"hardware": {},
|
"hardware": {},
|
||||||
"helper": {},
|
"helper": {},
|
||||||
|
"hub": {},
|
||||||
|
"service": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
for integration in custom_integrations.values():
|
for integration in custom_integrations.values():
|
||||||
|
@ -272,7 +276,7 @@ async def async_get_integration_descriptions(
|
||||||
if integration.integration_type in ("entity", "system"):
|
if integration.integration_type in ("entity", "system"):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for integration_type in ("integration", "hardware", "helper"):
|
for integration_type in ("device", "hardware", "helper", "hub", "service"):
|
||||||
if integration.domain not in core_flows[integration_type]:
|
if integration.domain not in core_flows[integration_type]:
|
||||||
continue
|
continue
|
||||||
del core_flows[integration_type][integration.domain]
|
del core_flows[integration_type][integration.domain]
|
||||||
|
@ -281,6 +285,7 @@ async def async_get_integration_descriptions(
|
||||||
|
|
||||||
metadata = {
|
metadata = {
|
||||||
"config_flow": integration.config_flow,
|
"config_flow": integration.config_flow,
|
||||||
|
"integration_type": integration.integration_type,
|
||||||
"iot_class": integration.iot_class,
|
"iot_class": integration.iot_class,
|
||||||
"name": integration.name,
|
"name": integration.name,
|
||||||
}
|
}
|
||||||
|
@ -599,9 +604,9 @@ class Integration:
|
||||||
@property
|
@property
|
||||||
def integration_type(
|
def integration_type(
|
||||||
self,
|
self,
|
||||||
) -> Literal["entity", "integration", "hardware", "helper", "system"]:
|
) -> Literal["entity", "device", "hardware", "helper", "hub", "service", "system"]:
|
||||||
"""Return the integration type."""
|
"""Return the integration type."""
|
||||||
return self.manifest.get("integration_type", "integration")
|
return self.manifest.get("integration_type", "hub")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mqtt(self) -> list[str] | None:
|
def mqtt(self) -> list[str] | None:
|
||||||
|
|
|
@ -86,7 +86,10 @@ def _generate_and_validate(integrations: dict[str, Integration], config: Config)
|
||||||
|
|
||||||
_validate_integration(config, integration)
|
_validate_integration(config, integration)
|
||||||
|
|
||||||
domains[integration.integration_type].append(domain)
|
if integration.integration_type == "helper":
|
||||||
|
domains["helper"].append(domain)
|
||||||
|
else:
|
||||||
|
domains["integration"].append(domain)
|
||||||
|
|
||||||
return black.format_str(BASE.format(to_string(domains)), mode=black.Mode())
|
return black.format_str(BASE.format(to_string(domains)), mode=black.Mode())
|
||||||
|
|
||||||
|
@ -106,6 +109,7 @@ def _populate_brand_integrations(
|
||||||
metadata = {}
|
metadata = {}
|
||||||
metadata["config_flow"] = integration.config_flow
|
metadata["config_flow"] = integration.config_flow
|
||||||
metadata["iot_class"] = integration.iot_class
|
metadata["iot_class"] = integration.iot_class
|
||||||
|
metadata["integration_type"] = integration.integration_type
|
||||||
if integration.translated_name:
|
if integration.translated_name:
|
||||||
integration_data["translated_name"].add(domain)
|
integration_data["translated_name"].add(domain)
|
||||||
else:
|
else:
|
||||||
|
@ -169,11 +173,16 @@ def _generate_integrations(
|
||||||
continue
|
continue
|
||||||
metadata["config_flow"] = integration.config_flow
|
metadata["config_flow"] = integration.config_flow
|
||||||
metadata["iot_class"] = integration.iot_class
|
metadata["iot_class"] = integration.iot_class
|
||||||
|
metadata["integration_type"] = integration.integration_type
|
||||||
if integration.translated_name:
|
if integration.translated_name:
|
||||||
result["translated_name"].add(domain)
|
result["translated_name"].add(domain)
|
||||||
else:
|
else:
|
||||||
metadata["name"] = integration.name
|
metadata["name"] = integration.name
|
||||||
result[integration.integration_type][domain] = metadata
|
|
||||||
|
if integration.integration_type == "helper":
|
||||||
|
result["helper"][domain] = metadata
|
||||||
|
else:
|
||||||
|
result["integration"][domain] = metadata
|
||||||
|
|
||||||
return json.dumps(
|
return json.dumps(
|
||||||
result | {"translated_name": sorted(result["translated_name"])}, indent=2
|
result | {"translated_name": sorted(result["translated_name"])}, indent=2
|
||||||
|
|
|
@ -162,8 +162,16 @@ MANIFEST_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required("domain"): str,
|
vol.Required("domain"): str,
|
||||||
vol.Required("name"): str,
|
vol.Required("name"): str,
|
||||||
vol.Optional("integration_type"): vol.In(
|
vol.Optional("integration_type", default="hub"): vol.In(
|
||||||
["entity", "hardware", "helper", "system"]
|
[
|
||||||
|
"device",
|
||||||
|
"entity",
|
||||||
|
"hardware",
|
||||||
|
"helper",
|
||||||
|
"hub",
|
||||||
|
"service",
|
||||||
|
"system",
|
||||||
|
]
|
||||||
),
|
),
|
||||||
vol.Optional("config_flow"): bool,
|
vol.Optional("config_flow"): bool,
|
||||||
vol.Optional("mqtt"): [str],
|
vol.Optional("mqtt"): [str],
|
||||||
|
|
|
@ -177,7 +177,7 @@ class Integration:
|
||||||
@property
|
@property
|
||||||
def integration_type(self) -> str:
|
def integration_type(self) -> str:
|
||||||
"""Get integration_type."""
|
"""Get integration_type."""
|
||||||
return self.manifest.get("integration_type", "integration")
|
return self.manifest.get("integration_type", "hub")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def iot_class(self) -> str | None:
|
def iot_class(self) -> str | None:
|
||||||
|
|
|
@ -37,7 +37,6 @@ async def test_options_flow_disabled_not_setup(
|
||||||
"id": 5,
|
"id": 5,
|
||||||
"type": "config_entries/get",
|
"type": "config_entries/get",
|
||||||
"domain": "bluetooth",
|
"domain": "bluetooth",
|
||||||
"type_filter": "integration",
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
response = await ws_client.receive_json()
|
response = await ws_client.receive_json()
|
||||||
|
@ -341,7 +340,6 @@ async def test_options_flow_disabled_macos(
|
||||||
"id": 5,
|
"id": 5,
|
||||||
"type": "config_entries/get",
|
"type": "config_entries/get",
|
||||||
"domain": "bluetooth",
|
"domain": "bluetooth",
|
||||||
"type_filter": "integration",
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
response = await ws_client.receive_json()
|
response = await ws_client.receive_json()
|
||||||
|
@ -371,7 +369,6 @@ async def test_options_flow_enabled_linux(
|
||||||
"id": 5,
|
"id": 5,
|
||||||
"type": "config_entries/get",
|
"type": "config_entries/get",
|
||||||
"domain": "bluetooth",
|
"domain": "bluetooth",
|
||||||
"type_filter": "integration",
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
response = await ws_client.receive_json()
|
response = await ws_client.receive_json()
|
||||||
|
|
|
@ -51,7 +51,15 @@ async def test_get_entries(hass, client, clear_handlers):
|
||||||
mock_integration(
|
mock_integration(
|
||||||
hass, MockModule("comp2", partial_manifest={"integration_type": "helper"})
|
hass, MockModule("comp2", partial_manifest={"integration_type": "helper"})
|
||||||
)
|
)
|
||||||
mock_integration(hass, MockModule("comp3"))
|
mock_integration(
|
||||||
|
hass, MockModule("comp3", partial_manifest={"integration_type": "hub"})
|
||||||
|
)
|
||||||
|
mock_integration(
|
||||||
|
hass, MockModule("comp4", partial_manifest={"integration_type": "device"})
|
||||||
|
)
|
||||||
|
mock_integration(
|
||||||
|
hass, MockModule("comp5", partial_manifest={"integration_type": "service"})
|
||||||
|
)
|
||||||
|
|
||||||
@HANDLERS.register("comp1")
|
@HANDLERS.register("comp1")
|
||||||
class Comp1ConfigFlow:
|
class Comp1ConfigFlow:
|
||||||
|
@ -91,6 +99,16 @@ async def test_get_entries(hass, client, clear_handlers):
|
||||||
source="bla3",
|
source="bla3",
|
||||||
disabled_by=core_ce.ConfigEntryDisabler.USER,
|
disabled_by=core_ce.ConfigEntryDisabler.USER,
|
||||||
).add_to_hass(hass)
|
).add_to_hass(hass)
|
||||||
|
MockConfigEntry(
|
||||||
|
domain="comp4",
|
||||||
|
title="Test 4",
|
||||||
|
source="bla4",
|
||||||
|
).add_to_hass(hass)
|
||||||
|
MockConfigEntry(
|
||||||
|
domain="comp5",
|
||||||
|
title="Test 5",
|
||||||
|
source="bla5",
|
||||||
|
).add_to_hass(hass)
|
||||||
|
|
||||||
resp = await client.get("/api/config/config_entries/entry")
|
resp = await client.get("/api/config/config_entries/entry")
|
||||||
assert resp.status == HTTPStatus.OK
|
assert resp.status == HTTPStatus.OK
|
||||||
|
@ -137,6 +155,32 @@ async def test_get_entries(hass, client, clear_handlers):
|
||||||
"disabled_by": core_ce.ConfigEntryDisabler.USER,
|
"disabled_by": core_ce.ConfigEntryDisabler.USER,
|
||||||
"reason": None,
|
"reason": None,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"domain": "comp4",
|
||||||
|
"title": "Test 4",
|
||||||
|
"source": "bla4",
|
||||||
|
"state": core_ce.ConfigEntryState.NOT_LOADED.value,
|
||||||
|
"supports_options": False,
|
||||||
|
"supports_remove_device": False,
|
||||||
|
"supports_unload": False,
|
||||||
|
"pref_disable_new_entities": False,
|
||||||
|
"pref_disable_polling": False,
|
||||||
|
"disabled_by": None,
|
||||||
|
"reason": None,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"domain": "comp5",
|
||||||
|
"title": "Test 5",
|
||||||
|
"source": "bla5",
|
||||||
|
"state": core_ce.ConfigEntryState.NOT_LOADED.value,
|
||||||
|
"supports_options": False,
|
||||||
|
"supports_remove_device": False,
|
||||||
|
"supports_unload": False,
|
||||||
|
"pref_disable_new_entities": False,
|
||||||
|
"pref_disable_polling": False,
|
||||||
|
"disabled_by": None,
|
||||||
|
"reason": None,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
resp = await client.get("/api/config/config_entries/entry?domain=comp3")
|
resp = await client.get("/api/config/config_entries/entry?domain=comp3")
|
||||||
|
@ -150,20 +194,25 @@ async def test_get_entries(hass, client, clear_handlers):
|
||||||
data = await resp.json()
|
data = await resp.json()
|
||||||
assert len(data) == 0
|
assert len(data) == 0
|
||||||
|
|
||||||
resp = await client.get(
|
resp = await client.get("/api/config/config_entries/entry?type=hub")
|
||||||
"/api/config/config_entries/entry?domain=comp3&type=integration"
|
|
||||||
)
|
|
||||||
assert resp.status == HTTPStatus.OK
|
|
||||||
data = await resp.json()
|
|
||||||
assert len(data) == 1
|
|
||||||
|
|
||||||
resp = await client.get("/api/config/config_entries/entry?type=integration")
|
|
||||||
assert resp.status == HTTPStatus.OK
|
assert resp.status == HTTPStatus.OK
|
||||||
data = await resp.json()
|
data = await resp.json()
|
||||||
assert len(data) == 2
|
assert len(data) == 2
|
||||||
assert data[0]["domain"] == "comp1"
|
assert data[0]["domain"] == "comp1"
|
||||||
assert data[1]["domain"] == "comp3"
|
assert data[1]["domain"] == "comp3"
|
||||||
|
|
||||||
|
resp = await client.get("/api/config/config_entries/entry?type=device")
|
||||||
|
assert resp.status == HTTPStatus.OK
|
||||||
|
data = await resp.json()
|
||||||
|
assert len(data) == 1
|
||||||
|
assert data[0]["domain"] == "comp4"
|
||||||
|
|
||||||
|
resp = await client.get("/api/config/config_entries/entry?type=service")
|
||||||
|
assert resp.status == HTTPStatus.OK
|
||||||
|
data = await resp.json()
|
||||||
|
assert len(data) == 1
|
||||||
|
assert data[0]["domain"] == "comp5"
|
||||||
|
|
||||||
|
|
||||||
async def test_remove_entry(hass, client):
|
async def test_remove_entry(hass, client):
|
||||||
"""Test removing an entry via the API."""
|
"""Test removing an entry via the API."""
|
||||||
|
@ -1123,7 +1172,16 @@ async def test_get_entries_ws(hass, hass_ws_client, clear_handlers):
|
||||||
mock_integration(
|
mock_integration(
|
||||||
hass, MockModule("comp2", partial_manifest={"integration_type": "helper"})
|
hass, MockModule("comp2", partial_manifest={"integration_type": "helper"})
|
||||||
)
|
)
|
||||||
mock_integration(hass, MockModule("comp3"))
|
mock_integration(
|
||||||
|
hass, MockModule("comp3", partial_manifest={"integration_type": "hub"})
|
||||||
|
)
|
||||||
|
mock_integration(
|
||||||
|
hass, MockModule("comp4", partial_manifest={"integration_type": "device"})
|
||||||
|
)
|
||||||
|
mock_integration(
|
||||||
|
hass, MockModule("comp5", partial_manifest={"integration_type": "service"})
|
||||||
|
)
|
||||||
|
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
domain="comp1",
|
domain="comp1",
|
||||||
title="Test 1",
|
title="Test 1",
|
||||||
|
@ -1143,6 +1201,16 @@ async def test_get_entries_ws(hass, hass_ws_client, clear_handlers):
|
||||||
source="bla3",
|
source="bla3",
|
||||||
disabled_by=core_ce.ConfigEntryDisabler.USER,
|
disabled_by=core_ce.ConfigEntryDisabler.USER,
|
||||||
).add_to_hass(hass)
|
).add_to_hass(hass)
|
||||||
|
MockConfigEntry(
|
||||||
|
domain="comp4",
|
||||||
|
title="Test 4",
|
||||||
|
source="bla4",
|
||||||
|
).add_to_hass(hass)
|
||||||
|
MockConfigEntry(
|
||||||
|
domain="comp5",
|
||||||
|
title="Test 5",
|
||||||
|
source="bla5",
|
||||||
|
).add_to_hass(hass)
|
||||||
|
|
||||||
ws_client = await hass_ws_client(hass)
|
ws_client = await hass_ws_client(hass)
|
||||||
|
|
||||||
|
@ -1197,6 +1265,34 @@ async def test_get_entries_ws(hass, hass_ws_client, clear_handlers):
|
||||||
"supports_unload": False,
|
"supports_unload": False,
|
||||||
"title": "Test 3",
|
"title": "Test 3",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"disabled_by": None,
|
||||||
|
"domain": "comp4",
|
||||||
|
"entry_id": ANY,
|
||||||
|
"pref_disable_new_entities": False,
|
||||||
|
"pref_disable_polling": False,
|
||||||
|
"reason": None,
|
||||||
|
"source": "bla4",
|
||||||
|
"state": "not_loaded",
|
||||||
|
"supports_options": False,
|
||||||
|
"supports_remove_device": False,
|
||||||
|
"supports_unload": False,
|
||||||
|
"title": "Test 4",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disabled_by": None,
|
||||||
|
"domain": "comp5",
|
||||||
|
"entry_id": ANY,
|
||||||
|
"pref_disable_new_entities": False,
|
||||||
|
"pref_disable_polling": False,
|
||||||
|
"reason": None,
|
||||||
|
"source": "bla5",
|
||||||
|
"state": "not_loaded",
|
||||||
|
"supports_options": False,
|
||||||
|
"supports_remove_device": False,
|
||||||
|
"supports_unload": False,
|
||||||
|
"title": "Test 5",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
await ws_client.send_json(
|
await ws_client.send_json(
|
||||||
|
@ -1204,7 +1300,7 @@ async def test_get_entries_ws(hass, hass_ws_client, clear_handlers):
|
||||||
"id": 6,
|
"id": 6,
|
||||||
"type": "config_entries/get",
|
"type": "config_entries/get",
|
||||||
"domain": "comp1",
|
"domain": "comp1",
|
||||||
"type_filter": "integration",
|
"type_filter": "hub",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
response = await ws_client.receive_json()
|
response = await ws_client.receive_json()
|
||||||
|
@ -1225,22 +1321,102 @@ async def test_get_entries_ws(hass, hass_ws_client, clear_handlers):
|
||||||
"title": "Test 1",
|
"title": "Test 1",
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
# Verify we skip broken integrations
|
|
||||||
|
|
||||||
|
await ws_client.send_json(
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"type": "config_entries/get",
|
||||||
|
"type_filter": ["service", "device"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
response = await ws_client.receive_json()
|
||||||
|
assert response["id"] == 7
|
||||||
|
assert response["result"] == [
|
||||||
|
{
|
||||||
|
"disabled_by": None,
|
||||||
|
"domain": "comp4",
|
||||||
|
"entry_id": ANY,
|
||||||
|
"pref_disable_new_entities": False,
|
||||||
|
"pref_disable_polling": False,
|
||||||
|
"reason": None,
|
||||||
|
"source": "bla4",
|
||||||
|
"state": "not_loaded",
|
||||||
|
"supports_options": False,
|
||||||
|
"supports_remove_device": False,
|
||||||
|
"supports_unload": False,
|
||||||
|
"title": "Test 4",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disabled_by": None,
|
||||||
|
"domain": "comp5",
|
||||||
|
"entry_id": ANY,
|
||||||
|
"pref_disable_new_entities": False,
|
||||||
|
"pref_disable_polling": False,
|
||||||
|
"reason": None,
|
||||||
|
"source": "bla5",
|
||||||
|
"state": "not_loaded",
|
||||||
|
"supports_options": False,
|
||||||
|
"supports_remove_device": False,
|
||||||
|
"supports_unload": False,
|
||||||
|
"title": "Test 5",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
await ws_client.send_json(
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"type": "config_entries/get",
|
||||||
|
"type_filter": "hub",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
response = await ws_client.receive_json()
|
||||||
|
assert response["id"] == 8
|
||||||
|
assert response["result"] == [
|
||||||
|
{
|
||||||
|
"disabled_by": None,
|
||||||
|
"domain": "comp1",
|
||||||
|
"entry_id": ANY,
|
||||||
|
"pref_disable_new_entities": False,
|
||||||
|
"pref_disable_polling": False,
|
||||||
|
"reason": None,
|
||||||
|
"source": "bla",
|
||||||
|
"state": "not_loaded",
|
||||||
|
"supports_options": False,
|
||||||
|
"supports_remove_device": False,
|
||||||
|
"supports_unload": False,
|
||||||
|
"title": "Test 1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disabled_by": "user",
|
||||||
|
"domain": "comp3",
|
||||||
|
"entry_id": ANY,
|
||||||
|
"pref_disable_new_entities": False,
|
||||||
|
"pref_disable_polling": False,
|
||||||
|
"reason": None,
|
||||||
|
"source": "bla3",
|
||||||
|
"state": "not_loaded",
|
||||||
|
"supports_options": False,
|
||||||
|
"supports_remove_device": False,
|
||||||
|
"supports_unload": False,
|
||||||
|
"title": "Test 3",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
# Verify we skip broken integrations
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.config.config_entries.async_get_integration",
|
"homeassistant.components.config.config_entries.async_get_integration",
|
||||||
side_effect=IntegrationNotFound("any"),
|
side_effect=IntegrationNotFound("any"),
|
||||||
):
|
):
|
||||||
await ws_client.send_json(
|
await ws_client.send_json(
|
||||||
{
|
{
|
||||||
"id": 7,
|
"id": 9,
|
||||||
"type": "config_entries/get",
|
"type": "config_entries/get",
|
||||||
"type_filter": "integration",
|
"type_filter": "hub",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
response = await ws_client.receive_json()
|
response = await ws_client.receive_json()
|
||||||
|
|
||||||
assert response["id"] == 7
|
assert response["id"] == 9
|
||||||
assert response["result"] == [
|
assert response["result"] == [
|
||||||
{
|
{
|
||||||
"disabled_by": None,
|
"disabled_by": None,
|
||||||
|
@ -1284,8 +1460,53 @@ async def test_get_entries_ws(hass, hass_ws_client, clear_handlers):
|
||||||
"supports_unload": False,
|
"supports_unload": False,
|
||||||
"title": "Test 3",
|
"title": "Test 3",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"disabled_by": None,
|
||||||
|
"domain": "comp4",
|
||||||
|
"entry_id": ANY,
|
||||||
|
"pref_disable_new_entities": False,
|
||||||
|
"pref_disable_polling": False,
|
||||||
|
"reason": None,
|
||||||
|
"source": "bla4",
|
||||||
|
"state": "not_loaded",
|
||||||
|
"supports_options": False,
|
||||||
|
"supports_remove_device": False,
|
||||||
|
"supports_unload": False,
|
||||||
|
"title": "Test 4",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disabled_by": None,
|
||||||
|
"domain": "comp5",
|
||||||
|
"entry_id": ANY,
|
||||||
|
"pref_disable_new_entities": False,
|
||||||
|
"pref_disable_polling": False,
|
||||||
|
"reason": None,
|
||||||
|
"source": "bla5",
|
||||||
|
"state": "not_loaded",
|
||||||
|
"supports_options": False,
|
||||||
|
"supports_remove_device": False,
|
||||||
|
"supports_unload": False,
|
||||||
|
"title": "Test 5",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Verify we don't send config entries when only helpers are requested
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.config.config_entries.async_get_integration",
|
||||||
|
side_effect=IntegrationNotFound("any"),
|
||||||
|
):
|
||||||
|
await ws_client.send_json(
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"type": "config_entries/get",
|
||||||
|
"type_filter": ["helper"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
response = await ws_client.receive_json()
|
||||||
|
|
||||||
|
assert response["id"] == 10
|
||||||
|
assert response["result"] == []
|
||||||
|
|
||||||
# Verify we raise if something really goes wrong
|
# Verify we raise if something really goes wrong
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
|
@ -1294,14 +1515,14 @@ async def test_get_entries_ws(hass, hass_ws_client, clear_handlers):
|
||||||
):
|
):
|
||||||
await ws_client.send_json(
|
await ws_client.send_json(
|
||||||
{
|
{
|
||||||
"id": 8,
|
"id": 11,
|
||||||
"type": "config_entries/get",
|
"type": "config_entries/get",
|
||||||
"type_filter": "integration",
|
"type_filter": ["device", "hub", "service"],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
response = await ws_client.receive_json()
|
response = await ws_client.receive_json()
|
||||||
|
|
||||||
assert response["id"] == 8
|
assert response["id"] == 11
|
||||||
assert response["success"] is False
|
assert response["success"] is False
|
||||||
|
|
||||||
|
|
||||||
|
@ -1312,7 +1533,9 @@ async def test_subscribe_entries_ws(hass, hass_ws_client, clear_handlers):
|
||||||
mock_integration(
|
mock_integration(
|
||||||
hass, MockModule("comp2", partial_manifest={"integration_type": "helper"})
|
hass, MockModule("comp2", partial_manifest={"integration_type": "helper"})
|
||||||
)
|
)
|
||||||
mock_integration(hass, MockModule("comp3"))
|
mock_integration(
|
||||||
|
hass, MockModule("comp3", partial_manifest={"integration_type": "device"})
|
||||||
|
)
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
domain="comp1",
|
domain="comp1",
|
||||||
title="Test 1",
|
title="Test 1",
|
||||||
|
@ -1476,7 +1699,12 @@ async def test_subscribe_entries_ws_filtered(hass, hass_ws_client, clear_handler
|
||||||
mock_integration(
|
mock_integration(
|
||||||
hass, MockModule("comp2", partial_manifest={"integration_type": "helper"})
|
hass, MockModule("comp2", partial_manifest={"integration_type": "helper"})
|
||||||
)
|
)
|
||||||
mock_integration(hass, MockModule("comp3"))
|
mock_integration(
|
||||||
|
hass, MockModule("comp3", partial_manifest={"integration_type": "device"})
|
||||||
|
)
|
||||||
|
mock_integration(
|
||||||
|
hass, MockModule("comp4", partial_manifest={"integration_type": "service"})
|
||||||
|
)
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
domain="comp1",
|
domain="comp1",
|
||||||
title="Test 1",
|
title="Test 1",
|
||||||
|
@ -1491,12 +1719,19 @@ async def test_subscribe_entries_ws_filtered(hass, hass_ws_client, clear_handler
|
||||||
reason="Unsupported API",
|
reason="Unsupported API",
|
||||||
)
|
)
|
||||||
entry2.add_to_hass(hass)
|
entry2.add_to_hass(hass)
|
||||||
MockConfigEntry(
|
entry3 = MockConfigEntry(
|
||||||
domain="comp3",
|
domain="comp3",
|
||||||
title="Test 3",
|
title="Test 3",
|
||||||
source="bla3",
|
source="bla3",
|
||||||
disabled_by=core_ce.ConfigEntryDisabler.USER,
|
disabled_by=core_ce.ConfigEntryDisabler.USER,
|
||||||
).add_to_hass(hass)
|
)
|
||||||
|
entry3.add_to_hass(hass)
|
||||||
|
entry4 = MockConfigEntry(
|
||||||
|
domain="comp4",
|
||||||
|
title="Test 4",
|
||||||
|
source="bla4",
|
||||||
|
)
|
||||||
|
entry4.add_to_hass(hass)
|
||||||
|
|
||||||
ws_client = await hass_ws_client(hass)
|
ws_client = await hass_ws_client(hass)
|
||||||
|
|
||||||
|
@ -1504,7 +1739,7 @@ async def test_subscribe_entries_ws_filtered(hass, hass_ws_client, clear_handler
|
||||||
{
|
{
|
||||||
"id": 5,
|
"id": 5,
|
||||||
"type": "config_entries/subscribe",
|
"type": "config_entries/subscribe",
|
||||||
"type_filter": "integration",
|
"type_filter": ["hub", "device"],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
response = await ws_client.receive_json()
|
response = await ws_client.receive_json()
|
||||||
|
@ -1551,6 +1786,8 @@ async def test_subscribe_entries_ws_filtered(hass, hass_ws_client, clear_handler
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
assert hass.config_entries.async_update_entry(entry, title="changed")
|
assert hass.config_entries.async_update_entry(entry, title="changed")
|
||||||
|
assert hass.config_entries.async_update_entry(entry3, title="changed too")
|
||||||
|
assert hass.config_entries.async_update_entry(entry4, title="changed but ignored")
|
||||||
response = await ws_client.receive_json()
|
response = await ws_client.receive_json()
|
||||||
assert response["id"] == 5
|
assert response["id"] == 5
|
||||||
assert response["event"] == [
|
assert response["event"] == [
|
||||||
|
@ -1572,6 +1809,27 @@ async def test_subscribe_entries_ws_filtered(hass, hass_ws_client, clear_handler
|
||||||
"type": "updated",
|
"type": "updated",
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
response = await ws_client.receive_json()
|
||||||
|
assert response["id"] == 5
|
||||||
|
assert response["event"] == [
|
||||||
|
{
|
||||||
|
"entry": {
|
||||||
|
"disabled_by": "user",
|
||||||
|
"domain": "comp3",
|
||||||
|
"entry_id": ANY,
|
||||||
|
"pref_disable_new_entities": False,
|
||||||
|
"pref_disable_polling": False,
|
||||||
|
"reason": None,
|
||||||
|
"source": "bla3",
|
||||||
|
"state": "not_loaded",
|
||||||
|
"supports_options": False,
|
||||||
|
"supports_remove_device": False,
|
||||||
|
"supports_unload": False,
|
||||||
|
"title": "changed too",
|
||||||
|
},
|
||||||
|
"type": "updated",
|
||||||
|
}
|
||||||
|
]
|
||||||
await hass.config_entries.async_remove(entry.entry_id)
|
await hass.config_entries.async_remove(entry.entry_id)
|
||||||
await hass.config_entries.async_remove(entry2.entry_id)
|
await hass.config_entries.async_remove(entry2.entry_id)
|
||||||
response = await ws_client.receive_json()
|
response = await ws_client.receive_json()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue