python-weather 2.1.0__tar.gz → 2.1.1__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.0 → python_weather-2.1.1}/MANIFEST.in +8 -1
- {python_weather-2.1.0/python_weather.egg-info → python_weather-2.1.1}/PKG-INFO +104 -110
- {python_weather-2.1.0 → python_weather-2.1.1}/README.md +10 -15
- {python_weather-2.1.0 → python_weather-2.1.1}/pyproject.toml +17 -7
- {python_weather-2.1.0 → python_weather-2.1.1}/python_weather/__init__.py +5 -0
- {python_weather-2.1.0 → python_weather-2.1.1}/python_weather/base.py +13 -5
- {python_weather-2.1.0 → python_weather-2.1.1}/python_weather/client.py +56 -19
- {python_weather-2.1.0 → python_weather-2.1.1}/python_weather/constants.py +9 -3
- {python_weather-2.1.0 → python_weather-2.1.1}/python_weather/enums.py +51 -45
- {python_weather-2.1.0 → python_weather-2.1.1}/python_weather/errors.py +11 -9
- {python_weather-2.1.0 → python_weather-2.1.1}/python_weather/forecast.py +28 -26
- {python_weather-2.1.0 → python_weather-2.1.1/python_weather.egg-info}/PKG-INFO +104 -110
- python_weather-2.1.1/python_weather.egg-info/requires.txt +1 -0
- {python_weather-2.1.0 → python_weather-2.1.1}/setup.cfg +4 -4
- python_weather-2.1.0/python_weather.egg-info/requires.txt +0 -1
- {python_weather-2.1.0 → python_weather-2.1.1}/LICENSE +0 -0
- {python_weather-2.1.0 → python_weather-2.1.1}/python_weather/version.py +0 -0
- {python_weather-2.1.0 → python_weather-2.1.1}/python_weather.egg-info/SOURCES.txt +0 -0
- {python_weather-2.1.0 → python_weather-2.1.1}/python_weather.egg-info/dependency_links.txt +0 -0
- {python_weather-2.1.0 → python_weather-2.1.1}/python_weather.egg-info/top_level.txt +0 -0
|
@@ -1,10 +1,17 @@
|
|
|
1
|
+
prune .pytest_cache
|
|
1
2
|
prune .ruff_cache
|
|
2
3
|
prune .github
|
|
4
|
+
prune tests
|
|
3
5
|
prune docs
|
|
4
6
|
exclude .gitattributes
|
|
5
7
|
exclude .gitignore
|
|
6
8
|
exclude .readthedocs.yml
|
|
9
|
+
exclude .coverage
|
|
7
10
|
exclude ruff.toml
|
|
8
11
|
exclude LICENSE
|
|
9
12
|
exclude changelog.yml
|
|
10
|
-
exclude
|
|
13
|
+
exclude codacy-coverage
|
|
14
|
+
exclude codecov*
|
|
15
|
+
exclude coverage.xml
|
|
16
|
+
exclude junit.xml
|
|
17
|
+
global-exclude __pycache__/*
|
|
@@ -1,110 +1,104 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: python-weather
|
|
3
|
-
Version: 2.1.
|
|
4
|
-
Summary: A free and asynchronous weather API wrapper made in Python, for Python.
|
|
5
|
-
Author: null8626
|
|
6
|
-
License: MIT
|
|
7
|
-
Project-URL: Documentation, https://python-weather.readthedocs.io/en/latest/
|
|
8
|
-
Project-URL: Repository, https://github.com/null8626/python-weather
|
|
9
|
-
Project-URL: Changelog, https://python-weather.readthedocs.io/en/latest/changelog.html
|
|
10
|
-
Project-URL: Donate via GitHub Sponsors, https://github.com/sponsors/null8626
|
|
11
|
-
Project-URL: Donate via Ko-fi, https://ko-fi.com/null8626
|
|
12
|
-
Keywords: weather,forecast,weather-
|
|
13
|
-
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
-
Classifier: Intended Audience :: Education
|
|
15
|
-
Classifier: Intended Audience :: Developers
|
|
16
|
-
Classifier: Intended Audience :: Information Technology
|
|
17
|
-
Classifier: Natural Language :: English
|
|
18
|
-
Classifier: Operating System :: OS Independent
|
|
19
|
-
Classifier: Topic :: Education
|
|
20
|
-
Classifier: Topic :: Internet
|
|
21
|
-
Classifier: Topic :: Scientific/Engineering
|
|
22
|
-
Classifier: Topic :: Scientific/Engineering :: Astronomy
|
|
23
|
-
Classifier: Topic :: Software Development
|
|
24
|
-
Classifier: Topic :: Software Development :: Libraries
|
|
25
|
-
Classifier:
|
|
26
|
-
Classifier: Programming Language :: Python
|
|
27
|
-
Classifier: Programming Language :: Python :: 3
|
|
28
|
-
Classifier: Programming Language :: Python :: 3
|
|
29
|
-
Classifier: Programming Language :: Python :: 3.
|
|
30
|
-
Classifier: Programming Language :: Python :: 3.
|
|
31
|
-
Classifier: Programming Language :: Python :: 3.
|
|
32
|
-
Classifier: Programming Language :: Python :: 3.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
[pypi-image]: https://img.shields.io/pypi/v/python-weather.svg?style=flat-square
|
|
43
|
-
[pypi-
|
|
44
|
-
[
|
|
45
|
-
[
|
|
46
|
-
[
|
|
47
|
-
[
|
|
48
|
-
[ko-fi-
|
|
49
|
-
[ko-fi-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
import
|
|
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.)
|
|
76
|
-
async with python_weather.Client(unit=python_weather.IMPERIAL) as client:
|
|
77
|
-
|
|
78
|
-
# Fetch a weather forecast from a city.
|
|
79
|
-
weather = await client.get('New York')
|
|
80
|
-
|
|
81
|
-
# Fetch the temperature for today.
|
|
82
|
-
print(weather.temperature)
|
|
83
|
-
|
|
84
|
-
# Fetch weather forecast for upcoming days.
|
|
85
|
-
for daily in weather:
|
|
86
|
-
print(daily)
|
|
87
|
-
|
|
88
|
-
# Each daily forecast has their own hourly forecasts.
|
|
89
|
-
for hourly in daily:
|
|
90
|
-
print(f' --> {hourly!r}')
|
|
91
|
-
|
|
92
|
-
if __name__ == '__main__':
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
## Donations
|
|
107
|
-
|
|
108
|
-
If you want to support this project, consider donating! ❤
|
|
109
|
-
|
|
110
|
-
[![ko-fi][ko-fi-image]][ko-fi-url]
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: python-weather
|
|
3
|
+
Version: 2.1.1
|
|
4
|
+
Summary: A free and asynchronous weather API wrapper made in Python, for Python.
|
|
5
|
+
Author: null8626
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Documentation, https://python-weather.readthedocs.io/en/latest/
|
|
8
|
+
Project-URL: Repository, https://github.com/null8626/python-weather
|
|
9
|
+
Project-URL: Changelog, https://python-weather.readthedocs.io/en/latest/changelog.html
|
|
10
|
+
Project-URL: Donate via GitHub Sponsors, https://github.com/sponsors/null8626
|
|
11
|
+
Project-URL: Donate via Ko-fi, https://ko-fi.com/null8626
|
|
12
|
+
Keywords: weather,forecast,weather-forecast
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Intended Audience :: Education
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Intended Audience :: Information Technology
|
|
17
|
+
Classifier: Natural Language :: English
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Topic :: Education
|
|
20
|
+
Classifier: Topic :: Internet
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Astronomy
|
|
23
|
+
Classifier: Topic :: Software Development
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
25
|
+
Classifier: Programming Language :: Python
|
|
26
|
+
Classifier: Programming Language :: Python :: 3
|
|
27
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
28
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
29
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
30
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
31
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
32
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
33
|
+
Requires-Python: >=3.10
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
License-File: LICENSE
|
|
36
|
+
Requires-Dist: aiohttp>=3.13.2
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
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]
|
|
40
|
+
|
|
41
|
+
[pypi-url]: https://pypi.org/project/python-weather/
|
|
42
|
+
[pypi-image]: https://img.shields.io/pypi/v/python-weather.svg?style=flat-square
|
|
43
|
+
[pypi-downloads-image]: https://img.shields.io/pypi/dm/python-weather?style=flat-square
|
|
44
|
+
[codacy-url]: https://app.codacy.com/gh/null8626/python-weather/dashboard
|
|
45
|
+
[codacy-image]: https://app.codacy.com/project/badge/Grade/0f7721b7e4314a748c75a04f0a7e0ce3
|
|
46
|
+
[codecov-url]: https://codecov.io/gh/null8626/python-weather
|
|
47
|
+
[codecov-image]: https://codecov.io/gh/null8626/python-weather/graph/badge.svg
|
|
48
|
+
[ko-fi-url]: https://ko-fi.com/null8626
|
|
49
|
+
[ko-fi-image]: https://ko-fi.com/img/githubbutton_sm.svg
|
|
50
|
+
[ko-fi-brief-image]: https://img.shields.io/badge/donations-ko--fi-red?color=ff5e5b&style=flat-square
|
|
51
|
+
|
|
52
|
+
A free and asynchronous weather Python API wrapper made in Python, for Python.
|
|
53
|
+
|
|
54
|
+
## Getting started
|
|
55
|
+
|
|
56
|
+
Run the following command in your terminal:
|
|
57
|
+
|
|
58
|
+
```console
|
|
59
|
+
pip install python-weather
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Example
|
|
63
|
+
|
|
64
|
+
For more information, please read the [documentation](https://python-weather.readthedocs.io/en/latest/).
|
|
65
|
+
|
|
66
|
+
```py
|
|
67
|
+
# Import the module.
|
|
68
|
+
import python_weather
|
|
69
|
+
|
|
70
|
+
import asyncio
|
|
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.)
|
|
76
|
+
async with python_weather.Client(unit=python_weather.IMPERIAL) as client:
|
|
77
|
+
|
|
78
|
+
# Fetch a weather forecast from a city.
|
|
79
|
+
weather = await client.get('New York')
|
|
80
|
+
|
|
81
|
+
# Fetch the temperature for today.
|
|
82
|
+
print(weather.temperature)
|
|
83
|
+
|
|
84
|
+
# Fetch weather forecast for upcoming days.
|
|
85
|
+
for daily in weather:
|
|
86
|
+
print(daily)
|
|
87
|
+
|
|
88
|
+
# Each daily forecast has their own hourly forecasts.
|
|
89
|
+
for hourly in daily:
|
|
90
|
+
print(f' --> {hourly!r}')
|
|
91
|
+
|
|
92
|
+
if __name__ == '__main__':
|
|
93
|
+
asyncio.run(main())
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Data source
|
|
97
|
+
|
|
98
|
+
This library depends on [`wttr.in`](https://github.com/chubin/wttr.in), which uses data from the [World Weather Online API](https://www.worldweatheronline.com/weather-api/).
|
|
99
|
+
|
|
100
|
+
## Donations
|
|
101
|
+
|
|
102
|
+
If you want to support this project, consider donating! ❤
|
|
103
|
+
|
|
104
|
+
[![ko-fi][ko-fi-image]][ko-fi-url]
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
# [python-weather][pypi-url] [![pypi][pypi-image]][pypi-url] [![downloads][downloads-image]][pypi-url] [![codacy-badge][codacy-
|
|
1
|
+
# [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]
|
|
2
2
|
|
|
3
|
-
[pypi-image]: https://img.shields.io/pypi/v/python-weather.svg?style=flat-square
|
|
4
3
|
[pypi-url]: https://pypi.org/project/python-weather/
|
|
5
|
-
[
|
|
6
|
-
[
|
|
7
|
-
[codacy-url]: https://app.codacy.com/
|
|
8
|
-
[codacy-image]: https://app.codacy.com/
|
|
9
|
-
[
|
|
4
|
+
[pypi-image]: https://img.shields.io/pypi/v/python-weather.svg?style=flat-square
|
|
5
|
+
[pypi-downloads-image]: https://img.shields.io/pypi/dm/python-weather?style=flat-square
|
|
6
|
+
[codacy-url]: https://app.codacy.com/gh/null8626/python-weather/dashboard
|
|
7
|
+
[codacy-image]: https://app.codacy.com/project/badge/Grade/0f7721b7e4314a748c75a04f0a7e0ce3
|
|
8
|
+
[codecov-url]: https://codecov.io/gh/null8626/python-weather
|
|
9
|
+
[codecov-image]: https://codecov.io/gh/null8626/python-weather/graph/badge.svg
|
|
10
10
|
[ko-fi-url]: https://ko-fi.com/null8626
|
|
11
|
+
[ko-fi-image]: https://ko-fi.com/img/githubbutton_sm.svg
|
|
12
|
+
[ko-fi-brief-image]: https://img.shields.io/badge/donations-ko--fi-red?color=ff5e5b&style=flat-square
|
|
11
13
|
|
|
12
14
|
A free and asynchronous weather Python API wrapper made in Python, for Python.
|
|
13
15
|
|
|
@@ -16,7 +18,7 @@ A free and asynchronous weather Python API wrapper made in Python, for Python.
|
|
|
16
18
|
Run the following command in your terminal:
|
|
17
19
|
|
|
18
20
|
```console
|
|
19
|
-
|
|
21
|
+
pip install python-weather
|
|
20
22
|
```
|
|
21
23
|
|
|
22
24
|
## Example
|
|
@@ -28,7 +30,6 @@ For more information, please read the [documentation](https://python-weather.rea
|
|
|
28
30
|
import python_weather
|
|
29
31
|
|
|
30
32
|
import asyncio
|
|
31
|
-
import os
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
async def main() -> None:
|
|
@@ -51,12 +52,6 @@ async def main() -> None:
|
|
|
51
52
|
print(f' --> {hourly!r}')
|
|
52
53
|
|
|
53
54
|
if __name__ == '__main__':
|
|
54
|
-
|
|
55
|
-
# See https://stackoverflow.com/questions/45600579/asyncio-event-loop-is-closed-when-getting-loop
|
|
56
|
-
# for more details.
|
|
57
|
-
if os.name == 'nt':
|
|
58
|
-
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
|
59
|
-
|
|
60
55
|
asyncio.run(main())
|
|
61
56
|
```
|
|
62
57
|
|
|
@@ -3,13 +3,13 @@ requires = ["setuptools"]
|
|
|
3
3
|
|
|
4
4
|
[project]
|
|
5
5
|
name = "python-weather"
|
|
6
|
-
version = "2.1.
|
|
6
|
+
version = "2.1.1"
|
|
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
|
-
keywords = ["weather", "forecast", "weather-
|
|
12
|
-
dependencies = ["aiohttp>=3.
|
|
11
|
+
keywords = ["weather", "forecast", "weather-forecast"]
|
|
12
|
+
dependencies = ["aiohttp>=3.13.2"]
|
|
13
13
|
classifiers = [
|
|
14
14
|
"Development Status :: 5 - Production/Stable",
|
|
15
15
|
"Intended Audience :: Education",
|
|
@@ -23,17 +23,16 @@ classifiers = [
|
|
|
23
23
|
"Topic :: Scientific/Engineering :: Astronomy",
|
|
24
24
|
"Topic :: Software Development",
|
|
25
25
|
"Topic :: Software Development :: Libraries",
|
|
26
|
-
"License :: OSI Approved :: MIT License",
|
|
27
26
|
"Programming Language :: Python",
|
|
28
27
|
"Programming Language :: Python :: 3",
|
|
29
28
|
"Programming Language :: Python :: 3 :: Only",
|
|
30
|
-
"Programming Language :: Python :: 3.9",
|
|
31
29
|
"Programming Language :: Python :: 3.10",
|
|
32
30
|
"Programming Language :: Python :: 3.11",
|
|
33
31
|
"Programming Language :: Python :: 3.12",
|
|
34
|
-
"Programming Language :: Python :: 3.13"
|
|
32
|
+
"Programming Language :: Python :: 3.13",
|
|
33
|
+
"Programming Language :: Python :: 3.14"
|
|
35
34
|
]
|
|
36
|
-
requires-python = ">=3.
|
|
35
|
+
requires-python = ">=3.10"
|
|
37
36
|
|
|
38
37
|
[project.urls]
|
|
39
38
|
Documentation = "https://python-weather.readthedocs.io/en/latest/"
|
|
@@ -41,3 +40,14 @@ Repository = "https://github.com/null8626/python-weather"
|
|
|
41
40
|
Changelog = "https://python-weather.readthedocs.io/en/latest/changelog.html"
|
|
42
41
|
"Donate via GitHub Sponsors" = "https://github.com/sponsors/null8626"
|
|
43
42
|
"Donate via Ko-fi" = "https://ko-fi.com/null8626"
|
|
43
|
+
|
|
44
|
+
[tool.pytest.ini_options]
|
|
45
|
+
minversion = "8.0"
|
|
46
|
+
testpaths = ["tests"]
|
|
47
|
+
|
|
48
|
+
[tool.coverage.report]
|
|
49
|
+
exclude_lines = [
|
|
50
|
+
"^\\s*async def __a\\w+__\\(",
|
|
51
|
+
"# pragma: nocover",
|
|
52
|
+
"TYPE_CHECKING:$"
|
|
53
|
+
]
|
|
@@ -25,12 +25,16 @@ 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 .forecast import Forecast
|
|
28
29
|
from .version import VERSION
|
|
29
30
|
from .client import Client
|
|
30
31
|
|
|
31
32
|
|
|
32
33
|
__title__ = 'python-weather'
|
|
33
34
|
__author__ = 'null8626'
|
|
35
|
+
__credits__ = (__author__,)
|
|
36
|
+
__maintainer__ = __author__
|
|
37
|
+
__status__ = 'Production'
|
|
34
38
|
__license__ = 'MIT'
|
|
35
39
|
__copyright__ = 'Copyright (c) 2021-2025 null8626'
|
|
36
40
|
__version__ = VERSION
|
|
@@ -39,6 +43,7 @@ __all__ = (
|
|
|
39
43
|
'IMPERIAL',
|
|
40
44
|
'Client',
|
|
41
45
|
'Error',
|
|
46
|
+
'Forecast',
|
|
42
47
|
'RequestError',
|
|
43
48
|
'HeatIndex',
|
|
44
49
|
'Kind',
|
|
@@ -28,7 +28,10 @@ from .errors import Error
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class CustomizableBase:
|
|
31
|
-
__slots__: tuple[str, ...] =
|
|
31
|
+
__slots__: tuple[str, ...] = '_unit', '_locale'
|
|
32
|
+
|
|
33
|
+
_unit: _Unit
|
|
34
|
+
_locale: Locale
|
|
32
35
|
|
|
33
36
|
def __init__(self, unit: _Unit, locale: Locale):
|
|
34
37
|
self.unit = unit
|
|
@@ -38,7 +41,7 @@ class CustomizableBase:
|
|
|
38
41
|
def unit(self) -> _Unit:
|
|
39
42
|
"""The measuring unit used."""
|
|
40
43
|
|
|
41
|
-
return self.
|
|
44
|
+
return self._unit
|
|
42
45
|
|
|
43
46
|
@unit.setter
|
|
44
47
|
def unit(self, to: _Unit) -> None:
|
|
@@ -53,13 +56,13 @@ class CustomizableBase:
|
|
|
53
56
|
if not isinstance(to, _Unit):
|
|
54
57
|
raise Error('Invalid measuring unit specified!')
|
|
55
58
|
|
|
56
|
-
self.
|
|
59
|
+
self._unit = to
|
|
57
60
|
|
|
58
61
|
@property
|
|
59
62
|
def locale(self) -> Locale:
|
|
60
63
|
"""The localization used."""
|
|
61
64
|
|
|
62
|
-
return self.
|
|
65
|
+
return self._locale
|
|
63
66
|
|
|
64
67
|
@locale.setter
|
|
65
68
|
def locale(self, to: Locale) -> None:
|
|
@@ -75,11 +78,12 @@ class CustomizableBase:
|
|
|
75
78
|
if not isinstance(to, Locale):
|
|
76
79
|
raise Error(f'Expected {to!r} to be a Locale enum')
|
|
77
80
|
|
|
78
|
-
self.
|
|
81
|
+
self._locale = to
|
|
79
82
|
|
|
80
83
|
|
|
81
84
|
class BaseForecast:
|
|
82
85
|
__slots__: tuple[str, ...] = (
|
|
86
|
+
'cloud_cover',
|
|
83
87
|
'ultraviolet',
|
|
84
88
|
'humidity',
|
|
85
89
|
'wind_direction',
|
|
@@ -93,6 +97,9 @@ class BaseForecast:
|
|
|
93
97
|
'description',
|
|
94
98
|
)
|
|
95
99
|
|
|
100
|
+
cloud_cover: int
|
|
101
|
+
"""The cloud cover value in percent."""
|
|
102
|
+
|
|
96
103
|
ultraviolet: UltraViolet
|
|
97
104
|
"""The ultra-violet index."""
|
|
98
105
|
|
|
@@ -133,6 +140,7 @@ class BaseForecast:
|
|
|
133
140
|
else json[f'lang_{locale.value}'][0]['value']
|
|
134
141
|
)
|
|
135
142
|
|
|
143
|
+
self.cloud_cover = int(json['cloudcover'])
|
|
136
144
|
self.ultraviolet = UltraViolet._new(int(json['uvIndex']))
|
|
137
145
|
self.humidity = int(json['humidity'])
|
|
138
146
|
self.wind_direction = WindDirection._new(
|
|
@@ -24,7 +24,6 @@ SOFTWARE.
|
|
|
24
24
|
|
|
25
25
|
from aiohttp import ClientSession, ClientTimeout, ClientResponseError, TCPConnector
|
|
26
26
|
from urllib.parse import quote_plus
|
|
27
|
-
from typing import Optional
|
|
28
27
|
from asyncio import sleep
|
|
29
28
|
|
|
30
29
|
from .errors import Error, RequestError
|
|
@@ -39,56 +38,82 @@ class Client(CustomizableBase):
|
|
|
39
38
|
"""
|
|
40
39
|
Interact with the API's endpoints.
|
|
41
40
|
|
|
41
|
+
Examples:
|
|
42
|
+
|
|
43
|
+
.. code-block:: python
|
|
44
|
+
|
|
45
|
+
# Explicit cleanup
|
|
46
|
+
client = python_weather.Client(unit=python_weather.IMPERIAL)
|
|
47
|
+
|
|
48
|
+
# ...
|
|
49
|
+
|
|
50
|
+
await client.close()
|
|
51
|
+
|
|
52
|
+
# Implicit cleanup
|
|
53
|
+
async with python_weather.Client(unit=python_weather.IMPERIAL) as client:
|
|
54
|
+
# ...
|
|
55
|
+
|
|
42
56
|
:param unit: Whether to use the metric or imperial/customary system (:data:`~.constants.IMPERIAL`). Defaults to :data:`~.constants.METRIC`.
|
|
43
57
|
:type unit: ``_Unit``
|
|
44
58
|
:param locale: Whether to use a different locale/language as the description for the returned forecast. Defaults to :attr:`.Locale.ENGLISH`.
|
|
45
59
|
:type locale: :class:`.Locale`
|
|
46
60
|
:param session: Whether to use an existing :class:`~aiohttp.ClientSession` for requesting or not. Defaults to :py:obj:`None` (creates a new one instead).
|
|
47
|
-
:type session:
|
|
48
|
-
:param max_retries: Maximum amount of retries upon request failure before raising a :class:`.RequestError`.
|
|
49
|
-
|
|
61
|
+
:type session: :class:`~aiohttp.ClientSession` | :py:obj:`None`
|
|
62
|
+
:param max_retries: Maximum amount of retries upon request failure before raising a :class:`.RequestError`.
|
|
63
|
+
Use ``-1`` to disable (NOT recommended). Defaults to 3 retries.
|
|
64
|
+
:type max_retries: :class:`int` | :py:obj:`None`
|
|
50
65
|
|
|
51
66
|
:exception Error: ``unit`` is not :data:`~.constants.METRIC` or :data:`~.constants.IMPERIAL` or ``locale`` is not a part of the :class:`.Locale` enum.
|
|
52
67
|
"""
|
|
53
68
|
|
|
54
|
-
__slots__: tuple[str, ...] =
|
|
69
|
+
__slots__: tuple[str, ...] = '__own_session', '__session', '_max_retries'
|
|
70
|
+
|
|
71
|
+
__own_session: bool
|
|
72
|
+
__session: ClientSession
|
|
73
|
+
_max_retries: int
|
|
55
74
|
|
|
56
75
|
def __init__(
|
|
57
76
|
self,
|
|
58
77
|
*,
|
|
59
78
|
unit: _Unit = METRIC,
|
|
60
79
|
locale: Locale = Locale.ENGLISH,
|
|
61
|
-
session:
|
|
62
|
-
max_retries:
|
|
80
|
+
session: ClientSession | None = None,
|
|
81
|
+
max_retries: int | None = None,
|
|
63
82
|
):
|
|
64
83
|
super().__init__(unit, locale)
|
|
65
84
|
|
|
66
85
|
self.__own_session = session is None
|
|
67
86
|
self.__session = session or ClientSession(
|
|
68
87
|
timeout=ClientTimeout(total=5000.0),
|
|
69
|
-
connector=TCPConnector(
|
|
88
|
+
connector=TCPConnector(ssl=False),
|
|
70
89
|
)
|
|
71
|
-
self.
|
|
90
|
+
self._max_retries = max_retries or 3
|
|
72
91
|
|
|
73
92
|
def __repr__(self) -> str:
|
|
74
|
-
return f'<{__class__.__name__} {self.__session!r}>'
|
|
93
|
+
return f'<{__class__.__module__}.{__class__.__name__} {self.__session!r}>'
|
|
75
94
|
|
|
76
95
|
async def get(
|
|
77
96
|
self,
|
|
78
97
|
location: str,
|
|
79
98
|
*,
|
|
80
|
-
unit:
|
|
81
|
-
locale:
|
|
99
|
+
unit: _Unit | None = None,
|
|
100
|
+
locale: Locale | None = None,
|
|
82
101
|
) -> Forecast:
|
|
83
102
|
"""
|
|
84
103
|
Fetches a weather forecast for a specific location.
|
|
85
104
|
|
|
105
|
+
Example:
|
|
106
|
+
|
|
107
|
+
.. code-block:: python
|
|
108
|
+
|
|
109
|
+
weather = await client.get('New York')
|
|
110
|
+
|
|
86
111
|
:param location: The requested location.
|
|
87
112
|
:type location: :py:class:`str`
|
|
88
113
|
:param unit: Overrides the unit used.
|
|
89
|
-
:type unit:
|
|
114
|
+
:type unit: ``_Unit`` | :py:obj:`None`
|
|
90
115
|
:param locale: Overrides the locale used.
|
|
91
|
-
:type locale:
|
|
116
|
+
:type locale: :class:`.Locale` | :py:obj:`None`
|
|
92
117
|
|
|
93
118
|
:exception TypeError: ``location`` is not a :py:class:`str` or is empty.
|
|
94
119
|
:exception Error: The client is already closed.
|
|
@@ -105,14 +130,16 @@ class Client(CustomizableBase):
|
|
|
105
130
|
raise TypeError(f'Expected a proper location str, got {location!r}')
|
|
106
131
|
|
|
107
132
|
if not isinstance(unit, _Unit):
|
|
108
|
-
unit = self.
|
|
133
|
+
unit = self._unit
|
|
109
134
|
|
|
110
135
|
if not isinstance(locale, Locale):
|
|
111
|
-
locale = self.
|
|
136
|
+
locale = self._locale
|
|
112
137
|
|
|
113
138
|
subdomain = f'{locale.value}.' if locale != Locale.ENGLISH else ''
|
|
114
139
|
attempts = 0
|
|
140
|
+
|
|
115
141
|
status = None
|
|
142
|
+
reason = None
|
|
116
143
|
|
|
117
144
|
while True:
|
|
118
145
|
try:
|
|
@@ -124,18 +151,28 @@ class Client(CustomizableBase):
|
|
|
124
151
|
},
|
|
125
152
|
) as resp:
|
|
126
153
|
status = resp.status
|
|
154
|
+
reason = resp.reason
|
|
155
|
+
|
|
127
156
|
resp.raise_for_status()
|
|
128
157
|
|
|
129
158
|
return Forecast(await resp.json(), unit, locale)
|
|
130
159
|
except ClientResponseError:
|
|
131
|
-
if attempts == self.
|
|
132
|
-
raise RequestError(status) from None
|
|
160
|
+
if attempts == self._max_retries:
|
|
161
|
+
raise RequestError(status, reason) from None
|
|
133
162
|
|
|
134
163
|
await sleep(0.5 * (2**attempts))
|
|
135
164
|
attempts += 1
|
|
136
165
|
|
|
137
166
|
async def close(self) -> None:
|
|
138
|
-
"""
|
|
167
|
+
"""
|
|
168
|
+
Closes the :class:`.Client` object.
|
|
169
|
+
|
|
170
|
+
Example:
|
|
171
|
+
|
|
172
|
+
.. code-block:: python
|
|
173
|
+
|
|
174
|
+
await client.close()
|
|
175
|
+
"""
|
|
139
176
|
|
|
140
177
|
if self.__own_session and not self.__session.closed:
|
|
141
178
|
await self.__session.close()
|
|
@@ -22,7 +22,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
22
22
|
SOFTWARE.
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
|
-
from typing import Union
|
|
26
25
|
import re
|
|
27
26
|
|
|
28
27
|
|
|
@@ -36,6 +35,13 @@ class _Unit:
|
|
|
36
35
|
'cm_divisor',
|
|
37
36
|
)
|
|
38
37
|
|
|
38
|
+
temperature: str
|
|
39
|
+
velocity: str
|
|
40
|
+
pressure: str
|
|
41
|
+
precipitation: str
|
|
42
|
+
visibility: str
|
|
43
|
+
cm_divisor: float | int
|
|
44
|
+
|
|
39
45
|
def __init__(
|
|
40
46
|
self,
|
|
41
47
|
temperature: str,
|
|
@@ -43,7 +49,7 @@ class _Unit:
|
|
|
43
49
|
pressure: str,
|
|
44
50
|
precipitation: str,
|
|
45
51
|
visibility: str,
|
|
46
|
-
cm_divisor:
|
|
52
|
+
cm_divisor: float | int,
|
|
47
53
|
):
|
|
48
54
|
self.temperature = temperature
|
|
49
55
|
self.velocity = velocity
|
|
@@ -59,5 +65,5 @@ class _Unit:
|
|
|
59
65
|
METRIC = _Unit('C', 'Kmph', '', 'MM', '', 1)
|
|
60
66
|
IMPERIAL = _Unit('F', 'Miles', 'Inches', 'Inches', 'Miles', 2.54)
|
|
61
67
|
|
|
62
|
-
WIND_DIRECTION_EMOJIS =
|
|
68
|
+
WIND_DIRECTION_EMOJIS = '↓', '↙', '←', '↖', '↑', '↗', '→', '↘'
|
|
63
69
|
LATLON_REGEX = re.compile(r'^Lat (\-?[\d\.]+) and Lon (\-?[\d\.]+)$')
|
|
@@ -22,7 +22,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
22
22
|
SOFTWARE.
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
|
-
from typing import Union
|
|
26
25
|
from enum import Enum
|
|
27
26
|
|
|
28
27
|
from .constants import WIND_DIRECTION_EMOJIS
|
|
@@ -44,19 +43,22 @@ class IndexedEnum(Enum):
|
|
|
44
43
|
index: int
|
|
45
44
|
"""The index value."""
|
|
46
45
|
|
|
47
|
-
def __lt__(self, other:
|
|
46
|
+
def __lt__(self, other: 'IndexedEnum | float | int') -> bool:
|
|
48
47
|
return float(self.index) < float(other)
|
|
49
48
|
|
|
50
|
-
def __le__(self, other:
|
|
49
|
+
def __le__(self, other: 'IndexedEnum | float | int') -> bool:
|
|
51
50
|
return float(self.index) <= float(other)
|
|
52
51
|
|
|
53
|
-
def __eq__(self, other:
|
|
54
|
-
|
|
52
|
+
def __eq__(self, other: object) -> bool:
|
|
53
|
+
if other_float := getattr(other, '__float__', None):
|
|
54
|
+
return float(self.index) == other_float()
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
return False # pragma: nocover
|
|
57
|
+
|
|
58
|
+
def __gt__(self, other: 'IndexedEnum | float | int') -> bool:
|
|
57
59
|
return float(self.index) > float(other)
|
|
58
60
|
|
|
59
|
-
def __ge__(self, other:
|
|
61
|
+
def __ge__(self, other: 'IndexedEnum | float | int') -> bool:
|
|
60
62
|
return float(self.index) >= float(other)
|
|
61
63
|
|
|
62
64
|
def __hash__(self) -> int:
|
|
@@ -79,6 +81,7 @@ class HeatIndex(IndexedEnum):
|
|
|
79
81
|
DANGER = None
|
|
80
82
|
EXTREME_DANGER = None
|
|
81
83
|
|
|
84
|
+
@staticmethod
|
|
82
85
|
def _new(celcius_index: int, true_index: int) -> 'HeatIndex':
|
|
83
86
|
enum = HeatIndex(celcius_index)
|
|
84
87
|
enum.index = true_index
|
|
@@ -86,15 +89,16 @@ class HeatIndex(IndexedEnum):
|
|
|
86
89
|
return enum
|
|
87
90
|
|
|
88
91
|
@classmethod
|
|
89
|
-
def _missing_(
|
|
90
|
-
if
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
92
|
+
def _missing_(cls, value: object) -> 'HeatIndex | None':
|
|
93
|
+
if isinstance(value, int):
|
|
94
|
+
if value <= 32:
|
|
95
|
+
return cls.CAUTION
|
|
96
|
+
elif value <= 39:
|
|
97
|
+
return cls.EXTREME_CAUTION
|
|
98
|
+
elif value <= 51:
|
|
99
|
+
return cls.DANGER
|
|
100
|
+
else:
|
|
101
|
+
return cls.EXTREME_DANGER
|
|
98
102
|
|
|
99
103
|
|
|
100
104
|
class UltraViolet(BasicEnum, IndexedEnum):
|
|
@@ -108,6 +112,7 @@ class UltraViolet(BasicEnum, IndexedEnum):
|
|
|
108
112
|
VERY_HIGH = None
|
|
109
113
|
EXTREME = None
|
|
110
114
|
|
|
115
|
+
@staticmethod
|
|
111
116
|
def _new(index: int) -> 'UltraViolet':
|
|
112
117
|
enum = UltraViolet(index)
|
|
113
118
|
enum.index = index
|
|
@@ -115,17 +120,18 @@ class UltraViolet(BasicEnum, IndexedEnum):
|
|
|
115
120
|
return enum
|
|
116
121
|
|
|
117
122
|
@classmethod
|
|
118
|
-
def _missing_(
|
|
119
|
-
if
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
123
|
+
def _missing_(cls, value: object) -> 'UltraViolet | None':
|
|
124
|
+
if isinstance(value, int):
|
|
125
|
+
if value <= 2:
|
|
126
|
+
return cls.LOW
|
|
127
|
+
elif value <= 5:
|
|
128
|
+
return cls.MODERATE
|
|
129
|
+
elif value <= 7:
|
|
130
|
+
return cls.HIGH
|
|
131
|
+
elif value <= 10:
|
|
132
|
+
return cls.VERY_HIGH
|
|
133
|
+
else:
|
|
134
|
+
return cls.EXTREME
|
|
129
135
|
|
|
130
136
|
|
|
131
137
|
class WindDirection(BasicEnum):
|
|
@@ -153,13 +159,14 @@ class WindDirection(BasicEnum):
|
|
|
153
159
|
degrees: float
|
|
154
160
|
"""The wind direction's value in degrees."""
|
|
155
161
|
|
|
162
|
+
@staticmethod
|
|
156
163
|
def _new(value: str, degrees: float) -> 'WindDirection':
|
|
157
164
|
enum = WindDirection(value)
|
|
158
165
|
enum.degrees = degrees
|
|
159
166
|
|
|
160
167
|
return enum
|
|
161
168
|
|
|
162
|
-
def __contains__(self, other:
|
|
169
|
+
def __contains__(self, other: 'WindDirection | float | int') -> bool:
|
|
163
170
|
other = float(other)
|
|
164
171
|
|
|
165
172
|
if self is self.NORTH:
|
|
@@ -291,7 +298,8 @@ class Locale(Enum):
|
|
|
291
298
|
|
|
292
299
|
def __str__(self) -> str:
|
|
293
300
|
arr = self.name.title().split('_')
|
|
294
|
-
|
|
301
|
+
|
|
302
|
+
return f'{" ".join(arr[:-1])} ({arr[-1]})' if len(arr) != 1 else arr[0]
|
|
295
303
|
|
|
296
304
|
|
|
297
305
|
class Kind(BasicEnum):
|
|
@@ -319,31 +327,31 @@ class Kind(BasicEnum):
|
|
|
319
327
|
THUNDERY_SNOW_SHOWERS = 392
|
|
320
328
|
|
|
321
329
|
@classmethod
|
|
322
|
-
def _missing_(
|
|
330
|
+
def _missing_(cls, value: object) -> 'Kind | None':
|
|
323
331
|
if value in (248, 260):
|
|
324
|
-
return
|
|
332
|
+
return cls.FOG
|
|
325
333
|
elif value in (263, 353):
|
|
326
|
-
return
|
|
334
|
+
return cls.LIGHT_SHOWERS
|
|
327
335
|
elif value in (362, 365, 374):
|
|
328
|
-
return
|
|
336
|
+
return cls.LIGHT_SLEET_SHOWERS
|
|
329
337
|
elif value in (185, 281, 284, 311, 314, 317, 350, 377):
|
|
330
|
-
return
|
|
338
|
+
return cls.LIGHT_SLEET
|
|
331
339
|
elif value == 386:
|
|
332
|
-
return
|
|
340
|
+
return cls.THUNDERY_SHOWERS
|
|
333
341
|
elif value == 320:
|
|
334
|
-
return
|
|
342
|
+
return cls.LIGHT_SNOW
|
|
335
343
|
elif value in (329, 332, 338):
|
|
336
|
-
return
|
|
344
|
+
return cls.HEAVY_SNOW
|
|
337
345
|
elif value in (293, 296):
|
|
338
|
-
return
|
|
346
|
+
return cls.LIGHT_RAIN
|
|
339
347
|
elif value in (305, 356):
|
|
340
|
-
return
|
|
348
|
+
return cls.HEAVY_SHOWERS
|
|
341
349
|
elif value in (308, 359):
|
|
342
|
-
return
|
|
350
|
+
return cls.HEAVY_RAIN
|
|
343
351
|
elif value in (326, 368):
|
|
344
|
-
return
|
|
352
|
+
return cls.LIGHT_SNOW_SHOWERS
|
|
345
353
|
elif value in (371, 395):
|
|
346
|
-
return
|
|
354
|
+
return cls.HEAVY_SNOW_SHOWERS
|
|
347
355
|
|
|
348
356
|
@property
|
|
349
357
|
def emoji(self) -> str:
|
|
@@ -383,10 +391,8 @@ class Kind(BasicEnum):
|
|
|
383
391
|
return '⛈'
|
|
384
392
|
elif self is self.THUNDERY_SNOW_SHOWERS:
|
|
385
393
|
return '⛈'
|
|
386
|
-
elif self is self.VERY_CLOUDY:
|
|
387
|
-
return '☁️'
|
|
388
394
|
else:
|
|
389
|
-
return '
|
|
395
|
+
return '☁️'
|
|
390
396
|
|
|
391
397
|
|
|
392
398
|
class Phase(BasicEnum):
|
|
@@ -22,8 +22,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
22
22
|
SOFTWARE.
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
|
-
from typing import Optional
|
|
26
|
-
|
|
27
25
|
|
|
28
26
|
class Error(Exception):
|
|
29
27
|
"""The base error class. Extends :py:class:`Exception`."""
|
|
@@ -34,15 +32,19 @@ class Error(Exception):
|
|
|
34
32
|
class RequestError(Error):
|
|
35
33
|
"""Thrown upon HTTP request failure. Extends :class:`.Error`."""
|
|
36
34
|
|
|
37
|
-
__slots__: tuple[str, ...] =
|
|
35
|
+
__slots__: tuple[str, ...] = 'status', 'reason'
|
|
36
|
+
|
|
37
|
+
status: int | None
|
|
38
|
+
"""The status code."""
|
|
38
39
|
|
|
39
|
-
|
|
40
|
-
"""The
|
|
40
|
+
reason: str | None
|
|
41
|
+
"""The reason for this status code."""
|
|
41
42
|
|
|
42
|
-
def __init__(self, status:
|
|
43
|
+
def __init__(self, status: int | None, reason: str | None):
|
|
43
44
|
self.status = status
|
|
45
|
+
self.reason = reason
|
|
44
46
|
|
|
45
|
-
super().__init__()
|
|
47
|
+
super().__init__(f'{status}: {reason}')
|
|
46
48
|
|
|
47
|
-
def __repr__(self) -> str:
|
|
48
|
-
return f'<{__class__.__name__} status={self.status}>'
|
|
49
|
+
def __repr__(self) -> str: # pragma: nocover
|
|
50
|
+
return f'<{__class__.__module__}.{__class__.__name__} status={self.status} reason={self.reason!r}>'
|
|
@@ -23,13 +23,17 @@ SOFTWARE.
|
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
25
|
from datetime import datetime, date, time
|
|
26
|
-
from collections.abc import
|
|
27
|
-
from typing import
|
|
26
|
+
from collections.abc import Iterator
|
|
27
|
+
from typing import TYPE_CHECKING
|
|
28
28
|
|
|
29
|
-
from .enums import Phase,
|
|
30
|
-
from .constants import
|
|
29
|
+
from .enums import Phase, HeatIndex
|
|
30
|
+
from .constants import LATLON_REGEX
|
|
31
31
|
from .base import BaseForecast
|
|
32
32
|
|
|
33
|
+
if TYPE_CHECKING:
|
|
34
|
+
from .constants import _Unit
|
|
35
|
+
from .enums import Locale
|
|
36
|
+
|
|
33
37
|
|
|
34
38
|
class HourlyForecast(BaseForecast):
|
|
35
39
|
"""A weather forecast for a specific hour."""
|
|
@@ -45,7 +49,6 @@ class HourlyForecast(BaseForecast):
|
|
|
45
49
|
'chances_of_sunshine',
|
|
46
50
|
'chances_of_thunder',
|
|
47
51
|
'chances_of_windy',
|
|
48
|
-
'cloud_cover',
|
|
49
52
|
'time',
|
|
50
53
|
'dew_point',
|
|
51
54
|
'heat_index',
|
|
@@ -83,9 +86,6 @@ class HourlyForecast(BaseForecast):
|
|
|
83
86
|
chances_of_windy: int
|
|
84
87
|
"""Chances of windy in percent."""
|
|
85
88
|
|
|
86
|
-
cloud_cover: int
|
|
87
|
-
"""The cloud cover value in percent."""
|
|
88
|
-
|
|
89
89
|
time: 'time'
|
|
90
90
|
"""The local time in hours and minutes."""
|
|
91
91
|
|
|
@@ -101,7 +101,7 @@ class HourlyForecast(BaseForecast):
|
|
|
101
101
|
wind_gust: int
|
|
102
102
|
"""The wind gust value in either kilometers/hour or miles/hour."""
|
|
103
103
|
|
|
104
|
-
def __init__(self, json: dict, unit: _Unit, locale: Locale):
|
|
104
|
+
def __init__(self, json: dict, unit: '_Unit', locale: 'Locale'):
|
|
105
105
|
# For inheritance purposes.
|
|
106
106
|
if 'temp_C' not in json:
|
|
107
107
|
json['temp_C'] = json.pop('tempC')
|
|
@@ -122,7 +122,6 @@ class HourlyForecast(BaseForecast):
|
|
|
122
122
|
self.chances_of_sunshine = int(json['chanceofsunshine'])
|
|
123
123
|
self.chances_of_thunder = int(json['chanceofthunder'])
|
|
124
124
|
self.chances_of_windy = int(json['chanceofwindy'])
|
|
125
|
-
self.cloud_cover = int(json['cloudcover'])
|
|
126
125
|
self.time = time() if len(t) < 3 else datetime.strptime(t, '%H%M').time()
|
|
127
126
|
self.dew_point = int(json[f'DewPoint{unit.temperature}'])
|
|
128
127
|
self.heat_index = HeatIndex._new(
|
|
@@ -135,7 +134,7 @@ class HourlyForecast(BaseForecast):
|
|
|
135
134
|
super().__init__(json, unit, locale)
|
|
136
135
|
|
|
137
136
|
def __repr__(self) -> str:
|
|
138
|
-
return f'<{__class__.__name__} time={self.time!r} temperature={self.temperature}
|
|
137
|
+
return f'<{__class__.__module__}.{__class__.__name__} time={self.time!r} temperature={self.temperature} kind={self.kind!r}>'
|
|
139
138
|
|
|
140
139
|
|
|
141
140
|
class DailyForecast:
|
|
@@ -163,16 +162,16 @@ class DailyForecast:
|
|
|
163
162
|
moon_phase: Phase
|
|
164
163
|
"""The moon's phase."""
|
|
165
164
|
|
|
166
|
-
moonrise:
|
|
165
|
+
moonrise: time | None
|
|
167
166
|
"""The local time when the moon rises."""
|
|
168
167
|
|
|
169
|
-
moonset:
|
|
168
|
+
moonset: time | None
|
|
170
169
|
"""The local time when the moon sets."""
|
|
171
170
|
|
|
172
|
-
sunrise:
|
|
171
|
+
sunrise: time | None
|
|
173
172
|
"""The local time when the sun rises."""
|
|
174
173
|
|
|
175
|
-
sunset:
|
|
174
|
+
sunset: time | None
|
|
176
175
|
"""The local time when the sun sets."""
|
|
177
176
|
|
|
178
177
|
date: 'date'
|
|
@@ -196,7 +195,7 @@ class DailyForecast:
|
|
|
196
195
|
hourly_forecasts: list[HourlyForecast]
|
|
197
196
|
"""The hourly forecasts of this day."""
|
|
198
197
|
|
|
199
|
-
def __init__(self, json: dict, unit: _Unit, locale: Locale):
|
|
198
|
+
def __init__(self, json: dict, unit: '_Unit', locale: 'Locale'):
|
|
200
199
|
astronomy = json['astronomy'][0]
|
|
201
200
|
|
|
202
201
|
self.moon_illumination = int(astronomy['moon_illumination'])
|
|
@@ -216,19 +215,19 @@ class DailyForecast:
|
|
|
216
215
|
]
|
|
217
216
|
|
|
218
217
|
@staticmethod
|
|
219
|
-
def __parse_time(timestamp: str) ->
|
|
218
|
+
def __parse_time(timestamp: str) -> time | None:
|
|
220
219
|
try:
|
|
221
220
|
return datetime.strptime(timestamp, '%I:%M %p').time()
|
|
222
|
-
except ValueError:
|
|
221
|
+
except ValueError: # pragma: nocover
|
|
223
222
|
...
|
|
224
223
|
|
|
225
224
|
def __repr__(self) -> str:
|
|
226
|
-
return f'<{__class__.__name__} date={self.date!r} temperature={self.temperature}>'
|
|
225
|
+
return f'<{__class__.__module__}.{__class__.__name__} date={self.date!r} temperature={self.temperature}>'
|
|
227
226
|
|
|
228
227
|
def __len__(self) -> int:
|
|
229
228
|
return len(self.hourly_forecasts)
|
|
230
229
|
|
|
231
|
-
def __iter__(self) ->
|
|
230
|
+
def __iter__(self) -> Iterator[HourlyForecast]:
|
|
232
231
|
return iter(self.hourly_forecasts)
|
|
233
232
|
|
|
234
233
|
|
|
@@ -266,7 +265,7 @@ class Forecast(BaseForecast):
|
|
|
266
265
|
daily_forecasts: list[DailyForecast]
|
|
267
266
|
"""Daily weather forecasts in this location."""
|
|
268
267
|
|
|
269
|
-
def __init__(self, json: dict, unit: _Unit, locale: Locale):
|
|
268
|
+
def __init__(self, json: dict, unit: '_Unit', locale: 'Locale'):
|
|
270
269
|
current = json['current_condition'][0]
|
|
271
270
|
nearest = json['nearest_area'][0]
|
|
272
271
|
|
|
@@ -280,9 +279,12 @@ class Forecast(BaseForecast):
|
|
|
280
279
|
req = next(filter(lambda x: x['type'] == 'LatLon', json['request']))
|
|
281
280
|
match = LATLON_REGEX.match(req['query'])
|
|
282
281
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
282
|
+
if TYPE_CHECKING:
|
|
283
|
+
assert match is not None
|
|
284
|
+
|
|
285
|
+
self.coordinates = float(match[1]), float(match[2])
|
|
286
|
+
except (AssertionError, KeyError, StopIteration):
|
|
287
|
+
self.coordinates = float(nearest['latitude']), float(nearest['longitude'])
|
|
286
288
|
|
|
287
289
|
self.daily_forecasts = [
|
|
288
290
|
DailyForecast(elem, unit, locale) for elem in json['weather']
|
|
@@ -291,10 +293,10 @@ class Forecast(BaseForecast):
|
|
|
291
293
|
super().__init__(current, unit, locale)
|
|
292
294
|
|
|
293
295
|
def __repr__(self) -> str:
|
|
294
|
-
return f'<{__class__.__name__} location={self.location!r} datetime={self.datetime!r} temperature={self.temperature}>'
|
|
296
|
+
return f'<{__class__.__module__}.{__class__.__name__} location={self.location!r} datetime={self.datetime!r} temperature={self.temperature}>'
|
|
295
297
|
|
|
296
298
|
def __len__(self) -> int:
|
|
297
299
|
return len(self.daily_forecasts)
|
|
298
300
|
|
|
299
|
-
def __iter__(self) ->
|
|
301
|
+
def __iter__(self) -> Iterator[DailyForecast]:
|
|
300
302
|
return iter(self.daily_forecasts)
|
|
@@ -1,110 +1,104 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: python-weather
|
|
3
|
-
Version: 2.1.
|
|
4
|
-
Summary: A free and asynchronous weather API wrapper made in Python, for Python.
|
|
5
|
-
Author: null8626
|
|
6
|
-
License: MIT
|
|
7
|
-
Project-URL: Documentation, https://python-weather.readthedocs.io/en/latest/
|
|
8
|
-
Project-URL: Repository, https://github.com/null8626/python-weather
|
|
9
|
-
Project-URL: Changelog, https://python-weather.readthedocs.io/en/latest/changelog.html
|
|
10
|
-
Project-URL: Donate via GitHub Sponsors, https://github.com/sponsors/null8626
|
|
11
|
-
Project-URL: Donate via Ko-fi, https://ko-fi.com/null8626
|
|
12
|
-
Keywords: weather,forecast,weather-
|
|
13
|
-
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
-
Classifier: Intended Audience :: Education
|
|
15
|
-
Classifier: Intended Audience :: Developers
|
|
16
|
-
Classifier: Intended Audience :: Information Technology
|
|
17
|
-
Classifier: Natural Language :: English
|
|
18
|
-
Classifier: Operating System :: OS Independent
|
|
19
|
-
Classifier: Topic :: Education
|
|
20
|
-
Classifier: Topic :: Internet
|
|
21
|
-
Classifier: Topic :: Scientific/Engineering
|
|
22
|
-
Classifier: Topic :: Scientific/Engineering :: Astronomy
|
|
23
|
-
Classifier: Topic :: Software Development
|
|
24
|
-
Classifier: Topic :: Software Development :: Libraries
|
|
25
|
-
Classifier:
|
|
26
|
-
Classifier: Programming Language :: Python
|
|
27
|
-
Classifier: Programming Language :: Python :: 3
|
|
28
|
-
Classifier: Programming Language :: Python :: 3
|
|
29
|
-
Classifier: Programming Language :: Python :: 3.
|
|
30
|
-
Classifier: Programming Language :: Python :: 3.
|
|
31
|
-
Classifier: Programming Language :: Python :: 3.
|
|
32
|
-
Classifier: Programming Language :: Python :: 3.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
[pypi-image]: https://img.shields.io/pypi/v/python-weather.svg?style=flat-square
|
|
43
|
-
[pypi-
|
|
44
|
-
[
|
|
45
|
-
[
|
|
46
|
-
[
|
|
47
|
-
[
|
|
48
|
-
[ko-fi-
|
|
49
|
-
[ko-fi-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
import
|
|
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.)
|
|
76
|
-
async with python_weather.Client(unit=python_weather.IMPERIAL) as client:
|
|
77
|
-
|
|
78
|
-
# Fetch a weather forecast from a city.
|
|
79
|
-
weather = await client.get('New York')
|
|
80
|
-
|
|
81
|
-
# Fetch the temperature for today.
|
|
82
|
-
print(weather.temperature)
|
|
83
|
-
|
|
84
|
-
# Fetch weather forecast for upcoming days.
|
|
85
|
-
for daily in weather:
|
|
86
|
-
print(daily)
|
|
87
|
-
|
|
88
|
-
# Each daily forecast has their own hourly forecasts.
|
|
89
|
-
for hourly in daily:
|
|
90
|
-
print(f' --> {hourly!r}')
|
|
91
|
-
|
|
92
|
-
if __name__ == '__main__':
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
## Donations
|
|
107
|
-
|
|
108
|
-
If you want to support this project, consider donating! ❤
|
|
109
|
-
|
|
110
|
-
[![ko-fi][ko-fi-image]][ko-fi-url]
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: python-weather
|
|
3
|
+
Version: 2.1.1
|
|
4
|
+
Summary: A free and asynchronous weather API wrapper made in Python, for Python.
|
|
5
|
+
Author: null8626
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Documentation, https://python-weather.readthedocs.io/en/latest/
|
|
8
|
+
Project-URL: Repository, https://github.com/null8626/python-weather
|
|
9
|
+
Project-URL: Changelog, https://python-weather.readthedocs.io/en/latest/changelog.html
|
|
10
|
+
Project-URL: Donate via GitHub Sponsors, https://github.com/sponsors/null8626
|
|
11
|
+
Project-URL: Donate via Ko-fi, https://ko-fi.com/null8626
|
|
12
|
+
Keywords: weather,forecast,weather-forecast
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Intended Audience :: Education
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Intended Audience :: Information Technology
|
|
17
|
+
Classifier: Natural Language :: English
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Topic :: Education
|
|
20
|
+
Classifier: Topic :: Internet
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Astronomy
|
|
23
|
+
Classifier: Topic :: Software Development
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
25
|
+
Classifier: Programming Language :: Python
|
|
26
|
+
Classifier: Programming Language :: Python :: 3
|
|
27
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
28
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
29
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
30
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
31
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
32
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
33
|
+
Requires-Python: >=3.10
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
License-File: LICENSE
|
|
36
|
+
Requires-Dist: aiohttp>=3.13.2
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
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]
|
|
40
|
+
|
|
41
|
+
[pypi-url]: https://pypi.org/project/python-weather/
|
|
42
|
+
[pypi-image]: https://img.shields.io/pypi/v/python-weather.svg?style=flat-square
|
|
43
|
+
[pypi-downloads-image]: https://img.shields.io/pypi/dm/python-weather?style=flat-square
|
|
44
|
+
[codacy-url]: https://app.codacy.com/gh/null8626/python-weather/dashboard
|
|
45
|
+
[codacy-image]: https://app.codacy.com/project/badge/Grade/0f7721b7e4314a748c75a04f0a7e0ce3
|
|
46
|
+
[codecov-url]: https://codecov.io/gh/null8626/python-weather
|
|
47
|
+
[codecov-image]: https://codecov.io/gh/null8626/python-weather/graph/badge.svg
|
|
48
|
+
[ko-fi-url]: https://ko-fi.com/null8626
|
|
49
|
+
[ko-fi-image]: https://ko-fi.com/img/githubbutton_sm.svg
|
|
50
|
+
[ko-fi-brief-image]: https://img.shields.io/badge/donations-ko--fi-red?color=ff5e5b&style=flat-square
|
|
51
|
+
|
|
52
|
+
A free and asynchronous weather Python API wrapper made in Python, for Python.
|
|
53
|
+
|
|
54
|
+
## Getting started
|
|
55
|
+
|
|
56
|
+
Run the following command in your terminal:
|
|
57
|
+
|
|
58
|
+
```console
|
|
59
|
+
pip install python-weather
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Example
|
|
63
|
+
|
|
64
|
+
For more information, please read the [documentation](https://python-weather.readthedocs.io/en/latest/).
|
|
65
|
+
|
|
66
|
+
```py
|
|
67
|
+
# Import the module.
|
|
68
|
+
import python_weather
|
|
69
|
+
|
|
70
|
+
import asyncio
|
|
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.)
|
|
76
|
+
async with python_weather.Client(unit=python_weather.IMPERIAL) as client:
|
|
77
|
+
|
|
78
|
+
# Fetch a weather forecast from a city.
|
|
79
|
+
weather = await client.get('New York')
|
|
80
|
+
|
|
81
|
+
# Fetch the temperature for today.
|
|
82
|
+
print(weather.temperature)
|
|
83
|
+
|
|
84
|
+
# Fetch weather forecast for upcoming days.
|
|
85
|
+
for daily in weather:
|
|
86
|
+
print(daily)
|
|
87
|
+
|
|
88
|
+
# Each daily forecast has their own hourly forecasts.
|
|
89
|
+
for hourly in daily:
|
|
90
|
+
print(f' --> {hourly!r}')
|
|
91
|
+
|
|
92
|
+
if __name__ == '__main__':
|
|
93
|
+
asyncio.run(main())
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Data source
|
|
97
|
+
|
|
98
|
+
This library depends on [`wttr.in`](https://github.com/chubin/wttr.in), which uses data from the [World Weather Online API](https://www.worldweatheronline.com/weather-api/).
|
|
99
|
+
|
|
100
|
+
## Donations
|
|
101
|
+
|
|
102
|
+
If you want to support this project, consider donating! ❤
|
|
103
|
+
|
|
104
|
+
[![ko-fi][ko-fi-image]][ko-fi-url]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
aiohttp>=3.13.2
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[egg_info]
|
|
2
|
-
tag_build =
|
|
3
|
-
tag_date = 0
|
|
4
|
-
|
|
1
|
+
[egg_info]
|
|
2
|
+
tag_build =
|
|
3
|
+
tag_date = 0
|
|
4
|
+
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
aiohttp>=3.12.4
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|