python-weather 2.0.4__tar.gz → 2.0.5__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.5}/PKG-INFO +4 -4
  2. {python_weather-2.0.4 → python_weather-2.0.5}/pyproject.toml +5 -5
  3. {python_weather-2.0.4 → python_weather-2.0.5}/python_weather/__init__.py +1 -1
  4. {python_weather-2.0.4 → python_weather-2.0.5}/python_weather/base.py +53 -66
  5. {python_weather-2.0.4 → python_weather-2.0.5}/python_weather/client.py +2 -1
  6. {python_weather-2.0.4 → python_weather-2.0.5}/python_weather/enums.py +5 -8
  7. python_weather-2.0.5/python_weather/forecast.py +296 -0
  8. {python_weather-2.0.4 → python_weather-2.0.5/python_weather.egg-info}/PKG-INFO +4 -4
  9. python_weather-2.0.5/python_weather.egg-info/requires.txt +1 -0
  10. python_weather-2.0.4/python_weather/forecast.py +0 -350
  11. python_weather-2.0.4/python_weather.egg-info/requires.txt +0 -1
  12. {python_weather-2.0.4 → python_weather-2.0.5}/LICENSE +0 -0
  13. {python_weather-2.0.4 → python_weather-2.0.5}/MANIFEST.in +0 -0
  14. {python_weather-2.0.4 → python_weather-2.0.5}/README.md +0 -0
  15. {python_weather-2.0.4 → python_weather-2.0.5}/python_weather/constants.py +0 -0
  16. {python_weather-2.0.4 → python_weather-2.0.5}/python_weather/errors.py +0 -0
  17. {python_weather-2.0.4 → python_weather-2.0.5}/python_weather.egg-info/SOURCES.txt +0 -0
  18. {python_weather-2.0.4 → python_weather-2.0.5}/python_weather.egg-info/dependency_links.txt +0 -0
  19. {python_weather-2.0.4 → python_weather-2.0.5}/python_weather.egg-info/top_level.txt +0 -0
  20. {python_weather-2.0.4 → python_weather-2.0.5}/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.5
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.5"
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.5'
35
35
  __all__ = (
36
36
  'METRIC',
37
37
  'IMPERIAL',
@@ -79,84 +79,71 @@ class CustomizableBase:
79
79
  self.__locale = to
80
80
 
81
81
 
82
- class BaseForecast(CustomizableBase):
83
- __slots__: Tuple[str, ...] = ('__inner',)
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
+ )
84
96
 
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."""
97
+ ultraviolet: UltraViolet
98
+ """The ultra-violet (UV) index."""
99
99
 
100
- return int(self.__inner[f'FeelsLike{self._CustomizableBase__unit.temperature}'])
100
+ humidity: int
101
+ """The humidity value in percent."""
101
102
 
102
- @property
103
- def humidity(self) -> int:
104
- """The humidity value in percent."""
103
+ wind_direction: WindDirection
104
+ """The wind direction."""
105
105
 
106
- return int(self.__inner['humidity'])
106
+ kind: Kind
107
+ """The kind of the forecast."""
107
108
 
108
- @property
109
- def temperature(self) -> int:
110
- """The temperature in either celcius or Fahrenheit."""
109
+ feels_like: int
110
+ """What it felt like, in celcius or fahrenheit."""
111
111
 
112
- return int(self.__inner[f'temp_{self._CustomizableBase__unit.temperature}'])
112
+ temperature: int
113
+ """The temperature in either celcius or Fahrenheit."""
113
114
 
114
- @property
115
- def precipitation(self) -> float:
116
- """The precipitation in either millimeters or inches."""
115
+ precipitation: float
116
+ """The precipitation in either millimeters or inches."""
117
117
 
118
- return float(self.__inner[f'precip{self._CustomizableBase__unit.precipitation}'])
118
+ pressure: float
119
+ """The pressure in either pascal or inches."""
119
120
 
120
- @property
121
- def pressure(self) -> float:
122
- """The pressure in either pascal or inches."""
121
+ visibility: int
122
+ """The visibility distance in either kilometers or miles."""
123
123
 
124
- return float(self.__inner[f'pressure{self._CustomizableBase__unit.pressure}'])
124
+ wind_speed: int
125
+ """The wind speeds in either kilometers/hour or miles/hour."""
125
126
 
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."""
127
+ description: str
128
+ """The description regarding the forecast. This can be localized in different languages depending on the localization used."""
149
129
 
130
+ def __init__(self, json: dict, unit: _Unit, locale: Locale):
150
131
  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']
132
+ json['weatherDesc'][0]['value']
133
+ if locale is Locale.ENGLISH
134
+ else json[f'lang_{locale.value}'][0]['value']
154
135
  )
155
136
 
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']))
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()
@@ -62,7 +62,6 @@ class Client(CustomizableBase):
62
62
 
63
63
  self.__own_session = session is None
64
64
  self.__session = session or ClientSession(
65
- raise_for_status=True,
66
65
  timeout=ClientTimeout(total=5000.0),
67
66
  connector=TCPConnector(verify_ssl=False),
68
67
  )
@@ -112,6 +111,8 @@ class Client(CustomizableBase):
112
111
  async with self.__session.get(
113
112
  f'https://{subdomain}wttr.in/{quote_plus(location)}?format=j1'
114
113
  ) as resp:
114
+ resp.raise_for_status()
115
+
115
116
  return Forecast(await resp.json(), unit, locale)
116
117
  except Exception as err:
117
118
  if delay == 4:
@@ -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
+ degrees: float
145
+ """The wind direction's value in degrees."""
146
+
144
147
  def _new(value: str, degrees: float) -> 'WindDirection':
145
148
  enum = WindDirection(value)
146
- enum.__degrees = degrees
149
+ enum.degrees = degrees
147
150
 
148
151
  return enum
149
152
 
@@ -186,12 +189,6 @@ class WindDirection(BasicEnum):
186
189
  def __float__(self) -> float:
187
190
  return self.__degrees
188
191
 
189
- @property
190
- def degrees(self) -> int:
191
- """The wind direction's value in degrees."""
192
-
193
- return self.__degrees
194
-
195
192
  @property
196
193
  def emoji(self) -> str:
197
194
  """The emoji representing this enum."""
@@ -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.5
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