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
|
File without changes
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from pyquery import PyQuery
|
|
2
|
+
from urllib.error import HTTPError
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
class WeatherApi:
|
|
6
|
+
|
|
7
|
+
def __init__(self):
|
|
8
|
+
self.logger = logging.getLogger(__name__)
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
def get_weather(self,language: str, location: str) -> PyQuery:
|
|
12
|
+
url = f"https://weather.com/{language}/weather/today/l/{location}"
|
|
13
|
+
|
|
14
|
+
if location == None:
|
|
15
|
+
url = f"https://weather.com/{language}/weather/today"
|
|
16
|
+
elif len(location) < 64 :
|
|
17
|
+
raise ValueError("Invalid location")
|
|
18
|
+
|
|
19
|
+
if language == None:
|
|
20
|
+
raise ValueError("language must be specified")
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
self.logger.debug(f"Fetching weather data from URL: %s.", url)
|
|
24
|
+
return PyQuery(url=url)
|
|
25
|
+
except HTTPError as e:
|
|
26
|
+
self.logger.error("HTTP '%s' error when fetching weather data from URL: '%s'.", e.code, url)
|
|
27
|
+
raise ValueError(f"HTTP error {e.code} when fetching weather data.")
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import requests
|
|
3
|
+
|
|
4
|
+
class WeatherSearchApi:
|
|
5
|
+
def __init__(self):
|
|
6
|
+
self.logger = logging.getLogger(__name__)
|
|
7
|
+
self.cache = {}
|
|
8
|
+
|
|
9
|
+
def search(self, location_name: str, lang: str = 'en-US'):
|
|
10
|
+
|
|
11
|
+
if not location_name or len(location_name) < 1:
|
|
12
|
+
raise ValueError("Location name must be provided and cannot be empty.")
|
|
13
|
+
if len(location_name) > 100:
|
|
14
|
+
raise ValueError("Location name is too long.")
|
|
15
|
+
|
|
16
|
+
key = (location_name, lang)
|
|
17
|
+
|
|
18
|
+
if key in self.cache:
|
|
19
|
+
self.logger.debug("Cache hit for location '%s' and language '%s'.", location_name, lang)
|
|
20
|
+
return self.cache[key]
|
|
21
|
+
|
|
22
|
+
url = "https://weather.com/api/v1/p/redux-dal"
|
|
23
|
+
headers = {"content-type": "application/json"}
|
|
24
|
+
|
|
25
|
+
payload = [
|
|
26
|
+
{
|
|
27
|
+
"name": "getSunV3LocationSearchUrlConfig",
|
|
28
|
+
"params": {
|
|
29
|
+
"query": location_name,
|
|
30
|
+
"language": lang,
|
|
31
|
+
"locationType": "locale"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
self.logger.debug("Sending request to Weather Search API '%s' for location '%s' with language '%s'...", url, location_name, lang)
|
|
37
|
+
|
|
38
|
+
resp = requests.post(url, json=payload, headers=headers)
|
|
39
|
+
|
|
40
|
+
if resp.status_code != 200:
|
|
41
|
+
self.logger.error("HTTP '%s' error when searching for location '%s' with language '%s'.", resp.status_code, location_name, lang)
|
|
42
|
+
raise ValueError(f"HTTP error {resp.status_code} when searching for location.")
|
|
43
|
+
|
|
44
|
+
self.logger.debug("Received successful response from Weather Search API.")
|
|
45
|
+
|
|
46
|
+
data = resp.json()
|
|
47
|
+
|
|
48
|
+
self.cache[key] = data
|
|
49
|
+
|
|
50
|
+
return data
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from weathergrabber.usecase.use_case import UseCase
|
|
2
|
+
from weathergrabber.domain.adapter.params import Params
|
|
3
|
+
from weathergrabber.domain.adapter.icon_enum import IconEnum
|
|
4
|
+
from weathergrabber.domain.weather_icon_enum import WeatherIconEnum
|
|
5
|
+
from weathergrabber.weathergrabber_application import WeatherGrabberApplication
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
class ConsoleTTY:
|
|
9
|
+
|
|
10
|
+
def __init__(self, use_case: UseCase):
|
|
11
|
+
self.logger = logging.getLogger(__name__)
|
|
12
|
+
self.use_case = use_case
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
def execute(self, params: Params) -> None:
|
|
16
|
+
self.logger.info("Executing Console output")
|
|
17
|
+
|
|
18
|
+
is_fa = params.icons == IconEnum.FA
|
|
19
|
+
|
|
20
|
+
forecast = self.use_case.execute(params)
|
|
21
|
+
|
|
22
|
+
rain_icon = WeatherIconEnum.RAIN.fa_icon if is_fa else WeatherIconEnum.RAIN.emoji_icon
|
|
23
|
+
|
|
24
|
+
city = forecast.current_conditions.location.city
|
|
25
|
+
state_province = forecast.current_conditions.location.state_province
|
|
26
|
+
icon = forecast.current_conditions.icon.fa_icon if is_fa else forecast.current_conditions.icon.emoji_icon
|
|
27
|
+
temperature = forecast.current_conditions.temperature
|
|
28
|
+
|
|
29
|
+
day_temp_label = WeatherIconEnum.DAY.fa_icon if is_fa else WeatherIconEnum.DAY.emoji_icon
|
|
30
|
+
day_temp_value = forecast.current_conditions.day_night.day.value
|
|
31
|
+
night_temp_label = WeatherIconEnum.NIGHT.fa_icon if is_fa else WeatherIconEnum.NIGHT.emoji_icon
|
|
32
|
+
night_temp_value = forecast.current_conditions.day_night.night.value
|
|
33
|
+
|
|
34
|
+
moon_icon = forecast.today_details.moon_phase.icon.fa_icon if is_fa else forecast.today_details.moon_phase.icon.emoji_icon
|
|
35
|
+
moon_phase = forecast.today_details.moon_phase.phase
|
|
36
|
+
summary = forecast.current_conditions.summary
|
|
37
|
+
|
|
38
|
+
feelslike_icon = WeatherIconEnum.FEEL.fa_icon if is_fa else WeatherIconEnum.FEEL.emoji_icon
|
|
39
|
+
feelslike = forecast.today_details.feelslike.value
|
|
40
|
+
|
|
41
|
+
sunrise_icon = forecast.today_details.sunrise_sunset.sunrise.icon.fa_icon if is_fa else forecast.today_details.sunrise_sunset.sunrise.icon.emoji_icon
|
|
42
|
+
sunset_icon = forecast.today_details.sunrise_sunset.sunset.icon.fa_icon if is_fa else forecast.today_details.sunrise_sunset.sunset.icon.emoji_icon
|
|
43
|
+
|
|
44
|
+
sunrise_value = forecast.today_details.sunrise_sunset.sunrise.value
|
|
45
|
+
sunset_value = forecast.today_details.sunrise_sunset.sunset.value
|
|
46
|
+
|
|
47
|
+
wind_icon = WeatherIconEnum.WIND.fa_icon if is_fa else WeatherIconEnum.WIND.emoji_icon
|
|
48
|
+
wind = forecast.today_details.wind.value
|
|
49
|
+
|
|
50
|
+
humidity_icon = WeatherIconEnum.HUMIDITY.fa_icon if is_fa else WeatherIconEnum.HUMIDITY.emoji_icon
|
|
51
|
+
humidity = forecast.today_details.humidity.value
|
|
52
|
+
|
|
53
|
+
pressure = forecast.today_details.pressure
|
|
54
|
+
|
|
55
|
+
uv_index = forecast.today_details.uv_index
|
|
56
|
+
|
|
57
|
+
visibility_icon = WeatherIconEnum.VISIBILITY.fa_icon if is_fa else WeatherIconEnum.VISIBILITY.emoji_icon
|
|
58
|
+
visibility = forecast.today_details.visibility.value
|
|
59
|
+
|
|
60
|
+
r, g, b = forecast.air_quality_index.color.red, forecast.air_quality_index.color.green, forecast.air_quality_index.color.blue
|
|
61
|
+
aqi_category = f"\033[38;2;{r};{g};{b}m{forecast.air_quality_index.category}\033[0m"
|
|
62
|
+
aqi_acronym = forecast.air_quality_index.acronym
|
|
63
|
+
aqi_value = forecast.air_quality_index.value
|
|
64
|
+
|
|
65
|
+
hourly_predictions = [
|
|
66
|
+
f"{h.title}\t{h.temperature}\t{h.icon.fa_icon if is_fa else h.icon.emoji_icon}\t{rain_icon if h.precipitation.percentage else ''} {h.precipitation.percentage}"
|
|
67
|
+
for h in forecast.hourly_predictions
|
|
68
|
+
]
|
|
69
|
+
daily_predictions = [
|
|
70
|
+
f"{d.title}\t{d.high_low}\t{d.icon.fa_icon if is_fa else d.icon.emoji_icon}\t{rain_icon} {d.precipitation.percentage}"
|
|
71
|
+
for d in forecast.daily_predictions
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
print_value = (
|
|
75
|
+
"\n"
|
|
76
|
+
f"{city}, {state_province}\n"
|
|
77
|
+
"\n"
|
|
78
|
+
f"{icon} {temperature}\n"
|
|
79
|
+
"\n"
|
|
80
|
+
f"{summary}\n"
|
|
81
|
+
f"{day_temp_label} {day_temp_value}/{night_temp_label} {night_temp_value}\t{feelslike_icon} {feelslike}\n"
|
|
82
|
+
"\n"
|
|
83
|
+
f"{sunrise_icon} {sunrise_value} • {sunset_icon} {sunset_value}\n"
|
|
84
|
+
"\n"
|
|
85
|
+
f"{moon_icon} {moon_phase}\n"
|
|
86
|
+
"\n"
|
|
87
|
+
f"{wind_icon} {wind}\t {uv_index}\n"
|
|
88
|
+
f"{humidity_icon} {humidity}\t\t {pressure}\n"
|
|
89
|
+
f"{visibility_icon} {visibility}\t {aqi_acronym} {aqi_category} {aqi_value}\n"
|
|
90
|
+
"\n"
|
|
91
|
+
f"{'\n'.join(hourly_predictions)}\n"
|
|
92
|
+
"\n"
|
|
93
|
+
f"{'\n'.join(daily_predictions)}\n"
|
|
94
|
+
"\n"
|
|
95
|
+
f"{forecast.current_conditions.timestamp}"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
print(print_value)
|
|
99
|
+
if(params.keep_open):
|
|
100
|
+
lines_count = print_value.count("\n") + 1
|
|
101
|
+
ret_prev_line = f"\033[{lines_count}A"
|
|
102
|
+
print(ret_prev_line, end='') # Move cursor back to the beginning for overwriting, the application is responsable for executing again
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from weathergrabber.usecase.use_case import UseCase
|
|
2
|
+
from weathergrabber.domain.adapter.params import Params
|
|
3
|
+
from weathergrabber.domain.adapter.mapper.forecast_mapper import forecast_to_dict
|
|
4
|
+
import logging
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
class JsonTTY:
|
|
8
|
+
|
|
9
|
+
def __init__(self, use_case: UseCase):
|
|
10
|
+
self.logger = logging.getLogger(__name__)
|
|
11
|
+
self.use_case = use_case
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
def execute(self, params: Params) -> None:
|
|
15
|
+
self.logger.info("Executing JSON output")
|
|
16
|
+
forecast = self.use_case.execute(params)
|
|
17
|
+
output: dict = forecast_to_dict(forecast)
|
|
18
|
+
output_json = json.dumps(output, indent=4)
|
|
19
|
+
print(output_json)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from weathergrabber.usecase.use_case import UseCase
|
|
2
|
+
from weathergrabber.domain.adapter.params import Params
|
|
3
|
+
from weathergrabber.domain.adapter.icon_enum import IconEnum
|
|
4
|
+
from weathergrabber.domain.weather_icon_enum import WeatherIconEnum
|
|
5
|
+
import logging
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
class WaybarTTY:
|
|
9
|
+
|
|
10
|
+
def __init__(self, use_case: UseCase):
|
|
11
|
+
self.logger = logging.getLogger(__name__)
|
|
12
|
+
self.use_case = use_case
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
def execute(self, params: Params) -> None:
|
|
16
|
+
self.logger.info("Executing Waybar output")
|
|
17
|
+
|
|
18
|
+
is_fa = params.icons == IconEnum.FA
|
|
19
|
+
forecast = self.use_case.execute(params)
|
|
20
|
+
|
|
21
|
+
# Forecast icon and temperature
|
|
22
|
+
icon = forecast.current_conditions.icon.fa_icon if is_fa else forecast.current_conditions.icon.emoji_icon
|
|
23
|
+
temperature = forecast.current_conditions.temperature
|
|
24
|
+
|
|
25
|
+
# City and state/province
|
|
26
|
+
city = forecast.current_conditions.location.city
|
|
27
|
+
state_province = forecast.current_conditions.location.state_province
|
|
28
|
+
|
|
29
|
+
# Summary
|
|
30
|
+
summary = forecast.current_conditions.summary
|
|
31
|
+
|
|
32
|
+
#Day/Night temperatures
|
|
33
|
+
day_temp_label = WeatherIconEnum.DAY.fa_icon if is_fa else WeatherIconEnum.DAY.emoji_icon
|
|
34
|
+
day_temp_value = forecast.current_conditions.day_night.day.value
|
|
35
|
+
night_temp_label = WeatherIconEnum.NIGHT.fa_icon if is_fa else WeatherIconEnum.NIGHT.emoji_icon
|
|
36
|
+
night_temp_value = forecast.current_conditions.day_night.night.value
|
|
37
|
+
|
|
38
|
+
# Feels like
|
|
39
|
+
feelslike_icon = WeatherIconEnum.FEEL.fa_icon if is_fa else WeatherIconEnum.FEEL.emoji_icon
|
|
40
|
+
feelslike = forecast.today_details.feelslike.value
|
|
41
|
+
|
|
42
|
+
# Sunrise and Sunset
|
|
43
|
+
sunrise_icon = forecast.today_details.sunrise_sunset.sunrise.icon.fa_icon if is_fa else forecast.today_details.sunrise_sunset.sunrise.icon.emoji_icon
|
|
44
|
+
sunrise_value = forecast.today_details.sunrise_sunset.sunrise.value
|
|
45
|
+
sunset_icon = forecast.today_details.sunrise_sunset.sunset.icon.fa_icon if is_fa else forecast.today_details.sunrise_sunset.sunset.icon.emoji_icon
|
|
46
|
+
sunset_value = forecast.today_details.sunrise_sunset.sunset.value
|
|
47
|
+
|
|
48
|
+
# Moon phase
|
|
49
|
+
moon_icon = forecast.today_details.moon_phase.icon.fa_icon if is_fa else forecast.today_details.moon_phase.icon.emoji_icon
|
|
50
|
+
moon_phase = forecast.today_details.moon_phase.phase
|
|
51
|
+
|
|
52
|
+
#Summary data
|
|
53
|
+
wind_icon = WeatherIconEnum.WIND.fa_icon if is_fa else WeatherIconEnum.WIND.emoji_icon
|
|
54
|
+
wind = forecast.today_details.wind.value
|
|
55
|
+
uv_index = forecast.today_details.uv_index
|
|
56
|
+
humidity_icon = WeatherIconEnum.HUMIDITY.fa_icon if is_fa else WeatherIconEnum.HUMIDITY.emoji_icon
|
|
57
|
+
humidity = forecast.today_details.humidity.value
|
|
58
|
+
pressure = forecast.today_details.pressure
|
|
59
|
+
visibility_icon = WeatherIconEnum.VISIBILITY.fa_icon if is_fa else WeatherIconEnum.VISIBILITY.emoji_icon
|
|
60
|
+
visibility = forecast.today_details.visibility.value
|
|
61
|
+
|
|
62
|
+
#Air quality index
|
|
63
|
+
color = forecast.air_quality_index.color.hex
|
|
64
|
+
aqi_category = f" <span color=\"{color}\">{forecast.air_quality_index.category}</span>"
|
|
65
|
+
aqi_acronym = forecast.air_quality_index.acronym
|
|
66
|
+
aqi_value = forecast.air_quality_index.value
|
|
67
|
+
|
|
68
|
+
# Hourly predictions and daily predictions
|
|
69
|
+
rain_icon = WeatherIconEnum.RAIN.fa_icon if is_fa else WeatherIconEnum.RAIN.emoji_icon
|
|
70
|
+
hourly_predictions = [
|
|
71
|
+
f"{h.title}{'\t' if len(h.title) < 5 else ''}\t{h.temperature}\t\t{h.icon.fa_icon if is_fa else h.icon.emoji_icon}\t{rain_icon if h.precipitation.percentage else ''} {h.precipitation.percentage}"
|
|
72
|
+
for h in forecast.hourly_predictions
|
|
73
|
+
]
|
|
74
|
+
daily_predictions = [
|
|
75
|
+
f"{d.title}{'\t' if len(d.title) < 5 else ''}\t{d.high_low.high}/<span size='small'>{d.high_low.low}</span>\t{d.icon.fa_icon if is_fa else d.icon.emoji_icon}\t{rain_icon} {d.precipitation.percentage}"
|
|
76
|
+
for d in forecast.daily_predictions
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
tooltip = (
|
|
80
|
+
f"{city}, {state_province}\n"
|
|
81
|
+
"\n"
|
|
82
|
+
f"<span size='xx-large'>{icon}\t\t{temperature}</span>\n"
|
|
83
|
+
"\n"
|
|
84
|
+
f"{summary}\n"
|
|
85
|
+
"\n"
|
|
86
|
+
f"{day_temp_label} {day_temp_value} {night_temp_label} {night_temp_value}\t\t{feelslike_icon} {feelslike}\n"
|
|
87
|
+
"\n"
|
|
88
|
+
f"{sunrise_icon} {sunrise_value} • {sunset_icon} {sunset_value}\n"
|
|
89
|
+
"\n"
|
|
90
|
+
f"{moon_icon} {moon_phase}\n"
|
|
91
|
+
"\n"
|
|
92
|
+
f"{wind_icon} {wind}\t {uv_index}\n"
|
|
93
|
+
f"{humidity_icon} {humidity}\t\t {pressure}\n"
|
|
94
|
+
f"{visibility_icon} {visibility}\t {aqi_acronym} {aqi_category} {aqi_value}\n"
|
|
95
|
+
"\n"
|
|
96
|
+
f"{'\n'.join(hourly_predictions)}\n"
|
|
97
|
+
"\n"
|
|
98
|
+
f"{'\n'.join(daily_predictions)}\n"
|
|
99
|
+
"\n"
|
|
100
|
+
f"<span size='small' style='italic' weight='light' gravity='east'>{forecast.current_conditions.timestamp}</span>"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
waybar_output = {
|
|
104
|
+
"text" : f"{icon} {temperature}",
|
|
105
|
+
"alt": f"{summary}",
|
|
106
|
+
"tooltip": f"{tooltip}",
|
|
107
|
+
"class": f"{forecast.current_conditions.icon.name.lower() if forecast.current_conditions.icon else 'na'}",
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
print(json.dumps(waybar_output))
|
weathergrabber/cli.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
from .core import main
|
|
4
|
+
|
|
5
|
+
def main_cli():
|
|
6
|
+
## Get current locale, or use the default one
|
|
7
|
+
parser = argparse.ArgumentParser(description="Weather forecast grabber from weather.com")
|
|
8
|
+
parser.add_argument("location_name", type=str, nargs='?', help="Location (city name, zip code, etc.)")
|
|
9
|
+
parser.add_argument("--location-id", "-l", type=str, help="64-character-hex code for location obtained from weather.com")
|
|
10
|
+
parser.add_argument("--lang", "-L", type=str, help="Language (pt-BR, fr-FR, etc.), If not set, uses the machine one.")
|
|
11
|
+
parser.add_argument("--output", "-o", type=str, choices=['console','json','waybar'], default='console', help="Output format. console, json or waybar")
|
|
12
|
+
parser.add_argument("--keep-open", "-k",action='store_true', default=False, help="Keep open and refreshing every 5 minutes instead of exiting after execution. Does only makes sense for --output=console")
|
|
13
|
+
parser.add_argument("--icons", "-i", type=str, choices=['fa','emoji'], default='emoji', help="Icon set. 'fa' for Font-Awesome, or 'emoji'")
|
|
14
|
+
parser.add_argument(
|
|
15
|
+
"--log",
|
|
16
|
+
default="critical",
|
|
17
|
+
choices=["debug", "info", "warning", "error", "critical", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
|
|
18
|
+
help="Set the logging level (default: CRITICAL)"
|
|
19
|
+
)
|
|
20
|
+
args = parser.parse_args()
|
|
21
|
+
|
|
22
|
+
# Check for language and location from environment variables if not provided as arguments
|
|
23
|
+
lang = args.lang if args.lang else os.getenv("LANG","en_US.UTF-8").split(".")[0].replace("_","-")
|
|
24
|
+
location_id = args.location_id if args.location_id else os.getenv('WEATHER_LOCATION_ID') if not args.location_name else args.location_name
|
|
25
|
+
|
|
26
|
+
main(
|
|
27
|
+
log_level=args.log,
|
|
28
|
+
location_name = args.location_name,
|
|
29
|
+
location_id = location_id,
|
|
30
|
+
lang=lang,
|
|
31
|
+
output=args.output,
|
|
32
|
+
keep_open=args.keep_open,
|
|
33
|
+
icons=args.icons
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
if __name__ == "__main__":
|
|
37
|
+
main_cli()
|
weathergrabber/core.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from weathergrabber.weathergrabber_application import WeatherGrabberApplication
|
|
3
|
+
from weathergrabber.domain.adapter.params import Params
|
|
4
|
+
from weathergrabber.domain.adapter.output_enum import OutputEnum
|
|
5
|
+
from weathergrabber.domain.adapter.icon_enum import IconEnum
|
|
6
|
+
|
|
7
|
+
logging.basicConfig(
|
|
8
|
+
level=logging.INFO,
|
|
9
|
+
format="[%(levelname)s] %(message)s"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
def main(log_level: str, location_name: str, location_id: str, lang: str, output: str, keep_open: bool, icons: str):
|
|
13
|
+
logging.getLogger().setLevel(log_level.upper())
|
|
14
|
+
|
|
15
|
+
logging.info(f"Log level set to {log_level}")
|
|
16
|
+
logging.info(f"Location name: {location_name}")
|
|
17
|
+
logging.info(f"Location id: {location_id}")
|
|
18
|
+
logging.info(f"Language: {lang}")
|
|
19
|
+
logging.info(f"Output: {output}")
|
|
20
|
+
logging.info(f"Keep open: {keep_open}")
|
|
21
|
+
logging.info(f"Icons: {icons}")
|
|
22
|
+
|
|
23
|
+
params = Params(
|
|
24
|
+
location=Params.Location(search_name=location_name, id=location_id),
|
|
25
|
+
language=lang if lang else "en-US",
|
|
26
|
+
output_format= OutputEnum(output),
|
|
27
|
+
keep_open=keep_open,
|
|
28
|
+
icons=IconEnum(icons)
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
app = WeatherGrabberApplication(params)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
|
|
2
|
+
from weathergrabber.domain.air_quality_index import AirQualityIndex
|
|
3
|
+
from weathergrabber.domain.adapter.mapper.color_mapper import color_to_dict
|
|
4
|
+
|
|
5
|
+
def air_quality_index_to_dict(aqi: AirQualityIndex) -> dict:
|
|
6
|
+
return {
|
|
7
|
+
"title": aqi.title,
|
|
8
|
+
"value": aqi.value,
|
|
9
|
+
"category": aqi.category,
|
|
10
|
+
"description": aqi.description,
|
|
11
|
+
"acronym": aqi.acronym,
|
|
12
|
+
"color": color_to_dict(aqi.color) if aqi.color else None,
|
|
13
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
from weathergrabber.domain.current_conditions import CurrentConditions
|
|
3
|
+
from weathergrabber.domain.adapter.mapper.city_location_mapper import city_location_to_dict
|
|
4
|
+
from weathergrabber.domain.adapter.mapper.timestamp_mapper import timestamp_to_dict
|
|
5
|
+
from weathergrabber.domain.adapter.mapper.weather_icon_enum_mapper import weather_icon_enum_to_dict
|
|
6
|
+
from weathergrabber.domain.adapter.mapper.day_night_mapper import day_night_to_dict
|
|
7
|
+
|
|
8
|
+
def current_conditions_to_dict(cc: CurrentConditions) -> dict:
|
|
9
|
+
return {
|
|
10
|
+
"location": city_location_to_dict(cc.location) if cc.location else None,
|
|
11
|
+
"timestamp": timestamp_to_dict(cc.timestamp) if cc.timestamp else None,
|
|
12
|
+
"temperature": cc.temperature,
|
|
13
|
+
"icon": weather_icon_enum_to_dict(cc.icon) if cc.icon else None,
|
|
14
|
+
"summary": cc.summary,
|
|
15
|
+
"day_night": day_night_to_dict(cc.day_night) if cc.day_night else None,
|
|
16
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from weathergrabber.domain.daily_predictions import DailyPredictions
|
|
2
|
+
from weathergrabber.domain.adapter.mapper.temperature_high_low_mapper import temperature_high_low_to_dict
|
|
3
|
+
from weathergrabber.domain.adapter.mapper.weather_icon_enum_mapper import weather_icon_enum_to_dict
|
|
4
|
+
from weathergrabber.domain.adapter.mapper.precipitation_mapper import precipitation_to_dict
|
|
5
|
+
from weathergrabber.domain.adapter.mapper.moon_phase_mapper import moon_phase_to_dict
|
|
6
|
+
|
|
7
|
+
def daily_predictions_to_dict(dp: DailyPredictions) -> dict:
|
|
8
|
+
return {
|
|
9
|
+
"title": dp.title,
|
|
10
|
+
"high_low": temperature_high_low_to_dict(dp.high_low) if dp.high_low else None,
|
|
11
|
+
"icon": weather_icon_enum_to_dict(dp.icon) if dp.icon else None,
|
|
12
|
+
"summary": dp.summary,
|
|
13
|
+
"precipitation": precipitation_to_dict(dp.precipitation) if dp.precipitation else None,
|
|
14
|
+
"moon_phase": moon_phase_to_dict(dp.moon_phase) if dp.moon_phase else None,
|
|
15
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from weathergrabber.domain.day_night import DayNight
|
|
2
|
+
|
|
3
|
+
def day_night_to_dict(dn: DayNight) -> dict:
|
|
4
|
+
def temp_to_dict(temp):
|
|
5
|
+
return {
|
|
6
|
+
"label": temp.label,
|
|
7
|
+
"value": temp.value,
|
|
8
|
+
} if temp else None
|
|
9
|
+
return {
|
|
10
|
+
"day": temp_to_dict(dn.day) if dn.day else None,
|
|
11
|
+
"night": temp_to_dict(dn.night) if dn.night else None,
|
|
12
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from weathergrabber.domain.forecast import Forecast
|
|
2
|
+
|
|
3
|
+
from weathergrabber.domain.adapter.mapper.search_mapper import search_to_dict
|
|
4
|
+
from weathergrabber.domain.adapter.mapper.current_conditions_mapper import current_conditions_to_dict
|
|
5
|
+
from weathergrabber.domain.adapter.mapper.today_details_mapper import today_details_to_dict
|
|
6
|
+
from weathergrabber.domain.adapter.mapper.air_quality_index_mapper import air_quality_index_to_dict
|
|
7
|
+
from weathergrabber.domain.adapter.mapper.health_activities_mapper import health_activities_to_dict
|
|
8
|
+
from weathergrabber.domain.adapter.mapper.hourly_predictions_mapper import hourly_predictions_to_dict
|
|
9
|
+
from weathergrabber.domain.adapter.mapper.daily_predictions_mapper import daily_predictions_to_dict
|
|
10
|
+
|
|
11
|
+
def forecast_to_dict(forecast: Forecast) -> dict:
|
|
12
|
+
return {
|
|
13
|
+
"search": search_to_dict(forecast.search) if forecast.search else None,
|
|
14
|
+
"current_conditions": current_conditions_to_dict(forecast.current_conditions) if forecast.current_conditions else None,
|
|
15
|
+
"today_details": today_details_to_dict(forecast.today_details) if forecast.today_details else None,
|
|
16
|
+
"air_quality_index": air_quality_index_to_dict(forecast.air_quality_index) if forecast.air_quality_index else None,
|
|
17
|
+
"health_activities": health_activities_to_dict(forecast.health_activities) if forecast.health_activities else None,
|
|
18
|
+
"hourly_predictions": [hourly_predictions_to_dict(h) for h in forecast.hourly_predictions],
|
|
19
|
+
"daily_predictions": [daily_predictions_to_dict(d) for d in forecast.daily_predictions],
|
|
20
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from weathergrabber.domain.hourly_predictions import HourlyPredictions
|
|
2
|
+
from weathergrabber.domain.adapter.mapper.weather_icon_enum_mapper import weather_icon_enum_to_dict
|
|
3
|
+
from weathergrabber.domain.adapter.mapper.precipitation_mapper import precipitation_to_dict
|
|
4
|
+
from weathergrabber.domain.adapter.mapper.wind_mapper import wind_to_dict
|
|
5
|
+
from weathergrabber.domain.adapter.mapper.uv_index_mapper import uv_index_to_dict
|
|
6
|
+
|
|
7
|
+
def hourly_predictions_to_dict(hp: HourlyPredictions) -> dict:
|
|
8
|
+
return {
|
|
9
|
+
"title": hp.title,
|
|
10
|
+
"temperature": hp.temperature,
|
|
11
|
+
"icon": weather_icon_enum_to_dict(hp.icon) if hp.icon else None,
|
|
12
|
+
"summary": hp.summary,
|
|
13
|
+
"precipitation": precipitation_to_dict(hp.precipitation) if hp.precipitation else None,
|
|
14
|
+
"wind": wind_to_dict(hp.wind) if hp.wind else None,
|
|
15
|
+
"feels_like": hp.feels_like,
|
|
16
|
+
"humidity": hp.humidity,
|
|
17
|
+
"uv_index": uv_index_to_dict(hp.uv_index) if hp.uv_index else None,
|
|
18
|
+
"cloud_cover": hp.cloud_cover,
|
|
19
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from weathergrabber.domain.moon_phase import MoonPhase
|
|
2
|
+
from weathergrabber.domain.moon_phase_enum import MoonPhaseEnum
|
|
3
|
+
|
|
4
|
+
def moon_phase_to_dict(mp: MoonPhase) -> dict:
|
|
5
|
+
return {
|
|
6
|
+
"icon": mp.icon.name if mp.icon else None,
|
|
7
|
+
"phase": mp.phase,
|
|
8
|
+
"label": mp.label,
|
|
9
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from weathergrabber.domain.sunrise_sunset import SunriseSunset
|
|
2
|
+
from weathergrabber.domain.adapter.mapper.weather_icon_enum_mapper import weather_icon_enum_to_dict
|
|
3
|
+
|
|
4
|
+
def sunrise_sunset_to_dict(ss: SunriseSunset) -> dict:
|
|
5
|
+
def icon_value_to_dict(iv):
|
|
6
|
+
return {
|
|
7
|
+
"icon": weather_icon_enum_to_dict(iv.icon) if iv.icon else None,
|
|
8
|
+
"value": iv.value,
|
|
9
|
+
} if iv else None
|
|
10
|
+
return {
|
|
11
|
+
"sunrise": icon_value_to_dict(ss.sunrise) if ss.sunrise else None,
|
|
12
|
+
"sunset": icon_value_to_dict(ss.sunset) if ss.sunset else None,
|
|
13
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
|
|
2
|
+
from weathergrabber.domain.today_details import TodayDetails
|
|
3
|
+
from weathergrabber.domain.adapter.mapper.label_value_mapper import label_value_to_dict
|
|
4
|
+
from weathergrabber.domain.adapter.mapper.sunrise_sunset_mapper import sunrise_sunset_to_dict
|
|
5
|
+
from weathergrabber.domain.adapter.mapper.temperature_high_low_mapper import temperature_high_low_to_dict
|
|
6
|
+
from weathergrabber.domain.adapter.mapper.uv_index_mapper import uv_index_to_dict
|
|
7
|
+
from weathergrabber.domain.adapter.mapper.moon_phase_mapper import moon_phase_to_dict
|
|
8
|
+
|
|
9
|
+
def today_details_to_dict(td: TodayDetails) -> dict:
|
|
10
|
+
return {
|
|
11
|
+
"feelslike": label_value_to_dict(td.feelslike) if td.feelslike else None,
|
|
12
|
+
"sunrise_sunset": sunrise_sunset_to_dict(td.sunrise_sunset) if td.sunrise_sunset else None,
|
|
13
|
+
"high_low": temperature_high_low_to_dict(td.high_low) if td.high_low else None,
|
|
14
|
+
"wind": label_value_to_dict(td.wind) if td.wind else None,
|
|
15
|
+
"humidity": label_value_to_dict(td.humidity) if td.humidity else None,
|
|
16
|
+
"dew_point": label_value_to_dict(td.dew_point) if td.dew_point else None,
|
|
17
|
+
"pressure": label_value_to_dict(td.pressure) if td.pressure else None,
|
|
18
|
+
"uv_index": uv_index_to_dict(td.uv_index) if td.uv_index else None,
|
|
19
|
+
"visibility": label_value_to_dict(td.visibility) if td.visibility else None,
|
|
20
|
+
"moon_phase": moon_phase_to_dict(td.moon_phase) if td.moon_phase else None,
|
|
21
|
+
}
|