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.
Files changed (20) hide show
  1. {python_weather-2.1.0 → python_weather-2.1.1}/MANIFEST.in +8 -1
  2. {python_weather-2.1.0/python_weather.egg-info → python_weather-2.1.1}/PKG-INFO +104 -110
  3. {python_weather-2.1.0 → python_weather-2.1.1}/README.md +10 -15
  4. {python_weather-2.1.0 → python_weather-2.1.1}/pyproject.toml +17 -7
  5. {python_weather-2.1.0 → python_weather-2.1.1}/python_weather/__init__.py +5 -0
  6. {python_weather-2.1.0 → python_weather-2.1.1}/python_weather/base.py +13 -5
  7. {python_weather-2.1.0 → python_weather-2.1.1}/python_weather/client.py +56 -19
  8. {python_weather-2.1.0 → python_weather-2.1.1}/python_weather/constants.py +9 -3
  9. {python_weather-2.1.0 → python_weather-2.1.1}/python_weather/enums.py +51 -45
  10. {python_weather-2.1.0 → python_weather-2.1.1}/python_weather/errors.py +11 -9
  11. {python_weather-2.1.0 → python_weather-2.1.1}/python_weather/forecast.py +28 -26
  12. {python_weather-2.1.0 → python_weather-2.1.1/python_weather.egg-info}/PKG-INFO +104 -110
  13. python_weather-2.1.1/python_weather.egg-info/requires.txt +1 -0
  14. {python_weather-2.1.0 → python_weather-2.1.1}/setup.cfg +4 -4
  15. python_weather-2.1.0/python_weather.egg-info/requires.txt +0 -1
  16. {python_weather-2.1.0 → python_weather-2.1.1}/LICENSE +0 -0
  17. {python_weather-2.1.0 → python_weather-2.1.1}/python_weather/version.py +0 -0
  18. {python_weather-2.1.0 → python_weather-2.1.1}/python_weather.egg-info/SOURCES.txt +0 -0
  19. {python_weather-2.1.0 → python_weather-2.1.1}/python_weather.egg-info/dependency_links.txt +0 -0
  20. {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 test.py
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.0
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-api,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: License :: OSI Approved :: MIT License
26
- Classifier: Programming Language :: Python
27
- Classifier: Programming Language :: Python :: 3
28
- Classifier: Programming Language :: Python :: 3 :: Only
29
- Classifier: Programming Language :: Python :: 3.9
30
- Classifier: Programming Language :: Python :: 3.10
31
- Classifier: Programming Language :: Python :: 3.11
32
- Classifier: Programming Language :: Python :: 3.12
33
- Classifier: Programming Language :: Python :: 3.13
34
- Requires-Python: >=3.9
35
- Description-Content-Type: text/markdown
36
- License-File: LICENSE
37
- Requires-Dist: aiohttp>=3.12.4
38
- Dynamic: license-file
39
-
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
-
42
- [pypi-image]: https://img.shields.io/pypi/v/python-weather.svg?style=flat-square
43
- [pypi-url]: https://pypi.org/project/python-weather/
44
- [downloads-image]: https://img.shields.io/pypi/dm/python-weather?style=flat-square
45
- [ko-fi-brief-image]: https://img.shields.io/badge/donations-ko--fi-red?color=ff5e5b&style=flat-square
46
- [codacy-url]: https://app.codacy.com/project/badge/Grade/0f7721b7e4314a748c75a04f0a7e0ce3
47
- [codacy-image]: https://app.codacy.com/gh/null8626/python-weather/dashboard
48
- [ko-fi-image]: https://ko-fi.com/img/githubbutton_sm.svg
49
- [ko-fi-url]: https://ko-fi.com/null8626
50
-
51
- A free and asynchronous weather Python API wrapper made in Python, for Python.
52
-
53
- ## Getting started
54
-
55
- Run the following command in your terminal:
56
-
57
- ```console
58
- $ pip install python-weather
59
- ```
60
-
61
- ## Example
62
-
63
- For more information, please read the [documentation](https://python-weather.readthedocs.io/en/latest/).
64
-
65
- ```py
66
- # Import the module.
67
- import python_weather
68
-
69
- import asyncio
70
- import os
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
- # See https://stackoverflow.com/questions/45600579/asyncio-event-loop-is-closed-when-getting-loop
95
- # for more details.
96
- if os.name == 'nt':
97
- asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
98
-
99
- asyncio.run(main())
100
- ```
101
-
102
- ## Data source
103
-
104
- 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/).
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-url]][codacy-image] [![ko-fi][ko-fi-brief-image]][ko-fi-url]
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
- [downloads-image]: https://img.shields.io/pypi/dm/python-weather?style=flat-square
6
- [ko-fi-brief-image]: https://img.shields.io/badge/donations-ko--fi-red?color=ff5e5b&style=flat-square
7
- [codacy-url]: https://app.codacy.com/project/badge/Grade/0f7721b7e4314a748c75a04f0a7e0ce3
8
- [codacy-image]: https://app.codacy.com/gh/null8626/python-weather/dashboard
9
- [ko-fi-image]: https://ko-fi.com/img/githubbutton_sm.svg
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
- $ pip install python-weather
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.0"
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-api", "weather-forecast"]
12
- dependencies = ["aiohttp>=3.12.4"]
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.9"
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, ...] = ('__unit', '__locale')
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.__unit
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.__unit = to
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.__locale
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.__locale = to
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: Optional[:class:`~aiohttp.ClientSession`]
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.
49
- :type max_retries: Optional[:class:`int`]
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, ...] = ('__own_session', '__session', '__max_retries')
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: Optional[ClientSession] = None,
62
- max_retries: Optional[int] = None,
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(verify_ssl=False),
88
+ connector=TCPConnector(ssl=False),
70
89
  )
71
- self.__max_retries = max_retries or 3
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: Optional[_Unit] = None,
81
- locale: Optional[Locale] = None,
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: Optional[``_Unit``]
114
+ :type unit: ``_Unit`` | :py:obj:`None`
90
115
  :param locale: Overrides the locale used.
91
- :type locale: Optional[:class:`.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._CustomizableBase__unit
133
+ unit = self._unit
109
134
 
110
135
  if not isinstance(locale, Locale):
111
- locale = self._CustomizableBase__locale
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.__max_retries:
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
- """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."""
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: Union[float, int],
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: Union['IndexedEnum', float, int]) -> bool:
46
+ def __lt__(self, other: 'IndexedEnum | float | int') -> bool:
48
47
  return float(self.index) < float(other)
49
48
 
50
- def __le__(self, other: Union['IndexedEnum', float, int]) -> bool:
49
+ def __le__(self, other: 'IndexedEnum | float | int') -> bool:
51
50
  return float(self.index) <= float(other)
52
51
 
53
- def __eq__(self, other: Union['IndexedEnum', float, int]) -> bool:
54
- return float(self.index) == float(other)
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
- def __gt__(self, other: Union['IndexedEnum', float, int]) -> bool:
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: Union['IndexedEnum', float, int]) -> bool:
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_(self, celcius_index: int) -> 'HeatIndex':
90
- if celcius_index <= 32:
91
- return self.CAUTION
92
- elif celcius_index <= 39:
93
- return self.EXTREME_CAUTION
94
- elif celcius_index <= 51:
95
- return self.DANGER
96
- else:
97
- return self.EXTREME_DANGER
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_(self, index: int) -> 'UltraViolet':
119
- if index <= 2:
120
- return self.LOW
121
- elif index <= 5:
122
- return self.MODERATE
123
- elif index <= 7:
124
- return self.HIGH
125
- elif index <= 10:
126
- return self.VERY_HIGH
127
- else:
128
- return self.EXTREME
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: Union['WindDirection', float, int]) -> bool:
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
- return f'{arr[:-1].join(" ")} ({arr[-1]})' if len(arr) != 1 else arr[0]
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_(self, value: int) -> 'Kind':
330
+ def _missing_(cls, value: object) -> 'Kind | None':
323
331
  if value in (248, 260):
324
- return self.FOG
332
+ return cls.FOG
325
333
  elif value in (263, 353):
326
- return self.LIGHT_SHOWERS
334
+ return cls.LIGHT_SHOWERS
327
335
  elif value in (362, 365, 374):
328
- return self.LIGHT_SLEET_SHOWERS
336
+ return cls.LIGHT_SLEET_SHOWERS
329
337
  elif value in (185, 281, 284, 311, 314, 317, 350, 377):
330
- return self.LIGHT_SLEET
338
+ return cls.LIGHT_SLEET
331
339
  elif value == 386:
332
- return self.THUNDERY_SHOWERS
340
+ return cls.THUNDERY_SHOWERS
333
341
  elif value == 320:
334
- return self.LIGHT_SNOW
342
+ return cls.LIGHT_SNOW
335
343
  elif value in (329, 332, 338):
336
- return self.HEAVY_SNOW
344
+ return cls.HEAVY_SNOW
337
345
  elif value in (293, 296):
338
- return self.LIGHT_RAIN
346
+ return cls.LIGHT_RAIN
339
347
  elif value in (305, 356):
340
- return self.HEAVY_SHOWERS
348
+ return cls.HEAVY_SHOWERS
341
349
  elif value in (308, 359):
342
- return self.HEAVY_RAIN
350
+ return cls.HEAVY_RAIN
343
351
  elif value in (326, 368):
344
- return self.LIGHT_SNOW_SHOWERS
352
+ return cls.LIGHT_SNOW_SHOWERS
345
353
  elif value in (371, 395):
346
- return self.HEAVY_SNOW_SHOWERS
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, ...] = ('status',)
35
+ __slots__: tuple[str, ...] = 'status', 'reason'
36
+
37
+ status: int | None
38
+ """The status code."""
38
39
 
39
- status: Optional[int]
40
- """The status code returned from the API."""
40
+ reason: str | None
41
+ """The reason for this status code."""
41
42
 
42
- def __init__(self, status: Optional[int]):
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 Iterable
27
- from typing import Optional
26
+ from collections.abc import Iterator
27
+ from typing import TYPE_CHECKING
28
28
 
29
- from .enums import Phase, Locale, HeatIndex
30
- from .constants import _Unit, LATLON_REGEX
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} description={self.description!r} kind={self.kind!r}>'
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: Optional[time]
165
+ moonrise: time | None
167
166
  """The local time when the moon rises."""
168
167
 
169
- moonset: Optional[time]
168
+ moonset: time | None
170
169
  """The local time when the moon sets."""
171
170
 
172
- sunrise: Optional[time]
171
+ sunrise: time | None
173
172
  """The local time when the sun rises."""
174
173
 
175
- sunset: Optional[time]
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) -> Optional[time]:
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) -> Iterable[HourlyForecast]:
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
- self.coordinates = (float(match[1]), float(match[2]))
284
- except (KeyError, IndexError, StopIteration):
285
- self.coordinates = (float(nearest['latitude']), float(nearest['longitude']))
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) -> Iterable[DailyForecast]:
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.0
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-api,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: License :: OSI Approved :: MIT License
26
- Classifier: Programming Language :: Python
27
- Classifier: Programming Language :: Python :: 3
28
- Classifier: Programming Language :: Python :: 3 :: Only
29
- Classifier: Programming Language :: Python :: 3.9
30
- Classifier: Programming Language :: Python :: 3.10
31
- Classifier: Programming Language :: Python :: 3.11
32
- Classifier: Programming Language :: Python :: 3.12
33
- Classifier: Programming Language :: Python :: 3.13
34
- Requires-Python: >=3.9
35
- Description-Content-Type: text/markdown
36
- License-File: LICENSE
37
- Requires-Dist: aiohttp>=3.12.4
38
- Dynamic: license-file
39
-
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
-
42
- [pypi-image]: https://img.shields.io/pypi/v/python-weather.svg?style=flat-square
43
- [pypi-url]: https://pypi.org/project/python-weather/
44
- [downloads-image]: https://img.shields.io/pypi/dm/python-weather?style=flat-square
45
- [ko-fi-brief-image]: https://img.shields.io/badge/donations-ko--fi-red?color=ff5e5b&style=flat-square
46
- [codacy-url]: https://app.codacy.com/project/badge/Grade/0f7721b7e4314a748c75a04f0a7e0ce3
47
- [codacy-image]: https://app.codacy.com/gh/null8626/python-weather/dashboard
48
- [ko-fi-image]: https://ko-fi.com/img/githubbutton_sm.svg
49
- [ko-fi-url]: https://ko-fi.com/null8626
50
-
51
- A free and asynchronous weather Python API wrapper made in Python, for Python.
52
-
53
- ## Getting started
54
-
55
- Run the following command in your terminal:
56
-
57
- ```console
58
- $ pip install python-weather
59
- ```
60
-
61
- ## Example
62
-
63
- For more information, please read the [documentation](https://python-weather.readthedocs.io/en/latest/).
64
-
65
- ```py
66
- # Import the module.
67
- import python_weather
68
-
69
- import asyncio
70
- import os
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
- # See https://stackoverflow.com/questions/45600579/asyncio-event-loop-is-closed-when-getting-loop
95
- # for more details.
96
- if os.name == 'nt':
97
- asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
98
-
99
- asyncio.run(main())
100
- ```
101
-
102
- ## Data source
103
-
104
- 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/).
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