python-weather 1.1.2__tar.gz → 2.0.1__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 (19) hide show
  1. {python-weather-1.1.2 → python-weather-2.0.1}/MANIFEST.in +2 -1
  2. {python-weather-1.1.2/python_weather.egg-info → python-weather-2.0.1}/PKG-INFO +10 -19
  3. {python-weather-1.1.2 → python-weather-2.0.1}/README.md +5 -13
  4. {python-weather-1.1.2 → python-weather-2.0.1}/pyproject.toml +5 -6
  5. python-weather-2.0.1/python_weather/__init__.py +18 -0
  6. {python-weather-1.1.2 → python-weather-2.0.1}/python_weather/base.py +59 -63
  7. {python-weather-1.1.2 → python-weather-2.0.1}/python_weather/client.py +29 -28
  8. {python-weather-1.1.2 → python-weather-2.0.1}/python_weather/constants.py +11 -5
  9. {python-weather-1.1.2 → python-weather-2.0.1}/python_weather/enums.py +139 -95
  10. {python-weather-1.1.2 → python-weather-2.0.1}/python_weather/errors.py +2 -2
  11. {python-weather-1.1.2 → python-weather-2.0.1}/python_weather/forecast.py +189 -257
  12. {python-weather-1.1.2 → python-weather-2.0.1/python_weather.egg-info}/PKG-INFO +10 -19
  13. python-weather-1.1.2/python_weather/__init__.py +0 -14
  14. {python-weather-1.1.2 → python-weather-2.0.1}/LICENSE +0 -0
  15. {python-weather-1.1.2 → python-weather-2.0.1}/python_weather.egg-info/SOURCES.txt +0 -0
  16. {python-weather-1.1.2 → python-weather-2.0.1}/python_weather.egg-info/dependency_links.txt +0 -0
  17. {python-weather-1.1.2 → python-weather-2.0.1}/python_weather.egg-info/requires.txt +0 -0
  18. {python-weather-1.1.2 → python-weather-2.0.1}/python_weather.egg-info/top_level.txt +0 -0
  19. {python-weather-1.1.2 → python-weather-2.0.1}/setup.cfg +0 -0
@@ -1,9 +1,10 @@
1
+ prune .ruff_cache
1
2
  prune .github
2
3
  prune docs
3
4
  exclude .gitattributes
4
5
  exclude .gitignore
5
6
  exclude .readthedocs.yml
6
- exclude .style.yapf
7
+ exclude ruff.toml
7
8
  exclude LICENSE
8
9
  exclude changelog.yml
9
10
  exclude test.py
@@ -1,14 +1,13 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-weather
3
- Version: 1.1.2
3
+ Version: 2.0.1
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.1"
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,18 @@
1
+ from .enums import HeatIndex, Kind, Locale, Phase, UltraViolet, WindDirection
2
+ from .constants import METRIC, IMPERIAL
3
+ from .client import Client
4
+ from .errors import Error
5
+
6
+ __version__ = '2.0.1'
7
+ __all__ = (
8
+ 'METRIC',
9
+ 'IMPERIAL',
10
+ 'Client',
11
+ 'Error',
12
+ 'HeatIndex',
13
+ 'Kind',
14
+ 'Locale',
15
+ 'Phase',
16
+ 'UltraViolet',
17
+ 'WindDirection',
18
+ )
@@ -1,141 +1,137 @@
1
- from enum import auto, Enum
1
+ from enum import auto
2
2
 
3
- from .enums import WindDirection, Kind, Locale, Ultraviolet
3
+ from .errors import Error
4
+ from .enums import WindDirection, Kind, Locale, UltraViolet
4
5
  from .constants import _Unit
5
6
 
7
+
6
8
  class CustomizableBase:
7
9
  __slots__ = ('__unit', '__locale')
8
-
10
+
9
11
  def __init__(self, unit: _Unit, locale: Locale):
10
12
  self.unit = unit
11
13
  self.locale = locale
12
-
14
+
13
15
  @property
14
16
  def unit(self) -> auto:
15
17
  """The measuring unit used to display information in this object."""
16
-
18
+
17
19
  return self.__unit
18
-
20
+
19
21
  @unit.setter
20
22
  def unit(self, to: _Unit):
21
23
  """
22
24
  Sets the default measuring unit used to display information in this object.
23
-
25
+
24
26
  :param to: The new default measuring unit to be used to display information in this object. Must be either ``METRIC`` or ``IMPERIAL``.
25
27
  :exception Error: If the ``to`` argument is not either ``METRIC`` or ``IMPERIAL``.
26
28
  """
27
-
29
+
28
30
  if not isinstance(to, _Unit):
29
31
  raise Error('Invalid measuring unit specified!')
30
-
32
+
31
33
  self.__unit = to
32
-
34
+
33
35
  @property
34
36
  def locale(self) -> Locale:
35
37
  """The localization used to display information in this object."""
36
-
38
+
37
39
  return self.__locale
38
-
40
+
39
41
  @locale.setter
40
42
  def locale(self, to: Locale):
