pyVisualCrossing 0.1.15__tar.gz → 1.0.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: pyVisualCrossing
3
- Version: 0.1.15
3
+ Version: 1.0.0
4
4
  Summary: Gets the weather data from Visual Crossing
5
5
  Home-page: https://github.com/briis/pyVisualCrossing
6
6
  Author: briis
@@ -12,6 +12,15 @@ Classifier: Operating System :: OS Independent
12
12
  Classifier: Development Status :: 4 - Beta
13
13
  Description-Content-Type: text/markdown
14
14
  License-File: LICENSE
15
+ Dynamic: author
16
+ Dynamic: author-email
17
+ Dynamic: classifier
18
+ Dynamic: description
19
+ Dynamic: description-content-type
20
+ Dynamic: home-page
21
+ Dynamic: license
22
+ Dynamic: license-file
23
+ Dynamic: summary
15
24
 
16
25
  # Python Wrapper for Visual Crossing Weather API
17
26
 
@@ -1,4 +1,5 @@
1
1
  """Python Wrapper for Visual Crossing Weather API."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from pyVisualCrossing.api import (
@@ -17,6 +18,19 @@ from pyVisualCrossing.data import (
17
18
  from pyVisualCrossing.const import SUPPORTED_LANGUAGES
18
19
 
19
20
  __title__ = "pyVisualCrossing"
20
- __version__ = "0.1.15"
21
+ __version__ = "1.0.0"
21
22
  __author__ = "briis"
22
23
  __license__ = "MIT"
24
+
25
+ __all__ = [
26
+ "VisualCrossing",
27
+ "VisualCrossingBadRequest",
28
+ "VisualCrossingException",
29
+ "VisualCrossingInternalServerError",
30
+ "VisualCrossingUnauthorized",
31
+ "VisualCrossingTooManyRequests",
32
+ "ForecastData",
33
+ "ForecastDailyData",
34
+ "ForecastHourlyData",
35
+ "SUPPORTED_LANGUAGES",
36
+ ]
@@ -2,6 +2,7 @@
2
2
 
3
3
  See: https://www.visualcrossing.com/.
4
4
  """
5
+
5
6
  from __future__ import annotations
6
7
 
7
8
  import abc
@@ -11,11 +12,17 @@ import json
11
12
  import logging
12
13
 
13
14
  from typing import Any
15
+ import urllib.error
14
16
  import urllib.request
15
17
 
16
18
  import aiohttp
17
19
 
18
- from .const import DATE_FORMAT, DATE_TIME_FORMAT, SUPPORTED_LANGUAGES, VISUALCROSSING_BASE_URL
20
+ from .const import (
21
+ DATE_FORMAT,
22
+ DATE_TIME_FORMAT,
23
+ SUPPORTED_LANGUAGES,
24
+ VISUALCROSSING_BASE_URL,
25
+ )
19
26
  from .data import ForecastData, ForecastDailyData, ForecastHourlyData
20
27
 
21
28
  UTC = datetime.timezone.utc
@@ -46,16 +53,18 @@ class VisualCrossingInternalServerError(Exception):
46
53
  class VisualCrossingAPIBase:
47
54
  """Baseclass to use as dependency injection pattern for easier automatic testing."""
48
55
 
56
+ session: aiohttp.ClientSession | None = None
57
+
49
58
  @abc.abstractmethod
50
59
  def fetch_data(
51
60
  self, api_key: str, latitude: float, longitude: float, days: int, language: str
52
- ) -> dict[str, Any]:
61
+ ) -> dict[str, Any] | None:
53
62
  """Override this."""
54
63
  raise NotImplementedError("users must define fetch_data to use this base class")
55
64
 
56
65
  @abc.abstractmethod
57
66
  async def async_fetch_data(
58
- api_key: str, latitude: float, longitude: float, days: int, language: str
67
+ self, api_key: str, latitude: float, longitude: float, days: int, language: str
59
68
  ) -> dict[str, Any]:
60
69
  """Override this."""
61
70
  raise NotImplementedError("users must define fetch_data to use this base class")
@@ -70,7 +79,7 @@ class VisualCrossingAPI(VisualCrossingAPIBase):
70
79
 
71
80
  def fetch_data(
72
81
  self, api_key: str, latitude: float, longitude: float, days: int, language: str
73
- ) -> dict[str, Any]:
82
+ ) -> dict[str, Any] | None:
74
83
  """Get data from API."""
75
84
  api_url = f"{VISUALCROSSING_BASE_URL}{latitude},{longitude}/today/next{days}days?unitGroup=metric&key={api_key}&contentType=json&iconSet=icons2&lang={language}"
76
85
  _LOGGER.debug("URL: %s", api_url)
@@ -149,8 +158,8 @@ class VisualCrossing:
149
158
  longitude: float,
150
159
  days: int = 14,
151
160
  language: str = "en",
152
- session: aiohttp.ClientSession = None,
153
- api: VisualCrossingAPIBase = VisualCrossingAPI(),
161
+ session: aiohttp.ClientSession | None = None,
162
+ api: VisualCrossingAPIBase | None = None,
154
163
  ) -> None:
155
164
  """Return data from Weather API."""
156
165
  self._api_key = api_key
@@ -158,7 +167,7 @@ class VisualCrossing:
158
167
  self._longitude = longitude
159
168
  self._days = days
160
169
  self._language = language
161
- self._api = api
170
+ self._api = api if api is not None else VisualCrossingAPI()
162
171
  self._json_data = None
163
172
 
164
173
  if days > 14:
@@ -170,7 +179,7 @@ class VisualCrossing:
170
179
  if language not in SUPPORTED_LANGUAGES:
171
180
  self._language = "en"
172
181
 
173
- def fetch_data(self) -> list[ForecastData]:
182
+ def fetch_data(self) -> ForecastData | None:
174
183
  """Return list of weather data."""
175
184
 
