python-weather 1.1.2__tar.gz → 2.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python-weather-1.1.2/python_weather.egg-info → python-weather-2.0.0}/PKG-INFO +10 -19
- {python-weather-1.1.2 → python-weather-2.0.0}/README.md +5 -13
- {python-weather-1.1.2 → python-weather-2.0.0}/pyproject.toml +5 -6
- python-weather-2.0.0/python_weather/__init__.py +14 -0
- {python-weather-1.1.2 → python-weather-2.0.0}/python_weather/base.py +9 -10
- {python-weather-1.1.2 → python-weather-2.0.0}/python_weather/client.py +5 -5
- {python-weather-1.1.2 → python-weather-2.0.0}/python_weather/constants.py +1 -0
- {python-weather-1.1.2 → python-weather-2.0.0}/python_weather/enums.py +73 -45
- {python-weather-1.1.2 → python-weather-2.0.0}/python_weather/forecast.py +95 -163
- {python-weather-1.1.2 → python-weather-2.0.0/python_weather.egg-info}/PKG-INFO +10 -19
- python-weather-1.1.2/python_weather/__init__.py +0 -14
- {python-weather-1.1.2 → python-weather-2.0.0}/LICENSE +0 -0
- {python-weather-1.1.2 → python-weather-2.0.0}/MANIFEST.in +0 -0
- {python-weather-1.1.2 → python-weather-2.0.0}/python_weather/errors.py +0 -0
- {python-weather-1.1.2 → python-weather-2.0.0}/python_weather.egg-info/SOURCES.txt +0 -0
- {python-weather-1.1.2 → python-weather-2.0.0}/python_weather.egg-info/dependency_links.txt +0 -0
- {python-weather-1.1.2 → python-weather-2.0.0}/python_weather.egg-info/requires.txt +0 -0
- {python-weather-1.1.2 → python-weather-2.0.0}/python_weather.egg-info/top_level.txt +0 -0
- {python-weather-1.1.2 → python-weather-2.0.0}/setup.cfg +0 -0
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-weather
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: A free and asynchronous weather API wrapper made in Python, for Python.
|
|
5
5
|
Author: null8626
|
|
6
6
|
License: MIT
|
|
7
|
-
Project-URL:
|
|
8
|
-
Project-URL:
|
|
9
|
-
Project-URL:
|
|
10
|
-
Project-URL:
|
|
11
|
-
Project-URL: download_url, https://github.com/null8626/python-weather/archive/1.1.2.tar.gz
|
|
7
|
+
Project-URL: Changelog, https://python-weather.readthedocs.io/en/latest/changelog.html
|
|
8
|
+
Project-URL: Homepage, https://python-weather.readthedocs.io/en/latest/
|
|
9
|
+
Project-URL: Documentation, https://python-weather.readthedocs.io/en/latest/
|
|
10
|
+
Project-URL: Repository, https://github.com/null8626/python-weather
|
|
12
11
|
Keywords: weather,forecast,weather-api,weather-forecast
|
|
13
12
|
Classifier: Development Status :: 5 - Production/Stable
|
|
14
13
|
Classifier: Framework :: aiohttp
|
|
@@ -36,19 +35,11 @@ Description-Content-Type: text/markdown
|
|
|
36
35
|
License-File: LICENSE
|
|
37
36
|
Requires-Dist: aiohttp==3.9.3
|
|
38
37
|
|
|
39
|
-
# [python-weather][pypi-url] [![pypi][pypi-image]][pypi-url] [![downloads][downloads-image]][pypi-url]
|
|
38
|
+
# [python-weather][pypi-url] [![pypi][pypi-image]][pypi-url] [![downloads][downloads-image]][pypi-url]
|
|
40
39
|
|
|
41
40
|
[pypi-image]: https://img.shields.io/pypi/v/python-weather.svg?style=flat-square
|
|
42
41
|
[pypi-url]: https://pypi.org/project/python-weather/
|
|
43
42
|
[downloads-image]: https://img.shields.io/pypi/dm/python-weather?style=flat-square
|
|
44
|
-
[ci-image]: https://github.com/null8626/python-weather/workflows/CI/badge.svg
|
|
45
|
-
[ci-url]: https://github.com/null8626/python-weather/actions/workflows/CI.yml
|
|
46
|
-
[doc-ci-image]: https://readthedocs.org/projects/python-weather/badge/?version=latest
|
|
47
|
-
[doc-ci-url]: https://python-weather.readthedocs.io/en/latest/?badge=latest
|
|
48
|
-
[github-license-image]: https://img.shields.io/github/license/null8626/python-weather?style=flat-square
|
|
49
|
-
[github-license-url]: https://github.com/null8626/python-weather/blob/main/LICENSE
|
|
50
|
-
[blazingly-fast-image]: https://img.shields.io/badge/speed-BLAZINGLY%20FAST!!!%20%F0%9F%94%A5%F0%9F%9A%80%F0%9F%92%AA%F0%9F%98%8E-brightgreen.svg?style=flat-square
|
|
51
|
-
[blazingly-fast-url]: https://twitter.com/acdlite/status/974390255393505280
|
|
52
43
|
|
|
53
44
|
A free and asynchronous weather Python API wrapper made in Python, for Python.
|
|
54
45
|
|
|
@@ -76,14 +67,14 @@ async def getweather():
|
|
|
76
67
|
weather = await client.get('New York')
|
|
77
68
|
|
|
78
69
|
# returns the current day's forecast temperature (int)
|
|
79
|
-
print(weather.
|
|
70
|
+
print(weather.temperature)
|
|
80
71
|
|
|
81
72
|
# get the weather forecast for a few days
|
|
82
|
-
for
|
|
83
|
-
print(
|
|
73
|
+
for daily in weather.daily_forecasts:
|
|
74
|
+
print(daily)
|
|
84
75
|
|
|
85
76
|
# hourly forecasts
|
|
86
|
-
for hourly in
|
|
77
|
+
for hourly in daily.hourly_forecasts:
|
|
87
78
|
print(f' --> {hourly!r}')
|
|
88
79
|
|
|
89
80
|
if __name__ == '__main__':
|
|
@@ -1,16 +1,8 @@
|
|
|
1
|
-
# [python-weather][pypi-url] [![pypi][pypi-image]][pypi-url] [![downloads][downloads-image]][pypi-url]
|
|
1
|
+
# [python-weather][pypi-url] [![pypi][pypi-image]][pypi-url] [![downloads][downloads-image]][pypi-url]
|
|
2
2
|
|
|
3
3
|
[pypi-image]: https://img.shields.io/pypi/v/python-weather.svg?style=flat-square
|
|
4
4
|
[pypi-url]: https://pypi.org/project/python-weather/
|
|
5
5
|
[downloads-image]: https://img.shields.io/pypi/dm/python-weather?style=flat-square
|
|
6
|
-
[ci-image]: https://github.com/null8626/python-weather/workflows/CI/badge.svg
|
|
7
|
-
[ci-url]: https://github.com/null8626/python-weather/actions/workflows/CI.yml
|
|
8
|
-
[doc-ci-image]: https://readthedocs.org/projects/python-weather/badge/?version=latest
|
|
9
|
-
[doc-ci-url]: https://python-weather.readthedocs.io/en/latest/?badge=latest
|
|
10
|
-
[github-license-image]: https://img.shields.io/github/license/null8626/python-weather?style=flat-square
|
|
11
|
-
[github-license-url]: https://github.com/null8626/python-weather/blob/main/LICENSE
|
|
12
|
-
[blazingly-fast-image]: https://img.shields.io/badge/speed-BLAZINGLY%20FAST!!!%20%F0%9F%94%A5%F0%9F%9A%80%F0%9F%92%AA%F0%9F%98%8E-brightgreen.svg?style=flat-square
|
|
13
|
-
[blazingly-fast-url]: https://twitter.com/acdlite/status/974390255393505280
|
|
14
6
|
|
|
15
7
|
A free and asynchronous weather Python API wrapper made in Python, for Python.
|
|
16
8
|
|
|
@@ -38,14 +30,14 @@ async def getweather():
|
|
|
38
30
|
weather = await client.get('New York')
|
|
39
31
|
|
|
40
32
|
# returns the current day's forecast temperature (int)
|
|
41
|
-
print(weather.
|
|
33
|
+
print(weather.temperature)
|
|
42
34
|
|
|
43
35
|
# get the weather forecast for a few days
|
|
44
|
-
for
|
|
45
|
-
print(
|
|
36
|
+
for daily in weather.daily_forecasts:
|
|
37
|
+
print(daily)
|
|
46
38
|
|
|
47
39
|
# hourly forecasts
|
|
48
|
-
for hourly in
|
|
40
|
+
for hourly in daily.hourly_forecasts:
|
|
49
41
|
print(f' --> {hourly!r}')
|
|
50
42
|
|
|
51
43
|
if __name__ == '__main__':
|
|
@@ -3,7 +3,7 @@ requires = ["setuptools"]
|
|
|
3
3
|
|
|
4
4
|
[project]
|
|
5
5
|
name = "python-weather"
|
|
6
|
-
version = "
|
|
6
|
+
version = "2.0.0"
|
|
7
7
|
description = "A free and asynchronous weather API wrapper made in Python, for Python."
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
license = { text = "MIT" }
|
|
@@ -36,8 +36,7 @@ classifiers = [
|
|
|
36
36
|
requires-python = ">=3.8"
|
|
37
37
|
|
|
38
38
|
[project.urls]
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
download_url = "https://github.com/null8626/python-weather/archive/1.1.2.tar.gz"
|
|
39
|
+
Changelog = "https://python-weather.readthedocs.io/en/latest/changelog.html"
|
|
40
|
+
Homepage = "https://python-weather.readthedocs.io/en/latest/"
|
|
41
|
+
Documentation = "https://python-weather.readthedocs.io/en/latest/"
|
|
42
|
+
Repository = "https://github.com/null8626/python-weather"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""
|
|
2
|
+
python-weather
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .enums import HeatIndex, Kind, Locale, Phase, UltraViolet, WindDirection
|
|
6
|
+
from .constants import METRIC, IMPERIAL
|
|
7
|
+
from .client import Client
|
|
8
|
+
from .errors import Error
|
|
9
|
+
|
|
10
|
+
__version__ = '2.0.0'
|
|
11
|
+
__all__ = (
|
|
12
|
+
'METRIC', 'IMPERIAL', 'Client', 'Error', 'HeatIndex', 'Kind', 'Locale',
|
|
13
|
+
'Phase', 'UltraViolet', 'WindDirection'
|
|
14
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from enum import auto, Enum
|
|
2
2
|
|
|
3
|
-
from .enums import WindDirection, Kind, Locale,
|
|
3
|
+
from .enums import WindDirection, Kind, Locale, UltraViolet
|
|
4
4
|
from .constants import _Unit
|
|
5
5
|
|
|
6
6
|
class CustomizableBase:
|
|
@@ -60,10 +60,10 @@ class BaseForecast(CustomizableBase):
|
|
|
60
60
|
super().__init__(unit, locale)
|
|
61
61
|
|
|
62
62
|
@property
|
|
63
|
-
def ultraviolet(self) ->
|
|
64
|
-
"""The
|
|
63
|
+
def ultraviolet(self) -> UltraViolet:
|
|
64
|
+
"""The ultra-violet (UV) index."""
|
|
65
65
|
|
|
66
|
-
return
|
|
66
|
+
return UltraViolet._new(int(self.__inner['uvIndex']))
|
|
67
67
|
|
|
68
68
|
@property
|
|
69
69
|
def feels_like(self) -> int:
|
|
@@ -81,7 +81,7 @@ class BaseForecast(CustomizableBase):
|
|
|
81
81
|
|
|
82
82
|
@property
|
|
83
83
|
def temperature(self) -> int:
|
|
84
|
-
"""The
|
|
84
|
+
"""The temperature in either Celcius or Fahrenheit."""
|
|
85
85
|
|
|
86
86
|
return int(self.__inner[f'temp_{self._CustomizableBase__unit.temperature}'])
|
|
87
87
|
|
|
@@ -129,13 +129,12 @@ class BaseForecast(CustomizableBase):
|
|
|
129
129
|
def description(self) -> str:
|
|
130
130
|
"""The description regarding the forecast. This can be localized in different languages depending on the localization used."""
|
|
131
131
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
f'lang_{self._CustomizableBase__locale.value}'][0]['value']
|
|
132
|
+
description = self.__inner['weatherDesc'][0]['value'] if self._CustomizableBase__locale == Locale.ENGLISH else self.__inner[f'lang_{self._CustomizableBase__locale.value}'][0]['value'] # yapf: disable
|
|
133
|
+
|
|
134
|
+
return description.strip()
|
|
136
135
|
|
|
137
136
|
@property
|
|
138
137
|
def kind(self) -> Kind:
|
|
139
|
-
"""The kind of the
|
|
138
|
+
"""The kind of the forecast."""
|
|
140
139
|
|
|
141
140
|
return Kind(int(self.__inner['weatherCode']))
|
|
@@ -6,7 +6,7 @@ from enum import auto
|
|
|
6
6
|
|
|
7
7
|
from .constants import _Unit, METRIC
|
|
8
8
|
from .base import CustomizableBase
|
|
9
|
-
from .forecast import
|
|
9
|
+
from .forecast import Forecast
|
|
10
10
|
from .errors import Error
|
|
11
11
|
from .enums import Locale
|
|
12
12
|
|
|
@@ -16,7 +16,7 @@ class Client(CustomizableBase):
|
|
|
16
16
|
|
|
17
17
|
:param unit: Whether to use the metric or imperial/customary system (``IMPERIAL``). Defaults to ``METRIC``.
|
|
18
18
|
:type unit: Optional[:py:class:`enum.auto`]
|
|
19
|
-
:param locale: Whether to use a different locale/language as the description for the returned
|
|
19
|
+
:param locale: Whether to use a different locale/language as the description for the returned forecast. Defaults to ``Locale.ENGLISH``.
|
|
20
20
|
:type locale: Optional[Locale]
|
|
21
21
|
:param session: Whether to use an existing aiohttp client session for requesting or not. Defaults to ``None`` (creates a new one instead)
|
|
22
22
|
:type session: Optional[:class:`aiohttp.ClientSession`]
|
|
@@ -49,7 +49,7 @@ class Client(CustomizableBase):
|
|
|
49
49
|
*,
|
|
50
50
|
unit: Optional[auto] = None,
|
|
51
51
|
locale: Optional[Locale] = None
|
|
52
|
-
) ->
|
|
52
|
+
) -> Forecast:
|
|
53
53
|
"""
|
|
54
54
|
Fetches a weather forecast for a specific location.
|
|
55
55
|
|
|
@@ -63,7 +63,7 @@ class Client(CustomizableBase):
|
|
|
63
63
|
:exception Error: If the aiohttp client session used by the :class:`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:`Locale` enum, or if the :class:`Client` cannot send a web request to the web server.
|
|
64
64
|
|
|
65
65
|
:returns: The requested weather forecast.
|
|
66
|
-
:rtype:
|
|
66
|
+
:rtype: Forecast
|
|
67
67
|
"""
|
|
68
68
|
|
|
69
69
|
if (not isinstance(location, str)) or (not location):
|
|
@@ -89,7 +89,7 @@ class Client(CustomizableBase):
|
|
|
89
89
|
f'https://{subdomain}wttr.in/{quote_plus(location)}?format=j1'
|
|
90
90
|
) as resp:
|
|
91
91
|
try:
|
|
92
|
-
return
|
|
92
|
+
return Forecast(await resp.json(), unit, locale)
|
|
93
93
|
except Exception as e:
|
|
94
94
|
if delay == 4:
|
|
95
95
|
raise e # okay, that's too much requests - just raise the error
|
|
@@ -30,4 +30,5 @@ class _Unit:
|
|
|
30
30
|
METRIC = _Unit('C', 'Kmph', '', 'MM', '', 1)
|
|
31
31
|
IMPERIAL = _Unit('F', 'Miles', 'Inches', 'Inches', 'Miles', 2.54)
|
|
32
32
|
|
|
33
|
+
WIND_DIRECTION_EMOJIS = ['↓', '↙', '←', '↖', '↑', '↗', '→', '↘']
|
|
33
34
|
LATLON_REGEX = compile(r'^Lat (\-?[\d\.]+) and Lon (\-?[\d\.]+)$')
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from enum import Enum, auto
|
|
2
2
|
from typing import Union
|
|
3
3
|
|
|
4
|
+
from .constants import WIND_DIRECTION_EMOJIS
|
|
4
5
|
from .errors import Error
|
|
5
6
|
|
|
6
7
|
class BasicEnum(Enum):
|
|
@@ -12,55 +13,22 @@ class BasicEnum(Enum):
|
|
|
12
13
|
def __str__(self) -> str:
|
|
13
14
|
return self.name.replace('_', ' ').title()
|
|
14
15
|
|
|
15
|
-
class
|
|
16
|
-
"""Represents UV (ultraviolet) index."""
|
|
17
|
-
|
|
16
|
+
class IndexedEnum(Enum):
|
|
18
17
|
__slots__ = ('__index',)
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
MODERATE = auto()
|
|
22
|
-
HIGH = auto()
|
|
23
|
-
VERY_HIGH = auto()
|
|
24
|
-
EXTREME = auto()
|
|
25
|
-
|
|
26
|
-
def _new(index: int):
|
|
27
|
-
enum = Ultraviolet(index)
|
|
28
|
-
enum.__index = index
|
|
29
|
-
|
|
30
|
-
return enum
|
|
31
|
-
|
|
32
|
-
@classmethod
|
|
33
|
-
def _missing_(self, index: int):
|
|
34
|
-
if index <= 2:
|
|
35
|
-
return self.LOW
|
|
36
|
-
elif index <= 5:
|
|
37
|
-
return self.MODERATE
|
|
38
|
-
elif index <= 7:
|
|
39
|
-
return self.HIGH
|
|
40
|
-
elif index <= 10:
|
|
41
|
-
return self.VERY_HIGH
|
|
42
|
-
else:
|
|
43
|
-
return self.EXTREME
|
|
44
|
-
|
|
45
|
-
def __repr__(self) -> str:
|
|
46
|
-
return f'<{self.__class__.__name__}.{self.name} index={self.__index}>'
|
|
47
|
-
|
|
48
|
-
def __str__(self) -> str:
|
|
49
|
-
return self.name.replace('_', ' ').title()
|
|
50
|
-
|
|
51
|
-
def __lt__(self, other: Union["Ultraviolet", int, float]) -> bool:
|
|
19
|
+
def __lt__(self, other: Union["Self", int, float]) -> bool:
|
|
52
20
|
if isinstance(other, self.__class__):
|
|
53
21
|
return self.__index < other.index
|
|
54
22
|
else:
|
|
55
23
|
return self.__index < other
|
|
56
24
|
|
|
57
|
-
def __eq__(self, other: Union["
|
|
25
|
+
def __eq__(self, other: Union["Self", int, float]) -> bool:
|
|
58
26
|
if isinstance(other, self.__class__):
|
|
59
27
|
return self.__index == other.index
|
|
60
28
|
else:
|
|
61
29
|
return self.__index == other
|
|
62
30
|
|
|
63
|
-
def __gt__(self, other: Union["
|
|
31
|
+
def __gt__(self, other: Union["Self", int, float]) -> bool:
|
|
64
32
|
if isinstance(other, self.__class__):
|
|
65
33
|
return self.__index > other.index
|
|
66
34
|
else:
|
|
@@ -74,11 +42,68 @@ class Ultraviolet(Enum):
|
|
|
74
42
|
|
|
75
43
|
@property
|
|
76
44
|
def index(self) -> int:
|
|
77
|
-
"""The
|
|
45
|
+
"""The index value."""
|
|
78
46
|
|
|
79
47
|
return self.__index
|
|
48
|
+
|
|
49
|
+
@index.setter
|
|
50
|
+
def index(self, new_index: int) -> int:
|
|
51
|
+
self.__index = new_index
|
|
52
|
+
|
|
53
|
+
class HeatIndex(IndexedEnum):
|
|
54
|
+
"""Represents a heat index."""
|
|
55
|
+
|
|
56
|
+
CAUTION = None
|
|
57
|
+
EXTREME_CAUTION = None
|
|
58
|
+
DANGER = None
|
|
59
|
+
EXTREME_DANGER = None
|
|
60
|
+
|
|
61
|
+
def _new(celcius_index: int, true_index: int):
|
|
62
|
+
enum = HeatIndex(celcius_index)
|
|
63
|
+
enum.index = true_index
|
|
64
|
+
|
|
65
|
+
return enum
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def _missing_(self, celcius_index: int):
|
|
69
|
+
if celcius_index <= 32:
|
|
70
|
+
return self.CAUTION
|
|
71
|
+
elif celcius_index <= 39:
|
|
72
|
+
return self.EXTREME_CAUTION
|
|
73
|
+
elif celcius_index <= 51:
|
|
74
|
+
return self.DANGER
|
|
75
|
+
else:
|
|
76
|
+
return self.EXTREME_DANGER
|
|
77
|
+
|
|
78
|
+
class UltraViolet(BasicEnum, IndexedEnum):
|
|
79
|
+
"""Represents ultra-violet (UV) index."""
|
|
80
|
+
|
|
81
|
+
LOW = None
|
|
82
|
+
MODERATE = None
|
|
83
|
+
HIGH = None
|
|
84
|
+
VERY_HIGH = None
|
|
85
|
+
EXTREME = None
|
|
86
|
+
|
|
87
|
+
def _new(index: int):
|
|
88
|
+
enum = UltraViolet(index)
|
|
89
|
+
enum.index = index
|
|
90
|
+
|
|
91
|
+
return enum
|
|
92
|
+
|
|
93
|
+
@classmethod
|
|
94
|
+
def _missing_(self, index: int):
|
|
95
|
+
if index <= 2:
|
|
96
|
+
return self.LOW
|
|
97
|
+
elif index <= 5:
|
|
98
|
+
return self.MODERATE
|
|
99
|
+
elif index <= 7:
|
|
100
|
+
return self.HIGH
|
|
101
|
+
elif index <= 10:
|
|
102
|
+
return self.VERY_HIGH
|
|
103
|
+
else:
|
|
104
|
+
return self.EXTREME
|
|
80
105
|
|
|
81
|
-
class WindDirection(
|
|
106
|
+
class WindDirection(BasicEnum):
|
|
82
107
|
"""Represents a wind direction."""
|
|
83
108
|
|
|
84
109
|
__slots__ = ('__degrees',)
|
|
@@ -106,12 +131,6 @@ class WindDirection(Enum):
|
|
|
106
131
|
|
|
107
132
|
return enum
|
|
108
133
|
|
|
109
|
-
def __repr__(self) -> str:
|
|
110
|
-
return f'<{self.__class__.__name__}.{self.name} degrees={self.__degrees!r}>'
|
|
111
|
-
|
|
112
|
-
def __str__(self) -> str:
|
|
113
|
-
return self.name.replace('_', ' ').title()
|
|
114
|
-
|
|
115
134
|
def __contains__(self, other: Union["WindDirection", float, int]) -> bool:
|
|
116
135
|
if isinstance(other, self.__class__):
|
|
117
136
|
other = other.degrees
|
|
@@ -149,11 +168,20 @@ class WindDirection(Enum):
|
|
|
149
168
|
else:
|
|
150
169
|
return 326.25 < other <= 348.75
|
|
151
170
|
|
|
171
|
+
def __float__(self) -> float:
|
|
172
|
+
return self.__degrees
|
|
173
|
+
|
|
152
174
|
@property
|
|
153
175
|
def degrees(self) -> int:
|
|
154
176
|
"""The wind direction's value in degrees."""
|
|
155
177
|
|
|
156
178
|
return self.__degrees
|
|
179
|
+
|
|
180
|
+
@property
|
|
181
|
+
def emoji(self) -> str:
|
|
182
|
+
"""The emoji representing this enum."""
|
|
183
|
+
|
|
184
|
+
return WIND_DIRECTION_EMOJIS[int(((self.__degrees + 22.5) % 360) / 45.0)]
|
|
157
185
|
|
|
158
186
|
class Locale(Enum):
|
|
159
187
|
"""Represents the list of supported locales/languages by this library."""
|
|
@@ -1,133 +1,10 @@
|
|
|
1
|
-
import warnings
|
|
2
1
|
from typing import Iterable, Optional, Tuple
|
|
3
2
|
from datetime import datetime, date, time
|
|
4
3
|
from enum import auto
|
|
5
4
|
|
|
6
5
|
from .base import BaseForecast, CustomizableBase
|
|
6
|
+
from .enums import Phase, Locale, HeatIndex
|
|
7
7
|
from .constants import _Unit, LATLON_REGEX
|
|
8
|
-
from .enums import Phase, Locale
|
|
9
|
-
|
|
10
|
-
class Area:
|
|
11
|
-
"""Represents the location of the weather forecast."""
|
|
12
|
-
|
|
13
|
-
__slots__ = ('__inner',)
|
|
14
|
-
|
|
15
|
-
def __init__(self, json: dict):
|
|
16
|
-
self.__inner = json
|
|
17
|
-
|
|
18
|
-
def __repr__(self) -> str:
|
|
19
|
-
return f'<{self.__class__.__name__} name={self.name!r} country={self.country!r} region={self.region!r}>'
|
|
20
|
-
|
|
21
|
-
@property
|
|
22
|
-
def latitude(self) -> float:
|
|
23
|
-
"""The latitude."""
|
|
24
|
-
|
|
25
|
-
return float(self.__inner['latitude'])
|
|
26
|
-
|
|
27
|
-
@property
|
|
28
|
-
def longitude(self) -> float:
|
|
29
|
-
"""The longitude."""
|
|
30
|
-
|
|
31
|
-
return float(self.__inner['longitude'])
|
|
32
|
-
|
|
33
|
-
@property
|
|
34
|
-
def population(self) -> int:
|
|
35
|
-
"""The location's population count."""
|
|
36
|
-
|
|
37
|
-
return int(self.__inner['population'])
|
|
38
|
-
|
|
39
|
-
@property
|
|
40
|
-
def region(self) -> str:
|
|
41
|
-
"""The location's region name."""
|
|
42
|
-
|
|
43
|
-
return self.__inner['region'][0]['value']
|
|
44
|
-
|
|
45
|
-
@property
|
|
46
|
-
def name(self) -> str:
|
|
47
|
-
"""The location's name."""
|
|
48
|
-
|
|
49
|
-
return self.__inner['areaName'][0]['value']
|
|
50
|
-
|
|
51
|
-
@property
|
|
52
|
-
def country(self) -> str:
|
|
53
|
-
"""The location's country name."""
|
|
54
|
-
|
|
55
|
-
return self.__inner['country'][0]['value']
|
|
56
|
-
|
|
57
|
-
class Astronomy:
|
|
58
|
-
"""Represents the astronomical information of said weather forecast."""
|
|
59
|
-
|
|
60
|
-
__slots__ = ('__inner',)
|
|
61
|
-
|
|
62
|
-
def __init__(self, json: dict):
|
|
63
|
-
self.__inner = json
|
|
64
|
-
|
|
65
|
-
def __repr__(self) -> str:
|
|
66
|
-
return f'<{self.__class__.__name__} moon_phase="{self.moon_phase!r}" sun_rise={self.sun_rise!r} sun_set={self.sun_set!r}>'
|
|
67
|
-
|
|
68
|
-
@property
|
|
69
|
-
def moon_illumination(self) -> int:
|
|
70
|
-
"""The percentage of the moon illuminated."""
|
|
71
|
-
|
|
72
|
-
return int(self.__inner['moon_illumination'])
|
|
73
|
-
|
|
74
|
-
@property
|
|
75
|
-
def moon_phase(self) -> Phase:
|
|
76
|
-
"""The moon's phase."""
|
|
77
|
-
|
|
78
|
-
return Phase(self.__inner['moon_phase'])
|
|
79
|
-
|
|
80
|
-
@property
|
|
81
|
-
def moon_rise(self) -> Optional[time]:
|
|
82
|
-
"""The local time when the moon rises. This can be ``None``."""
|
|
83
|
-
|
|
84
|
-
try:
|
|
85
|
-
return datetime.strptime(self.__inner['moonrise'], '%I:%M %p').time()
|
|
86
|
-
except ValueError:
|
|
87
|
-
...
|
|
88
|
-
|
|
89
|
-
@property
|
|
90
|
-
def moon_set(self) -> time:
|
|
91
|
-
"""The local time when the moon sets. This can be ``None``."""
|
|
92
|
-
|
|
93
|
-
try:
|
|
94
|
-
return datetime.strptime(self.__inner['moonset'], '%I:%M %p').time()
|
|
95
|
-
except ValueError:
|
|
96
|
-
...
|
|
97
|
-
|
|
98
|
-
@property
|
|
99
|
-
def sun_rise(self) -> Optional[time]:
|
|
100
|
-
"""The local time when the sun rises. This can be ``None``."""
|
|
101
|
-
|
|
102
|
-
try:
|
|
103
|
-
return datetime.strptime(self.__inner['sunrise'], '%I:%M %p').time()
|
|
104
|
-
except ValueError:
|
|
105
|
-
...
|
|
106
|
-
|
|
107
|
-
@property
|
|
108
|
-
def sun_set(self) -> Optional[time]:
|
|
109
|
-
"""The local time when the sun sets. This can be ``None``."""
|
|
110
|
-
|
|
111
|
-
try:
|
|
112
|
-
return datetime.strptime(self.__inner['sunset'], '%I:%M %p').time()
|
|
113
|
-
except ValueError:
|
|
114
|
-
...
|
|
115
|
-
|
|
116
|
-
class CurrentForecast(BaseForecast):
|
|
117
|
-
"""Represents a weather forecast of the current day."""
|
|
118
|
-
|
|
119
|
-
__slots__ = ()
|
|
120
|
-
|
|
121
|
-
def __repr__(self) -> str:
|
|
122
|
-
return f'<{self.__class__.__name__} temperature={self.temperature!r} description={self.description!r} kind="{self.kind!r}">'
|
|
123
|
-
|
|
124
|
-
@property
|
|
125
|
-
def date(self) -> datetime:
|
|
126
|
-
"""The local date of this weather forecast."""
|
|
127
|
-
|
|
128
|
-
return datetime.strptime(
|
|
129
|
-
self._BaseForecast__inner['localObsDateTime'], '%Y-%m-%d %I:%M %p'
|
|
130
|
-
)
|
|
131
8
|
|
|
132
9
|
class HourlyForecast(BaseForecast):
|
|
133
10
|
"""Represents a weather forecast of a specific hour."""
|
|
@@ -155,11 +32,14 @@ class HourlyForecast(BaseForecast):
|
|
|
155
32
|
) # yapf: disable
|
|
156
33
|
|
|
157
34
|
@property
|
|
158
|
-
def heat_index(self) ->
|
|
35
|
+
def heat_index(self) -> HeatIndex:
|
|
159
36
|
"""The heat index in either Celcius or Fahrenheit."""
|
|
160
37
|
|
|
161
|
-
|
|
162
|
-
|
|
38
|
+
celcius_index = int(self._BaseForecast__inner['HeatIndexC'])
|
|
39
|
+
|
|
40
|
+
return HeatIndex._new(
|
|
41
|
+
celcius_index,
|
|
42
|
+
int(self._BaseForecast__inner[f'HeatIndex{self._CustomizableBase__unit.temperature}'])
|
|
163
43
|
) # yapf: disable
|
|
164
44
|
|
|
165
45
|
@property
|
|
@@ -190,7 +70,7 @@ class HourlyForecast(BaseForecast):
|
|
|
190
70
|
return int(self._BaseForecast__inner['chanceoffrost'])
|
|
191
71
|
|
|
192
72
|
@property
|
|
193
|
-
def
|
|
73
|
+
def chances_of_high_temperature(self) -> int:
|
|
194
74
|
"""Chances of a high temperature in percent."""
|
|
195
75
|
|
|
196
76
|
return int(self._BaseForecast__inner['chanceofhightemp'])
|
|
@@ -213,16 +93,6 @@ class HourlyForecast(BaseForecast):
|
|
|
213
93
|
|
|
214
94
|
return int(self._BaseForecast__inner['chanceofremdry'])
|
|
215
95
|
|
|
216
|
-
@property
|
|
217
|
-
def chances_of_remdry(self) -> int:
|
|
218
|
-
"""Deprecated, use chances_of_remaining_dry instead."""
|
|
219
|
-
|
|
220
|
-
warnings.warn(
|
|
221
|
-
'Deprecated as of v1.1.2. Use `chances_of_remaining_dry` instead.',
|
|
222
|
-
DeprecationWarning
|
|
223
|
-
)
|
|
224
|
-
return self.chances_of_remaining_dry
|
|
225
|
-
|
|
226
96
|
@property
|
|
227
97
|
def chances_of_snow(self) -> int:
|
|
228
98
|
"""Chances of a snow in percent."""
|
|
@@ -260,21 +130,64 @@ class HourlyForecast(BaseForecast):
|
|
|
260
130
|
return time() if len(self._BaseForecast__inner['time']) < 3 else datetime.strptime(self._BaseForecast__inner['time'], '%H%M').time() # yapf: disable
|
|
261
131
|
|
|
262
132
|
class DailyForecast(CustomizableBase):
|
|
263
|
-
__slots__ = ('__inner',)
|
|
133
|
+
__slots__ = ('__inner', '__astronomy')
|
|
264
134
|
|
|
265
135
|
def __init__(self, json: dict, unit: _Unit, locale: Locale):
|
|
136
|
+
self.__astronomy = json.pop('astronomy')[0]
|
|
266
137
|
self.__inner = json
|
|
267
138
|
|
|
268
139
|
super().__init__(unit, locale)
|
|
269
140
|
|
|
270
141
|
def __repr__(self) -> str:
|
|
271
|
-
return f'<{self.__class__.__name__} date={self.date!r}
|
|
142
|
+
return f'<{self.__class__.__name__} date={self.date!r} temperature={self.temperature!r}>'
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def moon_illumination(self) -> int:
|
|
146
|
+
"""The percentage of the moon illuminated."""
|
|
147
|
+
|
|
148
|
+
return int(self.__astronomy['moon_illumination'])
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def moon_phase(self) -> Phase:
|
|
152
|
+
"""The moon's phase."""
|
|
153
|
+
|
|
154
|
+
return Phase(self.__astronomy['moon_phase'])
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def moonrise(self) -> Optional[time]:
|
|
158
|
+
"""The local time when the moon rises. This can be ``None``."""
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
return datetime.strptime(self.__astronomy['moonrise'], '%I:%M %p').time()
|
|
162
|
+
except ValueError:
|
|
163
|
+
...
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def moonset(self) -> Optional[time]:
|
|
167
|
+
"""The local time when the moon sets. This can be ``None``."""
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
return datetime.strptime(self.__astronomy['moonset'], '%I:%M %p').time()
|
|
171
|
+
except ValueError:
|
|
172
|
+
...
|
|
173
|
+
|
|
174
|
+
@property
|
|
175
|
+
def sunrise(self) -> Optional[time]:
|
|
176
|
+
"""The local time when the sun rises. This can be ``None``."""
|
|
177
|
+
|
|
178
|
+
try:
|
|
179
|
+
return datetime.strptime(self.__astronomy['sunrise'], '%I:%M %p').time()
|
|
180
|
+
except ValueError:
|
|
181
|
+
...
|
|
272
182
|
|
|
273
183
|
@property
|
|
274
|
-
def
|
|
275
|
-
"""The
|
|
184
|
+
def sunset(self) -> Optional[time]:
|
|
185
|
+
"""The local time when the sun sets. This can be ``None``."""
|
|
276
186
|
|
|
277
|
-
|
|
187
|
+
try:
|
|
188
|
+
return datetime.strptime(self.__astronomy['sunset'], '%I:%M %p').time()
|
|
189
|
+
except ValueError:
|
|
190
|
+
...
|
|
278
191
|
|
|
279
192
|
@property
|
|
280
193
|
def date(self) -> date:
|
|
@@ -321,7 +234,7 @@ class DailyForecast(CustomizableBase):
|
|
|
321
234
|
) / self._CustomizableBase__unit.cm_divisor
|
|
322
235
|
|
|
323
236
|
@property
|
|
324
|
-
def
|
|
237
|
+
def hourly_forecasts(self) -> Iterable[HourlyForecast]:
|
|
325
238
|
"""The hourly forecasts of this day."""
|
|
326
239
|
|
|
327
240
|
return (
|
|
@@ -330,37 +243,56 @@ class DailyForecast(CustomizableBase):
|
|
|
330
243
|
) for elem in self.__inner['hourly']
|
|
331
244
|
)
|
|
332
245
|
|
|
333
|
-
class
|
|
334
|
-
"""Represents
|
|
246
|
+
class Forecast(BaseForecast):
|
|
247
|
+
"""Represents today's weather forecast, alongside daily and hourly weather forecasts."""
|
|
335
248
|
|
|
336
|
-
__slots__ = ('__inner',)
|
|
249
|
+
__slots__ = ('__inner', '__nearest')
|
|
337
250
|
|
|
338
251
|
def __init__(self, json: dict, unit: _Unit, locale: Locale):
|
|
252
|
+
current = json['current_condition'][0]
|
|
253
|
+
self.__nearest = json.pop('nearest_area')[0]
|
|
339
254
|
self.__inner = json
|
|
340
255
|
|
|
341
|
-
super().__init__(unit, locale)
|
|
256
|
+
super().__init__(current, unit, locale)
|
|
342
257
|
|
|
343
258
|
def __repr__(self) -> str:
|
|
344
|
-
return f'<{self.__class__.__name__}
|
|
259
|
+
return f'<{self.__class__.__name__} location={self.location!r} datetime={self.datetime!r} temperature={self.temperature!r}>'
|
|
345
260
|
|
|
346
261
|
@property
|
|
347
|
-
def
|
|
348
|
-
"""The
|
|
262
|
+
def local_population(self) -> int:
|
|
263
|
+
"""The local population count."""
|
|
349
264
|
|
|
350
|
-
return
|
|
351
|
-
self.__inner['current_condition'][0], self._CustomizableBase__unit,
|
|
352
|
-
self._CustomizableBase__locale
|
|
353
|
-
)
|
|
265
|
+
return int(self.__nearest['population'])
|
|
354
266
|
|
|
355
267
|
@property
|
|
356
|
-
def
|
|
357
|
-
"""The
|
|
268
|
+
def region(self) -> str:
|
|
269
|
+
"""The local region's name."""
|
|
358
270
|
|
|
359
|
-
return
|
|
271
|
+
return self.__nearest['region'][0]['value']
|
|
360
272
|
|
|
361
273
|
@property
|
|
362
|
-
def
|
|
363
|
-
"""
|
|
274
|
+
def location(self) -> str:
|
|
275
|
+
"""The location's name."""
|
|
276
|
+
|
|
277
|
+
return self.__nearest['areaName'][0]['value']
|
|
278
|
+
|
|
279
|
+
@property
|
|
280
|
+
def country(self) -> str:
|
|
281
|
+
"""The local country's name."""
|
|
282
|
+
|
|
283
|
+
return self.__nearest['country'][0]['value']
|
|
284
|
+
|
|
285
|
+
@property
|
|
286
|
+
def datetime(self) -> datetime:
|
|
287
|
+
"""The local date and time of this weather forecast."""
|
|
288
|
+
|
|
289
|
+
return datetime.strptime(
|
|
290
|
+
self._BaseForecast__inner['localObsDateTime'], '%Y-%m-%d %I:%M %p'
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
@property
|
|
294
|
+
def daily_forecasts(self) -> Iterable[DailyForecast]:
|
|
295
|
+
"""Daily weather forecasts in this location."""
|
|
364
296
|
|
|
365
297
|
return (
|
|
366
298
|
DailyForecast(
|
|
@@ -369,8 +301,8 @@ class Weather(CustomizableBase):
|
|
|
369
301
|
)
|
|
370
302
|
|
|
371
303
|
@property
|
|
372
|
-
def
|
|
373
|
-
"""A tuple of
|
|
304
|
+
def coordinates(self) -> Tuple[float, float]:
|
|
305
|
+
"""A tuple of this forecast's latitude and longitude."""
|
|
374
306
|
|
|
375
307
|
try:
|
|
376
308
|
for req in filter(
|
|
@@ -380,4 +312,4 @@ class Weather(CustomizableBase):
|
|
|
380
312
|
|
|
381
313
|
return float(lat), float(lon)
|
|
382
314
|
except:
|
|
383
|
-
|
|
315
|
+
return float(self.__nearest['latitude']), float(self.__nearest['longitude']) # yapf: disable
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-weather
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: A free and asynchronous weather API wrapper made in Python, for Python.
|
|
5
5
|
Author: null8626
|
|
6
6
|
License: MIT
|
|
7
|
-
Project-URL:
|
|
8
|
-
Project-URL:
|
|
9
|
-
Project-URL:
|
|
10
|
-
Project-URL:
|
|
11
|
-
Project-URL: download_url, https://github.com/null8626/python-weather/archive/1.1.2.tar.gz
|
|
7
|
+
Project-URL: Changelog, https://python-weather.readthedocs.io/en/latest/changelog.html
|
|
8
|
+
Project-URL: Homepage, https://python-weather.readthedocs.io/en/latest/
|
|
9
|
+
Project-URL: Documentation, https://python-weather.readthedocs.io/en/latest/
|
|
10
|
+
Project-URL: Repository, https://github.com/null8626/python-weather
|
|
12
11
|
Keywords: weather,forecast,weather-api,weather-forecast
|
|
13
12
|
Classifier: Development Status :: 5 - Production/Stable
|
|
14
13
|
Classifier: Framework :: aiohttp
|
|
@@ -36,19 +35,11 @@ Description-Content-Type: text/markdown
|
|
|
36
35
|
License-File: LICENSE
|
|
37
36
|
Requires-Dist: aiohttp==3.9.3
|
|
38
37
|
|
|
39
|
-
# [python-weather][pypi-url] [![pypi][pypi-image]][pypi-url] [![downloads][downloads-image]][pypi-url]
|
|
38
|
+
# [python-weather][pypi-url] [![pypi][pypi-image]][pypi-url] [![downloads][downloads-image]][pypi-url]
|
|
40
39
|
|
|
41
40
|
[pypi-image]: https://img.shields.io/pypi/v/python-weather.svg?style=flat-square
|
|
42
41
|
[pypi-url]: https://pypi.org/project/python-weather/
|
|
43
42
|
[downloads-image]: https://img.shields.io/pypi/dm/python-weather?style=flat-square
|
|
44
|
-
[ci-image]: https://github.com/null8626/python-weather/workflows/CI/badge.svg
|
|
45
|
-
[ci-url]: https://github.com/null8626/python-weather/actions/workflows/CI.yml
|
|
46
|
-
[doc-ci-image]: https://readthedocs.org/projects/python-weather/badge/?version=latest
|
|
47
|
-
[doc-ci-url]: https://python-weather.readthedocs.io/en/latest/?badge=latest
|
|
48
|
-
[github-license-image]: https://img.shields.io/github/license/null8626/python-weather?style=flat-square
|
|
49
|
-
[github-license-url]: https://github.com/null8626/python-weather/blob/main/LICENSE
|
|
50
|
-
[blazingly-fast-image]: https://img.shields.io/badge/speed-BLAZINGLY%20FAST!!!%20%F0%9F%94%A5%F0%9F%9A%80%F0%9F%92%AA%F0%9F%98%8E-brightgreen.svg?style=flat-square
|
|
51
|
-
[blazingly-fast-url]: https://twitter.com/acdlite/status/974390255393505280
|
|
52
43
|
|
|
53
44
|
A free and asynchronous weather Python API wrapper made in Python, for Python.
|
|
54
45
|
|
|
@@ -76,14 +67,14 @@ async def getweather():
|
|
|
76
67
|
weather = await client.get('New York')
|
|
77
68
|
|
|
78
69
|
# returns the current day's forecast temperature (int)
|
|
79
|
-
print(weather.
|
|
70
|
+
print(weather.temperature)
|
|
80
71
|
|
|
81
72
|
# get the weather forecast for a few days
|
|
82
|
-
for
|
|
83
|
-
print(
|
|
73
|
+
for daily in weather.daily_forecasts:
|
|
74
|
+
print(daily)
|
|
84
75
|
|
|
85
76
|
# hourly forecasts
|
|
86
|
-
for hourly in
|
|
77
|
+
for hourly in daily.hourly_forecasts:
|
|
87
78
|
print(f' --> {hourly!r}')
|
|
88
79
|
|
|
89
80
|
if __name__ == '__main__':
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
python-weather
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from .enums import Kind, Locale, Phase, Ultraviolet, WindDirection
|
|
6
|
-
from .constants import METRIC, IMPERIAL
|
|
7
|
-
from .client import Client
|
|
8
|
-
from .errors import Error
|
|
9
|
-
|
|
10
|
-
__version__ = '1.0.3'
|
|
11
|
-
__all__ = (
|
|
12
|
-
'METRIC', 'IMPERIAL', 'Client', 'Error', 'Kind', 'Locale', 'Phase',
|
|
13
|
-
'Ultraviolet', 'WindDirection'
|
|
14
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|