pyVisualCrossing 0.1.0__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.
- pyVisualCrossing/__init__.py +10 -0
- pyVisualCrossing/api.py +275 -0
- pyVisualCrossing/const.py +5 -0
- pyVisualCrossing/data.py +392 -0
- pyVisualCrossing-0.1.0.dist-info/LICENSE +21 -0
- pyVisualCrossing-0.1.0.dist-info/METADATA +83 -0
- pyVisualCrossing-0.1.0.dist-info/RECORD +9 -0
- pyVisualCrossing-0.1.0.dist-info/WHEEL +5 -0
- pyVisualCrossing-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Python Wrapper for Visual Crossing Weather API."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from pyVisualCrossing.api import VisualCrossing
|
|
5
|
+
from pyVisualCrossing.data import ForecastData, ForecastDailyData, ForecastHourlyData
|
|
6
|
+
|
|
7
|
+
__title__ = "pyVisualCrossing"
|
|
8
|
+
__version__ = "0.1.0"
|
|
9
|
+
__author__ = "briis"
|
|
10
|
+
__license__ = "MIT"
|
pyVisualCrossing/api.py
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module contains the code to get weather data from
|
|
3
|
+
Visual Crossing API.
|
|
4
|
+
See: https://www.visualcrossing.com/
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import abc
|
|
9
|
+
import datetime
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
|
|
13
|
+
from typing import List, Any, Dict
|
|
14
|
+
from urllib.request import urlopen
|
|
15
|
+
|
|
16
|
+
import aiohttp
|
|
17
|
+
|
|
18
|
+
from .const import SUPPORTED_LANGUAGES, VISUALCROSSING_BASE_URL
|
|
19
|
+
from .data import ForecastData, ForecastDailyData, ForecastHourlyData
|
|
20
|
+
|
|
21
|
+
UTC = datetime.timezone.utc
|
|
22
|
+
|
|
23
|
+
_LOGGER = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
class VisualCrossingException(Exception):
|
|
26
|
+
"""Exception thrown if failing to access API."""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class VisualCrossingAPIBase:
|
|
30
|
+
"""
|
|
31
|
+
Baseclass to use as dependency injection pattern for easier
|
|
32
|
+
automatic testing
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
@abc.abstractmethod
|
|
36
|
+
def fetch_data(self, api_key: str, latitude: float, longitude: float, days: int, language: str) -> Dict[str, Any]:
|
|
37
|
+
"""Override this"""
|
|
38
|
+
raise NotImplementedError(
|
|
39
|
+
"users must define fetch_data to use this base class"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
@abc.abstractmethod
|
|
43
|
+
async def async_fetch_data(
|
|
44
|
+
api_key: str, latitude: float, longitude: float, days: int, language: str
|
|
45
|
+
) -> Dict[str, Any]:
|
|
46
|
+
"""Override this"""
|
|
47
|
+
raise NotImplementedError(
|
|
48
|
+
"users must define fetch_data to use this base class"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
class VisualCrossingAPI(VisualCrossingAPIBase):
|
|
52
|
+
"""Default implementation for WeatherFlow api"""
|
|
53
|
+
|
|
54
|
+
def __init__(self) -> None:
|
|
55
|
+
"""Init the API with or without session"""
|
|
56
|
+
self.session = None
|
|
57
|
+
|
|
58
|
+
def fetch_data(self, api_key: str, latitude: float, longitude: float, days: int, language: str) -> Dict[str, Any]:
|
|
59
|
+
"""Get data from API."""
|
|
60
|
+
api_url =f"{VISUALCROSSING_BASE_URL}{latitude},{longitude}/today/next{days}days?unitGroup=metric&key={api_key}&contentType=json&iconSet=icons2&lang={language}"
|
|
61
|
+
_LOGGER.debug("URL: %s", api_url)
|
|
62
|
+
|
|
63
|
+
response = urlopen(api_url)
|
|
64
|
+
data = response.read().decode("utf-8")
|
|
65
|
+
json_data = json.loads(data)
|
|
66
|
+
|
|
67
|
+
return json_data
|
|
68
|
+
|
|
69
|
+
async def async_fetch_data(self, api_key: str, latitude: float, longitude: float, days: int, language: str) -> Dict[str, Any]:
|
|
70
|
+
"""Get data from API."""
|
|
71
|
+
api_url =f"{VISUALCROSSING_BASE_URL}{latitude},{longitude}/today/next{days}days?unitGroup=metric&key={api_key}&contentType=json&iconSet=icons2&lang={language}"
|
|
72
|
+
|
|
73
|
+
is_new_session = False
|
|
74
|
+
if self.session is None:
|
|
75
|
+
self.session = aiohttp.ClientSession()
|
|
76
|
+
is_new_session = True
|
|
77
|
+
|
|
78
|
+
async with self.session.get(api_url) as response:
|
|
79
|
+
if response.status != 200:
|
|
80
|
+
if is_new_session:
|
|
81
|
+
await self.session.close()
|
|
82
|
+
raise VisualCrossingException(
|
|
83
|
+
f"Failed to access Visual Crossing API with status code {response.status}"
|
|
84
|
+
)
|
|
85
|
+
data = await response.text()
|
|
86
|
+
if is_new_session:
|
|
87
|
+
await self.session.close()
|
|
88
|
+
return json.loads(data)
|
|
89
|
+
|
|
90
|
+
class VisualCrossing:
|
|
91
|
+
"""
|
|
92
|
+
Class that uses the weather API from Visual Crossing
|
|
93
|
+
to retreive Weather Data for supplied Latitude and
|
|
94
|
+
longitude location
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
def __init__(
|
|
98
|
+
self,
|
|
99
|
+
api_key: str,
|
|
100
|
+
latitude: float,
|
|
101
|
+
longitude: float,
|
|
102
|
+
days: int = 14,
|
|
103
|
+
language: str = "en",
|
|
104
|
+
session: aiohttp.ClientSession = None,
|
|
105
|
+
api: VisualCrossingAPIBase =VisualCrossingAPI(),
|
|
106
|
+
) -> None:
|
|
107
|
+
self._api_key = api_key
|
|
108
|
+
self._latitude = latitude
|
|
109
|
+
self._longitude = longitude
|
|
110
|
+
self._days = days
|
|
111
|
+
self._language = language
|
|
112
|
+
self._api = api
|
|
113
|
+
self._json_data = None
|
|
114
|
+
|
|
115
|
+
if days > 14:
|
|
116
|
+
self._days = 14
|
|
117
|
+
|
|
118
|
+
if session:
|
|
119
|
+
self._api.session = session
|
|
120
|
+
|
|
121
|
+
if language not in SUPPORTED_LANGUAGES:
|
|
122
|
+
self._language = "en"
|
|
123
|
+
|
|
124
|
+
def fetch_data(self) -> List[ForecastData]:
|
|
125
|
+
"""Returns a list of weather data."""
|
|
126
|
+
|
|
127
|
+
if self._json_data is None:
|
|
128
|
+
self._json_data = self._api.fetch_data(self._api_key, self._latitude, self._longitude, self._days, self._language)
|
|
129
|
+
|
|
130
|
+
return _fetch_data(self._json_data)
|
|
131
|
+
|
|
132
|
+
async def async_fetch_data(self) -> List[ForecastData]:
|
|
133
|
+
"""Returns a list of weather data."""
|
|
134
|
+
|
|
135
|
+
if self._json_data is None:
|
|
136
|
+
self._json_data = await self._api.async_fetch_data(self._api_key, self._latitude, self._longitude, self._days, self._language)
|
|
137
|
+
|
|
138
|
+
return _fetch_data(self._json_data)
|
|
139
|
+
|
|
140
|
+
def _fetch_data(api_result: dict) -> List[ForecastData]:
|
|
141
|
+
"""Converts result from API to ForecastData List."""
|
|
142
|
+
|
|
143
|
+
# Add Current Condition Data
|
|
144
|
+
weather_data: ForecastData = _get_current_data(api_result)
|
|
145
|
+
|
|
146
|
+
forecast_daily = []
|
|
147
|
+
forecast_hourly = []
|
|
148
|
+
|
|
149
|
+
# Loop Through Records and add Daily and Hourly Forecast Data
|
|
150
|
+
for item in api_result["days"]:
|
|
151
|
+
valid_time = datetime.datetime.utcfromtimestamp(item["datetimeEpoch"]).replace(tzinfo=UTC)
|
|
152
|
+
condition = item.get("conditions", None)
|
|
153
|
+
cloudcover = item.get("cloudcover", None)
|
|
154
|
+
icon = item.get("icon", None)
|
|
155
|
+
temperature = item.get("tempmax", None)
|
|
156
|
+
temp_low = item.get("tempmin", None)
|
|
157
|
+
dew_point = item.get("dew", None)
|
|
158
|
+
apparent_temperature = item.get("feelslike", None)
|
|
159
|
+
precipitation = item.get("precip", None)
|
|
160
|
+
precipitation_probability = item.get("precipprob", None)
|
|
161
|
+
humidity = item.get("humidity", None)
|
|
162
|
+
pressure = item.get("pressure", None)
|
|
163
|
+
uv_index = item.get("uvindex", None)
|
|
164
|
+
wind_speed = item.get("windspeed", None)
|
|
165
|
+
wind_gust_speed = item.get("windgust", None)
|
|
166
|
+
wind_bearing = item.get("winddir", None)
|
|
167
|
+
|
|
168
|
+
day_data = ForecastDailyData(
|
|
169
|
+
valid_time,
|
|
170
|
+
temperature,
|
|
171
|
+
temp_low,
|
|
172
|
+
apparent_temperature,
|
|
173
|
+
condition,
|
|
174
|
+
icon,
|
|
175
|
+
cloudcover,
|
|
176
|
+
dew_point,
|
|
177
|
+
humidity,
|
|
178
|
+
precipitation_probability,
|
|
179
|
+
precipitation,
|
|
180
|
+
pressure,
|
|
181
|
+
wind_bearing,
|
|
182
|
+
wind_speed,
|
|
183
|
+
wind_gust_speed,
|
|
184
|
+
uv_index,
|
|
185
|
+
)
|
|
186
|
+
forecast_daily.append(day_data)
|
|
187
|
+
|
|
188
|
+
# Add Hourly data for this day
|
|
189
|
+
for row in item["hours"]:
|
|
190
|
+
valid_time = datetime.datetime.utcfromtimestamp(row["datetimeEpoch"]).replace(tzinfo=UTC)
|
|
191
|
+
condition = row.get("conditions", None)
|
|
192
|
+
cloudcover = row.get("cloudcover", None)
|
|
193
|
+
icon = row.get("icon", None)
|
|
194
|
+
temperature = row.get("temp", None)
|
|
195
|
+
dew_point = row.get("dew", None)
|
|
196
|
+
apparent_temperature = row.get("feelslike", None)
|
|
197
|
+
precipitation = row.get("precip", None)
|
|
198
|
+
precipitation_probability = row.get("precipprob", None)
|
|
199
|
+
humidity = row.get("humidity", None)
|
|
200
|
+
pressure = row.get("pressure", None)
|
|
201
|
+
uv_index = row.get("uvindex", None)
|
|
202
|
+
wind_speed = row.get("windspeed", None)
|
|
203
|
+
wind_gust_speed = row.get("windgust", None)
|
|
204
|
+
wind_bearing = row.get("winddir", None)
|
|
205
|
+
|
|
206
|
+
hour_data = ForecastHourlyData(
|
|
207
|
+
valid_time,
|
|
208
|
+
temperature,
|
|
209
|
+
apparent_temperature,
|
|
210
|
+
condition,
|
|
211
|
+
cloudcover,
|
|
212
|
+
icon,
|
|
213
|
+
dew_point,
|
|
214
|
+
humidity,
|
|
215
|
+
precipitation,
|
|
216
|
+
precipitation_probability,
|
|
217
|
+
pressure,
|
|
218
|
+
wind_bearing,
|
|
219
|
+
wind_gust_speed,
|
|
220
|
+
wind_speed,
|
|
221
|
+
uv_index,
|
|
222
|
+
)
|
|
223
|
+
forecast_hourly.append(hour_data)
|
|
224
|
+
|
|
225
|
+
weather_data.forecast_daily = forecast_daily
|
|
226
|
+
weather_data.forecast_hourly = forecast_hourly
|
|
227
|
+
|
|
228
|
+
return weather_data
|
|
229
|
+
|
|
230
|
+
# pylint: disable=R0914, R0912, W0212, R0915
|
|
231
|
+
def _get_current_data(api_result: dict) -> List[ForecastData]:
|
|
232
|
+
"""Converts results from API to WeatherFlowForecast list"""
|
|
233
|
+
|
|
234
|
+
item = api_result["currentConditions"]
|
|
235
|
+
|
|
236
|
+
valid_time = datetime.datetime.utcfromtimestamp(item["datetimeEpoch"]).replace(tzinfo=UTC)
|
|
237
|
+
condition = item.get("conditions", None)
|
|
238
|
+
cloudcover = item.get("cloudcover", None)
|
|
239
|
+
icon = item.get("icon", None)
|
|
240
|
+
temperature = item.get("temp", None)
|
|
241
|
+
dew_point = item.get("dew", None)
|
|
242
|
+
apparent_temperature = item.get("feelslike", None)
|
|
243
|
+
precipitation = item.get("precip", None)
|
|
244
|
+
precipitation_probability = item.get("precipprob", None)
|
|
245
|
+
humidity = item.get("humidity", None)
|
|
246
|
+
solarradiation = item.get("solarradiation", None)
|
|
247
|
+
visibility = item.get("visibility", None)
|
|
248
|
+
pressure = item.get("pressure", None)
|
|
249
|
+
uv_index = item.get("uvindex", None)
|
|
250
|
+
wind_speed = item.get("windspeed", None)
|
|
251
|
+
wind_gust_speed = item.get("windgust", None)
|
|
252
|
+
wind_bearing = item.get("winddir", None)
|
|
253
|
+
|
|
254
|
+
current_condition = ForecastData(
|
|
255
|
+
valid_time,
|
|
256
|
+
apparent_temperature,
|
|
257
|
+
condition,
|
|
258
|
+
cloudcover,
|
|
259
|
+
dew_point,
|
|
260
|
+
humidity,
|
|
261
|
+
icon,
|
|
262
|
+
precipitation,
|
|
263
|
+
precipitation_probability,
|
|
264
|
+
pressure,
|
|
265
|
+
solarradiation,
|
|
266
|
+
temperature,
|
|
267
|
+
visibility,
|
|
268
|
+
uv_index,
|
|
269
|
+
wind_bearing,
|
|
270
|
+
wind_gust_speed,
|
|
271
|
+
wind_speed,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
return current_condition
|
|
275
|
+
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"""System Wide constants for Visual Crossing Weather Wrapper."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
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"]
|
|
5
|
+
VISUALCROSSING_BASE_URL = "https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/"
|
pyVisualCrossing/data.py
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
"""Holds the Data Classes for Visual Crossing Wrapper"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
class ForecastData:
|
|
8
|
+
"""Class to hold forecast data."""
|
|
9
|
+
# pylint: disable=R0913, R0902, R0914
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
datetime: datetime,
|
|
13
|
+
apparent_temperature: float,
|
|
14
|
+
condition: str,
|
|
15
|
+
cloud_cover: int,
|
|
16
|
+
dew_point: float,
|
|
17
|
+
humidity: int,
|
|
18
|
+
icon: str,
|
|
19
|
+
precipitation: float,
|
|
20
|
+
precipitation_probability: int,
|
|
21
|
+
pressure: float,
|
|
22
|
+
solarradiation: float,
|
|
23
|
+
temperature: float,
|
|
24
|
+
visibility: int,
|
|
25
|
+
uv_index: int,
|
|
26
|
+
wind_bearing: int,
|
|
27
|
+
wind_gust_speed: float,
|
|
28
|
+
wind_speed: float,
|
|
29
|
+
forecast_daily: ForecastDailyData = None,
|
|
30
|
+
forecast_hourly: ForecastHourlyData = None,
|
|
31
|
+
) -> None:
|
|
32
|
+
"""Constructor"""
|
|
33
|
+
self._datetime = datetime
|
|
34
|
+
self._apparent_temperature = apparent_temperature
|
|
35
|
+
self._condition = condition
|
|
36
|
+
self._cloud_cover = cloud_cover
|
|
37
|
+
self._dew_point = dew_point
|
|
38
|
+
self._humidity = humidity
|
|
39
|
+
self._icon = icon
|
|
40
|
+
self._precipitation = precipitation
|
|
41
|
+
self._precipitation_probability = precipitation_probability
|
|
42
|
+
self._pressure = pressure
|
|
43
|
+
self._solarradiation = solarradiation
|
|
44
|
+
self._visibility = visibility
|
|
45
|
+
self._temperature = temperature
|
|
46
|
+
self._uv_index = uv_index
|
|
47
|
+
self._wind_bearing = wind_bearing
|
|
48
|
+
self._wind_gust_speed = wind_gust_speed
|
|
49
|
+
self._wind_speed = wind_speed
|
|
50
|
+
self._forecast_daily = forecast_daily
|
|
51
|
+
self._forecast_hourly = forecast_hourly
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def temperature(self) -> float:
|
|
56
|
+
"""Air temperature (Celcius)"""
|
|
57
|
+
return self._temperature
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def dew_point(self) -> float:
|
|
61
|
+
"""Dew Point (Celcius)"""
|
|
62
|
+
return self._dew_point
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def condition(self) -> str:
|
|
66
|
+
"""Weather condition text."""
|
|
67
|
+
return self._condition
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def cloud_cover(self) -> int:
|
|
71
|
+
"""Cloud Coverage."""
|
|
72
|
+
return self._cloud_cover
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def icon(self) -> str:
|
|
76
|
+
"""Weather condition symbol."""
|
|
77
|
+
return self._icon
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def humidity(self) -> int:
|
|
81
|
+
"""Humidity (%)."""
|
|
82
|
+
return self._humidity
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def apparent_temperature(self) -> float:
|
|
86
|
+
"""Feels like temperature (Celcius)."""
|
|
87
|
+
return self._apparent_temperature
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def precipitation(self) -> float:
|
|
91
|
+
"""Precipitation (mm)."""
|
|
92
|
+
return self._precipitation
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def precipitation_probability (self) -> int:
|
|
96
|
+
"""Posobility of Precipiation (%)."""
|
|
97
|
+
return self._precipitation_probability
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def pressure(self) -> float:
|
|
101
|
+
"""Sea Level Pressure (MB)"""
|
|
102
|
+
return self._pressure
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def solarradiation(self) -> float:
|
|
106
|
+
"""Solar Radiation (w/m2)"""
|
|
107
|
+
return self._solarradiation
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def visibility(self) -> int:
|
|
111
|
+
"""Visibility (km)"""
|
|
112
|
+
return self._visibility
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def wind_bearing(self) -> float:
|
|
117
|
+
"""Wind bearing (degrees)"""
|
|
118
|
+
return self._wind_bearing
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def wind_gust_speed(self) -> float:
|
|
122
|
+
"""Wind gust (m/s)"""
|
|
123
|
+
return self._wind_gust_speed
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def wind_speed(self) -> float:
|
|
127
|
+
"""Wind speed (m/s)"""
|
|
128
|
+
return self._wind_speed
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def uv_index(self) -> float:
|
|
132
|
+
"""UV Index"""
|
|
133
|
+
return self._uv_index
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def datetime(self) -> datetime:
|
|
137
|
+
"""Valid time"""
|
|
138
|
+
return self._datetime
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def forecast_daily(self) -> ForecastDailyData:
|
|
142
|
+
"""Forecast List"""
|
|
143
|
+
return self._forecast_daily
|
|
144
|
+
|
|
145
|
+
@forecast_daily.setter
|
|
146
|
+
def forecast_daily(self, new_forecast):
|
|
147
|
+
"""Sets a new Value for forecast daily"""
|
|
148
|
+
self._forecast_daily = new_forecast
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def forecast_hourly(self) -> ForecastHourlyData:
|
|
152
|
+
"""Forecast List"""
|
|
153
|
+
return self._forecast_hourly
|
|
154
|
+
|
|
155
|
+
@forecast_hourly.setter
|
|
156
|
+
def forecast_hourly(self, new_forecast):
|
|
157
|
+
"""Sets a new Value for forecast hourly"""
|
|
158
|
+
self._forecast_hourly = new_forecast
|
|
159
|
+
|
|
160
|
+
class ForecastDailyData:
|
|
161
|
+
"""Class to hold daily forecast data."""
|
|
162
|
+
# pylint: disable=R0913, R0902, R0914
|
|
163
|
+
def __init__(
|
|
164
|
+
self,
|
|
165
|
+
datetime: datetime,
|
|
166
|
+
temperature: float,
|
|
167
|
+
temp_low: float,
|
|
168
|
+
apparent_temperature: float,
|
|
169
|
+
condition: str,
|
|
170
|
+
icon: str,
|
|
171
|
+
cloud_cover: int,
|
|
172
|
+
dew_point: float,
|
|
173
|
+
humidity: int,
|
|
174
|
+
precipitation_probability: int,
|
|
175
|
+
precipitation: float,
|
|
176
|
+
pressure: float,
|
|
177
|
+
wind_bearing: int,
|
|
178
|
+
wind_speed: float,
|
|
179
|
+
wind_gust: float,
|
|
180
|
+
uv_index: int,
|
|
181
|
+
) -> None:
|
|
182
|
+
"""Constructor"""
|
|
183
|
+
self._datetime = datetime
|
|
184
|
+
self._temperature = temperature
|
|
185
|
+
self._temp_low = temp_low
|
|
186
|
+
self._apparent_temperature = apparent_temperature
|
|
187
|
+
self._condition = condition
|
|
188
|
+
self._cloud_cover = cloud_cover
|
|
189
|
+
self._dew_point = dew_point
|
|
190
|
+
self._humidity = humidity
|
|
191
|
+
self._icon = icon
|
|
192
|
+
self._precipitation_probability = precipitation_probability
|
|
193
|
+
self._precipitation = precipitation
|
|
194
|
+
self._pressure = pressure
|
|
195
|
+
self._wind_bearing = wind_bearing
|
|
196
|
+
self._wind_gust = wind_gust
|
|
197
|
+
self._wind_speed = wind_speed
|
|
198
|
+
self._uv_index = uv_index
|
|
199
|
+
|
|
200
|
+
@property
|
|
201
|
+
def datetime(self) -> datetime:
|
|
202
|
+
"""Valid time"""
|
|
203
|
+
return self._datetime
|
|
204
|
+
|
|
205
|
+
@property
|
|
206
|
+
def temperature(self) -> float:
|
|
207
|
+
"""Air temperature (Celcius)"""
|
|
208
|
+
return self._temperature
|
|
209
|
+
|
|
210
|
+
@property
|
|
211
|
+
def temp_low(self) -> float:
|
|
212
|
+
"""Air temperature min during the day (Celcius)"""
|
|
213
|
+
return self._temp_low
|
|
214
|
+
|
|
215
|
+
@property
|
|
216
|
+
def apparent_temperature(self) -> float:
|
|
217
|
+
"""Feels like temperature (Celcius)."""
|
|
218
|
+
return self._apparent_temperature
|
|
219
|
+
|
|
220
|
+
@property
|
|
221
|
+
def condition(self) -> str:
|
|
222
|
+
"""Weather condition text."""
|
|
223
|
+
return self._condition
|
|
224
|
+
|
|
225
|
+
@property
|
|
226
|
+
def cloud_cover(self) -> int:
|
|
227
|
+
"""Cloud Coverage."""
|
|
228
|
+
return self._cloud_cover
|
|
229
|
+
|
|
230
|
+
@property
|
|
231
|
+
def dew_point(self) -> float:
|
|
232
|
+
"""Dew Point (Celcius)"""
|
|
233
|
+
return self._dew_point
|
|
234
|
+
|
|
235
|
+
@property
|
|
236
|
+
def humidity(self) -> int:
|
|
237
|
+
"""Humidity (%)."""
|
|
238
|
+
return self._humidity
|
|
239
|
+
|
|
240
|
+
@property
|
|
241
|
+
def icon(self) -> str:
|
|
242
|
+
"""Weather condition symbol."""
|
|
243
|
+
return self._icon
|
|
244
|
+
|
|
245
|
+
@property
|
|
246
|
+
def precipitation_probability (self) -> int:
|
|
247
|
+
"""Posobility of Precipiation (%)."""
|
|
248
|
+
return self._precipitation_probability
|
|
249
|
+
|
|
250
|
+
@property
|
|
251
|
+
def precipitation(self) -> float:
|
|
252
|
+
"""Precipitation (mm)."""
|
|
253
|
+
return self._precipitation
|
|
254
|
+
|
|
255
|
+
@property
|
|
256
|
+
def pressure(self) -> float:
|
|
257
|
+
"""Sea Level Pressure (MB)"""
|
|
258
|
+
return self._pressure
|
|
259
|
+
|
|
260
|
+
@property
|
|
261
|
+
def uv_index(self) -> float:
|
|
262
|
+
"""UV Index"""
|
|
263
|
+
return self._uv_index
|
|
264
|
+
|
|
265
|
+
@property
|
|
266
|
+
def wind_bearing(self) -> float:
|
|
267
|
+
"""Wind bearing (degrees)"""
|
|
268
|
+
return self._wind_bearing
|
|
269
|
+
|
|
270
|
+
@property
|
|
271
|
+
def wind_gust(self) -> float:
|
|
272
|
+
"""Wind Gust speed (m/s)"""
|
|
273
|
+
return self._wind_gust
|
|
274
|
+
|
|
275
|
+
@property
|
|
276
|
+
def wind_speed(self) -> float:
|
|
277
|
+
"""Wind speed (m/s)"""
|
|
278
|
+
return self._wind_speed
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
class ForecastHourlyData:
|
|
282
|
+
"""Class to hold hourly forecast data."""
|
|
283
|
+
# pylint: disable=R0913, R0902, R0914
|
|
284
|
+
def __init__(
|
|
285
|
+
self,
|
|
286
|
+
datetime: datetime,
|
|
287
|
+
temperature: float,
|
|
288
|
+
apparent_temperature: float,
|
|
289
|
+
condition: str,
|
|
290
|
+
cloud_cover: int,
|
|
291
|
+
icon: str,
|
|
292
|
+
dew_point: float,
|
|
293
|
+
humidity: int,
|
|
294
|
+
precipitation: float,
|
|
295
|
+
precipitation_probability: int,
|
|
296
|
+
pressure: float,
|
|
297
|
+
wind_bearing: float,
|
|
298
|
+
wind_gust_speed: int,
|
|
299
|
+
wind_speed: int,
|
|
300
|
+
uv_index: float,
|
|
301
|
+
) -> None:
|
|
302
|
+
"""Constructor"""
|
|
303
|
+
self._datetime = datetime
|
|
304
|
+
self._temperature = temperature
|
|
305
|
+
self._apparent_temperature = apparent_temperature
|
|
306
|
+
self._condition = condition
|
|
307
|
+
self._cloud_cover = cloud_cover
|
|
308
|
+
self._icon = icon
|
|
309
|
+
self._dew_point = dew_point
|
|
310
|
+
self._humidity = humidity
|
|
311
|
+
self._precipitation = precipitation
|
|
312
|
+
self._precipitation_probability = precipitation_probability
|
|
313
|
+
self._pressure = pressure
|
|
314
|
+
self._wind_bearing = wind_bearing
|
|
315
|
+
self._wind_gust_speed = wind_gust_speed
|
|
316
|
+
self._wind_speed = wind_speed
|
|
317
|
+
self._uv_index = uv_index
|
|
318
|
+
|
|
319
|
+
@property
|
|
320
|
+
def temperature(self) -> float:
|
|
321
|
+
"""Air temperature (Celcius)"""
|
|
322
|
+
return self._temperature
|
|
323
|
+
|
|
324
|
+
@property
|
|
325
|
+
def condition(self) -> str:
|
|
326
|
+
"""Weather condition text."""
|
|
327
|
+
return self._condition
|
|
328
|
+
|
|
329
|
+
@property
|
|
330
|
+
def cloud_cover(self) -> int:
|
|
331
|
+
"""Cloud Coverage."""
|
|
332
|
+
return self._cloud_cover
|
|
333
|
+
|
|
334
|
+
@property
|
|
335
|
+
def dew_point(self) -> float:
|
|
336
|
+
"""Dew Point (Celcius)"""
|
|
337
|
+
return self._dew_point
|
|
338
|
+
|
|
339
|
+
@property
|
|
340
|
+
def icon(self) -> str:
|
|
341
|
+
"""Weather condition symbol."""
|
|
342
|
+
return self._icon
|
|
343
|
+
|
|
344
|
+
@property
|
|
345
|
+
def humidity(self) -> int:
|
|
346
|
+
"""Humidity (%)."""
|
|
347
|
+
return self._humidity
|
|
348
|
+
|
|
349
|
+
@property
|
|
350
|
+
def apparent_temperature(self) -> float:
|
|
351
|
+
"""Feels like temperature (Celcius)."""
|
|
352
|
+
return self._apparent_temperature
|
|
353
|
+
|
|
354
|
+
@property
|
|
355
|
+
def precipitation(self) -> float:
|
|
356
|
+
"""Precipitation (mm)."""
|
|
357
|
+
return self._precipitation
|
|
358
|
+
|
|
359
|
+
@property
|
|
360
|
+
def precipitation_probability (self) -> int:
|
|
361
|
+
"""Posobility of Precipiation (%)."""
|
|
362
|
+
return self._precipitation_probability
|
|
363
|
+
|
|
364
|
+
@property
|
|
365
|
+
def pressure(self) -> float:
|
|
366
|
+
"""Sea Level Pressure (MB)"""
|
|
367
|
+
return self._pressure
|
|
368
|
+
|
|
369
|
+
@property
|
|
370
|
+
def wind_bearing(self) -> float:
|
|
371
|
+
"""Wind bearing (degrees)"""
|
|
372
|
+
return self._wind_bearing
|
|
373
|
+
|
|
374
|
+
@property
|
|
375
|
+
def wind_gust_speed(self) -> float:
|
|
376
|
+
"""Wind gust (m/s)"""
|
|
377
|
+
return self._wind_gust_speed
|
|
378
|
+
|
|
379
|
+
@property
|
|
380
|
+
def wind_speed(self) -> float:
|
|
381
|
+
"""Wind speed (m/s)"""
|
|
382
|
+
return self._wind_speed
|
|
383
|
+
|
|
384
|
+
@property
|
|
385
|
+
def uv_index(self) -> float:
|
|
386
|
+
"""UV Index"""
|
|
387
|
+
return self._uv_index
|
|
388
|
+
|
|
389
|
+
@property
|
|
390
|
+
def datetime(self) -> datetime:
|
|
391
|
+
"""Valid time"""
|
|
392
|
+
return self._datetime
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Bjarne Riis
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: pyVisualCrossing
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Gets the weather data from Visual Crossing
|
|
5
|
+
Home-page: https://github.com/briis/pyVisualCrossing
|
|
6
|
+
Author: briis
|
|
7
|
+
Author-email: bjarne@briis.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
|
|
16
|
+
# Python Wrapper for Visual Crossing Weather API
|
|
17
|
+
|
|
18
|
+
This Python Wrapper retrives data from the [Visual Crossing](https://www.visualcrossing.com/) API. Visual Crossing has an extensive Weather API for both historical and forecast weather data, and they have a Free Tier API Key which enables up to 1000 calls per day.
|
|
19
|
+
|
|
20
|
+
In order to get started you must create an Account with Visual Crossing and then create an API Key. You do this by accessing [this website](https://www.visualcrossing.com/weather-data-editions) and clicking on the **Free** plan. Then follow the instructions to create and account and store your key in a safe place.
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
Install the module by using this command in a terminal: `pip install pyVisualCrossing`
|
|
25
|
+
|
|
26
|
+
And then see `test_module.py` and `async_test_module.py` in the `samples` directory for usage examples, both standard and async.
|
|
27
|
+
|
|
28
|
+
## Parameters
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
# Initialise the module
|
|
32
|
+
vcapi = VisualCrossing(
|
|
33
|
+
api_key,
|
|
34
|
+
latitude,
|
|
35
|
+
longitude,
|
|
36
|
+
days=7,
|
|
37
|
+
language="da"
|
|
38
|
+
)
|
|
39
|
+
````
|
|
40
|
+
|
|
41
|
+
| Parameter | Required | Default | Description |
|
|
42
|
+
| --------- | -------- | ------- | ----------- |
|
|
43
|
+
| `api_key` | Yes | `None` | This is the API Key you signed up for from Visual Crossing. See above for instructions |
|
|
44
|
+
| `latitude` | Yes | `None` | Latitude for the location position |
|
|
45
|
+
| `longitude` | Yes | `None` | Longitude for the location position |
|
|
46
|
+
| `days` | No | `14` | Numbers of days to retrieve forecast for. 14 days means today plus the next 14 days. On the Free plan, this is the maximum number of days |
|
|
47
|
+
| `language` | No | `en` | The language in which text strings should be returned. Se below for list of valid languages. |
|
|
48
|
+
| `session` | No | `None` | A session variable. Only used when using the async function. |
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
For an in-depth description of the Visual Crossing API, go [here](https://www.visualcrossing.com/resources/documentation/weather-api/timeline-weather-api/)
|
|
53
|
+
|
|
54
|
+
## Languages
|
|
55
|
+
Available languages include: ar (Arabic), bg (Bulgiarian), cs (Czech), da (Danish), de (German), el (Greek Modern), en (English), es (Spanish), fa (Farsi), fi (Finnish), fr (French), he (Hebrew), hu, (Hungarian), it (Italian), ja (Japanese), ko (Korean), nl (Dutch), pl (Polish), pt (Portuguese), ru (Russian),, sr (Serbian), sv (Swedish), tr (Turkish), uk (Ukranian), vi (Vietnamese) and zh (Chinese).
|
|
56
|
+
|
|
57
|
+
## Icons
|
|
58
|
+
We use the Iconset *icons2*, which gives a more detailed description of the conditions.
|
|
59
|
+
|
|
60
|
+
| Icon id | Weather Conditions |
|
|
61
|
+
| -------------------- | ---------------------------- |
|
|
62
|
+
| snow | Amount of snow is greater than zero |
|
|
63
|
+
| snow-showers-day | Periods of snow during the day |
|
|
64
|
+
| snow-showers-night | Periods of snow during the night |
|
|
65
|
+
| thunder-rain | Thunderstorms throughout the day or night |
|
|
66
|
+
| thunder-showers-day | Possible thunderstorms throughout the day |
|
|
67
|
+
| thunder-showers-night | Possible thunderstorms throughout the night |
|
|
68
|
+
| rain | Amount of rainfall is greater than zero |
|
|
69
|
+
| showers-day | Rain showers during the day |
|
|
70
|
+
| showers-night | Rain showers during the night |
|
|
71
|
+
| fog | Visibility is low (lower than one kilometer or mile) |
|
|
72
|
+
| wind | Wind speed is high (greater than 30 kph or mph) |
|
|
73
|
+
| cloudy | Cloud cover is greater than 90% cover |
|
|
74
|
+
| partly-cloudy-day | Cloud cover is greater than 20% cover during day time. |
|
|
75
|
+
| partly-cloudy-night | Cloud cover is greater than 20% cover during night time. |
|
|
76
|
+
| clear-day | Cloud cover is less than 20% cover during day time |
|
|
77
|
+
| clear-night | Cloud cover is less than 20% cover during night time |
|
|
78
|
+
|
|
79
|
+
## TODO
|
|
80
|
+
|
|
81
|
+
- Add all available items to the Data Structure
|
|
82
|
+
- Create `async_test_module.py` in the samples directory
|
|
83
|
+
- More detailed error handling.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
pyVisualCrossing/__init__.py,sha256=gDdseMKZlvecfdCpAtaSL1jP-Stxh-pkviKcFpJk0NM,319
|
|
2
|
+
pyVisualCrossing/api.py,sha256=stl4kk8Ov8UUHjfU_r2tV4JVHijunVhRf5a8cPtbHeg,9261
|
|
3
|
+
pyVisualCrossing/const.py,sha256=8vhxvLIguiPPDB8h93z1j7OVpzHFjyrpvtgjSUZD4go,387
|
|
4
|
+
pyVisualCrossing/data.py,sha256=45uZ15AxefuyUrkEZ2bmihXQIsNfKM3CYG4vpwIwl6A,10500
|
|
5
|
+
pyVisualCrossing-0.1.0.dist-info/LICENSE,sha256=Z_3SNSdtSUo8UsGZ7hnBOXjDSTvFWEbUxNJxdRjaxLk,1068
|
|
6
|
+
pyVisualCrossing-0.1.0.dist-info/METADATA,sha256=sjaslhNN7NDC6zmahmKBpLE1oSLycM4bKSwyC_MBl2A,4298
|
|
7
|
+
pyVisualCrossing-0.1.0.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
|
|
8
|
+
pyVisualCrossing-0.1.0.dist-info/top_level.txt,sha256=hw0rVTorvfSv7hOAv0z8xq49056RxL4slkUaklJ0mOU,17
|
|
9
|
+
pyVisualCrossing-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pyVisualCrossing
|