python-weather 2.0.4__tar.gz → 2.0.6__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.0.4/python_weather.egg-info → python_weather-2.0.6}/PKG-INFO +4 -4
  2. {python_weather-2.0.4 → python_weather-2.0.6}/pyproject.toml +5 -5
  3. {python_weather-2.0.4 → python_weather-2.0.6}/python_weather/__init__.py +1 -1
  4. {python_weather-2.0.4 → python_weather-2.0.6}/python_weather/base.py +56 -70
  5. {python_weather-2.0.4 → python_weather-2.0.6}/python_weather/client.py +26 -19
  6. {python_weather-2.0.4 → python_weather-2.0.6}/python_weather/enums.py +27 -36
  7. {python_weather-2.0.4 → python_weather-2.0.6}/python_weather/errors.py +2 -2
  8. python_weather-2.0.6/python_weather/forecast.py +296 -0
  9. {python_weather-2.0.4 → python_weather-2.0.6/python_weather.egg-info}/PKG-INFO +4 -4
  10. python_weather-2.0.6/python_weather.egg-info/requires.txt +1 -0
  11. python_weather-2.0.4/python_weather/forecast.py +0 -350
  12. python_weather-2.0.4/python_weather.egg-info/requires.txt +0 -1
  13. {python_weather-2.0.4 → python_weather-2.0.6}/LICENSE +0 -0
  14. {python_weather-2.0.4 → python_weather-2.0.6}/MANIFEST.in +0 -0
  15. {python_weather-2.0.4 → python_weather-2.0.6}/README.md +0 -0
  16. {python_weather-2.0.4 → python_weather-2.0.6}/python_weather/constants.py +0 -0
  17. {python_weather-2.0.4 → python_weather-2.0.6}/python_weather.egg-info/SOURCES.txt +0 -0
  18. {python_weather-2.0.4 → python_weather-2.0.6}/python_weather.egg-info/dependency_links.txt +0 -0
  19. {python_weather-2.0.4 → python_weather-2.0.6}/python_weather.egg-info/top_level.txt +0 -0
  20. {python_weather-2.0.4 → python_weather-2.0.6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-weather
3
- Version: 2.0.4
3
+ Version: 2.0.6
4
4
  Summary: A free and asynchronous weather API wrapper made in Python, for Python.
5
5
  Author: null8626
6
6
  License: MIT
@@ -11,12 +11,11 @@ Project-URL: Donate via GitHub Sponsors, https://github.com/sponsors/null8626
11
11
  Project-URL: Donate via Ko-fi, https://ko-fi.com/null8626
12
12
  Keywords: weather,forecast,weather-api,weather-forecast
13
13
  Classifier: Development Status :: 5 - Production/Stable
14
- Classifier: Framework :: aiohttp
15
- Classifier: Framework :: AsyncIO
16
14
  Classifier: Intended Audience :: Education
17
15
  Classifier: Intended Audience :: Developers
18
16
  Classifier: Intended Audience :: Information Technology
19
17
  Classifier: Natural Language :: English
18
+ Classifier: Operating System :: OS Independent
20
19
  Classifier: Topic :: Education
21
20
  Classifier: Topic :: Internet
22
21
  Classifier: Topic :: Scientific/Engineering
@@ -32,10 +31,11 @@ Classifier: Programming Language :: Python :: 3.9
32
31
  Classifier: Programming Language :: Python :: 3.10
33
32
  Classifier: Programming Language :: Python :: 3.11
34
33
  Classifier: Programming Language :: Python :: 3.12
34
+ Classifier: Programming Language :: Python :: 3.13
35
35
  Requires-Python: >=3.8
36
36
  Description-Content-Type: text/markdown
37
37
  License-File: LICENSE
38
- Requires-Dist: aiohttp>=3.10.8
38
+ Requires-Dist: aiohttp>=3.10.10
39
39
 
40
40
  # [python-weather][pypi-url] [![pypi][pypi-image]][pypi-url] [![downloads][downloads-image]][pypi-url] [![codacy-badge][codacy-url]][codacy-image] [![ko-fi][ko-fi-brief-image]][ko-fi-url]
41
41
 
@@ -3,21 +3,20 @@ requires = ["setuptools"]
3
3
 
4
4
  [project]
5
5
  name = "python-weather"
6
- version = "2.0.4"
6
+ version = "2.0.6"
7
7
  description = "A free and asynchronous weather API wrapper made in Python, for Python."
8
8
  readme = "README.md"
9
9
  license = { text = "MIT" }
10
10
  authors = [{ name = "null8626" }]
11
11
  keywords = ["weather", "forecast", "weather-api", "weather-forecast"]
12
- dependencies = ["aiohttp>=3.10.8"]
12
+ dependencies = ["aiohttp>=3.10.10"]
13
13
  classifiers = [
14
14
  "Development Status :: 5 - Production/Stable",
15
- "Framework :: aiohttp",
16
- "Framework :: AsyncIO",
17
15
  "Intended Audience :: Education",
18
16
  "Intended Audience :: Developers",
19
17
  "Intended Audience :: Information Technology",
20
18
  "Natural Language :: English",
19
+ "Operating System :: OS Independent",
21
20
  "Topic :: Education",
22
21
  "Topic :: Internet",
23
22
  "Topic :: Scientific/Engineering",
@@ -32,7 +31,8 @@ classifiers = [
32
31
  "Programming Language :: Python :: 3.9",
33
32
  "Programming Language :: Python :: 3.10",
34
33
  "Programming Language :: Python :: 3.11",
35
- "Programming Language :: Python :: 3.12"
34
+ "Programming Language :: Python :: 3.12",
35
+ "Programming Language :: Python :: 3.13"
36
36
  ]
37
37
  requires-python = ">=3.8"
38
38
 
@@ -31,7 +31,7 @@ __title__ = 'python-weather'
31
31
  __author__ = 'null8626'
32
32
  __license__ = 'MIT'
33
33
  __copyright__ = 'Copyright (c) 2021-2024 null8626'
34
- __version__ = '2.0.4'
34
+ __version__ = '2.0.6'
35
35
  __all__ = (
36
36
  'METRIC',
37
37
  'IMPERIAL',
@@ -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 enum import auto
26
25
  from typing import Tuple
27
26
 
28
27
  from .errors import Error
@@ -38,7 +37,7 @@ class CustomizableBase:
38
37
  self.locale = locale
39
38
 
40
39
  @property
41
- def unit(self) -> auto:
40
+ def unit(self) -> _Unit:
42
41
  """The measuring unit used to display information in this object."""
43
42
 
44
43
  return self.__unit
@@ -68,9 +67,9 @@ class CustomizableBase:
68
67
  """
69
68
  Sets the default localization used to display information in this object.
70
69
 
71
- :param to: The new :class:`Locale` to be used to display information in this object.
70
+ :param to: The new :class:`~python_weather.enums.Locale` to be used to display information in this object.
72
71
  :type to: Locale
73
- :exception Error: If the ``to`` argument is not a part of the :class:`Locale` enum.
72
+ :exception Error: If the ``to`` argument is not a part of the :class:`~python_weather.enums.Locale` enum.
74
73
  """
75
74
 
76
75
  if not isinstance(to, Locale):
@@ -79,84 +78,71 @@ class CustomizableBase:
79
78
  self.__locale = to
80
79
 
81
80
 
82
- class BaseForecast(CustomizableBase):
83
- __slots__: Tuple[str, ...] = ('__inner',)
81
+ class BaseForecast:
82
+ __slots__: Tuple[str, ...] = (
83
+ 'ultraviolet',
84
+ 'humidity',
85
+ 'wind_direction',
86
+ 'kind',
87
+ 'feels_like',
88
+ 'temperature',
89
+ 'precipitation',
90
+ 'pressure',
91
+ 'visibility',
92
+ 'wind_speed',
93
+ 'description',
94
+ )
84
95
 
85
- def __init__(self, json: dict, unit: _Unit, locale: Locale):
86
- self.__inner = json
87
-
88
- super().__init__(unit, locale)
89
-
90
- @property
91
- def ultraviolet(self) -> UltraViolet:
92
- """The ultra-violet (UV) index."""
93
-
94
- return UltraViolet._new(int(self.__inner['uvIndex']))
95
-
96
- @property
97
- def feels_like(self) -> int:
98
- """What it felt like, in celcius or fahrenheit."""
96
+ ultraviolet: UltraViolet
97
+ """The ultra-violet (UV) index."""
99
98
 
100
- return int(self.__inner[f'FeelsLike{self._CustomizableBase__unit.temperature}'])
99
+ humidity: int
100
+ """The humidity value in percent."""
101
101
 
102
- @property
103
- def humidity(self) -> int:
104
- """The humidity value in percent."""
102
+ wind_direction: WindDirection
103
+ """The wind direction."""
105
104
 
106
- return int(self.__inner['humidity'])
105
+ kind: Kind
106
+ """The kind of the forecast."""
107
107
 
108
- @property
109
- def temperature(self) -> int:
110
- """The temperature in either celcius or Fahrenheit."""
108
+ feels_like: int
109
+ """What it felt like, in celcius or fahrenheit."""
111
110
 
112
- return int(self.__inner[f'temp_{self._CustomizableBase__unit.temperature}'])
111
+ temperature: int
112
+ """The temperature in either celcius or Fahrenheit."""
113
113
 
114
- @property
115
- def precipitation(self) -> float:
116
- """The precipitation in either millimeters or inches."""
114
+ precipitation: float
115
+ """The precipitation in either millimeters or inches."""
117
116
 
118
- return float(self.__inner[f'precip{self._CustomizableBase__unit.precipitation}'])
117
+ pressure: float
118
+ """The pressure in either pascal or inches."""
119
119
 
120
- @property
121
- def pressure(self) -> float:
122
- """The pressure in either pascal or inches."""
120
+ visibility: int
121
+ """The visibility distance in either kilometers or miles."""
123
122
 
124
- return float(self.__inner[f'pressure{self._CustomizableBase__unit.pressure}'])
123
+ wind_speed: int
124
+ """The wind speeds in either kilometers/hour or miles/hour."""
125
125
 
126
- @property
127
- def visibility(self) -> int:
128
- """The visibility distance in either kilometers or miles."""
129
-
130
- return int(self.__inner[f'visibility{self._CustomizableBase__unit.visibility}'])
131
-
132
- @property
133
- def wind_speed(self) -> int:
134
- """The wind speeds in either kilometers/hour or miles/hour."""
135
-
136
- return int(self.__inner[f'windspeed{self._CustomizableBase__unit.velocity}'])
137
-
138
- @property
139
- def wind_direction(self) -> WindDirection:
140
- """The wind direction."""
141
-
142
- return WindDirection._new(
143
- self.__inner['winddir16Point'], int(self.__inner['winddirDegree'])
144
- )
145
-
146
- @property
147
- def description(self) -> str:
148
- """The description regarding the forecast. This can be localized in different languages depending on the localization used."""
126
+ description: str
127
+ """The description regarding the forecast. This can be localized in different languages depending on the localization used."""
149
128
 
129
+ def __init__(self, json: dict, unit: _Unit, locale: Locale):
150
130
  description = (
151
- self.__inner['weatherDesc'][0]['value']
152
- if self._CustomizableBase__locale == Locale.ENGLISH
153
- else self.__inner[f'lang_{self._CustomizableBase__locale.value}'][0]['value']
131
+ json['weatherDesc'][0]['value']
132
+ if locale is Locale.ENGLISH
133
+ else json[f'lang_{locale.value}'][0]['value']
154
134
  )
155
135
 
156
- return description.strip()
157
-
158
- @property
159
- def kind(self) -> Kind:
160
- """The kind of the forecast."""
161
-
162
- return Kind(int(self.__inner['weatherCode']))
136
+ self.ultraviolet = UltraViolet._new(int(json['uvIndex']))
137
+ self.humidity = int(json['humidity'])
138
+ self.wind_direction = WindDirection._new(
139
+ json['winddir16Point'], int(json['winddirDegree'])
140
+ )
141
+ self.kind = Kind(int(json['weatherCode']))
142
+ self.feels_like = int(json[f'FeelsLike{unit.temperature}'])
143
+ self.temperature = int(json[f'temp_{unit.temperature}'])
144
+ self.precipitation = float(json[f'precip{unit.precipitation}'])
145
+ self.pressure = float(json[f'pressure{unit.pressure}'])
146
+ self.visibility = int(json[f'visibility{unit.visibility}'])
147
+ self.wind_speed = int(json[f'windspeed{unit.velocity}'])
148
+ self.description = description.strip()
@@ -23,10 +23,9 @@ SOFTWARE.
23
23
  """
24
24
 
25
25
  from aiohttp import ClientSession, ClientTimeout, TCPConnector
26
+ from typing import Optional, Tuple, Self
26
27
  from urllib.parse import quote_plus
27
- from typing import Optional, Tuple
28
28
  from asyncio import sleep
29
- from enum import auto
30
29
 
31
30
  from .errors import Error, RequestError
32
31
  from .constants import _Unit, METRIC
@@ -40,32 +39,35 @@ class Client(CustomizableBase):
40
39
  The class that lets you interact with the API.
41
40
 
42
41
  :param unit: Whether to use the metric or imperial/customary system (``IMPERIAL``). Defaults to ``METRIC``.
43
- :type unit: Optional[:py:class:`enum.auto`]
42
+ :type unit: _Unit
44
43
  :param locale: Whether to use a different locale/language as the description for the returned forecast. Defaults to ``Locale.ENGLISH``.
45
- :type locale: Optional[Locale]
46
- :param session: Whether to use an existing aiohttp client session for requesting or not. Defaults to ``None`` (creates a new one instead)
47
- :type session: Optional[:class:`aiohttp.ClientSession`]
44
+ :type locale: Locale
45
+ :param session: Whether to use an existing aiohttp client session for requesting or not. Defaults to ``None`` (creates a new one instead).
46
+ :type session: Optional[:class:`~aiohttp.ClientSession`]
47
+ :param max_retries: Maximum amount of retries upon receiving HTTP request failure before raising a :class:`~python_weather.errors.RequestError`. To have infinite retries, use ``-1`` (NOT recommended). Defaults to ``None`` (or 3 retries).
48
+ :type max_retries: Optional[:class:`int`]
48
49
 
49
- :raises Error: If ``unit`` is not ``METRIC`` or ``IMPERIAL``, or if ``locale`` is not ``None`` and not a part of the :class:`Locale` enum.
50
+ :raises Error: If ``unit`` is not ``METRIC`` or ``IMPERIAL``, or if ``locale`` is not ``None`` and not a part of the :class:`~python_weather.enums.Locale` enum.
50
51
  """
51
52
 
52
- __slots__: Tuple[str, ...] = ('__own_session', '__session')
53
+ __slots__: Tuple[str, ...] = ('__own_session', '__session', '__max_retries')
53
54
 
54
55
  def __init__(
55
56
  self,
56
57
  *,
57
- unit: Optional[auto] = METRIC,
58
- locale: Optional[Locale] = Locale.ENGLISH,
58
+ unit: _Unit = METRIC,
59
+ locale: Locale = Locale.ENGLISH,
59
60
  session: Optional[ClientSession] = None,
61
+ max_retries: Optional[int] = None,
60
62
  ):
61
63
  super().__init__(unit, locale)
62
64
 
63
65
  self.__own_session = session is None
64
66
  self.__session = session or ClientSession(
65
- raise_for_status=True,
66
67
  timeout=ClientTimeout(total=5000.0),
67
68
  connector=TCPConnector(verify_ssl=False),
68
69
  )
70
+ self.__max_retries = max_retries or 3
69
71
 
70
72
  def __repr__(self) -> str:
71
73
  return f'<{__class__.__name__} {self.__session!r}>'
@@ -74,7 +76,7 @@ class Client(CustomizableBase):
74
76
  self,
75
77
  location: str,
76
78
  *,
77
- unit: Optional[auto] = None,
79
+ unit: Optional[_Unit] = None,
78
80
  locale: Optional[Locale] = None,
79
81
  ) -> Forecast:
80
82
  """
@@ -83,11 +85,12 @@ class Client(CustomizableBase):
83
85
  :param location: The requested location name for said weather forecast.
84
86
  :type location: str
85
87
  :param unit: Overrides the unit used by this object. Defaults to the one used by this object.
86
- :type unit: Optional[:py:class:`enum.auto`]
88
+ :type unit: Optional[``_Unit``]
87
89
  :param locale: Overrides the locale used by this object. Defaults to the one used by this object.
88
- :type locale: Optional[Locale]
90
+ :type locale: Optional[:class:`~python_weather.enums.Locale`]
89
91
 
90
- :exception Error: If the aiohttp client session used by the :class:`Client` object is already closed, if the ``unit`` argument is not ``None`` and it's also not ``METRIC`` or ``IMPERIAL``, if the ``locale`` argument is not ``None`` and it's also not a part of the :class:`Locale` enum, or if the :class:`Client` cannot send a web request to the web server.
92
+ :exception Error: If the aiohttp client session used by the :class:`~python_weather.client.Client` object is already closed, if the ``unit`` argument is not ``None`` and it's also not ``METRIC`` or ``IMPERIAL``, if the ``locale`` argument is not ``None`` and it's also not a part of the :class:`~python_weather.enums.Locale` enum.
93
+ :exception RequestError: If the :class:`~python_weather.client.Client` can't send a web request to the web server.
91
94
 
92
95
  :returns: The requested weather forecast.
93
96
  :rtype: Forecast
@@ -106,28 +109,32 @@ class Client(CustomizableBase):
106
109
 
107
110
  subdomain = f'{locale.value}.' if locale != Locale.ENGLISH else ''
108
111
  delay = 0.5
112
+ attempts = 0
109
113
 
110
114
  while True:
111
115
  try:
112
116
  async with self.__session.get(
113
117
  f'https://{subdomain}wttr.in/{quote_plus(location)}?format=j1'
114
118
  ) as resp:
119
+ resp.raise_for_status()
120
+
115
121
  return Forecast(await resp.json(), unit, locale)
116
122
  except Exception as err:
117
- if delay == 4:
123
+ if attempts == self.__max_retries:
118
124
  raise RequestError(err)
119
125
 
120
126
  await sleep(delay)
127
+ attempts += 1
121
128
  delay *= 2
122
129
 
123
130
  async def close(self) -> None:
124
- """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."""
131
+ """Closes the :class:`~python_weather.client.Client` object. Nothing will happen if the client uses a pre-existing :class:`~aiohttp.ClientSession` or if the session is already closed."""
125
132
 
126
133
  if self.__own_session and not self.__session.closed:
127
134
  await self.__session.close()
128
135
 
129
- async def __aenter__(self) -> 'Client':
136
+ async def __aenter__(self) -> Self:
130
137
  return self
131
138
 
132
- async def __aexit__(self, *_, **__):
139
+ async def __aexit__(self, *_, **__) -> None:
133
140
  await self.close()
@@ -23,7 +23,7 @@ SOFTWARE.
23
23
  """
24
24
 
25
25
  from enum import Enum
26
- from typing import Union, Tuple
26
+ from typing import Union, Tuple, Self
27
27
 
28
28
  from .constants import WIND_DIRECTION_EMOJIS
29
29
 
@@ -70,14 +70,14 @@ class HeatIndex(IndexedEnum):
70
70
  DANGER = None
71
71
  EXTREME_DANGER = None
72
72
 
73
- def _new(celcius_index: int, true_index: int) -> 'HeatIndex':
73
+ def _new(celcius_index: int, true_index: int) -> Self:
74
74
  enum = HeatIndex(celcius_index)
75
75
  enum.index = true_index
76
76
 
77
77
  return enum
78
78
 
79
79
  @classmethod
80
- def _missing_(self, celcius_index: int) -> 'HeatIndex':
80
+ def _missing_(self, celcius_index: int) -> Self:
81
81
  if celcius_index <= 32:
82
82
  return self.CAUTION
83
83
  elif celcius_index <= 39:
@@ -99,14 +99,14 @@ class UltraViolet(BasicEnum, IndexedEnum):
99
99
  VERY_HIGH = None
100
100
  EXTREME = None
101
101
 
102
- def _new(index: int) -> 'UltraViolet':
102
+ def _new(index: int) -> Self:
103
103
  enum = UltraViolet(index)
104
104
  enum.index = index
105
105
 
106
106
  return enum
107
107
 
108
108
  @classmethod
109
- def _missing_(self, index: int) -> 'UltraViolet':
109
+ def _missing_(self, index: int) -> Self:
110
110
  if index <= 2:
111
111
  return self.LOW
112
112
  elif index <= 5:
@@ -122,7 +122,7 @@ class UltraViolet(BasicEnum, IndexedEnum):
122
122
  class WindDirection(BasicEnum):
123
123
  """Represents a wind direction."""
124
124
 
125
- __slots__: Tuple[str, ...] = ('__degrees',)
125
+ __slots__: Tuple[str, ...] = ('degrees',)
126
126
 
127
127
  NORTH = 'N'
128
128
  NORTH_NORTHEAST = 'NNE'
@@ -141,9 +141,12 @@ class WindDirection(BasicEnum):
141
141
  NORTHWEST = 'NW'
142
142
  NORTH_NORTHWEST = 'NNW'
143
143
 
144
- def _new(value: str, degrees: float) -> 'WindDirection':
144
+ degrees: float
145
+ """The wind direction's value in degrees."""
146
+
147
+ def _new(value: str, degrees: float) -> Self:
145
148
  enum = WindDirection(value)
146
- enum.__degrees = degrees
149
+ enum.degrees = degrees
147
150
 
148
151
  return enum
149
152
 
@@ -183,20 +186,17 @@ class WindDirection(BasicEnum):
183
186
  else:
184
187
  return 326.25 < other <= 348.75
185
188
 
186
- def __float__(self) -> float:
187
- return self.__degrees
188
-
189
- @property
190
- def degrees(self) -> int:
191
- """The wind direction's value in degrees."""
189
+ def __int__(self) -> int:
190
+ return int(self.degrees)
192
191
 
193
- return self.__degrees
192
+ def __float__(self) -> float:
193
+ return self.degrees
194
194
 
195
195
  @property
196
196
  def emoji(self) -> str:
197
197
  """The emoji representing this enum."""
198
198
 
199
- return WIND_DIRECTION_EMOJIS[int(((self.__degrees + 22.5) % 360) / 45.0)]
199
+ return WIND_DIRECTION_EMOJIS[int(((self.degrees + 22.5) % 360) / 45.0)]
200
200
 
201
201
 
202
202
  class Locale(Enum):
@@ -310,39 +310,30 @@ class Kind(BasicEnum):
310
310
  THUNDERY_SNOW_SHOWERS = 392
311
311
 
312
312
  @classmethod
313
- def _missing_(self, value: int) -> 'Kind':
314
- if value == 248 or value == 260:
313
+ def _missing_(self, value: int) -> Self:
314
+ if value in (248, 260):
315
315
  return self.FOG
316
- elif value == 263 or value == 353:
316
+ elif value in (263, 353):
317
317
  return self.LIGHT_SHOWERS
318
- elif value == 362 or value == 365 or value == 374:
318
+ elif value in (362, 365, 374):
319
319
  return self.LIGHT_SLEET_SHOWERS
320
- elif (
321
- value == 185
322
- or value == 281
323
- or value == 284
324
- or value == 311
325
- or value == 314
326
- or value == 317
327
- or value == 350
328
- or value == 377
329
- ):
320
+ elif value in (185, 281, 284, 311, 314, 317, 350, 377):
330
321
  return self.LIGHT_SLEET
331
322
  elif value == 386:
332
323
  return self.THUNDERY_SHOWERS
333
324
  elif value == 320:
334
325
  return self.LIGHT_SNOW
335
- elif value == 329 or value == 332 or value == 338:
326
+ elif value in (329, 332, 338):
336
327
  return self.HEAVY_SNOW
337
- elif value == 293 or value == 296:
328
+ elif value in (293, 296):
338
329
  return self.LIGHT_RAIN
339
- elif value == 305 or value == 356:
330
+ elif value in (305, 356):
340
331
  return self.HEAVY_SHOWERS
341
- elif value == 308 or value == 359:
332
+ elif value in (308, 359):
342
333
  return self.HEAVY_RAIN
343
- elif value == 326 or value == 368:
334
+ elif value in (326, 368):
344
335
  return self.LIGHT_SNOW_SHOWERS
345
- elif value == 371 or value == 395:
336
+ elif value in (371, 395):
346
337
  return self.HEAVY_SNOW_SHOWERS
347
338
 
348
339
  @property
@@ -32,12 +32,12 @@ class Error(Exception):
32
32
 
33
33
 
34
34
  class RequestError(Error):
35
- """Thrown upon HTTP request failure. Extends :class:`Error`."""
35
+ """Thrown upon HTTP request failure. Extends :class:`~python_weather.errors.Error`."""
36
36
 
37
37
  __slots__: Tuple[str, ...] = ('source',)
38
38
 
39
39
  source: Exception
40
- """The :class:`Exception` instance causing this exception."""
40
+ """The :py:class:`Exception` instance causing this exception."""
41
41
 
42
42
  def __init__(self, source: Exception):
43
43
  self.source = source
@@ -0,0 +1,296 @@
1
+ """
2
+ The MIT License (MIT)
3
+
4
+ Copyright (c) 2021-2024 null8626
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
23
+ """
24
+
25
+ from typing import Iterable, Optional, Tuple, List
26
+ from datetime import datetime, date, time
27
+
28
+ from .base import BaseForecast
29
+ from .enums import Phase, Locale, HeatIndex
30
+ from .constants import _Unit, LATLON_REGEX
31
+
32
+
33
+ class HourlyForecast(BaseForecast):
34
+ """Represents a weather forecast of a specific hour."""
35
+
36
+ __slots__: Tuple[str, ...] = (
37
+ 'chances_of_fog',
38
+ 'chances_of_frost',
39
+ 'chances_of_high_temperature',
40
+ 'chances_of_overcast',
41
+ 'chances_of_rain',
42
+ 'chances_of_remaining_dry',
43
+ 'chances_of_snow',
44
+ 'chances_of_sunshine',
45
+ 'chances_of_thunder',
46
+ 'chances_of_windy',
47
+ 'cloud_cover',
48
+ 'time',
49
+ 'dew_point',
50
+ 'heat_index',
51
+ 'wind_chill',
52
+ 'wind_gust',
53
+ )
54
+
55
+ chances_of_fog: int
56
+ """Chances of a fog in percent."""
57
+
58
+ chances_of_frost: int
59
+ """Chances of a frost in percent."""
60
+
61
+ chances_of_high_temperature: int
62
+ """Chances of a high temperature in percent."""
63
+
64
+ chances_of_overcast: int
65
+ """Chances of an overcast in percent."""
66
+
67
+ chances_of_rain: int
68
+ """Chances of a rain in percent."""
69
+
70
+ chances_of_remaining_dry: int
71
+ """Chances of remaining dry in percent."""
72
+
73
+ chances_of_snow: int
74
+ """Chances of a snow in percent."""
75
+
76
+ chances_of_sunshine: int
77
+ """Chances of a sunshine in percent."""
78
+
79
+ chances_of_thunder: int
80
+ """Chances of a thunder in percent."""
81
+
82
+ chances_of_windy: int
83
+ """Chances of windy in percent."""
84
+
85
+ cloud_cover: int
86
+ """The cloud cover value in percent."""
87
+
88
+ time: 'time'
89
+ """The local time in hours and minutes."""
90
+
91
+ dew_point: int
92
+ """The dew point in either celcius or fahrenheit."""
93
+
94
+ heat_index: HeatIndex
95
+ """The heat index in either celcius or fahrenheit."""
96
+
97
+ wind_chill: int
98
+ """The wind chill value in either celcius or fahrenheit."""
99
+
100
+ wind_gust: int
101
+ """The wind gust value in either kilometers/hour or miles/hour."""
102
+
103
+ def __init__(self, json: dict, unit: _Unit, locale: Locale):
104
+ # for inheritance purposes
105
+ if 'temp_C' not in json:
106
+ json['temp_C'] = json.pop('tempC')
107
+ if 'temp_F' not in json:
108
+ json['temp_F'] = json.pop('tempF')
109
+
110
+ celcius_index = int(json['HeatIndexC'])
111
+ t = json['time']
112
+
113
+ self.chances_of_fog = int(json['chanceoffog'])
114
+ self.chances_of_frost = int(json['chanceoffrost'])
115
+ self.chances_of_high_temperature = int(json['chanceofhightemp'])
116
+ self.chances_of_overcast = int(json['chanceofovercast'])
117
+ self.chances_of_rain = int(json['chanceofrain'])
118
+ self.chances_of_remaining_dry = int(json['chanceofremdry'])
119
+ self.chances_of_snow = int(json['chanceofsnow'])
120
+ self.chances_of_sunshine = int(json['chanceofsunshine'])
121
+ self.chances_of_thunder = int(json['chanceofthunder'])
122
+ self.chances_of_windy = int(json['chanceofwindy'])
123
+ self.cloud_cover = int(json['cloudcover'])
124
+ self.time = time() if len(t) < 3 else datetime.strptime(t, '%H%M').time()
125
+ self.dew_point = int(json[f'DewPoint{unit.temperature}'])
126
+ self.heat_index = HeatIndex._new(
127
+ celcius_index,
128
+ int(json[f'HeatIndex{unit.temperature}']),
129
+ )
130
+ self.wind_chill = int(json[f'WindChill{unit.temperature}'])
131
+ self.wind_gust = int(json[f'WindGust{unit.velocity}'])
132
+
133
+ super().__init__(json, unit, locale)
134
+
135
+ def __repr__(self) -> str:
136
+ return f'<{__class__.__name__} time={self.time!r} temperature={self.temperature!r} description={self.description!r} kind={self.kind!r}>'
137
+
138
+
139
+ class DailyForecast:
140
+ __slots__: Tuple[str, ...] = (
141
+ 'moon_illumination',
142
+ 'moon_phase',
143
+ 'moonrise',
144
+ 'moonset',
145
+ 'sunrise',
146
+ 'sunset',
147
+ 'date',
148
+ 'sunlight',
149
+ 'lowest_temperature',
150
+ 'highest_temperature',
151
+ 'temperature',
152
+ 'snowfall',
153
+ 'hourly_forecasts',
154
+ )
155
+
156
+ moon_illumination: int
157
+ """The percentage of the moon illuminated."""
158
+
159
+ moon_phase: Phase
160
+ """The moon's phase."""
161
+
162
+ moonrise: Optional[time]
163
+ """The local time when the moon rises. This can be ``None``."""
164
+
165
+ moonset: Optional[time]
166
+ """The local time when the moon sets. This can be ``None``."""
167
+
168
+ sunrise: Optional[time]
169
+ """The local time when the sun rises. This can be ``None``."""
170
+
171
+ sunset: Optional[time]
172
+ """The local time when the sun sets. This can be ``None``."""
173
+
174
+ date: 'date'
175
+ """The local date of this forecast."""
176
+
177
+ sunlight: float
178
+ """Hours of sunlight."""
179
+
180
+ lowest_temperature: int
181
+ """The lowest temperature in either celcius or fahrenheit."""
182
+
183
+ highest_temperature: int
184
+ """The highest temperature in either celcius or fahrenheit."""
185
+
186
+ temperature: int
187
+ """The average temperature in either celcius or fahrenheit."""
188
+
189
+ snowfall: float
190
+ """Total snowfall in either centimeters or inches."""
191
+
192
+ hourly_forecasts: List[HourlyForecast]
193
+ """The hourly forecasts of this day."""
194
+
195
+ def __init__(self, json: dict, unit: _Unit, locale: Locale):
196
+ astronomy = json['astronomy'][0]
197
+
198
+ self.moon_illumination = int(astronomy['moon_illumination'])
199
+ self.moon_phase = Phase(astronomy['moon_phase'])
200
+ self.moonrise = __class__.__parse_time(astronomy['moonrise'])
201
+ self.moonset = __class__.__parse_time(astronomy['moonset'])
202
+ self.sunrise = __class__.__parse_time(astronomy['sunrise'])
203
+ self.sunset = __class__.__parse_time(astronomy['sunset'])
204
+ self.date = datetime.strptime(json['date'], '%Y-%m-%d').date()
205
+ self.sunlight = float(json['sunHour'])
206
+ self.lowest_temperature = int(json[f'mintemp{unit.temperature}'])
207
+ self.highest_temperature = int(json[f'maxtemp{unit.temperature}'])
208
+ self.temperature = int(json[f'avgtemp{unit.temperature}'])
209
+ self.snowfall = float(json['totalSnow_cm']) / unit.cm_divisor
210
+ self.hourly_forecasts = [
211
+ HourlyForecast(elem, unit, locale) for elem in json['hourly']
212
+ ]
213
+
214
+ @staticmethod
215
+ def __parse_time(timestamp: str) -> Optional[time]:
216
+ try:
217
+ return datetime.strptime(timestamp, '%I:%M %p').time()
218
+ except ValueError:
219
+ ...
220
+
221
+ def __repr__(self) -> str:
222
+ return f'<{__class__.__name__} date={self.date!r} temperature={self.temperature!r}>'
223
+
224
+ def __len__(self) -> int:
225
+ return len(self.hourly_forecasts)
226
+
227
+ def __iter__(self) -> Iterable[HourlyForecast]:
228
+ return iter(self.hourly_forecasts)
229
+
230
+
231
+ class Forecast(BaseForecast):
232
+ """Represents today's weather forecast, alongside daily and hourly weather forecasts."""
233
+
234
+ __slots__: Tuple[str, ...] = (
235
+ 'local_population',
236
+ 'region',
237
+ 'location',
238
+ 'country',
239
+ 'datetime',
240
+ 'coordinates',
241
+ 'daily_forecasts',
242
+ )
243
+
244
+ local_population: int
245
+ """The local population count."""
246
+
247
+ region: str
248
+ """The local region's name."""
249
+
250
+ location: str
251
+ """The location's name."""
252
+
253
+ country: str
254
+ """The local country's name."""
255
+
256
+ datetime: 'datetime'
257
+ """The local date and time of this weather forecast."""
258
+
259
+ coordinates: Tuple[float, float]
260
+ """A tuple of this forecast's latitude and longitude."""
261
+
262
+ daily_forecasts: List[DailyForecast]
263
+ """Daily weather forecasts in this location."""
264
+
265
+ def __init__(self, json: dict, unit: _Unit, locale: Locale):
266
+ current = json['current_condition'][0]
267
+ nearest = json['nearest_area'][0]
268
+
269
+ self.local_population = int(nearest['population'])
270
+ self.region = nearest['region'][0]['value']
271
+ self.location = nearest['areaName'][0]['value']
272
+ self.country = nearest['country'][0]['value']
273
+ self.datetime = datetime.strptime(current['localObsDateTime'], '%Y-%m-%d %I:%M %p')
274
+
275
+ try:
276
+ req = next(filter(lambda x: x['type'] == 'LatLon', json['request']))
277
+ match = LATLON_REGEX.match(req['query'])
278
+
279
+ self.coordinates = (float(match[1]), float(match[2]))
280
+ except:
281
+ self.coordinates = (float(nearest['latitude']), float(nearest['longitude']))
282
+
283
+ self.daily_forecasts = [
284
+ DailyForecast(elem, unit, locale) for elem in json['weather']
285
+ ]
286
+
287
+ super().__init__(current, unit, locale)
288
+
289
+ def __repr__(self) -> str:
290
+ return f'<{__class__.__name__} location={self.location!r} datetime={self.datetime!r} temperature={self.temperature!r}>'
291
+
292
+ def __len__(self) -> int:
293
+ return len(self.daily_forecasts)
294
+
295
+ def __iter__(self) -> Iterable[DailyForecast]:
296
+ return iter(self.daily_forecasts)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-weather
3
- Version: 2.0.4
3
+ Version: 2.0.6
4
4
  Summary: A free and asynchronous weather API wrapper made in Python, for Python.
5
5
  Author: null8626
6
6
  License: MIT
@@ -11,12 +11,11 @@ Project-URL: Donate via GitHub Sponsors, https://github.com/sponsors/null8626
11
11
  Project-URL: Donate via Ko-fi, https://ko-fi.com/null8626
12
12
  Keywords: weather,forecast,weather-api,weather-forecast
13
13
  Classifier: Development Status :: 5 - Production/Stable
14
- Classifier: Framework :: aiohttp
15
- Classifier: Framework :: AsyncIO
16
14
  Classifier: Intended Audience :: Education
17
15
  Classifier: Intended Audience :: Developers
18
16
  Classifier: Intended Audience :: Information Technology
19
17
  Classifier: Natural Language :: English
18
+ Classifier: Operating System :: OS Independent
20
19
  Classifier: Topic :: Education
21
20
  Classifier: Topic :: Internet
22
21
  Classifier: Topic :: Scientific/Engineering
@@ -32,10 +31,11 @@ Classifier: Programming Language :: Python :: 3.9
32
31
  Classifier: Programming Language :: Python :: 3.10
33
32
  Classifier: Programming Language :: Python :: 3.11
34
33
  Classifier: Programming Language :: Python :: 3.12
34
+ Classifier: Programming Language :: Python :: 3.13
35
35
  Requires-Python: >=3.8
36
36
  Description-Content-Type: text/markdown
37
37
  License-File: LICENSE
38
- Requires-Dist: aiohttp>=3.10.8
38
+ Requires-Dist: aiohttp>=3.10.10
39
39
 
40
40
  # [python-weather][pypi-url] [![pypi][pypi-image]][pypi-url] [![downloads][downloads-image]][pypi-url] [![codacy-badge][codacy-url]][codacy-image] [![ko-fi][ko-fi-brief-image]][ko-fi-url]
41
41
 
@@ -0,0 +1 @@
1
+ aiohttp>=3.10.10
@@ -1,350 +0,0 @@
1
- """
2
- The MIT License (MIT)
3
-
4
- Copyright (c) 2021-2024 null8626
5
-
6
- Permission is hereby granted, free of charge, to any person obtaining a copy
7
- of this software and associated documentation files (the "Software"), to deal
8
- in the Software without restriction, including without limitation the rights
9
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
- copies of the Software, and to permit persons to whom the Software is
11
- furnished to do so, subject to the following conditions:
12
-
13
- The above copyright notice and this permission notice shall be included in all
14
- copies or substantial portions of the Software.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
- SOFTWARE.
23
- """
24
-
25
- from typing import Iterable, Optional, Tuple, List
26
- from datetime import datetime, date, time
27
-
28
- from .base import BaseForecast, CustomizableBase
29
- from .enums import Phase, Locale, HeatIndex
30
- from .constants import _Unit, LATLON_REGEX
31
-
32
-
33
- class HourlyForecast(BaseForecast):
34
- """Represents a weather forecast of a specific hour."""
35
-
36
- __slots__: Tuple[str, ...] = ()
37
-
38
- def __init__(self, json: dict, unit: _Unit, locale: Locale):
39
- # for inheritance purposes
40
- if 'temp_C' not in json:
41
- json['temp_C'] = json.pop('tempC')
42
- if 'temp_F' not in json:
43
- json['temp_F'] = json.pop('tempF')
44
-
45
- super().__init__(json, unit, locale)
46
-
47
- def __repr__(self) -> str:
48
- return f'<{__class__.__name__} time={self.time!r} temperature={self.temperature!r} description={self.description!r} kind={self.kind!r}>'
49
-
50
- @property
51
- def dew_point(self) -> int:
52
- """The dew point in either celcius or fahrenheit."""
53
-
54
- return int(
55
- self._BaseForecast__inner[f'DewPoint{self._CustomizableBase__unit.temperature}']
56
- )
57
-
58
- @property
59
- def heat_index(self) -> HeatIndex:
60
- """The heat index in either celcius or fahrenheit."""
61
-
62
- celcius_index = int(self._BaseForecast__inner['HeatIndexC'])
63
-
64
- return HeatIndex._new(
65
- celcius_index,
66
- int(
67
- self._BaseForecast__inner[
68
- f'HeatIndex{self._CustomizableBase__unit.temperature}'
69
- ]
70
- ),
71
- )
72
-
73
- @property
74
- def wind_chill(self) -> int:
75
- """The wind chill value in either celcius or fahrenheit."""
76
-
77
- return int(
78
- self._BaseForecast__inner[f'WindChill{self._CustomizableBase__unit.temperature}']
79
- )
80
-
81
- @property
82
- def wind_gust(self) -> int:
83
- """The wind gust value in either kilometers/hour or miles/hour."""
84
-
85
- return int(
86
- self._BaseForecast__inner[f'WindGust{self._CustomizableBase__unit.velocity}']
87
- )
88
-
89
- @property
90
- def chances_of_fog(self) -> int:
91
- """Chances of a fog in percent."""
92
-
93
- return int(self._BaseForecast__inner['chanceoffog'])
94
-
95
- @property
96
- def chances_of_frost(self) -> int:
97
- """Chances of a frost in percent."""
98
-
99
- return int(self._BaseForecast__inner['chanceoffrost'])
100
-
101
- @property
102
- def chances_of_high_temperature(self) -> int:
103
- """Chances of a high temperature in percent."""
104
-
105
- return int(self._BaseForecast__inner['chanceofhightemp'])
106
-
107
- @property
108
- def chances_of_overcast(self) -> int:
109
- """Chances of an overcast in percent."""
110
-
111
- return int(self._BaseForecast__inner['chanceofovercast'])
112
-
113
- @property
114
- def chances_of_rain(self) -> int:
115
- """Chances of a rain in percent."""
116
-
117
- return int(self._BaseForecast__inner['chanceofrain'])
118
-
119
- @property
120
- def chances_of_remaining_dry(self) -> int:
121
- """Chances of remaining dry in percent."""
122
-
123
- return int(self._BaseForecast__inner['chanceofremdry'])
124
-
125
- @property
126
- def chances_of_snow(self) -> int:
127
- """Chances of a snow in percent."""
128
-
129
- return int(self._BaseForecast__inner['chanceofsnow'])
130
-
131
- @property
132
- def chances_of_sunshine(self) -> int:
133
- """Chances of a sunshine in percent."""
134
-
135
- return int(self._BaseForecast__inner['chanceofsunshine'])
136
-
137
- @property
138
- def chances_of_thunder(self) -> int:
139
- """Chances of a thunder in percent."""
140
-
141
- return int(self._BaseForecast__inner['chanceofthunder'])
142
-
143
- @property
144
- def chances_of_windy(self) -> int:
145
- """Chances of windy in percent."""
146
-
147
- return int(self._BaseForecast__inner['chanceofwindy'])
148
-
149
- @property
150
- def cloud_cover(self) -> int:
151
- """The cloud cover value in percent."""
152
-
153
- return int(self._BaseForecast__inner['cloudcover'])
154
-
155
- @property
156
- def time(self) -> time:
157
- """The local time in hours and minutes."""
158
-
159
- return (
160
- time()
161
- if len(self._BaseForecast__inner['time']) < 3
162
- else datetime.strptime(self._BaseForecast__inner['time'], '%H%M').time()
163
- )
164
-
165
-
166
- class DailyForecast(CustomizableBase):
167
- __slots__: Tuple[str, ...] = ('__inner', '__astronomy')
168
-
169
- def __init__(self, json: dict, unit: _Unit, locale: Locale):
170
- self.__astronomy = json.pop('astronomy')[0]
171
- self.__inner = json
172
-
173
- super().__init__(unit, locale)
174
-
175
- def __repr__(self) -> str:
176
- return f'<{__class__.__name__} date={self.date!r} temperature={self.temperature!r}>'
177
-
178
- def __iter__(self) -> Iterable[HourlyForecast]:
179
- return self.hourly_forecasts
180
-
181
- def __list__(self) -> List[HourlyForecast]:
182
- return list(iter(self))
183
-
184
- @property
185
- def moon_illumination(self) -> int:
186
- """The percentage of the moon illuminated."""
187
-
188
- return int(self.__astronomy['moon_illumination'])
189
-
190
- @property
191
- def moon_phase(self) -> Phase:
192
- """The moon's phase."""
193
-
194
- return Phase(self.__astronomy['moon_phase'])
195
-
196
- @property
197
- def moonrise(self) -> Optional[time]:
198
- """The local time when the moon rises. This can be ``None``."""
199
-
200
- try:
201
- return datetime.strptime(self.__astronomy['moonrise'], '%I:%M %p').time()
202
- except ValueError:
203
- ...
204
-
205
- @property
206
- def moonset(self) -> Optional[time]:
207
- """The local time when the moon sets. This can be ``None``."""
208
-
209
- try:
210
- return datetime.strptime(self.__astronomy['moonset'], '%I:%M %p').time()
211
- except ValueError:
212
- ...
213
-
214
- @property
215
- def sunrise(self) -> Optional[time]:
216
- """The local time when the sun rises. This can be ``None``."""
217
-
218
- try:
219
- return datetime.strptime(self.__astronomy['sunrise'], '%I:%M %p').time()
220
- except ValueError:
221
- ...
222
-
223
- @property
224
- def sunset(self) -> Optional[time]:
225
- """The local time when the sun sets. This can be ``None``."""
226
-
227
- try:
228
- return datetime.strptime(self.__astronomy['sunset'], '%I:%M %p').time()
229
- except ValueError:
230
- ...
231
-
232
- @property
233
- def date(self) -> date:
234
- """The local date of this forecast."""
235
-
236
- return datetime.strptime(self.__inner['date'], '%Y-%m-%d').date()
237
-
238
- @property
239
- def lowest_temperature(self) -> int:
240
- """The lowest temperature in either celcius or fahrenheit."""
241
-
242
- return int(self.__inner[f'mintemp{self._CustomizableBase__unit.temperature}'])
243
-
244
- @property
245
- def highest_temperature(self) -> int:
246
- """The highest temperature in either celcius or fahrenheit."""
247
-
248
- return int(self.__inner[f'maxtemp{self._CustomizableBase__unit.temperature}'])
249
-
250
- @property
251
- def temperature(self) -> int:
252
- """The average temperature in either celcius or fahrenheit."""
253
-
254
- return int(self.__inner[f'avgtemp{self._CustomizableBase__unit.temperature}'])
255
-
256
- @property
257
- def sunlight(self) -> float:
258
- """Hours of sunlight."""
259
-
260
- return float(self.__inner['sunHour'])
261
-
262
- @property
263
- def snowfall(self) -> float:
264
- """Total snowfall in either centimeters or inches."""
265
-
266
- return float(self.__inner['totalSnow_cm']) / self._CustomizableBase__unit.cm_divisor
267
-
268
- @property
269
- def hourly_forecasts(self) -> Iterable[HourlyForecast]:
270
- """The hourly forecasts of this day."""
271
-
272
- return (
273
- HourlyForecast(elem, self._CustomizableBase__unit, self._CustomizableBase__locale)
274
- for elem in self.__inner['hourly']
275
- )
276
-
277
-
278
- class Forecast(BaseForecast):
279
- """Represents today's weather forecast, alongside daily and hourly weather forecasts."""
280
-
281
- __slots__: Tuple[str, ...] = ('__inner', '__nearest')
282
-
283
- def __init__(self, json: dict, unit: _Unit, locale: Locale):
284
- current = json['current_condition'][0]
285
- self.__nearest = json.pop('nearest_area')[0]
286
- self.__inner = json
287
-
288
- super().__init__(current, unit, locale)
289
-
290
- def __repr__(self) -> str:
291
- return f'<{__class__.__name__} location={self.location!r} datetime={self.datetime!r} temperature={self.temperature!r}>'
292
-
293
- def __iter__(self) -> Iterable[DailyForecast]:
294
- return self.daily_forecasts
295
-
296
- def __list__(self) -> List[DailyForecast]:
297
- return list(iter(self))
298
-
299
- @property
300
- def local_population(self) -> int:
301
- """The local population count."""
302
-
303
- return int(self.__nearest['population'])
304
-
305
- @property
306
- def region(self) -> str:
307
- """The local region's name."""
308
-
309
- return self.__nearest['region'][0]['value']
310
-
311
- @property
312
- def location(self) -> str:
313
- """The location's name."""
314
-
315
- return self.__nearest['areaName'][0]['value']
316
-
317
- @property
318
- def country(self) -> str:
319
- """The local country's name."""
320
-
321
- return self.__nearest['country'][0]['value']
322
-
323
- @property
324
- def datetime(self) -> datetime:
325
- """The local date and time of this weather forecast."""
326
-
327
- return datetime.strptime(
328
- self._BaseForecast__inner['localObsDateTime'], '%Y-%m-%d %I:%M %p'
329
- )
330
-
331
- @property
332
- def daily_forecasts(self) -> Iterable[DailyForecast]:
333
- """Daily weather forecasts in this location."""
334
-
335
- return (
336
- DailyForecast(elem, self._CustomizableBase__unit, self._CustomizableBase__locale)
337
- for elem in self.__inner['weather']
338
- )
339
-
340
- @property
341
- def coordinates(self) -> Tuple[float, float]:
342
- """A tuple of this forecast's latitude and longitude."""
343
-
344
- try:
345
- req = next(filter(lambda x: x['type'] == 'LatLon', self.__inner['request']))
346
- match = LATLON_REGEX.match(req['query'])
347
-
348
- return float(match[1]), float(match[2])
349
- except:
350
- return float(self.__nearest['latitude']), float(self.__nearest['longitude'])
@@ -1 +0,0 @@
1
- aiohttp>=3.10.8
File without changes
File without changes
File without changes