simple-dwd-weatherforecast 2.0.31__py3-none-any.whl → 2.0.33__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- simple_dwd_weatherforecast/dwdforecast.py +128 -94
- simple_dwd_weatherforecast/dwdmap.py +48 -10
- {simple_dwd_weatherforecast-2.0.31.dist-info → simple_dwd_weatherforecast-2.0.33.dist-info}/METADATA +3 -1
- simple_dwd_weatherforecast-2.0.33.dist-info/RECORD +42 -0
- {simple_dwd_weatherforecast-2.0.31.dist-info → simple_dwd_weatherforecast-2.0.33.dist-info}/WHEEL +1 -1
- tests/test_get_daily_avg.py +56 -0
- tests/test_get_daily_condition.py +1 -2
- tests/test_get_daily_max.py +9 -2
- tests/test_get_daily_min.py +9 -2
- tests/test_get_daily_sum.py +11 -4
- tests/test_get_day_values.py +21 -2
- tests/test_get_forecast_condition.py +1 -2
- tests/test_get_forecast_data.py +1 -2
- tests/test_get_station_name.py +1 -2
- tests/test_get_timeframe_avg.py +1 -2
- tests/test_get_timeframe_condition.py +1 -2
- tests/test_get_timeframe_max.py +1 -2
- tests/test_get_timeframe_min.py +1 -2
- tests/test_get_timeframe_sum.py +1 -2
- tests/test_get_timeframe_values.py +1 -2
- tests/test_is_in_timerange.py +13 -2
- tests/test_is_valid_timeframe.py +2 -3
- tests/test_station.py +2 -2
- tests/test_update.py +1 -2
- tests/test_update_hourly.py +2 -3
- tests/test_weather.py +2 -3
- simple_dwd_weatherforecast-2.0.31.dist-info/RECORD +0 -41
- {simple_dwd_weatherforecast-2.0.31.dist-info → simple_dwd_weatherforecast-2.0.33.dist-info}/LICENCE +0 -0
- {simple_dwd_weatherforecast-2.0.31.dist-info → simple_dwd_weatherforecast-2.0.33.dist-info}/top_level.txt +0 -0
@@ -1,22 +1,23 @@
|
|
1
|
+
import csv
|
2
|
+
import importlib
|
3
|
+
import json
|
4
|
+
import math
|
1
5
|
from collections import OrderedDict, defaultdict
|
2
|
-
import
|
6
|
+
from datetime import datetime, timedelta, timezone
|
7
|
+
from enum import Enum
|
3
8
|
from io import BytesIO
|
4
9
|
from zipfile import ZipFile
|
5
|
-
|
6
|
-
from lxml import etree
|
7
|
-
from datetime import datetime, timedelta, timezone
|
10
|
+
|
8
11
|
import arrow
|
9
|
-
import
|
10
|
-
import
|
11
|
-
import csv
|
12
|
-
import importlib
|
12
|
+
import requests
|
13
|
+
from lxml import etree
|
13
14
|
|
14
|
-
with importlib.resources.files("simple_dwd_weatherforecast").joinpath(
|
15
|
+
with importlib.resources.files("simple_dwd_weatherforecast").joinpath( # type: ignore
|
15
16
|
"stations.json"
|
16
17
|
).open("r", encoding="utf-8") as file:
|
17
18
|
stations = json.load(file)
|
18
19
|
|
19
|
-
with importlib.resources.files("simple_dwd_weatherforecast").joinpath(
|
20
|
+
with importlib.resources.files("simple_dwd_weatherforecast").joinpath( # type: ignore
|
20
21
|
"uv_stations.json"
|
21
22
|
).open("r", encoding="utf-8") as file:
|
22
23
|
uv_stations = json.load(file)
|
@@ -32,6 +33,7 @@ def get_station_by_name(station_name: str):
|
|
32
33
|
for station in stations.items():
|
33
34
|
if station[1]["name"] == station_name:
|
34
35
|
return station
|
36
|
+
return None
|
35
37
|
|
36
38
|
|
37
39
|
def get_nearest_station_id(lat: float, lon: float):
|
@@ -40,14 +42,21 @@ def get_nearest_station_id(lat: float, lon: float):
|
|
40
42
|
|
41
43
|
def get_stations_sorted_by_distance(lat: float, lon: float):
|
42
44
|
"""
|
43
|
-
|
45
|
+
Return stations sort by distance.
|
46
|
+
|
47
|
+
Given a latitude and longitude, this function returns a list of stations sorted by
|
48
|
+
their distance from the provided location.
|
44
49
|
|
45
|
-
|
46
|
-
|
47
|
-
|
50
|
+
Args:
|
51
|
+
----
|
52
|
+
lat: The latitude of the location.
|
53
|
+
lon: The longitude of the location.
|
48
54
|
|
49
55
|
Returns:
|
50
|
-
|
56
|
+
-------
|
57
|
+
list: A list of stations sorted by distance, where each element is a list
|
58
|
+
containing the station ID and its distance from the location.
|
59
|
+
|
51
60
|
"""
|
52
61
|
result = []
|
53
62
|
for station in stations.items():
|
@@ -70,40 +79,40 @@ def get_distance(lat, lon, _lat, _lon):
|
|
70
79
|
|
71
80
|
def get_region(station_id: str):
|
72
81
|
if (
|
73
|
-
station_id in stations
|
74
|
-
and stations[station_id]["bundesland"] in Weather.region_codes
|
82
|
+
station_id in stations
|
83
|
+
and stations[station_id]["bundesland"] in Weather.region_codes
|
75
84
|
):
|
76
85
|
return stations[station_id]["bundesland"]
|
77
86
|
return None
|
78
87
|
|
79
88
|
|
80
89
|
class WeatherDataType(Enum):
|
81
|
-
CONDITION =
|
82
|
-
TEMPERATURE =
|
83
|
-
DEWPOINT =
|
84
|
-
PRESSURE =
|
85
|
-
WIND_SPEED =
|
90
|
+
CONDITION = ("condition", "present_weather")
|
91
|
+
TEMPERATURE = ("TTT", "dry_bulb_temperature_at_2_meter_above_ground") # Unit: K
|
92
|
+
DEWPOINT = ("Td", "dew_point_temperature_at_2_meter_above_ground") # Unit: K
|
93
|
+
PRESSURE = ("PPPP", "pressure_reduced_to_mean_sea_level") # Unit: Pa
|
94
|
+
WIND_SPEED = (
|
86
95
|
"FF",
|
87
96
|
"maximum_wind_speed_as_10_minutes_mean_during_last_hour",
|
88
|
-
|
89
|
-
WIND_DIRECTION =
|
97
|
+
) # Unit: m/s
|
98
|
+
WIND_DIRECTION = (
|
90
99
|
"DD",
|
91
100
|
"mean_wind_direction_during_last_10 min_at_10_meters_above_ground",
|
92
|
-
|
93
|
-
WIND_GUSTS =
|
94
|
-
PRECIPITATION =
|
95
|
-
PRECIPITATION_PROBABILITY =
|
96
|
-
PRECIPITATION_DURATION =
|
97
|
-
CLOUD_COVERAGE =
|
98
|
-
VISIBILITY =
|
99
|
-
SUN_DURATION =
|
100
|
-
SUN_IRRADIANCE =
|
101
|
-
FOG_PROBABILITY =
|
102
|
-
HUMIDITY =
|
101
|
+
) # Unit: Degrees
|
102
|
+
WIND_GUSTS = ("FX1", "maximum_wind_speed_last_hour") # Unit: m/s
|
103
|
+
PRECIPITATION = ("RR1c", "precipitation_amount_last_hour") # Unit: kg/m^2
|
104
|
+
PRECIPITATION_PROBABILITY = ("wwP", "") # Unit: % (0..100)
|
105
|
+
PRECIPITATION_DURATION = ("DRR1", "") # Unit: s
|
106
|
+
CLOUD_COVERAGE = ("N", "cloud_cover_total") # Unit: % (0..100)
|
107
|
+
VISIBILITY = ("VV", "horizontal_visibility") # Unit: m
|
108
|
+
SUN_DURATION = ("SunD1", "") # Unit: s
|
109
|
+
SUN_IRRADIANCE = ("Rad1h", "global_radiation_last_hour") # Unit: kJ/m^2
|
110
|
+
FOG_PROBABILITY = ("wwM", "") # Unit: % (0..100)
|
111
|
+
HUMIDITY = ("humidity", "relative_humidity") # Unit: %
|
103
112
|
|
104
113
|
|
105
114
|
class Weather:
|
106
|
-
"""A class for interacting with weather data from dwd.de"""
|
115
|
+
"""A class for interacting with weather data from dwd.de."""
|
107
116
|
|
108
117
|
NOT_AVAILABLE = "---"
|
109
118
|
|
@@ -254,15 +263,17 @@ class Weather:
|
|
254
263
|
self.region = get_region(station_id)
|
255
264
|
self.nearest_uv_index_station = self.get_nearest_station_id_with_uv()
|
256
265
|
else:
|
257
|
-
|
266
|
+
msg = "Not a valid station_id"
|
267
|
+
raise ValueError(msg)
|
258
268
|
|
259
269
|
def get_nearest_station_id_with_uv(self):
|
260
270
|
nearest_distance = float("inf")
|
261
271
|
nearest_station_id = None
|
272
|
+
_station = self.station
|
262
273
|
for station in uv_stations.items():
|
263
274
|
distance = get_distance(
|
264
|
-
self.station["lat"],
|
265
|
-
self.station["lon"],
|
275
|
+
self.station["lat"], # type: ignore
|
276
|
+
self.station["lon"], # type: ignore
|
266
277
|
station[1]["lat"],
|
267
278
|
station[1]["lon"],
|
268
279
|
)
|
@@ -274,17 +285,32 @@ class Weather:
|
|
274
285
|
return nearest_station_id
|
275
286
|
|
276
287
|
def get_station_name(self):
|
277
|
-
return self.station["name"]
|
288
|
+
return self.station["name"] # type: ignore
|
278
289
|
|
279
290
|
def is_in_timerange(self, timestamp: datetime):
|
280
291
|
return (
|
281
|
-
list(self.forecast_data.keys())[0]
|
292
|
+
list(self.forecast_data.keys())[0] # type: ignore
|
282
293
|
<= self.strip_to_hour_str(timestamp)
|
283
|
-
<= list(self.forecast_data.keys())[-1]
|
294
|
+
<= list(self.forecast_data.keys())[-1] # type: ignore
|
284
295
|
)
|
285
296
|
|
286
|
-
def
|
287
|
-
|
297
|
+
def is_in_timerange_day(self, timestamp: datetime):
|
298
|
+
return (
|
299
|
+
self.strip_to_day(
|
300
|
+
arrow.get(
|
301
|
+
list(self.forecast_data.keys())[0], "YYYY-MM-DDTHH:mm:ss.SSSZ" # type: ignore
|
302
|
+
).datetime
|
303
|
+
)
|
304
|
+
<= self.strip_to_day(timestamp)
|
305
|
+
<= self.strip_to_day(
|
306
|
+
arrow.get(
|
307
|
+
list(self.forecast_data.keys())[-1], "YYYY-MM-DDTHH:mm:ss.SSSZ" # type: ignore
|
308
|
+
).datetime
|
309
|
+
)
|
310
|
+
)
|
311
|
+
|
312
|
+
def is_valid_timeframe(self, timeframe: int) -> bool:
|
313
|
+
if timeframe > 24 or timeframe <= 0:
|
288
314
|
return False
|
289
315
|
return 24 % timeframe == 0
|
290
316
|
|
@@ -299,7 +325,7 @@ class Weather:
|
|
299
325
|
if shouldUpdate:
|
300
326
|
self.update()
|
301
327
|
if self.is_in_timerange(timestamp):
|
302
|
-
return self.forecast_data[self.strip_to_hour_str(timestamp)][
|
328
|
+
return self.forecast_data[self.strip_to_hour_str(timestamp)][ # type: ignore
|
303
329
|
weatherDataType.value[0]
|
304
330
|
]
|
305
331
|
return None
|
@@ -311,7 +337,7 @@ class Weather:
|
|
311
337
|
if self.is_in_timerange(timestamp):
|
312
338
|
return str(
|
313
339
|
self.weather_codes[
|
314
|
-
self.forecast_data[self.strip_to_hour_str(timestamp)][
|
340
|
+
self.forecast_data[self.strip_to_hour_str(timestamp)][ # type: ignore
|
315
341
|
WeatherDataType.CONDITION.value[0]
|
316
342
|
]
|
317
343
|
][0]
|
@@ -400,7 +426,7 @@ class Weather:
|
|
400
426
|
else:
|
401
427
|
print("no report for this station available. Have you updated first?")
|
402
428
|
|
403
|
-
def get_uv_index(self, days_from_today: int, shouldUpdate=True) -> int:
|
429
|
+
def get_uv_index(self, days_from_today: int, shouldUpdate=True) -> int | None:
|
404
430
|
if not self.uv_reports and shouldUpdate:
|
405
431
|
self.update(
|
406
432
|
force_hourly=False,
|
@@ -444,11 +470,11 @@ class Weather:
|
|
444
470
|
):
|
445
471
|
if shouldUpdate:
|
446
472
|
self.update()
|
447
|
-
if self.
|
473
|
+
if self.is_in_timerange_day(timestamp):
|
448
474
|
return self.get_max(self.get_day_values(timestamp), weatherDataType)
|
449
475
|
return None
|
450
476
|
|
451
|
-
def get_max(
|
477
|
+
def get_max(self, weather_data, weatherDataType: WeatherDataType):
|
452
478
|
value = None
|
453
479
|
for item in weather_data:
|
454
480
|
value_new = item[weatherDataType.value[0]]
|
@@ -481,11 +507,11 @@ class Weather:
|
|
481
507
|
):
|
482
508
|
if shouldUpdate:
|
483
509
|
self.update()
|
484
|
-
if self.
|
510
|
+
if self.is_in_timerange_day(timestamp):
|
485
511
|
return self.get_min(self.get_day_values(timestamp), weatherDataType)
|
486
512
|
return None
|
487
513
|
|
488
|
-
def get_min(
|
514
|
+
def get_min(self, weather_data, weatherDataType: WeatherDataType):
|
489
515
|
value = None
|
490
516
|
for item in weather_data:
|
491
517
|
value_new = item[weatherDataType.value[0]]
|
@@ -519,11 +545,11 @@ class Weather:
|
|
519
545
|
):
|
520
546
|
if shouldUpdate:
|
521
547
|
self.update()
|
522
|
-
if self.
|
548
|
+
if self.is_in_timerange_day(timestamp):
|
523
549
|
return self.get_sum(self.get_day_values(timestamp), weatherDataType)
|
524
550
|
return None
|
525
551
|
|
526
|
-
def get_sum(
|
552
|
+
def get_sum(self, weather_data, weatherDataType):
|
527
553
|
value_sum = 0.0
|
528
554
|
for item in weather_data:
|
529
555
|
value = item[weatherDataType.value[0]]
|
@@ -546,7 +572,16 @@ class Weather:
|
|
546
572
|
)
|
547
573
|
return None
|
548
574
|
|
549
|
-
def
|
575
|
+
def get_daily_avg(
|
576
|
+
self, weatherDataType: WeatherDataType, timestamp: datetime, shouldUpdate=True
|
577
|
+
):
|
578
|
+
if shouldUpdate:
|
579
|
+
self.update()
|
580
|
+
if self.is_in_timerange_day(timestamp):
|
581
|
+
return self.get_avg(self.get_day_values(timestamp), weatherDataType)
|
582
|
+
return None
|
583
|
+
|
584
|
+
def get_avg(self, weather_data, weatherDataType):
|
550
585
|
value_sum = 0.0
|
551
586
|
count = len(weather_data)
|
552
587
|
if count != 0:
|
@@ -565,54 +600,50 @@ class Weather:
|
|
565
600
|
for _ in range(timeframe):
|
566
601
|
hour_str = self.strip_to_hour_str(time_step)
|
567
602
|
time_step += timedelta(hours=1)
|
568
|
-
if hour_str not in self.forecast_data:
|
603
|
+
if hour_str not in self.forecast_data: # type: ignore
|
569
604
|
continue
|
570
|
-
result.append(self.forecast_data[hour_str])
|
605
|
+
result.append(self.forecast_data[hour_str]) # type: ignore
|
571
606
|
return result
|
572
607
|
|
573
608
|
def get_day_values(self, timestamp: datetime):
|
574
609
|
"timestamp has to be checked prior to be in timerange"
|
575
610
|
result = []
|
576
611
|
first_entry_date = arrow.get(
|
577
|
-
next(iter(self.forecast_data)), "YYYY-MM-DDTHH:mm:ss.SSSZ"
|
578
|
-
).datetime
|
612
|
+
next(iter(self.forecast_data)), "YYYY-MM-DDTHH:mm:ss.SSSZ" # type: ignore
|
613
|
+
).datetime # type: ignore
|
579
614
|
if timestamp.day != first_entry_date.day:
|
580
615
|
time_step = self.strip_to_day(timestamp)
|
581
616
|
for _ in range(24):
|
582
617
|
hour_str = self.strip_to_hour_str(time_step)
|
583
|
-
if hour_str not in self.forecast_data:
|
618
|
+
if hour_str not in self.forecast_data: # type: ignore
|
584
619
|
break
|
585
|
-
result.append(self.forecast_data[hour_str])
|
620
|
+
result.append(self.forecast_data[hour_str]) # type: ignore
|
586
621
|
time_step += timedelta(hours=1)
|
587
622
|
else:
|
588
623
|
time_step = first_entry_date
|
589
|
-
endtime = (
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
)
|
600
|
-
+ timedelta(days=1)
|
601
|
-
+ timedelta(hours=-1)
|
602
|
-
)
|
624
|
+
endtime = datetime(
|
625
|
+
time_step.year,
|
626
|
+
time_step.month,
|
627
|
+
time_step.day,
|
628
|
+
0,
|
629
|
+
0,
|
630
|
+
0,
|
631
|
+
0,
|
632
|
+
timezone.utc,
|
633
|
+
) + timedelta(days=1)
|
603
634
|
timediff = endtime - time_step
|
604
635
|
for _ in range(round(timediff.total_seconds() / 3600)):
|
605
|
-
result.append(self.forecast_data[self.strip_to_hour_str(time_step)])
|
636
|
+
result.append(self.forecast_data[self.strip_to_hour_str(time_step)]) # type: ignore
|
606
637
|
time_step += timedelta(hours=1)
|
607
638
|
return result
|
608
639
|
|
609
|
-
def strip_to_hour_str(
|
640
|
+
def strip_to_hour_str(self, timestamp: datetime):
|
610
641
|
return timestamp.strftime("%Y-%m-%dT%H:00:00.000Z")
|
611
642
|
|
612
|
-
def strip_to_hour(
|
643
|
+
def strip_to_hour(self, timestamp: datetime):
|
613
644
|
return datetime(timestamp.year, timestamp.month, timestamp.day, timestamp.hour)
|
614
645
|
|
615
|
-
def strip_to_day(
|
646
|
+
def strip_to_day(self, timestamp: datetime):
|
616
647
|
return datetime(timestamp.year, timestamp.month, timestamp.day)
|
617
648
|
|
618
649
|
def update(
|
@@ -672,7 +703,8 @@ class Weather:
|
|
672
703
|
|
673
704
|
self.loaded_station_name = self.parse_station_name(tree)
|
674
705
|
|
675
|
-
value
|
706
|
+
def value(wdt):
|
707
|
+
return self.get_weather_type(tree, wdt)
|
676
708
|
|
677
709
|
values = [
|
678
710
|
(wdt, value(wdt))
|
@@ -720,9 +752,9 @@ class Weather:
|
|
720
752
|
):
|
721
753
|
item = placemark.find(".//kml:name", namespaces=self.namespaces)
|
722
754
|
|
723
|
-
if item.text == self.station_id:
|
755
|
+
if item is not None and item.text == self.station_id:
|
724
756
|
return placemark
|
725
|
-
placemark.clear()
|
757
|
+
# placemark.clear()
|
726
758
|
|
727
759
|
def parse_issue_time(self, tree):
|
728
760
|
issue_time_new = arrow.get(
|
@@ -755,7 +787,9 @@ class Weather:
|
|
755
787
|
if None in (temperature, dewpoint):
|
756
788
|
return
|
757
789
|
|
758
|
-
celsius
|
790
|
+
def celsius(t):
|
791
|
+
return t - 273.1
|
792
|
+
|
759
793
|
T = celsius(temperature)
|
760
794
|
TD = celsius(dewpoint)
|
761
795
|
|
@@ -868,7 +902,7 @@ class Weather:
|
|
868
902
|
headers = {
|
869
903
|
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.116 Safari/537.36"
|
870
904
|
}
|
871
|
-
headers["If-None-Match"] = self.etags[url] if url in self.etags else ""
|
905
|
+
headers["If-None-Match"] = self.etags[url] if url in self.etags else "" # type: ignore
|
872
906
|
try:
|
873
907
|
request = requests.get(url, headers=headers, timeout=30)
|
874
908
|
# If resource has not been modified, return
|
@@ -876,7 +910,7 @@ class Weather:
|
|
876
910
|
return
|
877
911
|
elif request.status_code != 200:
|
878
912
|
raise Exception(f"Unexpected status code {request.status_code}")
|
879
|
-
self.etags[url] = request.headers["ETag"]
|
913
|
+
self.etags[url] = request.headers["ETag"] # type: ignore
|
880
914
|
uv_reports = json.loads(request.text)["content"]
|
881
915
|
# Match with existing stations
|
882
916
|
for uv_report in uv_reports:
|
@@ -884,22 +918,22 @@ class Weather:
|
|
884
918
|
self.uv_index_stations_reference_names[uv_report["city"]]
|
885
919
|
)
|
886
920
|
# uv_report.update({"lat": station[1]["lat"], "lon": station[1]["lon"]})
|
887
|
-
self.uv_reports[station[0]] = uv_report
|
921
|
+
self.uv_reports[station[0]] = uv_report # type: ignore
|
888
922
|
except Exception as error:
|
889
|
-
print(f"Error in
|
923
|
+
print(f"Error in download_uv_index: {type(error)} args: {error.args}")
|
890
924
|
|
891
925
|
def download_weather_report(self, region_code):
|
892
926
|
url = f"https://www.dwd.de/DWD/wetter/wv_allg/deutschland/text/vhdl13_{region_code}.html"
|
893
927
|
headers = {
|
894
928
|
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.116 Safari/537.36"
|
895
929
|
}
|
896
|
-
headers["If-None-Match"] = self.etags[url] if url in self.etags else ""
|
930
|
+
headers["If-None-Match"] = self.etags[url] if url in self.etags else "" # type: ignore
|
897
931
|
try:
|
898
932
|
request = requests.get(url, headers=headers, timeout=30)
|
899
933
|
# If resource has not been modified, return
|
900
934
|
if request.status_code == 304:
|
901
935
|
return
|
902
|
-
self.etags[url] = request.headers["ETag"]
|
936
|
+
self.etags[url] = request.headers["ETag"] # type: ignore
|
903
937
|
weather_report = request.text
|
904
938
|
a = weather_report.find(">")
|
905
939
|
if a != -1:
|
@@ -910,22 +944,22 @@ class Weather:
|
|
910
944
|
|
911
945
|
def download_latest_kml(self, stationid, force_hourly=False):
|
912
946
|
if force_hourly:
|
913
|
-
url =
|
947
|
+
url = "https://opendata.dwd.de/weather/local_forecasts/mos/MOSMIX_S/all_stations/kml/MOSMIX_S_LATEST_240.kmz"
|
914
948
|
else:
|
915
949
|
url = f"https://opendata.dwd.de/weather/local_forecasts/mos/MOSMIX_L/single_stations/{stationid}/kml/MOSMIX_L_LATEST_{stationid}.kmz"
|
916
|
-
headers = {"If-None-Match": self.etags[url] if url in self.etags else ""}
|
950
|
+
headers = {"If-None-Match": self.etags[url] if url in self.etags else ""} # type: ignore
|
917
951
|
try:
|
918
952
|
request = requests.get(url, headers=headers, timeout=30)
|
919
953
|
# If resource has not been modified, return
|
920
954
|
if request.status_code == 304:
|
921
955
|
return
|
922
|
-
self.etags[url] = request.headers["ETag"]
|
956
|
+
self.etags[url] = request.headers["ETag"] # type: ignore
|
923
957
|
with ZipFile(BytesIO(request.content), "r") as kmz:
|
924
958
|
# large RAM allocation
|
925
959
|
with kmz.open(kmz.namelist()[0], "r") as kml:
|
926
960
|
self.parse_kml(kml, force_hourly)
|
927
961
|
except Exception as error:
|
928
|
-
print(f"Error in
|
962
|
+
print(f"Error in download_latest_kml: {type(error)} args: {error.args}")
|
929
963
|
|
930
964
|
def download_latest_report(self):
|
931
965
|
station_id = self.station_id
|
@@ -934,7 +968,7 @@ class Weather:
|
|
934
968
|
url = (
|
935
969
|
f"https://opendata.dwd.de/weather/weather_reports/poi/{station_id}-BEOB.csv"
|
936
970
|
)
|
937
|
-
headers = {"If-None-Match": self.etags[url] if url in self.etags else ""}
|
971
|
+
headers = {"If-None-Match": self.etags[url] if url in self.etags else ""} # type: ignore
|
938
972
|
try:
|
939
973
|
response = requests.get(url, headers=headers, timeout=30)
|
940
974
|
if response.status_code == 200:
|
@@ -974,4 +1008,4 @@ class Weather:
|
|
974
1008
|
# Handle other status codes
|
975
1009
|
print(f"Failed to download report. Status code: {response.status_code}")
|
976
1010
|
except Exception as error:
|
977
|
-
print(f"Error in
|
1011
|
+
print(f"Error in download_latest_report: {type(error)} args: {error.args}")
|
@@ -6,7 +6,7 @@ from enum import Enum
|
|
6
6
|
|
7
7
|
|
8
8
|
class WeatherMapType(Enum):
|
9
|
-
NIEDERSCHLAGSRADAR = "dwd:Niederschlagsradar"
|
9
|
+
NIEDERSCHLAGSRADAR = "dwd:Niederschlagsradar,dwd:NCEW_EU"
|
10
10
|
MAXTEMP = "dwd:GefuehlteTempMax"
|
11
11
|
UVINDEX = "dwd:UVI_CS"
|
12
12
|
POLLENFLUG = "dwd:Pollenflug"
|
@@ -15,6 +15,7 @@ class WeatherMapType(Enum):
|
|
15
15
|
WARNUNGEN_GEMEINDEN = "dwd:Warnungen_Gemeinden"
|
16
16
|
WARNUNGEN_KREISE = "dwd:Warnungen_Landkreise"
|
17
17
|
|
18
|
+
|
18
19
|
class WeatherBackgroundMapType(Enum):
|
19
20
|
LAENDER = "dwd:Laender"
|
20
21
|
BUNDESLAENDER = "dwd:Warngebiete_Bundeslaender"
|
@@ -23,7 +24,16 @@ class WeatherBackgroundMapType(Enum):
|
|
23
24
|
SATELLIT = "dwd:bluemarble"
|
24
25
|
GEWAESSER = "dwd:Gewaesser"
|
25
26
|
|
26
|
-
|
27
|
+
|
28
|
+
def get_from_location(
|
29
|
+
longitude,
|
30
|
+
latitude,
|
31
|
+
radius_km,
|
32
|
+
map_type: WeatherMapType,
|
33
|
+
background_type: WeatherBackgroundMapType = WeatherBackgroundMapType.BUNDESLAENDER,
|
34
|
+
image_width=520,
|
35
|
+
image_height=580,
|
36
|
+
):
|
27
37
|
if radius_km <= 0:
|
28
38
|
raise ValueError("Radius must be greater than 0")
|
29
39
|
if latitude < -90 or latitude > 90:
|
@@ -31,17 +41,45 @@ def get_from_location(longitude, latitude, radius_km, map_type: WeatherMapType,
|
|
31
41
|
if longitude < -180 or longitude > 180:
|
32
42
|
raise ValueError("Longitude must be between -180 and 180")
|
33
43
|
radius = math.fabs(radius_km / (111.3 * math.cos(latitude)))
|
34
|
-
return get_map(
|
44
|
+
return get_map(
|
45
|
+
latitude - radius,
|
46
|
+
longitude - radius,
|
47
|
+
latitude + radius,
|
48
|
+
longitude + radius,
|
49
|
+
map_type,
|
50
|
+
background_type,
|
51
|
+
image_width,
|
52
|
+
image_height,
|
53
|
+
)
|
35
54
|
|
36
|
-
def get_germany(map_type: WeatherMapType, background_type: WeatherBackgroundMapType = WeatherBackgroundMapType.BUNDESLAENDER, image_width=520, image_height=580):
|
37
|
-
return get_map(4.4, 46.4, 16.1, 55.6, map_type, background_type, image_width, image_height)
|
38
55
|
|
39
|
-
def
|
40
|
-
|
41
|
-
|
56
|
+
def get_germany(
|
57
|
+
map_type: WeatherMapType,
|
58
|
+
background_type: WeatherBackgroundMapType = WeatherBackgroundMapType.BUNDESLAENDER,
|
59
|
+
image_width=520,
|
60
|
+
image_height=580,
|
61
|
+
):
|
62
|
+
return get_map(
|
63
|
+
4.4, 46.4, 16.1, 55.6, map_type, background_type, image_width, image_height
|
64
|
+
)
|
42
65
|
|
43
|
-
|
66
|
+
|
67
|
+
def get_map(
|
68
|
+
minx,
|
69
|
+
miny,
|
70
|
+
maxx,
|
71
|
+
maxy,
|
72
|
+
map_type: WeatherMapType,
|
73
|
+
background_type: WeatherBackgroundMapType,
|
74
|
+
image_width=520,
|
75
|
+
image_height=580,
|
76
|
+
):
|
77
|
+
if image_width > 1200 or image_height > 1400:
|
78
|
+
raise ValueError(
|
79
|
+
"Width and height must not exceed 1200 and 1400 respectively. Please be kind to the DWD servers."
|
80
|
+
)
|
81
|
+
url = f"https://maps.dwd.de/geoserver/dwd/wms?service=WMS&version=1.1.0&request=GetMap&layers={map_type.value},{background_type.value}&bbox={minx},{miny},{maxx},{maxy}&width={image_width}&height={image_height}&srs=EPSG:4326&styles=&format=image/png"
|
44
82
|
request = requests.get(url, stream=True)
|
45
83
|
if request.status_code == 200:
|
46
84
|
image = Image.open(BytesIO(request.content))
|
47
|
-
return image
|
85
|
+
return image
|
{simple_dwd_weatherforecast-2.0.31.dist-info → simple_dwd_weatherforecast-2.0.33.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: simple_dwd_weatherforecast
|
3
|
-
Version: 2.0.
|
3
|
+
Version: 2.0.33
|
4
4
|
Summary: A simple tool to retrieve a weather forecast from DWD OpenData
|
5
5
|
Home-page: https://github.com/FL550/simple_dwd_weatherforecast.git
|
6
6
|
Author: Max Fermor
|
@@ -127,6 +127,8 @@ class Weather:
|
|
127
127
|
|
128
128
|
get_timeframe_sum(weatherDataType: see WeatherDataType, datetime, timeframe: hours after datetime as int, optional bool shouldUpdate) # Returns the sum of that value within the time frame
|
129
129
|
|
130
|
+
get_daily_avg(weatherDataType: see WeatherDataType, datetime, optional bool shouldUpdate) # Returns the daily average of that value
|
131
|
+
|
130
132
|
get_timeframe_avg(weatherDataType: see WeatherDataType, datetime, timeframe: hours after datetime as int, optional bool shouldUpdate) # Returns the average of that value within the time frame
|
131
133
|
|
132
134
|
get_forecast_condition(datetime, optional bool shouldUpdate) # Result is condition as text
|
@@ -0,0 +1,42 @@
|
|
1
|
+
simple_dwd_weatherforecast/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
simple_dwd_weatherforecast/dwdforecast.py,sha256=4ZqQweWyXCMJ8NajdKV_Ca0WkbxrsJXKX7RD0lQayXU,36587
|
3
|
+
simple_dwd_weatherforecast/dwdmap.py,sha256=fZMi3nk4MwzZm0dJ8TUwCgOuTw6t8kZmnOkoHGKpXx4,2695
|
4
|
+
simple_dwd_weatherforecast/stations.json,sha256=1u8qc2CT_rVy49SAlOicGixzHln6Y0FXevuFAz2maBw,838948
|
5
|
+
simple_dwd_weatherforecast/uv_stations.json,sha256=ADenYo-aR6qbf0UFkfYr72kkFzL9HyUKe4VQ23POGF8,2292
|
6
|
+
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7
|
+
tests/dummy_data.py,sha256=sF8pXxq8innSpRc4lW0XxGcr34dESSAoBdAf2F2D4AI,63395
|
8
|
+
tests/dummy_data_full.py,sha256=IwYoeqVX8cpfn_gE-7arXaWV9btRm_HHqka_LfKqW-E,64053
|
9
|
+
tests/dummy_uv.py,sha256=TM1TwvNq_ea4WgIJyYsNAEXZJTx1dXH1ZVrW_LfLBLg,4548
|
10
|
+
tests/test_get_daily_avg.py,sha256=MNd7u9Y7_E6SIyxg59EKfdKx4QJ7R_WT5wvRgm7Gzks,2315
|
11
|
+
tests/test_get_daily_condition.py,sha256=foRgUO0W4ctWuer04RvUi2JqcFpiDBhFoLg9OdQSFj8,2004
|
12
|
+
tests/test_get_daily_max.py,sha256=schjGJDflg1YEQSdfRndw2NT4AvrVheSDveyVZrFySM,2220
|
13
|
+
tests/test_get_daily_min.py,sha256=GzqlOfLdwLqGLYVeAJ0fbK1JRq-_fT7z6Gea74lSY7E,2224
|
14
|
+
tests/test_get_daily_sum.py,sha256=UEntbPwGSleHocu0587WnuC5ijptQtzVNreR1d6yoM0,2221
|
15
|
+
tests/test_get_day_values.py,sha256=KOMkp_bh_nqZIufP8F3PNmF3WqXnPLOn6LKHrSi3SMA,29310
|
16
|
+
tests/test_get_forecast_condition.py,sha256=YVapxXH5Svq5P7XvWn48n19hriQ1-7CSezuGc6DswJQ,2866
|
17
|
+
tests/test_get_forecast_data.py,sha256=E1pIsgcWfQLgiVBMJ4URao3rBJgXebDWcPlV6FYZF-U,3394
|
18
|
+
tests/test_get_station_name.py,sha256=S18TZCUrLSRUgAz7sJ66igC0X-9JX_7O3Yh9Z6-QHnQ,423
|
19
|
+
tests/test_get_timeframe_avg.py,sha256=2XQU8posZWJ54fqcOTq2Ci0EE_KG38mtZ5Qi2emDL20,2425
|
20
|
+
tests/test_get_timeframe_condition.py,sha256=9ED_Uyx6v4Gq6qQ5SQyWtS_fOkbbhExKzfGGM18M38k,3237
|
21
|
+
tests/test_get_timeframe_max.py,sha256=gas73o1Ferkk87M4Y3k4LZ-fXKehI2ulsxcQfIviDTs,2407
|
22
|
+
tests/test_get_timeframe_min.py,sha256=WnFO36U_E-55TW7jR598lyLS4u-TOtXmufadJF7IWQs,2407
|
23
|
+
tests/test_get_timeframe_sum.py,sha256=hjHY9fvJUqi4o8AdFLPSDwi8GpXtjzLqsRzVMoUVjn4,2424
|
24
|
+
tests/test_get_timeframe_values.py,sha256=z8ozpWlyilw8-zjbk2gDExhTobNbgmcVTbZ6ZHjxAW4,6891
|
25
|
+
tests/test_is_in_timerange.py,sha256=3y88L3N73NxSTJ-_edx6OCnxHWKJWWFma98gjZvJDGg,1338
|
26
|
+
tests/test_is_valid_timeframe.py,sha256=mXjeu3lUyixiBUEljirTf6qDM_FZFQGWa-Rk0NBMUDU,891
|
27
|
+
tests/test_location_tools.py,sha256=wto_XzVnARJQ-Qc83YAn0ahfMBSaOHpfzqAeKRDsNm8,1208
|
28
|
+
tests/test_map.py,sha256=Bnf7XSap7xAfozE5U9aB0GjjMiHmXrpyJPE79VmEvFg,830
|
29
|
+
tests/test_parsekml.py,sha256=Y0EHJi5e8lqFUS8N9yfA5ByQkKV4-iyrwUaIipQpM7w,1065
|
30
|
+
tests/test_region.py,sha256=ReUB9Cy9roBemkpEkTjZZav-Mu3Ha7ADOAfa9J-gi80,877
|
31
|
+
tests/test_reported_weather.py,sha256=ULg4ogZRxus01p2rdxiSFL75AisqtcvnLDOc7uJMBH0,767
|
32
|
+
tests/test_station.py,sha256=Zjx-q0yxKVxVI_L1yB_bqY5pjZPoa1L94uC8Gx6shdY,1026
|
33
|
+
tests/test_stationsfile.py,sha256=slRH5N4Gznr6tkN2oMFWJbVCw3Xrma7Hvzn1lG5E-Qg,1401
|
34
|
+
tests/test_update.py,sha256=r763R-MfqFqQQy43PcmE9yWAiQU6T2rQ1u55ujlwMt8,7216
|
35
|
+
tests/test_update_hourly.py,sha256=mUc66JHndgIMdZ0DD_KXQTbPPg1g86F6CPUwJYjBV5U,1619
|
36
|
+
tests/test_uv_index.py,sha256=tr6wnOyHlXT1S3yp1oeHc4-Brmc-EMEdM4mtyrdpcHg,579
|
37
|
+
tests/test_weather.py,sha256=ZyX4ldUoJpJp7YpiNQwU6Od-nYRay-3qcaDJdNq8fhY,780
|
38
|
+
simple_dwd_weatherforecast-2.0.33.dist-info/LICENCE,sha256=27UG7gteqvSWuZlsbIq2_OAbh7VyifGGl-1zpuUoBcw,1072
|
39
|
+
simple_dwd_weatherforecast-2.0.33.dist-info/METADATA,sha256=OoVmEQpJwfD2ThO8I7DcKwD2Xcv0sm5izDTWFaJkOTs,10937
|
40
|
+
simple_dwd_weatherforecast-2.0.33.dist-info/WHEEL,sha256=Mdi9PDNwEZptOjTlUcAth7XJDFtKrHYaQMPulZeBCiQ,91
|
41
|
+
simple_dwd_weatherforecast-2.0.33.dist-info/top_level.txt,sha256=iyEobUh14Tzitx39Oi8qm0NhBrnZovl_dNKtvLUkLEM,33
|
42
|
+
simple_dwd_weatherforecast-2.0.33.dist-info/RECORD,,
|
@@ -0,0 +1,56 @@
|
|
1
|
+
from simple_dwd_weatherforecast.dwdforecast import WeatherDataType
|
2
|
+
import unittest
|
3
|
+
from unittest.mock import patch
|
4
|
+
from datetime import datetime
|
5
|
+
from simple_dwd_weatherforecast import dwdforecast
|
6
|
+
from dummy_data import parsed_data
|
7
|
+
|
8
|
+
|
9
|
+
class Weather_get_daily_avg(unittest.TestCase):
|
10
|
+
def setUp(self):
|
11
|
+
self.dwd_weather = dwdforecast.Weather("H889")
|
12
|
+
self.dwd_weather.forecast_data = parsed_data
|
13
|
+
self.dwd_weather.station_name = "BAD HOMBURG"
|
14
|
+
|
15
|
+
@patch("simple_dwd_weatherforecast.dwdforecast.Weather.update", return_value=None)
|
16
|
+
def test_shouldupdate(self, mock_update):
|
17
|
+
test_time = datetime(2020, 11, 7, 3, 30)
|
18
|
+
self.dwd_weather.get_daily_avg(WeatherDataType.PRECIPITATION, test_time, True)
|
19
|
+
mock_update.assert_called()
|
20
|
+
|
21
|
+
@patch("simple_dwd_weatherforecast.dwdforecast.Weather.update", return_value=None)
|
22
|
+
def test_shouldupdate_not(self, mock_update):
|
23
|
+
test_time = datetime(2020, 11, 7, 3, 30)
|
24
|
+
self.dwd_weather.get_daily_avg(WeatherDataType.PRECIPITATION, test_time, False)
|
25
|
+
mock_update.assert_not_called()
|
26
|
+
|
27
|
+
def test_not_in_timerange(self):
|
28
|
+
test_time = datetime(2000, 11, 7, 3, 30)
|
29
|
+
self.assertIsNone(
|
30
|
+
self.dwd_weather.get_daily_avg(WeatherDataType.PRECIPITATION, test_time)
|
31
|
+
)
|
32
|
+
|
33
|
+
@patch("simple_dwd_weatherforecast.dwdforecast.Weather.update", return_value=None)
|
34
|
+
def test_precipitation(self, mock_update):
|
35
|
+
test_time = datetime(2020, 11, 6, 12, 0)
|
36
|
+
print(self.dwd_weather.get_day_values(test_time))
|
37
|
+
self.assertEqual(
|
38
|
+
self.dwd_weather.get_daily_avg(WeatherDataType.PRECIPITATION, test_time),
|
39
|
+
1.8,
|
40
|
+
)
|
41
|
+
|
42
|
+
@patch("simple_dwd_weatherforecast.dwdforecast.Weather.update", return_value=None)
|
43
|
+
def test_temperature(self, mock_update):
|
44
|
+
test_time = datetime(2020, 11, 6, 23, 0)
|
45
|
+
self.assertEqual(
|
46
|
+
self.dwd_weather.get_daily_avg(WeatherDataType.TEMPERATURE, test_time),
|
47
|
+
278.09,
|
48
|
+
)
|
49
|
+
|
50
|
+
@patch("simple_dwd_weatherforecast.dwdforecast.Weather.update", return_value=None)
|
51
|
+
def test_midnight(self, mock_update):
|
52
|
+
test_time = datetime(2020, 11, 6, 0, 0)
|
53
|
+
self.assertEqual(
|
54
|
+
self.dwd_weather.get_daily_avg(WeatherDataType.TEMPERATURE, test_time),
|
55
|
+
278.09,
|
56
|
+
)
|
@@ -8,8 +8,7 @@ from dummy_data import parsed_data
|
|
8
8
|
class Weather_get_daily_condition(unittest.TestCase):
|
9
9
|
def setUp(self):
|
10
10
|
self.dwd_weather = dwdforecast.Weather("H889")
|
11
|
-
self.dwd_weather.forecast_data = parsed_data
|
12
|
-
self.dwd_weather.station_name = "BAD HOMBURG"
|
11
|
+
self.dwd_weather.forecast_data = parsed_data # type: ignore
|
13
12
|
|
14
13
|
@patch("simple_dwd_weatherforecast.dwdforecast.Weather.update", return_value=None)
|
15
14
|
def test_shouldupdate(self, mock_update):
|
tests/test_get_daily_max.py
CHANGED
@@ -9,8 +9,7 @@ from dummy_data import parsed_data
|
|
9
9
|
class Weather_get_daily_max(unittest.TestCase):
|
10
10
|
def setUp(self):
|
11
11
|
self.dwd_weather = dwdforecast.Weather("H889")
|
12
|
-
self.dwd_weather.forecast_data = parsed_data
|
13
|
-
self.dwd_weather.station_name = "BAD HOMBURG"
|
12
|
+
self.dwd_weather.forecast_data = parsed_data # type: ignore
|
14
13
|
|
15
14
|
@patch("simple_dwd_weatherforecast.dwdforecast.Weather.update", return_value=None)
|
16
15
|
def test_shouldupdate(self, mock_update):
|
@@ -45,3 +44,11 @@ class Weather_get_daily_max(unittest.TestCase):
|
|
45
44
|
self.dwd_weather.get_daily_max(WeatherDataType.TEMPERATURE, test_time),
|
46
45
|
283.05,
|
47
46
|
)
|
47
|
+
|
48
|
+
@patch("simple_dwd_weatherforecast.dwdforecast.Weather.update", return_value=None)
|
49
|
+
def test_midnight(self, mock_update):
|
50
|
+
test_time = datetime(2020, 11, 6, 0, 0)
|
51
|
+
self.assertEqual(
|
52
|
+
self.dwd_weather.get_daily_max(WeatherDataType.TEMPERATURE, test_time),
|
53
|
+
283.05,
|
54
|
+
)
|
tests/test_get_daily_min.py
CHANGED
@@ -9,8 +9,7 @@ from dummy_data import parsed_data
|
|
9
9
|
class Weather_get_daily_min(unittest.TestCase):
|
10
10
|
def setUp(self):
|
11
11
|
self.dwd_weather = dwdforecast.Weather("H889")
|
12
|
-
self.dwd_weather.forecast_data = parsed_data
|
13
|
-
self.dwd_weather.station_name = "BAD HOMBURG"
|
12
|
+
self.dwd_weather.forecast_data = parsed_data # type: ignore
|
14
13
|
|
15
14
|
@patch("simple_dwd_weatherforecast.dwdforecast.Weather.update", return_value=None)
|
16
15
|
def test_shouldupdate(self, mock_update):
|
@@ -45,3 +44,11 @@ class Weather_get_daily_min(unittest.TestCase):
|
|
45
44
|
self.dwd_weather.get_daily_min(WeatherDataType.TEMPERATURE, test_time),
|
46
45
|
272.95,
|
47
46
|
)
|
47
|
+
|
48
|
+
@patch("simple_dwd_weatherforecast.dwdforecast.Weather.update", return_value=None)
|
49
|
+
def test_midnight(self, mock_update):
|
50
|
+
test_time = datetime(2020, 11, 6, 0, 0)
|
51
|
+
self.assertEqual(
|
52
|
+
self.dwd_weather.get_daily_min(WeatherDataType.TEMPERATURE, test_time),
|
53
|
+
272.95,
|
54
|
+
)
|
tests/test_get_daily_sum.py
CHANGED
@@ -9,8 +9,7 @@ from dummy_data import parsed_data
|
|
9
9
|
class Weather_get_daily_sum(unittest.TestCase):
|
10
10
|
def setUp(self):
|
11
11
|
self.dwd_weather = dwdforecast.Weather("H889")
|
12
|
-
self.dwd_weather.forecast_data = parsed_data
|
13
|
-
self.dwd_weather.station_name = "BAD HOMBURG"
|
12
|
+
self.dwd_weather.forecast_data = parsed_data # type: ignore
|
14
13
|
|
15
14
|
@patch("simple_dwd_weatherforecast.dwdforecast.Weather.update", return_value=None)
|
16
15
|
def test_shouldupdate(self, mock_update):
|
@@ -35,7 +34,7 @@ class Weather_get_daily_sum(unittest.TestCase):
|
|
35
34
|
test_time = datetime(2020, 11, 6, 10, 0)
|
36
35
|
self.assertEqual(
|
37
36
|
self.dwd_weather.get_daily_sum(WeatherDataType.PRECIPITATION, test_time),
|
38
|
-
|
37
|
+
36.01,
|
39
38
|
)
|
40
39
|
|
41
40
|
@patch("simple_dwd_weatherforecast.dwdforecast.Weather.update", return_value=None)
|
@@ -43,5 +42,13 @@ class Weather_get_daily_sum(unittest.TestCase):
|
|
43
42
|
test_time = datetime(2020, 11, 6, 10, 0)
|
44
43
|
self.assertEqual(
|
45
44
|
self.dwd_weather.get_daily_sum(WeatherDataType.TEMPERATURE, test_time),
|
46
|
-
|
45
|
+
5561.8,
|
46
|
+
)
|
47
|
+
|
48
|
+
@patch("simple_dwd_weatherforecast.dwdforecast.Weather.update", return_value=None)
|
49
|
+
def test_midnight(self, mock_update):
|
50
|
+
test_time = datetime(2020, 11, 6, 0, 0)
|
51
|
+
self.assertEqual(
|
52
|
+
self.dwd_weather.get_daily_sum(WeatherDataType.TEMPERATURE, test_time),
|
53
|
+
5561.8,
|
47
54
|
)
|
tests/test_get_day_values.py
CHANGED
@@ -7,8 +7,7 @@ from dummy_data import parsed_data
|
|
7
7
|
class Weather_get_day_values(unittest.TestCase):
|
8
8
|
def setUp(self):
|
9
9
|
self.dwd_weather = dwdforecast.Weather("H889")
|
10
|
-
self.dwd_weather.forecast_data = parsed_data
|
11
|
-
self.dwd_weather.station_name = "BAD HOMBURG"
|
10
|
+
self.dwd_weather.forecast_data = parsed_data # type: ignore
|
12
11
|
|
13
12
|
def test_day_not_current_day(self):
|
14
13
|
test_time = datetime(2020, 11, 7, 10, 0)
|
@@ -451,6 +450,7 @@ class Weather_get_day_values(unittest.TestCase):
|
|
451
450
|
self.dwd_weather.get_day_values(test_time),
|
452
451
|
test_data,
|
453
452
|
)
|
453
|
+
self.assertEqual(len(self.dwd_weather.get_day_values(test_time)), 24)
|
454
454
|
|
455
455
|
def test_day_not_last_day(self):
|
456
456
|
test_time = datetime(2020, 11, 16, 1, 0)
|
@@ -1004,8 +1004,27 @@ class Weather_get_day_values(unittest.TestCase):
|
|
1004
1004
|
"wwM": 3.0,
|
1005
1005
|
"humidity": 81.3,
|
1006
1006
|
},
|
1007
|
+
{
|
1008
|
+
"TTT": 275.55,
|
1009
|
+
"Td": 273.45,
|
1010
|
+
"condition": "51",
|
1011
|
+
"PPPP": 103060.0,
|
1012
|
+
"DD": 52.0,
|
1013
|
+
"FF": 1.54,
|
1014
|
+
"FX1": 3.09,
|
1015
|
+
"RR1c": 8.76,
|
1016
|
+
"wwP": 2.0,
|
1017
|
+
"DRR1": 0.0,
|
1018
|
+
"N": 22.0,
|
1019
|
+
"VV": 15500.0,
|
1020
|
+
"SunD1": 0.0,
|
1021
|
+
"Rad1h": None,
|
1022
|
+
"wwM": 2.0,
|
1023
|
+
"humidity": 86.0,
|
1024
|
+
},
|
1007
1025
|
]
|
1008
1026
|
self.assertEqual(
|
1009
1027
|
self.dwd_weather.get_day_values(test_time),
|
1010
1028
|
test_data,
|
1011
1029
|
)
|
1030
|
+
self.assertEqual(len(self.dwd_weather.get_day_values(test_time)), 20)
|
@@ -8,8 +8,7 @@ from dummy_data import parsed_data
|
|
8
8
|
class Weather_get_forecast_condition(unittest.TestCase):
|
9
9
|
def setUp(self):
|
10
10
|
self.dwd_weather = dwdforecast.Weather("H889")
|
11
|
-
self.dwd_weather.forecast_data = parsed_data
|
12
|
-
self.dwd_weather.station_name = "BAD HOMBURG"
|
11
|
+
self.dwd_weather.forecast_data = parsed_data # type: ignore
|
13
12
|
|
14
13
|
@patch("simple_dwd_weatherforecast.dwdforecast.Weather.update", return_value=None)
|
15
14
|
def test_shouldupdate(self, mock_update):
|
tests/test_get_forecast_data.py
CHANGED
@@ -9,8 +9,7 @@ from dummy_data import parsed_data
|
|
9
9
|
class Weather_get_forecast_data(unittest.TestCase):
|
10
10
|
def setUp(self):
|
11
11
|
self.dwd_weather = dwdforecast.Weather("H889")
|
12
|
-
self.dwd_weather.forecast_data = parsed_data
|
13
|
-
self.dwd_weather.station_name = "BAD HOMBURG"
|
12
|
+
self.dwd_weather.forecast_data = parsed_data # type: ignore
|
14
13
|
|
15
14
|
@patch("simple_dwd_weatherforecast.dwdforecast.Weather.update", return_value=None)
|
16
15
|
def test_shouldupdate(self, mock_update):
|
tests/test_get_station_name.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
import unittest
|
2
|
-
from unittest.mock import patch
|
3
2
|
from simple_dwd_weatherforecast import dwdforecast
|
4
3
|
from dummy_data import parsed_data
|
5
4
|
|
@@ -7,7 +6,7 @@ from dummy_data import parsed_data
|
|
7
6
|
class Weather_get_station_name(unittest.TestCase):
|
8
7
|
def setUp(self):
|
9
8
|
self.dwd_weather = dwdforecast.Weather("H889")
|
10
|
-
self.dwd_weather.forecast_data = parsed_data
|
9
|
+
self.dwd_weather.forecast_data = parsed_data # type: ignore
|
11
10
|
|
12
11
|
def test_get_station_name(self):
|
13
12
|
self.assertEqual(self.dwd_weather.get_station_name(), "Burbach-Wuergendorf")
|
tests/test_get_timeframe_avg.py
CHANGED
@@ -9,8 +9,7 @@ from dummy_data import parsed_data
|
|
9
9
|
class Weather_get_timeframe_avg(unittest.TestCase):
|
10
10
|
def setUp(self):
|
11
11
|
self.dwd_weather = dwdforecast.Weather("H889")
|
12
|
-
self.dwd_weather.forecast_data = parsed_data
|
13
|
-
self.dwd_weather.station_name = "BAD HOMBURG"
|
12
|
+
self.dwd_weather.forecast_data = parsed_data # type: ignore
|
14
13
|
|
15
14
|
@patch("simple_dwd_weatherforecast.dwdforecast.Weather.update", return_value=None)
|
16
15
|
def test_shouldupdate(self, mock_update):
|
@@ -8,8 +8,7 @@ from dummy_data import parsed_data
|
|
8
8
|
class Weather_get_timeframe_condition(unittest.TestCase):
|
9
9
|
def setUp(self):
|
10
10
|
self.dwd_weather = dwdforecast.Weather("H889")
|
11
|
-
self.dwd_weather.forecast_data = parsed_data
|
12
|
-
self.dwd_weather.station_name = "BAD HOMBURG"
|
11
|
+
self.dwd_weather.forecast_data = parsed_data # type: ignore
|
13
12
|
|
14
13
|
@patch("simple_dwd_weatherforecast.dwdforecast.Weather.update", return_value=None)
|
15
14
|
def test_shouldupdate(self, mock_update):
|
tests/test_get_timeframe_max.py
CHANGED
@@ -9,8 +9,7 @@ from dummy_data import parsed_data
|
|
9
9
|
class Weather_get_timeframe_max(unittest.TestCase):
|
10
10
|
def setUp(self):
|
11
11
|
self.dwd_weather = dwdforecast.Weather("H889")
|
12
|
-
self.dwd_weather.forecast_data = parsed_data
|
13
|
-
self.dwd_weather.station_name = "BAD HOMBURG"
|
12
|
+
self.dwd_weather.forecast_data = parsed_data # type: ignore
|
14
13
|
|
15
14
|
@patch("simple_dwd_weatherforecast.dwdforecast.Weather.update", return_value=None)
|
16
15
|
def test_shouldupdate(self, mock_update):
|
tests/test_get_timeframe_min.py
CHANGED
@@ -9,8 +9,7 @@ from dummy_data import parsed_data
|
|
9
9
|
class Weather_get_timeframe_min(unittest.TestCase):
|
10
10
|
def setUp(self):
|
11
11
|
self.dwd_weather = dwdforecast.Weather("H889")
|
12
|
-
self.dwd_weather.forecast_data = parsed_data
|
13
|
-
self.dwd_weather.station_name = "BAD HOMBURG"
|
12
|
+
self.dwd_weather.forecast_data = parsed_data # type: ignore
|
14
13
|
|
15
14
|
@patch("simple_dwd_weatherforecast.dwdforecast.Weather.update", return_value=None)
|
16
15
|
def test_shouldupdate(self, mock_update):
|
tests/test_get_timeframe_sum.py
CHANGED
@@ -9,8 +9,7 @@ from dummy_data import parsed_data
|
|
9
9
|
class Weather_get_timeframe_sum(unittest.TestCase):
|
10
10
|
def setUp(self):
|
11
11
|
self.dwd_weather = dwdforecast.Weather("H889")
|
12
|
-
self.dwd_weather.forecast_data = parsed_data
|
13
|
-
self.dwd_weather.station_name = "BAD HOMBURG"
|
12
|
+
self.dwd_weather.forecast_data = parsed_data # type: ignore
|
14
13
|
|
15
14
|
@patch("simple_dwd_weatherforecast.dwdforecast.Weather.update", return_value=None)
|
16
15
|
def test_shouldupdate(self, mock_update):
|
@@ -7,8 +7,7 @@ from dummy_data import parsed_data
|
|
7
7
|
class Weather_get_timeframe_values(unittest.TestCase):
|
8
8
|
def setUp(self):
|
9
9
|
self.dwd_weather = dwdforecast.Weather("H889")
|
10
|
-
self.dwd_weather.forecast_data = parsed_data
|
11
|
-
self.dwd_weather.station_name = "BAD HOMBURG"
|
10
|
+
self.dwd_weather.forecast_data = parsed_data # type: ignore
|
12
11
|
|
13
12
|
def test_timeframe_6(self):
|
14
13
|
test_time = datetime(2020, 11, 7, 1, 0)
|
tests/test_is_in_timerange.py
CHANGED
@@ -7,8 +7,7 @@ from dummy_data import parsed_data
|
|
7
7
|
class Weather_is_in_timerange(unittest.TestCase):
|
8
8
|
def setUp(self):
|
9
9
|
self.dwd_weather = dwdforecast.Weather("H889")
|
10
|
-
self.dwd_weather.forecast_data = parsed_data
|
11
|
-
self.dwd_weather.station_name = "BAD HOMBURG"
|
10
|
+
self.dwd_weather.forecast_data = parsed_data # type: ignore
|
12
11
|
|
13
12
|
def test_is_in_timerange(self):
|
14
13
|
test_time = datetime(2020, 11, 7, 3, 30)
|
@@ -21,3 +20,15 @@ class Weather_is_in_timerange(unittest.TestCase):
|
|
21
20
|
def test_is_in_timerange_future(self):
|
22
21
|
test_time = datetime(2102, 11, 5, 7, 30)
|
23
22
|
self.assertFalse(self.dwd_weather.is_in_timerange(test_time))
|
23
|
+
|
24
|
+
def test_is_in_timerange_day(self):
|
25
|
+
test_time = datetime(2020, 11, 6, 0, 0)
|
26
|
+
self.assertTrue(self.dwd_weather.is_in_timerange_day(test_time))
|
27
|
+
|
28
|
+
def test_is_in_timerange_day_past(self):
|
29
|
+
test_time = datetime(2000, 11, 5, 17, 30)
|
30
|
+
self.assertFalse(self.dwd_weather.is_in_timerange(test_time))
|
31
|
+
|
32
|
+
def test_is_in_timerange_day_future(self):
|
33
|
+
test_time = datetime(2102, 11, 5, 7, 30)
|
34
|
+
self.assertFalse(self.dwd_weather.is_in_timerange(test_time))
|
tests/test_is_valid_timeframe.py
CHANGED
@@ -6,8 +6,7 @@ from dummy_data import parsed_data
|
|
6
6
|
class Weather_is_valid_timeframe(unittest.TestCase):
|
7
7
|
def setUp(self):
|
8
8
|
self.dwd_weather = dwdforecast.Weather("H889")
|
9
|
-
self.dwd_weather.forecast_data = parsed_data
|
10
|
-
self.dwd_weather.station_name = "BAD HOMBURG"
|
9
|
+
self.dwd_weather.forecast_data = parsed_data # type: ignore
|
11
10
|
|
12
11
|
def test_is_day(self):
|
13
12
|
self.assertTrue(self.dwd_weather.is_valid_timeframe(24))
|
@@ -25,4 +24,4 @@ class Weather_is_valid_timeframe(unittest.TestCase):
|
|
25
24
|
self.assertFalse(self.dwd_weather.is_valid_timeframe(0))
|
26
25
|
|
27
26
|
def test_is_negative(self):
|
28
|
-
self.assertFalse(self.dwd_weather.is_valid_timeframe(-5))
|
27
|
+
self.assertFalse(self.dwd_weather.is_valid_timeframe(-5))
|
tests/test_station.py
CHANGED
@@ -17,7 +17,7 @@ class StationTestCase(unittest.TestCase):
|
|
17
17
|
|
18
18
|
def test_is_valid_station_id_empty_string(self):
|
19
19
|
self.assertFalse(dwdforecast.load_station_id(""))
|
20
|
-
self.assertFalse(dwdforecast.load_station_id(1))
|
20
|
+
self.assertFalse(dwdforecast.load_station_id("1"))
|
21
21
|
|
22
22
|
def test_get_station_by_name(self):
|
23
|
-
self.assertEqual(dwdforecast.get_station_by_name("Ulm")[0], "10838")
|
23
|
+
self.assertEqual(dwdforecast.get_station_by_name("Ulm")[0], "10838") # type: ignore
|
tests/test_update.py
CHANGED
@@ -9,8 +9,7 @@ import time
|
|
9
9
|
class WeatherUpdate(unittest.TestCase):
|
10
10
|
def setUp(self):
|
11
11
|
self.dwd_weather = dwdforecast.Weather("H889")
|
12
|
-
self.dwd_weather.forecast_data = parsed_data
|
13
|
-
self.dwd_weather.station_name = "BAD HOMBURG"
|
12
|
+
self.dwd_weather.forecast_data = parsed_data # type: ignore
|
14
13
|
|
15
14
|
@patch(
|
16
15
|
"simple_dwd_weatherforecast.dwdforecast.Weather.download_latest_report",
|
tests/test_update_hourly.py
CHANGED
@@ -2,15 +2,14 @@ import unittest
|
|
2
2
|
from unittest.mock import patch
|
3
3
|
from simple_dwd_weatherforecast import dwdforecast
|
4
4
|
from dummy_data import parsed_data
|
5
|
-
from datetime import datetime, timezone
|
5
|
+
from datetime import datetime, timezone
|
6
6
|
import time
|
7
7
|
|
8
8
|
|
9
9
|
class WeatherUpdate(unittest.TestCase):
|
10
10
|
def setUp(self):
|
11
11
|
self.dwd_weather = dwdforecast.Weather("H889")
|
12
|
-
self.dwd_weather.forecast_data = parsed_data
|
13
|
-
self.dwd_weather.station_name = "BAD HOMBURG"
|
12
|
+
self.dwd_weather.forecast_data = parsed_data # type: ignore
|
14
13
|
|
15
14
|
def test_download(self):
|
16
15
|
self.dwd_weather.update(force_hourly=True)
|
tests/test_weather.py
CHANGED
@@ -6,8 +6,7 @@ from dummy_data import parsed_data
|
|
6
6
|
class WeatherInit(unittest.TestCase):
|
7
7
|
def setUp(self):
|
8
8
|
self.dwd_weather = dwdforecast.Weather("L821")
|
9
|
-
self.dwd_weather.forecast_data = parsed_data
|
10
|
-
self.dwd_weather.station_name = "BAD HOMBURG"
|
9
|
+
self.dwd_weather.forecast_data = parsed_data # type: ignore
|
11
10
|
|
12
11
|
def test_init_with_wrong_id(self):
|
13
12
|
with self.assertRaises(ValueError) as _:
|
@@ -19,7 +18,7 @@ class WeatherInit(unittest.TestCase):
|
|
19
18
|
|
20
19
|
def test_init_with_no_id(self):
|
21
20
|
with self.assertRaises(TypeError) as _:
|
22
|
-
dwdforecast.Weather()
|
21
|
+
dwdforecast.Weather() # type: ignore
|
23
22
|
|
24
23
|
def test_uv_index(self):
|
25
24
|
self.assertEqual(self.dwd_weather.nearest_uv_index_station, "10637")
|
@@ -1,41 +0,0 @@
|
|
1
|
-
simple_dwd_weatherforecast/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
simple_dwd_weatherforecast/dwdforecast.py,sha256=ZTKS32lRedjzNucZHZ0w9bBuzJd3--EibU9wG6Au_M4,35282
|
3
|
-
simple_dwd_weatherforecast/dwdmap.py,sha256=bFAZfYjUryZkErJ3aDNRm9ZhNwKGMcy4pEl8TnWJKzA,2476
|
4
|
-
simple_dwd_weatherforecast/stations.json,sha256=1u8qc2CT_rVy49SAlOicGixzHln6Y0FXevuFAz2maBw,838948
|
5
|
-
simple_dwd_weatherforecast/uv_stations.json,sha256=ADenYo-aR6qbf0UFkfYr72kkFzL9HyUKe4VQ23POGF8,2292
|
6
|
-
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7
|
-
tests/dummy_data.py,sha256=sF8pXxq8innSpRc4lW0XxGcr34dESSAoBdAf2F2D4AI,63395
|
8
|
-
tests/dummy_data_full.py,sha256=IwYoeqVX8cpfn_gE-7arXaWV9btRm_HHqka_LfKqW-E,64053
|
9
|
-
tests/dummy_uv.py,sha256=TM1TwvNq_ea4WgIJyYsNAEXZJTx1dXH1ZVrW_LfLBLg,4548
|
10
|
-
tests/test_get_daily_condition.py,sha256=J_0gg9fejPqESP7Np5wl5kr7wwcm04a-G1Txx4JH02g,2043
|
11
|
-
tests/test_get_daily_max.py,sha256=vC873a1Uuw01a61fkvV0XKwTnkxJWkrOycTOSLrOp9Q,1940
|
12
|
-
tests/test_get_daily_min.py,sha256=jYgPK-dK7P_QRBe1oNlSSZlnd-BrTDXGt5AfS-sEThw,1944
|
13
|
-
tests/test_get_daily_sum.py,sha256=WeOVXl905X-n5gnPuNM6ut-q9VeBM2j2f6heBDol3Ok,1942
|
14
|
-
tests/test_get_day_values.py,sha256=zn9GaLnsLA_rvk6pNApmR9ZHD_OsElU01T6oFJiqL7I,28680
|
15
|
-
tests/test_get_forecast_condition.py,sha256=mawVIJ7xQgH9Xbz_nLnAHNS5Ib-mp-A3zLbkEllw2hw,2904
|
16
|
-
tests/test_get_forecast_data.py,sha256=dsKtfGTlQbhB7tteBuB6d_RHEOtM7ikfVWh_Fk-CtyU,3432
|
17
|
-
tests/test_get_station_name.py,sha256=58fBETHAgDaTAsAFP9xOcYfipNO-AqUf_n_bMj5FwWc,439
|
18
|
-
tests/test_get_timeframe_avg.py,sha256=Ye22DDmNBQB1uDUHz3r92zA-9ItTZiPxNwsLHoNfc0g,2463
|
19
|
-
tests/test_get_timeframe_condition.py,sha256=scalL3W4Ts1epuqsdyBDoN0NG85dj2wYH8zzr2T7ld8,3276
|
20
|
-
tests/test_get_timeframe_max.py,sha256=AM4arB06XRymq3cSCUPeT9f-MtejuMjZkgzM0NCVfrs,2445
|
21
|
-
tests/test_get_timeframe_min.py,sha256=dLkPfOSfQaEXiw1AAmXTjhaqTEu7-s1Gj_KI5Jmqvbo,2445
|
22
|
-
tests/test_get_timeframe_sum.py,sha256=3jzxUwFvqkEmVRCZc_3_5Piu05pDbxDdcCNTZWD8x0Y,2462
|
23
|
-
tests/test_get_timeframe_values.py,sha256=qcS-gWD4D3zF3lYBuhULlM28NfAISnHFSYjdVVhNBq8,6929
|
24
|
-
tests/test_is_in_timerange.py,sha256=Zy8YwO_O272IwjdrGKfqARw5EPnIdhZM5e648bBXaWQ,870
|
25
|
-
tests/test_is_valid_timeframe.py,sha256=lmQTapM6BztqFEgei-JTuuETq5ZhscPzxqT2Y2_u3Ak,928
|
26
|
-
tests/test_location_tools.py,sha256=wto_XzVnARJQ-Qc83YAn0ahfMBSaOHpfzqAeKRDsNm8,1208
|
27
|
-
tests/test_map.py,sha256=Bnf7XSap7xAfozE5U9aB0GjjMiHmXrpyJPE79VmEvFg,830
|
28
|
-
tests/test_parsekml.py,sha256=Y0EHJi5e8lqFUS8N9yfA5ByQkKV4-iyrwUaIipQpM7w,1065
|
29
|
-
tests/test_region.py,sha256=ReUB9Cy9roBemkpEkTjZZav-Mu3Ha7ADOAfa9J-gi80,877
|
30
|
-
tests/test_reported_weather.py,sha256=ULg4ogZRxus01p2rdxiSFL75AisqtcvnLDOc7uJMBH0,767
|
31
|
-
tests/test_station.py,sha256=Ug5honzk2WuVjOJsfatan-3UDDrAxbsM72nFm3rmCdQ,1008
|
32
|
-
tests/test_stationsfile.py,sha256=slRH5N4Gznr6tkN2oMFWJbVCw3Xrma7Hvzn1lG5E-Qg,1401
|
33
|
-
tests/test_update.py,sha256=JMdlN_lc9Zb58yU4GNrO_sOaKN9pZEx8nt4E2UeKBi0,7254
|
34
|
-
tests/test_update_hourly.py,sha256=Zx0e_E2n2Wi1yGMDN6TURzIbk_xVYaMc-7IDK1sC5UY,1668
|
35
|
-
tests/test_uv_index.py,sha256=tr6wnOyHlXT1S3yp1oeHc4-Brmc-EMEdM4mtyrdpcHg,579
|
36
|
-
tests/test_weather.py,sha256=U4FkTtqLcLs8k-xy6YKNM_4HVscITymURCEIUShk6iE,802
|
37
|
-
simple_dwd_weatherforecast-2.0.31.dist-info/LICENCE,sha256=27UG7gteqvSWuZlsbIq2_OAbh7VyifGGl-1zpuUoBcw,1072
|
38
|
-
simple_dwd_weatherforecast-2.0.31.dist-info/METADATA,sha256=qVASoG2VqxxpKIKQ7-6m1yYBVL1nQYu7CqBO_7SgdO4,10800
|
39
|
-
simple_dwd_weatherforecast-2.0.31.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
40
|
-
simple_dwd_weatherforecast-2.0.31.dist-info/top_level.txt,sha256=iyEobUh14Tzitx39Oi8qm0NhBrnZovl_dNKtvLUkLEM,33
|
41
|
-
simple_dwd_weatherforecast-2.0.31.dist-info/RECORD,,
|
{simple_dwd_weatherforecast-2.0.31.dist-info → simple_dwd_weatherforecast-2.0.33.dist-info}/LICENCE
RENAMED
File without changes
|
File without changes
|