python-weather 2.0.3__py3-none-any.whl → 2.0.5__py3-none-any.whl

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.
@@ -1,19 +1,43 @@
1
- # -*- coding: utf-8 -*-
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
+ """
2
24
 
3
25
  from .enums import HeatIndex, Kind, Locale, Phase, UltraViolet, WindDirection
4
26
  from .constants import METRIC, IMPERIAL
27
+ from .errors import Error, RequestError
5
28
  from .client import Client
6
- from .errors import Error
7
29
 
8
30
  __title__ = 'python-weather'
9
31
  __author__ = 'null8626'
10
32
  __license__ = 'MIT'
11
- __version__ = '2.0.3'
33
+ __copyright__ = 'Copyright (c) 2021-2024 null8626'
34
+ __version__ = '2.0.5'
12
35
  __all__ = (
13
36
  'METRIC',
14
37
  'IMPERIAL',
15
38
  'Client',
16
39
  'Error',
40
+ 'RequestError',
17
41
  'HeatIndex',
18
42
  'Kind',
19
43
  'Locale',
python_weather/base.py CHANGED
@@ -1,4 +1,29 @@
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
+
1
25
  from enum import auto
26
+ from typing import Tuple
2
27
 
3
28
  from .errors import Error
4
29
  from .enums import WindDirection, Kind, Locale, UltraViolet
@@ -6,7 +31,7 @@ from .constants import _Unit
6
31
 
7
32
 
8
33
  class CustomizableBase:
9
- __slots__ = ('__unit', '__locale')
34
+ __slots__: Tuple[str, ...] = ('__unit', '__locale')
10
35
 
11
36
  def __init__(self, unit: _Unit, locale: Locale):
12
37
  self.unit = unit
@@ -19,7 +44,7 @@ class CustomizableBase:
19
44
  return self.__unit
20
45
 
21
46
  @unit.setter
22
- def unit(self, to: _Unit):
47
+ def unit(self, to: _Unit) -> None:
23
48
  """
24
49
  Sets the default measuring unit used to display information in this object.
25
50
 
@@ -39,7 +64,7 @@ class CustomizableBase:
39
64
  return self.__locale
40
65
 
41
66
  @locale.setter
