python-weather 1.1.2__tar.gz → 2.0.0__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.
@@ -1,14 +1,13 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-weather
3
- Version: 1.1.2
3
+ Version: 2.0.0
4
4
  Summary: A free and asynchronous weather API wrapper made in Python, for Python.
5
5
  Author: null8626
6
6
  License: MIT
7
- Project-URL: changes, https://python-weather.readthedocs.io/en/latest/changelog.html
8
- Project-URL: homepage, https://python-weather.readthedocs.io/en/latest/
9
- Project-URL: documentation, https://python-weather.readthedocs.io/en/latest/
10
- Project-URL: repository, https://github.com/null8626/python-weather
11
- Project-URL: download_url, https://github.com/null8626/python-weather/archive/1.1.2.tar.gz
7
+ Project-URL: Changelog, https://python-weather.readthedocs.io/en/latest/changelog.html
8
+ Project-URL: Homepage, https://python-weather.readthedocs.io/en/latest/
9
+ Project-URL: Documentation, https://python-weather.readthedocs.io/en/latest/
10
+ Project-URL: Repository, https://github.com/null8626/python-weather
12
11
  Keywords: weather,forecast,weather-api,weather-forecast
13
12
  Classifier: Development Status :: 5 - Production/Stable
14
13
  Classifier: Framework :: aiohttp
@@ -36,19 +35,11 @@ Description-Content-Type: text/markdown
36
35
  License-File: LICENSE
37
36
  Requires-Dist: aiohttp==3.9.3
38
37
 
39
- # [python-weather][pypi-url] [![pypi][pypi-image]][pypi-url] [![downloads][downloads-image]][pypi-url] [![Build Status][ci-image]][ci-url] [![Documentation Build Status][doc-ci-image]][doc-ci-url] [![license][github-license-image]][github-license-url] [![BLAZINGLY FAST!!!][blazingly-fast-image]][blazingly-fast-url]
38
+ # [python-weather][pypi-url] [![pypi][pypi-image]][pypi-url] [![downloads][downloads-image]][pypi-url]
40
39
 
41
40
  [pypi-image]: https://img.shields.io/pypi/v/python-weather.svg?style=flat-square
42
41
  [pypi-url]: https://pypi.org/project/python-weather/
43
42
  [downloads-image]: https://img.shields.io/pypi/dm/python-weather?style=flat-square
44
- [ci-image]: https://github.com/null8626/python-weather/workflows/CI/badge.svg
45
- [ci-url]: https://github.com/null8626/python-weather/actions/workflows/CI.yml
46
- [doc-ci-image]: https://readthedocs.org/projects/python-weather/badge/?version=latest
47
- [doc-ci-url]: https://python-weather.readthedocs.io/en/latest/?badge=latest
48
- [github-license-image]: https://img.shields.io/github/license/null8626/python-weather?style=flat-square
49
- [github-license-url]: https://github.com/null8626/python-weather/blob/main/LICENSE
50
- [blazingly-fast-image]: https://img.shields.io/badge/speed-BLAZINGLY%20FAST!!!%20%F0%9F%94%A5%F0%9F%9A%80%F0%9F%92%AA%F0%9F%98%8E-brightgreen.svg?style=flat-square
51
- [blazingly-fast-url]: https://twitter.com/acdlite/status/974390255393505280
52
43
 
53
44
  A free and asynchronous weather Python API wrapper made in Python, for Python.
54
45
 
@@ -76,14 +67,14 @@ async def getweather():
76
67
  weather = await client.get('New York')
77
68
 
78
69
  # returns the current day's forecast temperature (int)
79
- print(weather.current.temperature)
70
+ print(weather.temperature)
80
71
 
81
72
  # get the weather forecast for a few days
82
- for forecast in weather.forecasts:
83
- print(forecast)
73
+ for daily in weather.daily_forecasts:
74
+ print(daily)
84
75
 
85
76
  # hourly forecasts
86
- for hourly in forecast.hourly:
77
+ for hourly in daily.hourly_forecasts:
87
78
  print(f' --> {hourly!r}')
88
79
 
89
80
  if __name__ == '__main__':
@@ -1,16 +1,8 @@
1
- # [python-weather][pypi-url] [![pypi][pypi-image]][pypi-url] [![downloads][downloads-image]][pypi-url] [![Build Status][ci-image]][ci-url] [![Documentation Build Status][doc-ci-image]][doc-ci-url] [![license][github-license-image]][github-license-url] [![BLAZINGLY FAST!!!][blazingly-fast-image]][blazingly-fast-url]
1
+ # [python-weather][pypi-url] [![pypi][pypi-image]][pypi-url] [![downloads][downloads-image]][pypi-url]
2
2
 
3
3
  [pypi-image]: https://img.shields.io/pypi/v/python-weather.svg?style=flat-square
4
4
  [pypi-url]: https://pypi.org/project/python-weather/
5
5
  [downloads-image]: https://img.shields.io/pypi/dm/python-weather?style=flat-square
6
- [ci-image]: https://github.com/null8626/python-weather/workflows/CI/badge.svg
7
- [ci-url]: https://github.com/null8626/python-weather/actions/workflows/CI.yml
8
- [doc-ci-image]: https://readthedocs.org/projects/python-weather/badge/?version=latest
9
- [doc-ci-url]: https://python-weather.readthedocs.io/en/latest/?badge=latest
10
- [github-license-image]: https://img.shields.io/github/license/null8626/python-weather?style=flat-square
11
- [github-license-url]: https://github.com/null8626/python-weather/blob/main/LICENSE
12
- [blazingly-fast-image]: https://img.shields.io/badge/speed-BLAZINGLY%20FAST!!!%20%F0%9F%94%A5%F0%9F%9A%80%F0%9F%92%AA%F0%9F%98%8E-brightgreen.svg?style=flat-square
13
- [blazingly-fast-url]: https://twitter.com/acdlite/status/974390255393505280
14
6
 
15
7
  A free and asynchronous weather Python API wrapper made in Python, for Python.
16
8
 
@@ -38,14 +30,14 @@ async def getweather():
38
30
  weather = await client.get('New York')
39
31
 
40
32
  # returns the current day's forecast temperature (int)
41
- print(weather.current.temperature)
33
+ print(weather.temperature)
42
34
 
43
35
  # get the weather forecast for a few days
44
- for forecast in weather.forecasts:
45
- print(forecast)
36
+ for daily in weather.daily_forecasts:
37
+ print(daily)
46
38
 
47
39
  # hourly forecasts
48
- for hourly in forecast.hourly:
40
+ for hourly in daily.hourly_forecasts:
49
41
  print(f' --> {hourly!r}')
50
42
 
51
43
  if __name__ == '__main__':
@@ -3,7 +3,7 @@ requires = ["setuptools"]
3
3
 
4
4
  [project]
5
5
  name = "python-weather"
6
- version = "1.1.2"
6
+ version = "2.0.0"
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" }
@@ -36,8 +36,7 @@ classifiers = [
36
36
  requires-python = ">=3.8"
37
37
 
38
38
  [project.urls]
39
- changes = "https://python-weather.readthedocs.io/en/latest/changelog.html"
40
- homepage = "https://python-weather.readthedocs.io/en/latest/"
41
- documentation = "https://python-weather.readthedocs.io/en/latest/"
42
- repository = "https://github.com/null8626/python-weather"
43
- download_url = "https://github.com/null8626/python-weather/archive/1.1.2.tar.gz"
39
+ Changelog = "https://python-weather.readthedocs.io/en/latest/changelog.html"
40
+ Homepage = "https://python-weather.readthedocs.io/en/latest/"
41
+ Documentation = "https://python-weather.readthedocs.io/en/latest/"
42
+ Repository = "https://github.com/null8626/python-weather"
@@ -0,0 +1,14 @@
1
+ """
2
+ python-weather
3
+ """
4
+
5
+ from .enums import HeatIndex, Kind, Locale, Phase, UltraViolet, WindDirection
6
+ from .constants import METRIC, IMPERIAL
7
+ from .client import Client
8
+ from .errors import Error
9
+
10
+ __version__ = '2.0.0'
11
+ __all__ = (
12
+ 'METRIC', 'IMPERIAL', 'Client', 'Error', 'HeatIndex', 'Kind', 'Locale',
13
+ 'Phase', 'UltraViolet', 'WindDirection'
14
+ )
@@ -1,6 +1,6 @@
1
1
  from enum import auto, Enum
2
2
 
3
- from .enums import WindDirection, Kind, Locale, Ultraviolet
3
+ from .enums import WindDirection, Kind, Locale, UltraViolet
4
4
  from .constants import _Unit
5
5
 
6
6
  class CustomizableBase:
@@ -60,10 +60,10 @@ class BaseForecast(CustomizableBase):
60
60
  super().__init__(unit, locale)
61
61
 
62
62
  @property
63
- def ultraviolet(self) -> Ultraviolet:
64
- """The UV (ultraviolet) index."""
63
+ def ultraviolet(self) -> UltraViolet:
64
+ """The ultra-violet (UV) index."""
65
65
 
66
- return Ultraviolet._new(int(self.__inner['uvIndex']))
66
+ return UltraViolet._new(int(self.__inner['uvIndex']))
67
67
 
68
68
  @property
69
69
  def feels_like(self) -> int:
@@ -81,7 +81,7 @@ class BaseForecast(CustomizableBase):
81
81
 
82
82
  @property
83
83
  def temperature(self) -> int:
84
- """The weather temperature in either Celcius or Fahrenheit."""
84
+ """The temperature in either Celcius or Fahrenheit."""
85
85
 
86
86
  return int(self.__inner[f'temp_{self._CustomizableBase__unit.temperature}'])
87
87
 
@@ -129,13 +129,12 @@ class BaseForecast(CustomizableBase):
129
129
  def description(self) -> str:
130
130
  """The description regarding the forecast. This can be localized in different languages depending on the localization used."""
131
131
 
132
- return self.__inner['weatherDesc'][0][
133
- 'value'
134
- ] if self._CustomizableBase__locale == Locale.ENGLISH else self.__inner[
135
- f'lang_{self._CustomizableBase__locale.value}'][0]['value']
132
+ description = self.__inner['weatherDesc'][0]['value'] if self._CustomizableBase__locale == Locale.ENGLISH else self.__inner[f'lang_{self._CustomizableBase__locale.value}'][0]['value'] # yapf: disable
133
+
134
+ return description.strip()
136
135
 
137
136
  @property
138
137
  def kind(self) -> Kind:
139
- """The kind of the weather."""
138
+ """The kind of the forecast."""
140
139
 
141
140
  return Kind(int(self.__inner['weatherCode']))
@@ -6,7 +6,7 @@ from enum import auto
6
6
 
7
7
  from .constants import _Unit, METRIC
8
8
  from .base import CustomizableBase
9
- from .forecast import Weather
9
+ from .forecast import Forecast
10
10
  from .errors import Error
11
11
  from .enums import Locale
12
12
 
@@ -16,7 +16,7 @@ class Client(CustomizableBase):
16
16
 
17
17
  :param unit: Whether to use the metric or imperial/customary system (``IMPERIAL``). Defaults to ``METRIC``.
18
18
  :type unit: Optional[:py:class:`enum.auto`]
19
- :param locale: Whether to use a different locale/language as the description for the returned weather events. Defaults to ``Locale.ENGLISH``.
19
+ :param locale: Whether to use a different locale/language as the description for the returned forecast. Defaults to ``Locale.ENGLISH``.
20
20
  :type locale: Optional[Locale]
21
21
  :param session: Whether to use an existing aiohttp client session for requesting or not. Defaults to ``None`` (creates a new one instead)
22
22
  :type session: Optional[:class:`aiohttp.ClientSession`]
@@ -49,7 +49,7 @@ class Client(CustomizableBase):
49
49
  *,
50
50
  unit: Optional[auto] = None,
51
51
  locale: Optional[Locale] = None
52
- ) -> Weather:
52
+ ) -> Forecast:
53
53
  """
54
54
  Fetches a weather forecast for a specific location.
55
55
 
@@ -63,7 +63,7 @@ class Client(CustomizableBase):
63
63
  :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.
64
64
 
65
65
  :returns: The requested weather forecast.
66
- :rtype: Weather
66
+ :rtype: Forecast
67
67
  """
68
68
 
69
69
  if (not isinstance(location, str)) or (not location):
@@ -89,7 +89,7 @@ class Client(CustomizableBase):
89
89
  f'https://{subdomain}wttr.in/{quote_plus(location)}?format=j1'
90
90
  ) as resp:
91
91
  try:
92
- return Weather(await resp.json(), unit, locale)
92
+ return Forecast(await resp.json(), unit, locale)
93
93
  except Exception as e:
94
94
  if delay == 4:
95
95
  raise e # okay, that's too much requests - just raise the error
@@ -30,4 +30,5 @@ class _Unit:
30
30
  METRIC = _Unit('C', 'Kmph', '', 'MM', '', 1)
31
31
  IMPERIAL = _Unit('F', 'Miles', 'Inches', 'Inches', 'Miles', 2.54)
32
32
 
33
+ WIND_DIRECTION_EMOJIS = ['↓', '↙', '←', '↖', '↑', '↗', '→', '↘']
33
34
  LATLON_REGEX = compile(r'^Lat (\-?[\d\.]+) and Lon (\-?[\d\.]+)$')
@@ -1,6 +1,7 @@
1
1
  from enum import Enum, auto
2
2
  from typing import Union
3
3
 
4
+ from .constants import WIND_DIRECTION_EMOJIS
4
5
  from .errors import Error
5
6
 
6
7
  class BasicEnum(Enum):
@@ -12,55 +13,22 @@ class BasicEnum(Enum):
12
13
  def __str__(self) -> str:
13
14
  return self.name.replace('_', ' ').title()
