weathergrabber 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- weathergrabber/__init__.py +0 -0
- weathergrabber/adapter/client/weather_api.py +27 -0
- weathergrabber/adapter/client/weather_search_api.py +50 -0
- weathergrabber/adapter/tty/console_tty.py +102 -0
- weathergrabber/adapter/tty/json_tty.py +19 -0
- weathergrabber/adapter/tty/waybar_tty.py +110 -0
- weathergrabber/cli.py +37 -0
- weathergrabber/core.py +31 -0
- weathergrabber/domain/adapter/icon_enum.py +5 -0
- weathergrabber/domain/adapter/mapper/air_quality_index_mapper.py +13 -0
- weathergrabber/domain/adapter/mapper/city_location_mapper.py +8 -0
- weathergrabber/domain/adapter/mapper/color_mapper.py +10 -0
- weathergrabber/domain/adapter/mapper/current_conditions_mapper.py +16 -0
- weathergrabber/domain/adapter/mapper/daily_predictions_mapper.py +15 -0
- weathergrabber/domain/adapter/mapper/day_night_mapper.py +12 -0
- weathergrabber/domain/adapter/mapper/forecast_mapper.py +20 -0
- weathergrabber/domain/adapter/mapper/health_activities_mapper.py +8 -0
- weathergrabber/domain/adapter/mapper/hourly_predictions_mapper.py +19 -0
- weathergrabber/domain/adapter/mapper/label_value_mapper.py +7 -0
- weathergrabber/domain/adapter/mapper/moon_phase_mapper.py +9 -0
- weathergrabber/domain/adapter/mapper/precipitation_mapper.py +7 -0
- weathergrabber/domain/adapter/mapper/search_mapper.py +7 -0
- weathergrabber/domain/adapter/mapper/sunrise_sunset_mapper.py +13 -0
- weathergrabber/domain/adapter/mapper/temperature_high_low_mapper.py +8 -0
- weathergrabber/domain/adapter/mapper/timestamp_mapper.py +8 -0
- weathergrabber/domain/adapter/mapper/today_details_mapper.py +21 -0
- weathergrabber/domain/adapter/mapper/uv_index_mapper.py +9 -0
- weathergrabber/domain/adapter/mapper/weather_icon_enum_mapper.py +8 -0
- weathergrabber/domain/adapter/mapper/wind_mapper.py +7 -0
- weathergrabber/domain/adapter/output_enum.py +6 -0
- weathergrabber/domain/adapter/params.py +58 -0
- weathergrabber/domain/air_quality_index.py +78 -0
- weathergrabber/domain/city_location.py +37 -0
- weathergrabber/domain/color.py +43 -0
- weathergrabber/domain/current_conditions.py +53 -0
- weathergrabber/domain/daily_predictions.py +58 -0
- weathergrabber/domain/day_night.py +50 -0
- weathergrabber/domain/forecast.py +68 -0
- weathergrabber/domain/health_activities.py +39 -0
- weathergrabber/domain/hourly_predictions.py +76 -0
- weathergrabber/domain/label_value.py +19 -0
- weathergrabber/domain/moon_phase.py +22 -0
- weathergrabber/domain/moon_phase_enum.py +65 -0
- weathergrabber/domain/precipitation.py +20 -0
- weathergrabber/domain/search.py +15 -0
- weathergrabber/domain/sunrise_sunset.py +40 -0
- weathergrabber/domain/temperature_hight_low.py +32 -0
- weathergrabber/domain/timestamp.py +39 -0
- weathergrabber/domain/today_details.py +79 -0
- weathergrabber/domain/uv_index.py +43 -0
- weathergrabber/domain/weather_icon_enum.py +58 -0
- weathergrabber/domain/wind.py +28 -0
- weathergrabber/service/extract_aqi_service.py +30 -0
- weathergrabber/service/extract_current_conditions_service.py +47 -0
- weathergrabber/service/extract_daily_forecast_oldstyle_service.py +51 -0
- weathergrabber/service/extract_daily_forecast_service.py +56 -0
- weathergrabber/service/extract_health_activities_service.py +25 -0
- weathergrabber/service/extract_hourly_forecast_oldstyle_service.py +50 -0
- weathergrabber/service/extract_hourly_forecast_service.py +64 -0
- weathergrabber/service/extract_temperature_service.py +18 -0
- weathergrabber/service/extract_today_details_service.py +85 -0
- weathergrabber/service/read_weather_service.py +23 -0
- weathergrabber/service/search_location_service.py +35 -0
- weathergrabber/usecase/use_case.py +85 -0
- weathergrabber/weathergrabber_application.py +78 -0
- weathergrabber-0.0.1.dist-info/METADATA +176 -0
- weathergrabber-0.0.1.dist-info/RECORD +70 -0
- weathergrabber-0.0.1.dist-info/WHEEL +5 -0
- weathergrabber-0.0.1.dist-info/entry_points.txt +2 -0
- weathergrabber-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
class Precipitation:
|
|
2
|
+
|
|
3
|
+
def __init__(
|
|
4
|
+
self,
|
|
5
|
+
percentage: str,
|
|
6
|
+
amount: str = None
|
|
7
|
+
):
|
|
8
|
+
self._percentage = percentage
|
|
9
|
+
self._amount = amount
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def percentage(self) -> str:
|
|
13
|
+
return self._percentage
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def amount(self) -> str:
|
|
17
|
+
return self._amount
|
|
18
|
+
|
|
19
|
+
def __repr__(self):
|
|
20
|
+
return f"Precipitation(percentage='{self.percentage}', amount='{self.amount}')"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class Search:
|
|
2
|
+
def __init__(self, id: str, search_name: str | None = None):
|
|
3
|
+
self._id = id
|
|
4
|
+
self._search_name = search_name
|
|
5
|
+
|
|
6
|
+
@property
|
|
7
|
+
def id(self) -> str:
|
|
8
|
+
return self._id
|
|
9
|
+
|
|
10
|
+
@property
|
|
11
|
+
def search_name(self) -> str:
|
|
12
|
+
return self._search_name
|
|
13
|
+
|
|
14
|
+
def __repr__(self):
|
|
15
|
+
return f"Search(id={self.id!r}, search_name={self.search_name!r})"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from .weather_icon_enum import WeatherIconEnum
|
|
2
|
+
|
|
3
|
+
class SunriseSunset:
|
|
4
|
+
class IconValue:
|
|
5
|
+
def __init__(self, icon: WeatherIconEnum, value: str):
|
|
6
|
+
self._icon = icon
|
|
7
|
+
self._value = value
|
|
8
|
+
|
|
9
|
+
@property
|
|
10
|
+
def icon(self) -> WeatherIconEnum:
|
|
11
|
+
return self._icon
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def value(self) -> str:
|
|
15
|
+
return self._value
|
|
16
|
+
|
|
17
|
+
def __str__(self):
|
|
18
|
+
return f"{self._icon.emoji_icon} {self._value}"
|
|
19
|
+
|
|
20
|
+
def __repr__(self):
|
|
21
|
+
return f"IconValue(icon={self._icon}, value='{self._value}')"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def __init__(self, sunrise: str, sunset: str):
|
|
25
|
+
self._sunrise = SunriseSunset.IconValue(WeatherIconEnum.SUNRISE, sunrise)
|
|
26
|
+
self._sunset = SunriseSunset.IconValue(WeatherIconEnum.SUNSET, sunset)
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def sunrise(self) -> "SunriseSunset.IconValue":
|
|
30
|
+
return self._sunrise
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def sunset(self) -> "SunriseSunset.IconValue":
|
|
34
|
+
return self._sunset
|
|
35
|
+
|
|
36
|
+
def __str__(self):
|
|
37
|
+
return f"Sunrise: {self._sunrise}, Sunset: {self._sunset}"
|
|
38
|
+
|
|
39
|
+
def __repr__(self):
|
|
40
|
+
return f"SunriseSunset(sunrise={self._sunrise}, sunset={self._sunset})"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
class TemperatureHighLow:
|
|
2
|
+
def __init__(self, high: str, low: str, label:str = None):
|
|
3
|
+
self._high = high
|
|
4
|
+
self._low = low
|
|
5
|
+
self._label = label
|
|
6
|
+
|
|
7
|
+
@classmethod
|
|
8
|
+
def from_string(cls, data: str, label:str = None) -> 'TemperatureHighLow':
|
|
9
|
+
parts = data.split('/')
|
|
10
|
+
if len(parts) == 2:
|
|
11
|
+
high, low = parts
|
|
12
|
+
return cls(high=high.strip(), low=low.strip(), label=label)
|
|
13
|
+
else:
|
|
14
|
+
raise ValueError("Invalid temperature high/low string format")
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def high(self) -> str:
|
|
18
|
+
return self._high
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def low(self) -> str:
|
|
22
|
+
return self._low
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def label(self) -> str:
|
|
26
|
+
return self._label
|
|
27
|
+
|
|
28
|
+
def __repr__(self):
|
|
29
|
+
return f"TemperatureHighLow(high={self.high!r}, low={self.low!r}, label={self.label!r})"
|
|
30
|
+
|
|
31
|
+
def __str__(self):
|
|
32
|
+
return f"{self.high}/{self.low}"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
class Timestamp:
|
|
4
|
+
|
|
5
|
+
def __init__(self, time: str, gmt: str, text: str = None):
|
|
6
|
+
self._time = time
|
|
7
|
+
self._gmt = gmt
|
|
8
|
+
self._text = text
|
|
9
|
+
|
|
10
|
+
@property
|
|
11
|
+
def time(self) -> str:
|
|
12
|
+
return self._time
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def gmt(self) -> str:
|
|
16
|
+
return self._gmt
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def text(self) -> str:
|
|
20
|
+
return self._text
|
|
21
|
+
|
|
22
|
+
def __repr__(self):
|
|
23
|
+
return f"Timestamp(time='{self.time}', gmt='{self.gmt}', text='{self.text}')"
|
|
24
|
+
|
|
25
|
+
def __str__(self):
|
|
26
|
+
if self.text != None:
|
|
27
|
+
return self.text
|
|
28
|
+
return f"As of {self.time} {self.gmt}"
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def from_string(cls, text) -> "Timestamp":
|
|
32
|
+
# "As of 4:23 pm GMT-03:00",
|
|
33
|
+
# "As of 16:37 GMT-03:00",
|
|
34
|
+
# "Até 16:38 GMT-03:00"
|
|
35
|
+
pattern = re.compile(r'(\d{1,2}:\d{2}(?: ?[ap]m)?)\s+(GMT[+-]\d{2}:\d{2})')
|
|
36
|
+
match = pattern.search(text)
|
|
37
|
+
if match:
|
|
38
|
+
time, gmt = match.groups()
|
|
39
|
+
return cls(time=time, gmt=gmt, text=text)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from .temperature_hight_low import TemperatureHighLow
|
|
2
|
+
from .label_value import LabelValue
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from .uv_index import UVIndex
|
|
5
|
+
from .moon_phase import MoonPhase
|
|
6
|
+
from .sunrise_sunset import SunriseSunset
|
|
7
|
+
|
|
8
|
+
class TodayDetails:
|
|
9
|
+
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
feelslike: LabelValue,
|
|
13
|
+
sunrise_sunset: Optional["SunriseSunset"],
|
|
14
|
+
high_low: Optional[TemperatureHighLow],
|
|
15
|
+
wind: LabelValue,
|
|
16
|
+
humidity: LabelValue,
|
|
17
|
+
dew_point: LabelValue,
|
|
18
|
+
pressure: LabelValue,
|
|
19
|
+
uv_index: Optional[UVIndex],
|
|
20
|
+
visibility: LabelValue,
|
|
21
|
+
moon_phase: Optional[MoonPhase]
|
|
22
|
+
):
|
|
23
|
+
self._feelslike = feelslike
|
|
24
|
+
self._sunrise_sunset = sunrise_sunset
|
|
25
|
+
self._high_low = high_low
|
|
26
|
+
self._wind = wind
|
|
27
|
+
self._humidity = humidity
|
|
28
|
+
self._dew_point = dew_point
|
|
29
|
+
self._pressure = pressure
|
|
30
|
+
self._uv_index = uv_index
|
|
31
|
+
self._visibility = visibility
|
|
32
|
+
self._moon_phase = moon_phase
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def feelslike(self) -> LabelValue:
|
|
36
|
+
return self._feelslike
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def sunrise_sunset(self) -> Optional[SunriseSunset]:
|
|
40
|
+
return self._sunrise_sunset
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def high_low(self) -> Optional[TemperatureHighLow]:
|
|
44
|
+
return self._high_low
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def wind(self) -> LabelValue:
|
|
48
|
+
return self._wind
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def humidity(self) -> LabelValue:
|
|
52
|
+
return self._humidity
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def dew_point(self) -> LabelValue:
|
|
56
|
+
return self._dew_point
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def pressure(self) -> LabelValue:
|
|
60
|
+
return self._pressure
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def uv_index(self) -> Optional[UVIndex]:
|
|
64
|
+
return self._uv_index
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def visibility(self) -> LabelValue:
|
|
68
|
+
return self._visibility
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def moon_phase(self) -> Optional[MoonPhase]:
|
|
72
|
+
return self._moon_phase
|
|
73
|
+
|
|
74
|
+
def __repr__(self):
|
|
75
|
+
return (f"TodayDetails(feelslike={self.feelslike!r}, sunrise_sunset={self.sunrise_sunset!r},"
|
|
76
|
+
f"high_low={self.high_low!r}, wind={self.wind!r}, "
|
|
77
|
+
f"humidity={self.humidity!r}, dew_point={self.dew_point!r}, "
|
|
78
|
+
f"pressure={self.pressure!r}, uv_index={self.uv_index!r}, "
|
|
79
|
+
f"visibility={self.visibility!r}, moon_phase={self.moon_phase!r})")
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
class UVIndex:
|
|
2
|
+
|
|
3
|
+
def __init__(self, string_value: str, index: str, of: str = None, label:str = None):
|
|
4
|
+
self._string_value = string_value
|
|
5
|
+
self._index = index
|
|
6
|
+
self._of = of
|
|
7
|
+
self._label = label
|
|
8
|
+
|
|
9
|
+
@property
|
|
10
|
+
def string_value(self) -> str:
|
|
11
|
+
return self._string_value
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def index(self) -> str:
|
|
15
|
+
return self._index
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def of(self) -> str:
|
|
19
|
+
return self._of
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def label(self) -> str:
|
|
23
|
+
return self._label
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def from_string(cls, data: str, label: str = None) -> 'UVIndex':
|
|
27
|
+
parts = data.split(' ')
|
|
28
|
+
if len(parts) == 1:
|
|
29
|
+
return cls(string_value = data, index= parts[0].strip(), of="", label=label)
|
|
30
|
+
elif len(parts) == 3:
|
|
31
|
+
index, of, some = parts
|
|
32
|
+
return cls(string_value = data, index=index.strip(), of=some.strip(), label=label)
|
|
33
|
+
else:
|
|
34
|
+
raise ValueError(f"Cannot parse UV Index from string: {data}")
|
|
35
|
+
|
|
36
|
+
def __repr__(self) -> str:
|
|
37
|
+
return f"UVIndex(string_value={self.string_value!r}, index={self.index!r}, of={self.of!r}, label={self.label!r})"
|
|
38
|
+
|
|
39
|
+
def __str__(self) -> str:
|
|
40
|
+
if self.string_value:
|
|
41
|
+
return f"{self.label}: {self.string_value}"
|
|
42
|
+
elif self.index and self.of:
|
|
43
|
+
return f"{self.label}: {self.index} {self.of}" if self.label else f"{self.index} {self.of}"
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
class WeatherIconEnum(Enum):
|
|
4
|
+
CLEAR = ('clear', chr(0xF0599), '☀️')
|
|
5
|
+
CLEAR_NIGHT = ('clear-night', chr(0xF0594), '🌙')
|
|
6
|
+
CLOUDY = ('cloudy', '\uf0c2', '☁️')
|
|
7
|
+
CLOUDY_FOGGY_DAY = ('cloudy-foggy-day', '\u200B', '\u200B')
|
|
8
|
+
CLOUDY_FOGGY_NIGHT = ('cloudy-foggy-night', '\u200B', '\u200B')
|
|
9
|
+
DAY = ('day', '\uf185', '🌞')
|
|
10
|
+
FEEL = ('feel', '\uf2c9', '🥵')
|
|
11
|
+
HUMIDITY = ('humidity', '\uf043', '💧')
|
|
12
|
+
MOSTLY_CLEAR_DAY = ('mostly-clear-day', chr(0xF0599), '☀️')
|
|
13
|
+
MOSTLY_CLEAR_NIGHT = ('mostly-clear-night', chr(0xF0594), '🌙')
|
|
14
|
+
MOSTLY_CLOUDY_DAY = ('mostly-cloudy-day', chr(0xf013), '☁️')
|
|
15
|
+
MOSTLY_CLOUDY_NIGHT = ('mostly-cloudy-night', chr(0xf013), '☁️')
|
|
16
|
+
NIGHT = ('night', '\uf186', '🌜')
|
|
17
|
+
PARTLY_CLOUDY_DAY = ('partly-cloudy-day', chr(0xF0595), '⛅')
|
|
18
|
+
PARTLY_CLOUDY_NIGHT = ('partly-cloudy-night', chr(0xF0F31), '☁️')
|
|
19
|
+
RAIN = ('rain', '\uf0e9', '🌧️')
|
|
20
|
+
RAINY_DAY = ('rainy-day', chr(0x1F326), '🌧️')
|
|
21
|
+
RAINY_NIGHT = ('rainy-night', chr(0x1F326), '🌧️')
|
|
22
|
+
SCATTERED_SHOWERS_DAY = ('scattered-showers-day', chr(0x1F326), '🌦️')
|
|
23
|
+
SCATTERED_SHOWERS_NIGHT = ('scattered-showers-night', chr(0x1F326), '🌦️')
|
|
24
|
+
SEVERE = ('severe', '\ue317', '🌩️')
|
|
25
|
+
SHOWERS = ('showers', '\u26c6', '🌧️')
|
|
26
|
+
SNOW = ('snow', '\uf2dc', '❄️')
|
|
27
|
+
SNOWY_ICY_DAY = ('snowy-icy-day', '\uf2dc', '❄️')
|
|
28
|
+
SNOWY_ICY_NIGHT = ('snowy-icy-night', '\uf2dc', '❄️')
|
|
29
|
+
SUNNY = ('sunny', chr(0xF0599), '☀️')
|
|
30
|
+
SUNRISE = ('sunrise', '\ue34c', '🌅')
|
|
31
|
+
SUNSET = ('sunset', '\ue34d', '🌇')
|
|
32
|
+
THUNDERSTORM = ('thunderstorm', '\uf0e7', '⛈️')
|
|
33
|
+
VISIBILITY = ('visibility', '\uf06e', '👁️')
|
|
34
|
+
WIND = ('wind', chr(0xf059d), '🌪️')
|
|
35
|
+
|
|
36
|
+
def __init__(self, name: str, fa_icon: str, emoji_icon: str):
|
|
37
|
+
self._name = name
|
|
38
|
+
self._fa_icon = fa_icon
|
|
39
|
+
self._emoji_icon = emoji_icon
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def name(self):
|
|
43
|
+
return self._name
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def fa_icon(self):
|
|
47
|
+
return self._fa_icon
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def emoji_icon(self):
|
|
51
|
+
return self._emoji_icon
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def from_name(name: str):
|
|
55
|
+
for item in WeatherIconEnum:
|
|
56
|
+
if item._name == name:
|
|
57
|
+
return item
|
|
58
|
+
return None
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
class Wind:
|
|
2
|
+
|
|
3
|
+
def __init__(self,direction: str,speed: str):
|
|
4
|
+
self._direction = direction
|
|
5
|
+
self._speed = speed
|
|
6
|
+
|
|
7
|
+
@property
|
|
8
|
+
def direction(self) -> str:
|
|
9
|
+
return self._direction
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def speed(self) -> str:
|
|
13
|
+
return self._speed
|
|
14
|
+
|
|
15
|
+
def __str__(self):
|
|
16
|
+
return f"Wind Speed: {self.direction} {self.speed}"
|
|
17
|
+
|
|
18
|
+
def __repr__(self):
|
|
19
|
+
return f"Wind(direction:'{self.direction}', speed: '{self.speed}')"
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def from_string(cls, data: str) -> 'Wind':
|
|
23
|
+
parts = data.split(" ")
|
|
24
|
+
if len(parts) == 2:
|
|
25
|
+
direction, speed = parts
|
|
26
|
+
return cls(direction=direction.strip(), speed=speed.strip())
|
|
27
|
+
else:
|
|
28
|
+
raise ValueError("Invalid Wind Speed string format")
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from weathergrabber.domain.air_quality_index import AirQualityIndex
|
|
3
|
+
from pyquery import PyQuery
|
|
4
|
+
|
|
5
|
+
class ExtractAQIService:
|
|
6
|
+
|
|
7
|
+
def __init__(self):
|
|
8
|
+
self.logger = logging.getLogger(__name__)
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
def execute(self, weather_data: PyQuery) -> AirQualityIndex | None:
|
|
12
|
+
|
|
13
|
+
self.logger.debug("Extracting Air Quality Index (AQI)...")
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
# 'Air Quality Index\n27\nGood\nAir quality is considered satisfactory, and air pollution poses little or no risk.\nSee Details\nInfo'
|
|
17
|
+
aqi_data = weather_data("section[data-testid='AirQualityModule']").text()
|
|
18
|
+
|
|
19
|
+
# 'stroke-width:5;stroke-dasharray:10.021680564951442 172.78759594743863;stroke:#00E838'
|
|
20
|
+
color_data = weather_data("section[data-testid='AirQualityModule'] svg[data-testid='DonutChart'] circle:nth-of-type(2)").attr("style")
|
|
21
|
+
|
|
22
|
+
air_quality_index = AirQualityIndex.aqi_color_from_string(aqi_data,color_data)
|
|
23
|
+
|
|
24
|
+
self.logger.debug(f"Extracted AQI data: {air_quality_index}")
|
|
25
|
+
|
|
26
|
+
return air_quality_index
|
|
27
|
+
except Exception as e:
|
|
28
|
+
self.logger.error(f"Error extracting AQI data: {e}")
|
|
29
|
+
raise ValueError("Could not extract AQI data") from e
|
|
30
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pyquery import PyQuery
|
|
3
|
+
from weathergrabber.domain.weather_icon_enum import WeatherIconEnum
|
|
4
|
+
from weathergrabber.domain.city_location import CityLocation
|
|
5
|
+
from weathergrabber.domain.timestamp import Timestamp
|
|
6
|
+
from weathergrabber.domain.day_night import DayNight
|
|
7
|
+
from weathergrabber.domain.current_conditions import CurrentConditions
|
|
8
|
+
|
|
9
|
+
class ExtractCurrentConditionsService:
|
|
10
|
+
def __init__(self):
|
|
11
|
+
self.logger = logging.getLogger(__name__)
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
def execute(self, weather_data: PyQuery) -> dict:
|
|
15
|
+
try:
|
|
16
|
+
self.logger.debug("Extracting Current Conditions")
|
|
17
|
+
|
|
18
|
+
data = PyQuery(weather_data('div[data-testid="CurrentConditionsContainer"]'))
|
|
19
|
+
|
|
20
|
+
city_location_data = data.find('h1').text() #'Nova Friburgo, Rio de Janeiro, Brazil'
|
|
21
|
+
timestamp_data = data.find('span[class*="timestamp"]').text() # 'As of 8:01 pm GMT-03:00'
|
|
22
|
+
icon_data = data.find('svg[class*="wxIcon"]').attr('name') # 'partly-cloudy-night'
|
|
23
|
+
temp_day_night = data.find('div[class*="tempHiLoValue"]').text() #'Day\xa063°\xa0•\xa0Night\xa046°'
|
|
24
|
+
|
|
25
|
+
city_location = CityLocation.from_string(city_location_data)
|
|
26
|
+
timestamp = Timestamp.from_string(timestamp_data)
|
|
27
|
+
temperature = data.find('span[class*="tempValue"]').text() # '48°'
|
|
28
|
+
icon = WeatherIconEnum.from_name(icon_data)
|
|
29
|
+
phrase = data.find('div[data-testid*="wxPhrase"]').text() # 'Partly Cloudy'
|
|
30
|
+
day_night = DayNight.from_string(temp_day_night)
|
|
31
|
+
|
|
32
|
+
current_conditions = CurrentConditions(
|
|
33
|
+
location=city_location,
|
|
34
|
+
timestamp=timestamp,
|
|
35
|
+
temperature=temperature,
|
|
36
|
+
icon=icon,
|
|
37
|
+
summary=phrase,
|
|
38
|
+
day_night=day_night
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
self.logger.debug(f"Extracted current conditions: {current_conditions}")
|
|
42
|
+
|
|
43
|
+
return current_conditions
|
|
44
|
+
|
|
45
|
+
except Exception as e:
|
|
46
|
+
self.logger.error(f"Error extracting current conditions: {e}")
|
|
47
|
+
raise ValueError("Could not extract current conditions") from e
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pyquery import PyQuery
|
|
3
|
+
from weathergrabber.domain.daily_predictions import DailyPredictions
|
|
4
|
+
from weathergrabber.domain.temperature_hight_low import TemperatureHighLow
|
|
5
|
+
from weathergrabber.domain.weather_icon_enum import WeatherIconEnum
|
|
6
|
+
from weathergrabber.domain.precipitation import Precipitation
|
|
7
|
+
from typing import List
|
|
8
|
+
|
|
9
|
+
class ExtractDailyForecastOldstyleService:
|
|
10
|
+
def __init__(self):
|
|
11
|
+
self.logger = logging.getLogger(__name__)
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
def execute(self, weather_data: PyQuery) -> List[DailyPredictions]:
|
|
15
|
+
self.logger.debug("Extracting daily forecast, oldstyle...")
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
|
|
19
|
+
data = weather_data.find("div[class*='DailyWeatherCard'] > ul li")
|
|
20
|
+
|
|
21
|
+
if len(data) == 0:
|
|
22
|
+
raise ValueError("Unable to extract hourly forecast using old style")
|
|
23
|
+
|
|
24
|
+
details = [ {
|
|
25
|
+
"title": PyQuery(item).find("h3 > span").text(),
|
|
26
|
+
"high-low" : PyQuery(item).find("div[data-testid='SegmentHighTemp']").text(),
|
|
27
|
+
"icon" : PyQuery(item).find("svg").attr("name"),
|
|
28
|
+
"summary" : PyQuery(item).find("span[class*='Column--iconPhrase']").text(),
|
|
29
|
+
"precip-percentage" : PyQuery(item).find("div[data-testid='SegmentPrecipPercentage'] span[class*='Column--precip']").contents().eq(1).text()
|
|
30
|
+
} for item in data ]
|
|
31
|
+
|
|
32
|
+
self.logger.debug("Extracted %s register(s)...",len(details))
|
|
33
|
+
|
|
34
|
+
daily_predictions = [DailyPredictions(
|
|
35
|
+
title=item["title"],
|
|
36
|
+
high_low=TemperatureHighLow.from_string(item["high-low"]),
|
|
37
|
+
icon=WeatherIconEnum.from_name(item["icon"]),
|
|
38
|
+
summary=item["summary"],
|
|
39
|
+
precipitation=Precipitation(
|
|
40
|
+
percentage=item["precip-percentage"]
|
|
41
|
+
)
|
|
42
|
+
) for item in details]
|
|
43
|
+
|
|
44
|
+
self.logger.debug("Created daily forecast list with %s registers", len(daily_predictions))
|
|
45
|
+
|
|
46
|
+
return daily_predictions
|
|
47
|
+
|
|
48
|
+
except Exception as e:
|
|
49
|
+
|
|
50
|
+
self.logger.error(f"Error extracting daily forecast: {e}")
|
|
51
|
+
raise ValueError("Could not extract daily forecast.") from e
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pyquery import PyQuery
|
|
3
|
+
from weathergrabber.domain.precipitation import Precipitation
|
|
4
|
+
from weathergrabber.domain.weather_icon_enum import WeatherIconEnum
|
|
5
|
+
from weathergrabber.domain.moon_phase_enum import MoonPhaseEnum
|
|
6
|
+
from weathergrabber.domain.moon_phase import MoonPhase
|
|
7
|
+
from weathergrabber.domain.temperature_hight_low import TemperatureHighLow
|
|
8
|
+
from weathergrabber.domain.daily_predictions import DailyPredictions
|
|
9
|
+
|
|
10
|
+
from typing import List
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ExtractDailyForecastService:
|
|
14
|
+
def __init__(self):
|
|
15
|
+
self.logger = logging.getLogger(__name__)
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
def execute(self, weather_data: PyQuery) -> List[DailyPredictions]:
|
|
19
|
+
try:
|
|
20
|
+
self.logger.debug("Extracting hourly forecast...")
|
|
21
|
+
|
|
22
|
+
data = weather_data.find("section[data-testid='DailyForecast'] div[class*='Card'] details")
|
|
23
|
+
|
|
24
|
+
if len(data) == 0:
|
|
25
|
+
raise ValueError("Unable to extract daily forecast")
|
|
26
|
+
|
|
27
|
+
details = [ {
|
|
28
|
+
"title": PyQuery(item).find("h2[data-testid='daypartName']").text(),
|
|
29
|
+
"high-low" : PyQuery(item).find("div[data-testid='detailsTemperature']").text(),
|
|
30
|
+
"icon" : PyQuery(item).find("svg[class*='DetailsSummary']").attr("name"),
|
|
31
|
+
"summary" : PyQuery(item).find("span[class*='DetailsSummary--wxPhrase']").text(),
|
|
32
|
+
"precip-percentage": PyQuery(item).find("div[data-testid='Precip'] span[data-testid='PercentageValue']").text(),
|
|
33
|
+
"moon-phase-icon": PyQuery(item).find("li[data-testid='MoonphaseSection'] svg[class*='DetailsTable']").attr('name'),
|
|
34
|
+
"moon-phase-value": PyQuery(item).find("li[data-testid='MoonphaseSection'] span[data-testid='moonPhase']").text(),
|
|
35
|
+
} for item in data ]
|
|
36
|
+
|
|
37
|
+
self.logger.debug("Extracted %s register(s)...",len(details))
|
|
38
|
+
|
|
39
|
+
daily_predictions = [
|
|
40
|
+
DailyPredictions(
|
|
41
|
+
title=item["title"],
|
|
42
|
+
high_low = TemperatureHighLow.from_string(item["high-low"]),
|
|
43
|
+
icon = WeatherIconEnum.from_name(item["icon"]),
|
|
44
|
+
summary = item["summary"],
|
|
45
|
+
precipitation = Precipitation(percentage=item["precip-percentage"]),
|
|
46
|
+
moon_phase = MoonPhase(MoonPhaseEnum.from_name(item["moon-phase-icon"]),item["moon-phase-value"])
|
|
47
|
+
) for item in details ]
|
|
48
|
+
|
|
49
|
+
self.logger.debug("Created list of daily predictions with %s items", len(daily_predictions))
|
|
50
|
+
|
|
51
|
+
return daily_predictions
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
except Exception as e:
|
|
55
|
+
self.logger.error(f"Error extracting daily forecast: {e}")
|
|
56
|
+
raise ValueError("Could not extract daily forecast.") from e
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pyquery import PyQuery
|
|
3
|
+
from weathergrabber.domain.health_activities import HealthActivities
|
|
4
|
+
|
|
5
|
+
class ExtractHealthActivitiesService:
|
|
6
|
+
def __init__(self):
|
|
7
|
+
self.logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
def execute(self, weather_data: PyQuery) -> HealthActivities | None:
|
|
10
|
+
|
|
11
|
+
self.logger.debug("Extracting Health & Activities...")
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
# 'Health & Activities\nGrass\nSeasonal Allergies and Pollen Count Forecast\nGrass pollen is low in your area'
|
|
15
|
+
data = weather_data("section[data-testid='HealthAndActivitiesModule']").text()
|
|
16
|
+
|
|
17
|
+
health_activities = HealthActivities.from_text(data) if data else None
|
|
18
|
+
|
|
19
|
+
self.logger.debug(f"Extracted Health & Activities data: {health_activities}")
|
|
20
|
+
|
|
21
|
+
return health_activities
|
|
22
|
+
|
|
23
|
+
except Exception as e:
|
|
24
|
+
self.logger.error(f"Error extracting Health & Activities data: {e}")
|
|
25
|
+
raise ValueError("Could not extract Health & Activities data") from e
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pyquery import PyQuery
|
|
3
|
+
from weathergrabber.domain.hourly_predictions import HourlyPredictions
|
|
4
|
+
from weathergrabber.domain.weather_icon_enum import WeatherIconEnum
|
|
5
|
+
from weathergrabber.domain.precipitation import Precipitation
|
|
6
|
+
from typing import List
|
|
7
|
+
|
|
8
|
+
class ExtractHourlyForecastOldstyleService:
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self.logger = logging.getLogger(__name__)
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
def execute(self, weather_data: PyQuery) -> List[HourlyPredictions]:
|
|
14
|
+
self.logger.debug("Extracting hourly forecast, oldstyle...")
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
|
|
18
|
+
data = weather_data.find("div[class*='TodayWeatherCard'] > ul li")
|
|
19
|
+
|
|
20
|
+
if len(data) == 0:
|
|
21
|
+
raise ValueError("Unable to extract hourly forecast using old style")
|
|
22
|
+
|
|
23
|
+
details = [ {
|
|
24
|
+
"title": PyQuery(item).find("h3 > span").text(),
|
|
25
|
+
"temperature" : PyQuery(item).find("span[data-testid='TemperatureValue']").text(),
|
|
26
|
+
"icon" : PyQuery(item).find("svg").attr("name"),
|
|
27
|
+
"summary" : PyQuery(item).find("span[class*='Column--iconPhrase']").text(),
|
|
28
|
+
"precip-percentage" : PyQuery(item).find("div[data-testid='SegmentPrecipPercentage'] span[class*='Column--precip']").contents().eq(1).text()
|
|
29
|
+
} for item in data ]
|
|
30
|
+
|
|
31
|
+
self.logger.debug("Extracted %s register(s)...",len(details))
|
|
32
|
+
|
|
33
|
+
hourly_forecasts = [HourlyPredictions(
|
|
34
|
+
title=item["title"],
|
|
35
|
+
temperature=item["temperature"],
|
|
36
|
+
icon=WeatherIconEnum.from_name(item["icon"]),
|
|
37
|
+
summary=item["summary"],
|
|
38
|
+
precipitation=Precipitation(
|
|
39
|
+
percentage=item["precip-percentage"]
|
|
40
|
+
)
|
|
41
|
+
) for item in details]
|
|
42
|
+
|
|
43
|
+
self.logger.debug("Created hourly forecast list with %s registers", len(hourly_forecasts))
|
|
44
|
+
|
|
45
|
+
return hourly_forecasts
|
|
46
|
+
|
|
47
|
+
except Exception as e:
|
|
48
|
+
|
|
49
|
+
self.logger.error(f"Error extracting hourly forecast: {e}")
|
|
50
|
+
raise ValueError("Could not extract hourly forecast.") from e
|