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
2811ec7b3f
commit
130b51f5b6
2 changed files with 191 additions and 176 deletions
|
|
@ -74,8 +74,8 @@ def _verify_response_or_raise(response: aiohttp.ClientResponse) -> None:
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
|
|
||||||
async def _verify_graphql_response(response_json: dict) -> None:
|
async def _verify_graphql_response(response_json: dict, query_type: QueryType) -> None:
|
||||||
"""Verify the GraphQL response for errors and data completeness."""
|
"""Verify the GraphQL response for errors and data completeness, including empty data."""
|
||||||
if "errors" in response_json:
|
if "errors" in response_json:
|
||||||
errors = response_json["errors"]
|
errors = response_json["errors"]
|
||||||
if not errors:
|
if not errors:
|
||||||
|
|
@ -98,16 +98,27 @@ async def _verify_graphql_response(response_json: dict) -> None:
|
||||||
TibberPricesApiClientError.GRAPHQL_ERROR.format(message="Response missing data object")
|
TibberPricesApiClientError.GRAPHQL_ERROR.format(message="Response missing data object")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Empty data check (for retry logic) - always check, regardless of query_type
|
||||||
|
if _is_data_empty(response_json["data"], query_type.value):
|
||||||
|
_LOGGER.debug("Empty data detected for query_type: %s", query_type)
|
||||||
|
raise TibberPricesApiClientError(
|
||||||
|
TibberPricesApiClientError.EMPTY_DATA_ERROR.format(query_type=query_type.value)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _is_data_empty(data: dict, query_type: str) -> bool:
|
def _is_data_empty(data: dict, query_type: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Check if the response data is empty or incomplete.
|
Check if the response data is empty or incomplete.
|
||||||
|
|
||||||
|
For viewer data:
|
||||||
|
- Must have userId and homes
|
||||||
|
- If either is missing, data is considered empty
|
||||||
|
- If homes is empty, data is considered empty
|
||||||
|
- If userId is None, data is considered empty
|
||||||
|
|
||||||
For price info:
|
For price info:
|
||||||
- Must have either range/edges or yesterday data
|
- Must have range data
|
||||||
- Must have today data
|
- Must have today data
|
||||||
- If neither range/edges nor yesterday data exists, data is considered empty
|
|
||||||
- If today data is empty, data is considered empty
|
|
||||||
- tomorrow can be empty if we have valid historical and today data
|
- tomorrow can be empty if we have valid historical and today data
|
||||||
|
|
||||||
For rating data:
|
For rating data:
|
||||||
|
|
@ -116,78 +127,116 @@ def _is_data_empty(data: dict, query_type: str) -> bool:
|
||||||
"""
|
"""
|
||||||
_LOGGER.debug("Checking if data is empty for query_type %s", query_type)
|
_LOGGER.debug("Checking if data is empty for query_type %s", query_type)
|
||||||
|
|
||||||
|
is_empty = False
|
||||||
try:
|
try:
|
||||||
subscription = data["viewer"]["homes"][0]["currentSubscription"]
|
if query_type == "viewer":
|
||||||
|
has_user_id = (
|
||||||
if query_type == "price_info":
|
"viewer" in data
|
||||||
price_info = subscription["priceInfo"]
|
and isinstance(data["viewer"], dict)
|
||||||
|
and "userId" in data["viewer"]
|
||||||
# Check historical data (either range or yesterday)
|
and data["viewer"]["userId"] is not None
|
||||||
has_range = (
|
|
||||||
"range" in price_info
|
|
||||||
and price_info["range"] is not None
|
|
||||||
and "edges" in price_info["range"]
|
|
||||||
and price_info["range"]["edges"]
|
|
||||||
)
|
)
|
||||||
has_yesterday = (
|
has_homes = (
|
||||||
"yesterday" in price_info and price_info["yesterday"] is not None and len(price_info["yesterday"]) > 0
|
"viewer" in data
|
||||||
|
and isinstance(data["viewer"], dict)
|
||||||
|
and "homes" in data["viewer"]
|
||||||
|
and isinstance(data["viewer"]["homes"], list)
|
||||||
|
and len(data["viewer"]["homes"]) > 0
|
||||||
)
|
)
|
||||||
has_historical = has_range or has_yesterday
|
is_empty = not has_user_id or not has_homes
|
||||||
|
|
||||||
# Check today's data
|
|
||||||
has_today = "today" in price_info and price_info["today"] is not None and len(price_info["today"]) > 0
|
|
||||||
|
|
||||||
# Data is empty if we don't have historical data or today's data
|
|
||||||
is_empty = not has_historical or not has_today
|
|
||||||
|
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Price info check - historical data (range: %s, yesterday: %s), today: %s, is_empty: %s",
|
"Viewer check - has_user_id: %s, has_homes: %s, is_empty: %s", has_user_id, has_homes, is_empty
|
||||||
bool(has_range),
|
|
||||||
bool(has_yesterday),
|
|
||||||
bool(has_today),
|
|
||||||
is_empty,
|
|
||||||
)
|
|
||||||
return is_empty
|
|
||||||
|
|
||||||
if query_type in ["daily", "hourly", "monthly"]:
|
|
||||||
rating = subscription["priceRating"]
|
|
||||||
|
|
||||||
# Check threshold percentages
|
|
||||||
has_thresholds = (
|
|
||||||
"thresholdPercentages" in rating
|
|
||||||
and rating["thresholdPercentages"] is not None
|
|
||||||
and "low" in rating["thresholdPercentages"]
|
|
||||||
and "high" in rating["thresholdPercentages"]
|
|
||||||
)
|
|
||||||
if not has_thresholds:
|
|
||||||
_LOGGER.debug("Missing or invalid threshold percentages for %s rating", query_type)
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Check rating entries
|
|
||||||
has_entries = (
|
|
||||||
query_type in rating
|
|
||||||
and rating[query_type] is not None
|
|
||||||
and "entries" in rating[query_type]
|
|
||||||
and rating[query_type]["entries"] is not None
|
|
||||||
and len(rating[query_type]["entries"]) > 0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
is_empty = not has_entries
|
elif query_type == "price_info":
|
||||||
_LOGGER.debug(
|
# Check for homes existence and non-emptiness before accessing
|
||||||
"%s rating check - has_thresholds: %s, entries count: %d, is_empty: %s",
|
if (
|
||||||
query_type,
|
"viewer" not in data
|
||||||
has_thresholds,
|
or "homes" not in data["viewer"]
|
||||||
len(rating[query_type]["entries"]) if has_entries else 0,
|
or not isinstance(data["viewer"]["homes"], list)
|
||||||
is_empty,
|
or len(data["viewer"]["homes"]) == 0
|
||||||
)
|
or "currentSubscription" not in data["viewer"]["homes"][0]
|
||||||
return is_empty
|
or data["viewer"]["homes"][0]["currentSubscription"] is None
|
||||||
|
or "priceInfo" not in data["viewer"]["homes"][0]["currentSubscription"]
|
||||||
|
):
|
||||||
|
_LOGGER.debug("Missing homes/currentSubscription/priceInfo in price_info check")
|
||||||
|
is_empty = True
|
||||||
|
else:
|
||||||
|
price_info = data["viewer"]["homes"][0]["currentSubscription"]["priceInfo"]
|
||||||
|
|
||||||
_LOGGER.debug("Unknown query type %s, treating as non-empty", query_type)
|
# Check historical data (either range or yesterday)
|
||||||
|
has_historical = (
|
||||||
|
"range" in price_info
|
||||||
|
and price_info["range"] is not None
|
||||||
|
and "edges" in price_info["range"]
|
||||||
|
and price_info["range"]["edges"]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check today's data
|
||||||
|
has_today = "today" in price_info and price_info["today"] is not None and len(price_info["today"]) > 0
|
||||||
|
|
||||||
|
# Data is empty if we don't have historical data or today's data
|
||||||
|
is_empty = not has_historical or not has_today
|
||||||
|
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Price info check - historical data historical: %s, today: %s, is_empty: %s",
|
||||||
|
bool(has_historical),
|
||||||
|
bool(has_today),
|
||||||
|
is_empty,
|
||||||
|
)
|
||||||
|
|
||||||
|
elif query_type in ["daily", "hourly", "monthly"]:
|
||||||
|
# Check for homes existence and non-emptiness before accessing
|
||||||
|
if (
|
||||||
|
"viewer" not in data
|
||||||
|
or "homes" not in data["viewer"]
|
||||||
|
or not isinstance(data["viewer"]["homes"], list)
|
||||||
|
or len(data["viewer"]["homes"]) == 0
|
||||||
|
or "currentSubscription" not in data["viewer"]["homes"][0]
|
||||||
|
or data["viewer"]["homes"][0]["currentSubscription"] is None
|
||||||
|
or "priceRating" not in data["viewer"]["homes"][0]["currentSubscription"]
|
||||||
|
):
|
||||||
|
_LOGGER.debug("Missing homes/currentSubscription/priceRating in rating check")
|
||||||
|
is_empty = True
|
||||||
|
else:
|
||||||
|
rating = data["viewer"]["homes"][0]["currentSubscription"]["priceRating"]
|
||||||
|
|
||||||
|
# Check threshold percentages
|
||||||
|
has_thresholds = (
|
||||||
|
"thresholdPercentages" in rating
|
||||||
|
and rating["thresholdPercentages"] is not None
|
||||||
|
and "low" in rating["thresholdPercentages"]
|
||||||
|
and "high" in rating["thresholdPercentages"]
|
||||||
|
)
|
||||||
|
if not has_thresholds:
|
||||||
|
_LOGGER.debug("Missing or invalid threshold percentages for %s rating", query_type)
|
||||||
|
is_empty = True
|
||||||
|
else:
|
||||||
|
# Check rating entries
|
||||||
|
has_entries = (
|
||||||
|
query_type in rating
|
||||||
|
and rating[query_type] is not None
|
||||||
|
and "entries" in rating[query_type]
|
||||||
|
and rating[query_type]["entries"] is not None
|
||||||
|
and len(rating[query_type]["entries"]) > 0
|
||||||
|
)
|
||||||
|
|
||||||
|
is_empty = not has_entries
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s rating check - has_thresholds: %s, entries count: %d, is_empty: %s",
|
||||||
|
query_type,
|
||||||
|
has_thresholds,
|
||||||
|
len(rating[query_type]["entries"]) if has_entries else 0,
|
||||||
|
is_empty,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
_LOGGER.debug("Unknown query type %s, treating as non-empty", query_type)
|
||||||
|
is_empty = False
|
||||||
except (KeyError, IndexError, TypeError) as error:
|
except (KeyError, IndexError, TypeError) as error:
|
||||||
_LOGGER.debug("Error checking data emptiness: %s", error)
|
_LOGGER.debug("Error checking data emptiness: %s", error)
|
||||||
return True
|
is_empty = True
|
||||||
else:
|
|
||||||
return False
|
return is_empty
|
||||||
|
|
||||||
|
|
||||||
def _prepare_headers(access_token: str) -> dict[str, str]:
|
def _prepare_headers(access_token: str) -> dict[str, str]:
|
||||||
|
|
@ -316,7 +365,7 @@ class TibberPricesApiClient:
|
||||||
self._request_semaphore = asyncio.Semaphore(2)
|
self._request_semaphore = asyncio.Semaphore(2)
|
||||||
self._last_request_time = dt_util.now()
|
self._last_request_time = dt_util.now()
|
||||||
self._min_request_interval = timedelta(seconds=1)
|
self._min_request_interval = timedelta(seconds=1)
|
||||||
self._max_retries = 3
|
self._max_retries = 5
|
||||||
self._retry_delay = 2
|
self._retry_delay = 2
|
||||||
|
|
||||||
async def async_get_viewer_details(self) -> Any:
|
async def async_get_viewer_details(self) -> Any:
|
||||||
|
|
@ -347,9 +396,9 @@ class TibberPricesApiClient:
|
||||||
query_type=QueryType.VIEWER,
|
query_type=QueryType.VIEWER,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_get_price_info(self) -> dict:
|
async def async_get_price_info(self, home_id: str) -> dict:
|
||||||
"""Get price info data in flat format."""
|
"""Get price info data in flat format for the specified home_id."""
|
||||||
response = await self._api_wrapper(
|
data = await self._api_wrapper(
|
||||||
data={
|
data={
|
||||||
"query": """
|
"query": """
|
||||||
{viewer{homes{id,currentSubscription{priceInfo{
|
{viewer{homes{id,currentSubscription{priceInfo{
|
||||||
|
|
@ -362,16 +411,17 @@ class TibberPricesApiClient:
|
||||||
},
|
},
|
||||||
query_type=QueryType.PRICE_INFO,
|
query_type=QueryType.PRICE_INFO,
|
||||||
)
|
)
|
||||||
# response is already transformed, but we want flat
|
homes = data.get("viewer", {}).get("homes", [])
|
||||||
try:
|
home = next((h for h in homes if h.get("id") == home_id), None)
|
||||||
subscription = response["viewer"]["homes"][0]["currentSubscription"]
|
if home and "currentSubscription" in home:
|
||||||
except KeyError:
|
data["priceInfo"] = _flatten_price_info(home["currentSubscription"])
|
||||||
subscription = response["data"]["viewer"]["homes"][0]["currentSubscription"]
|
else:
|
||||||
return {"priceInfo": _flatten_price_info(subscription)}
|
data["priceInfo"] = {}
|
||||||
|
return data
|
||||||
|
|
||||||
async def async_get_daily_price_rating(self) -> dict:
|
async def async_get_daily_price_rating(self, home_id: str) -> dict:
|
||||||
"""Get daily price rating data in flat format."""
|
"""Get daily price rating data in flat format for the specified home_id."""
|
||||||
response = await self._api_wrapper(
|
data = await self._api_wrapper(
|
||||||
data={
|
data={
|
||||||
"query": """
|
"query": """
|
||||||
{viewer{homes{id,currentSubscription{priceRating{
|
{viewer{homes{id,currentSubscription{priceRating{
|
||||||
|
|
@ -384,21 +434,17 @@ class TibberPricesApiClient:
|
||||||
},
|
},
|
||||||
query_type=QueryType.DAILY_RATING,
|
query_type=QueryType.DAILY_RATING,
|
||||||
)
|
)
|
||||||
try:
|
homes = data.get("viewer", {}).get("homes", [])
|
||||||
subscription = response["viewer"]["homes"][0]["currentSubscription"]
|
home = next((h for h in homes if h.get("id") == home_id), None)
|
||||||
except KeyError:
|
if home and "currentSubscription" in home:
|
||||||
subscription = response["data"]["viewer"]["homes"][0]["currentSubscription"]
|
data["priceRating"] = _flatten_price_rating(home["currentSubscription"])
|
||||||
return {
|
else:
|
||||||
"priceRating": {
|
data["priceRating"] = {}
|
||||||
"daily": _flatten_price_rating(subscription)["daily"],
|
return data
|
||||||
"thresholdPercentages": _flatten_price_rating(subscription)["thresholdPercentages"],
|
|
||||||
"currency": _flatten_price_rating(subscription)["currency"],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async def async_get_hourly_price_rating(self) -> dict:
|
async def async_get_hourly_price_rating(self, home_id: str) -> dict:
|
||||||
"""Get hourly price rating data in flat format."""
|
"""Get hourly price rating data in flat format for the specified home_id."""
|
||||||
response = await self._api_wrapper(
|
data = await self._api_wrapper(
|
||||||
data={
|
data={
|
||||||
"query": """
|
"query": """
|
||||||
{viewer{homes{id,currentSubscription{priceRating{
|
{viewer{homes{id,currentSubscription{priceRating{
|
||||||
|
|
@ -411,21 +457,17 @@ class TibberPricesApiClient:
|
||||||
},
|
},
|
||||||
query_type=QueryType.HOURLY_RATING,
|
query_type=QueryType.HOURLY_RATING,
|
||||||
)
|
)
|
||||||
try:
|
homes = data.get("viewer", {}).get("homes", [])
|
||||||
subscription = response["viewer"]["homes"][0]["currentSubscription"]
|
home = next((h for h in homes if h.get("id") == home_id), None)
|
||||||
except KeyError:
|
if home and "currentSubscription" in home:
|
||||||
subscription = response["data"]["viewer"]["homes"][0]["currentSubscription"]
|
data["priceRating"] = _flatten_price_rating(home["currentSubscription"])
|
||||||
return {
|
else:
|
||||||
"priceRating": {
|
data["priceRating"] = {}
|
||||||
"hourly": _flatten_price_rating(subscription)["hourly"],
|
return data
|
||||||
"thresholdPercentages": _flatten_price_rating(subscription)["thresholdPercentages"],
|
|
||||||
"currency": _flatten_price_rating(subscription)["currency"],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async def async_get_monthly_price_rating(self) -> dict:
|
async def async_get_monthly_price_rating(self, home_id: str) -> dict:
|
||||||
"""Get monthly price rating data in flat format."""
|
"""Get monthly price rating data in flat format for the specified home_id."""
|
||||||
response = await self._api_wrapper(
|
data = await self._api_wrapper(
|
||||||
data={
|
data={
|
||||||
"query": """
|
"query": """
|
||||||
{viewer{homes{id,currentSubscription{priceRating{
|
{viewer{homes{id,currentSubscription{priceRating{
|
||||||
|
|
@ -438,30 +480,30 @@ class TibberPricesApiClient:
|
||||||
},
|
},
|
||||||
query_type=QueryType.MONTHLY_RATING,
|
query_type=QueryType.MONTHLY_RATING,
|
||||||
)
|
)
|
||||||
try:
|
homes = data.get("viewer", {}).get("homes", [])
|
||||||
subscription = response["viewer"]["homes"][0]["currentSubscription"]
|
home = next((h for h in homes if h.get("id") == home_id), None)
|
||||||
except KeyError:
|
if home and "currentSubscription" in home:
|
||||||
subscription = response["data"]["viewer"]["homes"][0]["currentSubscription"]
|
data["priceRating"] = _flatten_price_rating(home["currentSubscription"])
|
||||||
return {
|
else:
|
||||||
"priceRating": {
|
data["priceRating"] = {}
|
||||||
"monthly": _flatten_price_rating(subscription)["monthly"],
|
return data
|
||||||
"thresholdPercentages": _flatten_price_rating(subscription)["thresholdPercentages"],
|
|
||||||
"currency": _flatten_price_rating(subscription)["currency"],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async def async_get_data(self) -> dict:
|
async def async_get_data(self, home_id: str) -> dict:
|
||||||
"""Get all data from the API by combining multiple queries in flat format."""
|
"""Get all data from the API by combining multiple queries in flat format for the specified home_id."""
|
||||||
price_info = await self.async_get_price_info()
|
price_info = await self.async_get_price_info(home_id)
|
||||||
daily_rating = await self.async_get_daily_price_rating()
|
daily_rating = await self.async_get_daily_price_rating(home_id)
|
||||||
hourly_rating = await self.async_get_hourly_price_rating()
|
hourly_rating = await self.async_get_hourly_price_rating(home_id)
|
||||||
monthly_rating = await self.async_get_monthly_price_rating()
|
monthly_rating = await self.async_get_monthly_price_rating(home_id)
|
||||||
# Merge all into one flat dict
|
|
||||||
price_rating = {
|
price_rating = {
|
||||||
"thresholdPercentages": daily_rating["priceRating"].get("thresholdPercentages"),
|
"thresholdPercentages": daily_rating["priceRating"].get("thresholdPercentages"),
|
||||||
"daily": daily_rating["priceRating"].get("daily", []),
|
"daily": daily_rating["priceRating"].get("daily", []),
|
||||||
"hourly": hourly_rating["priceRating"].get("hourly", []),
|
"hourly": hourly_rating["priceRating"].get("hourly", []),
|
||||||
"monthly": monthly_rating["priceRating"].get("monthly", []),
|
"monthly": monthly_rating["priceRating"].get("monthly", []),
|
||||||
|
"currency": (
|
||||||
|
daily_rating["priceRating"].get("currency")
|
||||||
|
or hourly_rating["priceRating"].get("currency")
|
||||||
|
or monthly_rating["priceRating"].get("currency")
|
||||||
|
),
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
"priceInfo": price_info["priceInfo"],
|
"priceInfo": price_info["priceInfo"],
|
||||||
|
|
@ -494,7 +536,7 @@ class TibberPricesApiClient:
|
||||||
response_json = await response.json()
|
response_json = await response.json()
|
||||||
_LOGGER.debug("Received API response: %s", response_json)
|
_LOGGER.debug("Received API response: %s", response_json)
|
||||||
|
|
||||||
await _verify_graphql_response(response_json)
|
await _verify_graphql_response(response_json, query_type)
|
||||||
|
|
||||||
return _transform_data(response_json["data"], query_type)
|
return _transform_data(response_json["data"], query_type)
|
||||||
|
|
||||||
|
|
@ -518,20 +560,12 @@ class TibberPricesApiClient:
|
||||||
|
|
||||||
async with async_timeout.timeout(10):
|
async with async_timeout.timeout(10):
|
||||||
self._last_request_time = dt_util.now()
|
self._last_request_time = dt_util.now()
|
||||||
response_data = await self._make_request(
|
return await self._make_request(
|
||||||
headers,
|
headers,
|
||||||
data or {},
|
data or {},
|
||||||
query_type,
|
query_type,
|
||||||
)
|
)
|
||||||
|
|
||||||
if query_type != QueryType.VIEWER and _is_data_empty(response_data, query_type.value):
|
|
||||||
_LOGGER.debug("Empty data detected for query_type: %s", query_type)
|
|
||||||
raise TibberPricesApiClientError(
|
|
||||||
TibberPricesApiClientError.EMPTY_DATA_ERROR.format(query_type=query_type.value)
|
|
||||||
)
|
|
||||||
|
|
||||||
return response_data
|
|
||||||
|
|
||||||
async def _api_wrapper(
|
async def _api_wrapper(
|
||||||
self,
|
self,
|
||||||
data: dict | None = None,
|
data: dict | None = None,
|
||||||
|
|
|
||||||
|
|
@ -316,48 +316,27 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict]):
|
||||||
return self._merge_all_cached_data()
|
return self._merge_all_cached_data()
|
||||||
|
|
||||||
async def _fetch_price_data(self) -> dict:
|
async def _fetch_price_data(self) -> dict:
|
||||||
"""Fetch fresh price data from API and check for GraphQL errors."""
|
"""Fetch fresh price data from API. Assumes errors are handled in api.py."""
|
||||||
client = self.config_entry.runtime_data.client
|
client = self.config_entry.runtime_data.client
|
||||||
data = await client.async_get_price_info()
|
home_id = self.config_entry.unique_id
|
||||||
# Check for GraphQL errors at the top level
|
if not home_id:
|
||||||
if isinstance(data, dict) and "errors" in data and data["errors"]:
|
LOGGER.error("No home_id (unique_id) set in config entry!")
|
||||||
errors = data["errors"]
|
return {}
|
||||||
# Look for authentication-related errors (extensions.code == 'UNAUTHENTICATED')
|
data = await client.async_get_price_info(home_id)
|
||||||
for err in errors:
|
if not data:
|
||||||
code = err.get("extensions", {}).get("code")
|
return {}
|
||||||
msg = str(err.get("message", ""))
|
price_info = data.get("priceInfo", {})
|
||||||
if code == "UNAUTHENTICATED":
|
if not price_info:
|
||||||
LOGGER.error(
|
return {}
|
||||||
"GraphQL authentication error (UNAUTHENTICATED): %s",
|
return price_info
|
||||||
msg,
|
|
||||||
extra={"error": msg, "error_type": "graphql_auth_failed", "code": code},
|
|
||||||
)
|
|
||||||
raise TibberPricesApiClientAuthenticationError(msg)
|
|
||||||
# Fallback: also check for other auth-related keywords in message/type
|
|
||||||
err_type = str(err.get("type", ""))
|
|
||||||
if any(
|
|
||||||
s in msg.lower() or s in err_type.lower()
|
|
||||||
for s in ("auth", "token", "credential", "unauth", "expired")
|
|
||||||
):
|
|
||||||
LOGGER.error(
|
|
||||||
"GraphQL authentication error: %s",
|
|
||||||
msg,
|
|
||||||
extra={"error": msg, "error_type": "graphql_auth_failed_fallback"},
|
|
||||||
)
|
|
||||||
raise TibberPricesApiClientAuthenticationError(msg)
|
|
||||||
# If errors exist but not auth-related, log and raise generic error
|
|
||||||
msg = f"GraphQL error(s): {errors}"
|
|
||||||
LOGGER.error(
|
|
||||||
"GraphQL error(s) in response: %s",
|
|
||||||
errors,
|
|
||||||
extra={"error_type": "graphql_error"},
|
|
||||||
)
|
|
||||||
raise TibberPricesApiClientError(msg)
|
|
||||||
return data
|
|
||||||
|
|
||||||
async def _get_rating_data_for_type(self, rating_type: str) -> dict:
|
async def _get_rating_data_for_type(self, rating_type: str) -> dict:
|
||||||
"""Get fresh rating data for a specific type in flat format."""
|
"""Get fresh rating data for a specific type in flat format. Assumes errors are handled in api.py."""
|
||||||
client = self.config_entry.runtime_data.client
|
client = self.config_entry.runtime_data.client
|
||||||
|
home_id = self.config_entry.unique_id
|
||||||
|
if not home_id:
|
||||||
|
LOGGER.error("No home_id (unique_id) set in config entry!")
|
||||||
|
return {}
|
||||||
method_map = {
|
method_map = {
|
||||||
"hourly": client.async_get_hourly_price_rating,
|
"hourly": client.async_get_hourly_price_rating,
|
||||||
"daily": client.async_get_daily_price_rating,
|
"daily": client.async_get_daily_price_rating,
|
||||||
|
|
@ -367,7 +346,9 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict]):
|
||||||
if not fetch_method:
|
if not fetch_method:
|
||||||
msg = f"Unknown rating type: {rating_type}"
|
msg = f"Unknown rating type: {rating_type}"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
data = await fetch_method()
|
data = await fetch_method(home_id)
|
||||||
|
if not data:
|
||||||
|
return {}
|
||||||
try:
|
try:
|
||||||
price_rating = data.get("priceRating", data)
|
price_rating = data.get("priceRating", data)
|
||||||
threshold = price_rating.get("thresholdPercentages")
|
threshold = price_rating.get("thresholdPercentages")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue