mirror of
https://github.com/jpawlowski/hass.tibber_prices.git
synced 2026-03-30 05:13:40 +00:00
fix
This commit is contained in:
parent
130b51f5b6
commit
86c8073a51
2 changed files with 70 additions and 106 deletions
|
|
@ -6,7 +6,7 @@ import asyncio
|
||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from enum import Enum, auto
|
from enum import Enum
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
@ -24,13 +24,6 @@ HTTP_UNAUTHORIZED = 401
|
||||||
HTTP_FORBIDDEN = 403
|
HTTP_FORBIDDEN = 403
|
||||||
|
|
||||||
|
|
||||||
class TransformMode(Enum):
|
|
||||||
"""Data transformation mode."""
|
|
||||||
|
|
||||||
TRANSFORM = auto() # Transform price info data
|
|
||||||
SKIP = auto() # Return raw data without transformation
|
|
||||||
|
|
||||||
|
|
||||||
class QueryType(Enum):
|
class QueryType(Enum):
|
||||||
"""Types of queries that can be made to the API."""
|
"""Types of queries that can be made to the API."""
|
||||||
|
|
||||||
|
|
@ -248,44 +241,16 @@ def _prepare_headers(access_token: str) -> dict[str, str]:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _transform_data(data: dict, query_type: QueryType) -> dict:
|
def _flatten_price_info(subscription: dict) -> dict:
|
||||||
"""Transform API response data based on query type."""
|
"""Transform and flatten priceInfo from full API data structure."""
|
||||||
if not data or "viewer" not in data:
|
price_info = subscription.get("priceInfo", {})
|
||||||
_LOGGER.debug("No data to transform or missing viewer key")
|
|
||||||
return data
|
|
||||||
|
|
||||||
_LOGGER.debug("Starting data transformation for query type %s", query_type)
|
|
||||||
|
|
||||||
if query_type == QueryType.PRICE_INFO:
|
|
||||||
return _transform_price_info(data)
|
|
||||||
if query_type in (
|
|
||||||
QueryType.DAILY_RATING,
|
|
||||||
QueryType.HOURLY_RATING,
|
|
||||||
QueryType.MONTHLY_RATING,
|
|
||||||
):
|
|
||||||
return data
|
|
||||||
if query_type == QueryType.VIEWER:
|
|
||||||
return data
|
|
||||||
|
|
||||||
_LOGGER.warning("Unknown query type %s, returning raw data", query_type)
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
def _transform_price_info(data: dict) -> dict:
|
|
||||||
"""Transform the price info data structure."""
|
|
||||||
if not data or "viewer" not in data:
|
|
||||||
_LOGGER.debug("No data to transform or missing viewer key")
|
|
||||||
return data
|
|
||||||
|
|
||||||
_LOGGER.debug("Starting price info transformation")
|
|
||||||
price_info = data["viewer"]["homes"][0]["currentSubscription"]["priceInfo"]
|
|
||||||
|
|
||||||
# Get today and yesterday dates using Home Assistant's dt_util
|
# Get today and yesterday dates using Home Assistant's dt_util
|
||||||
today_local = dt_util.now().date()
|
today_local = dt_util.now().date()
|
||||||
yesterday_local = today_local - timedelta(days=1)
|
yesterday_local = today_local - timedelta(days=1)
|
||||||
_LOGGER.debug("Processing data for yesterday's date: %s", yesterday_local)
|
_LOGGER.debug("Processing data for yesterday's date: %s", yesterday_local)
|
||||||
|
|
||||||
# Transform edges data
|
# Transform edges data (extract yesterday's prices)
|
||||||
if "range" in price_info and "edges" in price_info["range"]:
|
if "range" in price_info and "edges" in price_info["range"]:
|
||||||
edges = price_info["range"]["edges"]
|
edges = price_info["range"]["edges"]
|
||||||
yesterday_prices = []
|
yesterday_prices = []
|
||||||
|
|
@ -315,12 +280,6 @@ def _transform_price_info(data: dict) -> dict:
|
||||||
price_info["yesterday"] = yesterday_prices
|
price_info["yesterday"] = yesterday_prices
|
||||||
del price_info["range"]
|
del price_info["range"]
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
def _flatten_price_info(subscription: dict) -> dict:
|
|
||||||
"""Extract and flatten priceInfo from subscription."""
|
|
||||||
price_info = subscription.get("priceInfo", {})
|
|
||||||
return {
|
return {
|
||||||
"yesterday": price_info.get("yesterday", []),
|
"yesterday": price_info.get("yesterday", []),
|
||||||
"today": price_info.get("today", []),
|
"today": price_info.get("today", []),
|
||||||
|
|
@ -538,7 +497,7 @@ class TibberPricesApiClient:
|
||||||
|
|
||||||
await _verify_graphql_response(response_json, query_type)
|
await _verify_graphql_response(response_json, query_type)
|
||||||
|
|
||||||
return _transform_data(response_json["data"], query_type)
|
return response_json["data"]
|
||||||
|
|
||||||
async def _handle_request(
|
async def _handle_request(
|
||||||
self,
|
self,
|
||||||
|
|
|
||||||
|
|
@ -167,6 +167,36 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict]):
|
||||||
async def _async_update_data(self) -> dict:
|
async def _async_update_data(self) -> dict:
|
||||||
"""Fetch new state data for the coordinator. Handles expired credentials by raising ConfigEntryAuthFailed."""
|
"""Fetch new state data for the coordinator. Handles expired credentials by raising ConfigEntryAuthFailed."""
|
||||||
if self._cached_price_data is None:
|
if self._cached_price_data is None:
|
||||||
|
await self._handle_initialization()
|
||||||
|
try:
|
||||||
|
current_time = dt_util.now()
|
||||||
|
if self._force_update:
|
||||||
|
LOGGER.debug(
|
||||||
|
"Force updating data",
|
||||||
|
extra={
|
||||||
|
"reason": "force_update",
|
||||||
|
"last_success": self.last_update_success,
|
||||||
|
"last_price_update": self._last_price_update,
|
||||||
|
"last_rating_updates": {
|
||||||
|
"hourly": self._last_rating_update_hourly,
|
||||||
|
"daily": self._last_rating_update_daily,
|
||||||
|
"monthly": self._last_rating_update_monthly,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self._force_update = False
|
||||||
|
return await self._fetch_all_data()
|
||||||
|
return await self._handle_conditional_update(current_time)
|
||||||
|
except (
|
||||||
|
TibberPricesApiClientAuthenticationError,
|
||||||
|
TimeoutError,
|
||||||
|
TibberPricesApiClientCommunicationError,
|
||||||
|
TibberPricesApiClientError,
|
||||||
|
) as exception:
|
||||||
|
return await self._handle_update_exception(exception)
|
||||||
|
|
||||||
|
async def _handle_initialization(self) -> None:
|
||||||
|
"""Handle initialization and related errors for cached price data."""
|
||||||
try:
|
try:
|
||||||
await self._async_initialize()
|
await self._async_initialize()
|
||||||
except TimeoutError as exception:
|
except TimeoutError as exception:
|
||||||
|
|
@ -193,35 +223,17 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict]):
|
||||||
extra={"error": str(exception), "error_type": "unexpected_init"},
|
extra={"error": str(exception), "error_type": "unexpected_init"},
|
||||||
)
|
)
|
||||||
raise UpdateFailed(msg) from exception
|
raise UpdateFailed(msg) from exception
|
||||||
try:
|
|
||||||
current_time = dt_util.now()
|
async def _handle_update_exception(self, exception: Exception) -> dict:
|
||||||
result = None
|
"""Handle exceptions during update and return fallback or raise."""
|
||||||
if self._force_update:
|
if isinstance(exception, TibberPricesApiClientAuthenticationError):
|
||||||
LOGGER.debug(
|
|
||||||
"Force updating data",
|
|
||||||
extra={
|
|
||||||
"reason": "force_update",
|
|
||||||
"last_success": self.last_update_success,
|
|
||||||
"last_price_update": self._last_price_update,
|
|
||||||
"last_rating_updates": {
|
|
||||||
"hourly": self._last_rating_update_hourly,
|
|
||||||
"daily": self._last_rating_update_daily,
|
|
||||||
"monthly": self._last_rating_update_monthly,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
self._force_update = False
|
|
||||||
result = await self._fetch_all_data()
|
|
||||||
else:
|
|
||||||
result = await self._handle_conditional_update(current_time)
|
|
||||||
except TibberPricesApiClientAuthenticationError as exception:
|
|
||||||
msg = "Authentication failed: credentials expired or invalid"
|
msg = "Authentication failed: credentials expired or invalid"
|
||||||
LOGGER.error(
|
LOGGER.error(
|
||||||
"Authentication failed (likely expired credentials)",
|
"Authentication failed (likely expired credentials)",
|
||||||
extra={"error": str(exception), "error_type": "auth_failed"},
|
extra={"error": str(exception), "error_type": "auth_failed"},
|
||||||
)
|
)
|
||||||
raise ConfigEntryAuthFailed(msg) from exception
|
raise ConfigEntryAuthFailed(msg) from exception
|
||||||
except TimeoutError as exception:
|
if isinstance(exception, TimeoutError):
|
||||||
msg = "Timeout during data update"
|
msg = "Timeout during data update"
|
||||||
LOGGER.warning(
|
LOGGER.warning(
|
||||||
"%s: %s",
|
"%s: %s",
|
||||||
|
|
@ -233,11 +245,6 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict]):
|
||||||
LOGGER.info("Using cached data as fallback after timeout")
|
LOGGER.info("Using cached data as fallback after timeout")
|
||||||
return self._merge_all_cached_data()
|
return self._merge_all_cached_data()
|
||||||
raise UpdateFailed(msg) from exception
|
raise UpdateFailed(msg) from exception
|
||||||
except (
|
|
||||||
TibberPricesApiClientCommunicationError,
|
|
||||||
TibberPricesApiClientError,
|
|
||||||
Exception,
|
|
||||||
) as exception:
|
|
||||||
if isinstance(exception, TibberPricesApiClientCommunicationError):
|
if isinstance(exception, TibberPricesApiClientCommunicationError):
|
||||||
LOGGER.error(
|
LOGGER.error(
|
||||||
"API communication error",
|
"API communication error",
|
||||||
|
|
@ -260,8 +267,6 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict]):
|
||||||
LOGGER.info("Using cached data as fallback")
|
LOGGER.info("Using cached data as fallback")
|
||||||
return self._merge_all_cached_data()
|
return self._merge_all_cached_data()
|
||||||
raise UpdateFailed(UPDATE_FAILED_MSG) from exception
|
raise UpdateFailed(UPDATE_FAILED_MSG) from exception
|
||||||
else:
|
|
||||||
return result
|
|
||||||
|
|
||||||
async def _handle_conditional_update(self, current_time: datetime) -> dict:
|
async def _handle_conditional_update(self, current_time: datetime) -> dict:
|
||||||
"""Handle conditional update based on update conditions."""
|
"""Handle conditional update based on update conditions."""
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue