diff --git a/homeassistant/components/growatt_server/__init__.py b/homeassistant/components/growatt_server/__init__.py index abf9118c8fa5..9bb94e9ea58f 100644 --- a/homeassistant/components/growatt_server/__init__.py +++ b/homeassistant/components/growatt_server/__init__.py @@ -23,7 +23,6 @@ Error handling pattern for reauth: → raise ConfigEntryAuthFailed - All other errors → ConfigEntryError (setup) or UpdateFailed (coordinator) """ -# pylint: disable=home-assistant-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern from collections.abc import Mapping import datetime @@ -49,7 +48,6 @@ from homeassistant.helpers.typing import ConfigType from .const import ( AUTH_API_TOKEN, AUTH_PASSWORD, - CACHED_API_KEY, CONF_AUTH_TYPE, CONF_PLANT_ID, DEFAULT_PLANT_ID, @@ -70,6 +68,11 @@ _LOGGER = logging.getLogger(__name__) CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN) +# Temporary handoff of authenticated Classic API sessions from async_migrate_entry +# to async_setup_entry. Avoids a second login() during the same startup, which +# would hit Growatt's 5-minute per-endpoint rate limit. Popped after first use. +_CACHED_APIS: dict[str, growattServer.GrowattApi] = {} + async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Growatt Server integration.""" @@ -196,8 +199,7 @@ async def async_migrate_entry( ) # Cache the logged-in API instance for reuse in async_setup_entry() - hass.data.setdefault(DOMAIN, {}) - hass.data[DOMAIN][f"{CACHED_API_KEY}{config_entry.entry_id}"] = api + _CACHED_APIS[config_entry.entry_id] = api _LOGGER.info( "Migrated config entry to use specific plant_id '%s'", @@ -347,9 +349,7 @@ async def async_setup_entry( # Check if migration cached an authenticated API instance for us to reuse. # This avoids calling login() twice (once in migration, once here) which # would trigger rate limiting. - cached_api = hass.data.get(DOMAIN, {}).pop( - f"{CACHED_API_KEY}{config_entry.entry_id}", None - ) + cached_api = _CACHED_APIS.pop(config_entry.entry_id, None) if cached_api: # Reuse the logged-in API instance from migration (rate limit optimization) diff --git a/homeassistant/components/growatt_server/const.py b/homeassistant/components/growatt_server/const.py index 02c40a17aaef..a3478c418ed4 100644 --- a/homeassistant/components/growatt_server/const.py +++ b/homeassistant/components/growatt_server/const.py @@ -55,11 +55,6 @@ BATT_MODE_LOAD_FIRST = 0 BATT_MODE_BATTERY_FIRST = 1 BATT_MODE_GRID_FIRST = 2 -# Internal key prefix for caching authenticated API instance -# Used to pass logged-in session from async_migrate_entry to async_setup_entry -# to avoid double login() calls that trigger API rate limiting -CACHED_API_KEY = "_cached_api_" - # Supported device types for coordinator creation SUPPORTED_DEVICE_TYPES = ["inverter", "tlx", "storage", "mix", "min", "sph"] diff --git a/tests/components/growatt_server/test_init.py b/tests/components/growatt_server/test_init.py index 2519ea019d32..f2499de6fd28 100644 --- a/tests/components/growatt_server/test_init.py +++ b/tests/components/growatt_server/test_init.py @@ -10,10 +10,10 @@ import pytest import requests from syrupy.assertion import SnapshotAssertion +from homeassistant.components.growatt_server import _CACHED_APIS from homeassistant.components.growatt_server.const import ( AUTH_API_TOKEN, AUTH_PASSWORD, - CACHED_API_KEY, CONF_AUTH_TYPE, CONF_PLANT_ID, DEFAULT_PLANT_ID, @@ -733,10 +733,8 @@ async def test_setup_reuses_cached_api_from_migration( # This confirms setup did NOT resolve plant_id again (optimization working) mock_growatt_classic_api.plant_list.assert_called_once_with(123456) - # Verify the cached API was removed after use (should not be in hass.data anymore) - assert f"{CACHED_API_KEY}{mock_config_entry.entry_id}" not in hass.data.get( - DOMAIN, {} - ) + # Verify the cached API was removed after use (one-time handoff) + assert mock_config_entry.entry_id not in _CACHED_APIS async def test_migrate_failure_returns_false(