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.
- {pyVisualCrossing-0.1.15 → pyvisualcrossing-1.0.0}/PKG-INFO +11 -2
- {pyVisualCrossing-0.1.15 → pyvisualcrossing-1.0.0}/pyVisualCrossing/__init__.py +15 -1
- {pyVisualCrossing-0.1.15 → pyvisualcrossing-1.0.0}/pyVisualCrossing/api.py +34 -28
- {pyVisualCrossing-0.1.15 → pyvisualcrossing-1.0.0}/pyVisualCrossing/const.py +28 -1
- {pyVisualCrossing-0.1.15 → pyvisualcrossing-1.0.0}/pyVisualCrossing/data.py +7 -7
- {pyVisualCrossing-0.1.15 → pyvisualcrossing-1.0.0}/pyVisualCrossing.egg-info/PKG-INFO +11 -2
- {pyVisualCrossing-0.1.15 → pyvisualcrossing-1.0.0}/setup.py +1 -1
- {pyVisualCrossing-0.1.15 → pyvisualcrossing-1.0.0}/LICENSE +0 -0
- {pyVisualCrossing-0.1.15 → pyvisualcrossing-1.0.0}/README.md +0 -0
- {pyVisualCrossing-0.1.15 → pyvisualcrossing-1.0.0}/pyVisualCrossing.egg-info/SOURCES.txt +0 -0
- {pyVisualCrossing-0.1.15 → pyvisualcrossing-1.0.0}/pyVisualCrossing.egg-info/dependency_links.txt +0 -0
- {pyVisualCrossing-0.1.15 → pyvisualcrossing-1.0.0}/pyVisualCrossing.egg-info/top_level.txt +0 -0
- {pyVisualCrossing-0.1.15 → 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
|
|
@@ -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(
|
|
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(
|
|
263
|
-
|
|
264
|
-
|
|
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) ->
|
|
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
|
-
|
|
315
|
-
|
|
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",
|
|
334
|
-
description = api_result.get("description",
|
|
339
|
+
location = api_result.get("address", "")
|
|
340
|
+
description = api_result.get("description", "")
|
|
335
341
|
|
|
336
342
|
current_condition = ForecastData(
|
|
337
|
-
|
|
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 = [
|
|
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.15 → pyvisualcrossing-1.0.0}/pyVisualCrossing.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|