14
15
 
15
- class Ultraviolet(Enum):
16
- """Represents UV (ultraviolet) index."""
17
-
16
+ class IndexedEnum(Enum):
18
17
  __slots__ = ('__index',)
19
18
 
20
- LOW = 13
21
- MODERATE = auto()
22
- HIGH = auto()
23
- VERY_HIGH = auto()
24
- EXTREME = auto()
25
-
26
- def _new(index: int):
27
- enum = Ultraviolet(index)
28
- enum.__index = index
29
-
30
- return enum
31
-
32
- @classmethod
33
- def _missing_(self, index: int):
34
- if index <= 2:
35
- return self.LOW
36
- elif index <= 5:
37
- return self.MODERATE
38
- elif index <= 7:
39
- return self.HIGH
40
- elif index <= 10:
41
- return self.VERY_HIGH
42
- else:
43
- return self.EXTREME
44
-
45
- def __repr__(self) -> str:
46
- return f'<{self.__class__.__name__}.{self.name} index={self.__index}>'
47
-
48
- def __str__(self) -> str:
49
- return self.name.replace('_', ' ').title()
50
-
51
- def __lt__(self, other: Union["Ultraviolet", int, float]) -> bool:
19
+ def __lt__(self, other: Union["Self", int, float]) -> bool:
52
20
  if isinstance(other, self.__class__):
53
21
  return self.__index < other.index
54
22
  else:
55
23
  return self.__index < other
56
24
 
57
- def __eq__(self, other: Union["Ultraviolet", int, float]) -> bool:
25
+ def __eq__(self, other: Union["Self", int, float]) -> bool:
58
26
  if isinstance(other, self.__class__):
59
27
  return self.__index == other.index
60
28
  else:
61
29
  return self.__index == other
62
30
 
63
- def __gt__(self, other: Union["Ultraviolet", int, float]) -> bool:
31
+ def __gt__(self, other: Union["Self", int, float]) -> bool:
64
32
  if isinstance(other, self.__class__):
65
33
  return self.__index > other.index
66
34
  else:
@@ -74,11 +42,68 @@ class Ultraviolet(Enum):
74
42
 
75
43
  @property
76
44
  def index(self) -> int:
77
- """The ultraviolet index."""
45
+ """The index value."""
78
46
 
79
47
  return self.__index
48
+
49
+ @index.setter
50
+ def index(self, new_index: int) -> int:
51
+ self.__index = new_index
52
+
53
+ class HeatIndex(IndexedEnum):
54
+ """Represents a heat index."""
55
+
56
+ CAUTION = None
57
+ EXTREME_CAUTION = None
58
+ DANGER = None
59
+ EXTREME_DANGER = None
60
+
61
+ def _new(celcius_index: int, true_index: int):
62
+ enum = HeatIndex(celcius_index)
63
+ enum.index = true_index
64
+
65
+ return enum
66
+
67
+ @classmethod
68
+ def _missing_(self, celcius_index: int):
69
+ if celcius_index <= 32:
70
+ return self.CAUTION
71
+ elif celcius_index <= 39:
72
+ return self.EXTREME_CAUTION
73
+ elif celcius_index <= 51:
74
+ return self.DANGER
75
+ else:
76
+ return self.EXTREME_DANGER
77
+
78
+ class UltraViolet(BasicEnum, IndexedEnum):
79
+ """Represents ultra-violet (UV) index."""
80
+
81
+ LOW = None
82
+ MODERATE = None
83
+ HIGH = None
84
+ VERY_HIGH = None
85
+ EXTREME = None
86
+
87
+ def _new(index: int):
88
+ enum = UltraViolet(index)
89
+ enum.index = index
90
+
91
+ return enum
92
+
93
+ @classmethod
94
+ def _missing_(self, index: int):
95
+ if index <= 2:
96
+ return self.LOW
97
+ elif index <= 5:
98
+ return self.MODERATE
99
+ elif index <= 7:
100
+ return self.HIGH
101
+ elif index <= 10:
102
+ return self.VERY_HIGH
103
+ else:
104
+ return self.EXTREME
80
105
 
81
- class WindDirection(Enum):
106
+ class WindDirection(BasicEnum):
82
107
  """Represents a wind direction."""
83
108
 
84
109
  __slots__ = ('__degrees',)
@@ -106,12 +131,6 @@ class WindDirection(Enum):
106
131
 
107
132
  return enum
108
133
 
109
- def __repr__(self) -> str:
110
- return f'<{self.__class__.__name__}.{self.name} degrees={self.__degrees!r}>'
111
-
112
- def __str__(self) -> str:
113
- return self.name.replace('_', ' ').title()
114
-
115
134
  def __contains__(self, other: Union["WindDirection", float, int]) -> bool:
116
135
  if isinstance(other, self.__class__):
117
136
  other = other.degrees
@@ -149,11 +168,20 @@ class WindDirection(Enum):
149
168
  else:
150
169
  return 326.25 < other <= 348.75
151
170
 
171
+ def __float__(self) -> float:
172
+ return self.__degrees
173
+
152
174
  @property
153
175
  def degrees(self) -> int:
154
176
  """The wind direction's value in degrees."""
155
177
 
156
178
  return self.__degrees
179
+
180
+ @property
181
+ def emoji(self) -> str:
182
+ """The emoji representing this enum."""
183
+
184
+ return WIND_DIRECTION_EMOJIS[int(((self.__degrees + 22.5) % 360) / 45.0)]
157
185
 
158
186
  class Locale(Enum):
159
187
  """Represents the list of supported locales/languages by this library."""
@@ -1,133 +1,10 @@
1
- import warnings
2
1
  from typing import Iterable, Optional, Tuple
3
2
  from datetime import datetime, date, time
4
3
  from enum import auto
5
4
 
6
5
  from .base import BaseForecast, CustomizableBase
6
+ from .enums import Phase, Locale, HeatIndex
7
7
  from .constants import _Unit, LATLON_REGEX
8
- from .enums import Phase, Locale
9
-
10
- class Area:
11
- """Represents the location of the weather forecast."""
12
-
13
- __slots__ = ('__inner',)
14
-
15
- def __init__(self, json: dict):
16
- self.__inner = json
17
-
18
- def __repr__(self) -> str:
19
- return f'<{self.__class__.__name__} name={self.name!r} country={self.country!r} region={self.region!r}>'
20
-
21
- @property
22
- def latitude(self) -> float:
23
- """The latitude."""
24
-
25
- return float(self.__inner['latitude'])
26
-
27
- @property
28
- def longitude(self) -> float:
29
- """The longitude."""
30
-
31
- return float(self.__inner['longitude'])
32
-
33
- @property
34
- def population(self) -> int:
35
- """The location's population count."""
36
-
37
- return int(self.__inner['population'])
38
-
39
- @property
40
- def region(self) -> str:
41
- """The location's region name."""
42
-
43
- return self.__inner['region'][0]['value']
44
-
45
- @property
46
- def name(self) -> str:
47
- """The location's name."""
48
-
49
- return self.__inner['areaName'][0]['value']
50
-
51
- @property
52
- def country(self) -> str:
53
- """The location's country name."""
54
-
55
- return self.__inner['country'][0]['value']
56
-
57
- class Astronomy:
58
- """Represents the astronomical information of said weather forecast."""
59
-
60
- __slots__ = ('__inner',)
61
-
62
- def __init__(self, json: dict):
63
- self.__inner = json
64
-
65
- def __repr__(self) -> str:
66
- return f'<{self.__class__.__name__} moon_phase="{self.moon_phase!r}" sun_rise={self.sun_rise!r} sun_set={self.sun_set!r}>'
67
-
68
- @property
69
- def moon_illumination(self) -> int:
70
- """The percentage of the moon illuminated."""
71
-
72
- return int(self.__inner['moon_illumination'])
73
-
74
- @property
75
- def moon_phase(self) -> Phase:
76
- """The moon's phase."""
77
-
78
- return Phase(self.__inner['moon_phase'])
79
-
80
- @property
81
- def moon_rise(self) -> Optional[time]:
82
- """The local time when the moon rises. This can be ``None``."""
83
-
84
- try:
85
- return datetime.strptime(self.__inner['moonrise'], '%I:%M %p').time()
86
- except ValueError:
87
- ...
88
-
89
- @property
90
- def moon_set(self) -> time:
91
- """The local time when the moon sets. This can be ``None``."""
92
-
93
- try:
94
- return datetime.strptime(self.__inner['moonset'], '%I:%M %p').time()
95
- except ValueError:
96
- ...
97
-
98
- @property
99
- def sun_rise(self) -> Optional[time]:
100
- """The local time when the sun rises. This can be ``None``."""
101
-
102
- try:
103
- return datetime.strptime(self.__inner['sunrise'], '%I:%M %p').time()
104
- except ValueError:
105
- ...
106
-
107
- @property
108
- def sun_set(self) -> Optional[time]:
109
- """The local time when the sun sets. This can be ``None``."""
110
-
111
- try:
112
- return datetime.strptime(self.__inner['sunset'], '%I:%M %p').time()
113
- except ValueError:
114
- ...
115
-
116
- class CurrentForecast(BaseForecast):
117
- """Represents a weather forecast of the current day."""
118
-
119
- __slots__ = ()
120
-
121
- def __repr__(self) -> str:
122
- return f'<{self.__class__.__name__} temperature={self.temperature!r} description={self.description!r} kind="{self.kind!r}">'
123
-
124
- @property
125
- def date(self) -> datetime:
126
- """The local date of this weather forecast."""
127
-
128
- return datetime.strptime(
129
- self._BaseForecast__inner['localObsDateTime'], '%Y-%m-%d %I:%M %p'
130
- )
131
8
 