176
185
  self._json_data = self._api.fetch_data(
@@ -183,7 +192,7 @@ class VisualCrossing:
183
192
 
184
193
  return _fetch_data(self._json_data)
185
194
 
186
- async def async_fetch_data(self) -> list[ForecastData]:
195
+ async def async_fetch_data(self) -> ForecastData | None:
187
196
  """Return list of weather data."""
188
197
 
189
198
  self._json_data = await self._api.async_fetch_data(
@@ -197,7 +206,7 @@ class VisualCrossing:
197
206
  return _fetch_data(self._json_data)
198
207
 
199
208
 
200
- def _fetch_data(api_result: dict) -> list[ForecastData]:
209
+ def _fetch_data(api_result: dict[str, Any] | None) -> ForecastData | None:
201
210
  """Return result from API to ForecastData List."""
202
211
 
203
212
  # Return nothing af the Request for data fails
@@ -212,11 +221,10 @@ def _fetch_data(api_result: dict) -> list[ForecastData]:
212
221
 
213
222
  # Loop Through Records and add Daily and Hourly Forecast Data
214
223
  for item in api_result["days"]:
215
- # valid_time = datetime.datetime.utcfromtimestamp(item["datetimeEpoch"]).replace(
216
- # tzinfo=UTC
217
- # )
218
224
  day_str = item["datetime"]
219
- day_obj = datetime.datetime.strptime(day_str, DATE_FORMAT).astimezone(timezone.utc)
225
+ day_obj = datetime.datetime.strptime(day_str, DATE_FORMAT).astimezone(
226
+ timezone.utc
227
+ )
220
228
  condition = item.get("conditions", None)
221
229
  cloudcover = item.get("cloudcover", None)
222
230
  icon = item.get("icon", None)
@@ -234,7 +242,6 @@ def _fetch_data(api_result: dict) -> list[ForecastData]:
234
242
  wind_bearing = item.get("winddir", None)
235
243
 
236
244
  day_data = ForecastDailyData(
237
- # valid_time,
238
245
  day_obj,
239
246
  temperature,
240
247
  temp_low,
@@ -256,13 +263,11 @@ def _fetch_data(api_result: dict) -> list[ForecastData]:
256
263
 
257
264
  # Add Hourly data for this day
258
265
  for row in item["hours"]:
259
- # now = datetime.datetime.now().replace(tzinfo=UTC)
260
266
  now = datetime.datetime.now(timezone.utc)
261
267
  hour = row["datetime"]
262
- day_hour_obj = datetime.datetime.strptime(f"{day_str} {hour}", DATE_TIME_FORMAT).astimezone(timezone.utc)
263
- # valid_time = datetime.datetime.utcfromtimestamp(
264
- # row["datetimeEpoch"]
265
- # ).replace(tzinfo=UTC)
268
+ day_hour_obj = datetime.datetime.strptime(
269
+ f"{day_str} {hour}", DATE_TIME_FORMAT
270
+ ).astimezone(timezone.utc)
266
271
  if day_hour_obj > now:
267
272
  condition = row.get("conditions", None)
268
273
  cloudcover = row.get("cloudcover", None)
@@ -280,7 +285,6 @@ def _fetch_data(api_result: dict) -> list[ForecastData]:
280
285
  wind_bearing = row.get("winddir", None)
281
286
 
282
287
  hour_data = ForecastHourlyData(
283
- # valid_time,
284
288
  day_hour_obj,
285
289
  temperature,
286
290
  apparent_temperature,
@@ -306,14 +310,16 @@ def _fetch_data(api_result: dict) -> list[ForecastData]:
306
310
 
307
311
 
308
312
  # pylint: disable=R0914, R0912, W0212, R0915
309
- def _get_current_data(api_result: dict) -> list[ForecastData]:
313
+ def _get_current_data(api_result: dict[str, Any]) -> ForecastData:
310
314
  """Return WeatherFlowForecast list from API."""
311
315
 
312
316
  item = api_result["currentConditions"]
313
317
 
314
- valid_time = datetime.datetime.utcfromtimestamp(item["datetimeEpoch"]).replace(
315
- tzinfo=UTC
316
- )
318
+ day_str = datetime.datetime.today().strftime(DATE_FORMAT)
319
+ hour = item["datetime"]
320
+ day_hour_obj = datetime.datetime.strptime(
321
+ f"{day_str} {hour}", DATE_TIME_FORMAT
322
+ ).astimezone(timezone.utc)
317
323
  condition = item.get("conditions", None)
318
324
  cloudcover = item.get("cloudcover", None)
319
325
  icon = item.get("icon", None)
@@ -330,11 +336,11 @@ def _get_current_data(api_result: dict) -> list[ForecastData]:
330
336
  wind_speed = item.get("windspeed", None)
331
337
  wind_gust_speed = item.get("windgust", None)
332
338
  wind_bearing = item.get("winddir", None)
333
- location = api_result.get("address", None)
334
- description = api_result.get("description", None)
339
+ location = api_result.get("address", "")
340
+ description = api_result.get("description", "")
335
341
 
336
342
  current_condition = ForecastData(
337
- valid_time,
343
+ day_hour_obj,
338
344
  apparent_temperature,
339
345
  condition,
340
346
  cloudcover,
@@ -1,8 +1,35 @@
1
1
  """System Wide constants for Visual Crossing Weather Wrapper."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  DATE_FORMAT = "%Y-%m-%d"
5
6
  DATE_TIME_FORMAT = "%Y-%m-%d %H:%M:%S"
6
7
 
7
- SUPPORTED_LANGUAGES = ["ar", "bg", "cs", "da", "de", "el", "en", "es", "fa", "fi", "fr", "he", "hu", "it", "ja", "ko", "nl", "pl", "pt", "sr", "sv", "tr", "uk", "vi", "zh"]
8
+ SUPPORTED_LANGUAGES = [
9
+ "ar",
10
+ "bg",
11
+ "cs",
12
+ "da",
13
+ "de",
14
+ "el",
15
+ "en",
16
+ "es",
17
+ "fa",
18
+ "fi",
19
+ "fr",
20
+ "he",
21
+ "hu",
22
+ "it",
23
+ "ja",
24
+ "ko",
25
+ "nl",
26
+ "pl",
27
+ "pt",
28
+ "sr",
29
+ "sv",
30
+ "tr",
31
+ "uk",
32
+ "vi",
33
+ "zh",
34
+ ]
8
35
  VISUALCROSSING_BASE_URL = "https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/"
@@ -29,8 +29,8 @@ class ForecastData:
29
29
  wind_speed: float,
30
30
  location_name: str,
31
31
  description: str,
32
- forecast_daily: ForecastDailyData = None,
33
- forecast_hourly: ForecastHourlyData = None,
32
+ forecast_daily: list[ForecastDailyData] | None = None,
33
+ forecast_hourly: list[ForecastHourlyData] | None = None,
34
34
  ) -> None:
35
35
  """Dataset constructor."""
36
36
  self._datetime = datetime
@@ -151,27 +151,27 @@ class ForecastData:
151
151
  return self._description
152
152
 
153
153
  @property
154
- def update_time(self) -> datetime:
154
+ def update_time(self) -> str:
155
155
  """Last updated."""
156
156
  return datetime.now().isoformat()
157
157
 
158
158
  @property
159
- def forecast_daily(self) -> ForecastDailyData:
159
+ def forecast_daily(self) -> list[ForecastDailyData] | None:
160
160
  """Forecast List."""
161
161
  return self._forecast_daily
162
162
 
163
163
  @forecast_daily.setter
164
- def forecast_daily(self, new_forecast):
164
+ def forecast_daily(self, new_forecast: list[ForecastDailyData]) -> None:
165
165
  """Forecast daily new value."""
166
166
  self._forecast_daily = new_forecast
167
167
 
168
168
  @property
169
- def forecast_hourly(self) -> ForecastHourlyData:
169
+ def forecast_hourly(self) -> list[ForecastHourlyData] | None:
170
170
  """Forecast List."""
171
171
  return self._forecast_hourly
172
172
 
173
173
  @forecast_hourly.setter
174
- def forecast_hourly(self, new_forecast):
174
+ def forecast_hourly(self, new_forecast: list[ForecastHourlyData]) -> None:
175
175
  """Forecast hourly new value."""
176
176
  self._forecast_hourly = new_forecast
177
177
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: pyVisualCrossing
3
- Version: 0.1.15
3
+ Version: 1.0.0
4
4
  Summary: Gets the weather data from Visual Crossing
5
5
  Home-page: https://github.com/briis/pyVisualCrossing
6
6
  Author: briis
@@ -12,6 +12,15 @@ Classifier: Operating System :: OS Independent
12
12
  Classifier: Development Status :: 4 - Beta
13
13
  Description-Content-Type: text/markdown
14
14
  License-File: LICENSE
15
+ Dynamic: author
16
+ Dynamic: author-email
17
+ Dynamic: classifier
18
+ Dynamic: description
19
+ Dynamic: description-content-type
20
+ Dynamic: home-page
21
+ Dynamic: license
22
+ Dynamic: license-file
23
+ Dynamic: summary
15
24
 
16
25
  # Python Wrapper for Visual Crossing Weather API
17
26
 
@@ -7,7 +7,7 @@ with open("README.md") as fh:
7
7
 
8
8
  setuptools.setup(
9
9
  name="pyVisualCrossing",
10
- version="0.1.15",
10
+ version="1.0.0",
11
11
  author="briis",
12
12
  author_email="bjarne@briis.com",
13
13
  description="Gets the weather data from Visual Crossing",