41
43
  """
42
44
  Sets the default localization used to display information in this object.
43
-
45
+
44
46
  :param to: The new :class:`Locale` to be used to display information in this object.
45
47
  :type to: Locale
46
48
  :exception Error: If the ``to`` argument is not a part of the :class:`Locale` enum.
47
49
  """
48
-
50
+
49
51
  if not isinstance(to, Locale):
50
52
  raise Error(f'Expected {to!r} to be a Locale enum')
51
-
53
+
52
54
  self.__locale = to
53
55
 
56
+
54
57
  class BaseForecast(CustomizableBase):
55
58
  __slots__ = ('__inner',)
56
-
59
+
57
60
  def __init__(self, json: dict, unit: _Unit, locale: Locale):
58
61
  self.__inner = json
59
-
62
+
60
63
  super().__init__(unit, locale)
61
-
64
+
62
65
  @property
63
- def ultraviolet(self) -> Ultraviolet:
64
- """The UV (ultraviolet) index."""
65
-
66
- return Ultraviolet._new(int(self.__inner['uvIndex']))
67
-
66
+ def ultraviolet(self) -> UltraViolet:
67
+ """The ultra-violet (UV) index."""
68
+
69
+ return UltraViolet._new(int(self.__inner['uvIndex']))
70
+
68
71
  @property
69
72
  def feels_like(self) -> int:
70
73
  """What it felt like, in Celcius or Fahrenheit."""
71
-
72
- return int(
73
- self.__inner[f'FeelsLike{self._CustomizableBase__unit.temperature}']
74
- )
75
-
74
+
75
+ return int(self.__inner[f'FeelsLike{self._CustomizableBase__unit.temperature}'])
76
+
76
77
  @property
77
78
  def humidity(self) -> int:
78
79
  """The humidity value in percent."""
79
-
80
+
80
81
  return int(self.__inner['humidity'])
81
-
82
+
82
83
  @property
83
84
  def temperature(self) -> int:
84
- """The weather temperature in either Celcius or Fahrenheit."""
85
-
85
+ """The temperature in either Celcius or Fahrenheit."""
86
+
86
87
  return int(self.__inner[f'temp_{self._CustomizableBase__unit.temperature}'])
87
-
88
+
88
89
  @property
89
90
  def precipitation(self) -> float:
90
91
  """The precipitation in either Millimeters or Inches."""
91
-
92
- return float(
93
- self.__inner[f'precip{self._CustomizableBase__unit.precipitation}']
94
- )
95
-
92
+
93
+ return float(self.__inner[f'precip{self._CustomizableBase__unit.precipitation}'])
94
+
96
95
  @property
97
96
  def pressure(self) -> float:
98
97
  """The pressure in either Pascal or Inches."""
99
-
100
- return float(
101
- self.__inner[f'pressure{self._CustomizableBase__unit.pressure}']
102
- )
103
-
98
+
99
+ return float(self.__inner[f'pressure{self._CustomizableBase__unit.pressure}'])
100
+
104
101
  @property
105
102
  def visibility(self) -> int:
106
103
  """The visibility distance in either Kilometers or Miles."""
107
-
108
- return int(
109
- self.__inner[f'visibility{self._CustomizableBase__unit.visibility}']
110
- )
111
-
104
+
105
+ return int(self.__inner[f'visibility{self._CustomizableBase__unit.visibility}'])
106
+
112
107
  @property
113
108
  def wind_speed(self) -> int:
114
109
  """The wind speeds in either Kilometers per hour or Miles per hour."""
115
-
116
- return int(
117
- self.__inner[f'windspeed{self._CustomizableBase__unit.velocity}']
118
- )
119
-
110
+
111
+ return int(self.__inner[f'windspeed{self._CustomizableBase__unit.velocity}'])
112
+
120
113
  @property
121
114
  def wind_direction(self) -> WindDirection:
122
115
  """The wind direction."""
123
-
116
+
124
117
  return WindDirection._new(
125
118
  self.__inner['winddir16Point'], int(self.__inner['winddirDegree'])
126
119
  )
127
-
120
+
128
121
  @property
129
122
  def description(self) -> str:
130
123
  """The description regarding the forecast. This can be localized in different languages depending on the localization used."""
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']
136
-
124
+
125
+ 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']
129
+ )
130
+
131
+ return description.strip()
132
+
137
133
  @property
138
134
  def kind(self) -> Kind:
139
- """The kind of the weather."""
140
-
135
+ """The kind of the forecast."""
136
+
141
137
  return Kind(int(self.__inner['weatherCode']))
@@ -6,104 +6,105 @@ 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
 
13
+
13
14
  class Client(CustomizableBase):
14
15
  """
15
16
  The class that lets you interact with the API.
16
-
17
+
17
18
  :param unit: Whether to use the metric or imperial/customary system (``IMPERIAL``). Defaults to ``METRIC``.
18
19
  :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``.
20
+ :param locale: Whether to use a different locale/language as the description for the returned forecast. Defaults to ``Locale.ENGLISH``.
20
21
  :type locale: Optional[Locale]
21
22
  :param session: Whether to use an existing aiohttp client session for requesting or not. Defaults to ``None`` (creates a new one instead)
22
23
  :type session: Optional[:class:`aiohttp.ClientSession`]
23
-
24
+
24
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.
25
26
  """
