Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 2 additions & 0 deletions custom_components/hacs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from .frontend import async_register_frontend
from .utils.data import HacsData
from .utils.queue_manager import QueueManager
from .utils.store import STORE_CACHE_KEY
from .utils.version import version_left_higher_or_equal_then_right
from .websocket import async_register_websocket_commands

Expand Down Expand Up @@ -218,6 +219,7 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
hacs.disable_hacs(HacsDisabledReason.REMOVED)

hass.data.pop(DOMAIN, None)
hass.data.pop(STORE_CACHE_KEY, None)

return unload_ok

Expand Down
13 changes: 11 additions & 2 deletions custom_components/hacs/utils/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

_LOGGER = LOGGER

STORE_CACHE_KEY = "hacs_store_cache"


class HACSStore(Store):
"""A subclass of Store that allows multiple loads in the executor."""
Expand Down Expand Up @@ -43,8 +45,15 @@ def _get_store_for_key(hass, key, encoder):


def get_store_for_key(hass, key):
"""Create a Store object for the key."""
return _get_store_for_key(hass, key, JSONEncoder)
"""Get (or create and cache) the Store object for the key.

The cache is cleared in async_unload_entry so Store instances do not
survive an integration unload / reload.
"""
cache = hass.data.setdefault(STORE_CACHE_KEY, {})
if key not in cache:
cache[key] = _get_store_for_key(hass, key, JSONEncoder)
return cache[key]


async def async_load_from_store(hass, key):
Expand Down
18 changes: 18 additions & 0 deletions tests/utils/test_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from custom_components.hacs.const import VERSION_STORAGE
from custom_components.hacs.exceptions import HacsException
from custom_components.hacs.utils.store import (
STORE_CACHE_KEY,
async_load_from_store,
async_remove_store,
async_save_to_store,
Expand Down Expand Up @@ -64,3 +65,20 @@ async def test_store_store(hass: HomeAssistant, caplog: pytest.LogCaptureFixture

await async_save_to_store(hass, "test", {"test": "test"})
assert async_save_mock.call_count == 1


async def test_store_instance_cached_per_key(hass: HomeAssistant) -> None:
"""Repeated calls for the same key must return the same Store instance.

Store.async_delay_save() debounces via instance state, so callers that
schedule delayed writes need the same Store object each time.
"""
assert get_store_for_key(hass, "test") is get_store_for_key(hass, "test")
assert get_store_for_key(hass, "test") is not get_store_for_key(hass, "other")


async def test_store_cache_cleared_on_unload(hass: HomeAssistant) -> None:
"""Clearing STORE_CACHE_KEY (as async_unload_entry does) drops cached Stores."""
first = get_store_for_key(hass, "test")
hass.data.pop(STORE_CACHE_KEY, None)
assert get_store_for_key(hass, "test") is not first
Loading