Skip to content

Commit

Permalink
Seperate quota handling for system data and energy data
Browse files Browse the repository at this point in the history
  • Loading branch information
signalkraft committed Nov 1, 2024
1 parent f5f0aca commit b35b957
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 42 deletions.
5 changes: 1 addition & 4 deletions custom_components/mypyllant/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
brand = entry.options.get(OPTION_BRAND, entry.data.get(OPTION_BRAND, DEFAULT_BRAND))

hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = {
"quota_time": None,
"quota_exc_info": None,
}
hass.data[DOMAIN][entry.entry_id] = {}

_LOGGER.debug("Creating API and logging in with %s in realm %s", username, country)
api = MyPyllantAPI(
Expand Down
77 changes: 57 additions & 20 deletions custom_components/mypyllant/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ def __init__(
self.api = api
self.hass = hass
self.entry = entry
self._quota_time_key = f"quota_time_{self.__class__.__name__.lower()}"
self._quota_exc_info_key = f"quota_exc_info_{self.__class__.__name__.lower()}"

super().__init__(
hass,
Expand All @@ -62,6 +64,40 @@ def __init__(
def hass_data(self):
return self.hass.data[DOMAIN][self.entry.entry_id]

@property
def _quota_time(self) -> dt | None:
"""
Get the time when the quota was hit, separately for each subclass
"""
if self._quota_time_key not in self.hass_data:
self.hass_data[self._quota_time_key] = None
return self.hass_data[self._quota_time_key]

@_quota_time.setter
def _quota_time(self, value):
self.hass_data[self._quota_time_key] = value

@_quota_time.deleter
def _quota_time(self):
del self.hass_data[self._quota_time_key]

@property
def _quota_exc_info(self) -> BaseException | None:
"""
Get the exception that happened when the quota was hit, separately for each subclass
"""
if self._quota_exc_info_key not in self.hass_data:
self.hass_data[self._quota_exc_info_key] = None
return self.hass_data[self._quota_exc_info_key]

@_quota_exc_info.setter
def _quota_exc_info(self, value):
self.hass_data[self._quota_exc_info_key] = value

@_quota_exc_info.deleter
def _quota_exc_info(self):
del self.hass_data[self._quota_exc_info_key]

async def _refresh_session(self):
if (
self.api.oauth_session_expires is None
Expand Down Expand Up @@ -102,10 +138,11 @@ def _raise_api_down(self, exc_info: CancelledError | TimeoutError) -> None:
Sets a quota time, so the API isn't queried as often while it is down
"""
self.hass_data["quota_time"] = dt.now(timezone.utc)
self.hass_data["quota_exc_info"] = exc_info
self._quota_time = dt.now(timezone.utc)
self._quota_exc_info = exc_info
raise UpdateFailed(
f"myVAILLANT API is down, skipping update of myVAILLANT data for another {QUOTA_PAUSE_INTERVAL}s"
f"myVAILLANT API is down, skipping update of myVAILLANT {self.__class__.__name__} "
f"for another {QUOTA_PAUSE_INTERVAL}s"
) from exc_info

def _set_quota_and_raise(self, exc_info: ClientResponseError) -> None:
Expand All @@ -114,46 +151,46 @@ def _set_quota_and_raise(self, exc_info: ClientResponseError) -> None:
Raises UpdateFailed if a quota error is detected
"""
if is_quota_exceeded_exception(exc_info):
self.hass_data["quota_time"] = dt.now(timezone.utc)
self.hass_data["quota_exc_info"] = exc_info
self._quota_time = dt.now(timezone.utc)
self._quota_exc_info = exc_info
self._raise_if_quota_hit()

def _raise_if_quota_hit(self) -> None:
"""
Check if we previously hit a quota, and if the quota was hit within a certain interval
If yes, we keep raising UpdateFailed() until after the interval to avoid spamming the API
"""
quota_time: dt = self.hass_data["quota_time"]
if not quota_time:
if not self._quota_time:
return

time_elapsed = (dt.now(timezone.utc) - quota_time).seconds
exc_info: Exception = self.hass_data["quota_exc_info"]
time_elapsed = (dt.now(timezone.utc) - self._quota_time).seconds

if is_quota_exceeded_exception(exc_info):
if is_quota_exceeded_exception(self._quota_exc_info):
_LOGGER.debug(
"Quota was hit %ss ago on %s",
"Quota was hit %ss ago on %s by %s",
time_elapsed,
quota_time,
exc_info=exc_info,
self._quota_time,
self.__class__,
exc_info=self._quota_exc_info,
)
if time_elapsed < QUOTA_PAUSE_INTERVAL:
raise UpdateFailed(
f"{exc_info.message} on {exc_info.request_info.real_url}, " # type: ignore
f"skipping update of myVAILLANT data for another {QUOTA_PAUSE_INTERVAL - time_elapsed}s"
) from exc_info
f"{self._quota_exc_info.message} on {self._quota_exc_info.request_info.real_url}, " # type: ignore
f"skipping update of myVAILLANT {self.__class__.__name__} for another"
f" {QUOTA_PAUSE_INTERVAL - time_elapsed}s"
) from self._quota_exc_info
else:
_LOGGER.debug(
"myVAILLANT API is down since %ss (%s)",
time_elapsed,
quota_time,
exc_info=exc_info,
self._quota_time,
exc_info=self._quota_exc_info,
)
if time_elapsed < API_DOWN_PAUSE_INTERVAL:
raise UpdateFailed(
f"myVAILLANT API is down, skipping update of myVAILLANT data for another"
f"myVAILLANT API is down, skipping update of myVAILLANT {self.__class__.__name__} for another"
f" {API_DOWN_PAUSE_INTERVAL - time_elapsed}s"
) from exc_info
) from self._quota_exc_info


class SystemCoordinator(MyPyllantCoordinator):
Expand Down
2 changes: 1 addition & 1 deletion custom_components/mypyllant/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@
"requirements": [
"myPyllant==0.8.36"
],
"version": "v0.8.21"
"version": "v0.8.22"
}
2 changes: 1 addition & 1 deletion custom_components/mypyllant/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ def shorten_zone_name(zone_name: str) -> str:
return zone_name


def is_quota_exceeded_exception(exc_info: Exception) -> bool:
def is_quota_exceeded_exception(exc_info: BaseException | None) -> bool:
"""
Returns True if the exception is a quota exceeded ClientResponseError
"""
Expand Down
18 changes: 2 additions & 16 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,7 @@ async def system_coordinator_mock(hass, mocked_api) -> SystemCoordinator:
return_value="mockid",
) as entry:
entry.options = TEST_OPTIONS
hass.data = {
DOMAIN: {
entry.entry_id: {
"quota_time": None,
"quota_exc_info": None,
}
}
}
hass.data = {DOMAIN: {entry.entry_id: {}}}
return SystemCoordinator(hass, mocked_api, entry, update_interval=None)


Expand All @@ -111,14 +104,7 @@ async def daily_data_coordinator_mock(hass, mocked_api) -> DailyDataCoordinator:
new_callable=mock.PropertyMock,
return_value="mockid",
) as entry:
hass.data = {
DOMAIN: {
entry.entry_id: {
"quota_time": None,
"quota_exc_info": None,
}
}
}
hass.data = {DOMAIN: {entry.entry_id: {}}}
return DailyDataCoordinator(hass, mocked_api, entry, timedelta(seconds=10))


Expand Down

0 comments on commit b35b957

Please sign in to comment.