Repository: home-assistant/core
File: homeassistant/components/websocket_api/automation.py (Python)

Code before:

    domains: set[str | None] = set()
    for entity_filter_config in entity_filters_config:
        filter_integration = entity_filter_config.get("integration")
        filter_domains = entity_filter_config.get("domain", [])

        if not filter_domains and not filter_integration:
            domains.add(None)
            continue

        if filter_integration:
            domains.add(filter_integration)

        for domain in filter_domains:
            domains.add(domain)

    return domains


def _get_automation_component_lookup_table(
    hass: HomeAssistant, component_descriptions: Mapping[str, Mapping[str, Any] | None]
) -> _AutomationComponentLookupTable:
    """Get a dict of automation components keyed by domain, along with the total number of components."""

    if AUTOMATION_COMPONENT_LOOKUP_CACHE not in hass.data:
        hass.data[AUTOMATION_COMPONENT_LOOKUP_CACHE] = []

    cache = hass.data[AUTOMATION_COMPONENT_LOOKUP_CACHE]
    for cached_descriptions, cached_lookup in cache:
        if cached_descriptions is component_descriptions:
            _LOGGER.debug("Using cached automation component lookup data")
            return cached_lookup

    lookup_table = _AutomationComponentLookupTable(
        domain_components={}, component_count=0
    )
    for component, description in component_descriptions.items():
        if description is None or CONF_TARGET not in description:
            _LOGGER.debug("Skipping component %s without target description", component)
            continue
        domains = _get_automation_component_domains(description[CONF_TARGET])
        lookup_data = _AutomationComponentLookupData.create(
            component, description[CONF_TARGET]
        )
        for domain in domains:
            lookup_table.domain_components.setdefault(domain, []).append(lookup_data)
        lookup_table.component_count += 1

    cache.append((component_descriptions, lookup_table))
    if len(cache) > 3:  # Should have a max of 3: triggers, conditions, services
        cache.pop(0)

Review comment (performance):
I know this is copying an existing pattern, but why don't we just create the cache when setting up the websocket handlers so we don't need to check if we have this key each time?

As an alternative, do this - `try` is basically free in recent Python releases, you only pay for exception handling and that is guaranteed to only happen once in the lifetime of the program:

```py
try:
    cache = hass.data[AUTOMATION_COMPONENT_LOOKUP_CACHE]
except KeyError;
    cache = hass.data[AUTOMATION_COMPONENT_LOOKUP_CACHE] = []
```

We should improve the other caches with this pattern in the same manner.

Code after:

    domains: set[str | None] = set()
    for entity_filter_config in entity_filters_config:
        filter_integration = entity_filter_config.get("integration")
        filter_domains = entity_filter_config.get("domain", [])

        if not filter_domains and not filter_integration:
            domains.add(None)
            continue

        if filter_integration:
            domains.add(filter_integration)

        for domain in filter_domains:
            domains.add(domain)

    return domains


def _get_automation_component_lookup_table(
    hass: HomeAssistant,
    component_type: str,
    component_descriptions: Mapping[str, Mapping[str, Any] | None],
) -> _AutomationComponentLookupTable:
    """Get a dict of automation components keyed by domain, along with the total number of components.

    Returns a cached object if available.
    """

    try:
        cache = hass.data[AUTOMATION_COMPONENT_LOOKUP_CACHE]
    except KeyError:
        cache = hass.data[AUTOMATION_COMPONENT_LOOKUP_CACHE] = {}

    if (cached := cache.get(component_type)) is not None:
        cached_descriptions, cached_lookup = cached
        if cached_descriptions is component_descriptions:
            _LOGGER.debug(
                "Using cached automation component lookup data for %s", component_type
            )
            return cached_lookup

    lookup_table = _AutomationComponentLookupTable(
        domain_components={}, component_count=0
    )
    for component, description in component_descriptions.items():
        if description is None or CONF_TARGET not in description:
            _LOGGER.debug("Skipping component %s without target description", component)
            continue
        domains = _get_automation_component_domains(description[CONF_TARGET])
        lookup_data = _AutomationComponentLookupData.create(