42
- def locale(self, to: Locale):
67
+ def locale(self, to: Locale) -> None:
43
68
  """
44
69
  Sets the default localization used to display information in this object.
45
70
 
@@ -54,84 +79,71 @@ class CustomizableBase:
54
79
  self.__locale = to
55
80
 
56
81
 
57
- class BaseForecast(CustomizableBase):
58
- __slots__ = ('__inner',)
59
-
60
- def __init__(self, json: dict, unit: _Unit, locale: Locale):
61
- self.__inner = json
62
-
63
- super().__init__(unit, locale)
64
-
65
- @property
66
- def ultraviolet(self) -> UltraViolet:
67
- """The ultra-violet (UV) index."""
68
-
69
- return UltraViolet._new(int(self.__inner['uvIndex']))
70
-
71
- @property
72
- def feels_like(self) -> int:
73
- """What it felt like, in Celcius or Fahrenheit."""
74
-
75
- return int(self.__inner[f'FeelsLike{self._CustomizableBase__unit.temperature}'])
76
-
77
- @property
78
- def humidity(self) -> int:
79
- """The humidity value in percent."""
80
-
81
- return int(self.__inner['humidity'])
82
-
83
- @property
84
- def temperature(self) -> int:
85
- """The temperature in either Celcius or Fahrenheit."""
82
+ class BaseForecast:
83
+ __slots__: Tuple[str, ...] = (
84
+ 'ultraviolet',
85
+ 'humidity',
86
+ 'wind_direction',
87
+ 'kind',
88
+ 'feels_like',
89
+ 'temperature',
90
+ 'precipitation',
91
+ 'pressure',
92
+ 'visibility',
93
+ 'wind_speed',
94
+ 'description',
95
+ )
86
96
 
87
- return int(self.__inner[f'temp_{self._CustomizableBase__unit.temperature}'])
97
+ ultraviolet: UltraViolet
98
+ """The ultra-violet (UV) index."""
88
99
 
89
- @property
90
- def precipitation(self) -> float:
91
- """The precipitation in either Millimeters or Inches."""
100
+ humidity: int
101
+ """The humidity value in percent."""
92
102
 
93
- return float(self.__inner[f'precip{self._CustomizableBase__unit.precipitation}'])
103
+ wind_direction: WindDirection
104
+ """The wind direction."""
94
105
 
95
- @property
96
- def pressure(self) -> float:
97
- """The pressure in either Pascal or Inches."""
106
+ kind: Kind
107
+ """The kind of the forecast."""
98
108
 
99
- return float(self.__inner[f'pressure{self._CustomizableBase__unit.pressure}'])
109
+ feels_like: int
110
+ """What it felt like, in celcius or fahrenheit."""
100
111
 
101
- @property
102
- def visibility(self) -> int:
103
- """The visibility distance in either Kilometers or Miles."""
112
+ temperature: int
113
+ """The temperature in either celcius or Fahrenheit."""
104
114
 
105
- return int(self.__inner[f'visibility{self._CustomizableBase__unit.visibility}'])
115
+ precipitation: float
116
+ """The precipitation in either millimeters or inches."""
106
117
 
107
- @property
108
- def wind_speed(self) -> int:
109
- """The wind speeds in either Kilometers per hour or Miles per hour."""
110
-
111
- return int(self.__inner[f'windspeed{self._CustomizableBase__unit.velocity}'])
118
+ pressure: float
119
+ """The pressure in either pascal or inches."""
112
120
 
113
- @property
114
- def wind_direction(self) -> WindDirection:
115
- """The wind direction."""
121
+ visibility: int
122
+ """The visibility distance in either kilometers or miles."""
116
123
 
117
- return WindDirection._new(
118
- self.__inner['winddir16Point'], int(self.__inner['winddirDegree'])
119
- )
124
+ wind_speed: int
125
+ """The wind speeds in either kilometers/hour or miles/hour."""
120
126
 
121
- @property
122
- def description(self) -> str:
123
- """The description regarding the forecast. This can be localized in different languages depending on the localization used."""
127
+ description: str
128
+ """The description regarding the forecast. This can be localized in different languages depending on the localization used."""
124
129
 
130
+ def __init__(self, json: dict, unit: _Unit, locale: Locale):
125
131
  description = (
126
- self.__inner['weatherDesc'][0]['value']
127
- if self._CustomizableBase__locale == Locale.ENGLISH
128
- else self.__inner[f'lang_{self._CustomizableBase__locale.value}'][0]['value']
132
+ json['weatherDesc'][0]['value']
133
+ if locale is Locale.ENGLISH
134
+ else json[f'lang_{locale.value}'][0]['value']
129
135
  )
130
136
 
131
- return description.strip()
132
-
133
- @property
134
- def kind(self) -> Kind:
135
- """The kind of the forecast."""
136
-
137
- return Kind(int(self.__inner['weatherCode']))
137
+ self.ultraviolet = UltraViolet._new(int(json['uvIndex']))
138
+ self.humidity = int(json['humidity'])
139
+ self.wind_direction = WindDirection._new(
140
+ json['winddir16Point'], int(json['winddirDegree'])
141
+ )
142
+ self.kind = Kind(int(json['weatherCode']))
143
+ self.feels_like = int(json[f'FeelsLike{unit.temperature}'])
144
+ self.temperature = int(json[f'temp_{unit.temperature}'])
145
+ self.precipitation = float(json[f'precip{unit.precipitation}'])
146
+ self.pressure = float(json[f'pressure{unit.pressure}'])
147
+ self.visibility = int(json[f'visibility{unit.visibility}'])
148
+ self.wind_speed = int(json[f'windspeed{unit.velocity}'])
149
+ self.description = description.strip()
python_weather/client.py CHANGED
@@ -1,13 +1,37 @@
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
+
1
25
  from aiohttp import ClientSession, ClientTimeout, TCPConnector
2
26
  from urllib.parse import quote_plus
3
- from typing import Optional
27
+ from typing import Optional, Tuple
4
28
  from asyncio import sleep
5
29
  from enum import auto
6
30
 
31
+ from .errors import Error, RequestError
7
32
  from .constants import _Unit, METRIC
8
33
  from .base import CustomizableBase
9
34
  from .forecast import Forecast
10
- from .errors import Error
11
35
  from .enums import Locale
12
36
 
13
37
 
@@ -22,10 +46,10 @@ class Client(CustomizableBase):
22
46
  :param session: Whether to use an existing aiohttp client session for requesting or not. Defaults to ``None`` (creates a new one instead)
23
47
  :type session: Optional[:class:`aiohttp.ClientSession`]
24
48
 
25
- :raises Error: If the ``unit`` argument is not ``None`` and it's also not ``METRIC`` or ``IMPERIAL``, or if the ``locale`` argument is not ``None`` and it's also not a part of the :class:`Locale` enum.
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.
26
50
  """
