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,58 @@
|
|
|
1
|
+
from .output_enum import OutputEnum
|
|
2
|
+
from .icon_enum import IconEnum
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
class Params:
|
|
6
|
+
class Location:
|
|
7
|
+
def __init__(self, search_name: str = None, id: str = None):
|
|
8
|
+
self._search_name = search_name
|
|
9
|
+
self._id = id
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def search_name(self) -> str | None:
|
|
13
|
+
return self._search_name
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def id(self) -> str | None:
|
|
17
|
+
return self._id
|
|
18
|
+
|
|
19
|
+
def __str__(self):
|
|
20
|
+
return f"Location(search_name={self.search_name}, id={self.id})"
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
location: Optional["Params.Location"] = None,
|
|
25
|
+
language: str = "en-US",
|
|
26
|
+
output_format: OutputEnum = OutputEnum.CONSOLE,
|
|
27
|
+
keep_open: bool = False,
|
|
28
|
+
icons: IconEnum = IconEnum.EMOJI
|
|
29
|
+
):
|
|
30
|
+
self._location = location
|
|
31
|
+
self._language = language
|
|
32
|
+
self._output_format = output_format
|
|
33
|
+
self._keep_open = keep_open
|
|
34
|
+
self._icons = icons
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def location(self) -> Optional["Params.Location"]:
|
|
38
|
+
return self._location
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def language(self) -> str:
|
|
42
|
+
return self._language
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def output_format(self) -> OutputEnum:
|
|
46
|
+
return self._output_format
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def keep_open(self) -> bool:
|
|
50
|
+
return self._keep_open
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def icons(self) -> IconEnum:
|
|
54
|
+
return self._icons
|
|
55
|
+
|
|
56
|
+
def __str__(self):
|
|
57
|
+
return f"Params(location={self.location}, language={self.language}, output_format={self.output_format}, keep_open={self.keep_open}, icons={self.icons})"
|
|
58
|
+
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from .color import Color
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
class AirQualityIndex:
|
|
5
|
+
def __init__(self,
|
|
6
|
+
title: str,
|
|
7
|
+
value: int,
|
|
8
|
+
category: Optional[str] = None,
|
|
9
|
+
description: Optional[str] = None,
|
|
10
|
+
acronym: Optional[str] = None,
|
|
11
|
+
color: Optional[Color] = None
|
|
12
|
+
):
|
|
13
|
+
self._title = title
|
|
14
|
+
self._value = value
|
|
15
|
+
self._category = category
|
|
16
|
+
self._description = description
|
|
17
|
+
self._acronym = acronym
|
|
18
|
+
self._color = color
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def title(self) -> str:
|
|
22
|
+
return self._title
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def value(self) -> int:
|
|
26
|
+
return self._value
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def category(self) -> str | None:
|
|
30
|
+
return self._category
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def description(self) -> str | None:
|
|
34
|
+
return self._description
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def acronym(self) -> str | None:
|
|
38
|
+
return self._acronym
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def color(self) -> Color | None:
|
|
42
|
+
return self._color
|
|
43
|
+
|
|
44
|
+
def __str__(self) -> str:
|
|
45
|
+
return f"Title: {self.title}. AQI: {self.value}, Category: {self.category}, Description: {self.description}, Acronym: {self.acronym}, Color: {self.color}"
|
|
46
|
+
|
|
47
|
+
def __repr__(self) -> str:
|
|
48
|
+
return f"AirQualityIndex(title={self.title}, value={self.value}, category={self.category}, description={self.description}, acronym:{self.acronym}, color={self.color})"
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def _extract_aqi(data: str):
|
|
52
|
+
parts = data.split('\n')
|
|
53
|
+
title = parts[0].strip()
|
|
54
|
+
aqi = int(parts[1].strip())
|
|
55
|
+
category = parts[2].strip() if len(parts) > 2 else None
|
|
56
|
+
description = parts[3].strip() if len(parts) > 3 else None
|
|
57
|
+
acronym = ''.join(word[0].strip().upper() for word in title.split())
|
|
58
|
+
|
|
59
|
+
return title, aqi, category, description, acronym
|
|
60
|
+
|
|
61
|
+
# 'Air Quality Index\n26\nGood\nAir quality is considered satisfactory, and air pollution poses little or no risk.'
|
|
62
|
+
@classmethod
|
|
63
|
+
def from_string(cls, data: str) -> 'AirQualityIndex':
|
|
64
|
+
try:
|
|
65
|
+
title, aqi, category, description, acronym = AirQualityIndex._extract_aqi(data)
|
|
66
|
+
return cls(title, aqi, category, description, acronym)
|
|
67
|
+
except (ValueError, IndexError) as e:
|
|
68
|
+
raise ValueError("Invalid AQI data format") from e
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
def aqi_color_from_string(cls, aqi_data: str, color_data: str):
|
|
72
|
+
try:
|
|
73
|
+
title, aqi, category, description, acronym = AirQualityIndex._extract_aqi(aqi_data)
|
|
74
|
+
color = Color.from_string(color_data)
|
|
75
|
+
return cls(title, aqi, category, description, acronym, color)
|
|
76
|
+
except(ValueError, IndexError) as e:
|
|
77
|
+
raise ValueError("Invalid AQI data format or color data format") from e
|
|
78
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
class CityLocation:
|
|
2
|
+
|
|
3
|
+
def __init__(self, city: str, state_province: str = None, country: str = None):
|
|
4
|
+
self._city = city
|
|
5
|
+
self._state_province = state_province
|
|
6
|
+
self._country = country
|
|
7
|
+
|
|
8
|
+
@property
|
|
9
|
+
def city(self) -> str:
|
|
10
|
+
return self._city
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def state_province(self) -> str:
|
|
14
|
+
return self._state_province
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def country(self) -> str:
|
|
18
|
+
return self._country
|
|
19
|
+
|
|
20
|
+
def __repr__(self):
|
|
21
|
+
return f"CityLocation(city={self.city}, state_province={self.state_province}, country={self.country})"
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def from_string(cls, data: str) -> "CityLocation":
|
|
25
|
+
#'Nova Friburgo, Rio de Janeiro, Brazil'
|
|
26
|
+
parts = data.split(", ")
|
|
27
|
+
if len(parts) == 3:
|
|
28
|
+
city, state_province, country = parts
|
|
29
|
+
return cls(city=city, state_province=state_province, country=country)
|
|
30
|
+
if len(parts) == 2:
|
|
31
|
+
city, state_province = parts
|
|
32
|
+
return cls(city=city, state_province=state_province)
|
|
33
|
+
elif len(parts) == 1:
|
|
34
|
+
city = parts[0]
|
|
35
|
+
return cls(city=city)
|
|
36
|
+
else:
|
|
37
|
+
raise ValueError("Invalid city location string format")
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
class Color:
|
|
4
|
+
|
|
5
|
+
def __init__(self, red: str, green: str, blue: str):
|
|
6
|
+
self._red = red
|
|
7
|
+
self._green = green
|
|
8
|
+
self._blue = blue
|
|
9
|
+
|
|
10
|
+
@property
|
|
11
|
+
def red(self):
|
|
12
|
+
return self._red
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def green(self):
|
|
16
|
+
return self._green
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def blue(self):
|
|
20
|
+
return self._blue
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def from_string(cls,string_value: str) -> "Color":
|
|
24
|
+
|
|
25
|
+
color_pattern = r"#([0-9A-Fa-f]{6})"
|
|
26
|
+
|
|
27
|
+
match = re.search(color_pattern, string_value)
|
|
28
|
+
color = f"#{match.group(1)}"
|
|
29
|
+
hex_color = color.lstrip('#')
|
|
30
|
+
r, g, b = int(hex_color[:2], 16), int(hex_color[2:4], 16), int(hex_color[4:], 16)
|
|
31
|
+
|
|
32
|
+
return cls(r, g, b)
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def hex(self) -> str:
|
|
36
|
+
return f"#{self.red:02x}{self.green:02x}{self.blue:02x}"
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def rgb(self) -> str:
|
|
40
|
+
return f"rgb({self.red}, {self.green}, {self.blue})"
|
|
41
|
+
|
|
42
|
+
def __repr__(self):
|
|
43
|
+
return f"Color(red='{self.red}', green='{self.green}', blue='{self.blue}')"
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from .city_location import CityLocation
|
|
2
|
+
from .timestamp import Timestamp
|
|
3
|
+
from .weather_icon_enum import WeatherIconEnum
|
|
4
|
+
from .day_night import DayNight
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
class CurrentConditions:
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
location: Optional[CityLocation],
|
|
11
|
+
timestamp: Optional[Timestamp],
|
|
12
|
+
temperature: str,
|
|
13
|
+
icon: Optional[WeatherIconEnum],
|
|
14
|
+
summary: str,
|
|
15
|
+
day_night: Optional[DayNight]
|
|
16
|
+
):
|
|
17
|
+
self._location = location
|
|
18
|
+
self._timestamp = timestamp
|
|
19
|
+
self._temperature = temperature
|
|
20
|
+
self._icon = icon
|
|
21
|
+
self._summary = summary
|
|
22
|
+
self._day_night = day_night
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def location(self) -> Optional[CityLocation]:
|
|
26
|
+
return self._location
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def timestamp(self) -> Optional[Timestamp]:
|
|
30
|
+
return self._timestamp
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def temperature(self) -> str:
|
|
34
|
+
return self._temperature
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def icon(self) -> Optional[WeatherIconEnum]:
|
|
38
|
+
return self._icon
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def summary(self) -> str:
|
|
42
|
+
return self._summary
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def day_night(self) -> Optional[DayNight]:
|
|
46
|
+
return self._day_night
|
|
47
|
+
|
|
48
|
+
def __repr__(self):
|
|
49
|
+
return (
|
|
50
|
+
f"CurrentConditions(location={self._location!r}, timestamp={self._timestamp!r}, "
|
|
51
|
+
f"temperature={self._temperature!r}, icon={self._icon!r}, summary={self._summary!r}, "
|
|
52
|
+
f"day_night={self._day_night!r})"
|
|
53
|
+
)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from .temperature_hight_low import TemperatureHighLow
|
|
2
|
+
from .weather_icon_enum import WeatherIconEnum
|
|
3
|
+
from .moon_phase import MoonPhase
|
|
4
|
+
from .precipitation import Precipitation
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
class DailyPredictions:
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
title: str,
|
|
11
|
+
high_low: Optional[TemperatureHighLow],
|
|
12
|
+
icon: Optional[WeatherIconEnum],
|
|
13
|
+
summary: str,
|
|
14
|
+
precipitation: Optional[Precipitation],
|
|
15
|
+
moon_phase: Optional[MoonPhase] = None
|
|
16
|
+
):
|
|
17
|
+
self._title = title
|
|
18
|
+
self._high_low = high_low
|
|
19
|
+
self._icon = icon
|
|
20
|
+
self._summary = summary
|
|
21
|
+
self._precipitation = precipitation
|
|
22
|
+
self._moon_phase = moon_phase
|
|
23
|
+
|
|
24
|
+
# ---- Properties ----
|
|
25
|
+
@property
|
|
26
|
+
def title(self) -> str:
|
|
27
|
+
return self._title
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def high_low(self) -> Optional[TemperatureHighLow]:
|
|
31
|
+
return self._high_low
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def icon(self) -> Optional[WeatherIconEnum]:
|
|
35
|
+
return self._icon
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def summary(self) -> str:
|
|
39
|
+
return self._summary
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def precipitation(self) -> Optional[Precipitation]:
|
|
43
|
+
return self._precipitation
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def moon_phase(self) -> Optional[MoonPhase]:
|
|
47
|
+
return self._moon_phase
|
|
48
|
+
|
|
49
|
+
def __repr__(self) -> str:
|
|
50
|
+
return (
|
|
51
|
+
f"DailyPredictions("
|
|
52
|
+
f"title={self._title!r}, "
|
|
53
|
+
f"high_low={self._high_low!r}, "
|
|
54
|
+
f"icon={self._icon!r}, "
|
|
55
|
+
f"summary={self._summary!r}, "
|
|
56
|
+
f"precipitation={self._precipitation!r}, "
|
|
57
|
+
f"moon_phase={self._moon_phase!r})"
|
|
58
|
+
)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
class DayNight:
|
|
2
|
+
class Temperature:
|
|
3
|
+
def __init__(self, label: str, value: str):
|
|
4
|
+
self._label = label
|
|
5
|
+
self._value = value
|
|
6
|
+
|
|
7
|
+
@property
|
|
8
|
+
def label(self):
|
|
9
|
+
return self._label
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def value(self):
|
|
13
|
+
return self._value
|
|
14
|
+
|
|
15
|
+
@classmethod
|
|
16
|
+
def from_string(cls, text:str) -> 'DayNight.Temperature':
|
|
17
|
+
label, value = text.split("\xa0")
|
|
18
|
+
return cls(label.strip(), value.strip())
|
|
19
|
+
|
|
20
|
+
def __repr__(self):
|
|
21
|
+
return f"DayNight.Temperature(label={self.label!r}, value={self.value!r})"
|
|
22
|
+
|
|
23
|
+
def __str__(self):
|
|
24
|
+
return f"{self.label}: {self.value}"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def __init__(self, day: "DayNight.Temperature", night: "DayNight.Temperature"):
|
|
28
|
+
self._day = day
|
|
29
|
+
self._night = night
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def day(self):
|
|
33
|
+
return self._day
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def night(self):
|
|
37
|
+
return self._night
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def from_string(cls, text: str) -> 'DayNight':
|
|
41
|
+
day_label, night_label = text.split("•")
|
|
42
|
+
day = DayNight.Temperature.from_string(day_label.strip())
|
|
43
|
+
night = DayNight.Temperature.from_string(night_label.strip())
|
|
44
|
+
return cls(day, night)
|
|
45
|
+
|
|
46
|
+
def __repr__(self):
|
|
47
|
+
return f"DayNight(day={self.day!r}, night={self.night!r})"
|
|
48
|
+
|
|
49
|
+
def __str__(self):
|
|
50
|
+
return f"Day: {self.day}, Night: {self.night}"
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from typing import Optional, List
|
|
2
|
+
from .search import Search
|
|
3
|
+
from .weather_icon_enum import WeatherIconEnum
|
|
4
|
+
from .today_details import TodayDetails
|
|
5
|
+
from .air_quality_index import AirQualityIndex
|
|
6
|
+
from .health_activities import HealthActivities
|
|
7
|
+
from .hourly_predictions import HourlyPredictions
|
|
8
|
+
from .daily_predictions import DailyPredictions
|
|
9
|
+
from .current_conditions import CurrentConditions
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Forecast:
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
search: Optional[Search],
|
|
16
|
+
current_conditions: Optional[CurrentConditions],
|
|
17
|
+
today_details: Optional[TodayDetails],
|
|
18
|
+
air_quality_index: Optional[AirQualityIndex],
|
|
19
|
+
health_activities: Optional[HealthActivities],
|
|
20
|
+
hourly_predictions: List[HourlyPredictions],
|
|
21
|
+
daily_predictions: List[DailyPredictions],
|
|
22
|
+
):
|
|
23
|
+
self._search = search
|
|
24
|
+
self._current_conditions = current_conditions
|
|
25
|
+
self._today_details = today_details
|
|
26
|
+
self._air_quality_index = air_quality_index
|
|
27
|
+
self._health_activities = health_activities
|
|
28
|
+
self._hourly_predictions = hourly_predictions
|
|
29
|
+
self._daily_predictions = daily_predictions
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def search(self) -> Optional[Search]:
|
|
33
|
+
return self._search
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def current_conditions(self) -> Optional[CurrentConditions]:
|
|
37
|
+
return self._current_conditions
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def today_details(self) -> Optional[TodayDetails]:
|
|
41
|
+
return self._today_details
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def air_quality_index(self) -> Optional[AirQualityIndex]:
|
|
45
|
+
return self._air_quality_index
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def health_activities(self) -> Optional[HealthActivities]:
|
|
49
|
+
return self._health_activities
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def hourly_predictions(self) -> List[HourlyPredictions]:
|
|
53
|
+
return self._hourly_predictions
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def daily_predictions(self) -> List[DailyPredictions]:
|
|
57
|
+
return self._daily_predictions
|
|
58
|
+
|
|
59
|
+
def __repr__(self) -> str:
|
|
60
|
+
return (
|
|
61
|
+
f"Forecast(search={self._search}, "
|
|
62
|
+
f"current_conditions={self._current_conditions}, "
|
|
63
|
+
f"today_details={self._today_details}, "
|
|
64
|
+
f"air_quality_index={self._air_quality_index}, "
|
|
65
|
+
f"health_activities={self._health_activities}, "
|
|
66
|
+
f"hourly_predictions={len(self._hourly_predictions)} items, "
|
|
67
|
+
f"daily_predictions={len(self._daily_predictions)} items)"
|
|
68
|
+
)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
class HealthActivities:
|
|
2
|
+
def __init__(self, category_name: str, title: str, description: str):
|
|
3
|
+
self._category_name = category_name
|
|
4
|
+
self._title = title
|
|
5
|
+
self._description = description
|
|
6
|
+
|
|
7
|
+
@property
|
|
8
|
+
def category_name(self) -> str:
|
|
9
|
+
return self._category_name
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def title(self) -> str:
|
|
13
|
+
return self._title
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def description(self) -> str:
|
|
17
|
+
return self._description
|
|
18
|
+
|
|
19
|
+
def __str__(self) -> str:
|
|
20
|
+
return f"{self._category_name}: {self._title} - {self._description}"
|
|
21
|
+
|
|
22
|
+
def __repr__(self) -> str:
|
|
23
|
+
return f"HealthActivities(category_name={self._category_name!r}, title={self._title!r}, description={self._description!r})"
|
|
24
|
+
|
|
25
|
+
# 'Health & Activities\nGrass\nSeasonal Allergies and Pollen Count Forecast\nGrass pollen is low in your area'
|
|
26
|
+
@staticmethod
|
|
27
|
+
def from_text(text: str):
|
|
28
|
+
try:
|
|
29
|
+
lines = text.split('\n')
|
|
30
|
+
if len(lines) >= 4:
|
|
31
|
+
category_name = lines[0].strip()
|
|
32
|
+
#Ignore the "grass" line
|
|
33
|
+
title = lines[2].strip()
|
|
34
|
+
description = ' '.join(line.strip() for line in lines[3:]).strip()
|
|
35
|
+
return HealthActivities(category_name, title, description)
|
|
36
|
+
else:
|
|
37
|
+
raise ValueError("Insufficient data to parse HealthActivities")
|
|
38
|
+
except Exception as e:
|
|
39
|
+
raise ValueError("Could not parse HealthActivities from text") from e
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from .weather_icon_enum import WeatherIconEnum
|
|
2
|
+
from .precipitation import Precipitation
|
|
3
|
+
from .wind import Wind
|
|
4
|
+
from .uv_index import UVIndex
|
|
5
|
+
from typing import Optional
|
|
6
|
+
class HourlyPredictions:
|
|
7
|
+
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
title: str,
|
|
11
|
+
temperature: str,
|
|
12
|
+
icon: WeatherIconEnum,
|
|
13
|
+
summary: str,
|
|
14
|
+
precipitation: Optional[Precipitation],
|
|
15
|
+
wind: Optional[Wind] = None,
|
|
16
|
+
feels_like: str = None,
|
|
17
|
+
humidity: str = None,
|
|
18
|
+
uv_index: Optional[UVIndex] = None,
|
|
19
|
+
cloud_cover: str = None
|
|
20
|
+
):
|
|
21
|
+
self._title = title
|
|
22
|
+
self._temperature = temperature
|
|
23
|
+
self._icon = icon
|
|
24
|
+
self._summary = summary
|
|
25
|
+
self._precipitation = precipitation
|
|
26
|
+
self._wind = wind
|
|
27
|
+
self._feels_like = feels_like
|
|
28
|
+
self._humidity = humidity
|
|
29
|
+
self._uv_index = uv_index
|
|
30
|
+
self._cloud_cover = cloud_cover
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def title(self) -> str:
|
|
34
|
+
return self._title
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def temperature(self) -> str:
|
|
38
|
+
return self._temperature
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def icon(self) -> WeatherIconEnum:
|
|
42
|
+
return self._icon
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def summary(self) -> str:
|
|
46
|
+
return self._summary
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def precipitation(self) -> Optional[Precipitation]:
|
|
50
|
+
return self._precipitation
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def wind(self) -> Optional[Wind]:
|
|
54
|
+
return self._wind
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def feels_like(self) -> str:
|
|
58
|
+
return self._feels_like
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def humidity(self) -> str:
|
|
62
|
+
return self._humidity
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def uv_index(self) -> Optional[UVIndex]:
|
|
66
|
+
return self._uv_index
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def cloud_cover(self) -> str:
|
|
70
|
+
return self._cloud_cover
|
|
71
|
+
|
|
72
|
+
def __repr__(self):
|
|
73
|
+
return (f"HourlyPredictions(title={self._title!r}, temperature={self._temperature!r}, icon={self._icon!r}, "
|
|
74
|
+
f"summary={self._summary!r}, precipitation={self._precipitation!r}, wind={self._wind!r}, "
|
|
75
|
+
f"feels_like={self._feels_like!r}, humidity={self._humidity!r}, uv_index={self._uv_index!r}, "
|
|
76
|
+
f"cloud_cover={self._cloud_cover!r})")
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
class LabelValue:
|
|
2
|
+
def __init__(self, label: str, value: str):
|
|
3
|
+
self._label = label
|
|
4
|
+
self._value = value
|
|
5
|
+
|
|
6
|
+
@property
|
|
7
|
+
def label(self) -> str:
|
|
8
|
+
return self._label
|
|
9
|
+
|
|
10
|
+
@property
|
|
11
|
+
def value(self) -> str:
|
|
12
|
+
return self._value
|
|
13
|
+
|
|
14
|
+
def __repr__(self) -> str:
|
|
15
|
+
return f"LabelValue(label={self.label!r}, value={self.value!r})"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def __str__(self) -> str:
|
|
19
|
+
return f"{self.label} {self.value}"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from weathergrabber.domain.moon_phase_enum import MoonPhaseEnum
|
|
2
|
+
|
|
3
|
+
class MoonPhase:
|
|
4
|
+
def __init__(self, icon: MoonPhaseEnum, phase: str, label:str = None):
|
|
5
|
+
self._label = label
|
|
6
|
+
self._icon = icon
|
|
7
|
+
self._phase = phase
|
|
8
|
+
|
|
9
|
+
@property
|
|
10
|
+
def label(self) -> str:
|
|
11
|
+
return self._label
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def icon(self) -> MoonPhaseEnum:
|
|
15
|
+
return self._icon
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def phase(self) -> str:
|
|
19
|
+
return self._phase
|
|
20
|
+
|
|
21
|
+
def __repr__(self) -> str:
|
|
22
|
+
return f"MoonPhase(icon={self.icon!r}, phase={self.phase!r},label={self.label!r})"
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
class MoonPhaseEnum(Enum):
|
|
4
|
+
# New Moon
|
|
5
|
+
PHASE_0 = ("phase-0", "\uf186", "🌑")
|
|
6
|
+
# Waxing Crescent
|
|
7
|
+
PHASE_1 = ("phase-1", "\uf186", "🌒")
|
|
8
|
+
PHASE_2 = ("phase-2", "\uf186", "🌒")
|
|
9
|
+
PHASE_3 = ("phase-3", "\uf186", "🌒")
|
|
10
|
+
PHASE_4 = ("phase-4", "\uf186", "🌒")
|
|
11
|
+
PHASE_5 = ("phase-5", "\uf186", "🌒")
|
|
12
|
+
PHASE_6 = ("phase-6", "\uf186", "🌒")
|
|
13
|
+
# First Quarter
|
|
14
|
+
PHASE_7 = ("phase-7", "\uf186", "🌓")
|
|
15
|
+
# Waxing Gibbous
|
|
16
|
+
PHASE_8 = ("phase-8", "\uf186", "🌔")
|
|
17
|
+
PHASE_9 = ("phase-9", "\uf186", "🌔")
|
|
18
|
+
PHASE_10 = ("phase-10", "\uf186", "🌔")
|
|
19
|
+
PHASE_11 = ("phase-11", "\uf186", "🌔")
|
|
20
|
+
PHASE_12 = ("phase-12", "\uf186", "🌔")
|
|
21
|
+
PHASE_13 = ("phase-13", "\uf186", "🌔")
|
|
22
|
+
# Full Moon
|
|
23
|
+
PHASE_14 = ("phase-14", "\uf186", "🌕")
|
|
24
|
+
# Waning Gibbous
|
|
25
|
+
PHASE_15 = ("phase-15", "\uf186", "🌖")
|
|
26
|
+
PHASE_16 = ("phase-16", "\uf186", "🌖")
|
|
27
|
+
PHASE_17 = ("phase-17", "\uf186", "🌖")
|
|
28
|
+
PHASE_18 = ("phase-18", "\uf186", "🌖")
|
|
29
|
+
PHASE_19 = ("phase-19", "\uf186", "🌖")
|
|
30
|
+
PHASE_20 = ("phase-20", "\uf186", "🌖")
|
|
31
|
+
# Last Quarter
|
|
32
|
+
PHASE_21 = ("phase-21", "\uf186", "🌗")
|
|
33
|
+
# Waning Crescent
|
|
34
|
+
PHASE_22 = ("phase-22", "\uf186", "🌘")
|
|
35
|
+
PHASE_23 = ("phase-23", "\uf186", "🌘")
|
|
36
|
+
PHASE_24 = ("phase-24", "\uf186", "🌘")
|
|
37
|
+
PHASE_25 = ("phase-25", "\uf186", "🌘")
|
|
38
|
+
PHASE_26 = ("phase-26", "\uf186", "🌘")
|
|
39
|
+
PHASE_27 = ("phase-27", "\uf186", "🌘")
|
|
40
|
+
PHASE_28 = ("phase-28", "\uf186", "🌘")
|
|
41
|
+
DEFAULT = ("default", "\uf186", "🌑")
|
|
42
|
+
|
|
43
|
+
def __init__(self, name: str, fa_icon: str, emoji_icon: str):
|
|
44
|
+
self._name = name
|
|
45
|
+
self._fa_icon = fa_icon
|
|
46
|
+
self._emoji_icon = emoji_icon
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def name(self):
|
|
50
|
+
return self._name
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def fa_icon(self):
|
|
54
|
+
return self._fa_icon
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def emoji_icon(self):
|
|
58
|
+
return self._emoji_icon
|
|
59
|
+
|
|
60
|
+
@staticmethod
|
|
61
|
+
def from_name(name: str):
|
|
62
|
+
for item in MoonPhaseEnum:
|
|
63
|
+
if item._name == name:
|
|
64
|
+
return item
|
|
65
|
+
return None
|