Skip to content

Commit 19721ba

Browse files
authored
Merge pull request #141 from golles/130-update-failed-unexpected-character
Fix for JSON unexpected character error
2 parents 795ede4 + 98befcb commit 19721ba

File tree

7 files changed

+963
-37
lines changed

7 files changed

+963
-37
lines changed

custom_components/knmi/api.py

+32-14
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
"""KnmiApiClient"""
22

33
import asyncio
4+
import json
5+
import logging
46
import socket
57

68
import aiohttp
79

810
from .const import API_ENDPOINT, API_TIMEOUT
911

12+
_LOGGER: logging.Logger = logging.getLogger(__package__)
13+
1014

1115
class KnmiApiClientError(Exception):
1216
"""Exception to indicate a general API error."""
@@ -27,6 +31,8 @@ class KnmiApiRateLimitError(KnmiApiClientError):
2731
class KnmiApiClient:
2832
"""KNMI API wrapper"""
2933

34+
response_text = None
35+
3036
def __init__(
3137
self,
3238
api_key: str,
@@ -40,27 +46,39 @@ def __init__(
4046
self.longitude = longitude
4147
self._session = session
4248

49+
async def get_response_text(self) -> str:
50+
"""Get API response text"""
51+
async with asyncio.timeout(API_TIMEOUT):
52+
response = await self._session.get(
53+
API_ENDPOINT.format(self.api_key, self.latitude, self.longitude)
54+
)
55+
56+
return await response.text()
57+
4358
async def async_get_data(self) -> dict:
4459
"""Get data from the API."""
4560
try:
46-
async with asyncio.timeout(API_TIMEOUT):
47-
response = await self._session.get(
48-
API_ENDPOINT.format(self.api_key, self.latitude, self.longitude)
49-
)
61+
self.response_text = await self.get_response_text()
5062

51-
response_text = await response.text()
63+
# The API has no proper error handling for a wrong API key or rate limit.
64+
# Instead a 200 with a message is returned, try to detect that here.
65+
if "Vraag eerst een API-key op" in self.response_text:
66+
raise KnmiApiClientApiKeyError("The given API key is invalid")
5267

53-
# The API has no proper error handling for a wrong API key or rate limit.
54-
# Instead a 200 with a message is returned, try to detect that here.
55-
if "Vraag eerst een API-key op" in response_text:
56-
raise KnmiApiClientApiKeyError("The given API key is invalid")
68+
if "Dagelijkse limiet" in self.response_text:
69+
raise KnmiApiRateLimitError(
70+
"API key daily limit exceeded, try again tomorrow"
71+
)
5772

58-
if "Dagelijkse limiet" in response_text:
59-
raise KnmiApiRateLimitError(
60-
"API key daily limit exceeded, try again tomorrow"
61-
)
73+
# The API has an ongoing issue due to invalid JSON response.
74+
# Where a null value of a number field is set to _ (without quotes).
75+
# Here we fix the JSON by setting the value to null.
76+
# More info: https://github.com/golles/ha-knmi/issues/130
77+
if '": _,' in self.response_text:
78+
_LOGGER.debug("Detected invalid JSON, attempting to fix that...")
79+
return json.loads(self.response_text.replace('": _,', '": null,'))
6280

63-
return await response.json()
81+
return json.loads(self.response_text)
6482

6583
except asyncio.TimeoutError as exception:
6684
raise KnmiApiClientCommunicationError(

custom_components/knmi/diagnostics.py

+1
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ async def async_get_config_entry_diagnostics(
2020
return {
2121
"config_entry": async_redact_data(config_entry.as_dict(), TO_REDACT),
2222
"data": coordinator.data,
23+
"response_text": coordinator.api.response_text,
2324
}

tests/conftest.py

+10-22
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
#
1616
# See here for more info: https://docs.pytest.org/en/latest/fixture.html (note that
1717
# pytest includes fixtures OOB which you can use as defined on this page)
18-
import json
18+
1919
from unittest.mock import PropertyMock, patch
2020

2121
import pytest
@@ -63,31 +63,19 @@ def enable_all_entities():
6363
yield
6464

6565

66-
# This fixture, when used, will have the mocked values from response.json loaded in the integration.
66+
# This fixture, when used, will have the mocked data from a given json file.
6767
@pytest.fixture(name="mocked_data")
68-
def mocked_data_fixture():
69-
"""Skip calls to get data from API."""
70-
data = json.loads(load_fixture(response_json))
68+
def mocked_data_fixture(request):
69+
"""Use mocked data in the integration"""
70+
json_file = "response.json"
71+
fixture = request.node.get_closest_marker("fixture")
7172

72-
with patch(
73-
async_get_data,
74-
return_value=data,
75-
):
76-
yield
77-
78-
79-
# This fixture, when used, will have the mocked values from response.json loaded in the integration.
80-
# As an addition, the alarm and related values are set.
81-
@pytest.fixture(name="mocked_data_alarm")
82-
def mocked_data_alarm_fixture():
83-
"""Skip calls to get data from API."""
84-
data = json.loads(load_fixture(response_json))
85-
86-
data["liveweer"][0]["alarm"] = 1
73+
if fixture is not None:
74+
json_file = fixture.args[0]
8775

8876
with patch(
89-
async_get_data,
90-
return_value=data,
77+
"custom_components.knmi.KnmiApiClient.get_response_text",
78+
return_value=load_fixture(json_file),
9179
):
9280
yield
9381

0 commit comments

Comments
 (0)