pyopenweathermap 0.2.0__tar.gz → 0.2.2__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,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: pyopenweathermap
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: lib for OpenWeatherMap for Home Assistant
5
5
  License: MIT
6
6
  Keywords: homeassistant,owm,openweathermap
@@ -11,7 +11,10 @@ Classifier: License :: OSI Approved :: MIT License
11
11
  Classifier: Programming Language :: Python :: 3
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
14
15
  Requires-Dist: aiohttp (>=3.9.5,<4.0.0)
16
+ Project-URL: Homepage, https://github.com/freekode/pyopenweathermap
17
+ Project-URL: Repository, https://github.com/freekode/pyopenweathermap
15
18
  Description-Content-Type: text/markdown
16
19
 
17
20
  # pyopenweathermap
@@ -1,10 +1,12 @@
1
1
  [tool.poetry]
2
2
  name = "pyopenweathermap"
3
- version = "0.2.0"
3
+ version = "0.2.2"
4
4
  description = "lib for OpenWeatherMap for Home Assistant"
5
5
  authors = ["Evgeny <iam@freekode.org>"]
6
6
  license = "MIT"
7
7
  readme = "README.md"
8
+ homepage = "https://github.com/freekode/pyopenweathermap"
9
+ repository = "https://github.com/freekode/pyopenweathermap"
8
10
  keywords = ["homeassistant", "owm", "openweathermap"]
