mirror of
https://github.com/jpawlowski/hass.tibber_prices.git
synced 2026-03-29 21:03: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 socket
|
||||
from datetime import timedelta
|
||||
from enum import Enum, auto
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
import aiohttp
|
||||
|
|
@ -24,13 +24,6 @@ HTTP_UNAUTHORIZED = 401
|
|||
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):
|
||||
"""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:
|
||||
"""Transform API response data based on query type."""
|
||||
if not data or "viewer" not in data:
|
||||
_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"]
|
||||
def _flatten_price_info(subscription: dict) -> dict:
|
||||
"""Transform and flatten priceInfo from full API data structure."""
|
||||
price_info = subscription.get("priceInfo", {})
|
||||
|
||||
# Get today and yesterday dates using Home Assistant's dt_util
|
||||
today_local = dt_util.now().date()
|
||||
yesterday_local = today_local - timedelta(days=1)
|
||||
_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"]:
|
||||
edges = price_info["range"]["edges"]
|
||||
yesterday_prices = []
|
||||
|
|
@ -315,12 +280,6 @@ def _transform_price_info(data: dict) -> dict:
|
|||
price_info["yesterday"] = yesterday_prices
|
||||
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 {
|
||||
"yesterday": price_info.get("yesterday", []),
|
||||
"today": price_info.get("today", []),
|
||||
|
|
@ -538,7 +497,7 @@ class TibberPricesApiClient:
|
|||
|
||||
await _verify_graphql_response(response_json, query_type)
|
||||
|
||||
return _transform_data(response_json["data"], query_type)
|
||||
return response_json["data"]
|
||||
|
||||
async def _handle_request(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -167,35 +167,9 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict]):
|
|||
async def _async_update_data(self) -> dict:
|
||||
"""Fetch new state data for the coordinator. Handles expired credentials by raising ConfigEntryAuthFailed."""
|
||||
if self._cached_price_data is None:
|
||||
try:
|
||||
await self._async_initialize()
|
||||
except TimeoutError as exception:
|
||||
msg = "Timeout during initialization"
|
||||
LOGGER.error(
|
||||
"%s: %s",
|
||||
msg,
|
||||
exception,
|
||||
extra={"error_type": "timeout_init"},
|
||||
)
|
||||
raise UpdateFailed(msg) from exception
|
||||
except TibberPricesApiClientAuthenticationError as exception:
|
||||
msg = "Authentication failed: credentials expired or invalid"
|
||||
LOGGER.error(
|
||||
"Authentication failed (likely expired credentials) during initialization",
|
||||
extra={"error": str(exception), "error_type": "auth_failed_init"},
|
||||
)
|
||||
raise ConfigEntryAuthFailed(msg) from exception
|
||||
except Exception as exception:
|
||||
msg = "Unexpected error during initialization"
|
||||
LOGGER.exception(
|
||||
"%s",
|
||||
msg,
|
||||
extra={"error": str(exception), "error_type": "unexpected_init"},
|
||||
)
|
||||
raise UpdateFailed(msg) from exception
|
||||
await self._handle_initialization()
|
||||
try:
|
||||
current_time = dt_util.now()
|
||||
result = None
|
||||
if self._force_update:
|
||||
LOGGER.debug(
|
||||
"Force updating data",
|
||||
|
|
@ -211,17 +185,55 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict]):
|
|||
},
|
||||
)
|
||||
self._force_update = False
|
||||
result = await self._fetch_all_data()
|
||||
else:
|
||||
result = await self._handle_conditional_update(current_time)
|
||||
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:
|
||||
await self._async_initialize()
|
||||
except TimeoutError as exception:
|
||||
msg = "Timeout during initialization"
|
||||
LOGGER.error(
|
||||
"%s: %s",
|
||||
msg,
|
||||
exception,
|
||||
extra={"error_type": "timeout_init"},
|
||||
)
|
||||
raise UpdateFailed(msg) from exception
|
||||
except TibberPricesApiClientAuthenticationError as exception:
|
||||
msg = "Authentication failed: credentials expired or invalid"
|
||||
LOGGER.error(
|
||||
"Authentication failed (likely expired credentials) during initialization",
|
||||
extra={"error": str(exception), "error_type": "auth_failed_init"},
|
||||
)
|
||||
raise ConfigEntryAuthFailed(msg) from exception
|
||||
except Exception as exception:
|
||||
msg = "Unexpected error during initialization"
|
||||
LOGGER.exception(
|
||||
"%s",
|
||||
msg,
|
||||
extra={"error": str(exception), "error_type": "unexpected_init"},
|
||||
)
|
||||
raise UpdateFailed(msg) from exception
|
||||
|
||||
async def _handle_update_exception(self, exception: Exception) -> dict:
|
||||
"""Handle exceptions during update and return fallback or raise."""
|
||||
if isinstance(exception, TibberPricesApiClientAuthenticationError):
|
||||
msg = "Authentication failed: credentials expired or invalid"
|
||||
LOGGER.error(
|
||||
"Authentication failed (likely expired credentials)",
|
||||
extra={"error": str(exception), "error_type": "auth_failed"},
|
||||
)
|
||||
raise ConfigEntryAuthFailed(msg) from exception
|
||||
except TimeoutError as exception:
|
||||
if isinstance(exception, TimeoutError):
|
||||
msg = "Timeout during data update"
|
||||
LOGGER.warning(
|
||||
"%s: %s",
|
||||
|
|
@ -233,35 +245,28 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict]):
|
|||
LOGGER.info("Using cached data as fallback after timeout")
|
||||
return self._merge_all_cached_data()
|
||||
raise UpdateFailed(msg) from exception
|
||||
except (
|
||||
TibberPricesApiClientCommunicationError,
|
||||
TibberPricesApiClientError,
|
||||
Exception,
|
||||
) as exception:
|
||||
if isinstance(exception, TibberPricesApiClientCommunicationError):
|
||||
LOGGER.error(
|
||||
"API communication error",
|
||||
extra={
|
||||
"error": str(exception),
|
||||
"error_type": "communication_error",
|
||||
},
|
||||
)
|
||||
elif isinstance(exception, TibberPricesApiClientError):
|
||||
LOGGER.error(
|
||||
"API client error",
|
||||
extra={"error": str(exception), "error_type": "client_error"},
|
||||
)
|
||||
else:
|
||||
LOGGER.exception(
|
||||
"Unexpected error",
|
||||
extra={"error": str(exception), "error_type": "unexpected"},
|
||||
)
|
||||
if self._cached_price_data is not None:
|
||||
LOGGER.info("Using cached data as fallback")
|
||||
return self._merge_all_cached_data()
|
||||
raise UpdateFailed(UPDATE_FAILED_MSG) from exception
|
||||
if isinstance(exception, TibberPricesApiClientCommunicationError):
|
||||
LOGGER.error(
|
||||
"API communication error",
|
||||
extra={
|
||||
"error": str(exception),
|
||||
"error_type": "communication_error",
|
||||
},
|
||||
)
|
||||
elif isinstance(exception, TibberPricesApiClientError):
|
||||
LOGGER.error(
|
||||
"API client error",
|
||||
extra={"error": str(exception), "error_type": "client_error"},
|
||||
)
|
||||
else:
|
||||
return result
|
||||
LOGGER.exception(
|
||||
"Unexpected error",
|
||||
extra={"error": str(exception), "error_type": "unexpected"},
|
||||
)
|
||||
if self._cached_price_data is not None:
|
||||
LOGGER.info("Using cached data as fallback")
|
||||
return self._merge_all_cached_data()
|
||||
raise UpdateFailed(UPDATE_FAILED_MSG) from exception
|
||||
|
||||
async def _handle_conditional_update(self, current_time: datetime) -> dict:
|
||||
"""Handle conditional update based on update conditions."""
|
||||
|
|
|
|||
Loading…
Reference in a new issue