27
51
 
28
- __slots__ = ('__session',)
52
+ __slots__: Tuple[str, ...] = ('__own_session', '__session')
29
53
 
30
54
  def __init__(
31
55
  self,
@@ -36,13 +60,14 @@ class Client(CustomizableBase):
36
60
  ):
37
61
  super().__init__(unit, locale)
38
62
 
63
+ self.__own_session = session is None
39
64
  self.__session = session or ClientSession(
40
65
  timeout=ClientTimeout(total=5000.0),
41
66
  connector=TCPConnector(verify_ssl=False),
42
67
  )
43
68
 
44
69
  def __repr__(self) -> str:
45
- return f'<{self.__class__.__name__} {self.__session!r}>'
70
+ return f'<{__class__.__name__} {self.__session!r}>'
46
71
 
47
72
  async def get(
48
73
  self,
@@ -56,9 +81,9 @@ class Client(CustomizableBase):
56
81
 
57
82
  :param location: The requested location name for said weather forecast.
58
83
  :type location: str
59
- :param unit: Overrides the metric or imperial/customary system (``IMPERIAL``) used by the :class:`Client` object. Defaults to ``None`` (uses the one from the :class:`Client`).
84
+ :param unit: Overrides the unit used by this object. Defaults to the one used by this object.
60
85
  :type unit: Optional[:py:class:`enum.auto`]
61
- :param locale: Overrides the locale/language used by the :class:`Client` object. Defaults to ``None`` (uses the one from the :class:`Client`).
86
+ :param locale: Overrides the locale used by this object. Defaults to the one used by this object.
62
87
  :type locale: Optional[Locale]
63
88
 
64
89
  :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.
@@ -82,25 +107,27 @@ class Client(CustomizableBase):
82
107
  delay = 0.5
83
108
 
84
109
  while True:
85
- async with self.__session.get(
86
- f'https://{subdomain}wttr.in/{quote_plus(location)}?format=j1'
87
- ) as resp:
88
- try:
110
+ try:
111
+ async with self.__session.get(
112
+ f'https://{subdomain}wttr.in/{quote_plus(location)}?format=j1'
113
+ ) as resp:
114
+ resp.raise_for_status()
115
+
89
116
  return Forecast(await resp.json(), unit, locale)
90
- except Exception as e:
91
- if delay == 2:
92
- raise e # okay, that's too much requests - just raise the error
117
+ except Exception as err:
118
+ if delay == 4:
119
+ raise RequestError(err)
93
120
 
94
- await sleep(delay)
95
- delay *= 2
121
+ await sleep(delay)
122
+ delay *= 2
96
123
 
97
- async def close(self):
98
- """Closes the :class:`Client` object. Nothing will happen if it's already closed."""
124
+ async def close(self) -> None:
125
+ """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."""
99
126
 
100
- if not self.__session.closed:
127
+ if self.__own_session and not self.__session.closed:
101
128
  await self.__session.close()
102
129
 
103
- async def __aenter__(self):
130
+ async def __aenter__(self) -> 'Client':
104
131
  return self
105
132
 
106
133
  async def __aexit__(self, *_, **__):
@@ -1,9 +1,33 @@
1
- from typing import Union
2
- from re import compile
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 Union, Tuple
26
+ import re
3
27
 
4
28
 
5
29
  class _Unit:
6
- __slots__ = (
30
+ __slots__: Tuple[str, ...] = (
7
31
  'temperature',
8
32
  'velocity',
9
33
  'pressure',
@@ -36,4 +60,4 @@ METRIC = _Unit('C', 'Kmph', '', 'MM', '', 1)
36
60
  IMPERIAL = _Unit('F', 'Miles', 'Inches', 'Inches', 'Miles', 2.54)
37
61
 
38
62
  WIND_DIRECTION_EMOJIS = ('↓', '↙', '←', '↖', '↑', '↗', '→', '↘')
39
- LATLON_REGEX = compile(r'^Lat (\-?[\d\.]+) and Lon (\-?[\d\.]+)$')
63
+ LATLON_REGEX = re.compile(r'^Lat (\-?[\d\.]+) and Lon (\-?[\d\.]+)$')
python_weather/enums.py CHANGED
@@ -1,11 +1,35 @@
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
+
1
25
  from enum import Enum
2
- from typing import Union
26
+ from typing import Union, Tuple
3
27
 
4
28
  from .constants import WIND_DIRECTION_EMOJIS
5
29
 
6
30
 
7
31
  class BasicEnum(Enum):
8
- __slots__ = ()
32
+ __slots__: Tuple[str, ...] = ()
9
33
 
10
34
  def __repr__(self) -> str:
11
35
  return f'{self.__class__.__name__}.{self.name}'
@@ -15,59 +39,45 @@ class BasicEnum(Enum):
15
39
 
16
40
 
17
41
  class IndexedEnum(Enum):
18
- __slots__ = ('__index',)
42
+ __slots__: Tuple[str, ...] = ('index',)
43
+
44
+ index: int
45
+ """The index value."""
19
46
 
20
47
  def __lt__(self, other: Union['IndexedEnum', int, float]) -> bool:
21
- if isinstance(other, self.__class__):
22
- return self.__index < other.index
23
- else:
24
- return self.__index < other
48
+ return self.index < getattr(other, 'index', other)
25
49
 
26
50
  def __eq__(self, other: Union['IndexedEnum', int, float]) -> bool:
27
- if isinstance(other, self.__class__):
28
- return self.__index == other.index
29
- else:
30
- return self.__index == other
51
+ return self.index == getattr(other, 'index', other)
31
52
 
32
53
  def __gt__(self, other: Union['IndexedEnum', int, float]) -> bool:
33
- if isinstance(other, self.__class__):
34
- return self.__index > other.index
35
- else:
36
- return self.__index > other
54
+ return self.index > getattr(other, 'index', other)
37
55
 
38
56
  def __hash__(self) -> int:
39
- return self.__index
57
+ return self.index
40
58
 
41
59
  def __int__(self) -> int:
42
- return self.__index
43
-
44
- @property
45
- def index(self) -> int:
46
- """The index value."""
47
-
48
- return self.__index
49
-
50
- @index.setter
51
- def index(self, new_index: int) -> int:
52
- self.__index = new_index
60
+ return self.index
53
61
 
54
62
 
55
63
  class HeatIndex(IndexedEnum):
56
64
  """Represents a heat index."""
57
65
 
66
+ __slots__: Tuple[str, ...] = ()
67
+
58
68
  CAUTION = None
59
69
  EXTREME_CAUTION = None
60
70
  DANGER = None
61
71
  EXTREME_DANGER = None
62
72
 
63
- def _new(celcius_index: int, true_index: int):
73
+ def _new(celcius_index: int, true_index: int) -> 'HeatIndex':
64
74
  enum = HeatIndex(celcius_index)
65
75
  enum.index = true_index
66
76
 
67
77
  return enum
68
78
 
69
79
  @classmethod
70
- def _missing_(self, celcius_index: int):
80
+ def _missing_(self, celcius_index: int) -> 'HeatIndex':
71
81
  if celcius_index <= 32:
72
82
  return self.CAUTION
73
83
  elif celcius_index <= 39:
@@ -81,20 +91,22 @@ class HeatIndex(IndexedEnum):
81
91
  class UltraViolet(BasicEnum, IndexedEnum):
82
92
  """Represents ultra-violet (UV) index."""
83
93
 
94
+ __slots__: Tuple[str, ...] = ()
95
+
84
96
  LOW = None
85
97
  MODERATE = None
86
98
  HIGH = None
87
99
  VERY_HIGH = None
88
100
  EXTREME = None
89
101
 
90
- def _new(index: int):
102
+ def _new(index: int) -> 'UltraViolet':
91
103
  enum = UltraViolet(index)
92
104
  enum.index = index
93
105
 
94
106
  return enum
95
107
 
96
108
  @classmethod
97
- def _missing_(self, index: int):
109
+ def _missing_(self, index: int) -> 'UltraViolet':
98
110
  if index <= 2:
99
111
  return self.LOW
100
112
  elif index <= 5:
@@ -110,7 +122,7 @@ class UltraViolet(BasicEnum, IndexedEnum):
110
122
  class WindDirection(BasicEnum):
111
123
  """Represents a wind direction."""
112
124
 
113
- __slots__ = ('__degrees',)
125
+ __slots__: Tuple[str, ...] = ('degrees',)
114
126
 
115
127
  NORTH = 'N'
116
128
  NORTH_NORTHEAST = 'NNE'
@@ -129,15 +141,17 @@ class WindDirection(BasicEnum):
129
141
  NORTHWEST = 'NW'
130
142
  NORTH_NORTHWEST = 'NNW'
131
143
 
132
- def _new(value: str, degrees: float):
144
+ degrees: float
145
+ """The wind direction's value in degrees."""
146
+
147
+ def _new(value: str, degrees: float) -> 'WindDirection':
133
148
  enum = WindDirection(value)
134
- enum.__degrees = degrees
149
+ enum.degrees = degrees
135
150
 
136
151
  return enum
137
152
 
138
153
  def __contains__(self, other: Union['WindDirection', float, int]) -> bool:
139
- if isinstance(other, self.__class__):
140
- other = other.degrees
154
+ other = getattr(other, 'degrees', other)
141
155
 
142
156
  if self is self.NORTH:
143
157
  return other > 348.75 or other <= 11.25
@@ -175,12 +189,6 @@ class WindDirection(BasicEnum):
175
189
  def __float__(self) -> float:
176
190
  return self.__degrees
177
191
 
178
- @property
179
- def degrees(self) -> int:
180
- """The wind direction's value in degrees."""
181
-
182
- return self.__degrees
183
-
184
192
  @property
185
193
  def emoji(self) -> str:
186
194
  """The emoji representing this enum."""
@@ -191,7 +199,7 @@ class WindDirection(BasicEnum):
191
199
  class Locale(Enum):
192
200
  """Represents the list of supported locales/languages by this library."""
193
201
 
194
- __slots__ = ()
202
+ __slots__: Tuple[str, ...] = ()
195
203
 
196
204
  AFRIKAANS = 'af'
197
205
  AMHARIC = 'am'
@@ -267,7 +275,7 @@ class Locale(Enum):
267
275
  ZULU = 'zu'
268
276
 
269
277
  def __repr__(self) -> str:
270
- return f'{self.__class__.__name__}.{self.name}'
278
+ return f'{__class__.__name__}.{self.name}'
271
279
 
272
280
  def __str__(self) -> str:
273
281
  arr = self.name.title().split('_')
@@ -277,7 +285,7 @@ class Locale(Enum):
277
285
  class Kind(BasicEnum):
278
286
  """Represents a weather forecast kind."""
279
287
 
280
- __slots__ = ()
288
+ __slots__: Tuple[str, ...] = ()
281
289
 
282
290
  SUNNY = 113
283
291
  PARTLY_CLOUDY = 116
@@ -299,7 +307,7 @@ class Kind(BasicEnum):
299
307
  THUNDERY_SNOW_SHOWERS = 392
300
308
 
301
309
  @classmethod
302
- def _missing_(self, value: int):
310
+ def _missing_(self, value: int) -> 'Kind':
303
311
  if value == 248 or value == 260:
304
312
  return self.FOG
305
313
  elif value == 263 or value == 353:
@@ -381,7 +389,7 @@ class Kind(BasicEnum):
381
389
  class Phase(BasicEnum):
382
390
  """Represents a moon phase."""
383
391
 
384
- __slots__ = ()
392
+ __slots__: Tuple[str, ...] = ()
385
393
 
386
394
  NEW_MOON = 'New Moon'
387
395
  WAXING_CRESCENT = 'Waxing Crescent'
python_weather/errors.py CHANGED
@@ -1,4 +1,45 @@
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 Tuple
26
+
27
+
1
28
  class Error(Exception):
2
29
  """Represents a ``python_weather`` error class. Extends :py:class:`Exception`."""
3
30
 
4
- __slots__ = ()
31
+ __slots__: Tuple[str, ...] = ()
32
+
33
+
34
+ class RequestError(Error):
35
+ """Thrown upon HTTP request failure. Extends :class:`Error`."""
36
+
37
+ __slots__: Tuple[str, ...] = ('source',)
38
+
39
+ source: Exception
40
+ """The :class:`Exception` instance causing this exception."""
41
+
42
+ def __init__(self, source: Exception):
43
+ self.source = source
44
+
45
+ super().__init__()