Skip to content
Open
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
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