python-weather 2.2.1__tar.gz → 2.2.3__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.2.1/python_weather.egg-info → python_weather-2.2.3}/PKG-INFO +14 -7
  2. {python_weather-2.2.1 → python_weather-2.2.3}/README.md +5 -5
  3. {python_weather-2.2.1 → python_weather-2.2.3}/pyproject.toml +5 -2
  4. {python_weather-2.2.1 → python_weather-2.2.3}/python_weather/__init__.py +5 -6
  5. {python_weather-2.2.1 → python_weather-2.2.3}/python_weather/base.py +12 -8
  6. {python_weather-2.2.1 → python_weather-2.2.3}/python_weather/client.py +8 -7
  7. {python_weather-2.2.1 → python_weather-2.2.3}/python_weather/constants.py +3 -25
  8. {python_weather-2.2.1 → python_weather-2.2.3}/python_weather/enums.py +1 -0
  9. {python_weather-2.2.1 → python_weather-2.2.3}/python_weather/errors.py +6 -7
  10. {python_weather-2.2.1 → python_weather-2.2.3}/python_weather/forecast.py +23 -19
  11. {python_weather-2.2.1 → python_weather-2.2.3}/python_weather/version.py +1 -1
  12. {python_weather-2.2.1 → python_weather-2.2.3/python_weather.egg-info}/PKG-INFO +14 -7
  13. python_weather-2.2.3/python_weather.egg-info/requires.txt +9 -0
  14. python_weather-2.2.1/python_weather.egg-info/requires.txt +0 -1
  15. {python_weather-2.2.1 → python_weather-2.2.3}/LICENSE +0 -0
  16. {python_weather-2.2.1 → python_weather-2.2.3}/MANIFEST.in +0 -0
  17. {python_weather-2.2.1 → python_weather-2.2.3}/python_weather.egg-info/SOURCES.txt +0 -0
  18. {python_weather-2.2.1 → python_weather-2.2.3}/python_weather.egg-info/dependency_links.txt +0 -0
  19. {python_weather-2.2.1 → python_weather-2.2.3}/python_weather.egg-info/top_level.txt +0 -0
  20. {python_weather-2.2.1 → python_weather-2.2.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-weather
3
- Version: 2.2.1
3
+ Version: 2.2.3
4
4
  Summary: A free and asynchronous weather API wrapper made in Python, for Python.
5
5
  Author: null8626
6
6
  License-Expression: MIT
@@ -33,7 +33,14 @@ Classifier: Programming Language :: Python :: 3.14
33
33
  Requires-Python: >=3.10
34
34
  Description-Content-Type: text/markdown
35
35
  License-File: LICENSE
36
- Requires-Dist: aiohttp>=3.13.5
36
+ Requires-Dist: aiohttp>=3.14.3
37
+ Provides-Extra: dev
38
+ Requires-Dist: mock>=5.2.0; extra == "dev"
39
+ Requires-Dist: pytest>=9.1.1; extra == "dev"
40
+ Requires-Dist: pytest-asyncio>=1.4.0; extra == "dev"
41
+ Requires-Dist: pytest-cov>=7.1.0; extra == "dev"
42
+ Requires-Dist: multidict>=6.7.1; extra == "dev"
43
+ Requires-Dist: yarl>=1.24.5; extra == "dev"
37
44
  Dynamic: license-file
38
45
 
39
46
  # [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]
@@ -71,24 +78,24 @@ import asyncio
71
78
 
72
79
 
73
80
  async def main() -> None:
74
-
81
+
75
82
  # Declare the client. The measuring unit used defaults to the metric system (celcius, km/h, etc.)
76
83
  async with python_weather.Client(unit=python_weather.IMPERIAL) as client:
77
-
78
84
  # Fetch a weather forecast from a city.
79
85
  weather = await client.get('New York')
80
-
86
+
81
87
  # Fetch the temperature for today.
82
88
  print(weather.temperature)
83
-
89
+
84
90
  # Fetch weather forecast for upcoming days.
85
91
  for daily in weather:
86
92
  print(daily)
87
-
93
+
88
94
  # Each daily forecast has their own hourly forecasts.
89
95
  for hourly in daily:
90
96
  print(f' --> {hourly!r}')
91
97
 
98
+
92
99
  if __name__ == '__main__':
93
100
  asyncio.run(main())
94
101
  ```
@@ -33,24 +33,24 @@ import asyncio
33
33
 
34
34
 
35
35
  async def main() -> None:
36
-
36
+
37
37
  # Declare the client. The measuring unit used defaults to the metric system (celcius, km/h, etc.)
38
38
  async with python_weather.Client(unit=python_weather.IMPERIAL) as client:
39
-
40
39
  # Fetch a weather forecast from a city.
41
40
  weather = await client.get('New York')
42
-
41
+
43
42
  # Fetch the temperature for today.
44
43
  print(weather.temperature)
45
-
44
+
46
45
  # Fetch weather forecast for upcoming days.
47
46
  for daily in weather:
48
47
  print(daily)
49
-
48
+
50
49
  # Each daily forecast has their own hourly forecasts.
51
50
  for hourly in daily:
52
51
  print(f' --> {hourly!r}')
53
52
 
53
+
54
54
  if __name__ == '__main__':
55
55
  asyncio.run(main())
56
56
  ```
@@ -3,13 +3,13 @@ requires = ["setuptools"]
3
3
 
4
4
  [project]
5
5
  name = "python-weather"
6
- version = "2.2.1"
6
+ version = "2.2.3"
7
7
  description = "A free and asynchronous weather API wrapper made in Python, for Python."
8
8
  readme = "README.md"
9
9
  license = "MIT"
10
10
  authors = [{ name = "null8626" }]
11
11
  keywords = ["weather", "forecast", "weather-forecast"]
12
- dependencies = ["aiohttp>=3.13.5"]
12
+ dependencies = ["aiohttp>=3.14.3"]
13
13
  classifiers = [
14
14
  "Development Status :: 5 - Production/Stable",
15
15
  "Intended Audience :: Education",
@@ -34,6 +34,9 @@ classifiers = [
34
34
  ]
35
35
  requires-python = ">=3.10"
36
36
 
37
+ [project.optional-dependencies]
38
+ dev = ["mock>=5.2.0", "pytest>=9.1.1", "pytest-asyncio>=1.4.0", "pytest-cov>=7.1.0", "multidict>=6.7.1", "yarl>=1.24.5"]
39
+
37
40
  [project.urls]
38
41
  Documentation = "https://python-weather.readthedocs.io/en/latest/"
39
42
  Repository = "https://github.com/null8626/python-weather"
@@ -1,13 +1,12 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  # SPDX-FileCopyrightText: 2021-2026 null8626
3
3
 
4
+ from .client import Client
5
+ from .constants import IMPERIAL, METRIC
4
6
  from .enums import HeatIndex, Kind, Locale, Phase, UltraViolet, WindDirection
5
- from .constants import METRIC, IMPERIAL
6
7
  from .errors import Error, RequestError
7
8
  from .forecast import Forecast
8
9
  from .version import VERSION
9
- from .client import Client
10
-
11
10
 
12
11
  __title__ = 'python-weather'
13
12
  __author__ = 'null8626'
@@ -18,17 +17,17 @@ __license__ = 'MIT'
18
17
  __copyright__ = 'Copyright (c) 2021-2026 null8626'
19
18
  __version__ = VERSION
20
19
  __all__ = (
21
- 'METRIC',
22
20
  'IMPERIAL',
21
+ 'METRIC',
22
+ 'VERSION',
23
23
  'Client',
24
24
  'Error',
25
25
  'Forecast',
26
- 'RequestError',
27
26
  'HeatIndex',
28
27
  'Kind',
29
28
  'Locale',
30
29
  'Phase',
30
+ 'RequestError',
31
31
  'UltraViolet',
32
- 'VERSION',
33
32
  'WindDirection',
34
33
  )
@@ -1,8 +1,12 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  # SPDX-FileCopyrightText: 2021-2026 null8626
3
3
 
4
- from .enums import WindDirection, Kind, Locale, UltraViolet
5
- from .constants import _Unit
4
+ from typing import TYPE_CHECKING
5
+
6
+ from .enums import Kind, Locale, UltraViolet, WindDirection
7
+
8
+ if TYPE_CHECKING:
9
+ from .constants import _Unit
6
10
 
7
11
 
8
12
  class BaseForecast:
@@ -10,17 +14,17 @@ class BaseForecast:
10
14
 
11
15
  __slots__: tuple[str, ...] = (
12
16
  'cloud_cover',
13
- 'ultraviolet',
17
+ 'description',
18
+ 'feels_like',
14
19
  'humidity',
15
- 'wind_direction',
16
20
  'kind',
17
- 'feels_like',
18
- 'temperature',
19
21
  'precipitation',
20
22
  'pressure',
23
+ 'temperature',
24
+ 'ultraviolet',
21
25
  'visibility',
26
+ 'wind_direction',
22
27
  'wind_speed',
23
- 'description',
24
28
  )
25
29
 
26
30
  cloud_cover: int
@@ -59,7 +63,7 @@ class BaseForecast:
59
63
  description: str
60
64
  """The description regarding the forecast depending on the localization used."""
61
65
 
62
- def __init__(self, json: dict, unit: _Unit, locale: Locale):
66
+ def __init__(self, json: dict, unit: '_Unit', locale: Locale):
63
67
  description = (
64
68
  json['weatherDesc'][0]['value']
65
69
  if locale is Locale.ENGLISH
@@ -1,15 +1,16 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  # SPDX-FileCopyrightText: 2021-2026 null8626
3
3
 
4
- from aiohttp import ClientSession, ClientTimeout, ClientResponseError, TCPConnector
5
- from urllib.parse import quote_plus
6
4
  from asyncio import sleep
5
+ from urllib.parse import quote_plus
7
6
 
7
+ from aiohttp import ClientResponseError, ClientSession, ClientTimeout, TCPConnector
8
+
9
+ from .constants import METRIC, _Unit
10
+ from .enums import Locale
8
11
  from .errors import Error, RequestError
9
- from .constants import _Unit, METRIC
10
12
  from .forecast import Forecast
11
13
  from .version import VERSION
12
- from .enums import Locale
13
14
 
14
15
 
15
16
  class Client:
@@ -47,9 +48,9 @@ class Client:
47
48
  __slots__: tuple[str, ...] = (
48
49
  '__own_session',
49
50
  '__session',
51
+ '_locale',
50
52
  '_max_retries',
51
53
  '_unit',
52
- '_locale',
53
54
  )
54
55
 
55
56
  __own_session: bool
@@ -64,14 +65,14 @@ class Client:
64
65
  unit: _Unit = METRIC,
65
66
  locale: Locale = Locale.ENGLISH,
66
67
  session: ClientSession | None = None,
67
- max_retries: int | None = None,
68
+ max_retries: int = 3,
68
69
  ):
69
70
  self.__own_session = session is None
70
71
  self.__session = session or ClientSession(
71
72
  timeout=ClientTimeout(total=5000.0),
72
73
  connector=TCPConnector(ssl=False),
73
74
  )
74
- self._max_retries = max_retries or 3
75
+ self._max_retries = max_retries
75
76
  self.unit = unit
76
77
  self.locale = locale
77
78
 
@@ -2,20 +2,13 @@
2
2
  # SPDX-FileCopyrightText: 2021-2026 null8626
3
3
 
4
4
  import re
5
+ from dataclasses import dataclass
5
6
 
6
7
 
8
+ @dataclass(frozen=True, repr=False, slots=True)
7
9
  class _Unit:
8
10
  """A supported measurement unit."""
9
11
 
10
- __slots__: tuple[str, ...] = (
11
- 'temperature',
12
- 'velocity',
13
- 'pressure',
14
- 'precipitation',
15
- 'visibility',
16
- 'cm_divisor',
17
- )
18
-
19
12
  temperature: str
20
13
  velocity: str
21
14
  pressure: str
@@ -23,22 +16,6 @@ class _Unit:
23
16
  visibility: str
24
17
  cm_divisor: float | int
25
18
 
26
- def __init__(
27
- self,
28
- temperature: str,
29
- velocity: str,
30
- pressure: str,
31
- precipitation: str,
32
- visibility: str,
33
- cm_divisor: float | int,
34
- ):
35
- self.temperature = temperature
36
- self.velocity = velocity
37
- self.pressure = pressure
38
- self.precipitation = precipitation
39
- self.visibility = visibility
40
- self.cm_divisor = cm_divisor
41
-
42
19
  def __repr__(self) -> str:
43
20
  """The unit's debug string representation."""
44
21
  return f'<Unit [{self.temperature}, {self.velocity}]>'
@@ -68,5 +45,6 @@ KIND_EMOJIS = (
68
45
  '❄️',
69
46
  '🌩',
70
47
  '⛈',
48
+ '🌫',
71
49
  )
72
50
  WIND_DIRECTION_EMOJIS = '↑', '↖', '←', '↙', '↓', '↘', '→', '↗'
@@ -295,6 +295,7 @@ class Kind(BasicEnum):
295
295
  HEAVY_SNOW_SHOWERS = 335
296
296
  THUNDERY_HEAVY_RAIN = 389
297
297
  THUNDERY_SNOW_SHOWERS = 392
298
+ SMOKY_HAZE = 149
298
299
 
299
300
  @classmethod
300
301
  def _missing_(cls, value: object) -> 'Kind | None':
@@ -2,28 +2,27 @@
2
2
  # SPDX-FileCopyrightText: 2021-2026 null8626
3
3
 
4
4
 
5
+ from dataclasses import dataclass
6
+
7
+
5
8
  class Error(Exception):
6
9
  """The base error class. Extends :py:class:`Exception`."""
7
10
 
8
11
  __slots__: tuple[str, ...] = ()
9
12
 
10
13
 
14
+ @dataclass(frozen=True, repr=False, slots=True)
11
15
  class RequestError(Error):
12
16
  """Thrown upon HTTP request failure. Extends :class:`.Error`."""
13
17
 
14
- __slots__: tuple[str, ...] = 'status', 'reason'
15
-
16
18
  status: int | None
17
19
  """The status code."""
18
20
 
19
21
  reason: str | None
20
22
  """The reason for this status code."""
21
23
 
22
- def __init__(self, status: int | None, reason: str | None):
23
- self.status = status
24
- self.reason = reason
25
-
26
- super().__init__(f'{status}: {reason}')
24
+ def __post_init__(self) -> None:
25
+ super(Error, self).__init__(f'{self.status}: {self.reason}')
27
26
 
28
27
  def __repr__(self) -> str: # pragma: nocover
29
28
  """The error's debug string representation."""
@@ -1,15 +1,16 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  # SPDX-FileCopyrightText: 2021-2026 null8626
3
3
 
4
- from datetime import datetime, date, time
5
- from collections.abc import Iterator
4
+ from datetime import date, datetime, time
6
5
  from typing import TYPE_CHECKING
7
6
 
8
- from .enums import Phase, HeatIndex
9
- from .constants import LATLON_REGEX
10
7
  from .base import BaseForecast
8
+ from .constants import LATLON_REGEX
9
+ from .enums import HeatIndex, Phase
11
10
 
12
11
  if TYPE_CHECKING:
12
+ from collections.abc import Iterator
13
+
13
14
  from .constants import _Unit
14
15
  from .enums import Locale
15
16
 
@@ -28,9 +29,9 @@ class HourlyForecast(BaseForecast):
28
29
  'chances_of_sunshine',
29
30
  'chances_of_thunder',
30
31
  'chances_of_windy',
31
- 'time',
32
32
  'dew_point',
33
33
  'heat_index',
34
+ 'time',
34
35
  'wind_chill',
35
36
  'wind_gust',
36
37
  )
@@ -121,19 +122,19 @@ class DailyForecast:
121
122
  """A weather forecast for a specific day."""
122
123
 
123
124
  __slots__: tuple[str, ...] = (
125
+ 'date',
126
+ 'highest_temperature',
127
+ 'hourly_forecasts',
128
+ 'lowest_temperature',
124
129
  'moon_illumination',
125
130
  'moon_phase',
126
131
  'moonrise',
127
132
  'moonset',
133
+ 'snowfall',
134
+ 'sunlight',
128
135
  'sunrise',
129
136
  'sunset',
130
- 'date',
131
- 'sunlight',
132
- 'lowest_temperature',
133
- 'highest_temperature',
134
137
  'temperature',
135
- 'snowfall',
136
- 'hourly_forecasts',
137
138
  )
138
139
 
139
140
  moon_illumination: int
@@ -209,7 +210,7 @@ class DailyForecast:
209
210
  """The amount of hourly forecasts."""
210
211
  return len(self.hourly_forecasts)
211
212
 
212
- def __iter__(self) -> Iterator[HourlyForecast]:
213
+ def __iter__(self) -> 'Iterator[HourlyForecast]':
213
214
  """Iterates through the hourly forecasts."""
214
215
  return iter(self.hourly_forecasts)
215
216
 
@@ -218,13 +219,13 @@ class Forecast(BaseForecast):
218
219
  """A set of weather forecasts for a certain location."""
219
220
 
220
221
  __slots__: tuple[str, ...] = (
221
- 'local_population',
222
- 'region',
223
- 'location',
224
- 'country',
225
- 'datetime',
226
222
  'coordinates',
223
+ 'country',
227
224
  'daily_forecasts',
225
+ 'datetime',
226
+ 'local_population',
227
+ 'location',
228
+ 'region',
228
229
  )
229
230
 
230
231
  local_population: int
@@ -256,7 +257,10 @@ class Forecast(BaseForecast):
256
257
  self.region = nearest['region'][0]['value']
257
258
  self.location = nearest['areaName'][0]['value']
258
259
  self.country = nearest['country'][0]['value']
259
- self.datetime = datetime.strptime(current['localObsDateTime'], '%Y-%m-%d %I:%M %p')
260
+ self.datetime = datetime.combine(
261
+ datetime.today(),
262
+ datetime.strptime(current['observation_time'], '%I:%M %p').time(),
263
+ )
260
264
 
261
265
  try:
262
266
  req = next(filter(lambda x: x['type'] == 'LatLon', json['request']))
@@ -283,6 +287,6 @@ class Forecast(BaseForecast):
283
287
  """The amount of daily forecasts."""
284
288
  return len(self.daily_forecasts)
285
289
 
286
- def __iter__(self) -> Iterator[DailyForecast]:
290
+ def __iter__(self) -> 'Iterator[DailyForecast]':
287
291
  """Iterates through the daily forecasts."""
288
292
  return iter(self.daily_forecasts)
@@ -1,4 +1,4 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  # SPDX-FileCopyrightText: 2021-2026 null8626
3
3
 
4
- VERSION = '2.2.1'
4
+ VERSION = '2.2.3'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-weather
3
- Version: 2.2.1
3
+ Version: 2.2.3
4
4
  Summary: A free and asynchronous weather API wrapper made in Python, for Python.
5
5
  Author: null8626
6
6
  License-Expression: MIT
@@ -33,7 +33,14 @@ Classifier: Programming Language :: Python :: 3.14
33
33
  Requires-Python: >=3.10
34
34
  Description-Content-Type: text/markdown
35
35
  License-File: LICENSE
36
- Requires-Dist: aiohttp>=3.13.5
36
+ Requires-Dist: aiohttp>=3.14.3
37
+ Provides-Extra: dev
38
+ Requires-Dist: mock>=5.2.0; extra == "dev"
39
+ Requires-Dist: pytest>=9.1.1; extra == "dev"
40
+ Requires-Dist: pytest-asyncio>=1.4.0; extra == "dev"
41
+ Requires-Dist: pytest-cov>=7.1.0; extra == "dev"
42
+ Requires-Dist: multidict>=6.7.1; extra == "dev"
43
+ Requires-Dist: yarl>=1.24.5; extra == "dev"
37
44
  Dynamic: license-file
38
45
 
39
46
  # [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]
@@ -71,24 +78,24 @@ import asyncio
71
78
 
72
79
 
73
80
  async def main() -> None:
74
-
81
+
75
82
  # Declare the client. The measuring unit used defaults to the metric system (celcius, km/h, etc.)
76
83
  async with python_weather.Client(unit=python_weather.IMPERIAL) as client:
77
-
78
84
  # Fetch a weather forecast from a city.
79
85
  weather = await client.get('New York')
80
-
86
+
81
87
  # Fetch the temperature for today.
82
88
  print(weather.temperature)
83
-
89
+
84
90
  # Fetch weather forecast for upcoming days.
85
91
  for daily in weather:
86
92
  print(daily)
87
-
93
+
88
94
  # Each daily forecast has their own hourly forecasts.
89
95
  for hourly in daily:
90
96
  print(f' --> {hourly!r}')
91
97
 
98
+
92
99
  if __name__ == '__main__':
93
100
  asyncio.run(main())
94
101
  ```
@@ -0,0 +1,9 @@
1
+ aiohttp>=3.14.3
2
+
3
+ [dev]
4
+ mock>=5.2.0
5
+ pytest>=9.1.1
6
+ pytest-asyncio>=1.4.0
7
+ pytest-cov>=7.1.0
8
+ multidict>=6.7.1
9
+ yarl>=1.24.5
@@ -1 +0,0 @@
1
- aiohttp>=3.13.5
File without changes
File without changes