Skip to content

Commit 9f379f8

Browse files
authored
weather code float to int fix (#17)
* weather code float to int fix * add unittests and add better error handling
1 parent eb6f192 commit 9f379f8

File tree

6 files changed

+81
-32
lines changed

6 files changed

+81
-32
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
FROM alpine:3.23.2
22

33
LABEL maintainer="Michael Oberdorf <info@oberdorf-itc.de>"
4-
LABEL site.local.program.version="1.3.3"
4+
LABEL site.local.program.version="1.3.4"
55

66
ENV TZ="UTC" \
77
REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt \

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ Container image: [DockerHub](https://hub.docker.com/r/oitc/weather2mqtt)
2626

2727
# Supported tags and respective `Dockerfile` links
2828

29-
* [`latest`, `1.3.3`](https://github.com/cybcon/docker.weather2mqtt/blob/v1.3.3/Dockerfile)
29+
* [`latest`, `1.3.4`](https://github.com/cybcon/docker.weather2mqtt/blob/v1.3.4/Dockerfile)
30+
* [`1.3.3`](https://github.com/cybcon/docker.weather2mqtt/blob/v1.3.3/Dockerfile)
3031
* [`1.3.1`](https://github.com/cybcon/docker.weather2mqtt/blob/v1.3.1/Dockerfile)
3132
* [`1.3.0`](https://github.com/cybcon/docker.weather2mqtt/blob/v1.3.0/Dockerfile)
3233
* [`1.2.0`](https://github.com/cybcon/docker.weather2mqtt/blob/v1.2.0/Dockerfile)

src/app/bin/lib/weather_codes/__init__.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
__author__ = "Michael Oberdorf <info@oberdorf-itc.de>"
1414
__status__ = "production"
1515
__date__ = "2026-01-11"
16-
__version_info__ = ("1", "0", "1")
16+
__version_info__ = ("1", "0", "2")
1717
__version__ = ".".join(__version_info__)
1818

1919
__all__ = ["WeatherCodes"]
@@ -53,8 +53,14 @@ def translate(self, code: int) -> str:
5353
:param code: Weather code
5454
:return: Human readable description
5555
"""
56-
# transform the code to int and str to fit to the json keys
57-
56+
if code is None:
57+
self.logger.debug("Weather code is None, returning 'Unknown weather code'")
58+
return "Unknown weather code"
59+
if isinstance(code, str):
60+
self.logger.debug(
61+
f"Weather code {code} is a string but must be an integer, returning 'Unknown weather code'"
62+
)
63+
return "Unknown weather code"
5864
# Avoid "cannot convert float NaN to integer"
5965
if math.isnan(code):
6066
self.logger.debug(f"Weather code {code} is NaN, returning 'Unknown weather code'")

src/app/bin/weather2mqtt.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import datetime
1919
import json
2020
import logging
21+
import math
2122
import os
2223
import ssl
2324
import sys
@@ -29,7 +30,7 @@
2930
from lib.weather_codes import WeatherCodes
3031
from retry_requests import retry # seeAlso: https://pypi.org/project/retry-requests/
3132

32-
__version__ = "1.3.3"
33+
__version__ = "1.3.4"
3334
__script_path__ = os.path.dirname(__file__)
3435
__config_path__ = os.path.join(os.path.dirname(__script_path__), "etc")
3536
__local_tz__ = pytz.timezone("UTC")
@@ -266,13 +267,15 @@ def parse_current_weather(data: any, fields: list = []) -> dict:
266267
parsed_data = dict()
267268

268269
parsed_data["time"] = datetime.datetime.fromtimestamp(data.Time(), tz=__local_tz__).isoformat()
270+
log.debug(f"Current weather time: {parsed_data['time']}")
269271

270272
for i in range(0, len(fields)):
271273
parsed_data[fields[i]] = data.Variables(i).Value()
272274
log.debug(f"{fields[i]}: {parsed_data[fields[i]]}")
273275

274276
# Translate weather code
275-
if "weather_code" in parsed_data.keys():
277+
if "weather_code" in parsed_data.keys() and not math.isnan(parsed_data["weather_code"]):
278+
parsed_data["weather_code"] = int(parsed_data["weather_code"]) # be sure it's an int
276279
weather_code_language = os.environ.get("WEATHER_CODE_LANGUAGE", "en")[0:2].lower()
277280
parsed_data["weather_code_text"] = WeatherCodes(language=weather_code_language).translate(
278281
code=parsed_data["weather_code"]
@@ -302,28 +305,34 @@ def parse_daily_weather(data: any, fields: list = []) -> dict:
302305
forecast_ends_at = datetime.datetime.fromtimestamp(data.TimeEnd(), tz=__local_tz__)
303306
delta = forecast_ends_at - forecast_starts_at
304307
interval = datetime.timedelta(seconds=data.Interval())
308+
log.debug(
309+
f"Range from {forecast_starts_at.strftime('%Y-%m-%d')} to {forecast_ends_at.strftime('%Y-%m-%d')} with interval {interval.days} days"
310+
)
305311

306-
# print("Range from", daily_forecast_starts_at.strftime('%Y-%m-%d'), "to", daily_forecast_ends_at.strftime('%Y-%m-%d'), "with interval", interval.days, "days")
307312
interval_range = list()
308313
for i in range(delta.days):
309314
forecast_interval = (forecast_starts_at + i * interval).strftime("%Y-%m-%d")
310315
interval_range.append(forecast_interval)
316+
log.debug(f"Interval range: {interval_range}")
311317

312318
variables_with_time = [data.Variables(i) for i in range(0, data.VariablesLength())]
313319

314320
for i in range(0, len(fields)):
315-
# Not sure do we require to varify the field. Does field order matches?
316-
# And what about sunshine?
317321
field = parsed_data[fields[i]] = dict()
318322
for j in range(len(interval_range)):
319323
field[interval_range[j]] = variables_with_time[i].Values(j)
324+
log.debug(f"{fields[i]}[{interval_range[j]}]: {field[interval_range[j]]}")
320325

321326
# Translate weather code
322327
if "weather_code" in parsed_data.keys():
323328
weather_code_language = os.environ.get("WEATHER_CODE_LANGUAGE", "en")[0:2].lower()
324329
parsed_data["weather_code_text"] = dict()
325330
for k, v in parsed_data["weather_code"].items():
326-
parsed_data["weather_code_text"][k] = WeatherCodes(language=weather_code_language).translate(code=v)
331+
if math.isnan(v):
332+
parsed_data["weather_code_text"][k] = "Unknown weather code"
333+
continue
334+
parsed_data["weather_code"][k] = int(v) # be sure it's an int
335+
parsed_data["weather_code_text"][k] = WeatherCodes(language=weather_code_language).translate(code=int(v))
327336

328337
log.debug(f"Parsed current weather: {parsed_data}")
329338
return parsed_data

test/test_weather2mqtt.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
"""
2-
***************************************************************************\n
3-
test_weather2mqtt.py is a python unit test for the python script\n
4-
weather2mqtt.py\n
5-
Author: Michael Oberdorf\n
6-
Date: 2025-04-13\n
7-
Last modified by: Michael Oberdorf\n
8-
Last modified at: 2025-12-31\n
9-
***************************************************************************\n
2+
***************************************************************************
3+
test_weather2mqtt.py is a python unit test for the python script
4+
weather2mqtt.py
5+
Author: Michael Oberdorf
6+
Date: 2025-04-13
7+
Last modified by: Michael Oberdorf
8+
Last modified at: 2026-01-11
9+
***************************************************************************
1010
"""
1111

1212
__author__ = "Michael Oberdorf <info@oberdorf-itc.de>"
1313
__status__ = "production"
14-
__date__ = "2025-12-31"
15-
__version_info__ = ("1", "0", "0")
14+
__date__ = "2026-01-11"
15+
__version_info__ = ("1", "0", "1")
1616
__version__ = ".".join(__version_info__)
1717

1818
__all__ = ["TestWeather2Mqtt"]

test/test_weather_codes.py

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
"""
2-
***************************************************************************\n
2+
***************************************************************************
33
test_weather_codes.py is a python unit test for the python module
4-
weather_codes\n
5-
Author: Michael Oberdorf\n
6-
Date: 2025-04-11\n
7-
Last modified by: Michael Oberdorf\n
8-
Last modified at: 2025-04-12\n
9-
***************************************************************************\n
4+
weather_codes
5+
Author: Michael Oberdorf
6+
Date: 2025-04-11
7+
Last modified by: Michael Oberdorf
8+
Last modified at: 2026-01-11
9+
***************************************************************************
1010
"""
1111

1212
__author__ = "Michael Oberdorf <info@oberdorf-itc.de>"
1313
__status__ = "production"
14-
__date__ = "2025-04-12"
15-
__version_info__ = ("1", "0", "0")
14+
__date__ = "2026-01-11"
15+
__version_info__ = ("1", "1", "0")
1616
__version__ = ".".join(__version_info__)
1717

1818
__all__ = ["TestWeatherCodes"]
@@ -30,15 +30,48 @@ def test_translate(self):
3030

3131
# Test cases for specific languages (English and German)
3232
self.assertEqual(WeatherCodes(language="en").translate(code=3), "Cloudy")
33-
self.assertEqual(WeatherCodes(language="en").translate(code=199), "Unknown weather code")
3433
self.assertEqual(WeatherCodes(language="de").translate(code=45), "Nebel")
3534
self.assertEqual(WeatherCodes(language="de").translate(code=66), "Leichter Eisregen")
3635
self.assertEqual(WeatherCodes(language="de").translate(code=81), "Regenschauer")
37-
self.assertEqual(WeatherCodes(language="de").translate(code=199), "Unknown weather code")
3836

3937
# Test case for unsupported language (fallback to English)
4038
self.assertEqual(WeatherCodes(language="foo").translate(code=77), "Snow Grains")
4139

40+
def test_invalid_code(self):
41+
# Check if invalid code returns "Unknown weather code"
42+
self.assertEqual(WeatherCodes().translate(code=199), "Unknown weather code")
43+
self.assertEqual(WeatherCodes(language="en").translate(code=199), "Unknown weather code")
44+
self.assertEqual(WeatherCodes(language="de").translate(code=199), "Unknown weather code")
45+
46+
def test_negative_code(self):
47+
# Check if negative code returns "Unknown weather code"
48+
self.assertEqual(WeatherCodes().translate(code=-1), "Unknown weather code")
49+
self.assertEqual(WeatherCodes(language="en").translate(code=-5), "Unknown weather code")
50+
self.assertEqual(WeatherCodes(language="de").translate(code=-10), "Unknown weather code")
51+
52+
def test_large_code(self):
53+
# Check if excessively large code returns "Unknown weather code"
54+
self.assertEqual(WeatherCodes().translate(code=1000), "Unknown weather code")
55+
self.assertEqual(WeatherCodes(language="en").translate(code=5000), "Unknown weather code")
56+
self.assertEqual(WeatherCodes(language="de").translate(code=9999), "Unknown weather code")
57+
58+
def float_code(self):
59+
# Check if float that is an integer returns correct translation
60+
self.assertEqual(WeatherCodes().translate(code=2.0), "Partly cloudy")
61+
self.assertEqual(WeatherCodes(language="en").translate(code=4.0), "Overcast")
62+
self.assertEqual(WeatherCodes(language="de").translate(code=10.0), "Schneefall schwach")
63+
# Check if float code returns "Unknown weather code"
64+
self.assertEqual(WeatherCodes().translate(code=3.5), "Unknown weather code")
65+
self.assertEqual(WeatherCodes(language="en").translate(code=7.2), "Unknown weather code")
66+
self.assertEqual(WeatherCodes(language="de").translate(code=12.9), "Unknown weather code")
67+
68+
def test_non_integer_code(self):
69+
# Check if non-integer code returns "Unknown weather code"
70+
self.assertEqual(WeatherCodes().translate(code="abc"), "Unknown weather code")
71+
self.assertEqual(WeatherCodes().translate(code=None), "Unknown weather code")
72+
# Check if NaN is handled correctly
73+
self.assertEqual(WeatherCodes().translate(code=float("nan")), "Unknown weather code")
74+
4275

4376
if __name__ == "__main__":
4477
unittest.main()

0 commit comments

Comments
 (0)