9
11
  packages = [
10
12
  { include = "pyopenweathermap", from = "src" },
@@ -6,5 +6,6 @@ from .exception import (
6
6
  from .client.owm_abstract_client import OWMClient
7
7
  from .client.owm_client_factory import create_owm_client
8
8
  from .weather import (
9
- CurrentWeather, HourlyWeatherForecast, DailyWeatherForecast, WeatherReport, DailyTemperature, WeatherCondition
9
+ CurrentWeather, MinutelyWeatherForecast, HourlyWeatherForecast, DailyWeatherForecast, WeatherReport, DailyTemperature, WeatherCondition
10
10
  )
11
+ from .air_pollution import CurrentAirPollution, AirPollutionReport
@@ -0,0 +1,21 @@
1
+ import decimal
2
+ from dataclasses import dataclass
3
+ from datetime import datetime
4
+
5
+ @dataclass
6
+ class CurrentAirPollution:
7
+ date_time: datetime
8
+ aqi: int
9
+ co: decimal.Decimal
10
+ no: decimal.Decimal
11
+ no2: decimal.Decimal
12
+ o3: decimal.Decimal
13
+ so2: decimal.Decimal
14
+ pm2_5: decimal.Decimal
15
+ pm10: decimal.Decimal
16
+ nh3: decimal.Decimal
17
+
18
+ @dataclass
19
+ class AirPollutionReport:
20
+ current: CurrentAirPollution | None
21
+ hourly_forecast: list[CurrentAirPollution]
@@ -0,0 +1,42 @@
1
+ from .owm_abstract_client import OWMClient
2
+ from ..data_converter import DataConverter
3
+ from ..exception import UnauthorizedError
4
+ from ..weather import WeatherReport
5
+ from ..air_pollution import AirPollutionReport
6
+
7
+ CURRENT_AIR_POLLUTION_API_URL = 'https://api.openweathermap.org/data/2.5/air_pollution'
8
+ FORECAST_API_URL = 'https://api.openweathermap.org/data/2.5/air_pollution/forecast'
9
+
10
+ class OWMAirPollutionClient(OWMClient):
11
+ def __init__(self, api_key):
12
+ super().__init__()
13
+ self.api_key = api_key
14
+
15
+ async def get_air_pollution(self, lat, lon) -> AirPollutionReport:
16
+ url = self._get_url(lat, lon, 'current')
17
+ current_json = await self.http_client.request(url)
18
+ current = DataConverter.air_pollution_current(current_json)
19
+
20
+ url = self._get_url(lat, lon, 'forecast')
21
+ forecast_json = await self.http_client.request(url)
22
+ hourly = [DataConverter.air_pollution_hourly(item) for item in forecast_json['list']]
23
+
24
+ return AirPollutionReport(current, hourly)
25
+
26
+ async def get_weather(self, lat, lon) -> WeatherReport:
27
+ raise NotImplementedError('Air Pollution client does not have get_weather()')
28
+
29
+ async def validate_key(self) -> bool:
30
+ url = self._get_url(50.06, 14.44, 'api_type')
31
+ try:
32
+ await self.http_client.request(url)
33
+ return True
34
+ except UnauthorizedError:
35
+ return False
36
+
37
+ def _get_url(self, lat, lon, api_type):
38
+ url = CURRENT_AIR_POLLUTION_API_URL if api_type == 'current' else FORECAST_API_URL
39
+ return (f"{url}?"
40
+ f"lat={lat}&"
41
+ f"lon={lon}&"
42
+ f"appid={self.api_key}")
@@ -2,6 +2,7 @@ from .owm_abstract_client import OWMClient
2
2
  from ..data_converter import DataConverter
3
3
  from ..exception import UnauthorizedError
4
4
  from ..weather import WeatherReport
5
+ from ..air_pollution import AirPollutionReport
5
6
 
6
7
  CURRENT_WEATHER_API_URL = 'https://api.openweathermap.org/data/2.5/weather'
7
8
  FORECAST_API_URL = 'https://api.openweathermap.org/data/2.5/forecast'
@@ -26,6 +27,9 @@ class OWMFreeClient(OWMClient):
26
27
  hourly = [DataConverter.free_to_hourly_weather_forecast(item) for item in json_response['list']]
27
28
  return WeatherReport(current,[], hourly, [])
28
29
 
30
+ async def get_air_pollution(self, lat, lon) -> AirPollutionReport:
31
+ raise NotImplementedError('free client does not have get_air_pollution()')
32
+
29
33
  async def validate_key(self) -> bool:
30
34
  url = self._get_url(50.06, 14.44)
31
35
  try:
@@ -2,9 +2,9 @@ from .owm_abstract_client import OWMClient
2
2
  from ..data_converter import DataConverter
3
3
  from ..exception import UnauthorizedError
4
4
  from ..weather import WeatherReport
5
+ from ..air_pollution import AirPollutionReport
5
6
 
6
- V30_API_URL = 'https://api.openweathermap.org/data/3.0/onecall'
7
- V25_API_URL = 'https://api.openweathermap.org/data/2.5/onecall'
7
+ API_URL = 'https://api.openweathermap.org/data/3.0/onecall'
8
8
 
9
9
 
10
10
  class OWMOneCallClient(OWMClient):
@@ -19,7 +19,7 @@ class OWMOneCallClient(OWMClient):
19
19
  url = self._get_url(lat, lon)
20
20
  json_response = await self.http_client.request(url)
21
21
 
22
- current, hourly, daily = None, [], []
22
+ current, minutely, hourly, daily = None, [], [], []
23
23
  if json_response.get('current') is not None:
24
24
  current = DataConverter.onecall_to_current_weather(json_response['current'])
25
25
  if json_response.get('minutely') is not None:
@@ -31,6 +31,9 @@ class OWMOneCallClient(OWMClient):
31
31
 
32
32
  return WeatherReport(current, minutely, hourly, daily)
33
33
 
34
+ async def get_air_pollution(self, lat, lon) -> AirPollutionReport:
35
+ raise NotImplementedError('free client does not have get_air_pollution()')
36
+
34
37
  async def validate_key(self) -> bool:
35
38
  url = (f"{self._get_url(50.06, 14.44)}"
36
39
  f"&exclude=current,minutely,hourly,daily,alerts)")
@@ -41,8 +44,7 @@ class OWMOneCallClient(OWMClient):
41
44
  return False
42
45
 
43
46
  def _get_url(self, lat, lon):
44
- url = V30_API_URL if self.api_version == 'v3.0' else V25_API_URL
45
- return (f"{url}?"
47
+ return (f"{API_URL}?"
46
48
  f"lat={lat}&"
47
49
  f"lon={lon}&"
48
50
  f"appid={self.api_key}&"
@@ -1,5 +1,6 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from ..weather import WeatherReport
3
+ from ..air_pollution import AirPollutionReport
3
4
  from ..http_client import HttpClient
4
5
 
5
6
 
@@ -13,6 +14,10 @@ class OWMClient(ABC):
13
14
  async def get_weather(self, lat, lon) -> WeatherReport:
14
15
  pass
15
16
 
17
+ @abstractmethod
18
+ async def get_air_pollution(self, lat, lon) -> AirPollutionReport:
19
+ pass
20
+
16
21
  @abstractmethod
17
22
  async def validate_key(self) -> bool:
18
23
  pass
@@ -2,15 +2,18 @@ import logging
2
2
 
3
3
  from .free_client import OWMFreeClient
4
4
  from .onecall_client import OWMOneCallClient
5
+ from .air_pollution_client import OWMAirPollutionClient
5
6
 
6
7
 
7
8
  def create_owm_client(api_key, api_type, units="metric", lang='en'):
8
9
  logger = logging.getLogger(__name__)
9
10
 
10
11
  logger.info('Initializing OWMClient with api type: ' + str(api_type))
11
- if api_type == 'v3.0' or api_type == 'v2.5':
12
+ if api_type == 'v3.0':
12
13
  return OWMOneCallClient(api_key, api_type, units, lang)
13
14
  if api_type == 'current' or api_type == 'forecast':
14
15
  return OWMFreeClient(api_key, api_type, units, lang)
16
+ if api_type == 'air_pollution':
17
+ return OWMAirPollutionClient(api_key)
15
18
  else:
16
19
  raise Exception('Unsupported API type ' + str(api_type))
@@ -1,6 +1,7 @@
1
1
  from datetime import UTC, datetime
2
2
 
3
3
  from .weather import CurrentWeather, WeatherCondition, MinutelyWeatherForecast, HourlyWeatherForecast, DailyWeatherForecast, DailyTemperature
4
+ from .air_pollution import CurrentAirPollution
4
5
 
5
6
 
6
7
  class DataConverter:
@@ -72,7 +73,40 @@ class DataConverter:
72
73
  snow=json.get('snow', 0),
73
74
  condition=DataConverter._to_weather_condition(json['weather'][0]),
74
75
  )
75
-
76
+
77
+ @staticmethod
78
+ def air_pollution_current(json):
79
+ data = json['list'][0]
80
+ components = data['components']
81
+ return CurrentAirPollution(
82
+ date_time=datetime.fromtimestamp(data["dt"], tz=UTC),
83
+ aqi=data['main']['aqi'],
84
+ co=components['co'],
85
+ no=components['no'],
86
+ no2=components['no2'],
87
+ o3=components['o3'],
88
+ so2=components['so2'],
89
+ pm2_5=components['pm2_5'],
90
+ pm10=components['pm10'],
91
+ nh3=components['nh3'],
92
+ )
93
+
94
+ @staticmethod
95
+ def air_pollution_hourly(json):
96
+ components = json['components']
97
+ return CurrentAirPollution(
98
+ date_time=datetime.fromtimestamp(json["dt"], tz=UTC),
99
+ aqi=json['main']['aqi'],
100
+ co=components['co'],
101
+ no=components['no'],
102
+ no2=components['no2'],
103
+ o3=components['o3'],
104
+ so2=components['so2'],
105
+ pm2_5=components['pm2_5'],
106
+ pm10=components['pm10'],
107
+ nh3=components['nh3'],
108
+ )
109
+
76
110
  @staticmethod
77
111
  def free_to_current_weather(json):
78
112
  return CurrentWeather(