python-weather 2.0.4__py3-none-any.whl → 2.0.6__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.
- python_weather/__init__.py +1 -1
- python_weather/base.py +56 -70
- python_weather/client.py +26 -19
- python_weather/enums.py +27 -36
- python_weather/errors.py +2 -2
- python_weather/forecast.py +197 -251
- {python_weather-2.0.4.dist-info → python_weather-2.0.6.dist-info}/METADATA +4 -4
- python_weather-2.0.6.dist-info/RECORD +12 -0
- python_weather-2.0.4.dist-info/RECORD +0 -12
- {python_weather-2.0.4.dist-info → python_weather-2.0.6.dist-info}/LICENSE +0 -0
- {python_weather-2.0.4.dist-info → python_weather-2.0.6.dist-info}/WHEEL +0 -0
- {python_weather-2.0.4.dist-info → python_weather-2.0.6.dist-info}/top_level.txt +0 -0
python_weather/__init__.py
CHANGED
python_weather/base.py
CHANGED
|
@@ -22,7 +22,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
22
22
|
SOFTWARE.
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
|
-
from enum import auto
|
|
26
25
|
from typing import Tuple
|
|
27
26
|
|
|
28
27
|
from .errors import Error
|
|
@@ -38,7 +37,7 @@ class CustomizableBase:
|
|
|
38
37
|
self.locale = locale
|
|
39
38
|
|
|
40
39
|
@property
|
|
41
|
-
def unit(self) ->
|
|
40
|
+
def unit(self) -> _Unit:
|
|
42
41
|
"""The measuring unit used to display information in this object."""
|
|
43
42
|
|
|
44
43
|
return self.__unit
|
|
@@ -68,9 +67,9 @@ class CustomizableBase:
|
|
|
68
67
|
"""
|
|
69
68
|
Sets the default localization used to display information in this object.
|
|
70
69
|
|
|
71
|
-
:param to: The new :class
|
|
70
|
+
:param to: The new :class:`~python_weather.enums.Locale` to be used to display information in this object.
|
|
72
71
|
:type to: Locale
|
|
73
|
-
:exception Error: If the ``to`` argument is not a part of the :class
|
|
72
|
+
:exception Error: If the ``to`` argument is not a part of the :class:`~python_weather.enums.Locale` enum.
|
|
74
73
|
"""
|
|
75
74
|
|
|
76
75
|
if not isinstance(to, Locale):
|
|
@@ -79,84 +78,71 @@ class CustomizableBase:
|
|
|
79
78
|
self.__locale = to
|
|
80
79
|
|
|
81
80
|
|
|
82
|
-
class BaseForecast
|
|
83
|
-
__slots__: Tuple[str, ...] = (
|
|
81
|
+
class BaseForecast:
|
|
82
|
+
__slots__: Tuple[str, ...] = (
|
|
83
|
+
'ultraviolet',
|
|
84
|
+
'humidity',
|
|
85
|
+
'wind_direction',
|
|
86
|
+
'kind',
|
|
87
|
+
'feels_like',
|
|
88
|
+
'temperature',
|
|
89
|
+
'precipitation',
|
|
90
|
+
'pressure',
|
|
91
|
+
'visibility',
|
|
92
|
+
'wind_speed',
|
|
93
|
+
'description',
|
|
94
|
+
)
|
|
84
95
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
super().__init__(unit, locale)
|
|
89
|
-
|
|
90
|
-
@property
|
|
91
|
-
def ultraviolet(self) -> UltraViolet:
|
|
92
|
-
"""The ultra-violet (UV) index."""
|
|
93
|
-
|
|
94
|
-
return UltraViolet._new(int(self.__inner['uvIndex']))
|
|
95
|
-
|
|
96
|
-
@property
|
|
97
|
-
def feels_like(self) -> int:
|
|
98
|
-
"""What it felt like, in celcius or fahrenheit."""
|
|
96
|
+
ultraviolet: UltraViolet
|
|
97
|
+
"""The ultra-violet (UV) index."""
|
|
99
98
|
|
|
100
|
-
|
|
99
|
+
humidity: int
|
|
100
|
+
"""The humidity value in percent."""
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
"""The humidity value in percent."""
|
|
102
|
+
wind_direction: WindDirection
|
|
103
|
+
"""The wind direction."""
|
|
105
104
|
|
|
106
|
-
|
|
105
|
+
kind: Kind
|
|
106
|
+
"""The kind of the forecast."""
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
"""The temperature in either celcius or Fahrenheit."""
|
|
108
|
+
feels_like: int
|
|
109
|
+
"""What it felt like, in celcius or fahrenheit."""
|
|
111
110
|
|
|
112
|
-
|
|
111
|
+
temperature: int
|
|
112
|
+
"""The temperature in either celcius or Fahrenheit."""
|
|
113
113
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
"""The precipitation in either millimeters or inches."""
|
|
114
|
+
precipitation: float
|
|
115
|
+
"""The precipitation in either millimeters or inches."""
|
|
117
116
|
|
|
118
|
-
|
|
117
|
+
pressure: float
|
|
118
|
+
"""The pressure in either pascal or inches."""
|
|
119
119
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
"""The pressure in either pascal or inches."""
|
|
120
|
+
visibility: int
|
|
121
|
+
"""The visibility distance in either kilometers or miles."""
|
|
123
122
|
|
|
124
|
-
|
|
123
|
+
wind_speed: int
|
|
124
|
+
"""The wind speeds in either kilometers/hour or miles/hour."""
|
|
125
125
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
"""The visibility distance in either kilometers or miles."""
|
|
129
|
-
|
|
130
|
-
return int(self.__inner[f'visibility{self._CustomizableBase__unit.visibility}'])
|
|
131
|
-
|
|
132
|
-
@property
|
|
133
|
-
def wind_speed(self) -> int:
|
|
134
|
-
"""The wind speeds in either kilometers/hour or miles/hour."""
|
|
135
|
-
|
|
136
|
-
return int(self.__inner[f'windspeed{self._CustomizableBase__unit.velocity}'])
|
|
137
|
-
|
|
138
|
-
@property
|
|
139
|
-
def wind_direction(self) -> WindDirection:
|
|
140
|
-
"""The wind direction."""
|
|
141
|
-
|
|
142
|
-
return WindDirection._new(
|
|
143
|
-
self.__inner['winddir16Point'], int(self.__inner['winddirDegree'])
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
@property
|
|
147
|
-
def description(self) -> str:
|
|
148
|
-
"""The description regarding the forecast. This can be localized in different languages depending on the localization used."""
|
|
126
|
+
description: str
|
|
127
|
+
"""The description regarding the forecast. This can be localized in different languages depending on the localization used."""
|
|
149
128
|
|
|
129
|
+
def __init__(self, json: dict, unit: _Unit, locale: Locale):
|
|
150
130
|
description = (
|
|
151
|
-
|
|
152
|
-
if
|
|
153
|
-
else
|
|
131
|
+
json['weatherDesc'][0]['value']
|
|
132
|
+
if locale is Locale.ENGLISH
|
|
133
|
+
else json[f'lang_{locale.value}'][0]['value']
|
|
154
134
|
)
|
|
155
135
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
136
|
+
self.ultraviolet = UltraViolet._new(int(json['uvIndex']))
|
|
137
|
+
self.humidity = int(json['humidity'])
|
|
138
|
+
self.wind_direction = WindDirection._new(
|
|
139
|
+
json['winddir16Point'], int(json['winddirDegree'])
|
|
140
|
+
)
|
|
141
|
+
self.kind = Kind(int(json['weatherCode']))
|
|
142
|
+
self.feels_like = int(json[f'FeelsLike{unit.temperature}'])
|
|
143
|
+
self.temperature = int(json[f'temp_{unit.temperature}'])
|
|
144
|
+
self.precipitation = float(json[f'precip{unit.precipitation}'])
|
|
145
|
+
self.pressure = float(json[f'pressure{unit.pressure}'])
|
|
146
|
+
self.visibility = int(json[f'visibility{unit.visibility}'])
|
|
147
|
+
self.wind_speed = int(json[f'windspeed{unit.velocity}'])
|
|
148
|
+
self.description = description.strip()
|
python_weather/client.py
CHANGED
|
@@ -23,10 +23,9 @@ SOFTWARE.
|
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
25
|
from aiohttp import ClientSession, ClientTimeout, TCPConnector
|
|
26
|
+
from typing import Optional, Tuple, Self
|
|
26
27
|
from urllib.parse import quote_plus
|
|
27
|
-
from typing import Optional, Tuple
|
|
28
28
|
from asyncio import sleep
|
|
29
|
-
from enum import auto
|
|
30
29
|
|
|
31
30
|
from .errors import Error, RequestError
|
|
32
31
|
from .constants import _Unit, METRIC
|
|
@@ -40,32 +39,35 @@ class Client(CustomizableBase):
|
|
|
40
39
|
The class that lets you interact with the API.
|
|
41
40
|
|
|
42
41
|
:param unit: Whether to use the metric or imperial/customary system (``IMPERIAL``). Defaults to ``METRIC``.
|
|
43
|
-
:type unit:
|
|
42
|
+
:type unit: _Unit
|
|
44
43
|
:param locale: Whether to use a different locale/language as the description for the returned forecast. Defaults to ``Locale.ENGLISH``.
|
|
45
|
-
:type locale:
|
|
46
|
-
:param session: Whether to use an existing aiohttp client session for requesting or not. Defaults to ``None`` (creates a new one instead)
|
|
47
|
-
:type session: Optional[:class
|
|
44
|
+
:type locale: Locale
|
|
45
|
+
:param session: Whether to use an existing aiohttp client session for requesting or not. Defaults to ``None`` (creates a new one instead).
|
|
46
|
+
:type session: Optional[:class:`~aiohttp.ClientSession`]
|
|
47
|
+
:param max_retries: Maximum amount of retries upon receiving HTTP request failure before raising a :class:`~python_weather.errors.RequestError`. To have infinite retries, use ``-1`` (NOT recommended). Defaults to ``None`` (or 3 retries).
|
|
48
|
+
:type max_retries: Optional[:class:`int`]
|
|
48
49
|
|
|
49
|
-
:raises Error: If ``unit`` is not ``METRIC`` or ``IMPERIAL``, or if ``locale`` is not ``None`` and not a part of the :class
|
|
50
|
+
:raises Error: If ``unit`` is not ``METRIC`` or ``IMPERIAL``, or if ``locale`` is not ``None`` and not a part of the :class:`~python_weather.enums.Locale` enum.
|
|
50
51
|
"""
|
|
51
52
|
|
|
52
|
-
__slots__: Tuple[str, ...] = ('__own_session', '__session')
|
|
53
|
+
__slots__: Tuple[str, ...] = ('__own_session', '__session', '__max_retries')
|
|
53
54
|
|
|
54
55
|
def __init__(
|
|
55
56
|
self,
|
|
56
57
|
*,
|
|
57
|
-
unit:
|
|
58
|
-
locale:
|
|
58
|
+
unit: _Unit = METRIC,
|
|
59
|
+
locale: Locale = Locale.ENGLISH,
|
|
59
60
|
session: Optional[ClientSession] = None,
|
|
61
|
+
max_retries: Optional[int] = None,
|
|
60
62
|
):
|
|
61
63
|
super().__init__(unit, locale)
|
|
62
64
|
|
|
63
65
|
self.__own_session = session is None
|
|
64
66
|
self.__session = session or ClientSession(
|
|
65
|
-
raise_for_status=True,
|
|
66
67
|
timeout=ClientTimeout(total=5000.0),
|
|
67
68
|
connector=TCPConnector(verify_ssl=False),
|
|
68
69
|
)
|
|
70
|
+
self.__max_retries = max_retries or 3
|
|
69
71
|
|
|
70
72
|
def __repr__(self) -> str:
|
|
71
73
|
return f'<{__class__.__name__} {self.__session!r}>'
|
|
@@ -74,7 +76,7 @@ class Client(CustomizableBase):
|
|
|
74
76
|
self,
|
|
75
77
|
location: str,
|
|
76
78
|
*,
|
|
77
|
-
unit: Optional[
|
|
79
|
+
unit: Optional[_Unit] = None,
|
|
78
80
|
locale: Optional[Locale] = None,
|
|
79
81
|
) -> Forecast:
|
|
80
82
|
"""
|
|
@@ -83,11 +85,12 @@ class Client(CustomizableBase):
|
|
|
83
85
|
:param location: The requested location name for said weather forecast.
|
|
84
86
|
:type location: str
|
|
85
87
|
:param unit: Overrides the unit used by this object. Defaults to the one used by this object.
|
|
86
|
-
:type unit: Optional[
|
|
88
|
+
:type unit: Optional[``_Unit``]
|
|
87
89
|
:param locale: Overrides the locale used by this object. Defaults to the one used by this object.
|
|
88
|
-
:type locale: Optional[Locale]
|
|
90
|
+
:type locale: Optional[:class:`~python_weather.enums.Locale`]
|
|
89
91
|
|
|
90
|
-
:exception Error: If the aiohttp client session used by the :class
|
|
92
|
+
:exception Error: If the aiohttp client session used by the :class:`~python_weather.client.Client` object is already closed, if the ``unit`` argument is not ``None`` and it's also not ``METRIC`` or ``IMPERIAL``, if the ``locale`` argument is not ``None`` and it's also not a part of the :class:`~python_weather.enums.Locale` enum.
|
|
93
|
+
:exception RequestError: If the :class:`~python_weather.client.Client` can't send a web request to the web server.
|
|
91
94
|
|
|
92
95
|
:returns: The requested weather forecast.
|
|
93
96
|
:rtype: Forecast
|
|
@@ -106,28 +109,32 @@ class Client(CustomizableBase):
|
|
|
106
109
|
|
|
107
110
|
subdomain = f'{locale.value}.' if locale != Locale.ENGLISH else ''
|
|
108
111
|
delay = 0.5
|
|
112
|
+
attempts = 0
|
|
109
113
|
|
|
110
114
|
while True:
|
|
111
115
|
try:
|
|
112
116
|
async with self.__session.get(
|
|
113
117
|
f'https://{subdomain}wttr.in/{quote_plus(location)}?format=j1'
|
|
114
118
|
) as resp:
|
|
119
|
+
resp.raise_for_status()
|
|
120
|
+
|
|
115
121
|
return Forecast(await resp.json(), unit, locale)
|
|
116
122
|
except Exception as err:
|
|
117
|
-
if
|
|
123
|
+
if attempts == self.__max_retries:
|
|
118
124
|
raise RequestError(err)
|
|
119
125
|
|
|
120
126
|
await sleep(delay)
|
|
127
|
+
attempts += 1
|
|
121
128
|
delay *= 2
|
|
122
129
|
|
|
123
130
|
async def close(self) -> None:
|
|
124
|
-
"""Closes the :class
|
|
131
|
+
"""Closes the :class:`~python_weather.client.Client` object. Nothing will happen if the client uses a pre-existing :class:`~aiohttp.ClientSession` or if the session is already closed."""
|
|
125
132
|
|
|
126
133
|
if self.__own_session and not self.__session.closed:
|
|
127
134
|
await self.__session.close()
|
|
128
135
|
|
|
129
|
-
async def __aenter__(self) ->
|
|
136
|
+
async def __aenter__(self) -> Self:
|
|
130
137
|
return self
|
|
131
138
|
|
|
132
|
-
async def __aexit__(self, *_, **__):
|
|
139
|
+
async def __aexit__(self, *_, **__) -> None:
|
|
133
140
|
await self.close()
|
python_weather/enums.py
CHANGED
|
@@ -23,7 +23,7 @@ SOFTWARE.
|
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
25
|
from enum import Enum
|
|
26
|
-
from typing import Union, Tuple
|
|
26
|
+
from typing import Union, Tuple, Self
|
|
27
27
|
|
|
28
28
|
from .constants import WIND_DIRECTION_EMOJIS
|
|
29
29
|
|
|
@@ -70,14 +70,14 @@ class HeatIndex(IndexedEnum):
|
|
|
70
70
|
DANGER = None
|
|
71
71
|
EXTREME_DANGER = None
|
|
72
72
|
|
|
73
|
-
def _new(celcius_index: int, true_index: int) ->
|
|
73
|
+
def _new(celcius_index: int, true_index: int) -> Self:
|
|
74
74
|
enum = HeatIndex(celcius_index)
|
|
75
75
|
enum.index = true_index
|
|
76
76
|
|
|
77
77
|
return enum
|
|
78
78
|
|
|
79
79
|
@classmethod
|
|
80
|
-
def _missing_(self, celcius_index: int) ->
|
|
80
|
+
def _missing_(self, celcius_index: int) -> Self:
|
|
81
81
|
if celcius_index <= 32:
|
|
82
82
|
return self.CAUTION
|
|
83
83
|
elif celcius_index <= 39:
|
|
@@ -99,14 +99,14 @@ class UltraViolet(BasicEnum, IndexedEnum):
|
|
|
99
99
|
VERY_HIGH = None
|
|
100
100
|
EXTREME = None
|
|
101
101
|
|
|
102
|
-
def _new(index: int) ->
|
|
102
|
+
def _new(index: int) -> Self:
|
|
103
103
|
enum = UltraViolet(index)
|
|
104
104
|
enum.index = index
|
|
105
105
|
|
|
106
106
|
return enum
|
|
107
107
|
|
|
108
108
|
@classmethod
|
|
109
|
-
def _missing_(self, index: int) ->
|
|
109
|
+
def _missing_(self, index: int) -> Self:
|
|
110
110
|
if index <= 2:
|
|
111
111
|
return self.LOW
|
|
112
112
|
elif index <= 5:
|
|
@@ -122,7 +122,7 @@ class UltraViolet(BasicEnum, IndexedEnum):
|
|
|
122
122
|
class WindDirection(BasicEnum):
|
|
123
123
|
"""Represents a wind direction."""
|
|
124
124
|
|
|
125
|
-
__slots__: Tuple[str, ...] = ('
|
|
125
|
+
__slots__: Tuple[str, ...] = ('degrees',)
|
|
126
126
|
|
|
127
127
|
NORTH = 'N'
|
|
128
128
|
NORTH_NORTHEAST = 'NNE'
|
|
@@ -141,9 +141,12 @@ class WindDirection(BasicEnum):
|
|
|
141
141
|
NORTHWEST = 'NW'
|
|
142
142
|
NORTH_NORTHWEST = 'NNW'
|
|
143
143
|
|
|
144
|
-
|
|
144
|
+
degrees: float
|
|
145
|
+
"""The wind direction's value in degrees."""
|
|
146
|
+
|
|
147
|
+
def _new(value: str, degrees: float) -> Self:
|
|
145
148
|
enum = WindDirection(value)
|
|
146
|
-
enum.
|
|
149
|
+
enum.degrees = degrees
|
|
147
150
|
|
|
148
151
|
return enum
|
|
149
152
|
|
|
@@ -183,20 +186,17 @@ class WindDirection(BasicEnum):
|
|
|
183
186
|
else:
|
|
184
187
|
return 326.25 < other <= 348.75
|
|
185
188
|
|
|
186
|
-
def
|
|
187
|
-
return self.
|
|
188
|
-
|
|
189
|
-
@property
|
|
190
|
-
def degrees(self) -> int:
|
|
191
|
-
"""The wind direction's value in degrees."""
|
|
189
|
+
def __int__(self) -> int:
|
|
190
|
+
return int(self.degrees)
|
|
192
191
|
|
|
193
|
-
|
|
192
|
+
def __float__(self) -> float:
|
|
193
|
+
return self.degrees
|
|
194
194
|
|
|
195
195
|
@property
|
|
196
196
|
def emoji(self) -> str:
|
|
197
197
|
"""The emoji representing this enum."""
|
|
198
198
|
|
|
199
|
-
return WIND_DIRECTION_EMOJIS[int(((self.
|
|
199
|
+
return WIND_DIRECTION_EMOJIS[int(((self.degrees + 22.5) % 360) / 45.0)]
|
|
200
200
|
|
|
201
201
|
|
|
202
202
|
class Locale(Enum):
|
|
@@ -310,39 +310,30 @@ class Kind(BasicEnum):
|
|
|
310
310
|
THUNDERY_SNOW_SHOWERS = 392
|
|
311
311
|
|
|
312
312
|
@classmethod
|
|
313
|
-
def _missing_(self, value: int) ->
|
|
314
|
-
if value
|
|
313
|
+
def _missing_(self, value: int) -> Self:
|
|
314
|
+
if value in (248, 260):
|
|
315
315
|
return self.FOG
|
|
316
|
-
elif value
|
|
316
|
+
elif value in (263, 353):
|
|
317
317
|
return self.LIGHT_SHOWERS
|
|
318
|
-
elif value
|
|
318
|
+
elif value in (362, 365, 374):
|
|
319
319
|
return self.LIGHT_SLEET_SHOWERS
|
|
320
|
-
elif (
|
|
321
|
-
value == 185
|
|
322
|
-
or value == 281
|
|
323
|
-
or value == 284
|
|
324
|
-
or value == 311
|
|
325
|
-
or value == 314
|
|
326
|
-
or value == 317
|
|
327
|
-
or value == 350
|
|
328
|
-
or value == 377
|
|
329
|
-
):
|
|
320
|
+
elif value in (185, 281, 284, 311, 314, 317, 350, 377):
|
|
330
321
|
return self.LIGHT_SLEET
|
|
331
322
|
elif value == 386:
|
|
332
323
|
return self.THUNDERY_SHOWERS
|
|
333
324
|
elif value == 320:
|
|
334
325
|
return self.LIGHT_SNOW
|
|
335
|
-
elif value
|
|
326
|
+
elif value in (329, 332, 338):
|
|
336
327
|
return self.HEAVY_SNOW
|
|
337
|
-
elif value
|
|
328
|
+
elif value in (293, 296):
|
|
338
329
|
return self.LIGHT_RAIN
|
|
339
|
-
elif value
|
|
330
|
+
elif value in (305, 356):
|
|
340
331
|
return self.HEAVY_SHOWERS
|
|
341
|
-
elif value
|
|
332
|
+
elif value in (308, 359):
|
|
342
333
|
return self.HEAVY_RAIN
|
|
343
|
-
elif value
|
|
334
|
+
elif value in (326, 368):
|
|
344
335
|
return self.LIGHT_SNOW_SHOWERS
|
|
345
|
-
elif value
|
|
336
|
+
elif value in (371, 395):
|
|
346
337
|
return self.HEAVY_SNOW_SHOWERS
|
|
347
338
|
|
|
348
339
|
@property
|
python_weather/errors.py
CHANGED
|
@@ -32,12 +32,12 @@ class Error(Exception):
|
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
class RequestError(Error):
|
|
35
|
-
"""Thrown upon HTTP request failure. Extends :class
|
|
35
|
+
"""Thrown upon HTTP request failure. Extends :class:`~python_weather.errors.Error`."""
|
|
36
36
|
|
|
37
37
|
__slots__: Tuple[str, ...] = ('source',)
|
|
38
38
|
|
|
39
39
|
source: Exception
|
|
40
|
-
"""The :class:`Exception` instance causing this exception."""
|
|
40
|
+
"""The :py:class:`Exception` instance causing this exception."""
|
|
41
41
|
|
|
42
42
|
def __init__(self, source: Exception):
|
|
43
43
|
self.source = source
|
python_weather/forecast.py
CHANGED
|
@@ -25,7 +25,7 @@ SOFTWARE.
|
|
|
25
25
|
from typing import Iterable, Optional, Tuple, List
|
|
26
26
|
from datetime import datetime, date, time
|
|
27
27
|
|
|
28
|
-
from .base import BaseForecast
|
|
28
|
+
from .base import BaseForecast
|
|
29
29
|
from .enums import Phase, Locale, HeatIndex
|
|
30
30
|
from .constants import _Unit, LATLON_REGEX
|
|
31
31
|
|
|
@@ -33,318 +33,264 @@ from .constants import _Unit, LATLON_REGEX
|
|
|
33
33
|
class HourlyForecast(BaseForecast):
|
|
34
34
|
"""Represents a weather forecast of a specific hour."""
|
|
35
35
|
|
|
36
|
-
__slots__: Tuple[str, ...] = (
|
|
36
|
+
__slots__: Tuple[str, ...] = (
|
|
37
|
+
'chances_of_fog',
|
|
38
|
+
'chances_of_frost',
|
|
39
|
+
'chances_of_high_temperature',
|
|
40
|
+
'chances_of_overcast',
|
|
41
|
+
'chances_of_rain',
|
|
42
|
+
'chances_of_remaining_dry',
|
|
43
|
+
'chances_of_snow',
|
|
44
|
+
'chances_of_sunshine',
|
|
45
|
+
'chances_of_thunder',
|
|
46
|
+
'chances_of_windy',
|
|
47
|
+
'cloud_cover',
|
|
48
|
+
'time',
|
|
49
|
+
'dew_point',
|
|
50
|
+
'heat_index',
|
|
51
|
+
'wind_chill',
|
|
52
|
+
'wind_gust',
|
|
53
|
+
)
|
|
37
54
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if 'temp_C' not in json:
|
|
41
|
-
json['temp_C'] = json.pop('tempC')
|
|
42
|
-
if 'temp_F' not in json:
|
|
43
|
-
json['temp_F'] = json.pop('tempF')
|
|
44
|
-
|
|
45
|
-
super().__init__(json, unit, locale)
|
|
46
|
-
|
|
47
|
-
def __repr__(self) -> str:
|
|
48
|
-
return f'<{__class__.__name__} time={self.time!r} temperature={self.temperature!r} description={self.description!r} kind={self.kind!r}>'
|
|
49
|
-
|
|
50
|
-
@property
|
|
51
|
-
def dew_point(self) -> int:
|
|
52
|
-
"""The dew point in either celcius or fahrenheit."""
|
|
55
|
+
chances_of_fog: int
|
|
56
|
+
"""Chances of a fog in percent."""
|
|
53
57
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
@property
|
|
59
|
-
def heat_index(self) -> HeatIndex:
|
|
60
|
-
"""The heat index in either celcius or fahrenheit."""
|
|
61
|
-
|
|
62
|
-
celcius_index = int(self._BaseForecast__inner['HeatIndexC'])
|
|
63
|
-
|
|
64
|
-
return HeatIndex._new(
|
|
65
|
-
celcius_index,
|
|
66
|
-
int(
|
|
67
|
-
self._BaseForecast__inner[
|
|
68
|
-
f'HeatIndex{self._CustomizableBase__unit.temperature}'
|
|
69
|
-
]
|
|
70
|
-
),
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
@property
|
|
74
|
-
def wind_chill(self) -> int:
|
|
75
|
-
"""The wind chill value in either celcius or fahrenheit."""
|
|
76
|
-
|
|
77
|
-
return int(
|
|
78
|
-
self._BaseForecast__inner[f'WindChill{self._CustomizableBase__unit.temperature}']
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
@property
|
|
82
|
-
def wind_gust(self) -> int:
|
|
83
|
-
"""The wind gust value in either kilometers/hour or miles/hour."""
|
|
84
|
-
|
|
85
|
-
return int(
|
|
86
|
-
self._BaseForecast__inner[f'WindGust{self._CustomizableBase__unit.velocity}']
|
|
87
|
-
)
|
|
58
|
+
chances_of_frost: int
|
|
59
|
+
"""Chances of a frost in percent."""
|
|
88
60
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
"""Chances of a fog in percent."""
|
|
61
|
+
chances_of_high_temperature: int
|
|
62
|
+
"""Chances of a high temperature in percent."""
|
|
92
63
|
|
|
93
|
-
|
|
64
|
+
chances_of_overcast: int
|
|
65
|
+
"""Chances of an overcast in percent."""
|
|
94
66
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
"""Chances of a frost in percent."""
|
|
67
|
+
chances_of_rain: int
|
|
68
|
+
"""Chances of a rain in percent."""
|
|
98
69
|
|
|
99
|
-
|
|
70
|
+
chances_of_remaining_dry: int
|
|
71
|
+
"""Chances of remaining dry in percent."""
|
|
100
72
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
"""Chances of a high temperature in percent."""
|
|
73
|
+
chances_of_snow: int
|
|
74
|
+
"""Chances of a snow in percent."""
|
|
104
75
|
|
|
105
|
-
|
|
76
|
+
chances_of_sunshine: int
|
|
77
|
+
"""Chances of a sunshine in percent."""
|
|
106
78
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
"""Chances of an overcast in percent."""
|
|
79
|
+
chances_of_thunder: int
|
|
80
|
+
"""Chances of a thunder in percent."""
|
|
110
81
|
|
|
111
|
-
|
|
82
|
+
chances_of_windy: int
|
|
83
|
+
"""Chances of windy in percent."""
|
|
112
84
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
"""Chances of a rain in percent."""
|
|
85
|
+
cloud_cover: int
|
|
86
|
+
"""The cloud cover value in percent."""
|
|
116
87
|
|
|
117
|
-
|
|
88
|
+
time: 'time'
|
|
89
|
+
"""The local time in hours and minutes."""
|
|
118
90
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
"""Chances of remaining dry in percent."""
|
|
91
|
+
dew_point: int
|
|
92
|
+
"""The dew point in either celcius or fahrenheit."""
|
|
122
93
|
|
|
123
|
-
|
|
94
|
+
heat_index: HeatIndex
|
|
95
|
+
"""The heat index in either celcius or fahrenheit."""
|
|
124
96
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
"""Chances of a snow in percent."""
|
|
97
|
+
wind_chill: int
|
|
98
|
+
"""The wind chill value in either celcius or fahrenheit."""
|
|
128
99
|
|
|
129
|
-
|
|
100
|
+
wind_gust: int
|
|
101
|
+
"""The wind gust value in either kilometers/hour or miles/hour."""
|
|
130
102
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
@property
|
|
138
|
-
def chances_of_thunder(self) -> int:
|
|
139
|
-
"""Chances of a thunder in percent."""
|
|
140
|
-
|
|
141
|
-
return int(self._BaseForecast__inner['chanceofthunder'])
|
|
142
|
-
|
|
143
|
-
@property
|
|
144
|
-
def chances_of_windy(self) -> int:
|
|
145
|
-
"""Chances of windy in percent."""
|
|
146
|
-
|
|
147
|
-
return int(self._BaseForecast__inner['chanceofwindy'])
|
|
148
|
-
|
|
149
|
-
@property
|
|
150
|
-
def cloud_cover(self) -> int:
|
|
151
|
-
"""The cloud cover value in percent."""
|
|
152
|
-
|
|
153
|
-
return int(self._BaseForecast__inner['cloudcover'])
|
|
154
|
-
|
|
155
|
-
@property
|
|
156
|
-
def time(self) -> time:
|
|
157
|
-
"""The local time in hours and minutes."""
|
|
103
|
+
def __init__(self, json: dict, unit: _Unit, locale: Locale):
|
|
104
|
+
# for inheritance purposes
|
|
105
|
+
if 'temp_C' not in json:
|
|
106
|
+
json['temp_C'] = json.pop('tempC')
|
|
107
|
+
if 'temp_F' not in json:
|
|
108
|
+
json['temp_F'] = json.pop('tempF')
|
|
158
109
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
110
|
+
celcius_index = int(json['HeatIndexC'])
|
|
111
|
+
t = json['time']
|
|
112
|
+
|
|
113
|
+
self.chances_of_fog = int(json['chanceoffog'])
|
|
114
|
+
self.chances_of_frost = int(json['chanceoffrost'])
|
|
115
|
+
self.chances_of_high_temperature = int(json['chanceofhightemp'])
|
|
116
|
+
self.chances_of_overcast = int(json['chanceofovercast'])
|
|
117
|
+
self.chances_of_rain = int(json['chanceofrain'])
|
|
118
|
+
self.chances_of_remaining_dry = int(json['chanceofremdry'])
|
|
119
|
+
self.chances_of_snow = int(json['chanceofsnow'])
|
|
120
|
+
self.chances_of_sunshine = int(json['chanceofsunshine'])
|
|
121
|
+
self.chances_of_thunder = int(json['chanceofthunder'])
|
|
122
|
+
self.chances_of_windy = int(json['chanceofwindy'])
|
|
123
|
+
self.cloud_cover = int(json['cloudcover'])
|
|
124
|
+
self.time = time() if len(t) < 3 else datetime.strptime(t, '%H%M').time()
|
|
125
|
+
self.dew_point = int(json[f'DewPoint{unit.temperature}'])
|
|
126
|
+
self.heat_index = HeatIndex._new(
|
|
127
|
+
celcius_index,
|
|
128
|
+
int(json[f'HeatIndex{unit.temperature}']),
|
|
163
129
|
)
|
|
130
|
+
self.wind_chill = int(json[f'WindChill{unit.temperature}'])
|
|
131
|
+
self.wind_gust = int(json[f'WindGust{unit.velocity}'])
|
|
164
132
|
|
|
133
|
+
super().__init__(json, unit, locale)
|
|
165
134
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
def __init__(self, json: dict, unit: _Unit, locale: Locale):
|
|
170
|
-
self.__astronomy = json.pop('astronomy')[0]
|
|
171
|
-
self.__inner = json
|
|
135
|
+
def __repr__(self) -> str:
|
|
136
|
+
return f'<{__class__.__name__} time={self.time!r} temperature={self.temperature!r} description={self.description!r} kind={self.kind!r}>'
|
|
172
137
|
|
|
173
|
-
super().__init__(unit, locale)
|
|
174
138
|
|
|
175
|
-
|
|
176
|
-
|
|
139
|
+
class DailyForecast:
|
|
140
|
+
__slots__: Tuple[str, ...] = (
|
|
141
|
+
'moon_illumination',
|
|
142
|
+
'moon_phase',
|
|
143
|
+
'moonrise',
|
|
144
|
+
'moonset',
|
|
145
|
+
'sunrise',
|
|
146
|
+
'sunset',
|
|
147
|
+
'date',
|
|
148
|
+
'sunlight',
|
|
149
|
+
'lowest_temperature',
|
|
150
|
+
'highest_temperature',
|
|
151
|
+
'temperature',
|
|
152
|
+
'snowfall',
|
|
153
|
+
'hourly_forecasts',
|
|
154
|
+
)
|
|
177
155
|
|
|
178
|
-
|
|
179
|
-
|
|
156
|
+
moon_illumination: int
|
|
157
|
+
"""The percentage of the moon illuminated."""
|
|
180
158
|
|
|
181
|
-
|
|
182
|
-
|
|
159
|
+
moon_phase: Phase
|
|
160
|
+
"""The moon's phase."""
|
|
183
161
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
"""The percentage of the moon illuminated."""
|
|
162
|
+
moonrise: Optional[time]
|
|
163
|
+
"""The local time when the moon rises. This can be ``None``."""
|
|
187
164
|
|
|
188
|
-
|
|
165
|
+
moonset: Optional[time]
|
|
166
|
+
"""The local time when the moon sets. This can be ``None``."""
|
|
189
167
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
"""The moon's phase."""
|
|
168
|
+
sunrise: Optional[time]
|
|
169
|
+
"""The local time when the sun rises. This can be ``None``."""
|
|
193
170
|
|
|
194
|
-
|
|
171
|
+
sunset: Optional[time]
|
|
172
|
+
"""The local time when the sun sets. This can be ``None``."""
|
|
195
173
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
"""The local time when the moon rises. This can be ``None``."""
|
|
174
|
+
date: 'date'
|
|
175
|
+
"""The local date of this forecast."""
|
|
199
176
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
except ValueError:
|
|
203
|
-
...
|
|
177
|
+
sunlight: float
|
|
178
|
+
"""Hours of sunlight."""
|
|
204
179
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
"""The local time when the moon sets. This can be ``None``."""
|
|
180
|
+
lowest_temperature: int
|
|
181
|
+
"""The lowest temperature in either celcius or fahrenheit."""
|
|
208
182
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
except ValueError:
|
|
212
|
-
...
|
|
183
|
+
highest_temperature: int
|
|
184
|
+
"""The highest temperature in either celcius or fahrenheit."""
|
|
213
185
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
"""The local time when the sun rises. This can be ``None``."""
|
|
186
|
+
temperature: int
|
|
187
|
+
"""The average temperature in either celcius or fahrenheit."""
|
|
217
188
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
except ValueError:
|
|
221
|
-
...
|
|
189
|
+
snowfall: float
|
|
190
|
+
"""Total snowfall in either centimeters or inches."""
|
|
222
191
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
"""The local time when the sun sets. This can be ``None``."""
|
|
192
|
+
hourly_forecasts: List[HourlyForecast]
|
|
193
|
+
"""The hourly forecasts of this day."""
|
|
226
194
|
|
|
195
|
+
def __init__(self, json: dict, unit: _Unit, locale: Locale):
|
|
196
|
+
astronomy = json['astronomy'][0]
|
|
197
|
+
|
|
198
|
+
self.moon_illumination = int(astronomy['moon_illumination'])
|
|
199
|
+
self.moon_phase = Phase(astronomy['moon_phase'])
|
|
200
|
+
self.moonrise = __class__.__parse_time(astronomy['moonrise'])
|
|
201
|
+
self.moonset = __class__.__parse_time(astronomy['moonset'])
|
|
202
|
+
self.sunrise = __class__.__parse_time(astronomy['sunrise'])
|
|
203
|
+
self.sunset = __class__.__parse_time(astronomy['sunset'])
|
|
204
|
+
self.date = datetime.strptime(json['date'], '%Y-%m-%d').date()
|
|
205
|
+
self.sunlight = float(json['sunHour'])
|
|
206
|
+
self.lowest_temperature = int(json[f'mintemp{unit.temperature}'])
|
|
207
|
+
self.highest_temperature = int(json[f'maxtemp{unit.temperature}'])
|
|
208
|
+
self.temperature = int(json[f'avgtemp{unit.temperature}'])
|
|
209
|
+
self.snowfall = float(json['totalSnow_cm']) / unit.cm_divisor
|
|
210
|
+
self.hourly_forecasts = [
|
|
211
|
+
HourlyForecast(elem, unit, locale) for elem in json['hourly']
|
|
212
|
+
]
|
|
213
|
+
|
|
214
|
+
@staticmethod
|
|
215
|
+
def __parse_time(timestamp: str) -> Optional[time]:
|
|
227
216
|
try:
|
|
228
|
-
return datetime.strptime(
|
|
217
|
+
return datetime.strptime(timestamp, '%I:%M %p').time()
|
|
229
218
|
except ValueError:
|
|
230
219
|
...
|
|
231
220
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
"""The local date of this forecast."""
|
|
221
|
+
def __repr__(self) -> str:
|
|
222
|
+
return f'<{__class__.__name__} date={self.date!r} temperature={self.temperature!r}>'
|
|
235
223
|
|
|
236
|
-
|
|
224
|
+
def __len__(self) -> int:
|
|
225
|
+
return len(self.hourly_forecasts)
|
|
237
226
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
"""The lowest temperature in either celcius or fahrenheit."""
|
|
227
|
+
def __iter__(self) -> Iterable[HourlyForecast]:
|
|
228
|
+
return iter(self.hourly_forecasts)
|
|
241
229
|
|
|
242
|
-
return int(self.__inner[f'mintemp{self._CustomizableBase__unit.temperature}'])
|
|
243
230
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
"""The highest temperature in either celcius or fahrenheit."""
|
|
231
|
+
class Forecast(BaseForecast):
|
|
232
|
+
"""Represents today's weather forecast, alongside daily and hourly weather forecasts."""
|
|
247
233
|
|
|
248
|
-
|
|
234
|
+
__slots__: Tuple[str, ...] = (
|
|
235
|
+
'local_population',
|
|
236
|
+
'region',
|
|
237
|
+
'location',
|
|
238
|
+
'country',
|
|
239
|
+
'datetime',
|
|
240
|
+
'coordinates',
|
|
241
|
+
'daily_forecasts',
|
|
242
|
+
)
|
|
249
243
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
"""The average temperature in either celcius or fahrenheit."""
|
|
244
|
+
local_population: int
|
|
245
|
+
"""The local population count."""
|
|
253
246
|
|
|
254
|
-
|
|
247
|
+
region: str
|
|
248
|
+
"""The local region's name."""
|
|
255
249
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
"""Hours of sunlight."""
|
|
250
|
+
location: str
|
|
251
|
+
"""The location's name."""
|
|
259
252
|
|
|
260
|
-
|
|
253
|
+
country: str
|
|
254
|
+
"""The local country's name."""
|
|
261
255
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
"""Total snowfall in either centimeters or inches."""
|
|
256
|
+
datetime: 'datetime'
|
|
257
|
+
"""The local date and time of this weather forecast."""
|
|
265
258
|
|
|
266
|
-
|
|
259
|
+
coordinates: Tuple[float, float]
|
|
260
|
+
"""A tuple of this forecast's latitude and longitude."""
|
|
267
261
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
"""The hourly forecasts of this day."""
|
|
262
|
+
daily_forecasts: List[DailyForecast]
|
|
263
|
+
"""Daily weather forecasts in this location."""
|
|
271
264
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
)
|
|
265
|
+
def __init__(self, json: dict, unit: _Unit, locale: Locale):
|
|
266
|
+
current = json['current_condition'][0]
|
|
267
|
+
nearest = json['nearest_area'][0]
|
|
276
268
|
|
|
269
|
+
self.local_population = int(nearest['population'])
|
|
270
|
+
self.region = nearest['region'][0]['value']
|
|
271
|
+
self.location = nearest['areaName'][0]['value']
|
|
272
|
+
self.country = nearest['country'][0]['value']
|
|
273
|
+
self.datetime = datetime.strptime(current['localObsDateTime'], '%Y-%m-%d %I:%M %p')
|
|
277
274
|
|
|
278
|
-
|
|
279
|
-
|
|
275
|
+
try:
|
|
276
|
+
req = next(filter(lambda x: x['type'] == 'LatLon', json['request']))
|
|
277
|
+
match = LATLON_REGEX.match(req['query'])
|
|
280
278
|
|
|
281
|
-
|
|
279
|
+
self.coordinates = (float(match[1]), float(match[2]))
|
|
280
|
+
except:
|
|
281
|
+
self.coordinates = (float(nearest['latitude']), float(nearest['longitude']))
|
|
282
282
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
self.__inner = json
|
|
283
|
+
self.daily_forecasts = [
|
|
284
|
+
DailyForecast(elem, unit, locale) for elem in json['weather']
|
|
285
|
+
]
|
|
287
286
|
|
|
288
287
|
super().__init__(current, unit, locale)
|
|
289
288
|
|
|
290
289
|
def __repr__(self) -> str:
|
|
291
290
|
return f'<{__class__.__name__} location={self.location!r} datetime={self.datetime!r} temperature={self.temperature!r}>'
|
|
292
291
|
|
|
293
|
-
def
|
|
294
|
-
return self.daily_forecasts
|
|
295
|
-
|
|
296
|
-
def __list__(self) -> List[DailyForecast]:
|
|
297
|
-
return list(iter(self))
|
|
298
|
-
|
|
299
|
-
@property
|
|
300
|
-
def local_population(self) -> int:
|
|
301
|
-
"""The local population count."""
|
|
302
|
-
|
|
303
|
-
return int(self.__nearest['population'])
|
|
304
|
-
|
|
305
|
-
@property
|
|
306
|
-
def region(self) -> str:
|
|
307
|
-
"""The local region's name."""
|
|
308
|
-
|
|
309
|
-
return self.__nearest['region'][0]['value']
|
|
310
|
-
|
|
311
|
-
@property
|
|
312
|
-
def location(self) -> str:
|
|
313
|
-
"""The location's name."""
|
|
314
|
-
|
|
315
|
-
return self.__nearest['areaName'][0]['value']
|
|
316
|
-
|
|
317
|
-
@property
|
|
318
|
-
def country(self) -> str:
|
|
319
|
-
"""The local country's name."""
|
|
320
|
-
|
|
321
|
-
return self.__nearest['country'][0]['value']
|
|
322
|
-
|
|
323
|
-
@property
|
|
324
|
-
def datetime(self) -> datetime:
|
|
325
|
-
"""The local date and time of this weather forecast."""
|
|
292
|
+
def __len__(self) -> int:
|
|
293
|
+
return len(self.daily_forecasts)
|
|
326
294
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
)
|
|
330
|
-
|
|
331
|
-
@property
|
|
332
|
-
def daily_forecasts(self) -> Iterable[DailyForecast]:
|
|
333
|
-
"""Daily weather forecasts in this location."""
|
|
334
|
-
|
|
335
|
-
return (
|
|
336
|
-
DailyForecast(elem, self._CustomizableBase__unit, self._CustomizableBase__locale)
|
|
337
|
-
for elem in self.__inner['weather']
|
|
338
|
-
)
|
|
339
|
-
|
|
340
|
-
@property
|
|
341
|
-
def coordinates(self) -> Tuple[float, float]:
|
|
342
|
-
"""A tuple of this forecast's latitude and longitude."""
|
|
343
|
-
|
|
344
|
-
try:
|
|
345
|
-
req = next(filter(lambda x: x['type'] == 'LatLon', self.__inner['request']))
|
|
346
|
-
match = LATLON_REGEX.match(req['query'])
|
|
347
|
-
|
|
348
|
-
return float(match[1]), float(match[2])
|
|
349
|
-
except:
|
|
350
|
-
return float(self.__nearest['latitude']), float(self.__nearest['longitude'])
|
|
295
|
+
def __iter__(self) -> Iterable[DailyForecast]:
|
|
296
|
+
return iter(self.daily_forecasts)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-weather
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.6
|
|
4
4
|
Summary: A free and asynchronous weather API wrapper made in Python, for Python.
|
|
5
5
|
Author: null8626
|
|
6
6
|
License: MIT
|
|
@@ -11,12 +11,11 @@ Project-URL: Donate via GitHub Sponsors, https://github.com/sponsors/null8626
|
|
|
11
11
|
Project-URL: Donate via Ko-fi, https://ko-fi.com/null8626
|
|
12
12
|
Keywords: weather,forecast,weather-api,weather-forecast
|
|
13
13
|
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
-
Classifier: Framework :: aiohttp
|
|
15
|
-
Classifier: Framework :: AsyncIO
|
|
16
14
|
Classifier: Intended Audience :: Education
|
|
17
15
|
Classifier: Intended Audience :: Developers
|
|
18
16
|
Classifier: Intended Audience :: Information Technology
|
|
19
17
|
Classifier: Natural Language :: English
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
20
19
|
Classifier: Topic :: Education
|
|
21
20
|
Classifier: Topic :: Internet
|
|
22
21
|
Classifier: Topic :: Scientific/Engineering
|
|
@@ -32,10 +31,11 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
32
31
|
Classifier: Programming Language :: Python :: 3.10
|
|
33
32
|
Classifier: Programming Language :: Python :: 3.11
|
|
34
33
|
Classifier: Programming Language :: Python :: 3.12
|
|
34
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
35
35
|
Requires-Python: >=3.8
|
|
36
36
|
Description-Content-Type: text/markdown
|
|
37
37
|
License-File: LICENSE
|
|
38
|
-
Requires-Dist: aiohttp >=3.10.
|
|
38
|
+
Requires-Dist: aiohttp >=3.10.10
|
|
39
39
|
|
|
40
40
|
# [python-weather][pypi-url] [![pypi][pypi-image]][pypi-url] [![downloads][downloads-image]][pypi-url] [![codacy-badge][codacy-url]][codacy-image] [![ko-fi][ko-fi-brief-image]][ko-fi-url]
|
|
41
41
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
python_weather/__init__.py,sha256=2MqLVKS6sre_Q-2YqV-gNDpJkbT5G2rAGGiz4JgeQ24,1586
|
|
2
|
+
python_weather/base.py,sha256=LU_Es_vMWKiptMeLV2CClmY-GEm54J528g8LfHxw0yw,4684
|
|
3
|
+
python_weather/client.py,sha256=w2tp8Mip2FFVAgq9fIMozjpVwptBLihSo04C2-UwDHw,5567
|
|
4
|
+
python_weather/constants.py,sha256=NzfQkxmAPEYAbUHMBUEi9-4XOq0z8FYj1_G_1z5Of-4,2015
|
|
5
|
+
python_weather/enums.py,sha256=9lMyxQjJBHbAu_ceX4maunbDCk4AWjbT8vTUgpBMsVQ,10375
|
|
6
|
+
python_weather/errors.py,sha256=VmRYlHx6s1SMLZf8J7fEkp64RbEAxGibqW9ZxkQdx6c,1600
|
|
7
|
+
python_weather/forecast.py,sha256=gNdvMisnjIiNyQQfgImltDPhgB6900PTeCl87hZ_gkI,9124
|
|
8
|
+
python_weather-2.0.6.dist-info/LICENSE,sha256=OhQiuzPzGigVn4yKdwlDJMfyglio438lXJU5ny4WCGk,1080
|
|
9
|
+
python_weather-2.0.6.dist-info/METADATA,sha256=xCzycdBmD5XDMyAiQhOrppFO8RmHA8tZ4w8YU4tuhwM,4130
|
|
10
|
+
python_weather-2.0.6.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
11
|
+
python_weather-2.0.6.dist-info/top_level.txt,sha256=NDqLSO3dHB1tjjiNJfEg-a9_eoOmawAZynZsy9Yu48w,15
|
|
12
|
+
python_weather-2.0.6.dist-info/RECORD,,
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
python_weather/__init__.py,sha256=FbHn9_8A63GXbpFmiyPalMPynvtcurGVX8zZk96VNZs,1586
|
|
2
|
-
python_weather/base.py,sha256=esxcbl0nf7ExIczzG0cpvTKAFFKEKRtprj5sU_6wBqQ,5031
|
|
3
|
-
python_weather/client.py,sha256=k9PXBUhQIGOLluNasoOpMhvKMblOfGqZOnGE1lDU_Is,5026
|
|
4
|
-
python_weather/constants.py,sha256=NzfQkxmAPEYAbUHMBUEi9-4XOq0z8FYj1_G_1z5Of-4,2015
|
|
5
|
-
python_weather/enums.py,sha256=e5O1rbyQD42vv4ATDJP4z32UPbPn5UVytNdtycYhBOE,10650
|
|
6
|
-
python_weather/errors.py,sha256=sbCEmzXvC_jxyCVJ8aBVOwIMU3FSM4d0Myu2rYUWaI8,1574
|
|
7
|
-
python_weather/forecast.py,sha256=iFoGBhrVELHzbsuJsHcH9xM5FHcbQF8JiRhR74b4yfo,10101
|
|
8
|
-
python_weather-2.0.4.dist-info/LICENSE,sha256=OhQiuzPzGigVn4yKdwlDJMfyglio438lXJU5ny4WCGk,1080
|
|
9
|
-
python_weather-2.0.4.dist-info/METADATA,sha256=1IgvugBGriUP0sYZIePFw1Mo4WgnGvOBYbq_Kl6iUjQ,4097
|
|
10
|
-
python_weather-2.0.4.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
11
|
-
python_weather-2.0.4.dist-info/top_level.txt,sha256=NDqLSO3dHB1tjjiNJfEg-a9_eoOmawAZynZsy9Yu48w,15
|
|
12
|
-
python_weather-2.0.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|