simple-dwd-weatherforecast 2.0.28__py3-none-any.whl → 2.0.31__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.
@@ -400,8 +400,8 @@ class Weather:
400
400
  else:
401
401
  print("no report for this station available. Have you updated first?")
402
402
 
403
- def get_uv_index(self, days_from_today: int) -> int:
404
- if not self.uv_reports:
403
+ def get_uv_index(self, days_from_today: int, shouldUpdate=True) -> int:
404
+ if not self.uv_reports and shouldUpdate:
405
405
  self.update(
406
406
  force_hourly=False,
407
407
  with_forecast=False,
@@ -418,7 +418,11 @@ class Weather:
418
418
  day = "tomorrow"
419
419
  elif days_from_today == 2:
420
420
  day = "dayafter_to"
421
- return self.uv_reports[self.nearest_uv_index_station]["forecast"][day]
421
+ return (
422
+ self.uv_reports[self.nearest_uv_index_station]["forecast"][day]
423
+ if self.uv_reports
424
+ else None
425
+ )
422
426
 
423
427
  def get_timeframe_max(
424
428
  self,
@@ -766,79 +770,92 @@ class Weather:
766
770
  self.report_data = {
767
771
  "time": row["Parameter description"],
768
772
  "date": row["surface observations"],
769
- WeatherDataType.CONDITION.value[0]: int(
770
- row[WeatherDataType.CONDITION.value[1]]
771
- )
772
- if row[WeatherDataType.CONDITION.value[1]] != self.NOT_AVAILABLE
773
- else None,
774
- WeatherDataType.TEMPERATURE.value[0]: round(
775
- float(
776
- row[WeatherDataType.TEMPERATURE.value[1]]
777
- .replace(self.NOT_AVAILABLE, "0.0")
778
- .replace(",", ".")
773
+ WeatherDataType.CONDITION.value[0]: (
774
+ int(row[WeatherDataType.CONDITION.value[1]])
775
+ if row[WeatherDataType.CONDITION.value[1]] != self.NOT_AVAILABLE
776
+ else None
777
+ ),
778
+ WeatherDataType.TEMPERATURE.value[0]: (
779
+ round(
780
+ float(
781
+ row[WeatherDataType.TEMPERATURE.value[1]]
782
+ .replace(self.NOT_AVAILABLE, "0.0")
783
+ .replace(",", ".")
784
+ )
785
+ + 273.1,
786
+ 1,
779
787
  )
780
- + 273.1,
781
- 1,
782
- )
783
- if row[WeatherDataType.TEMPERATURE.value[1]] != self.NOT_AVAILABLE
784
- else None,
785
- WeatherDataType.DEWPOINT.value[0]: round(
786
- float(row[WeatherDataType.DEWPOINT.value[1]].replace(",", ".")) + 273.1,
787
- 1,
788
- )
789
- if row[WeatherDataType.DEWPOINT.value[1]] != self.NOT_AVAILABLE
790
- else None,
791
- WeatherDataType.PRESSURE.value[0]: float(
792
- row[WeatherDataType.PRESSURE.value[1]].replace(",", ".")
793
- )
794
- * 100
795
- if row[WeatherDataType.PRESSURE.value[1]] != self.NOT_AVAILABLE
796
- else None,
797
- WeatherDataType.WIND_SPEED.value[0]: round(
798
- float(row[WeatherDataType.WIND_SPEED.value[1]].replace(",", ".")) / 3.6,
799
- 1,
800
- )
801
- if row[WeatherDataType.WIND_SPEED.value[1]] != self.NOT_AVAILABLE
802
- else None,
803
- WeatherDataType.WIND_DIRECTION.value[0]: int(
804
- row[WeatherDataType.WIND_DIRECTION.value[1]]
805
- )
806
- if row[WeatherDataType.WIND_DIRECTION.value[1]] != self.NOT_AVAILABLE
807
- else None,
808
- WeatherDataType.WIND_GUSTS.value[0]: round(
809
- float(row[WeatherDataType.WIND_GUSTS.value[1]].replace(",", ".")) / 3.6,
810
- 1,
811
- )
812
- if row[WeatherDataType.WIND_GUSTS.value[1]] != self.NOT_AVAILABLE
813
- else None,
814
- WeatherDataType.PRECIPITATION.value[0]: float(
815
- row[WeatherDataType.PRECIPITATION.value[1]].replace(",", ".")
816
- )
817
- if row[WeatherDataType.PRECIPITATION.value[1]] != self.NOT_AVAILABLE
818
- else None,
819
- WeatherDataType.CLOUD_COVERAGE.value[0]: float(
820
- row[WeatherDataType.CLOUD_COVERAGE.value[1]].replace(",", ".")
821
- )
822
- if row[WeatherDataType.CLOUD_COVERAGE.value[1]] != self.NOT_AVAILABLE
823
- else None,
824
- WeatherDataType.VISIBILITY.value[0]: float(
825
- row[WeatherDataType.VISIBILITY.value[1]].replace(",", ".")
826
- )
827
- * 1e3
828
- if row[WeatherDataType.VISIBILITY.value[1]] != self.NOT_AVAILABLE
829
- else None,
830
- WeatherDataType.SUN_IRRADIANCE.value[0]: round(
831
- float(row[WeatherDataType.SUN_IRRADIANCE.value[1]].replace(",", "."))
832
- * 3.6,
833
- 1,
834
- )
835
- if row[WeatherDataType.SUN_IRRADIANCE.value[1]] != self.NOT_AVAILABLE
836
- else None,
837
- WeatherDataType.HUMIDITY.value[0]: float(
838
- row[WeatherDataType.HUMIDITY.value[1]].replace(",", ".")
839
- )
840
- if row[WeatherDataType.HUMIDITY.value[1]] != self.NOT_AVAILABLE
841
- else None,
788
+ if row[WeatherDataType.TEMPERATURE.value[1]] != self.NOT_AVAILABLE
789
+ else None
790
+ ),
791
+ WeatherDataType.DEWPOINT.value[0]: (
792
+ round(
793
+ float(row[WeatherDataType.DEWPOINT.value[1]].replace(",", "."))
794
+ + 273.1,
795
+ 1,
796
+ )
797
+ if row[WeatherDataType.DEWPOINT.value[1]] != self.NOT_AVAILABLE
798
+ else None
799
+ ),
800
+ WeatherDataType.PRESSURE.value[0]: (
801
+ float(row[WeatherDataType.PRESSURE.value[1]].replace(",", ".")) * 100
802
+ if row[WeatherDataType.PRESSURE.value[1]] != self.NOT_AVAILABLE
803
+ else None
804
+ ),
805
+ WeatherDataType.WIND_SPEED.value[0]: (
806
+ round(
807
+ float(row[WeatherDataType.WIND_SPEED.value[1]].replace(",", "."))
808
+ / 3.6,
809
+ 1,
810
+ )
811
+ if row[WeatherDataType.WIND_SPEED.value[1]] != self.NOT_AVAILABLE
812
+ else None
813
+ ),
814
+ WeatherDataType.WIND_DIRECTION.value[0]: (
815
+ int(row[WeatherDataType.WIND_DIRECTION.value[1]])
816
+ if row[WeatherDataType.WIND_DIRECTION.value[1]] != self.NOT_AVAILABLE
817
+ else None
818
+ ),
819
+ WeatherDataType.WIND_GUSTS.value[0]: (
820
+ round(
821
+ float(row[WeatherDataType.WIND_GUSTS.value[1]].replace(",", "."))
822
+ / 3.6,
823
+ 1,
824
+ )
825
+ if row[WeatherDataType.WIND_GUSTS.value[1]] != self.NOT_AVAILABLE
826
+ else None
827
+ ),
828
+ WeatherDataType.PRECIPITATION.value[0]: (
829
+ float(row[WeatherDataType.PRECIPITATION.value[1]].replace(",", "."))
830
+ if row[WeatherDataType.PRECIPITATION.value[1]] != self.NOT_AVAILABLE
831
+ else None
832
+ ),
833
+ WeatherDataType.CLOUD_COVERAGE.value[0]: (
834
+ float(row[WeatherDataType.CLOUD_COVERAGE.value[1]].replace(",", "."))
835
+ if row[WeatherDataType.CLOUD_COVERAGE.value[1]] != self.NOT_AVAILABLE
836
+ else None
837
+ ),
838
+ WeatherDataType.VISIBILITY.value[0]: (
839
+ float(row[WeatherDataType.VISIBILITY.value[1]].replace(",", ".")) * 1e3
840
+ if row[WeatherDataType.VISIBILITY.value[1]] != self.NOT_AVAILABLE
841
+ else None
842
+ ),
843
+ WeatherDataType.SUN_IRRADIANCE.value[0]: (
844
+ round(
845
+ float(
846
+ row[WeatherDataType.SUN_IRRADIANCE.value[1]].replace(",", ".")
847
+ )
848
+ * 3.6,
849
+ 1,
850
+ )
851
+ if row[WeatherDataType.SUN_IRRADIANCE.value[1]] != self.NOT_AVAILABLE
852
+ else None
853
+ ),
854
+ WeatherDataType.HUMIDITY.value[0]: (
855
+ float(row[WeatherDataType.HUMIDITY.value[1]].replace(",", "."))
856
+ if row[WeatherDataType.HUMIDITY.value[1]] != self.NOT_AVAILABLE
857
+ else None
858
+ ),
842
859
  }
843
860
 
844
861
  def get_weather_report(self, shouldUpdate=False):
@@ -852,21 +869,24 @@ class Weather:
852
869
  "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"
853
870
  }
854
871
  headers["If-None-Match"] = self.etags[url] if url in self.etags else ""
855
- request = requests.get(url, headers=headers)
856
- # If resource has not been modified, return
857
- if request.status_code == 304:
858
- return
859
- elif request.status_code != 200:
860
- raise Exception(f"Unexpected status code {request.status_code}")
861
- self.etags[url] = request.headers["ETag"]
862
- uv_reports = json.loads(request.text)["content"]
863
- # Match with existing stations
864
- for uv_report in uv_reports:
865
- station = get_station_by_name(
866
- self.uv_index_stations_reference_names[uv_report["city"]]
867
- )
868
- # uv_report.update({"lat": station[1]["lat"], "lon": station[1]["lon"]})
869
- self.uv_reports[station[0]] = uv_report
872
+ try:
873
+ request = requests.get(url, headers=headers, timeout=30)
874
+ # If resource has not been modified, return
875
+ if request.status_code == 304:
876
+ return
877
+ elif request.status_code != 200:
878
+ raise Exception(f"Unexpected status code {request.status_code}")
879
+ self.etags[url] = request.headers["ETag"]
880
+ uv_reports = json.loads(request.text)["content"]
881
+ # Match with existing stations
882
+ for uv_report in uv_reports:
883
+ station = get_station_by_name(
884
+ self.uv_index_stations_reference_names[uv_report["city"]]
885
+ )
886
+ # uv_report.update({"lat": station[1]["lat"], "lon": station[1]["lon"]})
887
+ self.uv_reports[station[0]] = uv_report
888
+ except Exception as error:
889
+ print(f"Error in download_weather_report: {type(error)} args: {error.args}")
870
890
 
871
891
  def download_weather_report(self, region_code):
872
892
  url = f"https://www.dwd.de/DWD/wetter/wv_allg/deutschland/text/vhdl13_{region_code}.html"
@@ -874,16 +894,19 @@ class Weather:
874
894
  "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"
875
895
  }
876
896
  headers["If-None-Match"] = self.etags[url] if url in self.etags else ""
877
- request = requests.get(url, headers=headers)
878
- # If resource has not been modified, return
879
- if request.status_code == 304:
880
- return
881
- self.etags[url] = request.headers["ETag"]
882
- weather_report = request.text
883
- a = weather_report.find(">")
884
- if a != -1:
885
- weather_report = weather_report[a + 1 :]
886
- self.weather_report = weather_report
897
+ try:
898
+ request = requests.get(url, headers=headers, timeout=30)
899
+ # If resource has not been modified, return
900
+ if request.status_code == 304:
901
+ return
902
+ self.etags[url] = request.headers["ETag"]
903
+ weather_report = request.text
904
+ a = weather_report.find(">")
905
+ if a != -1:
906
+ weather_report = weather_report[a + 1 :]
907
+ self.weather_report = weather_report
908
+ except Exception as error:
909
+ print(f"Error in download_weather_report: {type(error)} args: {error.args}")
887
910
 
888
911
  def download_latest_kml(self, stationid, force_hourly=False):
889
912
  if force_hourly:
@@ -891,15 +914,18 @@ class Weather:
891
914
  else:
892
915
  url = f"https://opendata.dwd.de/weather/local_forecasts/mos/MOSMIX_L/single_stations/{stationid}/kml/MOSMIX_L_LATEST_{stationid}.kmz"
893
916
  headers = {"If-None-Match": self.etags[url] if url in self.etags else ""}
894
- request = requests.get(url, headers=headers)
895
- # If resource has not been modified, return
896
- if request.status_code == 304:
897
- return
898
- self.etags[url] = request.headers["ETag"]
899
- with ZipFile(BytesIO(request.content), "r") as kmz:
900
- # large RAM allocation
901
- with kmz.open(kmz.namelist()[0], "r") as kml:
902
- self.parse_kml(kml, force_hourly)
917
+ try:
918
+ request = requests.get(url, headers=headers, timeout=30)
919
+ # If resource has not been modified, return
920
+ if request.status_code == 304:
921
+ return
922
+ self.etags[url] = request.headers["ETag"]
923
+ with ZipFile(BytesIO(request.content), "r") as kmz:
924
+ # large RAM allocation
925
+ with kmz.open(kmz.namelist()[0], "r") as kml:
926
+ self.parse_kml(kml, force_hourly)
927
+ except Exception as error:
928
+ print(f"Error in download_weather_report: {type(error)} args: {error.args}")
903
929
 
904
930
  def download_latest_report(self):
905
931
  station_id = self.station_id
@@ -909,38 +935,43 @@ class Weather:
909
935
  f"https://opendata.dwd.de/weather/weather_reports/poi/{station_id}-BEOB.csv"
910
936
  )
