pyVisualCrossing 0.1.16__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.16
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.16"
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
@@ -213,7 +222,9 @@ def _fetch_data(api_result: dict) -> list[ForecastData]:
213
222
  # Loop Through Records and add Daily and Hourly Forecast Data
214
223
  for item in api_result["days"]:
215
224
  day_str = item["datetime"]
216
- 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
+ )
217
228
  condition = item.get("conditions", None)
218
229
  cloudcover = item.get("cloudcover", None)
219
230
  icon = item.get("icon", None)
@@ -254,7 +265,9 @@ def _fetch_data(api_result: dict) -> list[ForecastData]:
254
265
  for row in item["hours"]:
255
266
  now = datetime.datetime.now(timezone.utc)
256
267
  hour = row["datetime"]
257
- day_hour_obj = datetime.datetime.strptime(f"{day_str} {hour}", DATE_TIME_FORMAT).astimezone(timezone.utc)
268
+ day_hour_obj = datetime.datetime.strptime(
269
+ f"{day_str} {hour}", DATE_TIME_FORMAT
270
+ ).astimezone(timezone.utc)
258
271
  if day_hour_obj > now:
259
272
  condition = row.get("conditions", None)
260
273
  cloudcover = row.get("cloudcover", None)
@@ -297,14 +310,16 @@ def _fetch_data(api_result: dict) -> list[ForecastData]:
297
310
 
298
311
 
299
312
  # pylint: disable=R0914, R0912, W0212, R0915
300
- def _get_current_data(api_result: dict) -> list[ForecastData]:
313
+ def _get_current_data(api_result: dict[str, Any]) -> ForecastData:
301
314
  """Return WeatherFlowForecast list from API."""
302
315
 
303
316
  item = api_result["currentConditions"]
304
317
 
305
318
  day_str = datetime.datetime.today().strftime(DATE_FORMAT)
306
319
  hour = item["datetime"]
307
- day_hour_obj = datetime.datetime.strptime(f"{day_str} {hour}", DATE_TIME_FORMAT).astimezone(timezone.utc)
320
+ day_hour_obj = datetime.datetime.strptime(
321
+ f"{day_str} {hour}", DATE_TIME_FORMAT
322
+ ).astimezone(timezone.utc)
308
323
  condition = item.get("conditions", None)
309
324
  cloudcover = item.get("cloudcover", None)
310
325
  icon = item.get("icon", None)
@@ -321,8 +336,8 @@ def _get_current_data(api_result: dict) -> list[ForecastData]:
321
336
  wind_speed = item.get("windspeed", None)
322
337
  wind_gust_speed = item.get("windgust", None)
323
338
  wind_bearing = item.get("winddir", None)
324
- location = api_result.get("address", None)
325
- description = api_result.get("description", None)
339
+ location = api_result.get("address", "")
340
+ description = api_result.get("description", "")
326
341
 
327
342
  current_condition = ForecastData(
328
343
  day_hour_obj,
@@ -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.16
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.16",
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",