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.
Files changed (70) hide show
  1. weathergrabber/__init__.py +0 -0
  2. weathergrabber/adapter/client/weather_api.py +27 -0
  3. weathergrabber/adapter/client/weather_search_api.py +50 -0
  4. weathergrabber/adapter/tty/console_tty.py +102 -0
  5. weathergrabber/adapter/tty/json_tty.py +19 -0
  6. weathergrabber/adapter/tty/waybar_tty.py +110 -0
  7. weathergrabber/cli.py +37 -0
  8. weathergrabber/core.py +31 -0
  9. weathergrabber/domain/adapter/icon_enum.py +5 -0
  10. weathergrabber/domain/adapter/mapper/air_quality_index_mapper.py +13 -0
  11. weathergrabber/domain/adapter/mapper/city_location_mapper.py +8 -0
  12. weathergrabber/domain/adapter/mapper/color_mapper.py +10 -0
  13. weathergrabber/domain/adapter/mapper/current_conditions_mapper.py +16 -0
  14. weathergrabber/domain/adapter/mapper/daily_predictions_mapper.py +15 -0
  15. weathergrabber/domain/adapter/mapper/day_night_mapper.py +12 -0
  16. weathergrabber/domain/adapter/mapper/forecast_mapper.py +20 -0
  17. weathergrabber/domain/adapter/mapper/health_activities_mapper.py +8 -0
  18. weathergrabber/domain/adapter/mapper/hourly_predictions_mapper.py +19 -0
  19. weathergrabber/domain/adapter/mapper/label_value_mapper.py +7 -0
  20. weathergrabber/domain/adapter/mapper/moon_phase_mapper.py +9 -0
  21. weathergrabber/domain/adapter/mapper/precipitation_mapper.py +7 -0
  22. weathergrabber/domain/adapter/mapper/search_mapper.py +7 -0
  23. weathergrabber/domain/adapter/mapper/sunrise_sunset_mapper.py +13 -0
  24. weathergrabber/domain/adapter/mapper/temperature_high_low_mapper.py +8 -0
  25. weathergrabber/domain/adapter/mapper/timestamp_mapper.py +8 -0
  26. weathergrabber/domain/adapter/mapper/today_details_mapper.py +21 -0
  27. weathergrabber/domain/adapter/mapper/uv_index_mapper.py +9 -0
  28. weathergrabber/domain/adapter/mapper/weather_icon_enum_mapper.py +8 -0
  29. weathergrabber/domain/adapter/mapper/wind_mapper.py +7 -0
  30. weathergrabber/domain/adapter/output_enum.py +6 -0
  31. weathergrabber/domain/adapter/params.py +58 -0
  32. weathergrabber/domain/air_quality_index.py +78 -0
  33. weathergrabber/domain/city_location.py +37 -0
  34. weathergrabber/domain/color.py +43 -0
  35. weathergrabber/domain/current_conditions.py +53 -0
  36. weathergrabber/domain/daily_predictions.py +58 -0
  37. weathergrabber/domain/day_night.py +50 -0
  38. weathergrabber/domain/forecast.py +68 -0
  39. weathergrabber/domain/health_activities.py +39 -0
  40. weathergrabber/domain/hourly_predictions.py +76 -0
  41. weathergrabber/domain/label_value.py +19 -0
  42. weathergrabber/domain/moon_phase.py +22 -0
  43. weathergrabber/domain/moon_phase_enum.py +65 -0
  44. weathergrabber/domain/precipitation.py +20 -0
  45. weathergrabber/domain/search.py +15 -0
  46. weathergrabber/domain/sunrise_sunset.py +40 -0
  47. weathergrabber/domain/temperature_hight_low.py +32 -0
  48. weathergrabber/domain/timestamp.py +39 -0
  49. weathergrabber/domain/today_details.py +79 -0
  50. weathergrabber/domain/uv_index.py +43 -0
  51. weathergrabber/domain/weather_icon_enum.py +58 -0
  52. weathergrabber/domain/wind.py +28 -0
  53. weathergrabber/service/extract_aqi_service.py +30 -0
  54. weathergrabber/service/extract_current_conditions_service.py +47 -0
  55. weathergrabber/service/extract_daily_forecast_oldstyle_service.py +51 -0
  56. weathergrabber/service/extract_daily_forecast_service.py +56 -0
  57. weathergrabber/service/extract_health_activities_service.py +25 -0
  58. weathergrabber/service/extract_hourly_forecast_oldstyle_service.py +50 -0
  59. weathergrabber/service/extract_hourly_forecast_service.py +64 -0
  60. weathergrabber/service/extract_temperature_service.py +18 -0
  61. weathergrabber/service/extract_today_details_service.py +85 -0
  62. weathergrabber/service/read_weather_service.py +23 -0
  63. weathergrabber/service/search_location_service.py +35 -0
  64. weathergrabber/usecase/use_case.py +85 -0
  65. weathergrabber/weathergrabber_application.py +78 -0
  66. weathergrabber-0.0.1.dist-info/METADATA +176 -0
  67. weathergrabber-0.0.1.dist-info/RECORD +70 -0
  68. weathergrabber-0.0.1.dist-info/WHEEL +5 -0
  69. weathergrabber-0.0.1.dist-info/entry_points.txt +2 -0
  70. 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,5 @@