911
937
  headers = {"If-None-Match": self.etags[url] if url in self.etags else ""}
912
- response = requests.get(url, headers=headers)
913
- if response.status_code == 200:
914
- content = response.content
915
- reader = csv.DictReader(content.decode("utf-8").splitlines(), delimiter=";")
916
- is_first_run = True
917
- backuprows = []
918
- for row in reader:
919
- if is_first_run:
920
- is_first_run = False
921
- reader.__next__()
922
- continue
923
- backuprows.append(reader.__next__())
924
- backuprows.append(reader.__next__())
925
- backuprows.append(reader.__next__())
926
-
927
- # Some items are only reported each hour
928
- for backuprow in backuprows:
929
- if row["cloud_cover_total"] == self.NOT_AVAILABLE:
930
- row["cloud_cover_total"] = backuprow["cloud_cover_total"]
931
- if row["horizontal_visibility"] == self.NOT_AVAILABLE:
932
- row["horizontal_visibility"] = backuprow[
933
- "horizontal_visibility"
934
- ]
935
- if row["present_weather"] == self.NOT_AVAILABLE:
936
- row["present_weather"] = backuprow["present_weather"]
937
- self.parse_csv_row(row)
938
- # We only want the latest report
939
- break
940
-
941
- elif response.status_code == 304:
942
- # The report is already up to date
943
- print("Report is already up to date")
944
- else:
945
- # Handle other status codes
946
- print(f"Failed to download report. Status code: {response.status_code}")
938
+ try:
939
+ response = requests.get(url, headers=headers, timeout=30)
940
+ if response.status_code == 200:
941
+ content = response.content
942
+ reader = csv.DictReader(
943
+ content.decode("utf-8").splitlines(), delimiter=";"
944
+ )
945
+ is_first_run = True
946
+ backuprows = []
947
+ for row in reader:
948
+ if is_first_run:
949
+ is_first_run = False
950
+ reader.__next__()
951
+ continue
952
+ backuprows.append(reader.__next__())
953
+ backuprows.append(reader.__next__())
954
+ backuprows.append(reader.__next__())
955
+
956
+ # Some items are only reported each hour
957
+ for backuprow in backuprows:
958
+ if row["cloud_cover_total"] == self.NOT_AVAILABLE:
959
+ row["cloud_cover_total"] = backuprow["cloud_cover_total"]
960
+ if row["horizontal_visibility"] == self.NOT_AVAILABLE:
961
+ row["horizontal_visibility"] = backuprow[
962
+ "horizontal_visibility"
963
+ ]
964
+ if row["present_weather"] == self.NOT_AVAILABLE:
965
+ row["present_weather"] = backuprow["present_weather"]
966
+ self.parse_csv_row(row)
967
+ # We only want the latest report
968
+ break
969
+
970
+ elif response.status_code == 304:
971
+ # The report is already up to date
972
+ print("Report is already up to date")
973
+ else:
974
+ # Handle other status codes
975
+ print(f"Failed to download report. Status code: {response.status_code}")
976
+ except Exception as error:
977
+ print(f"Error in download_weather_report: {type(error)} args: {error.args}")
@@ -40,7 +40,7 @@ def get_map(minx,miny,maxx,maxy, map_type: WeatherMapType, background_type: Weat
40
40
  if image_width > 1200 or image_height > 1400:
41
41
  raise ValueError("Width and height must not exceed 1200 and 1400 respectively. Please be kind to the DWD servers.")
42
42
 
43
- 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"
43
+ url = f"https://maps.dwd.de/geoserver/dwd/wms?service=WMS&version=1.1.0&request=GetMap&layers={background_type.value},{map_type.value}&bbox={minx},{miny},{maxx},{maxy}&width={image_width}&height={image_height}&srs=EPSG:4326&styles=&format=image/png"
44
44
  request = requests.get(url, stream=True)
45
45
  if request.status_code == 200:
46
46
  image = Image.open(BytesIO(request.content))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: simple_dwd_weatherforecast
3
- Version: 2.0.28
3
+ Version: 2.0.31
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
@@ -1,6 +1,6 @@
1
1
  simple_dwd_weatherforecast/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- simple_dwd_weatherforecast/dwdforecast.py,sha256=2yhMV4qXs8MllvL6vDXHGV20TX5811IrkjL7FnpR6Wg,33831
3
- simple_dwd_weatherforecast/dwdmap.py,sha256=e2bpfFmhk1SSa43AWlxTtiZhXzIdQ4EhzPaGo3O9_44,2476
2
+ simple_dwd_weatherforecast/dwdforecast.py,sha256=ZTKS32lRedjzNucZHZ0w9bBuzJd3--EibU9wG6Au_M4,35282
3
+ simple_dwd_weatherforecast/dwdmap.py,sha256=bFAZfYjUryZkErJ3aDNRm9ZhNwKGMcy4pEl8TnWJKzA,2476
4
4
  simple_dwd_weatherforecast/stations.json,sha256=1u8qc2CT_rVy49SAlOicGixzHln6Y0FXevuFAz2maBw,838948
5
5
  simple_dwd_weatherforecast/uv_stations.json,sha256=ADenYo-aR6qbf0UFkfYr72kkFzL9HyUKe4VQ23POGF8,2292
6
6
  tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -34,8 +34,8 @@ tests/test_update.py,sha256=JMdlN_lc9Zb58yU4GNrO_sOaKN9pZEx8nt4E2UeKBi0,7254
34
34
  tests/test_update_hourly.py,sha256=Zx0e_E2n2Wi1yGMDN6TURzIbk_xVYaMc-7IDK1sC5UY,1668
35
35
  tests/test_uv_index.py,sha256=tr6wnOyHlXT1S3yp1oeHc4-Brmc-EMEdM4mtyrdpcHg,579
36
36
  tests/test_weather.py,sha256=U4FkTtqLcLs8k-xy6YKNM_4HVscITymURCEIUShk6iE,802
37
- simple_dwd_weatherforecast-2.0.28.dist-info/LICENCE,sha256=27UG7gteqvSWuZlsbIq2_OAbh7VyifGGl-1zpuUoBcw,1072
38
- simple_dwd_weatherforecast-2.0.28.dist-info/METADATA,sha256=meCDZ3EMr6ZHP-N5mzcvGV5VARhpisS1LvW2G4QE3zY,10800
39
- simple_dwd_weatherforecast-2.0.28.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
40
- simple_dwd_weatherforecast-2.0.28.dist-info/top_level.txt,sha256=iyEobUh14Tzitx39Oi8qm0NhBrnZovl_dNKtvLUkLEM,33
41
- simple_dwd_weatherforecast-2.0.28.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.42.0)
2
+ Generator: bdist_wheel (0.43.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5