132
9
  class HourlyForecast(BaseForecast):
133
10
  """Represents a weather forecast of a specific hour."""
@@ -155,11 +32,14 @@ class HourlyForecast(BaseForecast):
155
32
  ) # yapf: disable
156
33
 
157
34
  @property
158
- def heat_index(self) -> int:
35
+ def heat_index(self) -> HeatIndex:
159
36
  """The heat index in either Celcius or Fahrenheit."""
160
37
 
161
- return int(
162
- self._BaseForecast__inner[f'HeatIndex{self._CustomizableBase__unit.temperature}']
38
+ celcius_index = int(self._BaseForecast__inner['HeatIndexC'])
39
+
40
+ return HeatIndex._new(
41
+ celcius_index,
42
+ int(self._BaseForecast__inner[f'HeatIndex{self._CustomizableBase__unit.temperature}'])
163
43
  ) # yapf: disable
164
44
 
165
45
  @property
@@ -190,7 +70,7 @@ class HourlyForecast(BaseForecast):
190
70
  return int(self._BaseForecast__inner['chanceoffrost'])
191
71
 
192
72
  @property
193
- def chances_of_hightemp(self) -> int:
73
+ def chances_of_high_temperature(self) -> int:
194
74
  """Chances of a high temperature in percent."""
195
75
 
196
76
  return int(self._BaseForecast__inner['chanceofhightemp'])
@@ -213,16 +93,6 @@ class HourlyForecast(BaseForecast):
213
93
 
214
94
  return int(self._BaseForecast__inner['chanceofremdry'])
215
95
 
216
- @property
217
- def chances_of_remdry(self) -> int:
218
- """Deprecated, use chances_of_remaining_dry instead."""
219
-
220
- warnings.warn(
221
- 'Deprecated as of v1.1.2. Use `chances_of_remaining_dry` instead.',
222
- DeprecationWarning
223
- )
224
- return self.chances_of_remaining_dry
225
-
226
96
  @property
227
97
  def chances_of_snow(self) -> int:
228
98
  """Chances of a snow in percent."""
@@ -260,21 +130,64 @@ class HourlyForecast(BaseForecast):
260
130
  return time() if len(self._BaseForecast__inner['time']) < 3 else datetime.strptime(self._BaseForecast__inner['time'], '%H%M').time() # yapf: disable
261
131
 
262
132
  class DailyForecast(CustomizableBase):
263
- __slots__ = ('__inner',)
133
+ __slots__ = ('__inner', '__astronomy')
264
134
 
265
135
  def __init__(self, json: dict, unit: _Unit, locale: Locale):
136
+ self.__astronomy = json.pop('astronomy')[0]
266
137
  self.__inner = json
267
138
 
268
139
  super().__init__(unit, locale)
269
140
 
270
141
  def __repr__(self) -> str:
271
- return f'<{self.__class__.__name__} date={self.date!r} astronomy={self.astronomy!r} temperature={self.temperature!r}>'
142
+ return f'<{self.__class__.__name__} date={self.date!r} temperature={self.temperature!r}>'
143
+
144
+ @property
145
+ def moon_illumination(self) -> int:
146
+ """The percentage of the moon illuminated."""
147
+
148
+ return int(self.__astronomy['moon_illumination'])
149
+
150
+ @property
151
+ def moon_phase(self) -> Phase:
152
+ """The moon's phase."""
153
+
154
+ return Phase(self.__astronomy['moon_phase'])
155
+
156
+ @property
157
+ def moonrise(self) -> Optional[time]:
158
+ """The local time when the moon rises. This can be ``None``."""
159
+
160
+ try:
161
+ return datetime.strptime(self.__astronomy['moonrise'], '%I:%M %p').time()
162
+ except ValueError:
163
+ ...
164
+
165
+ @property
166
+ def moonset(self) -> Optional[time]:
167
+ """The local time when the moon sets. This can be ``None``."""
168
+
169
+ try:
170
+ return datetime.strptime(self.__astronomy['moonset'], '%I:%M %p').time()
171
+ except ValueError:
172
+ ...
173
+
174
+ @property
175
+ def sunrise(self) -> Optional[time]:
176
+ """The local time when the sun rises. This can be ``None``."""
177
+
178
+ try:
179
+ return datetime.strptime(self.__astronomy['sunrise'], '%I:%M %p').time()
180
+ except ValueError:
181
+ ...
272
182
 
273
183
  @property
274
- def astronomy(self) -> Astronomy:
275
- """The astronomical information of said weather forecast."""
184
+ def sunset(self) -> Optional[time]:
185
+ """The local time when the sun sets. This can be ``None``."""
276
186
 
277
- return Astronomy(self.__inner['astronomy'][0])
187
+ try:
188
+ return datetime.strptime(self.__astronomy['sunset'], '%I:%M %p').time()
189
+ except ValueError:
190
+ ...
278
191
 
279
192
  @property
280
193
  def date(self) -> date:
@@ -321,7 +234,7 @@ class DailyForecast(CustomizableBase):
321
234
  ) / self._CustomizableBase__unit.cm_divisor