26
-
27
+
27
28
  __slots__ = ('__session',)
28
-
29
+
29
30
  def __init__(
30
31
  self,
31
32
  *,
32
33
  unit: Optional[auto] = METRIC,
33
34
  locale: Optional[Locale] = Locale.ENGLISH,
34
- session: Optional[ClientSession] = None
35
+ session: Optional[ClientSession] = None,
35
36
  ):
36
37
  super().__init__(unit, locale)
37
-
38
+
38
39
  self.__session = session or ClientSession(
39
40
  timeout=ClientTimeout(total=5000.0),
40
- connector=TCPConnector(verify_ssl=False)
41
+ connector=TCPConnector(verify_ssl=False),
41
42
  )
42
-
43
+
43
44
  def __repr__(self) -> str:
44
45
  return f'<{self.__class__.__name__} {self.__session!r}>'
45
-
46
+
46
47
  async def get(
47
48
  self,
48
49
  location: str,
49
50
  *,
50
51
  unit: Optional[auto] = None,
51
- locale: Optional[Locale] = None
52
- ) -> Weather:
52
+ locale: Optional[Locale] = None,
53
+ ) -> Forecast:
53
54
  """
54
55
  Fetches a weather forecast for a specific location.
55
-
56
+
56
57
  :param location: The requested location name for said weather forecast.
57
58
  :type location: str
58
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`).
59
60
  :type unit: Optional[:py:class:`enum.auto`]
60
61
  :param locale: Overrides the locale/language used by the :class:`Client` object. Defaults to ``None`` (uses the one from the :class:`Client`).
61
62
  :type locale: Optional[Locale]
62
-
63
+
63
64
  :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
-
65
+
65
66
  :returns: The requested weather forecast.
66
- :rtype: Weather
67
+ :rtype: Forecast
67
68
  """
68
-
69
+
69
70
  if (not isinstance(location, str)) or (not location):
70
71
  raise Error(f'Expected a proper location str, got {location!r}')
71
72
  elif self.__session.closed:
72
73
  raise Error('Client is already closed')
73
-
74
+
74
75
  if not isinstance(unit, _Unit):
75
76
  unit = self._CustomizableBase__unit
76
-
77
+
77
78
  if not isinstance(locale, Locale):
78
79
  locale = self._CustomizableBase__locale
79
-
80
+
80
81
  subdomain = f'{locale.value}.' if locale != Locale.ENGLISH else ''
81
82
  delay = 0
82
-
83
+
83
84
  while True:
84
85
  if delay != 0:
85
86
  await sleep(delay)
86
87
  delay *= 2
87
-
88
+
88
89
  async with self.__session.get(
89
90
  f'https://{subdomain}wttr.in/{quote_plus(location)}?format=j1'
90
91
  ) as resp:
91
92
  try:
92
- return Weather(await resp.json(), unit, locale)
93
+ return Forecast(await resp.json(), unit, locale)
93
94
  except Exception as e:
94
95
  if delay == 4:
95
96
  raise e # okay, that's too much requests - just raise the error
96
97
  elif delay == 0:
97
98
  delay = 0.5
98
-
99
+
99
100
  async def close(self):
100
101
  """Closes the :class:`Client` object. Nothing will happen if it's already closed."""
101
-
102
+
102
103
  if not self.__session.closed:
103
104
  await self.__session.close()
104
-
105
+
105
106
  async def __aenter__(self):
106
107
  return self
107
-
108
+
108
109
  async def __aexit__(self, *_, **__):
109
110
  await self.close()
@@ -1,13 +1,17 @@
1
1
  from typing import Union
2
2
  from re import compile
3
- from enum import auto
3
+
4
4
 
5
5
  class _Unit:
6
6
  __slots__ = (
7
- 'temperature', 'velocity', 'pressure', 'precipitation', 'visibility',
8
- 'cm_divisor'
7
+ 'temperature',
8
+ 'velocity',
9
+ 'pressure',
10
+ 'precipitation',
11
+ 'visibility',
12
+ 'cm_divisor',
9
13
  )
10
-
14
+
11
15
  def __init__(
12
16
  self,
13
17
  temperature: str,
@@ -23,11 +27,13 @@ class _Unit:
23
27
  self.precipitation = precipitation
24
28
  self.visibility = visibility
25
29
  self.cm_divisor = cm_divisor
26
-
30
+
27
31
  def __repr__(self) -> str:
28
32
  return f'<Unit [{self.temperature}, {self.velocity}]>'
29
33
 
34
+
30
35
  METRIC = _Unit('C', 'Kmph', '', 'MM', '', 1)
31
36
  IMPERIAL = _Unit('F', 'Miles', 'Inches', 'Inches', 'Miles', 2.54)
32
37
 
38
+ WIND_DIRECTION_EMOJIS = ['↓', '↙', '←', '↖', '↑', '↗', '→', '↘']
33
39
  LATLON_REGEX = compile(r'^Lat (\-?[\d\.]+) and Lon (\-?[\d\.]+)$')