Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions homeassistant/helpers/entity_platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -883,7 +883,6 @@ async def _async_add_entity( # noqa: C901
get_initial_options=entity.get_initial_entity_options,
has_entity_name=entity.has_entity_name,
hidden_by=hidden_by,
known_object_ids=self.entities,
original_device_class=entity.device_class,
original_icon=entity.icon,
original_name=entity_name,
Expand Down Expand Up @@ -927,7 +926,7 @@ async def _async_add_entity( # noqa: C901
f"{self.entity_namespace} {suggested_object_id}"
)
entity.entity_id = entity_registry.async_generate_entity_id(
self.domain, suggested_object_id, self.entities
self.domain, suggested_object_id
)

# Make sure it is valid in case an entity set the value themselves
Expand Down
27 changes: 7 additions & 20 deletions homeassistant/helpers/entity_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from __future__ import annotations

from collections import defaultdict
from collections.abc import Callable, Container, Hashable, KeysView, Mapping
from collections.abc import Callable, Hashable, KeysView, Mapping
from datetime import datetime, timedelta
from enum import StrEnum
import logging
Expand Down Expand Up @@ -787,34 +787,25 @@ def async_device_ids(self) -> list[str]:
"""Return known device ids."""
return list(self.entities.get_device_ids())

def _entity_id_available(
self, entity_id: str, known_object_ids: Container[str] | None
) -> bool:
def _entity_id_available(self, entity_id: str) -> bool:
"""Return True if the entity_id is available.

An entity_id is available if:
- It's not registered
- It's not known by the entity component adding the entity
- It's not in the state machine
- It's available (not in the state machine and not reserved)

Note that an entity_id which belongs to a deleted entity is considered
available.
"""
if known_object_ids is None:
known_object_ids = {}

return (
entity_id not in self.entities
and entity_id not in known_object_ids
and self.hass.states.async_available(entity_id)
return entity_id not in self.entities and self.hass.states.async_available(
entity_id
)

@callback
def async_generate_entity_id(
self,
domain: str,
suggested_object_id: str,
known_object_ids: Container[str] | None = None,
) -> str:
"""Generate an entity ID that does not conflict.

Expand All @@ -826,11 +817,9 @@ def async_generate_entity_id(
raise MaxLengthExceeded(domain, "domain", MAX_LENGTH_STATE_DOMAIN)

test_string = preferred_string[:MAX_LENGTH_STATE_ENTITY_ID]
if known_object_ids is None:
known_object_ids = set()

tries = 1
while not self._entity_id_available(test_string, known_object_ids):
while not self._entity_id_available(test_string):
tries += 1
len_suffix = len(str(tries)) + 1
test_string = (
Expand All @@ -847,7 +836,6 @@ def async_get_or_create(
unique_id: str,
*,
# To influence entity ID generation
known_object_ids: Container[str] | None = None,
suggested_object_id: str | None = None,
# To disable or hide an entity if it gets created
disabled_by: RegistryEntryDisabler | None = None,
Expand Down Expand Up @@ -921,7 +909,6 @@ def async_get_or_create(
entity_id = self.async_generate_entity_id(
domain,
suggested_object_id or f"{platform}_{unique_id}",
known_object_ids,
)

if (
Expand Down Expand Up @@ -1164,7 +1151,7 @@ def _async_update_entity(
)

if new_entity_id is not UNDEFINED and new_entity_id != old.entity_id:
if not self._entity_id_available(new_entity_id, None):
if not self._entity_id_available(new_entity_id):
raise ValueError("Entity with this ID is already registered")

if not valid_entity_id(new_entity_id):
Expand Down
21 changes: 8 additions & 13 deletions tests/helpers/test_entity_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -2017,7 +2017,9 @@ async def test_disabled_entities_excluded_from_entity_list(
) == [entry1, entry2]


async def test_entity_max_length_exceeded(entity_registry: er.EntityRegistry) -> None:
async def test_entity_max_length_exceeded(
hass: HomeAssistant, entity_registry: er.EntityRegistry
) -> None:
"""Test that an exception is raised when the max character length is exceeded."""

long_domain_name = (
Expand All @@ -2042,20 +2044,13 @@ async def test_entity_max_length_exceeded(entity_registry: er.EntityRegistry) ->
"1234567890123456789012345678901234567"
)

known = []
new_id = entity_registry.async_generate_entity_id(
"sensor", long_entity_id_name, known
)
new_id = entity_registry.async_generate_entity_id("sensor", long_entity_id_name)
assert new_id == "sensor." + long_entity_id_name[: 255 - 7]
known.append(new_id)
new_id = entity_registry.async_generate_entity_id(
"sensor", long_entity_id_name, known
)
hass.states.async_reserve(new_id)
new_id = entity_registry.async_generate_entity_id("sensor", long_entity_id_name)
assert new_id == "sensor." + long_entity_id_name[: 255 - 7 - 2] + "_2"
known.append(new_id)
new_id = entity_registry.async_generate_entity_id(
"sensor", long_entity_id_name, known
)
hass.states.async_reserve(new_id)
new_id = entity_registry.async_generate_entity_id("sensor", long_entity_id_name)
assert new_id == "sensor." + long_entity_id_name[: 255 - 7 - 2] + "_3"


Expand Down