python-weather 2.0.7__tar.gz → 2.1.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.0.7 → python_weather-2.1.0}/LICENSE +1 -1
- {python_weather-2.0.7/python_weather.egg-info → python_weather-2.1.0}/PKG-INFO +24 -18
- {python_weather-2.0.7 → python_weather-2.1.0}/README.md +19 -13
- {python_weather-2.0.7 → python_weather-2.1.0}/pyproject.toml +3 -4
- {python_weather-2.0.7 → python_weather-2.1.0}/python_weather/__init__.py +6 -3
- {python_weather-2.0.7 → python_weather-2.1.0}/python_weather/base.py +19 -19
- {python_weather-2.0.7 → python_weather-2.1.0}/python_weather/client.py +37 -30
- {python_weather-2.0.7 → python_weather-2.1.0}/python_weather/constants.py +4 -4
- {python_weather-2.0.7 → python_weather-2.1.0}/python_weather/enums.py +35 -26
- {python_weather-2.0.7 → python_weather-2.1.0}/python_weather/errors.py +13 -10
- {python_weather-2.0.7 → python_weather-2.1.0}/python_weather/forecast.py +26 -22
- python_weather-2.1.0/python_weather/version.py +1 -0
- {python_weather-2.0.7 → python_weather-2.1.0/python_weather.egg-info}/PKG-INFO +24 -18
- {python_weather-2.0.7 → python_weather-2.1.0}/python_weather.egg-info/SOURCES.txt +1 -0
- python_weather-2.1.0/python_weather.egg-info/requires.txt +1 -0
- python_weather-2.0.7/python_weather.egg-info/requires.txt +0 -1
- {python_weather-2.0.7 → python_weather-2.1.0}/MANIFEST.in +0 -0
- {python_weather-2.0.7 → python_weather-2.1.0}/python_weather.egg-info/dependency_links.txt +0 -0
- {python_weather-2.0.7 → python_weather-2.1.0}/python_weather.egg-info/top_level.txt +0 -0
- {python_weather-2.0.7 → python_weather-2.1.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: python-weather
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: A free and asynchronous weather API wrapper made in Python, for Python.
|
|
5
5
|
Author: null8626
|
|
6
6
|
License: MIT
|
|
@@ -26,16 +26,16 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
26
26
|
Classifier: Programming Language :: Python
|
|
27
27
|
Classifier: Programming Language :: Python :: 3
|
|
28
28
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
29
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
30
29
|
Classifier: Programming Language :: Python :: 3.9
|
|
31
30
|
Classifier: Programming Language :: Python :: 3.10
|
|
32
31
|
Classifier: Programming Language :: Python :: 3.11
|
|
33
32
|
Classifier: Programming Language :: Python :: 3.12
|
|
34
33
|
Classifier: Programming Language :: Python :: 3.13
|
|
35
|
-
Requires-Python: >=3.
|
|
34
|
+
Requires-Python: >=3.9
|
|
36
35
|
Description-Content-Type: text/markdown
|
|
37
36
|
License-File: LICENSE
|
|
38
|
-
Requires-Dist: aiohttp>=3.
|
|
37
|
+
Requires-Dist: aiohttp>=3.12.4
|
|
38
|
+
Dynamic: license-file
|
|
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
|
|
|
@@ -50,10 +50,12 @@ Requires-Dist: aiohttp>=3.10.10
|
|
|
50
50
|
|
|
51
51
|
A free and asynchronous weather Python API wrapper made in Python, for Python.
|
|
52
52
|
|
|
53
|
-
##
|
|
53
|
+
## Getting started
|
|
54
|
+
|
|
55
|
+
Run the following command in your terminal:
|
|
54
56
|
|
|
55
57
|
```console
|
|
56
|
-
pip install python-weather
|
|
58
|
+
$ pip install python-weather
|
|
57
59
|
```
|
|
58
60
|
|
|
59
61
|
## Example
|
|
@@ -61,36 +63,40 @@ pip install python-weather
|
|
|
61
63
|
For more information, please read the [documentation](https://python-weather.readthedocs.io/en/latest/).
|
|
62
64
|
|
|
63
65
|
```py
|
|
64
|
-
#
|
|
66
|
+
# Import the module.
|
|
65
67
|
import python_weather
|
|
66
68
|
|
|
67
69
|
import asyncio
|
|
68
70
|
import os
|
|
69
71
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
+
|
|
73
|
+
async def main() -> None:
|
|
74
|
+
|
|
75
|
+
# Declare the client. The measuring unit used defaults to the metric system (celcius, km/h, etc.)
|
|
72
76
|
async with python_weather.Client(unit=python_weather.IMPERIAL) as client:
|
|
73
|
-
|
|
77
|
+
|
|
78
|
+
# Fetch a weather forecast from a city.
|
|
74
79
|
weather = await client.get('New York')
|
|
75
80
|
|
|
76
|
-
#
|
|
81
|
+
# Fetch the temperature for today.
|
|
77
82
|
print(weather.temperature)
|
|
78
83
|
|
|
79
|
-
#
|
|
84
|
+
# Fetch weather forecast for upcoming days.
|
|
80
85
|
for daily in weather:
|
|
81
86
|
print(daily)
|
|
82
|
-
|
|
83
|
-
# hourly forecasts
|
|
87
|
+
|
|
88
|
+
# Each daily forecast has their own hourly forecasts.
|
|
84
89
|
for hourly in daily:
|
|
85
90
|
print(f' --> {hourly!r}')
|
|
86
91
|
|
|
87
92
|
if __name__ == '__main__':
|
|
88
|
-
|
|
89
|
-
#
|
|
93
|
+
|
|
94
|
+
# See https://stackoverflow.com/questions/45600579/asyncio-event-loop-is-closed-when-getting-loop
|
|
95
|
+
# for more details.
|
|
90
96
|
if os.name == 'nt':
|
|
91
97
|
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
|
92
98
|
|
|
93
|
-
asyncio.run(
|
|
99
|
+
asyncio.run(main())
|
|
94
100
|
```
|
|
95
101
|
|
|
96
102
|
## Data source
|
|
@@ -11,10 +11,12 @@
|
|
|
11
11
|
|
|
12
12
|
A free and asynchronous weather Python API wrapper made in Python, for Python.
|
|
13
13
|
|
|
14
|
-
##
|
|
14
|
+
## Getting started
|
|
15
|
+
|
|
16
|
+
Run the following command in your terminal:
|
|
15
17
|
|
|
16
18
|
```console
|
|
17
|
-
pip install python-weather
|
|
19
|
+
$ pip install python-weather
|
|
18
20
|
```
|
|
19
21
|
|
|
20
22
|
## Example
|
|
@@ -22,36 +24,40 @@ pip install python-weather
|
|
|
22
24
|
For more information, please read the [documentation](https://python-weather.readthedocs.io/en/latest/).
|
|
23
25
|
|
|
24
26
|
```py
|
|
25
|
-
#
|
|
27
|
+
# Import the module.
|
|
26
28
|
import python_weather
|
|
27
29
|
|
|
28
30
|
import asyncio
|
|
29
31
|
import os
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
|
|
34
|
+
async def main() -> None:
|
|
35
|
+
|
|
36
|
+
# Declare the client. The measuring unit used defaults to the metric system (celcius, km/h, etc.)
|
|
33
37
|
async with python_weather.Client(unit=python_weather.IMPERIAL) as client:
|
|
34
|
-
|
|
38
|
+
|
|
39
|
+
# Fetch a weather forecast from a city.
|
|
35
40
|
weather = await client.get('New York')
|
|
36
41
|
|
|
37
|
-
#
|
|
42
|
+
# Fetch the temperature for today.
|
|
38
43
|
print(weather.temperature)
|
|
39
44
|
|
|
40
|
-
#
|
|
45
|
+
# Fetch weather forecast for upcoming days.
|
|
41
46
|
for daily in weather:
|
|
42
47
|
print(daily)
|
|
43
|
-
|
|
44
|
-
# hourly forecasts
|
|
48
|
+
|
|
49
|
+
# Each daily forecast has their own hourly forecasts.
|
|
45
50
|
for hourly in daily:
|
|
46
51
|
print(f' --> {hourly!r}')
|
|
47
52
|
|
|
48
53
|
if __name__ == '__main__':
|
|
49
|
-
|
|
50
|
-
#
|
|
54
|
+
|
|
55
|
+
# See https://stackoverflow.com/questions/45600579/asyncio-event-loop-is-closed-when-getting-loop
|
|
56
|
+
# for more details.
|
|
51
57
|
if os.name == 'nt':
|
|
52
58
|
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
|
53
59
|
|
|
54
|
-
asyncio.run(
|
|
60
|
+
asyncio.run(main())
|
|
55
61
|
```
|
|
56
62
|
|
|
57
63
|
## Data source
|
|
@@ -3,13 +3,13 @@ requires = ["setuptools"]
|
|
|
3
3
|
|
|
4
4
|
[project]
|
|
5
5
|
name = "python-weather"
|
|
6
|
-
version = "2.0
|
|
6
|
+
version = "2.1.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" }
|
|
10
10
|
authors = [{ name = "null8626" }]
|
|
11
11
|
keywords = ["weather", "forecast", "weather-api", "weather-forecast"]
|
|
12
|
-
dependencies = ["aiohttp>=3.
|
|
12
|
+
dependencies = ["aiohttp>=3.12.4"]
|
|
13
13
|
classifiers = [
|
|
14
14
|
"Development Status :: 5 - Production/Stable",
|
|
15
15
|
"Intended Audience :: Education",
|
|
@@ -27,14 +27,13 @@ classifiers = [
|
|
|
27
27
|
"Programming Language :: Python",
|
|
28
28
|
"Programming Language :: Python :: 3",
|
|
29
29
|
"Programming Language :: Python :: 3 :: Only",
|
|
30
|
-
"Programming Language :: Python :: 3.8",
|
|
31
30
|
"Programming Language :: Python :: 3.9",
|
|
32
31
|
"Programming Language :: Python :: 3.10",
|
|
33
32
|
"Programming Language :: Python :: 3.11",
|
|
34
33
|
"Programming Language :: Python :: 3.12",
|
|
35
34
|
"Programming Language :: Python :: 3.13"
|
|
36
35
|
]
|
|
37
|
-
requires-python = ">=3.
|
|
36
|
+
requires-python = ">=3.9"
|
|
38
37
|
|
|
39
38
|
[project.urls]
|
|
40
39
|
Documentation = "https://python-weather.readthedocs.io/en/latest/"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
The MIT License (MIT)
|
|
3
3
|
|
|
4
|
-
Copyright (c) 2021-
|
|
4
|
+
Copyright (c) 2021-2025 null8626
|
|
5
5
|
|
|
6
6
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
7
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -25,13 +25,15 @@ SOFTWARE.
|
|
|
25
25
|
from .enums import HeatIndex, Kind, Locale, Phase, UltraViolet, WindDirection
|
|
26
26
|
from .constants import METRIC, IMPERIAL
|
|
27
27
|
from .errors import Error, RequestError
|
|
28
|
+
from .version import VERSION
|
|
28
29
|
from .client import Client
|
|
29
30
|
|
|
31
|
+
|
|
30
32
|
__title__ = 'python-weather'
|
|
31
33
|
__author__ = 'null8626'
|
|
32
34
|
__license__ = 'MIT'
|
|
33
|
-
__copyright__ = 'Copyright (c) 2021-
|
|
34
|
-
__version__ =
|
|
35
|
+
__copyright__ = 'Copyright (c) 2021-2025 null8626'
|
|
36
|
+
__version__ = VERSION
|
|
35
37
|
__all__ = (
|
|
36
38
|
'METRIC',
|
|
37
39
|
'IMPERIAL',
|
|
@@ -43,5 +45,6 @@ __all__ = (
|
|
|
43
45
|
'Locale',
|
|
44
46
|
'Phase',
|
|
45
47
|
'UltraViolet',
|
|
48
|
+
'VERSION',
|
|
46
49
|
'WindDirection',
|
|
47
50
|
)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
The MIT License (MIT)
|
|
3
3
|
|
|
4
|
-
Copyright (c) 2021-
|
|
4
|
+
Copyright (c) 2021-2025 null8626
|
|
5
5
|
|
|
6
6
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
7
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -22,15 +22,13 @@ 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 typing import Tuple
|
|
26
|
-
|
|
27
|
-
from .errors import Error
|
|
28
25
|
from .enums import WindDirection, Kind, Locale, UltraViolet
|
|
29
26
|
from .constants import _Unit
|
|
27
|
+
from .errors import Error
|
|
30
28
|
|
|
31
29
|
|
|
32
30
|
class CustomizableBase:
|
|
33
|
-
__slots__:
|
|
31
|
+
__slots__: tuple[str, ...] = ('__unit', '__locale')
|
|
34
32
|
|
|
35
33
|
def __init__(self, unit: _Unit, locale: Locale):
|
|
36
34
|
self.unit = unit
|
|
@@ -38,17 +36,18 @@ class CustomizableBase:
|
|
|
38
36
|
|
|
39
37
|
@property
|
|
40
38
|
def unit(self) -> _Unit:
|
|
41
|
-
"""The measuring unit used
|
|
39
|
+
"""The measuring unit used."""
|
|
42
40
|
|
|
43
41
|
return self.__unit
|
|
44
42
|
|
|
45
43
|
@unit.setter
|
|
46
44
|
def unit(self, to: _Unit) -> None:
|
|
47
45
|
"""
|
|
48
|
-
Sets the default measuring unit used
|
|
46
|
+
Sets the default measuring unit used.
|
|
49
47
|
|
|
50
|
-
:param to: The new default measuring unit to be used
|
|
51
|
-
|
|
48
|
+
:param to: The new default measuring unit to be used.
|
|
49
|
+
|
|
50
|
+
:exception Error: ``to`` is not either :data:`~.constants.METRIC` or :data:`~.constants.IMPERIAL`.
|
|
52
51
|
"""
|
|
53
52
|
|
|
54
53
|
if not isinstance(to, _Unit):
|
|
@@ -58,18 +57,19 @@ class CustomizableBase:
|
|
|
58
57
|
|
|
59
58
|
@property
|
|
60
59
|
def locale(self) -> Locale:
|
|
61
|
-
"""The localization used
|
|
60
|
+
"""The localization used."""
|
|
62
61
|
|
|
63
62
|
return self.__locale
|
|
64
63
|
|
|
65
64
|
@locale.setter
|
|
66
65
|
def locale(self, to: Locale) -> None:
|
|
67
66
|
"""
|
|
68
|
-
Sets the default localization used
|
|
67
|
+
Sets the default localization used.
|
|
68
|
+
|
|
69
|
+
:param to: The new :class:`.Locale` to be used.
|
|
70
|
+
:type to: :class:`.Locale`
|
|
69
71
|
|
|
70
|
-
:
|
|
71
|
-
:type to: Locale
|
|
72
|
-
:exception Error: If the ``to`` argument is not a part of the :class:`~python_weather.enums.Locale` enum.
|
|
72
|
+
:exception Error: ``to`` is not a part of the :class:`.Locale` enum.
|
|
73
73
|
"""
|
|
74
74
|
|
|
75
75
|
if not isinstance(to, Locale):
|
|
@@ -79,7 +79,7 @@ class CustomizableBase:
|
|
|
79
79
|
|
|
80
80
|
|
|
81
81
|
class BaseForecast:
|
|
82
|
-
__slots__:
|
|
82
|
+
__slots__: tuple[str, ...] = (
|
|
83
83
|
'ultraviolet',
|
|
84
84
|
'humidity',
|
|
85
85
|
'wind_direction',
|
|
@@ -94,7 +94,7 @@ class BaseForecast:
|
|
|
94
94
|
)
|
|
95
95
|
|
|
96
96
|
ultraviolet: UltraViolet
|
|
97
|
-
"""The ultra-violet
|
|
97
|
+
"""The ultra-violet index."""
|
|
98
98
|
|
|
99
99
|
humidity: int
|
|
100
100
|
"""The humidity value in percent."""
|
|
@@ -106,10 +106,10 @@ class BaseForecast:
|
|
|
106
106
|
"""The kind of the forecast."""
|
|
107
107
|
|
|
108
108
|
feels_like: int
|
|
109
|
-
"""What it felt like
|
|
109
|
+
"""What it felt like in either celcius or fahrenheit."""
|
|
110
110
|
|
|
111
111
|
temperature: int
|
|
112
|
-
"""The temperature in either celcius or
|
|
112
|
+
"""The temperature in either celcius or fahrenheit."""
|
|
113
113
|
|
|
114
114
|
precipitation: float
|
|
115
115
|
"""The precipitation in either millimeters or inches."""
|
|
@@ -124,7 +124,7 @@ class BaseForecast:
|
|
|
124
124
|
"""The wind speeds in either kilometers/hour or miles/hour."""
|
|
125
125
|
|
|
126
126
|
description: str
|
|
127
|
-
"""The description regarding the forecast
|
|
127
|
+
"""The description regarding the forecast depending on the localization used."""
|
|
128
128
|
|
|
129
129
|
def __init__(self, json: dict, unit: _Unit, locale: Locale):
|
|
130
130
|
description = (
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
The MIT License (MIT)
|
|
3
3
|
|
|
4
|
-
Copyright (c) 2021-
|
|
4
|
+
Copyright (c) 2021-2025 null8626
|
|
5
5
|
|
|
6
6
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
7
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -22,35 +22,36 @@ 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 aiohttp import ClientSession, ClientTimeout, TCPConnector
|
|
25
|
+
from aiohttp import ClientSession, ClientTimeout, ClientResponseError, TCPConnector
|
|
26
26
|
from urllib.parse import quote_plus
|
|
27
|
-
from typing import Optional
|
|
27
|
+
from typing import Optional
|
|
28
28
|
from asyncio import sleep
|
|
29
29
|
|
|
30
30
|
from .errors import Error, RequestError
|
|
31
31
|
from .constants import _Unit, METRIC
|
|
32
32
|
from .base import CustomizableBase
|
|
33
33
|
from .forecast import Forecast
|
|
34
|
+
from .version import VERSION
|
|
34
35
|
from .enums import Locale
|
|
35
36
|
|
|
36
37
|
|
|
37
38
|
class Client(CustomizableBase):
|
|
38
39
|
"""
|
|
39
|
-
|
|
40
|
+
Interact with the API's endpoints.
|
|
40
41
|
|
|
41
|
-
:param unit: Whether to use the metric or imperial/customary system (
|
|
42
|
-
:type unit: _Unit
|
|
43
|
-
:param locale: Whether to use a different locale/language as the description for the returned forecast. Defaults to
|
|
44
|
-
:type locale: Locale
|
|
45
|
-
:param session: Whether to use an existing aiohttp
|
|
42
|
+
:param unit: Whether to use the metric or imperial/customary system (:data:`~.constants.IMPERIAL`). Defaults to :data:`~.constants.METRIC`.
|
|
43
|
+
:type unit: ``_Unit``
|
|
44
|
+
:param locale: Whether to use a different locale/language as the description for the returned forecast. Defaults to :attr:`.Locale.ENGLISH`.
|
|
45
|
+
:type locale: :class:`.Locale`
|
|
46
|
+
:param session: Whether to use an existing :class:`~aiohttp.ClientSession` for requesting or not. Defaults to :py:obj:`None` (creates a new one instead).
|
|
46
47
|
:type session: Optional[:class:`~aiohttp.ClientSession`]
|
|
47
|
-
:param max_retries: Maximum amount of retries upon
|
|
48
|
+
:param max_retries: Maximum amount of retries upon request failure before raising a :class:`.RequestError`. Use ``-1`` to disable (NOT recommended). Defaults to 3 retries.
|
|
48
49
|
:type max_retries: Optional[:class:`int`]
|
|
49
50
|
|
|
50
|
-
:
|
|
51
|
+
:exception Error: ``unit`` is not :data:`~.constants.METRIC` or :data:`~.constants.IMPERIAL` or ``locale`` is not a part of the :class:`.Locale` enum.
|
|
51
52
|
"""
|
|
52
53
|
|
|
53
|
-
__slots__:
|
|
54
|
+
__slots__: tuple[str, ...] = ('__own_session', '__session', '__max_retries')
|
|
54
55
|
|
|
55
56
|
def __init__(
|
|
56
57
|
self,
|
|
@@ -82,24 +83,26 @@ class Client(CustomizableBase):
|
|
|
82
83
|
"""
|
|
83
84
|
Fetches a weather forecast for a specific location.
|
|
84
85
|
|
|
85
|
-
:param location: The requested location
|
|
86
|
-
:type location: str
|
|
87
|
-
:param unit: Overrides the unit used
|
|
86
|
+
:param location: The requested location.
|
|
87
|
+
:type location: :py:class:`str`
|
|
88
|
+
:param unit: Overrides the unit used.
|
|
88
89
|
:type unit: Optional[``_Unit``]
|
|
89
|
-
:param locale: Overrides the locale used
|
|
90
|
-
:type locale: Optional[:class
|
|
90
|
+
:param locale: Overrides the locale used.
|
|
91
|
+
:type locale: Optional[:class:`.Locale`]
|
|
91
92
|
|
|
92
|
-
:exception
|
|
93
|
-
:exception
|
|
93
|
+
:exception TypeError: ``location`` is not a :py:class:`str` or is empty.
|
|
94
|
+
:exception Error: The client is already closed.
|
|
95
|
+
:exception RequestError: The client received a non-favorable response from the API.
|
|
94
96
|
|
|
95
97
|
:returns: The requested weather forecast.
|
|
96
98
|
:rtype: Forecast
|
|
97
99
|
"""
|
|
98
100
|
|
|
99
|
-
if
|
|
100
|
-
raise Error(
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
if self.__session.closed:
|
|
102
|
+
raise Error('Client session is already closed.')
|
|
103
|
+
|
|
104
|
+
elif not isinstance(location, str) or not location:
|
|
105
|
+
raise TypeError(f'Expected a proper location str, got {location!r}')
|
|
103
106
|
|
|
104
107
|
if not isinstance(unit, _Unit):
|
|
105
108
|
unit = self._CustomizableBase__unit
|
|
@@ -108,27 +111,31 @@ class Client(CustomizableBase):
|
|
|
108
111
|
locale = self._CustomizableBase__locale
|
|
109
112
|
|
|
110
113
|
subdomain = f'{locale.value}.' if locale != Locale.ENGLISH else ''
|
|
111
|
-
delay = 0.5
|
|
112
114
|
attempts = 0
|
|
115
|
+
status = None
|
|
113
116
|
|
|
114
117
|
while True:
|
|
115
118
|
try:
|
|
116
119
|
async with self.__session.get(
|
|
117
|
-
f'https://{subdomain}wttr.in/{quote_plus(location)}?format=j1'
|
|
120
|
+
f'https://{subdomain}wttr.in/{quote_plus(location)}?format=j1',
|
|
121
|
+
headers={
|
|
122
|
+
'Content-Type': 'application/json',
|
|
123
|
+
'User-Agent': f'python_weather (https://github.com/null8626/python-weather {VERSION}) Python/',
|
|
124
|
+
},
|
|
118
125
|
) as resp:
|
|
126
|
+
status = resp.status
|
|
119
127
|
resp.raise_for_status()
|
|
120
128
|
|
|
121
129
|
return Forecast(await resp.json(), unit, locale)
|
|
122
|
-
except
|
|
130
|
+
except ClientResponseError:
|
|
123
131
|
if attempts == self.__max_retries:
|
|
124
|
-
raise RequestError(
|
|
132
|
+
raise RequestError(status) from None
|
|
125
133
|
|
|
126
|
-
await sleep(
|
|
134
|
+
await sleep(0.5 * (2**attempts))
|
|
127
135
|
attempts += 1
|
|
128
|
-
delay *= 2
|
|
129
136
|
|
|
130
137
|
async def close(self) -> None:
|
|
131
|
-
"""Closes the :class
|
|
138
|
+
"""Closes the :class:`.Client` object. Nothing will happen if the client uses a pre-existing :class:`~aiohttp.ClientSession` or if the session is already closed."""
|
|
132
139
|
|
|
133
140
|
if self.__own_session and not self.__session.closed:
|
|
134
141
|
await self.__session.close()
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
The MIT License (MIT)
|
|
3
3
|
|
|
4
|
-
Copyright (c) 2021-
|
|
4
|
+
Copyright (c) 2021-2025 null8626
|
|
5
5
|
|
|
6
6
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
7
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -22,12 +22,12 @@ 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 typing import Union
|
|
25
|
+
from typing import Union
|
|
26
26
|
import re
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
class _Unit:
|
|
30
|
-
__slots__:
|
|
30
|
+
__slots__: tuple[str, ...] = (
|
|
31
31
|
'temperature',
|
|
32
32
|
'velocity',
|
|
33
33
|
'pressure',
|
|
@@ -43,7 +43,7 @@ class _Unit:
|
|
|
43
43
|
pressure: str,
|
|
44
44
|
precipitation: str,
|
|
45
45
|
visibility: str,
|
|
46
|
-
cm_divisor: Union[
|
|
46
|
+
cm_divisor: Union[float, int],
|
|
47
47
|
):
|
|
48
48
|
self.temperature = temperature
|
|
49
49
|
self.velocity = velocity
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
The MIT License (MIT)
|
|
3
3
|
|
|
4
|
-
Copyright (c) 2021-
|
|
4
|
+
Copyright (c) 2021-2025 null8626
|
|
5
5
|
|
|
6
6
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
7
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -22,14 +22,14 @@ 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 typing import Union
|
|
25
26
|
from enum import Enum
|
|
26
|
-
from typing import Union, Tuple
|
|
27
27
|
|
|
28
28
|
from .constants import WIND_DIRECTION_EMOJIS
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
class BasicEnum(Enum):
|
|
32
|
-
__slots__:
|
|
32
|
+
__slots__: tuple[str, ...] = ()
|
|
33
33
|
|
|
34
34
|
def __repr__(self) -> str:
|
|
35
35
|
return f'{self.__class__.__name__}.{self.name}'
|
|
@@ -39,19 +39,25 @@ class BasicEnum(Enum):
|
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
class IndexedEnum(Enum):
|
|
42
|
-
__slots__:
|
|
42
|
+
__slots__: tuple[str, ...] = ('index',)
|
|
43
43
|
|
|
44
44
|
index: int
|
|
45
45
|
"""The index value."""
|
|
46
46
|
|
|
47
|
-
def __lt__(self, other: Union['IndexedEnum',
|
|
48
|
-
return self.index <
|
|
47
|
+
def __lt__(self, other: Union['IndexedEnum', float, int]) -> bool:
|
|
48
|
+
return float(self.index) < float(other)
|
|
49
49
|
|
|
50
|
-
def
|
|
51
|
-
return self.index
|
|
50
|
+
def __le__(self, other: Union['IndexedEnum', float, int]) -> bool:
|
|
51
|
+
return float(self.index) <= float(other)
|
|
52
52
|
|
|
53
|
-
def
|
|
54
|
-
return self.index
|
|
53
|
+
def __eq__(self, other: Union['IndexedEnum', float, int]) -> bool:
|
|
54
|
+
return float(self.index) == float(other)
|
|
55
|
+
|
|
56
|
+
def __gt__(self, other: Union['IndexedEnum', float, int]) -> bool:
|
|
57
|
+
return float(self.index) > float(other)
|
|
58
|
+
|
|
59
|
+
def __ge__(self, other: Union['IndexedEnum', float, int]) -> bool:
|
|
60
|
+
return float(self.index) >= float(other)
|
|
55
61
|
|
|
56
62
|
def __hash__(self) -> int:
|
|
57
63
|
return self.index
|
|
@@ -59,11 +65,14 @@ class IndexedEnum(Enum):
|
|
|
59
65
|
def __int__(self) -> int:
|
|
60
66
|
return self.index
|
|
61
67
|
|
|
68
|
+
def __float__(self) -> float:
|
|
69
|
+
return float(self.index)
|
|
70
|
+
|
|
62
71
|
|
|
63
72
|
class HeatIndex(IndexedEnum):
|
|
64
|
-
"""
|
|
73
|
+
"""A heat index."""
|
|
65
74
|
|
|
66
|
-
__slots__:
|
|
75
|
+
__slots__: tuple[str, ...] = ()
|
|
67
76
|
|
|
68
77
|
CAUTION = None
|
|
69
78
|
EXTREME_CAUTION = None
|
|
@@ -89,9 +98,9 @@ class HeatIndex(IndexedEnum):
|
|
|
89
98
|
|
|
90
99
|
|
|
91
100
|
class UltraViolet(BasicEnum, IndexedEnum):
|
|
92
|
-
"""
|
|
101
|
+
"""An ultra-violet (UV) index."""
|
|
93
102
|
|
|
94
|
-
__slots__:
|
|
103
|
+
__slots__: tuple[str, ...] = ()
|
|
95
104
|
|
|
96
105
|
LOW = None
|
|
97
106
|
MODERATE = None
|
|
@@ -120,9 +129,9 @@ class UltraViolet(BasicEnum, IndexedEnum):
|
|
|
120
129
|
|
|
121
130
|
|
|
122
131
|
class WindDirection(BasicEnum):
|
|
123
|
-
"""
|
|
132
|
+
"""A wind direction."""
|
|
124
133
|
|
|
125
|
-
__slots__:
|
|
134
|
+
__slots__: tuple[str, ...] = ('degrees',)
|
|
126
135
|
|
|
127
136
|
NORTH = 'N'
|
|
128
137
|
NORTH_NORTHEAST = 'NNE'
|
|
@@ -151,7 +160,7 @@ class WindDirection(BasicEnum):
|
|
|
151
160
|
return enum
|
|
152
161
|
|
|
153
162
|
def __contains__(self, other: Union['WindDirection', float, int]) -> bool:
|
|
154
|
-
other =
|
|
163
|
+
other = float(other)
|
|
155
164
|
|
|
156
165
|
if self is self.NORTH:
|
|
157
166
|
return other > 348.75 or other <= 11.25
|
|
@@ -194,15 +203,15 @@ class WindDirection(BasicEnum):
|
|
|
194
203
|
|
|
195
204
|
@property
|
|
196
205
|
def emoji(self) -> str:
|
|
197
|
-
"""
|
|
206
|
+
"""Emoji representation."""
|
|
198
207
|
|
|
199
208
|
return WIND_DIRECTION_EMOJIS[int(((self.degrees + 22.5) % 360) / 45.0)]
|
|
200
209
|
|
|
201
210
|
|
|
202
211
|
class Locale(Enum):
|
|
203
|
-
"""
|
|
212
|
+
"""Supported locales/languages."""
|
|
204
213
|
|
|
205
|
-
__slots__:
|
|
214
|
+
__slots__: tuple[str, ...] = ()
|
|
206
215
|
|
|
207
216
|
AFRIKAANS = 'af'
|
|
208
217
|
AMHARIC = 'am'
|
|
@@ -286,9 +295,9 @@ class Locale(Enum):
|
|
|
286
295
|
|
|
287
296
|
|
|
288
297
|
class Kind(BasicEnum):
|
|
289
|
-
"""
|
|
298
|
+
"""A weather forecast kind."""
|
|
290
299
|
|
|
291
|
-
__slots__:
|
|
300
|
+
__slots__: tuple[str, ...] = ()
|
|
292
301
|
|
|
293
302
|
SUNNY = 113
|
|
294
303
|
PARTLY_CLOUDY = 116
|
|
@@ -338,7 +347,7 @@ class Kind(BasicEnum):
|
|
|
338
347
|
|
|
339
348
|
@property
|
|
340
349
|
def emoji(self) -> str:
|
|
341
|
-
"""
|
|
350
|
+
"""Emoji representation."""
|
|
342
351
|
|
|
343
352
|
if self is self.CLOUDY:
|
|
344
353
|
return '☁️'
|
|
@@ -381,9 +390,9 @@ class Kind(BasicEnum):
|
|
|
381
390
|
|
|
382
391
|
|
|
383
392
|
class Phase(BasicEnum):
|
|
384
|
-
"""
|
|
393
|
+
"""A moon phase."""
|
|
385
394
|
|
|
386
|
-
__slots__:
|
|
395
|
+
__slots__: tuple[str, ...] = ()
|
|
387
396
|
|
|
388
397
|
NEW_MOON = 'New Moon'
|
|
389
398
|
WAXING_CRESCENT = 'Waxing Crescent'
|
|
@@ -396,7 +405,7 @@ class Phase(BasicEnum):
|
|
|
396
405
|
|
|
397
406
|
@property
|
|
398
407
|
def emoji(self) -> str:
|
|
399
|
-
"""
|
|
408
|
+
"""Emoji representation."""
|
|
400
409
|
|
|
401
410
|
if self is self.NEW_MOON:
|
|
402
411
|
return '🌑'
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
The MIT License (MIT)
|
|
3
3
|
|
|
4
|
-
Copyright (c) 2021-
|
|
4
|
+
Copyright (c) 2021-2025 null8626
|
|
5
5
|
|
|
6
6
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
7
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -22,24 +22,27 @@ 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 typing import
|
|
25
|
+
from typing import Optional
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
class Error(Exception):
|
|
29
|
-
"""
|
|
29
|
+
"""The base error class. Extends :py:class:`Exception`."""
|
|
30
30
|
|
|
31
|
-
__slots__:
|
|
31
|
+
__slots__: tuple[str, ...] = ()
|
|
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:`.Error`."""
|
|
36
36
|
|
|
37
|
-
__slots__:
|
|
37
|
+
__slots__: tuple[str, ...] = ('status',)
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
"""The
|
|
39
|
+
status: Optional[int]
|
|
40
|
+
"""The status code returned from the API."""
|
|
41
41
|
|
|
42
|
-
def __init__(self,
|
|
43
|
-
self.
|
|
42
|
+
def __init__(self, status: Optional[int]):
|
|
43
|
+
self.status = status
|
|
44
44
|
|
|
45
45
|
super().__init__()
|
|
46
|
+
|
|
47
|
+
def __repr__(self) -> str:
|
|
48
|
+
return f'<{__class__.__name__} status={self.status}>'
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
The MIT License (MIT)
|
|
3
3
|
|
|
4
|
-
Copyright (c) 2021-
|
|
4
|
+
Copyright (c) 2021-2025 null8626
|
|
5
5
|
|
|
6
6
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
7
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -22,18 +22,19 @@ 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 typing import Iterable, Optional, Tuple, List
|
|
26
25
|
from datetime import datetime, date, time
|
|
26
|
+
from collections.abc import Iterable
|
|
27
|
+
from typing import Optional
|
|
27
28
|
|
|
28
|
-
from .base import BaseForecast
|
|
29
29
|
from .enums import Phase, Locale, HeatIndex
|
|
30
30
|
from .constants import _Unit, LATLON_REGEX
|
|
31
|
+
from .base import BaseForecast
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
class HourlyForecast(BaseForecast):
|
|
34
|
-
"""
|
|
35
|
+
"""A weather forecast for a specific hour."""
|
|
35
36
|
|
|
36
|
-
__slots__:
|
|
37
|
+
__slots__: tuple[str, ...] = (
|
|
37
38
|
'chances_of_fog',
|
|
38
39
|
'chances_of_frost',
|
|
39
40
|
'chances_of_high_temperature',
|
|
@@ -101,9 +102,10 @@ class HourlyForecast(BaseForecast):
|
|
|
101
102
|
"""The wind gust value in either kilometers/hour or miles/hour."""
|
|
102
103
|
|
|
103
104
|
def __init__(self, json: dict, unit: _Unit, locale: Locale):
|
|
104
|
-
#
|
|
105
|
+
# For inheritance purposes.
|
|
105
106
|
if 'temp_C' not in json:
|
|
106
107
|
json['temp_C'] = json.pop('tempC')
|
|
108
|
+
|
|
107
109
|
if 'temp_F' not in json:
|
|
108
110
|
json['temp_F'] = json.pop('tempF')
|
|
109
111
|
|
|
@@ -133,11 +135,13 @@ class HourlyForecast(BaseForecast):
|
|
|
133
135
|
super().__init__(json, unit, locale)
|
|
134
136
|
|
|
135
137
|
def __repr__(self) -> str:
|
|
136
|
-
return f'<{__class__.__name__} time={self.time!r} temperature={self.temperature
|
|
138
|
+
return f'<{__class__.__name__} time={self.time!r} temperature={self.temperature} description={self.description!r} kind={self.kind!r}>'
|
|
137
139
|
|
|
138
140
|
|
|
139
141
|
class DailyForecast:
|
|
140
|
-
|
|
142
|
+
"""A weather forecast for a specific day."""
|
|
143
|
+
|
|
144
|
+
__slots__: tuple[str, ...] = (
|
|
141
145
|
'moon_illumination',
|
|
142
146
|
'moon_phase',
|
|
143
147
|
'moonrise',
|
|
@@ -160,16 +164,16 @@ class DailyForecast:
|
|
|
160
164
|
"""The moon's phase."""
|
|
161
165
|
|
|
162
166
|
moonrise: Optional[time]
|
|
163
|
-
"""The local time when the moon rises.
|
|
167
|
+
"""The local time when the moon rises."""
|
|
164
168
|
|
|
165
169
|
moonset: Optional[time]
|
|
166
|
-
"""The local time when the moon sets.
|
|
170
|
+
"""The local time when the moon sets."""
|
|
167
171
|
|
|
168
172
|
sunrise: Optional[time]
|
|
169
|
-
"""The local time when the sun rises.
|
|
173
|
+
"""The local time when the sun rises."""
|
|
170
174
|
|
|
171
175
|
sunset: Optional[time]
|
|
172
|
-
"""The local time when the sun sets.
|
|
176
|
+
"""The local time when the sun sets."""
|
|
173
177
|
|
|
174
178
|
date: 'date'
|
|
175
179
|
"""The local date of this forecast."""
|
|
@@ -189,7 +193,7 @@ class DailyForecast:
|
|
|
189
193
|
snowfall: float
|
|
190
194
|
"""Total snowfall in either centimeters or inches."""
|
|
191
195
|
|
|
192
|
-
hourly_forecasts:
|
|
196
|
+
hourly_forecasts: list[HourlyForecast]
|
|
193
197
|
"""The hourly forecasts of this day."""
|
|
194
198
|
|
|
195
199
|
def __init__(self, json: dict, unit: _Unit, locale: Locale):
|
|
@@ -201,7 +205,7 @@ class DailyForecast:
|
|
|
201
205
|
self.moonset = __class__.__parse_time(astronomy['moonset'])
|
|
202
206
|
self.sunrise = __class__.__parse_time(astronomy['sunrise'])
|
|
203
207
|
self.sunset = __class__.__parse_time(astronomy['sunset'])
|
|
204
|
-
self.date =
|
|
208
|
+
self.date = date.fromisoformat(json['date'])
|
|
205
209
|
self.sunlight = float(json['sunHour'])
|
|
206
210
|
self.lowest_temperature = int(json[f'mintemp{unit.temperature}'])
|
|
207
211
|
self.highest_temperature = int(json[f'maxtemp{unit.temperature}'])
|
|
@@ -219,7 +223,7 @@ class DailyForecast:
|
|
|
219
223
|
...
|
|
220
224
|
|
|
221
225
|
def __repr__(self) -> str:
|
|
222
|
-
return f'<{__class__.__name__} date={self.date!r} temperature={self.temperature
|
|
226
|
+
return f'<{__class__.__name__} date={self.date!r} temperature={self.temperature}>'
|
|
223
227
|
|
|
224
228
|
def __len__(self) -> int:
|
|
225
229
|
return len(self.hourly_forecasts)
|
|
@@ -229,9 +233,9 @@ class DailyForecast:
|
|
|
229
233
|
|
|
230
234
|
|
|
231
235
|
class Forecast(BaseForecast):
|
|
232
|
-
"""
|
|
236
|
+
"""A set of weather forecasts for a certain location."""
|
|
233
237
|
|
|
234
|
-
__slots__:
|
|
238
|
+
__slots__: tuple[str, ...] = (
|
|
235
239
|
'local_population',
|
|
236
240
|
'region',
|
|
237
241
|
'location',
|
|
@@ -256,10 +260,10 @@ class Forecast(BaseForecast):
|
|
|
256
260
|
datetime: 'datetime'
|
|
257
261
|
"""The local date and time of this weather forecast."""
|
|
258
262
|
|
|
259
|
-
coordinates:
|
|
260
|
-
"""
|
|
263
|
+
coordinates: tuple[float, float]
|
|
264
|
+
"""This forecast's latitude and longitude."""
|
|
261
265
|
|
|
262
|
-
daily_forecasts:
|
|
266
|
+
daily_forecasts: list[DailyForecast]
|
|
263
267
|
"""Daily weather forecasts in this location."""
|
|
264
268
|
|
|
265
269
|
def __init__(self, json: dict, unit: _Unit, locale: Locale):
|
|
@@ -277,7 +281,7 @@ class Forecast(BaseForecast):
|
|
|
277
281
|
match = LATLON_REGEX.match(req['query'])
|
|
278
282
|
|
|
279
283
|
self.coordinates = (float(match[1]), float(match[2]))
|
|
280
|
-
except:
|
|
284
|
+
except (KeyError, IndexError, StopIteration):
|
|
281
285
|
self.coordinates = (float(nearest['latitude']), float(nearest['longitude']))
|
|
282
286
|
|
|
283
287
|
self.daily_forecasts = [
|
|
@@ -287,7 +291,7 @@ class Forecast(BaseForecast):
|
|
|
287
291
|
super().__init__(current, unit, locale)
|
|
288
292
|
|
|
289
293
|
def __repr__(self) -> str:
|
|
290
|
-
return f'<{__class__.__name__} location={self.location!r} datetime={self.datetime!r} temperature={self.temperature
|
|
294
|
+
return f'<{__class__.__name__} location={self.location!r} datetime={self.datetime!r} temperature={self.temperature}>'
|
|
291
295
|
|
|
292
296
|
def __len__(self) -> int:
|
|
293
297
|
return len(self.daily_forecasts)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VERSION = '3.0.0'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: python-weather
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: A free and asynchronous weather API wrapper made in Python, for Python.
|
|
5
5
|
Author: null8626
|
|
6
6
|
License: MIT
|
|
@@ -26,16 +26,16 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
26
26
|
Classifier: Programming Language :: Python
|
|
27
27
|
Classifier: Programming Language :: Python :: 3
|
|
28
28
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
29
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
30
29
|
Classifier: Programming Language :: Python :: 3.9
|
|
31
30
|
Classifier: Programming Language :: Python :: 3.10
|
|
32
31
|
Classifier: Programming Language :: Python :: 3.11
|
|
33
32
|
Classifier: Programming Language :: Python :: 3.12
|
|
34
33
|
Classifier: Programming Language :: Python :: 3.13
|
|
35
|
-
Requires-Python: >=3.
|
|
34
|
+
Requires-Python: >=3.9
|
|
36
35
|
Description-Content-Type: text/markdown
|
|
37
36
|
License-File: LICENSE
|
|
38
|
-
Requires-Dist: aiohttp>=3.
|
|
37
|
+
Requires-Dist: aiohttp>=3.12.4
|
|
38
|
+
Dynamic: license-file
|
|
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
|
|
|
@@ -50,10 +50,12 @@ Requires-Dist: aiohttp>=3.10.10
|
|
|
50
50
|
|
|
51
51
|
A free and asynchronous weather Python API wrapper made in Python, for Python.
|
|
52
52
|
|
|
53
|
-
##
|
|
53
|
+
## Getting started
|
|
54
|
+
|
|
55
|
+
Run the following command in your terminal:
|
|
54
56
|
|
|
55
57
|
```console
|
|
56
|
-
pip install python-weather
|
|
58
|
+
$ pip install python-weather
|
|
57
59
|
```
|
|
58
60
|
|
|
59
61
|
## Example
|
|
@@ -61,36 +63,40 @@ pip install python-weather
|
|
|
61
63
|
For more information, please read the [documentation](https://python-weather.readthedocs.io/en/latest/).
|
|
62
64
|
|
|
63
65
|
```py
|
|
64
|
-
#
|
|
66
|
+
# Import the module.
|
|
65
67
|
import python_weather
|
|
66
68
|
|
|
67
69
|
import asyncio
|
|
68
70
|
import os
|
|
69
71
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
+
|
|
73
|
+
async def main() -> None:
|
|
74
|
+
|
|
75
|
+
# Declare the client. The measuring unit used defaults to the metric system (celcius, km/h, etc.)
|
|
72
76
|
async with python_weather.Client(unit=python_weather.IMPERIAL) as client:
|
|
73
|
-
|
|
77
|
+
|
|
78
|
+
# Fetch a weather forecast from a city.
|
|
74
79
|
weather = await client.get('New York')
|
|
75
80
|
|
|
76
|
-
#
|
|
81
|
+
# Fetch the temperature for today.
|
|
77
82
|
print(weather.temperature)
|
|
78
83
|
|
|
79
|
-
#
|
|
84
|
+
# Fetch weather forecast for upcoming days.
|
|
80
85
|
for daily in weather:
|
|
81
86
|
print(daily)
|
|
82
|
-
|
|
83
|
-
# hourly forecasts
|
|
87
|
+
|
|
88
|
+
# Each daily forecast has their own hourly forecasts.
|
|
84
89
|
for hourly in daily:
|
|
85
90
|
print(f' --> {hourly!r}')
|
|
86
91
|
|
|
87
92
|
if __name__ == '__main__':
|
|
88
|
-
|
|
89
|
-
#
|
|
93
|
+
|
|
94
|
+
# See https://stackoverflow.com/questions/45600579/asyncio-event-loop-is-closed-when-getting-loop
|
|
95
|
+
# for more details.
|
|
90
96
|
if os.name == 'nt':
|
|
91
97
|
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
|
92
98
|
|
|
93
|
-
asyncio.run(
|
|
99
|
+
asyncio.run(main())
|
|
94
100
|
```
|
|
95
101
|
|
|
96
102
|
## Data source
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
aiohttp>=3.12.4
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
aiohttp>=3.10.10
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|