diff --git a/custom_components/hacs/__init__.py b/custom_components/hacs/__init__.py index b93426ea380..00a44dc78d1 100644 --- a/custom_components/hacs/__init__.py +++ b/custom_components/hacs/__init__.py @@ -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 @@ -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 diff --git a/custom_components/hacs/utils/store.py b/custom_components/hacs/utils/store.py index f0afa07b6bf..18ddef60514 100644 --- a/custom_components/hacs/utils/store.py +++ b/custom_components/hacs/utils/store.py @@ -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.""" @@ -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): diff --git a/tests/utils/test_store.py b/tests/utils/test_store.py index a0a3b9adb4b..f753699369c 100644 --- a/tests/utils/test_store.py +++ b/tests/utils/test_store.py @@ -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, @@ -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