322
235
 
323
236
  @property
324
- def hourly(self) -> Iterable[HourlyForecast]:
237
+ def hourly_forecasts(self) -> Iterable[HourlyForecast]:
325
238
  """The hourly forecasts of this day."""
326
239
 
327
240
  return (
@@ -330,37 +243,56 @@ class DailyForecast(CustomizableBase):
330
243
  ) for elem in self.__inner['hourly']
331
244
  )
332
245
 
333
- class Weather(CustomizableBase):
334
- """Represents an entire weather forecast."""
246
+ class Forecast(BaseForecast):
247
+ """Represents today's weather forecast, alongside daily and hourly weather forecasts."""
335
248
 
336
- __slots__ = ('__inner',)
249
+ __slots__ = ('__inner', '__nearest')
337
250
 
338
251
  def __init__(self, json: dict, unit: _Unit, locale: Locale):
252
+ current = json['current_condition'][0]
253
+ self.__nearest = json.pop('nearest_area')[0]
339
254
  self.__inner = json
340
255
 
341
- super().__init__(unit, locale)
256
+ super().__init__(current, unit, locale)
342
257
 
343
258
  def __repr__(self) -> str:
344
- return f'<{self.__class__.__name__} current={self.current!r} location={self.location!r}>'
259
+ return f'<{self.__class__.__name__} location={self.location!r} datetime={self.datetime!r} temperature={self.temperature!r}>'
345
260
 
346
261
  @property
347
- def current(self) -> CurrentForecast:
348
- """The forecast of the current day."""
262
+ def local_population(self) -> int:
263
+ """The local population count."""
349
264
 
350
- return CurrentForecast(
351
- self.__inner['current_condition'][0], self._CustomizableBase__unit,
352
- self._CustomizableBase__locale
353
- )
265
+ return int(self.__nearest['population'])
354
266
 
355
267
  @property
356
- def nearest_area(self) -> Area:
357
- """The information of the nearest area of the current weather forecast."""
268
+ def region(self) -> str:
269
+ """The local region's name."""
358
270
 
359
- return Area(self.__inner['nearest_area'][0])
271
+ return self.__nearest['region'][0]['value']
360
272
 
361
273
  @property
362
- def forecasts(self) -> Iterable[DailyForecast]:
363
- """Daily forecasts of the current weather forecast."""
274
+ def location(self) -> str:
275
+ """The location's name."""
276
+
277
+ return self.__nearest['areaName'][0]['value']
278
+
279
+ @property
280
+ def country(self) -> str:
281
+ """The local country's name."""
282
+
283
+ return self.__nearest['country'][0]['value']
284
+
285
+ @property
286
+ def datetime(self) -> datetime:
287
+ """The local date and time of this weather forecast."""
288
+
289
+ return datetime.strptime(
290
+ self._BaseForecast__inner['localObsDateTime'], '%Y-%m-%d %I:%M %p'
291
+ )
292
+
293
+ @property
294
+ def daily_forecasts(self) -> Iterable[DailyForecast]:
295
+ """Daily weather forecasts in this location."""
364
296
 
