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.
- {pyVisualCrossing-0.1.16 → pyvisualcrossing-1.0.0}/PKG-INFO +11 -2
- {pyVisualCrossing-0.1.16 → pyvisualcrossing-1.0.0}/pyVisualCrossing/__init__.py +15 -1
- {pyVisualCrossing-0.1.16 → pyvisualcrossing-1.0.0}/pyVisualCrossing/api.py +31 -16
- {pyVisualCrossing-0.1.16 → pyvisualcrossing-1.0.0}/pyVisualCrossing/const.py +28 -1
- {pyVisualCrossing-0.1.16 → pyvisualcrossing-1.0.0}/pyVisualCrossing/data.py +7 -7
- {pyVisualCrossing-0.1.16 → pyvisualcrossing-1.0.0}/pyVisualCrossing.egg-info/PKG-INFO +11 -2
- {pyVisualCrossing-0.1.16 → pyvisualcrossing-1.0.0}/setup.py +1 -1
- {pyVisualCrossing-0.1.16 → pyvisualcrossing-1.0.0}/LICENSE +0 -0
- {pyVisualCrossing-0.1.16 → pyvisualcrossing-1.0.0}/README.md +0 -0
- {pyVisualCrossing-0.1.16 → pyvisualcrossing-1.0.0}/pyVisualCrossing.egg-info/SOURCES.txt +0 -0
- {pyVisualCrossing-0.1.16 → pyvisualcrossing-1.0.0}/pyVisualCrossing.egg-info/dependency_links.txt +0 -0
- {pyVisualCrossing-0.1.16 → pyvisualcrossing-1.0.0}/pyVisualCrossing.egg-info/top_level.txt +0 -0
- {pyVisualCrossing-0.1.16 → pyvisualcrossing-1.0.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: pyVisualCrossing
|
|
3
|
-
Version: 0.
|
|
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.
|
|
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
|
|
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 =
|
|
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) ->
|
|
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) ->
|
|
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) ->
|
|
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(
|
|
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(
|
|
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) ->
|
|
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(
|
|
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",
|
|
325
|
-
description = api_result.get("description",
|
|
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 = [
|
|
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) ->
|
|
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
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: pyVisualCrossing
|
|
3
|
-
Version: 0.
|
|
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
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyVisualCrossing-0.1.16 → pyvisualcrossing-1.0.0}/pyVisualCrossing.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|