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
38 changes: 18 additions & 20 deletions homeassistant/helpers/selector.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,12 @@ class Selector[_T: Mapping[str, Any]]:
# context for filtering for example. The selector defines
# which context keys it supports and what selector types
# are allowed for each key.
allowed_context_keys: dict[str, set[str]] = {}
allowed_context_keys: dict[str, set[str]]

def __init__(self, config: Mapping[str, Any] | None = None) -> None:
"""Instantiate a selector."""
self.config = self.CONFIG_SCHEMA(config)
self.allowed_context_keys = {}
Comment thread
jbouwh marked this conversation as resolved.

@override
def __eq__(self, other: object) -> bool:
Expand Down Expand Up @@ -427,11 +428,6 @@ class AttributeSelector(Selector[AttributeSelectorConfig]):

selector_type = "attribute"

allowed_context_keys = {
# Filters the available attributes based on the selected entity
"filter_entity": {"entity"}
}

CONFIG_SCHEMA = make_selector_config_schema(
{
vol.Required("entity_id"): cv.entity_id,
Expand All @@ -444,6 +440,10 @@ class AttributeSelector(Selector[AttributeSelectorConfig]):
def __init__(self, config: AttributeSelectorConfig) -> None:
"""Instantiate a selector."""
super().__init__(config)
self.allowed_context_keys = {
# Filters the available attributes based on the selected entity
"filter_entity": {"entity"}
}

def __call__(self, data: Any) -> str:
"""Validate the passed selection."""
Expand Down Expand Up @@ -1354,11 +1354,6 @@ class MediaSelector(Selector[MediaSelectorConfig]):

selector_type = "media"

allowed_context_keys = {
# Filters the available media based on the selected entity
"filter_entity": {EntitySelector.selector_type}
}

CONFIG_SCHEMA = make_selector_config_schema(
{
vol.Optional("accept"): [str],
Expand All @@ -1381,6 +1376,10 @@ class MediaSelector(Selector[MediaSelectorConfig]):
def __init__(self, config: MediaSelectorConfig | None = None) -> None:
"""Instantiate a selector."""
super().__init__(config)
self.allowed_context_keys = {
# Filters the available media based on the selected entity
"filter_entity": {EntitySelector.selector_type}
}

def __call__(self, data: Any) -> dict[str, Any] | list[dict[str, Any]]:
"""Validate the passed selection."""
Expand Down Expand Up @@ -2035,15 +2034,6 @@ class StateSelector(Selector[StateSelectorConfig]):

selector_type = "state"

allowed_context_keys = {
# Filters the available states based on the selected entity
"filter_entity": {EntitySelector.selector_type},
# Filters the available states based on the selected target
"filter_target": {"target"},
# Only show the attribute values of a specific attribute
"filter_attribute": {AttributeSelector.selector_type},
}

CONFIG_SCHEMA = make_selector_config_schema(
{
vol.Optional("entity_id"): cv.entity_id,
Expand All @@ -2056,6 +2046,14 @@ class StateSelector(Selector[StateSelectorConfig]):
def __init__(self, config: StateSelectorConfig) -> None:
"""Instantiate a selector."""
super().__init__(config)
self.allowed_context_keys = {
# Filters the available states based on the selected entity
"filter_entity": {EntitySelector.selector_type},
# Filters the available states based on the selected target
"filter_target": {"target"},
# Only show the attribute values of a specific attribute
"filter_attribute": {AttributeSelector.selector_type},
}

def __call__(self, data: Any) -> str | list[str]:
"""Validate the passed selection."""
Expand Down
24 changes: 24 additions & 0 deletions tests/helpers/test_selector.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,30 @@ def test_invalid_base_schema(schema) -> None:
selector.validate_selector(schema)


def test_allowed_context_keys_not_shared_between_instances() -> None:
"""Test allowed_context_keys is isolated between selector instances."""

class TestSelectorConfig(selector.BaseSelectorConfig, total=False):
"""Test selector config class."""

class TestSelector(selector.Selector):
"""Test selector used to verify instance isolation."""

CONFIG_SCHEMA = selector.make_selector_config_schema({})

selector_type = "test"

def __call__(self, data: Any) -> Any:
"""Validate the passed selection."""
return data

test_selector = TestSelector(TestSelectorConfig())
other_selector = TestSelector(TestSelectorConfig())
test_selector.allowed_context_keys["some_key"] = set()
assert test_selector.allowed_context_keys
assert not other_selector.allowed_context_keys


def _test_selector(
selector_type: str,
schema: dict | None,
Expand Down
Loading