365
297
  return (
366
298
  DailyForecast(
@@ -369,8 +301,8 @@ class Weather(CustomizableBase):
369
301
  )
370
302
 
371
303
  @property
372
- def location(self) -> Optional[Tuple[float, float]]:
373
- """A tuple of both latitude and longitude. This can be ``None``."""
304
+ def coordinates(self) -> Tuple[float, float]:
305
+ """A tuple of this forecast's latitude and longitude."""
374
306
 
375
307
  try:
376
308
  for req in filter(
@@ -380,4 +312,4 @@ class Weather(CustomizableBase):
380
312
 
381
313
  return float(lat), float(lon)
382
314
  except:
383
- ...
315
+ return float(self.__nearest['latitude']), float(self.__nearest['longitude']) # yapf: disable
@@ -1,14 +1,13 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-weather
3
- Version: 1.1.2
3
+ Version: 2.0.0
4
4
  Summary: A free and asynchronous weather API wrapper made in Python, for Python.
5
5
  Author: null8626
6
6
  License: MIT
7
- Project-URL: changes, https://python-weather.readthedocs.io/en/latest/changelog.html
8
- Project-URL: homepage, https://python-weather.readthedocs.io/en/latest/
9
- Project-URL: documentation, https://python-weather.readthedocs.io/en/latest/
10
- Project-URL: repository, https://github.com/null8626/python-weather
11
- Project-URL: download_url, https://github.com/null8626/python-weather/archive/1.1.2.tar.gz
7
+ Project-URL: Changelog, https://python-weather.readthedocs.io/en/latest/changelog.html
8
+ Project-URL: Homepage, https://python-weather.readthedocs.io/en/latest/
9
+ Project-URL: Documentation, https://python-weather.readthedocs.io/en/latest/
10
+ Project-URL: Repository, https://github.com/null8626/python-weather
12
11
  Keywords: weather,forecast,weather-api,weather-forecast
13
12
  Classifier: Development Status :: 5 - Production/Stable
14
13
  Classifier: Framework :: aiohttp
@@ -36,19 +35,11 @@ Description-Content-Type: text/markdown
36
35
  License-File: LICENSE
37
36
  Requires-Dist: aiohttp==3.9.3
38
37
 
39
- # [python-weather][pypi-url] [![pypi][pypi-image]][pypi-url] [![downloads][downloads-image]][pypi-url] [![Build Status][ci-image]][ci-url] [![Documentation Build Status][doc-ci-image]][doc-ci-url] [![license][github-license-image]][github-license-url] [![BLAZINGLY FAST!!!][blazingly-fast-image]][blazingly-fast-url]
38
+ # [python-weather][pypi-url] [![pypi][pypi-image]][pypi-url] [![downloads][downloads-image]][pypi-url]
40
39
 
41
40
  [pypi-image]: https://img.shields.io/pypi/v/python-weather.svg?style=flat-square
42
41
  [pypi-url]: https://pypi.org/project/python-weather/
43
42
  [downloads-image]: https://img.shields.io/pypi/dm/python-weather?style=flat-square
44
- [ci-image]: https://github.com/null8626/python-weather/workflows/CI/badge.svg
45
- [ci-url]: https://github.com/null8626/python-weather/actions/workflows/CI.yml
46
- [doc-ci-image]: https://readthedocs.org/projects/python-weather/badge/?version=latest
47
- [doc-ci-url]: https://python-weather.readthedocs.io/en/latest/?badge=latest
48
- [github-license-image]: https://img.shields.io/github/license/null8626/python-weather?style=flat-square
49
- [github-license-url]: https://github.com/null8626/python-weather/blob/main/LICENSE
50
- [blazingly-fast-image]: https://img.shields.io/badge/speed-BLAZINGLY%20FAST!!!%20%F0%9F%94%A5%F0%9F%9A%80%F0%9F%92%AA%F0%9F%98%8E-brightgreen.svg?style=flat-square
51
- [blazingly-fast-url]: https://twitter.com/acdlite/status/974390255393505280
52
43
 
53
44
  A free and asynchronous weather Python API wrapper made in Python, for Python.
54
45
 
@@ -76,14 +67,14 @@ async def getweather():
76
67
  weather = await client.get('New York')
77
68
 
78
69
  # returns the current day's forecast temperature (int)
79
- print(weather.current.temperature)
70
+ print(weather.temperature)
80
71
 
81
72
  # get the weather forecast for a few days
82
- for forecast in weather.forecasts:
83
- print(forecast)
73
+ for daily in weather.daily_forecasts:
74
+ print(daily)
84
75
 
85
76
  # hourly forecasts
86
- for hourly in forecast.hourly:
77
+ for hourly in daily.hourly_forecasts:
87
78
  print(f' --> {hourly!r}')
88
79
 
89
80
  if __name__ == '__main__':
@@ -1,14 +0,0 @@
1
- """
2
- python-weather
3
- """
4
-
5
- from .enums import Kind, Locale, Phase, Ultraviolet, WindDirection
6
- from .constants import METRIC, IMPERIAL
7
- from .client import Client
8
- from .errors import Error
9
-
10
- __version__ = '1.0.3'
11
- __all__ = (
12
- 'METRIC', 'IMPERIAL', 'Client', 'Error', 'Kind', 'Locale', 'Phase',
13
- 'Ultraviolet', 'WindDirection'
14
- )
File without changes
File without changes