Don't use storage collection helper in ExposedEntities (#92396)

* Don't use storage collection helper in ExposedEntities

* Fix tests
pull/92399/head
Erik Montnemery 2023-05-03 12:39:22 +02:00 committed by GitHub
parent 7aa94f97c0
commit 4860a8d1e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 248 additions and 299 deletions

View File

@ -84,7 +84,8 @@ class AbstractConfig(ABC):
unsub_func()
self._unsub_proactive_report = None
async def should_expose(self, entity_id):
@callback
def should_expose(self, entity_id):
"""If an entity should be exposed."""
return False

View File

@ -103,7 +103,7 @@ async def async_api_discovery(
discovery_endpoints = [
alexa_entity.serialize_discovery()
for alexa_entity in async_get_entities(hass, config)
if await config.should_expose(alexa_entity.entity_id)
if config.should_expose(alexa_entity.entity_id)
]
return directive.response(

View File

@ -30,7 +30,7 @@ class AlexaDirective:
self.entity = self.entity_id = self.endpoint = self.instance = None
async def load_entity(self, hass, config):
def load_entity(self, hass, config):
"""Set attributes related to the entity for this request.
Sets these attributes when self.has_endpoint is True:
@ -49,7 +49,7 @@ class AlexaDirective:
self.entity_id = _endpoint_id.replace("#", ".")
self.entity = hass.states.get(self.entity_id)
if not self.entity or not await config.should_expose(self.entity_id):
if not self.entity or not config.should_expose(self.entity_id):
raise AlexaInvalidEndpointError(_endpoint_id)
self.endpoint = ENTITY_ADAPTERS[self.entity.domain](hass, config, self.entity)

View File

@ -34,7 +34,7 @@ async def async_handle_message(hass, config, request, context=None, enabled=True
await config.set_authorized(True)
if directive.has_endpoint:
await directive.load_entity(hass, config)
directive.load_entity(hass, config)
funct_ref = HANDLERS.get((directive.namespace, directive.name))
if funct_ref:

View File

@ -60,7 +60,8 @@ class AlexaConfig(AbstractConfig):
"""Return an identifier for the user that represents this config."""
return ""
async def should_expose(self, entity_id):
@core.callback
def should_expose(self, entity_id):
"""If an entity should be exposed."""
if not self._config[CONF_FILTER].empty_filter:
return self._config[CONF_FILTER](entity_id)

View File

@ -64,7 +64,7 @@ async def async_enable_proactive_mode(hass, smart_home_config):
if new_state.domain not in ENTITY_ADAPTERS:
return
if not await smart_home_config.should_expose(changed_entity):
if not smart_home_config.should_expose(changed_entity):
_LOGGER.debug("Not exposing %s because filtered by config", changed_entity)
return

View File

@ -257,14 +257,15 @@ class CloudAlexaConfig(alexa_config.AbstractConfig):
and entity_supported(self.hass, entity_id)
)
async def should_expose(self, entity_id):
@callback
def should_expose(self, entity_id):
"""If an entity should be exposed."""
if not self._config[CONF_FILTER].empty_filter:
if entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
return False
return self._config[CONF_FILTER](entity_id)
return await async_should_expose(self.hass, CLOUD_ALEXA, entity_id)
return async_should_expose(self.hass, CLOUD_ALEXA, entity_id)
@callback
def async_invalidate_access_token(self):
@ -423,7 +424,7 @@ class CloudAlexaConfig(alexa_config.AbstractConfig):
is_enabled = self.enabled
for entity in alexa_entities.async_get_entities(self.hass, self):
if is_enabled and await self.should_expose(entity.entity_id):
if is_enabled and self.should_expose(entity.entity_id):
to_update.append(entity.entity_id)
else:
to_remove.append(entity.entity_id)
@ -482,7 +483,7 @@ class CloudAlexaConfig(alexa_config.AbstractConfig):
entity_id = event.data["entity_id"]
if not await self.should_expose(entity_id):
if not self.should_expose(entity_id):
return
action = event.data["action"]

View File

@ -222,9 +222,9 @@ class CloudGoogleConfig(AbstractConfig):
self._handle_device_registry_updated,
)
async def should_expose(self, state):
def should_expose(self, state):
"""If a state object should be exposed."""
return await self._should_expose_entity_id(state.entity_id)
return self._should_expose_entity_id(state.entity_id)
def _should_expose_legacy(self, entity_id):
"""If an entity ID should be exposed."""
@ -258,14 +258,14 @@ class CloudGoogleConfig(AbstractConfig):
and _supported_legacy(self.hass, entity_id)
)
async def _should_expose_entity_id(self, entity_id):
def _should_expose_entity_id(self, entity_id):
"""If an entity should be exposed."""
if not self._config[CONF_FILTER].empty_filter:
if entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
return False
return self._config[CONF_FILTER](entity_id)
return await async_should_expose(self.hass, CLOUD_GOOGLE, entity_id)
return async_should_expose(self.hass, CLOUD_GOOGLE, entity_id)
@property
def agent_user_id(self):
@ -358,7 +358,8 @@ class CloudGoogleConfig(AbstractConfig):
"""Handle updated preferences."""
self.async_schedule_google_sync_all()
async def _handle_entity_registry_updated(self, event: Event) -> None:
@callback
def _handle_entity_registry_updated(self, event: Event) -> None:
"""Handle when entity registry updated."""
if (
not self.enabled
@ -375,11 +376,12 @@ class CloudGoogleConfig(AbstractConfig):
entity_id = event.data["entity_id"]
if not await self._should_expose_entity_id(entity_id):
if not self._should_expose_entity_id(entity_id):
return
self.async_schedule_google_sync_all()
@callback
async def _handle_device_registry_updated(self, event: Event) -> None:
"""Handle when device registry updated."""
if (
@ -394,15 +396,13 @@ class CloudGoogleConfig(AbstractConfig):
return
# Check if any exposed entity uses the device area
used = False
for entity_entry in er.async_entries_for_device(
er.async_get(self.hass), event.data["device_id"]
if not any(
entity_entry.area_id is None
and self._should_expose_entity_id(entity_entry.entity_id)
for entity_entry in er.async_entries_for_device(
er.async_get(self.hass), event.data["device_id"]
)
):
if entity_entry.area_id is None and await self._should_expose_entity_id(
entity_entry.entity_id
):
used = True
if not used:
return
self.async_schedule_google_sync_all()

View File

@ -94,7 +94,7 @@ CONFIG_SCHEMA = vol.Schema(
def _get_agent_manager(hass: HomeAssistant) -> AgentManager:
"""Get the active agent."""
manager = AgentManager(hass)
hass.async_create_task(manager.async_setup())
manager.async_setup()
return manager
@ -393,9 +393,9 @@ class AgentManager:
self._agents: dict[str, AbstractConversationAgent] = {}
self._builtin_agent_init_lock = asyncio.Lock()
async def async_setup(self) -> None:
def async_setup(self) -> None:
"""Set up the conversation agents."""
await async_setup_default_agent(self.hass)
async_setup_default_agent(self.hass)
async def async_get_agent(
self, agent_id: str | None = None

View File

@ -73,20 +73,23 @@ def _get_language_variations(language: str) -> Iterable[str]:
yield lang
async def async_setup(hass: core.HomeAssistant) -> None:
@core.callback
def async_setup(hass: core.HomeAssistant) -> None:
"""Set up entity registry listener for the default agent."""
entity_registry = er.async_get(hass)
for entity_id in entity_registry.entities:
await async_should_expose(hass, DOMAIN, entity_id)
async_should_expose(hass, DOMAIN, entity_id)
async def async_handle_entity_registry_changed(event: core.Event) -> None:
@core.callback
def async_handle_entity_registry_changed(event: core.Event) -> None:
"""Set expose flag on newly created entities."""
if event.data["action"] == "create":
await async_should_expose(hass, DOMAIN, event.data["entity_id"])
async_should_expose(hass, DOMAIN, event.data["entity_id"])
hass.bus.async_listen(
er.EVENT_ENTITY_REGISTRY_UPDATED,
async_handle_entity_registry_changed,
run_immediately=True,
)
@ -154,7 +157,7 @@ class DefaultAgent(AbstractConversationAgent):
conversation_id,
)
slot_lists = await self._make_slot_lists()
slot_lists = self._make_slot_lists()
result = await self.hass.async_add_executor_job(
self._recognize,
@ -483,7 +486,7 @@ class DefaultAgent(AbstractConversationAgent):
"""Handle updated preferences."""
self._slot_lists = None
async def _make_slot_lists(self) -> dict[str, SlotList]:
def _make_slot_lists(self) -> dict[str, SlotList]:
"""Create slot lists with areas and entity names/aliases."""
if self._slot_lists is not None:
return self._slot_lists
@ -493,7 +496,7 @@ class DefaultAgent(AbstractConversationAgent):
entities = [
entity
for entity in entity_registry.entities.values()
if await async_should_expose(self.hass, DOMAIN, entity.entity_id)
if async_should_expose(self.hass, DOMAIN, entity.entity_id)
]
devices = dr.async_get(self.hass)

View File

@ -175,7 +175,7 @@ class AbstractConfig(ABC):
"""Get agent user ID from context."""
@abstractmethod
async def should_expose(self, state) -> bool:
def should_expose(self, state) -> bool:
"""Return if entity should be exposed."""
def should_2fa(self, state):
@ -535,14 +535,16 @@ class GoogleEntity:
]
return self._traits
async def should_expose(self):
@callback
def should_expose(self):
"""If entity should be exposed."""
return await self.config.should_expose(self.state)
return self.config.should_expose(self.state)
async def should_expose_local(self) -> bool:
@callback
def should_expose_local(self) -> bool:
"""Return if the entity should be exposed locally."""
return (
await self.should_expose()
self.should_expose()
and get_google_type(
self.state.domain, self.state.attributes.get(ATTR_DEVICE_CLASS)
)
@ -585,7 +587,7 @@ class GoogleEntity:
trait.might_2fa(domain, features, device_class) for trait in self.traits()
)
async def sync_serialize(self, agent_user_id, instance_uuid):
def sync_serialize(self, agent_user_id, instance_uuid):
"""Serialize entity for a SYNC response.
https://developers.google.com/actions/smarthome/create-app#actiondevicessync
@ -621,7 +623,7 @@ class GoogleEntity:
device["name"]["nicknames"].extend(entity_entry.aliases)
# Add local SDK info if enabled
if self.config.is_local_sdk_active and await self.should_expose_local():
if self.config.is_local_sdk_active and self.should_expose_local():
device["otherDeviceIds"] = [{"deviceId": self.entity_id}]
device["customData"] = {
"webhookId": self.config.get_local_webhook_id(agent_user_id),

View File

@ -111,7 +111,7 @@ class GoogleConfig(AbstractConfig):
"""Return if states should be proactively reported."""
return self._config.get(CONF_REPORT_STATE)
async def should_expose(self, state) -> bool:
def should_expose(self, state) -> bool:
"""Return if entity should be exposed."""
expose_by_default = self._config.get(CONF_EXPOSE_BY_DEFAULT)
exposed_domains = self._config.get(CONF_EXPOSED_DOMAINS)

View File

@ -63,7 +63,7 @@ def async_enable_report_state(hass: HomeAssistant, google_config: AbstractConfig
if not new_state:
return
if not await google_config.should_expose(new_state):
if not google_config.should_expose(new_state):
return
entity = GoogleEntity(hass, google_config, new_state)
@ -115,7 +115,7 @@ def async_enable_report_state(hass: HomeAssistant, google_config: AbstractConfig
checker = await create_checker(hass, DOMAIN, extra_significant_check)
for entity in async_get_entities(hass, google_config):
if not await entity.should_expose():
if not entity.should_expose():
continue
try:

View File

@ -87,11 +87,11 @@ async def async_devices_sync_response(hass, config, agent_user_id):
devices = []
for entity in entities:
if not await entity.should_expose():
if not entity.should_expose():
continue
try:
devices.append(await entity.sync_serialize(agent_user_id, instance_uuid))
devices.append(entity.sync_serialize(agent_user_id, instance_uuid))
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Error serializing %s", entity.entity_id)
@ -318,7 +318,7 @@ async def async_devices_reachable(
"devices": [
entity.reachable_device_serialize()
for entity in async_get_entities(hass, data.config)
if entity.entity_id in google_ids and await entity.should_expose_local()
if entity.entity_id in google_ids and entity.should_expose_local()
]
}

View File

@ -343,7 +343,7 @@ async def async_setup(hass: ha.HomeAssistant, config: ConfigType) -> bool: # no
)
exposed_entities = ExposedEntities(hass)
await exposed_entities.async_load()
await exposed_entities.async_initialize()
hass.data[DATA_EXPOSED_ENTITIES] = exposed_entities
return True

View File

@ -4,7 +4,7 @@ from __future__ import annotations
from collections.abc import Callable, Mapping
import dataclasses
from itertools import chain
from typing import Any
from typing import Any, TypedDict
import voluptuous as vol
@ -15,11 +15,6 @@ from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES
from homeassistant.core import HomeAssistant, callback, split_entity_id
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.collection import (
IDManager,
SerializedStorageCollection,
StorageCollection,
)
from homeassistant.helpers.entity import get_device_class
from homeassistant.helpers.storage import Store
@ -89,30 +84,21 @@ class ExposedEntity:
assistants: dict[str, dict[str, Any]]
def to_json(self, entity_id: str) -> dict[str, Any]:
def to_json(self) -> dict[str, Any]:
"""Return a JSON serializable representation for storage."""
return {
"assistants": self.assistants,
"id": entity_id,
}
class SerializedExposedEntities(SerializedStorageCollection):
class SerializedExposedEntities(TypedDict):
"""Serialized exposed entities storage storage collection."""
assistants: dict[str, dict[str, Any]]
exposed_entities: dict[str, dict[str, Any]]
class ExposedEntitiesIDManager(IDManager):
"""ID manager for tags."""
def generate_id(self, suggestion: str) -> str:
"""Generate an ID."""
assert not self.has_id(suggestion)
return suggestion
class ExposedEntities(StorageCollection[ExposedEntity, SerializedExposedEntities]):
class ExposedEntities:
"""Control assistant settings.
Settings for entities without a unique_id are stored in the store.
@ -120,21 +106,23 @@ class ExposedEntities(StorageCollection[ExposedEntity, SerializedExposedEntities
"""
_assistants: dict[str, AssistantPreferences]
entities: dict[str, ExposedEntity]
def __init__(self, hass: HomeAssistant) -> None:
"""Initialize."""
super().__init__(
Store(hass, STORAGE_VERSION, STORAGE_KEY), ExposedEntitiesIDManager()
)
self._hass = hass
self._listeners: dict[str, list[Callable[[], None]]] = {}
self._store: Store[SerializedExposedEntities] = Store(
hass, STORAGE_VERSION, STORAGE_KEY
)
async def async_load(self) -> None:
async def async_initialize(self) -> None:
"""Finish initializing."""
await super().async_load()
websocket_api.async_register_command(self.hass, ws_expose_entity)
websocket_api.async_register_command(self.hass, ws_expose_new_entities_get)
websocket_api.async_register_command(self.hass, ws_expose_new_entities_set)
websocket_api.async_register_command(self.hass, ws_list_exposed_entities)
websocket_api.async_register_command(self._hass, ws_expose_entity)
websocket_api.async_register_command(self._hass, ws_expose_new_entities_get)
websocket_api.async_register_command(self._hass, ws_expose_new_entities_set)
websocket_api.async_register_command(self._hass, ws_list_exposed_entities)
await self._async_load_data()
@callback
def async_listen_entity_updates(
@ -143,18 +131,17 @@ class ExposedEntities(StorageCollection[ExposedEntity, SerializedExposedEntities
"""Listen for updates to entity expose settings."""
self._listeners.setdefault(assistant, []).append(listener)
async def async_expose_entity(
@callback
def async_expose_entity(
self, assistant: str, entity_id: str, should_expose: bool
) -> None:
"""Expose an entity to an assistant.
Notify listeners if expose flag was changed.
"""
entity_registry = er.async_get(self.hass)
entity_registry = er.async_get(self._hass)
if not (registry_entry := entity_registry.async_get(entity_id)):
return await self._async_expose_legacy_entity(
assistant, entity_id, should_expose
)
return self._async_expose_legacy_entity(assistant, entity_id, should_expose)
assistant_options: Mapping[str, Any]
if (
@ -169,7 +156,7 @@ class ExposedEntities(StorageCollection[ExposedEntity, SerializedExposedEntities
for listener in self._listeners.get(assistant, []):
listener()
async def _async_expose_legacy_entity(
def _async_expose_legacy_entity(
self, assistant: str, entity_id: str, should_expose: bool
) -> None:
"""Expose an entity to an assistant.
@ -177,23 +164,20 @@ class ExposedEntities(StorageCollection[ExposedEntity, SerializedExposedEntities
Notify listeners if expose flag was changed.
"""
if (
(exposed_entity := self.data.get(entity_id))
(exposed_entity := self.entities.get(entity_id))
and (assistant_options := exposed_entity.assistants.get(assistant, {}))
and assistant_options.get("should_expose") == should_expose
):
return
if exposed_entity:
await self.async_update_item(
entity_id, {"assistants": {assistant: {"should_expose": should_expose}}}
new_exposed_entity = self._update_exposed_entity(
assistant, entity_id, should_expose
)
else:
await self.async_create_item(
{
"entity_id": entity_id,
"assistants": {assistant: {"should_expose": should_expose}},
}
)
new_exposed_entity = self._new_exposed_entity(assistant, should_expose)
self.entities[entity_id] = new_exposed_entity
self._async_schedule_save()
for listener in self._listeners.get(assistant, []):
listener()
@ -215,11 +199,11 @@ class ExposedEntities(StorageCollection[ExposedEntity, SerializedExposedEntities
self, assistant: str
) -> dict[str, Mapping[str, Any]]:
"""Get all entity expose settings for an assistant."""
entity_registry = er.async_get(self.hass)
entity_registry = er.async_get(self._hass)
result: dict[str, Mapping[str, Any]] = {}
options: Mapping | None
for entity_id, exposed_entity in self.data.items():
for entity_id, exposed_entity in self.entities.items():
if options := exposed_entity.assistants.get(assistant):
result[entity_id] = options
@ -232,13 +216,13 @@ class ExposedEntities(StorageCollection[ExposedEntity, SerializedExposedEntities
@callback
def async_get_entity_settings(self, entity_id: str) -> dict[str, Mapping[str, Any]]:
"""Get assistant expose settings for an entity."""
entity_registry = er.async_get(self.hass)
entity_registry = er.async_get(self._hass)
result: dict[str, Mapping[str, Any]] = {}
assistant_settings: Mapping
if registry_entry := entity_registry.async_get(entity_id):
assistant_settings = registry_entry.options
elif exposed_entity := self.data.get(entity_id):
elif exposed_entity := self.entities.get(entity_id):
assistant_settings = exposed_entity.assistants
else:
raise HomeAssistantError("Unknown entity")
@ -249,16 +233,17 @@ class ExposedEntities(StorageCollection[ExposedEntity, SerializedExposedEntities
return result
async def async_should_expose(self, assistant: str, entity_id: str) -> bool:
@callback
def async_should_expose(self, assistant: str, entity_id: str) -> bool:
"""Return True if an entity should be exposed to an assistant."""
should_expose: bool
if entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
return False
entity_registry = er.async_get(self.hass)
entity_registry = er.async_get(self._hass)
if not (registry_entry := entity_registry.async_get(entity_id)):
return await self._async_should_expose_legacy_entity(assistant, entity_id)
return self._async_should_expose_legacy_entity(assistant, entity_id)
if assistant in registry_entry.options:
if "should_expose" in registry_entry.options[assistant]:
should_expose = registry_entry.options[assistant]["should_expose"]
@ -277,14 +262,14 @@ class ExposedEntities(StorageCollection[ExposedEntity, SerializedExposedEntities
return should_expose
async def _async_should_expose_legacy_entity(
def _async_should_expose_legacy_entity(
self, assistant: str, entity_id: str
) -> bool:
"""Return True if an entity should be exposed to an assistant."""
should_expose: bool
if (
exposed_entity := self.data.get(entity_id)
exposed_entity := self.entities.get(entity_id)
) and assistant in exposed_entity.assistants:
if "should_expose" in exposed_entity.assistants[assistant]:
should_expose = exposed_entity.assistants[assistant]["should_expose"]
@ -296,16 +281,13 @@ class ExposedEntities(StorageCollection[ExposedEntity, SerializedExposedEntities
should_expose = False
if exposed_entity:
await self.async_update_item(
entity_id, {"assistants": {assistant: {"should_expose": should_expose}}}
new_exposed_entity = self._update_exposed_entity(
assistant, entity_id, should_expose
)
else:
await self.async_create_item(
{
"entity_id": entity_id,
"assistants": {assistant: {"should_expose": should_expose}},
}
)
new_exposed_entity = self._new_exposed_entity(assistant, should_expose)
self.entities[entity_id] = new_exposed_entity
self._async_schedule_save()
return should_expose
@ -323,7 +305,7 @@ class ExposedEntities(StorageCollection[ExposedEntity, SerializedExposedEntities
if domain in DEFAULT_EXPOSED_DOMAINS:
return True
device_class = get_device_class(self.hass, entity_id)
device_class = get_device_class(self._hass, entity_id)
if (
domain == "binary_sensor"
and device_class in DEFAULT_EXPOSED_BINARY_SENSOR_DEVICE_CLASSES
@ -335,73 +317,66 @@ class ExposedEntities(StorageCollection[ExposedEntity, SerializedExposedEntities
return False
async def _process_create_data(self, data: dict) -> dict:
"""Validate the config is valid."""
return data
@callback
def _get_suggested_id(self, info: dict) -> str:
"""Suggest an ID based on the config."""
entity_id: str = info["entity_id"]
return entity_id
async def _update_data(
self, item: ExposedEntity, update_data: dict
def _update_exposed_entity(
self,
assistant: str,
entity_id: str,
should_expose: bool,
) -> ExposedEntity:
"""Return a new updated item."""
new_assistant_settings: dict[str, Any] = update_data["assistants"]
old_assistant_settings = item.assistants
for assistant, old_settings in old_assistant_settings.items():
new_settings = new_assistant_settings.get(assistant, {})
new_assistant_settings[assistant] = old_settings | new_settings
return dataclasses.replace(item, assistants=new_assistant_settings)
"""Update an exposed entity."""
entity = self.entities[entity_id]
assistants = dict(entity.assistants)
old_settings = assistants.get(assistant, {})
assistants[assistant] = old_settings | {"should_expose": should_expose}
return ExposedEntity(assistants)
def _create_item(self, item_id: str, data: dict) -> ExposedEntity:
"""Create an item from validated config."""
def _new_exposed_entity(self, assistant: str, should_expose: bool) -> ExposedEntity:
"""Create a new exposed entity."""
return ExposedEntity(
assistants=data["assistants"],
assistants={assistant: {"should_expose": should_expose}},
)
def _deserialize_item(self, data: dict) -> ExposedEntity:
"""Create an item from its serialized representation."""
return ExposedEntity(
assistants=data["assistants"],
)
def _serialize_item(self, item_id: str, item: ExposedEntity) -> dict:
"""Return the serialized representation of an item for storing."""
return item.to_json(item_id)
async def _async_load_data(self) -> SerializedExposedEntities | None:
"""Load from the store."""
data = await super()._async_load_data()
data = await self._store.async_load()
assistants: dict[str, AssistantPreferences] = {}
exposed_entities: dict[str, ExposedEntity] = {}
if data and "assistants" in data:
if data:
for domain, preferences in data["assistants"].items():
assistants[domain] = AssistantPreferences(**preferences)
self._assistants = assistants
if data and "exposed_entities" in data:
for entity_id, preferences in data["exposed_entities"].items():
exposed_entities[entity_id] = ExposedEntity(**preferences)
if data and "items" not in data:
return None # type: ignore[unreachable]
self._assistants = assistants
self.entities = exposed_entities
return data
@callback
def _async_schedule_save(self) -> None:
"""Schedule saving the preferences."""
self._store.async_delay_save(self._data_to_save, SAVE_DELAY)
@callback
def _data_to_save(self) -> SerializedExposedEntities:
"""Return JSON-compatible date for storing to file."""
base_data = super()._base_data_to_save()
return {
"items": base_data["items"],
"assistants": {
domain: preferences.to_json()
for domain, preferences in self._assistants.items()
},
"exposed_entities": {
entity_id: entity.to_json()
for entity_id, entity in self.entities.items()
},
}
@callback
@websocket_api.require_admin
@websocket_api.websocket_command(
{
@ -411,8 +386,7 @@ class ExposedEntities(StorageCollection[ExposedEntity, SerializedExposedEntities
vol.Required("should_expose"): bool,
}
)
@websocket_api.async_response
async def ws_expose_entity(
def ws_expose_entity(
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any]
) -> None:
"""Expose an entity to an assistant."""
@ -434,7 +408,7 @@ async def ws_expose_entity(
exposed_entities: ExposedEntities = hass.data[DATA_EXPOSED_ENTITIES]
for entity_id in entity_ids:
for assistant in msg["assistants"]:
await exposed_entities.async_expose_entity(
exposed_entities.async_expose_entity(
assistant, entity_id, msg["should_expose"]
)
connection.send_result(msg["id"])
@ -455,7 +429,7 @@ def ws_list_exposed_entities(
exposed_entities: ExposedEntities = hass.data[DATA_EXPOSED_ENTITIES]
entity_registry = er.async_get(hass)
for entity_id in chain(exposed_entities.data, entity_registry.entities):
for entity_id in chain(exposed_entities.entities, entity_registry.entities):
result[entity_id] = {}
entity_settings = async_get_entity_settings(hass, entity_id)
for assistant, settings in entity_settings.items():
@ -527,7 +501,8 @@ def async_get_entity_settings(
return exposed_entities.async_get_entity_settings(entity_id)
async def async_expose_entity(
@callback
def async_expose_entity(
hass: HomeAssistant,
assistant: str,
entity_id: str,
@ -535,12 +510,11 @@ async def async_expose_entity(
) -> None:
"""Get assistant expose settings for an entity."""
exposed_entities: ExposedEntities = hass.data[DATA_EXPOSED_ENTITIES]
await exposed_entities.async_expose_entity(assistant, entity_id, should_expose)
exposed_entities.async_expose_entity(assistant, entity_id, should_expose)
async def async_should_expose(
hass: HomeAssistant, assistant: str, entity_id: str
) -> bool:
@callback
def async_should_expose(hass: HomeAssistant, assistant: str, entity_id: str) -> bool:
"""Return True if an entity should be exposed to an assistant."""
exposed_entities: ExposedEntities = hass.data[DATA_EXPOSED_ENTITIES]
return await exposed_entities.async_should_expose(assistant, entity_id)
return exposed_entities.async_should_expose(assistant, entity_id)

View File

@ -138,6 +138,6 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
for assistant, settings in expose_settings.items():
if (should_expose := settings.get("should_expose")) is None:
continue
await exposed_entities.async_expose_entity(
exposed_entities.async_expose_entity(
hass, assistant, switch_entity_id, should_expose
)

View File

@ -111,7 +111,7 @@ class BaseEntity(Entity):
return
registry.async_update_entity(self.entity_id, name=wrapped_switch.name)
async def copy_expose_settings() -> None:
def copy_expose_settings() -> None:
"""Copy assistant expose settings from the wrapped entity.
Also unexpose the wrapped entity if exposed.
@ -122,15 +122,15 @@ class BaseEntity(Entity):
for assistant, settings in expose_settings.items():
if (should_expose := settings.get("should_expose")) is None:
continue
await exposed_entities.async_expose_entity(
exposed_entities.async_expose_entity(
self.hass, assistant, self.entity_id, should_expose
)
await exposed_entities.async_expose_entity(
exposed_entities.async_expose_entity(
self.hass, assistant, self._switch_entity_id, False
)
copy_custom_name(wrapped_switch)
await copy_expose_settings()
copy_expose_settings()
class BaseToggleEntity(BaseEntity, ToggleEntity):

View File

@ -2450,18 +2450,13 @@ async def test_exclude_filters(hass: HomeAssistant) -> None:
hass.states.async_set("cover.deny", "off", {"friendly_name": "Blocked cover"})
alexa_config = MockConfig(hass)
filter = entityfilter.generate_filter(
alexa_config.should_expose = entityfilter.generate_filter(
include_domains=[],
include_entities=[],
exclude_domains=["script"],
exclude_entities=["cover.deny"],
)
async def mock_should_expose(entity_id):
return filter(entity_id)
alexa_config.should_expose = mock_should_expose
msg = await smart_home.async_handle_message(hass, alexa_config, request)
await hass.async_block_till_done()
@ -2486,18 +2481,13 @@ async def test_include_filters(hass: HomeAssistant) -> None:
hass.states.async_set("group.allow", "off", {"friendly_name": "Allowed group"})
alexa_config = MockConfig(hass)
filter = entityfilter.generate_filter(
alexa_config.should_expose = entityfilter.generate_filter(
include_domains=["automation", "group"],
include_entities=["script.deny"],
exclude_domains=[],
exclude_entities=[],
)
async def mock_should_expose(entity_id):
return filter(entity_id)
alexa_config.should_expose = mock_should_expose
msg = await smart_home.async_handle_message(hass, alexa_config, request)
await hass.async_block_till_done()
@ -2516,18 +2506,13 @@ async def test_never_exposed_entities(hass: HomeAssistant) -> None:
hass.states.async_set("group.allow", "off", {"friendly_name": "Allowed group"})
alexa_config = MockConfig(hass)
filter = entityfilter.generate_filter(
alexa_config.should_expose = entityfilter.generate_filter(
include_domains=["group"],
include_entities=[],
exclude_domains=[],
exclude_entities=[],
)
async def mock_should_expose(entity_id):
return filter(entity_id)
alexa_config.should_expose = mock_should_expose
msg = await smart_home.async_handle_message(hass, alexa_config, request)
await hass.async_block_till_done()

View File

@ -38,10 +38,10 @@ def expose_new(hass, expose_new):
exposed_entities.async_set_expose_new_entities("cloud.alexa", expose_new)
async def expose_entity(hass, entity_id, should_expose):
def expose_entity(hass, entity_id, should_expose):
"""Expose an entity to Alexa."""
exposed_entities: ExposedEntities = hass.data[DATA_EXPOSED_ENTITIES]
await exposed_entities.async_expose_entity("cloud.alexa", entity_id, should_expose)
exposed_entities.async_expose_entity("cloud.alexa", entity_id, should_expose)
async def test_alexa_config_expose_entity_prefs(
@ -95,35 +95,35 @@ async def test_alexa_config_expose_entity_prefs(
alexa_report_state=False,
)
expose_new(hass, True)
await expose_entity(hass, entity_entry5.entity_id, False)
expose_entity(hass, entity_entry5.entity_id, False)
conf = alexa_config.CloudAlexaConfig(
hass, ALEXA_SCHEMA({}), "mock-user-id", cloud_prefs, cloud_stub
)
await conf.async_initialize()
# an entity which is not in the entity registry can be exposed
await expose_entity(hass, "light.kitchen", True)
assert await conf.should_expose("light.kitchen")
expose_entity(hass, "light.kitchen", True)
assert conf.should_expose("light.kitchen")
# categorized and hidden entities should not be exposed
assert not await conf.should_expose(entity_entry1.entity_id)
assert not await conf.should_expose(entity_entry2.entity_id)
assert not await conf.should_expose(entity_entry3.entity_id)
assert not await conf.should_expose(entity_entry4.entity_id)
assert not conf.should_expose(entity_entry1.entity_id)
assert not conf.should_expose(entity_entry2.entity_id)
assert not conf.should_expose(entity_entry3.entity_id)
assert not conf.should_expose(entity_entry4.entity_id)
# this has been hidden
assert not await conf.should_expose(entity_entry5.entity_id)
assert not conf.should_expose(entity_entry5.entity_id)
# exposed by default
assert await conf.should_expose(entity_entry6.entity_id)
assert conf.should_expose(entity_entry6.entity_id)
await expose_entity(hass, entity_entry5.entity_id, True)
assert await conf.should_expose(entity_entry5.entity_id)
expose_entity(hass, entity_entry5.entity_id, True)
assert conf.should_expose(entity_entry5.entity_id)
await expose_entity(hass, entity_entry5.entity_id, None)
assert not await conf.should_expose(entity_entry5.entity_id)
expose_entity(hass, entity_entry5.entity_id, None)
assert not conf.should_expose(entity_entry5.entity_id)
assert "alexa" not in hass.config.components
await hass.async_block_till_done()
assert "alexa" in hass.config.components
assert not await conf.should_expose(entity_entry5.entity_id)
assert not conf.should_expose(entity_entry5.entity_id)
async def test_alexa_config_report_state(
@ -368,7 +368,7 @@ async def test_alexa_update_expose_trigger_sync(
await conf.async_initialize()
with patch_sync_helper() as (to_update, to_remove):
await expose_entity(hass, light_entry.entity_id, True)
expose_entity(hass, light_entry.entity_id, True)
await hass.async_block_till_done()
async_fire_time_changed(hass, fire_all=True)
await hass.async_block_till_done()
@ -378,9 +378,9 @@ async def test_alexa_update_expose_trigger_sync(
assert to_remove == []
with patch_sync_helper() as (to_update, to_remove):
await expose_entity(hass, light_entry.entity_id, False)
await expose_entity(hass, binary_sensor_entry.entity_id, True)
await expose_entity(hass, sensor_entry.entity_id, True)
expose_entity(hass, light_entry.entity_id, False)
expose_entity(hass, binary_sensor_entry.entity_id, True)
expose_entity(hass, sensor_entry.entity_id, True)
await hass.async_block_till_done()
async_fire_time_changed(hass, fire_all=True)
await hass.async_block_till_done()
@ -586,7 +586,7 @@ async def test_alexa_config_migrate_expose_entity_prefs(
alexa_report_state=False,
alexa_settings_version=1,
)
await expose_entity(hass, entity_migrated.entity_id, False)
expose_entity(hass, entity_migrated.entity_id, False)
cloud_prefs._prefs[PREF_ALEXA_ENTITY_CONFIGS]["light.unknown"] = {
PREF_SHOULD_EXPOSE: True

View File

@ -265,13 +265,13 @@ async def test_google_config_expose_entity(
state = State(entity_entry.entity_id, "on")
gconf = await cloud_client.get_google_config()
assert await gconf.should_expose(state)
assert gconf.should_expose(state)
await exposed_entities.async_expose_entity(
exposed_entities.async_expose_entity(
"cloud.google_assistant", entity_entry.entity_id, False
)
assert not await gconf.should_expose(state)
assert not gconf.should_expose(state)
async def test_google_config_should_2fa(

View File

@ -46,10 +46,10 @@ def expose_new(hass, expose_new):
exposed_entities.async_set_expose_new_entities("cloud.google_assistant", expose_new)
async def expose_entity(hass, entity_id, should_expose):
def expose_entity(hass, entity_id, should_expose):
"""Expose an entity to Google."""
exposed_entities: ExposedEntities = hass.data[DATA_EXPOSED_ENTITIES]
await exposed_entities.async_expose_entity(
exposed_entities.async_expose_entity(
"cloud.google_assistant", entity_id, should_expose
)
@ -150,7 +150,7 @@ async def test_google_update_expose_trigger_sync(
with patch.object(config, "async_sync_entities") as mock_sync, patch.object(
ga_helpers, "SYNC_DELAY", 0
):
await expose_entity(hass, light_entry.entity_id, True)
expose_entity(hass, light_entry.entity_id, True)
await hass.async_block_till_done()
async_fire_time_changed(hass, utcnow())
await hass.async_block_till_done()
@ -160,9 +160,9 @@ async def test_google_update_expose_trigger_sync(
with patch.object(config, "async_sync_entities") as mock_sync, patch.object(
ga_helpers, "SYNC_DELAY", 0
):
await expose_entity(hass, light_entry.entity_id, False)
await expose_entity(hass, binary_sensor_entry.entity_id, True)
await expose_entity(hass, sensor_entry.entity_id, True)
expose_entity(hass, light_entry.entity_id, False)
expose_entity(hass, binary_sensor_entry.entity_id, True)
expose_entity(hass, sensor_entry.entity_id, True)
await hass.async_block_till_done()
async_fire_time_changed(hass, utcnow())
await hass.async_block_till_done()
@ -384,7 +384,7 @@ async def test_google_config_expose_entity_prefs(
)
expose_new(hass, True)
await expose_entity(hass, entity_entry5.entity_id, False)
expose_entity(hass, entity_entry5.entity_id, False)
state = State("light.kitchen", "on")
state_config = State(entity_entry1.entity_id, "on")
@ -395,23 +395,23 @@ async def test_google_config_expose_entity_prefs(
state_exposed_default = State(entity_entry6.entity_id, "on")
# an entity which is not in the entity registry can be exposed
await expose_entity(hass, "light.kitchen", True)
assert await mock_conf.should_expose(state)
expose_entity(hass, "light.kitchen", True)
assert mock_conf.should_expose(state)
# categorized and hidden entities should not be exposed
assert not await mock_conf.should_expose(state_config)
assert not await mock_conf.should_expose(state_diagnostic)
assert not await mock_conf.should_expose(state_hidden_integration)
assert not await mock_conf.should_expose(state_hidden_user)
assert not mock_conf.should_expose(state_config)
assert not mock_conf.should_expose(state_diagnostic)
assert not mock_conf.should_expose(state_hidden_integration)
assert not mock_conf.should_expose(state_hidden_user)
# this has been hidden
assert not await mock_conf.should_expose(state_not_exposed)
assert not mock_conf.should_expose(state_not_exposed)
# exposed by default
assert await mock_conf.should_expose(state_exposed_default)
assert mock_conf.should_expose(state_exposed_default)
await expose_entity(hass, entity_entry5.entity_id, True)
assert await mock_conf.should_expose(state_not_exposed)
expose_entity(hass, entity_entry5.entity_id, True)
assert mock_conf.should_expose(state_not_exposed)
await expose_entity(hass, entity_entry5.entity_id, None)
assert not await mock_conf.should_expose(state_not_exposed)
expose_entity(hass, entity_entry5.entity_id, None)
assert not mock_conf.should_expose(state_not_exposed)
def test_enabled_requires_valid_sub(
@ -535,7 +535,7 @@ async def test_google_config_migrate_expose_entity_prefs(
google_report_state=False,
google_settings_version=1,
)
await expose_entity(hass, entity_migrated.entity_id, False)
expose_entity(hass, entity_migrated.entity_id, False)
cloud_prefs._prefs[PREF_GOOGLE_ENTITY_CONFIGS]["light.unknown"] = {
PREF_SHOULD_EXPOSE: True

View File

@ -51,9 +51,7 @@ def expose_new(hass, expose_new):
exposed_entities.async_set_expose_new_entities(conversation.DOMAIN, expose_new)
async def expose_entity(hass, entity_id, should_expose):
def expose_entity(hass, entity_id, should_expose):
"""Expose an entity to the default agent."""
exposed_entities: ExposedEntities = hass.data[DATA_EXPOSED_ENTITIES]
await exposed_entities.async_expose_entity(
conversation.DOMAIN, entity_id, should_expose
)
exposed_entities.async_expose_entity(conversation.DOMAIN, entity_id, should_expose)

View File

@ -108,7 +108,7 @@ async def test_exposed_areas(
hass.states.async_set(bedroom_light.entity_id, "on")
# Hide the bedroom light
await expose_entity(hass, bedroom_light.entity_id, False)
expose_entity(hass, bedroom_light.entity_id, False)
result = await conversation.async_converse(
hass, "turn on lights in the kitchen", None, Context(), None

View File

@ -680,7 +680,7 @@ async def test_http_processing_intent_entity_exposed(
}
# Unexpose the entity
await expose_entity(hass, "light.kitchen", False)
expose_entity(hass, "light.kitchen", False)
await hass.async_block_till_done()
client = await hass_client()
@ -730,7 +730,7 @@ async def test_http_processing_intent_entity_exposed(
}
# Now expose the entity
await expose_entity(hass, "light.kitchen", True)
expose_entity(hass, "light.kitchen", True)
await hass.async_block_till_done()
client = await hass_client()
@ -845,7 +845,7 @@ async def test_http_processing_intent_conversion_not_expose_new(
}
# Expose the entity
await expose_entity(hass, "light.kitchen", True)
expose_entity(hass, "light.kitchen", True)
await hass.async_block_till_done()
resp = await client.post(

View File

@ -58,7 +58,7 @@ class MockConfig(helpers.AbstractConfig):
"""Get agent user ID making request."""
return context.user_id
async def should_expose(self, state):
def should_expose(self, state):
"""Expose it all."""
return self._should_expose is None or self._should_expose(state)

View File

@ -49,13 +49,13 @@ async def test_google_entity_sync_serialize_with_local_sdk(hass: HomeAssistant)
)
entity = helpers.GoogleEntity(hass, config, hass.states.get("light.ceiling_lights"))
serialized = await entity.sync_serialize(None, "mock-uuid")
serialized = entity.sync_serialize(None, "mock-uuid")
assert "otherDeviceIds" not in serialized
assert "customData" not in serialized
config.async_enable_local_sdk()
serialized = await entity.sync_serialize("mock-user-id", "abcdef")
serialized = entity.sync_serialize("mock-user-id", "abcdef")
assert serialized["otherDeviceIds"] == [{"deviceId": "light.ceiling_lights"}]
assert serialized["customData"] == {
"httpPort": 1234,
@ -68,7 +68,7 @@ async def test_google_entity_sync_serialize_with_local_sdk(hass: HomeAssistant)
"homeassistant.components.google_assistant.helpers.get_google_type",
return_value=device_type,
):
serialized = await entity.sync_serialize(None, "mock-uuid")
serialized = entity.sync_serialize(None, "mock-uuid")
assert "otherDeviceIds" not in serialized
assert "customData" not in serialized

View File

@ -257,9 +257,7 @@ async def test_should_expose(hass: HomeAssistant) -> None:
await hass.async_block_till_done()
assert (
await config.should_expose(
State(DOMAIN + ".mock", "mock", {"view": "not None"})
)
config.should_expose(State(DOMAIN + ".mock", "mock", {"view": "not None"}))
is False
)
@ -267,10 +265,7 @@ async def test_should_expose(hass: HomeAssistant) -> None:
# Wait for google_assistant.helpers.async_initialize.sync_google to be called
await hass.async_block_till_done()
assert (
await config.should_expose(State(CLOUD_NEVER_EXPOSED_ENTITIES[0], "mock"))
is False
)
assert config.should_expose(State(CLOUD_NEVER_EXPOSED_ENTITIES[0], "mock")) is False
async def test_missing_service_account(hass: HomeAssistant) -> None:

View File

@ -909,7 +909,7 @@ async def test_serialize_input_boolean(hass: HomeAssistant) -> None:
"""Test serializing an input boolean entity."""
state = State("input_boolean.bla", "on")
entity = sh.GoogleEntity(hass, BASIC_CONFIG, state)
result = await entity.sync_serialize(None, "mock-uuid")
result = entity.sync_serialize(None, "mock-uuid")
assert result == {
"id": "input_boolean.bla",
"attributes": {},

View File

@ -101,21 +101,21 @@ async def test_load_preferences(hass: HomeAssistant) -> None:
exposed_entities.async_set_expose_new_entities("test1", True)
exposed_entities.async_set_expose_new_entities("test2", False)
await exposed_entities.async_expose_entity("test1", "light.kitchen", True)
await exposed_entities.async_expose_entity("test1", "light.living_room", True)
await exposed_entities.async_expose_entity("test2", "light.kitchen", True)
await exposed_entities.async_expose_entity("test2", "light.kitchen", True)
exposed_entities.async_expose_entity("test1", "light.kitchen", True)
exposed_entities.async_expose_entity("test1", "light.living_room", True)
exposed_entities.async_expose_entity("test2", "light.kitchen", True)
exposed_entities.async_expose_entity("test2", "light.kitchen", True)
assert list(exposed_entities._assistants) == ["test1", "test2"]
assert list(exposed_entities.data) == ["light.kitchen", "light.living_room"]
assert list(exposed_entities.entities) == ["light.kitchen", "light.living_room"]
await flush_store(exposed_entities.store)
await flush_store(exposed_entities._store)
exposed_entities2 = ExposedEntities(hass)
await exposed_entities2.async_load()
await exposed_entities2.async_initialize()
assert exposed_entities._assistants == exposed_entities2._assistants
assert exposed_entities.data == exposed_entities2.data
assert exposed_entities.entities == exposed_entities2.entities
async def test_expose_entity(
@ -132,7 +132,7 @@ async def test_expose_entity(
entry2 = entity_registry.async_get_or_create("test", "test", "unique2")
exposed_entities: ExposedEntities = hass.data[DATA_EXPOSED_ENTITIES]
assert len(exposed_entities.data) == 0
assert len(exposed_entities.entities) == 0
# Set options
await ws_client.send_json_auto_id(
@ -151,7 +151,7 @@ async def test_expose_entity(
assert entry1.options == {"cloud.alexa": {"should_expose": True}}
entry2 = entity_registry.async_get(entry2.entity_id)
assert entry2.options == {}
assert len(exposed_entities.data) == 0
assert len(exposed_entities.entities) == 0
# Update options
await ws_client.send_json_auto_id(
@ -176,7 +176,7 @@ async def test_expose_entity(
"cloud.alexa": {"should_expose": False},
"cloud.google_assistant": {"should_expose": False},
}
assert len(exposed_entities.data) == 0
assert len(exposed_entities.entities) == 0
async def test_expose_entity_unknown(
@ -189,7 +189,7 @@ async def test_expose_entity_unknown(
await hass.async_block_till_done()
exposed_entities: ExposedEntities = hass.data[DATA_EXPOSED_ENTITIES]
assert len(exposed_entities.data) == 0
assert len(exposed_entities.entities) == 0
# Set options
await ws_client.send_json_auto_id(
@ -204,8 +204,8 @@ async def test_expose_entity_unknown(
response = await ws_client.receive_json()
assert response["success"]
assert len(exposed_entities.data) == 1
assert exposed_entities.data == {
assert len(exposed_entities.entities) == 1
assert exposed_entities.entities == {
"test.test": ExposedEntity({"cloud.alexa": {"should_expose": True}})
}
@ -222,8 +222,8 @@ async def test_expose_entity_unknown(
response = await ws_client.receive_json()
assert response["success"]
assert len(exposed_entities.data) == 2
assert exposed_entities.data == {
assert len(exposed_entities.entities) == 2
assert exposed_entities.entities == {
"test.test": ExposedEntity(
{
"cloud.alexa": {"should_expose": False},
@ -292,7 +292,7 @@ async def test_expose_new_entities(
assert response["result"] == {"expose_new": False}
# Check if exposed - should be False
assert await async_should_expose(hass, "cloud.alexa", entry1.entity_id) is False
assert async_should_expose(hass, "cloud.alexa", entry1.entity_id) is False
# Expose new entities to Alexa
await ws_client.send_json_auto_id(
@ -315,12 +315,10 @@ async def test_expose_new_entities(
assert response["result"] == {"expose_new": expose_new}
# Check again if exposed - should still be False
assert await async_should_expose(hass, "cloud.alexa", entry1.entity_id) is False
assert async_should_expose(hass, "cloud.alexa", entry1.entity_id) is False
# Check if exposed - should be True
assert (
await async_should_expose(hass, "cloud.alexa", entry2.entity_id) == expose_new
)
assert async_should_expose(hass, "cloud.alexa", entry2.entity_id) == expose_new
async def test_listen_updates(
@ -342,21 +340,21 @@ async def test_listen_updates(
entry = entity_registry.async_get_or_create("climate", "test", "unique1")
# Call for another assistant - listener not called
await exposed_entities.async_expose_entity(
exposed_entities.async_expose_entity(
"cloud.google_assistant", entry.entity_id, True
)
assert len(calls) == 0
# Call for our assistant - listener called
await exposed_entities.async_expose_entity("cloud.alexa", entry.entity_id, True)
exposed_entities.async_expose_entity("cloud.alexa", entry.entity_id, True)
assert len(calls) == 1
# Settings not changed - listener not called
await exposed_entities.async_expose_entity("cloud.alexa", entry.entity_id, True)
exposed_entities.async_expose_entity("cloud.alexa", entry.entity_id, True)
assert len(calls) == 1
# Settings changed - listener called
await exposed_entities.async_expose_entity("cloud.alexa", entry.entity_id, False)
exposed_entities.async_expose_entity("cloud.alexa", entry.entity_id, False)
assert len(calls) == 2
@ -375,10 +373,8 @@ async def test_get_assistant_settings(
assert async_get_assistant_settings(hass, "cloud.alexa") == {}
await exposed_entities.async_expose_entity("cloud.alexa", entry.entity_id, True)
await exposed_entities.async_expose_entity(
"cloud.alexa", "light.not_in_registry", True
)
exposed_entities.async_expose_entity("cloud.alexa", entry.entity_id, True)
exposed_entities.async_expose_entity("cloud.alexa", "light.not_in_registry", True)
assert async_get_assistant_settings(hass, "cloud.alexa") == snapshot
assert async_get_assistant_settings(hass, "cloud.google_assistant") == snapshot
@ -412,45 +408,38 @@ async def test_should_expose(
assert response["success"]
# Unknown entity is not exposed
assert await async_should_expose(hass, "test.test", "test.test") is False
assert async_should_expose(hass, "test.test", "test.test") is False
# Blocked entity is not exposed
assert await async_should_expose(hass, "cloud.alexa", entities["blocked"]) is False
assert async_should_expose(hass, "cloud.alexa", entities["blocked"]) is False
# Lock is exposed
assert await async_should_expose(hass, "cloud.alexa", entities["lock"]) is True
assert async_should_expose(hass, "cloud.alexa", entities["lock"]) is True
# Binary sensor without device class is not exposed
assert (
await async_should_expose(hass, "cloud.alexa", entities["binary_sensor"])
is False
)
assert async_should_expose(hass, "cloud.alexa", entities["binary_sensor"]) is False
# Binary sensor with certain device class is exposed
assert (
await async_should_expose(hass, "cloud.alexa", entities["door_sensor"]) is True
)
assert async_should_expose(hass, "cloud.alexa", entities["door_sensor"]) is True
# Sensor without device class is not exposed
assert await async_should_expose(hass, "cloud.alexa", entities["sensor"]) is False
assert async_should_expose(hass, "cloud.alexa", entities["sensor"]) is False
# Sensor with certain device class is exposed
assert (
await async_should_expose(hass, "cloud.alexa", entities["temperature_sensor"])
is True
async_should_expose(hass, "cloud.alexa", entities["temperature_sensor"]) is True
)
# The second time we check, it should load it from storage
assert (
await async_should_expose(hass, "cloud.alexa", entities["temperature_sensor"])
is True
async_should_expose(hass, "cloud.alexa", entities["temperature_sensor"]) is True
)
# Check with a different assistant
exposed_entities: ExposedEntities = hass.data[DATA_EXPOSED_ENTITIES]
exposed_entities.async_set_expose_new_entities("cloud.no_default_expose", False)
assert (
await async_should_expose(
async_should_expose(
hass, "cloud.no_default_expose", entities["temperature_sensor"]
)
is False
@ -481,13 +470,13 @@ async def test_should_expose_hidden_categorized(
entity_registry.async_get_or_create(
"lock", "test", "unique2", hidden_by=er.RegistryEntryHider.USER
)
assert await async_should_expose(hass, "cloud.alexa", "lock.test_unique2") is False
assert async_should_expose(hass, "cloud.alexa", "lock.test_unique2") is False
# Entity with category is not exposed
entity_registry.async_get_or_create(
"lock", "test", "unique3", entity_category=EntityCategory.CONFIG
)
assert await async_should_expose(hass, "cloud.alexa", "lock.test_unique3") is False
assert async_should_expose(hass, "cloud.alexa", "lock.test_unique3") is False
async def test_list_exposed_entities(
@ -555,8 +544,8 @@ async def test_listeners(
callbacks = []
exposed_entities.async_listen_entity_updates("test1", lambda: callbacks.append(1))
await async_expose_entity(hass, "test1", "light.kitchen", True)
async_expose_entity(hass, "test1", "light.kitchen", True)
assert len(callbacks) == 1
entry1 = entity_registry.async_get_or_create("switch", "test", "unique1")
await async_expose_entity(hass, "test1", entry1.entity_id, True)
async_expose_entity(hass, "test1", entry1.entity_id, True)

View File

@ -702,7 +702,7 @@ async def test_import_expose_settings_1(
original_name="ABC",
)
for assistant, should_expose in EXPOSE_SETTINGS.items():
await exposed_entities.async_expose_entity(
exposed_entities.async_expose_entity(
hass, assistant, switch_entity_entry.entity_id, should_expose
)
@ -760,7 +760,7 @@ async def test_import_expose_settings_2(
original_name="ABC",
)
for assistant, should_expose in EXPOSE_SETTINGS.items():
await exposed_entities.async_expose_entity(
exposed_entities.async_expose_entity(
hass, assistant, switch_entity_entry.entity_id, should_expose
)
@ -785,7 +785,7 @@ async def test_import_expose_settings_2(
suggested_object_id="abc",
)
for assistant, should_expose in EXPOSE_SETTINGS.items():
await exposed_entities.async_expose_entity(
exposed_entities.async_expose_entity(
hass, assistant, switch_as_x_entity_entry.entity_id, not should_expose
)
@ -850,7 +850,7 @@ async def test_restore_expose_settings(
suggested_object_id="abc",
)
for assistant, should_expose in EXPOSE_SETTINGS.items():
await exposed_entities.async_expose_entity(
exposed_entities.async_expose_entity(
hass, assistant, switch_as_x_entity_entry.entity_id, should_expose
)