python-weather 2.1.1__tar.gz → 2.2.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.
- {python_weather-2.1.1 → python_weather-2.2.0}/LICENSE +1 -1
- {python_weather-2.1.1/python_weather.egg-info → python_weather-2.2.0}/PKG-INFO +3 -3
- {python_weather-2.1.1 → python_weather-2.2.0}/pyproject.toml +3 -3
- python_weather-2.2.0/python_weather/__init__.py +34 -0
- python_weather-2.2.0/python_weather/base.py +82 -0
- {python_weather-2.1.1 → python_weather-2.2.0}/python_weather/client.py +66 -36
- python_weather-2.2.0/python_weather/constants.py +72 -0
- {python_weather-2.1.1 → python_weather-2.2.0}/python_weather/enums.py +37 -119
- python_weather-2.2.0/python_weather/errors.py +30 -0
- {python_weather-2.1.1 → python_weather-2.2.0}/python_weather/forecast.py +9 -23
- python_weather-2.2.0/python_weather/version.py +4 -0
- {python_weather-2.1.1 → python_weather-2.2.0/python_weather.egg-info}/PKG-INFO +3 -3
- python_weather-2.2.0/python_weather.egg-info/requires.txt +1 -0
- python_weather-2.1.1/python_weather/__init__.py +0 -55
- python_weather-2.1.1/python_weather/base.py +0 -156
- python_weather-2.1.1/python_weather/constants.py +0 -69
- python_weather-2.1.1/python_weather/errors.py +0 -50
- python_weather-2.1.1/python_weather/version.py +0 -1
- python_weather-2.1.1/python_weather.egg-info/requires.txt +0 -1
- {python_weather-2.1.1 → python_weather-2.2.0}/MANIFEST.in +0 -0
- {python_weather-2.1.1 → python_weather-2.2.0}/README.md +0 -0
- {python_weather-2.1.1 → python_weather-2.2.0}/python_weather.egg-info/SOURCES.txt +0 -0
- {python_weather-2.1.1 → python_weather-2.2.0}/python_weather.egg-info/dependency_links.txt +0 -0
- {python_weather-2.1.1 → python_weather-2.2.0}/python_weather.egg-info/top_level.txt +0 -0
- {python_weather-2.1.1 → python_weather-2.2.0}/setup.cfg +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-weather
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: A free and asynchronous weather API wrapper made in Python, for Python.
|
|
5
5
|
Author: null8626
|
|
6
|
-
License: MIT
|
|
6
|
+
License-Expression: MIT
|
|
7
7
|
Project-URL: Documentation, https://python-weather.readthedocs.io/en/latest/
|
|
8
8
|
Project-URL: Repository, https://github.com/null8626/python-weather
|
|
9
9
|
Project-URL: Changelog, https://python-weather.readthedocs.io/en/latest/changelog.html
|
|
@@ -33,7 +33,7 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
33
33
|
Requires-Python: >=3.10
|
|
34
34
|
Description-Content-Type: text/markdown
|
|
35
35
|
License-File: LICENSE
|
|
36
|
-
Requires-Dist: aiohttp>=3.13.
|
|
36
|
+
Requires-Dist: aiohttp>=3.13.5
|
|
37
37
|
Dynamic: license-file
|
|
38
38
|
|
|
39
39
|
# [python-weather][pypi-url] [![pypi][pypi-image]][pypi-url] [![pypi downloads][pypi-downloads-image]][pypi-url] [![codacy-badge][codacy-image]][codacy-url] [![codecov-badge][codecov-image]][codecov-url] [![ko-fi][ko-fi-brief-image]][ko-fi-url]
|
|
@@ -3,13 +3,13 @@ requires = ["setuptools"]
|
|
|
3
3
|
|
|
4
4
|
[project]
|
|
5
5
|
name = "python-weather"
|
|
6
|
-
version = "2.
|
|
6
|
+
version = "2.2.0"
|
|
7
7
|
description = "A free and asynchronous weather API wrapper made in Python, for Python."
|
|
8
8
|
readme = "README.md"
|
|
9
|
-
license =
|
|
9
|
+
license = "MIT"
|
|
10
10
|
authors = [{ name = "null8626" }]
|
|
11
11
|
keywords = ["weather", "forecast", "weather-forecast"]
|
|
12
|
-
dependencies = ["aiohttp>=3.13.
|
|
12
|
+
dependencies = ["aiohttp>=3.13.5"]
|
|
13
13
|
classifiers = [
|
|
14
14
|
"Development Status :: 5 - Production/Stable",
|
|
15
15
|
"Intended Audience :: Education",
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# SPDX-FileCopyrightText: 2021-2026 null8626
|
|
3
|
+
|
|
4
|
+
from .enums import HeatIndex, Kind, Locale, Phase, UltraViolet, WindDirection
|
|
5
|
+
from .constants import METRIC, IMPERIAL
|
|
6
|
+
from .errors import Error, RequestError
|
|
7
|
+
from .forecast import Forecast
|
|
8
|
+
from .version import VERSION
|
|
9
|
+
from .client import Client
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
__title__ = 'python-weather'
|
|
13
|
+
__author__ = 'null8626'
|
|
14
|
+
__credits__ = (__author__,)
|
|
15
|
+
__maintainer__ = __author__
|
|
16
|
+
__status__ = 'Production'
|
|
17
|
+
__license__ = 'MIT'
|
|
18
|
+
__copyright__ = 'Copyright (c) 2021-2026 null8626'
|
|
19
|
+
__version__ = VERSION
|
|
20
|
+
__all__ = (
|
|
21
|
+
'METRIC',
|
|
22
|
+
'IMPERIAL',
|
|
23
|
+
'Client',
|
|
24
|
+
'Error',
|
|
25
|
+
'Forecast',
|
|
26
|
+
'RequestError',
|
|
27
|
+
'HeatIndex',
|
|
28
|
+
'Kind',
|
|
29
|
+
'Locale',
|
|
30
|
+
'Phase',
|
|
31
|
+
'UltraViolet',
|
|
32
|
+
'VERSION',
|
|
33
|
+
'WindDirection',
|
|
34
|
+
)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# SPDX-FileCopyrightText: 2021-2026 null8626
|
|
3
|
+
|
|
4
|
+
from .enums import WindDirection, Kind, Locale, UltraViolet
|
|
5
|
+
from .constants import _Unit
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BaseForecast:
|
|
9
|
+
"""A base weather forecast."""
|
|
10
|
+
|
|
11
|
+
__slots__: tuple[str, ...] = (
|
|
12
|
+
'cloud_cover',
|
|
13
|
+
'ultraviolet',
|
|
14
|
+
'humidity',
|
|
15
|
+
'wind_direction',
|
|
16
|
+
'kind',
|
|
17
|
+
'feels_like',
|
|
18
|
+
'temperature',
|
|
19
|
+
'precipitation',
|
|
20
|
+
'pressure',
|
|
21
|
+
'visibility',
|
|
22
|
+
'wind_speed',
|
|
23
|
+
'description',
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
cloud_cover: int
|
|
27
|
+
"""The cloud cover value in percent."""
|
|
28
|
+
|
|
29
|
+
ultraviolet: UltraViolet
|
|
30
|
+
"""The ultra-violet index."""
|
|
31
|
+
|
|
32
|
+
humidity: int
|
|
33
|
+
"""The humidity value in percent."""
|
|
34
|
+
|
|
35
|
+
wind_direction: WindDirection
|
|
36
|
+
"""The wind direction."""
|
|
37
|
+
|
|
38
|
+
kind: Kind
|
|
39
|
+
"""The kind of the forecast."""
|
|
40
|
+
|
|
41
|
+
feels_like: int
|
|
42
|
+
"""What it felt like in either celcius or fahrenheit."""
|
|
43
|
+
|
|
44
|
+
temperature: int
|
|
45
|
+
"""The temperature in either celcius or fahrenheit."""
|
|
46
|
+
|
|
47
|
+
precipitation: float
|
|
48
|
+
"""The precipitation in either millimeters or inches."""
|
|
49
|
+
|
|
50
|
+
pressure: float
|
|
51
|
+
"""The pressure in either pascal or inches."""
|
|
52
|
+
|
|
53
|
+
visibility: int
|
|
54
|
+
"""The visibility distance in either kilometers or miles."""
|
|
55
|
+
|
|
56
|
+
wind_speed: int
|
|
57
|
+
"""The wind speeds in either kilometers/hour or miles/hour."""
|
|
58
|
+
|
|
59
|
+
description: str
|
|
60
|
+
"""The description regarding the forecast depending on the localization used."""
|
|
61
|
+
|
|
62
|
+
def __init__(self, json: dict, unit: _Unit, locale: Locale):
|
|
63
|
+
description = (
|
|
64
|
+
json['weatherDesc'][0]['value']
|
|
65
|
+
if locale is Locale.ENGLISH
|
|
66
|
+
else json[f'lang_{locale.value}'][0]['value']
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
self.cloud_cover = int(json['cloudcover'])
|
|
70
|
+
self.ultraviolet = UltraViolet._new(int(json['uvIndex']))
|
|
71
|
+
self.humidity = int(json['humidity'])
|
|
72
|
+
self.wind_direction = WindDirection._new(
|
|
73
|
+
json['winddir16Point'], int(json['winddirDegree'])
|
|
74
|
+
)
|
|
75
|
+
self.kind = Kind(int(json['weatherCode']))
|
|
76
|
+
self.feels_like = int(json[f'FeelsLike{unit.temperature}'])
|
|
77
|
+
self.temperature = int(json[f'temp_{unit.temperature}'])
|
|
78
|
+
self.precipitation = float(json[f'precip{unit.precipitation}'])
|
|
79
|
+
self.pressure = float(json[f'pressure{unit.pressure}'])
|
|
80
|
+
self.visibility = int(json[f'visibility{unit.visibility}'])
|
|
81
|
+
self.wind_speed = int(json[f'windspeed{unit.velocity}'])
|
|
82
|
+
self.description = description.strip()
|
|
@@ -1,26 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
Copyright (c) 2021-2025 null8626
|
|
5
|
-
|
|
6
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
-
in the Software without restriction, including without limitation the rights
|
|
9
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
-
furnished to do so, subject to the following conditions:
|
|
12
|
-
|
|
13
|
-
The above copyright notice and this permission notice shall be included in all
|
|
14
|
-
copies or substantial portions of the Software.
|
|
15
|
-
|
|
16
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
-
SOFTWARE.
|
|
23
|
-
"""
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# SPDX-FileCopyrightText: 2021-2026 null8626
|
|
24
3
|
|
|
25
4
|
from aiohttp import ClientSession, ClientTimeout, ClientResponseError, TCPConnector
|
|
26
5
|
from urllib.parse import quote_plus
|
|
@@ -28,13 +7,12 @@ from asyncio import sleep
|
|
|
28
7
|
|
|
29
8
|
from .errors import Error, RequestError
|
|
30
9
|
from .constants import _Unit, METRIC
|
|
31
|
-
from .base import CustomizableBase
|
|
32
10
|
from .forecast import Forecast
|
|
33
11
|
from .version import VERSION
|
|
34
12
|
from .enums import Locale
|
|
35
13
|
|
|
36
14
|
|
|
37
|
-
class Client
|
|
15
|
+
class Client:
|
|
38
16
|
"""
|
|
39
17
|
Interact with the API's endpoints.
|
|
40
18
|
|
|
@@ -66,11 +44,19 @@ class Client(CustomizableBase):
|
|
|
66
44
|
:exception Error: ``unit`` is not :data:`~.constants.METRIC` or :data:`~.constants.IMPERIAL` or ``locale`` is not a part of the :class:`.Locale` enum.
|
|
67
45
|
"""
|
|
68
46
|
|
|
69
|
-
__slots__: tuple[str, ...] =
|
|
47
|
+
__slots__: tuple[str, ...] = (
|
|
48
|
+
'__own_session',
|
|
49
|
+
'__session',
|
|
50
|
+
'_max_retries',
|
|
51
|
+
'_unit',
|
|
52
|
+
'_locale',
|
|
53
|
+
)
|
|
70
54
|
|
|
71
55
|
__own_session: bool
|
|
72
56
|
__session: ClientSession
|
|
73
57
|
_max_retries: int
|
|
58
|
+
_unit: _Unit
|
|
59
|
+
_locale: Locale
|
|
74
60
|
|
|
75
61
|
def __init__(
|
|
76
62
|
self,
|
|
@@ -80,18 +66,58 @@ class Client(CustomizableBase):
|
|
|
80
66
|
session: ClientSession | None = None,
|
|
81
67
|
max_retries: int | None = None,
|
|
82
68
|
):
|
|
83
|
-
super().__init__(unit, locale)
|
|
84
|
-
|
|
85
69
|
self.__own_session = session is None
|
|
86
70
|
self.__session = session or ClientSession(
|
|
87
71
|
timeout=ClientTimeout(total=5000.0),
|
|
88
72
|
connector=TCPConnector(ssl=False),
|
|
89
73
|
)
|
|
90
74
|
self._max_retries = max_retries or 3
|
|
75
|
+
self.unit = unit
|
|
76
|
+
self.locale = locale
|
|
91
77
|
|
|
92
78
|
def __repr__(self) -> str:
|
|
79
|
+
"""The client's debug string representation."""
|
|
93
80
|
return f'<{__class__.__module__}.{__class__.__name__} {self.__session!r}>'
|
|
94
81
|
|
|
82
|
+
@property
|
|
83
|
+
def unit(self) -> _Unit:
|
|
84
|
+
"""The measuring unit used."""
|
|
85
|
+
return self._unit
|
|
86
|
+
|
|
87
|
+
@unit.setter
|
|
88
|
+
def unit(self, to: _Unit) -> None:
|
|
89
|
+
"""
|
|
90
|
+
Sets the default measuring unit used.
|
|
91
|
+
|
|
92
|
+
:param to: The new default measuring unit to be used.
|
|
93
|
+
|
|
94
|
+
:exception Error: ``to`` is not either :data:`~.constants.METRIC` or :data:`~.constants.IMPERIAL`.
|
|
95
|
+
"""
|
|
96
|
+
if not isinstance(to, _Unit):
|
|
97
|
+
raise Error('Invalid measuring unit specified!')
|
|
98
|
+
|
|
99
|
+
self._unit = to
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def locale(self) -> Locale:
|
|
103
|
+
"""The localization used."""
|
|
104
|
+
return self._locale
|
|
105
|
+
|
|
106
|
+
@locale.setter
|
|
107
|
+
def locale(self, to: Locale) -> None:
|
|
108
|
+
"""
|
|
109
|
+
Sets the default localization used.
|
|
110
|
+
|
|
111
|
+
:param to: The new :class:`.Locale` to be used.
|
|
112
|
+
:type to: :class:`.Locale`
|
|
113
|
+
|
|
114
|
+
:exception Error: ``to`` is not a part of the :class:`.Locale` enum.
|
|
115
|
+
"""
|
|
116
|
+
if not isinstance(to, Locale):
|
|
117
|
+
raise Error(f'Expected {to!r} to be a Locale enum')
|
|
118
|
+
|
|
119
|
+
self._locale = to
|
|
120
|
+
|
|
95
121
|
async def get(
|
|
96
122
|
self,
|
|
97
123
|
location: str,
|
|
@@ -115,19 +141,20 @@ class Client(CustomizableBase):
|
|
|
115
141
|
:param locale: Overrides the locale used.
|
|
116
142
|
:type locale: :class:`.Locale` | :py:obj:`None`
|
|
117
143
|
|
|
118
|
-
:exception TypeError:
|
|
144
|
+
:exception TypeError: The specified location is not a string.
|
|
145
|
+
:exception ValueError: The specified location is empty.
|
|
119
146
|
:exception Error: The client is already closed.
|
|
120
147
|
:exception RequestError: The client received a non-favorable response from the API.
|
|
121
148
|
|
|
122
149
|
:returns: The requested weather forecast.
|
|
123
150
|
:rtype: Forecast
|
|
124
151
|
"""
|
|
125
|
-
|
|
126
152
|
if self.__session.closed:
|
|
127
153
|
raise Error('Client session is already closed.')
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
154
|
+
elif not isinstance(location, str):
|
|
155
|
+
raise TypeError('The specified location must be a string.')
|
|
156
|
+
elif not location:
|
|
157
|
+
raise ValueError('The specified location must not be empty.')
|
|
131
158
|
|
|
132
159
|
if not isinstance(unit, _Unit):
|
|
133
160
|
unit = self._unit
|
|
@@ -155,7 +182,9 @@ class Client(CustomizableBase):
|
|
|
155
182
|
|
|
156
183
|
resp.raise_for_status()
|
|
157
184
|
|
|
158
|
-
return Forecast(
|
|
185
|
+
return Forecast(
|
|
186
|
+
await resp.json(content_type='application/text'), unit, locale
|
|
187
|
+
)
|
|
159
188
|
except ClientResponseError:
|
|
160
189
|
if attempts == self._max_retries:
|
|
161
190
|
raise RequestError(status, reason) from None
|
|
@@ -165,7 +194,7 @@ class Client(CustomizableBase):
|
|
|
165
194
|
|
|
166
195
|
async def close(self) -> None:
|
|
167
196
|
"""
|
|
168
|
-
Closes the
|
|
197
|
+
Closes the client.
|
|
169
198
|
|
|
170
199
|
Example:
|
|
171
200
|
|
|
@@ -173,12 +202,13 @@ class Client(CustomizableBase):
|
|
|
173
202
|
|
|
174
203
|
await client.close()
|
|
175
204
|
"""
|
|
176
|
-
|
|
177
205
|
if self.__own_session and not self.__session.closed:
|
|
178
206
|
await self.__session.close()
|
|
179
207
|
|
|
180
208
|
async def __aenter__(self) -> 'Client':
|
|
209
|
+
"""Starts using the client. This method is no-op and just returns itself."""
|
|
181
210
|
return self
|
|
182
211
|
|
|
183
212
|
async def __aexit__(self, *_, **__) -> None:
|
|
213
|
+
"""Closes the client."""
|
|
184
214
|
await self.close()
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# SPDX-FileCopyrightText: 2021-2026 null8626
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class _Unit:
|
|
8
|
+
"""A supported measurement unit."""
|
|
9
|
+
|
|
10
|
+
__slots__: tuple[str, ...] = (
|
|
11
|
+
'temperature',
|
|
12
|
+
'velocity',
|
|
13
|
+
'pressure',
|
|
14
|
+
'precipitation',
|
|
15
|
+
'visibility',
|
|
16
|
+
'cm_divisor',
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
temperature: str
|
|
20
|
+
velocity: str
|
|
21
|
+
pressure: str
|
|
22
|
+
precipitation: str
|
|
23
|
+
visibility: str
|
|
24
|
+
cm_divisor: float | int
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
temperature: str,
|
|
29
|
+
velocity: str,
|
|
30
|
+
pressure: str,
|
|
31
|
+
precipitation: str,
|
|
32
|
+
visibility: str,
|
|
33
|
+
cm_divisor: float | int,
|
|
34
|
+
):
|
|
35
|
+
self.temperature = temperature
|
|
36
|
+
self.velocity = velocity
|
|
37
|
+
self.pressure = pressure
|
|
38
|
+
self.precipitation = precipitation
|
|
39
|
+
self.visibility = visibility
|
|
40
|
+
self.cm_divisor = cm_divisor
|
|
41
|
+
|
|
42
|
+
def __repr__(self) -> str:
|
|
43
|
+
"""The unit's debug string representation."""
|
|
44
|
+
return f'<Unit [{self.temperature}, {self.velocity}]>'
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
METRIC = _Unit('C', 'Kmph', '', 'MM', '', 1)
|
|
48
|
+
IMPERIAL = _Unit('F', 'Miles', 'Inches', 'Inches', 'Miles', 2.54)
|
|
49
|
+
|
|
50
|
+
LATLON_REGEX = re.compile(r'^Lat (\-?[\d\.]+) and Lon (\-?[\d\.]+)$')
|
|
51
|
+
|
|
52
|
+
KIND_EMOJIS = (
|
|
53
|
+
'☀️',
|
|
54
|
+
'⛅️',
|
|
55
|
+
'☁️',
|
|
56
|
+
'☁️',
|
|
57
|
+
'🌫',
|
|
58
|
+
'🌦',
|
|
59
|
+
'🌧',
|
|
60
|
+
'🌧',
|
|
61
|
+
'⛈',
|
|
62
|
+
'🌨',
|
|
63
|
+
'❄️',
|
|
64
|
+
'🌦',
|
|
65
|
+
'🌧',
|
|
66
|
+
'🌧',
|
|
67
|
+
'🌨',
|
|
68
|
+
'❄️',
|
|
69
|
+
'🌩',
|
|
70
|
+
'⛈',
|
|
71
|
+
)
|
|
72
|
+
WIND_DIRECTION_EMOJIS = '↑', '↖', '←', '↙', '↓', '↘', '→', '↗'
|
|
@@ -1,73 +1,70 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
Copyright (c) 2021-2025 null8626
|
|
5
|
-
|
|
6
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
-
in the Software without restriction, including without limitation the rights
|
|
9
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
-
furnished to do so, subject to the following conditions:
|
|
12
|
-
|
|
13
|
-
The above copyright notice and this permission notice shall be included in all
|
|
14
|
-
copies or substantial portions of the Software.
|
|
15
|
-
|
|
16
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
-
SOFTWARE.
|
|
23
|
-
"""
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# SPDX-FileCopyrightText: 2021-2026 null8626
|
|
24
3
|
|
|
25
4
|
from enum import Enum
|
|
26
5
|
|
|
27
|
-
from .constants import WIND_DIRECTION_EMOJIS
|
|
6
|
+
from .constants import KIND_EMOJIS, WIND_DIRECTION_EMOJIS
|
|
28
7
|
|
|
29
8
|
|
|
30
9
|
class BasicEnum(Enum):
|
|
10
|
+
"""An ordinary enum."""
|
|
11
|
+
|
|
31
12
|
__slots__: tuple[str, ...] = ()
|
|
32
13
|
|
|
33
14
|
def __repr__(self) -> str:
|
|
15
|
+
"""The enum's debug string representation."""
|
|
34
16
|
return f'{self.__class__.__name__}.{self.name}'
|
|
35
17
|
|
|
36
18
|
def __str__(self) -> str:
|
|
19
|
+
"""The enum's friendly name."""
|
|
37
20
|
return self.name.replace('_', ' ').title()
|
|
38
21
|
|
|
22
|
+
@property
|
|
23
|
+
def _index(self) -> int:
|
|
24
|
+
return next(filter(lambda kind: self is kind[1], enumerate(self.__class__)))[0]
|
|
25
|
+
|
|
39
26
|
|
|
40
27
|
class IndexedEnum(Enum):
|
|
28
|
+
"""An enum that carries an index value."""
|
|
29
|
+
|
|
41
30
|
__slots__: tuple[str, ...] = ('index',)
|
|
42
31
|
|
|
43
32
|
index: int
|
|
44
33
|
"""The index value."""
|
|
45
34
|
|
|
46
35
|
def __lt__(self, other: 'IndexedEnum | float | int') -> bool:
|
|
36
|
+
"""Checks if the enum's index value is less than the other."""
|
|
47
37
|
return float(self.index) < float(other)
|
|
48
38
|
|
|
49
39
|
def __le__(self, other: 'IndexedEnum | float | int') -> bool:
|
|
40
|
+
"""Checks if the enum's index value is less than or equal to the other."""
|
|
50
41
|
return float(self.index) <= float(other)
|
|
51
42
|
|
|
52
43
|
def __eq__(self, other: object) -> bool:
|
|
44
|
+
"""Checks if the enum's index value is equal to the other."""
|
|
53
45
|
if other_float := getattr(other, '__float__', None):
|
|
54
46
|
return float(self.index) == other_float()
|
|
55
47
|
|
|
56
48
|
return False # pragma: nocover
|
|
57
49
|
|
|
58
50
|
def __gt__(self, other: 'IndexedEnum | float | int') -> bool:
|
|
51
|
+
"""Checks if the enum's index value is greater than the other."""
|
|
59
52
|
return float(self.index) > float(other)
|
|
60
53
|
|
|
61
54
|
def __ge__(self, other: 'IndexedEnum | float | int') -> bool:
|
|
55
|
+
"""Checks if the enum's index value is greater than or equal to the other."""
|
|
62
56
|
return float(self.index) >= float(other)
|
|
63
57
|
|
|
64
58
|
def __hash__(self) -> int:
|
|
59
|
+
"""The enum's index value."""
|
|
65
60
|
return self.index
|
|
66
61
|
|
|
67
62
|
def __int__(self) -> int:
|
|
63
|
+
"""The enum's index value."""
|
|
68
64
|
return self.index
|
|
69
65
|
|
|
70
66
|
def __float__(self) -> float:
|
|
67
|
+
"""The floating point representation of the enum's index value."""
|
|
71
68
|
return float(self.index)
|
|
72
69
|
|
|
73
70
|
|
|
@@ -157,7 +154,7 @@ class WindDirection(BasicEnum):
|
|
|
157
154
|
NORTH_NORTHWEST = 'NNW'
|
|
158
155
|
|
|
159
156
|
degrees: float
|
|
160
|
-
"""The wind direction's
|
|
157
|
+
"""The wind direction's angle in degrees."""
|
|
161
158
|
|
|
162
159
|
@staticmethod
|
|
163
160
|
def _new(value: str, degrees: float) -> 'WindDirection':
|
|
@@ -167,56 +164,27 @@ class WindDirection(BasicEnum):
|
|
|
167
164
|
return enum
|
|
168
165
|
|
|
169
166
|
def __contains__(self, other: 'WindDirection | float | int') -> bool:
|
|
170
|
-
other
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
elif self is self.NORTH_NORTHEAST:
|
|
175
|
-
return 11.25 < other <= 33.75
|
|
176
|
-
elif self is self.NORTHEAST:
|
|
177
|
-
return 33.75 < other <= 56.25
|
|
178
|
-
elif self is self.EAST_NORTHEAST:
|
|
179
|
-
return 56.25 < other <= 78.75
|
|
180
|
-
elif self is self.EAST:
|
|
181
|
-
return 78.75 < other <= 101.25
|
|
182
|
-
elif self is self.EAST_SOUTHEAST:
|
|
183
|
-
return 101.25 < other <= 123.75
|
|
184
|
-
elif self is self.SOUTHEAST:
|
|
185
|
-
return 123.75 < other <= 146.25
|
|
186
|
-
elif self is self.SOUTH_SOUTHEAST:
|
|
187
|
-
return 146.25 < other <= 168.75
|
|
188
|
-
elif self is self.SOUTH:
|
|
189
|
-
return 168.75 < other <= 191.25
|
|
190
|
-
elif self is self.SOUTH_SOUTHWEST:
|
|
191
|
-
return 191.25 < other <= 213.75
|
|
192
|
-
elif self is self.SOUTHWEST:
|
|
193
|
-
return 213.75 < other <= 236.25
|
|
194
|
-
elif self is self.WEST_SOUTHWEST:
|
|
195
|
-
return 236.25 < other <= 258.75
|
|
196
|
-
elif self is self.WEST:
|
|
197
|
-
return 258.75 < other <= 281.25
|
|
198
|
-
elif self is self.WEST_NORTHWEST:
|
|
199
|
-
return 281.25 < other <= 303.75
|
|
200
|
-
elif self is self.NORTHWEST:
|
|
201
|
-
return 303.75 < other <= 326.25
|
|
202
|
-
else:
|
|
203
|
-
return 326.25 < other <= 348.75
|
|
167
|
+
"""Checks if the other's angle is within the enum's wind direction."""
|
|
168
|
+
members = list(self.__class__)
|
|
169
|
+
|
|
170
|
+
return self is members[int(((float(other) % 360) + 11.25) // 22.5) % len(members)]
|
|
204
171
|
|
|
205
172
|
def __int__(self) -> int:
|
|
173
|
+
"""The integer representation of the wind direction's angle."""
|
|
206
174
|
return int(self.degrees)
|
|
207
175
|
|
|
208
176
|
def __float__(self) -> float:
|
|
177
|
+
"""The wind direction's angle."""
|
|
209
178
|
return self.degrees
|
|
210
179
|
|
|
211
180
|
@property
|
|
212
181
|
def emoji(self) -> str:
|
|
213
|
-
"""
|
|
214
|
-
|
|
215
|
-
return WIND_DIRECTION_EMOJIS[int(((self.degrees + 22.5) % 360) / 45.0)]
|
|
182
|
+
"""The wind direction's emoji representation."""
|
|
183
|
+
return WIND_DIRECTION_EMOJIS[int(((self.degrees + 22.5) % 360) // 45)]
|
|
216
184
|
|
|
217
185
|
|
|
218
186
|
class Locale(Enum):
|
|
219
|
-
"""
|
|
187
|
+
"""A supported locale."""
|
|
220
188
|
|
|
221
189
|
__slots__: tuple[str, ...] = ()
|
|
222
190
|
|
|
@@ -294,9 +262,11 @@ class Locale(Enum):
|
|
|
294
262
|
ZULU = 'zu'
|
|
295
263
|
|
|
296
264
|
def __repr__(self) -> str:
|
|
265
|
+
"""The locale's debug string representation."""
|
|
297
266
|
return f'{__class__.__name__}.{self.name}'
|
|
298
267
|
|
|
299
268
|
def __str__(self) -> str:
|
|
269
|
+
"""The locale's friendly name."""
|
|
300
270
|
arr = self.name.title().split('_')
|
|
301
271
|
|
|
302
272
|
return f'{" ".join(arr[:-1])} ({arr[-1]})' if len(arr) != 1 else arr[0]
|
|
@@ -355,44 +325,8 @@ class Kind(BasicEnum):
|
|
|
355
325
|
|
|
356
326
|
@property
|
|
357
327
|
def emoji(self) -> str:
|
|
358
|
-
"""
|
|
359
|
-
|
|
360
|
-
if self is self.CLOUDY:
|
|
361
|
-
return '☁️'
|
|
362
|
-
elif self is self.FOG:
|
|
363
|
-
return '🌫'
|
|
364
|
-
elif self is self.HEAVY_RAIN:
|
|
365
|
-
return '🌧'
|
|
366
|
-
elif self is self.HEAVY_SHOWERS:
|
|
367
|
-
return '🌧'
|
|
368
|
-
elif self is self.HEAVY_SNOW:
|
|
369
|
-
return '❄️'
|
|
370
|
-
elif self is self.HEAVY_SNOW_SHOWERS:
|
|
371
|
-
return '❄️'
|
|
372
|
-
elif self is self.LIGHT_RAIN:
|
|
373
|
-
return '🌦'
|
|
374
|
-
elif self is self.LIGHT_SHOWERS:
|
|
375
|
-
return '🌦'
|
|
376
|
-
elif self is self.LIGHT_SLEET:
|
|
377
|
-
return '🌧'
|
|
378
|
-
elif self is self.LIGHT_SLEET_SHOWERS:
|
|
379
|
-
return '🌧'
|
|
380
|
-
elif self is self.LIGHT_SNOW:
|
|
381
|
-
return '🌨'
|
|
382
|
-
elif self is self.LIGHT_SNOW_SHOWERS:
|
|
383
|
-
return '🌨'
|
|
384
|
-
elif self is self.PARTLY_CLOUDY:
|
|
385
|
-
return '⛅️'
|
|
386
|
-
elif self is self.SUNNY:
|
|
387
|
-
return '☀️'
|
|
388
|
-
elif self is self.THUNDERY_HEAVY_RAIN:
|
|
389
|
-
return '🌩'
|
|
390
|
-
elif self is self.THUNDERY_SHOWERS:
|
|
391
|
-
return '⛈'
|
|
392
|
-
elif self is self.THUNDERY_SNOW_SHOWERS:
|
|
393
|
-
return '⛈'
|
|
394
|
-
else:
|
|
395
|
-
return '☁️'
|
|
328
|
+
"""The weather forecast kind's emoji representation."""
|
|
329
|
+
return KIND_EMOJIS[self._index]
|
|
396
330
|
|
|
397
331
|
|
|
398
332
|
class Phase(BasicEnum):
|
|
@@ -411,21 +345,5 @@ class Phase(BasicEnum):
|
|
|
411
345
|
|
|
412
346
|
@property
|
|
413
347
|
def emoji(self) -> str:
|
|
414
|
-
"""
|
|
415
|
-
|
|
416
|
-
if self is self.NEW_MOON:
|
|
417
|
-
return '🌑'
|
|
418
|
-
elif self is self.WAXING_CRESCENT:
|
|
419
|
-
return '🌒'
|
|
420
|
-
elif self is self.FIRST_QUARTER:
|
|
421
|
-
return '🌓'
|
|
422
|
-
elif self is self.WAXING_GIBBOUS:
|
|
423
|
-
return '🌔'
|
|
424
|
-
elif self is self.FULL_MOON:
|
|
425
|
-
return '🌕'
|
|
426
|
-
elif self is self.WANING_GIBBOUS:
|
|
427
|
-
return '🌖'
|
|
428
|
-
elif self is self.LAST_QUARTER:
|
|
429
|
-
return '🌗'
|
|
430
|
-
else:
|
|
431
|
-
return '🌘'
|
|
348
|
+
"""The moon phase's emoji representation."""
|
|
349
|
+
return chr(0x1F311 + self._index)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# SPDX-FileCopyrightText: 2021-2026 null8626
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Error(Exception):
|
|
6
|
+
"""The base error class. Extends :py:class:`Exception`."""
|
|
7
|
+
|
|
8
|
+
__slots__: tuple[str, ...] = ()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RequestError(Error):
|
|
12
|
+
"""Thrown upon HTTP request failure. Extends :class:`.Error`."""
|
|
13
|
+
|
|
14
|
+
__slots__: tuple[str, ...] = 'status', 'reason'
|
|
15
|
+
|
|
16
|
+
status: int | None
|
|
17
|
+
"""The status code."""
|
|
18
|
+
|
|
19
|
+
reason: str | None
|
|
20
|
+
"""The reason for this status code."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, status: int | None, reason: str | None):
|
|
23
|
+
self.status = status
|
|
24
|
+
self.reason = reason
|
|
25
|
+
|
|
26
|
+
super().__init__(f'{status}: {reason}')
|
|
27
|
+
|
|
28
|
+
def __repr__(self) -> str: # pragma: nocover
|
|
29
|
+
"""The error's debug string representation."""
|
|
30
|
+
return f'<{__class__.__module__}.{__class__.__name__} status={self.status} reason={self.reason!r}>'
|
|
@@ -1,26 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
Copyright (c) 2021-2025 null8626
|
|
5
|
-
|
|
6
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
-
in the Software without restriction, including without limitation the rights
|
|
9
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
-
furnished to do so, subject to the following conditions:
|
|
12
|
-
|
|
13
|
-
The above copyright notice and this permission notice shall be included in all
|
|
14
|
-
copies or substantial portions of the Software.
|
|
15
|
-
|
|
16
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
-
SOFTWARE.
|
|
23
|
-
"""
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# SPDX-FileCopyrightText: 2021-2026 null8626
|
|
24
3
|
|
|
25
4
|
from datetime import datetime, date, time
|
|
26
5
|
from collections.abc import Iterator
|
|
@@ -134,6 +113,7 @@ class HourlyForecast(BaseForecast):
|
|
|
134
113
|
super().__init__(json, unit, locale)
|
|
135
114
|
|
|
136
115
|
def __repr__(self) -> str:
|
|
116
|
+
"""The forecast's debug string representation."""
|
|
137
117
|
return f'<{__class__.__module__}.{__class__.__name__} time={self.time!r} temperature={self.temperature} kind={self.kind!r}>'
|
|
138
118
|
|
|
139
119
|
|
|
@@ -222,12 +202,15 @@ class DailyForecast:
|
|
|
222
202
|
...
|
|
223
203
|
|
|
224
204
|
def __repr__(self) -> str:
|
|
205
|
+
"""The forecast's debug string representation."""
|
|
225
206
|
return f'<{__class__.__module__}.{__class__.__name__} date={self.date!r} temperature={self.temperature}>'
|
|
226
207
|
|
|
227
208
|
def __len__(self) -> int:
|
|
209
|
+
"""The amount of hourly forecasts."""
|
|
228
210
|
return len(self.hourly_forecasts)
|
|
229
211
|
|
|
230
212
|
def __iter__(self) -> Iterator[HourlyForecast]:
|
|
213
|
+
"""Iterates through the hourly forecasts."""
|
|
231
214
|
return iter(self.hourly_forecasts)
|
|
232
215
|
|
|
233
216
|
|
|
@@ -293,10 +276,13 @@ class Forecast(BaseForecast):
|
|
|
293
276
|
super().__init__(current, unit, locale)
|
|
294
277
|
|
|
295
278
|
def __repr__(self) -> str:
|
|
279
|
+
"""The forecast's debug string representation."""
|
|
296
280
|
return f'<{__class__.__module__}.{__class__.__name__} location={self.location!r} datetime={self.datetime!r} temperature={self.temperature}>'
|
|
297
281
|
|
|
298
282
|
def __len__(self) -> int:
|
|
283
|
+
"""The amount of daily forecasts."""
|
|
299
284
|
return len(self.daily_forecasts)
|
|
300
285
|
|
|
301
286
|
def __iter__(self) -> Iterator[DailyForecast]:
|
|
287
|
+
"""Iterates through the daily forecasts."""
|
|
302
288
|
return iter(self.daily_forecasts)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-weather
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: A free and asynchronous weather API wrapper made in Python, for Python.
|
|
5
5
|
Author: null8626
|
|
6
|
-
License: MIT
|
|
6
|
+
License-Expression: MIT
|
|
7
7
|
Project-URL: Documentation, https://python-weather.readthedocs.io/en/latest/
|
|
8
8
|
Project-URL: Repository, https://github.com/null8626/python-weather
|
|
9
9
|
Project-URL: Changelog, https://python-weather.readthedocs.io/en/latest/changelog.html
|
|
@@ -33,7 +33,7 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
33
33
|
Requires-Python: >=3.10
|
|
34
34
|
Description-Content-Type: text/markdown
|
|
35
35
|
License-File: LICENSE
|
|
36
|
-
Requires-Dist: aiohttp>=3.13.
|
|
36
|
+
Requires-Dist: aiohttp>=3.13.5
|
|
37
37
|
Dynamic: license-file
|
|
38
38
|
|
|
39
39
|
# [python-weather][pypi-url] [![pypi][pypi-image]][pypi-url] [![pypi downloads][pypi-downloads-image]][pypi-url] [![codacy-badge][codacy-image]][codacy-url] [![codecov-badge][codecov-image]][codecov-url] [![ko-fi][ko-fi-brief-image]][ko-fi-url]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
aiohttp>=3.13.5
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
The MIT License (MIT)
|
|
3
|
-
|
|
4
|
-
Copyright (c) 2021-2025 null8626
|
|
5
|
-
|
|
6
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
-
in the Software without restriction, including without limitation the rights
|
|
9
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
-
furnished to do so, subject to the following conditions:
|
|
12
|
-
|
|
13
|
-
The above copyright notice and this permission notice shall be included in all
|
|
14
|
-
copies or substantial portions of the Software.
|
|
15
|
-
|
|
16
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
-
SOFTWARE.
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
from .enums import HeatIndex, Kind, Locale, Phase, UltraViolet, WindDirection
|
|
26
|
-
from .constants import METRIC, IMPERIAL
|
|
27
|
-
from .errors import Error, RequestError
|
|
28
|
-
from .forecast import Forecast
|
|
29
|
-
from .version import VERSION
|
|
30
|
-
from .client import Client
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
__title__ = 'python-weather'
|
|
34
|
-
__author__ = 'null8626'
|
|
35
|
-
__credits__ = (__author__,)
|
|
36
|
-
__maintainer__ = __author__
|
|
37
|
-
__status__ = 'Production'
|
|
38
|
-
__license__ = 'MIT'
|
|
39
|
-
__copyright__ = 'Copyright (c) 2021-2025 null8626'
|
|
40
|
-
__version__ = VERSION
|
|
41
|
-
__all__ = (
|
|
42
|
-
'METRIC',
|
|
43
|
-
'IMPERIAL',
|
|
44
|
-
'Client',
|
|
45
|
-
'Error',
|
|
46
|
-
'Forecast',
|
|
47
|
-
'RequestError',
|
|
48
|
-
'HeatIndex',
|
|
49
|
-
'Kind',
|
|
50
|
-
'Locale',
|
|
51
|
-
'Phase',
|
|
52
|
-
'UltraViolet',
|
|
53
|
-
'VERSION',
|
|
54
|
-
'WindDirection',
|
|
55
|
-
)
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
The MIT License (MIT)
|
|
3
|
-
|
|
4
|
-
Copyright (c) 2021-2025 null8626
|
|
5
|
-
|
|
6
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
-
in the Software without restriction, including without limitation the rights
|
|
9
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
-
furnished to do so, subject to the following conditions:
|
|
12
|
-
|
|
13
|
-
The above copyright notice and this permission notice shall be included in all
|
|
14
|
-
copies or substantial portions of the Software.
|
|
15
|
-
|
|
16
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
-
SOFTWARE.
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
from .enums import WindDirection, Kind, Locale, UltraViolet
|
|
26
|
-
from .constants import _Unit
|
|
27
|
-
from .errors import Error
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class CustomizableBase:
|
|
31
|
-
__slots__: tuple[str, ...] = '_unit', '_locale'
|
|
32
|
-
|
|
33
|
-
_unit: _Unit
|
|
34
|
-
_locale: Locale
|
|
35
|
-
|
|
36
|
-
def __init__(self, unit: _Unit, locale: Locale):
|
|
37
|
-
self.unit = unit
|
|
38
|
-
self.locale = locale
|
|
39
|
-
|
|
40
|
-
@property
|
|
41
|
-
def unit(self) -> _Unit:
|
|
42
|
-
"""The measuring unit used."""
|
|
43
|
-
|
|
44
|
-
return self._unit
|
|
45
|
-
|
|
46
|
-
@unit.setter
|
|
47
|
-
def unit(self, to: _Unit) -> None:
|
|
48
|
-
"""
|
|
49
|
-
Sets the default measuring unit used.
|
|
50
|
-
|
|
51
|
-
:param to: The new default measuring unit to be used.
|
|
52
|
-
|
|
53
|
-
:exception Error: ``to`` is not either :data:`~.constants.METRIC` or :data:`~.constants.IMPERIAL`.
|
|
54
|
-
"""
|
|
55
|
-
|
|
56
|
-
if not isinstance(to, _Unit):
|
|
57
|
-
raise Error('Invalid measuring unit specified!')
|
|
58
|
-
|
|
59
|
-
self._unit = to
|
|
60
|
-
|
|
61
|
-
@property
|
|
62
|
-
def locale(self) -> Locale:
|
|
63
|
-
"""The localization used."""
|
|
64
|
-
|
|
65
|
-
return self._locale
|
|
66
|
-
|
|
67
|
-
@locale.setter
|
|
68
|
-
def locale(self, to: Locale) -> None:
|
|
69
|
-
"""
|
|
70
|
-
Sets the default localization used.
|
|
71
|
-
|
|
72
|
-
:param to: The new :class:`.Locale` to be used.
|
|
73
|
-
:type to: :class:`.Locale`
|
|
74
|
-
|
|
75
|
-
:exception Error: ``to`` is not a part of the :class:`.Locale` enum.
|
|
76
|
-
"""
|
|
77
|
-
|
|
78
|
-
if not isinstance(to, Locale):
|
|
79
|
-
raise Error(f'Expected {to!r} to be a Locale enum')
|
|
80
|
-
|
|
81
|
-
self._locale = to
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
class BaseForecast:
|
|
85
|
-
__slots__: tuple[str, ...] = (
|
|
86
|
-
'cloud_cover',
|
|
87
|
-
'ultraviolet',
|
|
88
|
-
'humidity',
|
|
89
|
-
'wind_direction',
|
|
90
|
-
'kind',
|
|
91
|
-
'feels_like',
|
|
92
|
-
'temperature',
|
|
93
|
-
'precipitation',
|
|
94
|
-
'pressure',
|
|
95
|
-
'visibility',
|
|
96
|
-
'wind_speed',
|
|
97
|
-
'description',
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
cloud_cover: int
|
|
101
|
-
"""The cloud cover value in percent."""
|
|
102
|
-
|
|
103
|
-
ultraviolet: UltraViolet
|
|
104
|
-
"""The ultra-violet index."""
|
|
105
|
-
|
|
106
|
-
humidity: int
|
|
107
|
-
"""The humidity value in percent."""
|
|
108
|
-
|
|
109
|
-
wind_direction: WindDirection
|
|
110
|
-
"""The wind direction."""
|
|
111
|
-
|
|
112
|
-
kind: Kind
|
|
113
|
-
"""The kind of the forecast."""
|
|
114
|
-
|
|
115
|
-
feels_like: int
|
|
116
|
-
"""What it felt like in either celcius or fahrenheit."""
|
|
117
|
-
|
|
118
|
-
temperature: int
|
|
119
|
-
"""The temperature in either celcius or fahrenheit."""
|
|
120
|
-
|
|
121
|
-
precipitation: float
|
|
122
|
-
"""The precipitation in either millimeters or inches."""
|
|
123
|
-
|
|
124
|
-
pressure: float
|
|
125
|
-
"""The pressure in either pascal or inches."""
|
|
126
|
-
|
|
127
|
-
visibility: int
|
|
128
|
-
"""The visibility distance in either kilometers or miles."""
|
|
129
|
-
|
|
130
|
-
wind_speed: int
|
|
131
|
-
"""The wind speeds in either kilometers/hour or miles/hour."""
|
|
132
|
-
|
|
133
|
-
description: str
|
|
134
|
-
"""The description regarding the forecast depending on the localization used."""
|
|
135
|
-
|
|
136
|
-
def __init__(self, json: dict, unit: _Unit, locale: Locale):
|
|
137
|
-
description = (
|
|
138
|
-
json['weatherDesc'][0]['value']
|
|
139
|
-
if locale is Locale.ENGLISH
|
|
140
|
-
else json[f'lang_{locale.value}'][0]['value']
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
self.cloud_cover = int(json['cloudcover'])
|
|
144
|
-
self.ultraviolet = UltraViolet._new(int(json['uvIndex']))
|
|
145
|
-
self.humidity = int(json['humidity'])
|
|
146
|
-
self.wind_direction = WindDirection._new(
|
|
147
|
-
json['winddir16Point'], int(json['winddirDegree'])
|
|
148
|
-
)
|
|
149
|
-
self.kind = Kind(int(json['weatherCode']))
|
|
150
|
-
self.feels_like = int(json[f'FeelsLike{unit.temperature}'])
|
|
151
|
-
self.temperature = int(json[f'temp_{unit.temperature}'])
|
|
152
|
-
self.precipitation = float(json[f'precip{unit.precipitation}'])
|
|
153
|
-
self.pressure = float(json[f'pressure{unit.pressure}'])
|
|
154
|
-
self.visibility = int(json[f'visibility{unit.visibility}'])
|
|
155
|
-
self.wind_speed = int(json[f'windspeed{unit.velocity}'])
|
|
156
|
-
self.description = description.strip()
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
The MIT License (MIT)
|
|
3
|
-
|
|
4
|
-
Copyright (c) 2021-2025 null8626
|
|
5
|
-
|
|
6
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
-
in the Software without restriction, including without limitation the rights
|
|
9
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
-
furnished to do so, subject to the following conditions:
|
|
12
|
-
|
|
13
|
-
The above copyright notice and this permission notice shall be included in all
|
|
14
|
-
copies or substantial portions of the Software.
|
|
15
|
-
|
|
16
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
-
SOFTWARE.
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
import re
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class _Unit:
|
|
29
|
-
__slots__: tuple[str, ...] = (
|
|
30
|
-
'temperature',
|
|
31
|
-
'velocity',
|
|
32
|
-
'pressure',
|
|
33
|
-
'precipitation',
|
|
34
|
-
'visibility',
|
|
35
|
-
'cm_divisor',
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
temperature: str
|
|
39
|
-
velocity: str
|
|
40
|
-
pressure: str
|
|
41
|
-
precipitation: str
|
|
42
|
-
visibility: str
|
|
43
|
-
cm_divisor: float | int
|
|
44
|
-
|
|
45
|
-
def __init__(
|
|
46
|
-
self,
|
|
47
|
-
temperature: str,
|
|
48
|
-
velocity: str,
|
|
49
|
-
pressure: str,
|
|
50
|
-
precipitation: str,
|
|
51
|
-
visibility: str,
|
|
52
|
-
cm_divisor: float | int,
|
|
53
|
-
):
|
|
54
|
-
self.temperature = temperature
|
|
55
|
-
self.velocity = velocity
|
|
56
|
-
self.pressure = pressure
|
|
57
|
-
self.precipitation = precipitation
|
|
58
|
-
self.visibility = visibility
|
|
59
|
-
self.cm_divisor = cm_divisor
|
|
60
|
-
|
|
61
|
-
def __repr__(self) -> str:
|
|
62
|
-
return f'<Unit [{self.temperature}, {self.velocity}]>'
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
METRIC = _Unit('C', 'Kmph', '', 'MM', '', 1)
|
|
66
|
-
IMPERIAL = _Unit('F', 'Miles', 'Inches', 'Inches', 'Miles', 2.54)
|
|
67
|
-
|
|
68
|
-
WIND_DIRECTION_EMOJIS = '↓', '↙', '←', '↖', '↑', '↗', '→', '↘'
|
|
69
|
-
LATLON_REGEX = re.compile(r'^Lat (\-?[\d\.]+) and Lon (\-?[\d\.]+)$')
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
The MIT License (MIT)
|
|
3
|
-
|
|
4
|
-
Copyright (c) 2021-2025 null8626
|
|
5
|
-
|
|
6
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
-
in the Software without restriction, including without limitation the rights
|
|
9
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
-
furnished to do so, subject to the following conditions:
|
|
12
|
-
|
|
13
|
-
The above copyright notice and this permission notice shall be included in all
|
|
14
|
-
copies or substantial portions of the Software.
|
|
15
|
-
|
|
16
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
-
SOFTWARE.
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class Error(Exception):
|
|
27
|
-
"""The base error class. Extends :py:class:`Exception`."""
|
|
28
|
-
|
|
29
|
-
__slots__: tuple[str, ...] = ()
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class RequestError(Error):
|
|
33
|
-
"""Thrown upon HTTP request failure. Extends :class:`.Error`."""
|
|
34
|
-
|
|
35
|
-
__slots__: tuple[str, ...] = 'status', 'reason'
|
|
36
|
-
|
|
37
|
-
status: int | None
|
|
38
|
-
"""The status code."""
|
|
39
|
-
|
|
40
|
-
reason: str | None
|
|
41
|
-
"""The reason for this status code."""
|
|
42
|
-
|
|
43
|
-
def __init__(self, status: int | None, reason: str | None):
|
|
44
|
-
self.status = status
|
|
45
|
-
self.reason = reason
|
|
46
|
-
|
|
47
|
-
super().__init__(f'{status}: {reason}')
|
|
48
|
-
|
|
49
|
-
def __repr__(self) -> str: # pragma: nocover
|
|
50
|
-
return f'<{__class__.__module__}.{__class__.__name__} status={self.status} reason={self.reason!r}>'
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
VERSION = '3.0.0'
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
aiohttp>=3.13.2
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|