1
+ from enum import StrEnum
2
+
3
+ class IconEnum(StrEnum):
4
+ FA = "fa"
5
+ EMOJI = "emoji"
@@ -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,8 @@
1
+ from weathergrabber.domain.city_location import CityLocation
2
+
3
+ def city_location_to_dict(loc: CityLocation) -> dict:
4
+ return {
5
+ "city": loc.city,
6
+ "state_province": loc.state_province,
7
+ "country": loc.country,
8
+ }
@@ -0,0 +1,10 @@
1
+ from weathergrabber.domain.color import Color
2
+
3
+ def color_to_dict(color: Color) -> dict:
4
+ return {
5
+ "red": color.red,
6
+ "green": color.green,
7
+ "blue": color.blue,
8
+ "hex": color.hex,
9
+ "rgb": color.rgb,
10
+ }
@@ -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,8 @@
1
+ from weathergrabber.domain.health_activities import HealthActivities
2
+
3
+ def health_activities_to_dict(ha: HealthActivities) -> dict:
4
+ return {
5
+ "category_name": ha.category_name,
6
+ "title": ha.title,
7
+ "description": ha.description,
8
+ }
@@ -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,7 @@
1
+ from weathergrabber.domain.label_value import LabelValue
2
+
3
+ def label_value_to_dict(lv: LabelValue) -> dict:
4
+ return {
5
+ "label": lv.label,
6
+ "value": lv.value,
7
+ }
@@ -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,7 @@
1
+ from weathergrabber.domain.precipitation import Precipitation
2
+
3
+ def precipitation_to_dict(p: Precipitation) -> dict:
4
+ return {
5
+ "percentage": p.percentage,
6
+ "amount": p.amount,
7
+ }
@@ -0,0 +1,7 @@
1
+ from weathergrabber.domain.search import Search
2
+
3
+ def search_to_dict(search: Search) -> dict:
4
+ return {
5
+ "id": search.id,
6
+ "search_name": search.search_name,
7
+ }
@@ -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,8 @@
1
+ from weathergrabber.domain.temperature_hight_low import TemperatureHighLow
2
+
3
+ def temperature_high_low_to_dict(thl: TemperatureHighLow) -> dict:
4
+ return {
5
+ "high": thl.high,
6
+ "low": thl.low,
7
+ "label": thl.label,
8
+ }
@@ -0,0 +1,8 @@
1
+ from weathergrabber.domain.timestamp import Timestamp
2
+
3
+ def timestamp_to_dict(ts: Timestamp) -> dict:
4
+ return {
5
+ "time": ts.time,
6
+ "gmt": ts.gmt,
7
+ "text": ts.text,
8
+ }
@@ -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
+ }
@@ -0,0 +1,9 @@
1
+ from weathergrabber.domain.uv_index import UVIndex
2
+
3
+ def uv_index_to_dict(uv: UVIndex) -> dict:
4
+ return {
5
+ "string_value": uv.string_value,
6
+ "index": uv.index,
7
+ "of": uv.of,
8
+ "label": uv.label,
9
+ }
@@ -0,0 +1,8 @@
1
+ from weathergrabber.domain.weather_icon_enum import WeatherIconEnum
2
+
3
+ def weather_icon_enum_to_dict(icon: WeatherIconEnum) -> dict:
4
+ return {
5
+ "name": icon.name,
6
+ "fa_icon": icon.fa_icon,
7
+ "emoji_icon": icon.emoji_icon,
8
+ }
@@ -0,0 +1,7 @@
1
+ def wind_to_dict(wind):
2
+ if wind is None:
3
+ return None
4
+ return {
5
+ "direction": wind.direction,
6
+ "speed": wind.speed
7
+ }