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,64 @@
|
|
|
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.uv_index import UVIndex
|
|
6
|
+
from weathergrabber.domain.precipitation import Precipitation
|
|
7
|
+
from weathergrabber.domain.wind import Wind
|
|
8
|
+
from typing import List
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ExtractHourlyForecastService:
|
|
12
|
+
def __init__(self):
|
|
13
|
+
self.logger = logging.getLogger(__name__)
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
def execute(self, weather_data: PyQuery) -> List[HourlyPredictions]:
|
|
17
|
+
try:
|
|
18
|
+
self.logger.debug("Extracting hourly forecast...")
|
|
19
|
+
|
|
20
|
+
data = weather_data.find("section[data-testid='HourlyForecast'] div[class*='Card'] details")
|
|
21
|
+
|
|
22
|
+
if len(data) == 0:
|
|
23
|
+
raise ValueError("Unable to extract hourly forecast")
|
|
24
|
+
|
|
25
|
+
details = [ {
|
|
26
|
+
"title": PyQuery(item).find("h2").text(),
|
|
27
|
+
"temperature" : PyQuery(item).find("div[data-testid='detailsTemperature']").text(),
|
|
28
|
+
"icon" : PyQuery(item).find("svg[class*='DetailsSummary']").attr("name"),
|
|
29
|
+
"summary" : PyQuery(item).find("span[class*='DetailsSummary--wxPhrase']").text(),
|
|
30
|
+
"precip-percentage": PyQuery(item).find("div[data-testid='Precip'] span[data-testid='PercentageValue']").text(),
|
|
31
|
+
"wind": PyQuery(item).find("span[data-testid='WindTitle']").next().eq(0).text(),
|
|
32
|
+
"feels-like" : PyQuery(item).find("span[data-testid='FeelsLikeTitle']").next().text(),
|
|
33
|
+
"humidity" : PyQuery(item).find("span[data-testid='HumidityTitle']").next().text(),
|
|
34
|
+
"uv-index" : PyQuery(item).find("span[data-testid='UVIndexValue']").text(),
|
|
35
|
+
"cloud-cover" : PyQuery(item).find("span[data-testid='CloudCoverTitle']").next().text(),
|
|
36
|
+
"rain-amount" : PyQuery(item).find("span[data-testid='AccumulationTitle']").next().text()
|
|
37
|
+
} for item in data ]
|
|
38
|
+
|
|
39
|
+
self.logger.debug("Extracted %s register(s)...",len(details))
|
|
40
|
+
|
|
41
|
+
hourly_forecasts = [HourlyPredictions(
|
|
42
|
+
title=item["title"],
|
|
43
|
+
temperature=item["temperature"],
|
|
44
|
+
icon=WeatherIconEnum.from_name(item["icon"]),
|
|
45
|
+
summary=item["summary"],
|
|
46
|
+
precipitation=Precipitation(
|
|
47
|
+
percentage=item["precip-percentage"],
|
|
48
|
+
amount=item["rain-amount"]
|
|
49
|
+
),
|
|
50
|
+
wind=Wind.from_string(item["wind"]),
|
|
51
|
+
feels_like=item["feels-like"],
|
|
52
|
+
humidity=item["humidity"],
|
|
53
|
+
uv_index=UVIndex.from_string(item["uv-index"]),
|
|
54
|
+
cloud_cover=item["cloud-cover"]
|
|
55
|
+
) for item in details]
|
|
56
|
+
|
|
57
|
+
self.logger.debug("Created hourly forecast list with %s registers", len(hourly_forecasts))
|
|
58
|
+
|
|
59
|
+
return hourly_forecasts
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
except Exception as e:
|
|
63
|
+
self.logger.error(f"Error extracting hourly forecast: {e}")
|
|
64
|
+
raise ValueError("Could not extract hourly forecast.") from e
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pyquery import PyQuery
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ExtractTemperatureService:
|
|
6
|
+
def __init__(self):
|
|
7
|
+
self.logger = logging.getLogger(__name__)
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
def execute(self, weather_data: PyQuery) -> str:
|
|
11
|
+
try:
|
|
12
|
+
temperature = weather_data("div[class*='CurrentConditions--tempIconContainer'] span[data-testid='TemperatureValue']").text()
|
|
13
|
+
self.logger.debug(f"Extracted temperature: {temperature}")
|
|
14
|
+
return temperature
|
|
15
|
+
|
|
16
|
+
except Exception as e:
|
|
17
|
+
self.logger.error(f"Error temperature: {e}")
|
|
18
|
+
raise ValueError("Could not extract temperature.")
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pyquery import PyQuery
|
|
3
|
+
from weathergrabber.domain.today_details import TodayDetails
|
|
4
|
+
from weathergrabber.domain.temperature_hight_low import TemperatureHighLow
|
|
5
|
+
from weathergrabber.domain.uv_index import UVIndex
|
|
6
|
+
from weathergrabber.domain.moon_phase import MoonPhase
|
|
7
|
+
from weathergrabber.domain.moon_phase_enum import MoonPhaseEnum
|
|
8
|
+
from weathergrabber.domain.label_value import LabelValue
|
|
9
|
+
from weathergrabber.domain.sunrise_sunset import SunriseSunset
|
|
10
|
+
|
|
11
|
+
class ExtractTodayDetailsService:
|
|
12
|
+
def __init__(self):
|
|
13
|
+
self.logger = logging.getLogger(__name__)
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
def execute(self, weather_data: PyQuery) -> TodayDetails:
|
|
17
|
+
try:
|
|
18
|
+
self.logger.debug("Extracting today's details...")
|
|
19
|
+
|
|
20
|
+
today_details_data = weather_data.find("div#todayDetails")
|
|
21
|
+
feelslike = PyQuery(today_details_data).find("div[data-testid='FeelsLikeSection'] span")
|
|
22
|
+
sunrise_sunset = PyQuery(today_details_data).find("div[data-testid='sunriseSunsetContainer'] div p[class*='TwcSunChart']")
|
|
23
|
+
|
|
24
|
+
feelslike_label = feelslike.eq(0).text() #'Feels Like'
|
|
25
|
+
feelslike_value = feelslike.eq(1).text() #'60°'
|
|
26
|
+
|
|
27
|
+
sunrise = sunrise_sunset.eq(0).text() #'6:12 AM'
|
|
28
|
+
sunset = sunrise_sunset.eq(1).text() #'7:45 PM'
|
|
29
|
+
|
|
30
|
+
icons = today_details_data.find('svg[class*="WeatherDetailsListItem--icon"]')
|
|
31
|
+
labels = today_details_data.find('div[class*="WeatherDetailsListItem--label"]')
|
|
32
|
+
values = today_details_data.find('div[data-testid="wxData"]')
|
|
33
|
+
|
|
34
|
+
self.logger.debug(f"Parsing today details values...")
|
|
35
|
+
high_low_label = labels.eq(0).text() #'High / Low'
|
|
36
|
+
high_low_value = values.eq(0).text() #'--/54°'
|
|
37
|
+
|
|
38
|
+
wind_label = labels.eq(1).text() #'Wind'
|
|
39
|
+
wind_value = values.eq(1).text() #'7\xa0mph'
|
|
40
|
+
|
|
41
|
+
humidity_label = labels.eq(2).text() #'Humidity'
|
|
42
|
+
humidity_value = values.eq(2).text() #'100%'
|
|
43
|
+
|
|
44
|
+
dew_point_label = labels.eq(3).text() #'Dew Point'
|
|
45
|
+
dew_point_value = values.eq(3).text() #'60°'
|
|
46
|
+
|
|
47
|
+
pressure_label = labels.eq(4).text() #'Pressure'
|
|
48
|
+
pressure_value = values.eq(4).text() #'30.31\xa0in'
|
|
49
|
+
|
|
50
|
+
uv_index_label = labels.eq(5).text() #'UV Index'
|
|
51
|
+
uv_index_value = values.eq(5).text() #'5 of 10'
|
|
52
|
+
|
|
53
|
+
visibility_label = labels.eq(6).text() #'Visibility'
|
|
54
|
+
visibility_value = values.eq(6).text() #'10.0 mi'
|
|
55
|
+
|
|
56
|
+
moon_phase_label = labels.eq(7).text() #'Moon Phase'
|
|
57
|
+
moon_phase_icon = icons.eq(7).attr('name') #'phase-2'
|
|
58
|
+
moon_phase_value = values.eq(7).text() #'Waxing Crescent'
|
|
59
|
+
|
|
60
|
+
self.logger.debug(f"Creating domain objects for today details...")
|
|
61
|
+
|
|
62
|
+
sunrise_sunset = SunriseSunset(sunrise=sunrise, sunset=sunset)
|
|
63
|
+
high_low = TemperatureHighLow.from_string(high_low_value, label=high_low_label)
|
|
64
|
+
uv_index = UVIndex.from_string(uv_index_value, label=uv_index_label)
|
|
65
|
+
moon_phase = MoonPhase(MoonPhaseEnum.from_name(moon_phase_icon), moon_phase_value, moon_phase_label)
|
|
66
|
+
|
|
67
|
+
today_details = TodayDetails(
|
|
68
|
+
feelslike=LabelValue(label=feelslike_label, value=feelslike_value),
|
|
69
|
+
sunrise_sunset=sunrise_sunset,
|
|
70
|
+
high_low=high_low,
|
|
71
|
+
wind=LabelValue(label=wind_label, value=wind_value),
|
|
72
|
+
humidity=LabelValue(label=humidity_label, value=humidity_value),
|
|
73
|
+
dew_point=LabelValue(label=dew_point_label, value=dew_point_value),
|
|
74
|
+
pressure=LabelValue(label=pressure_label, value=pressure_value),
|
|
75
|
+
uv_index=uv_index,
|
|
76
|
+
visibility=LabelValue(label=visibility_label, value=visibility_value),
|
|
77
|
+
moon_phase=moon_phase
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
self.logger.debug(f"Extracted today's details: {today_details}")
|
|
81
|
+
return today_details
|
|
82
|
+
|
|
83
|
+
except Exception as e:
|
|
84
|
+
self.logger.error(f"Error extracting today's details: {e}")
|
|
85
|
+
raise ValueError("Could not extract today's details.") from e
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from weathergrabber.adapter.client.weather_api import WeatherApi
|
|
2
|
+
import logging
|
|
3
|
+
from pyquery import PyQuery
|
|
4
|
+
|
|
5
|
+
class ReadWeatherService:
|
|
6
|
+
def __init__(
|
|
7
|
+
self,
|
|
8
|
+
weather_api: WeatherApi
|
|
9
|
+
):
|
|
10
|
+
self.weather_api = weather_api
|
|
11
|
+
self.logging = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
def execute(self, language: str, location: str) -> PyQuery:
|
|
14
|
+
|
|
15
|
+
self.logging.debug(f"Executing WeatherDataService with language: {language}, location: {location}")
|
|
16
|
+
|
|
17
|
+
weather_data = self.weather_api.get_weather(language, location)
|
|
18
|
+
|
|
19
|
+
self.logging.debug(f"Weather data retrieved.")
|
|
20
|
+
|
|
21
|
+
return weather_data
|
|
22
|
+
|
|
23
|
+
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from weathergrabber.adapter.client.weather_search_api import WeatherSearchApi
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
class SearchLocationService:
|
|
5
|
+
def __init__(self, api: WeatherSearchApi):
|
|
6
|
+
self.api = api
|
|
7
|
+
self.logger = logging.getLogger(__name__)
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
def execute(self, location_name: str, lang: str) -> str | None:
|
|
11
|
+
self.logger.debug(f"Searching for location: {location_name} with language: {lang}")
|
|
12
|
+
|
|
13
|
+
if not location_name:
|
|
14
|
+
self.logger.debug("No location name provided. Bypassing search.")
|
|
15
|
+
return None
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
data = self.api.search(location_name, lang)
|
|
19
|
+
if not data:
|
|
20
|
+
self.logger.error(f"No data found for location: {location_name}")
|
|
21
|
+
raise ValueError(f"Location '{location_name}' not found.")
|
|
22
|
+
|
|
23
|
+
dal = data["dal"]["getSunV3LocationSearchUrlConfig"]
|
|
24
|
+
|
|
25
|
+
# Pick the first (arbitrary) key, then get the value
|
|
26
|
+
first_key = next(iter(dal))
|
|
27
|
+
location_id = dal[first_key]["data"]["location"]["placeId"][0]
|
|
28
|
+
|
|
29
|
+
self.logger.debug(f"Found location ID: {location_id} for location name: {location_name}")
|
|
30
|
+
|
|
31
|
+
return location_id
|
|
32
|
+
except Exception as e:
|
|
33
|
+
self.logger.error(f"Error searching for location '{location_name}': {e}")
|
|
34
|
+
raise ValueError(f"Could not find location '{location_name}'.")
|
|
35
|
+
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from weathergrabber.domain.adapter.params import Params
|
|
3
|
+
from weathergrabber.service.search_location_service import SearchLocationService
|
|
4
|
+
from weathergrabber.service.read_weather_service import ReadWeatherService
|
|
5
|
+
from weathergrabber.service.extract_current_conditions_service import ExtractCurrentConditionsService
|
|
6
|
+
from weathergrabber.service.extract_today_details_service import ExtractTodayDetailsService
|
|
7
|
+
from weathergrabber.service.extract_aqi_service import ExtractAQIService
|
|
8
|
+
from weathergrabber.service.extract_health_activities_service import ExtractHealthActivitiesService
|
|
9
|
+
from weathergrabber.service.extract_hourly_forecast_service import ExtractHourlyForecastService
|
|
10
|
+
from weathergrabber.service.extract_hourly_forecast_oldstyle_service import ExtractHourlyForecastOldstyleService
|
|
11
|
+
from weathergrabber.service.extract_daily_forecast_service import ExtractDailyForecastService
|
|
12
|
+
from weathergrabber.service.extract_daily_forecast_oldstyle_service import ExtractDailyForecastOldstyleService
|
|
13
|
+
from weathergrabber.domain.search import Search
|
|
14
|
+
from weathergrabber.domain.forecast import Forecast
|
|
15
|
+
|
|
16
|
+
class UseCase:
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
search_location_service: SearchLocationService,
|
|
20
|
+
read_weather_service: ReadWeatherService,
|
|
21
|
+
extract_current_conditions_service: ExtractCurrentConditionsService,
|
|
22
|
+
extract_today_details_service: ExtractTodayDetailsService,
|
|
23
|
+
extract_aqi_service: ExtractAQIService,
|
|
24
|
+
extract_health_activities_service: ExtractHealthActivitiesService,
|
|
25
|
+
extract_hourly_forecast_service: ExtractHourlyForecastService,
|
|
26
|
+
extract_hourly_forecast_oldstyle_service: ExtractHourlyForecastOldstyleService,
|
|
27
|
+
extract_daily_forecast_service: ExtractDailyForecastService,
|
|
28
|
+
extract_daily_forecast_oldstyle_service: ExtractDailyForecastOldstyleService,
|
|
29
|
+
|
|
30
|
+
):
|
|
31
|
+
self.logger = logging.getLogger(__name__)
|
|
32
|
+
self.read_weather_service = read_weather_service
|
|
33
|
+
self.extract_current_conditions_service = extract_current_conditions_service
|
|
34
|
+
self.extract_today_details_service = extract_today_details_service
|
|
35
|
+
self.search_location_service = search_location_service
|
|
36
|
+
self.extract_aqi_service = extract_aqi_service
|
|
37
|
+
self.extract_health_activities_service = extract_health_activities_service
|
|
38
|
+
self.extract_hourly_forecast_service = extract_hourly_forecast_service
|
|
39
|
+
self.extract_hourly_forecast_oldstyle_service = extract_hourly_forecast_oldstyle_service
|
|
40
|
+
self.extract_daily_forecast_service = extract_daily_forecast_service
|
|
41
|
+
self.extract_daily_forecast_oldstyle_service = extract_daily_forecast_oldstyle_service
|
|
42
|
+
|
|
43
|
+
def execute(self, params: Params) -> Forecast:
|
|
44
|
+
|
|
45
|
+
self.logger.debug("Starting usecase")
|
|
46
|
+
|
|
47
|
+
location_id = params.location.id
|
|
48
|
+
search_name = params.location.search_name
|
|
49
|
+
if not location_id:
|
|
50
|
+
location_id = self.search_location_service.execute(params.location.search_name, params.language)
|
|
51
|
+
|
|
52
|
+
weather_data = self.read_weather_service.execute(params.language, location_id)
|
|
53
|
+
|
|
54
|
+
current_conditions = self.extract_current_conditions_service.execute(weather_data)
|
|
55
|
+
today_details = self.extract_today_details_service.execute(weather_data)
|
|
56
|
+
air_quality_index = self.extract_aqi_service.execute(weather_data)
|
|
57
|
+
health_activities = self.extract_health_activities_service.execute(weather_data)
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
hourly_predictions = self.extract_hourly_forecast_service.execute(weather_data)
|
|
61
|
+
except ValueError:
|
|
62
|
+
hourly_predictions = self.extract_hourly_forecast_oldstyle_service.execute(weather_data)
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
daily_predictions = self.extract_daily_forecast_service.execute(weather_data)
|
|
66
|
+
except ValueError:
|
|
67
|
+
daily_predictions = self.extract_daily_forecast_oldstyle_service.execute(weather_data)
|
|
68
|
+
|
|
69
|
+
forecast = Forecast(
|
|
70
|
+
search = Search(
|
|
71
|
+
id = location_id,
|
|
72
|
+
search_name = search_name
|
|
73
|
+
),
|
|
74
|
+
current_conditions = current_conditions,
|
|
75
|
+
today_details = today_details,
|
|
76
|
+
air_quality_index = air_quality_index,
|
|
77
|
+
health_activities = health_activities,
|
|
78
|
+
hourly_predictions = hourly_predictions,
|
|
79
|
+
daily_predictions = daily_predictions
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
self.logger.debug("Forecast data obtained %s", forecast)
|
|
83
|
+
|
|
84
|
+
return forecast
|
|
85
|
+
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from time import sleep
|
|
3
|
+
from .domain.adapter.params import Params
|
|
4
|
+
from .domain.adapter.output_enum import OutputEnum
|
|
5
|
+
from .adapter.client.weather_api import WeatherApi
|
|
6
|
+
from .adapter.client.weather_search_api import WeatherSearchApi
|
|
7
|
+
from .service.search_location_service import SearchLocationService
|
|
8
|
+
from .service.read_weather_service import ReadWeatherService
|
|
9
|
+
from .service.extract_current_conditions_service import ExtractCurrentConditionsService
|
|
10
|
+
from .service.extract_today_details_service import ExtractTodayDetailsService
|
|
11
|
+
from .service.extract_aqi_service import ExtractAQIService
|
|
12
|
+
from .service.extract_health_activities_service import ExtractHealthActivitiesService
|
|
13
|
+
from .service.extract_hourly_forecast_service import ExtractHourlyForecastService
|
|
14
|
+
from .service.extract_hourly_forecast_oldstyle_service import ExtractHourlyForecastOldstyleService
|
|
15
|
+
from .service.extract_daily_forecast_service import ExtractDailyForecastService
|
|
16
|
+
from .service.extract_daily_forecast_oldstyle_service import ExtractDailyForecastOldstyleService
|
|
17
|
+
from .usecase.use_case import UseCase
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class WeatherGrabberApplication:
|
|
21
|
+
|
|
22
|
+
def _beans(self):
|
|
23
|
+
self.weather_search_api = WeatherSearchApi()
|
|
24
|
+
self.weather_api = WeatherApi()
|
|
25
|
+
self.search_location_service = SearchLocationService(self.weather_search_api)
|
|
26
|
+
self.read_weather_service = ReadWeatherService(self.weather_api)
|
|
27
|
+
self.extract_current_conditions_service = ExtractCurrentConditionsService()
|
|
28
|
+
self.extract_today_details_service = ExtractTodayDetailsService()
|
|
29
|
+
self.extract_aqi_service = ExtractAQIService()
|
|
30
|
+
self.extract_health_activities_service = ExtractHealthActivitiesService()
|
|
31
|
+
self.extract_hourly_forecast_service = ExtractHourlyForecastService()
|
|
32
|
+
self.extract_hourly_forecast_oldstyle_service = ExtractHourlyForecastOldstyleService()
|
|
33
|
+
self.extract_daily_forecast_service = ExtractDailyForecastService()
|
|
34
|
+
self.extract_daily_forecast_oldstyle_service = ExtractDailyForecastOldstyleService()
|
|
35
|
+
self.use_case = UseCase(
|
|
36
|
+
self.search_location_service,
|
|
37
|
+
self.read_weather_service,
|
|
38
|
+
self.extract_current_conditions_service,
|
|
39
|
+
self.extract_today_details_service,
|
|
40
|
+
self.extract_aqi_service,
|
|
41
|
+
self.extract_health_activities_service,
|
|
42
|
+
self.extract_hourly_forecast_service,
|
|
43
|
+
self.extract_hourly_forecast_oldstyle_service,
|
|
44
|
+
self.extract_daily_forecast_service,
|
|
45
|
+
self.extract_daily_forecast_oldstyle_service
|
|
46
|
+
)
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
def _define_controller(self, output_format: OutputEnum):
|
|
50
|
+
if output_format == OutputEnum.CONSOLE:
|
|
51
|
+
from weathergrabber.adapter.tty.console_tty import ConsoleTTY
|
|
52
|
+
self.controller = ConsoleTTY(self.use_case)
|
|
53
|
+
|
|
54
|
+
elif output_format == OutputEnum.JSON:
|
|
55
|
+
from weathergrabber.adapter.tty.json_tty import JsonTTY
|
|
56
|
+
self.controller = JsonTTY(self.use_case)
|
|
57
|
+
|
|
58
|
+
elif output_format == OutputEnum.WAYBAR:
|
|
59
|
+
from weathergrabber.adapter.tty.waybar_tty import WaybarTTY
|
|
60
|
+
self.controller = WaybarTTY(self.use_case)
|
|
61
|
+
|
|
62
|
+
else:
|
|
63
|
+
self.logger.error(f"Unsupported output format: {output_format}")
|
|
64
|
+
raise ValueError(f"Unsupported output format: {output_format}")
|
|
65
|
+
|
|
66
|
+
def __init__(self, params: Params):
|
|
67
|
+
self.logger = logging.getLogger(__name__)
|
|
68
|
+
self._beans()
|
|
69
|
+
self._define_controller(params.output_format)
|
|
70
|
+
self.logger.info("Starting WeatherGrabber Application")
|
|
71
|
+
if params.keep_open:
|
|
72
|
+
self.logger.info("Keep open mode enabled, the application will refresh every 5 minutes")
|
|
73
|
+
while True:
|
|
74
|
+
self.controller.execute(params)
|
|
75
|
+
sleep(300) # Sleep for 5 minutes
|
|
76
|
+
else:
|
|
77
|
+
self.controller.execute(params)
|
|
78
|
+
self.logger.info("WeatherGrabber Application finished")
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: weathergrabber
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A grabber for weather.com data with various output formats.
|
|
5
|
+
Author-email: Carlos Anselmo Mendes Junior <cjuniorfox@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: homepage, https://github.com/cjuniorfox/weather
|
|
8
|
+
Project-URL: repository, https://github.com/cjuniorfox/weather
|
|
9
|
+
Requires-Python: >=3.12
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: pyquery>=2.0.1
|
|
12
|
+
Requires-Dist: requests>=2.32.5
|
|
13
|
+
|
|
14
|
+
# Weather Forecast CLI Script
|
|
15
|
+
|
|
16
|
+
## Overview
|
|
17
|
+
|
|
18
|
+
This script fetches and parses weather forecast data from Weather.com and formats it for display in various environments such as a terminal or Waybar, a status bar tool. It leverages `pyquery` for HTML parsing and provides detailed weather information, including hourly and daily predictions, formatted for ease of use.
|
|
19
|
+
|
|
20
|
+
### Waybar widget
|
|
21
|
+
|
|
22
|
+

|
|
23
|
+
|
|
24
|
+
### Terminal Console
|
|
25
|
+
|
|
26
|
+

|
|
27
|
+
|
|
28
|
+
## Features
|
|
29
|
+
|
|
30
|
+
- Retrieves current weather and forecasts for a specified location.
|
|
31
|
+
- Displays weather data in different formats:
|
|
32
|
+
- **Console output**: Richly formatted weather data with icons.
|
|
33
|
+
- **Waybar JSON**: For integration with Waybar.
|
|
34
|
+
- Supports multiple languages for Weather.com data.
|
|
35
|
+
- Includes data such as:
|
|
36
|
+
- Current temperature and "feels-like" temperature.
|
|
37
|
+
- Wind speed, humidity, visibility, and air quality.
|
|
38
|
+
- Hourly and daily forecasts with icons and precipitation chances.
|
|
39
|
+
|
|
40
|
+
## Requirements
|
|
41
|
+
|
|
42
|
+
Requires Python 3 or newer.
|
|
43
|
+
|
|
44
|
+
Install the package and all dependencies with:
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
pip install .
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Or, for development (editable install with dev dependencies):
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
pip install -e .[dev]
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Output Formats
|
|
57
|
+
|
|
58
|
+
#### Console Output
|
|
59
|
+
|
|
60
|
+
The script displays a formatted weather summary, including:
|
|
61
|
+
|
|
62
|
+
- Current weather status.
|
|
63
|
+
- Temperature (current, max/min).
|
|
64
|
+
- Wind, humidity, visibility, and air quality.
|
|
65
|
+
- Hourly and daily forecasts with icons.
|
|
66
|
+
|
|
67
|
+
#### Waybar JSON
|
|
68
|
+
|
|
69
|
+
The JSON includes:
|
|
70
|
+
|
|
71
|
+
- `text`: Current weather icon and temperature.
|
|
72
|
+
- `alt`: Weather status.
|
|
73
|
+
- `tooltip`: Detailed weather information.
|
|
74
|
+
- `class`: Status code for further customization.
|
|
75
|
+
|
|
76
|
+
### JSON General output
|
|
77
|
+
|
|
78
|
+
Here the following [JSON Schema](schema.json) for this output.
|
|
79
|
+
|
|
80
|
+
The key values for this json is:
|
|
81
|
+
|
|
82
|
+
- `temperature`: An object containing the temperature information with the following fields:
|
|
83
|
+
- `current`: The current temperature.
|
|
84
|
+
- `feel`: The temperature feel
|
|
85
|
+
- `max` : The maximum temperature
|
|
86
|
+
- `min` : The minimum temperature
|
|
87
|
+
|
|
88
|
+
There's also other fields like `hourly_predictions` and `daily_predictions` containing lists of predictions informations. More defaults on [JSON Schema](schema.json).
|
|
89
|
+
|
|
90
|
+
### Integration with Waybar
|
|
91
|
+
|
|
92
|
+
To integrate the script with Waybar:
|
|
93
|
+
|
|
94
|
+
1. Add a custom script module in Waybar's configuration:
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"modules-left": ["custom/weather"],
|
|
99
|
+
"custom/weather": {
|
|
100
|
+
"exec": "weathergrabber --output waybar",
|
|
101
|
+
"interval": 600
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
2. Reload Waybar to apply the changes.
|
|
107
|
+
|
|
108
|
+
## Error Handling
|
|
109
|
+
|
|
110
|
+
- Validates `weather_id` and `lang` inputs.
|
|
111
|
+
- Handles HTTP errors gracefully, including 404 errors for invalid locations.
|
|
112
|
+
|
|
113
|
+
## CI & Test Coverage
|
|
114
|
+
|
|
115
|
+

|
|
116
|
+
[](https://codecov.io/gh/cjuniorfox/weather)
|
|
117
|
+
|
|
118
|
+
The test suite is run automatically on every push and pull request using GitHub Actions. Coverage results are uploaded to Codecov and displayed above.
|
|
119
|
+
|
|
120
|
+
To run tests and check coverage locally:
|
|
121
|
+
|
|
122
|
+
```sh
|
|
123
|
+
pytest --cov=weathergrabber --cov-report=term
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
|
|
128
|
+
This script is open-source and available under the MIT License.
|
|
129
|
+
|
|
130
|
+
## CLI Usage
|
|
131
|
+
|
|
132
|
+
You can run the CLI as an installed command:
|
|
133
|
+
|
|
134
|
+
```sh
|
|
135
|
+
weathergrabber [location_name] [options]
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Or as a Python module:
|
|
139
|
+
|
|
140
|
+
```sh
|
|
141
|
+
python -m weathergrabber.cli [location_name] [options]
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Arguments
|
|
145
|
+
|
|
146
|
+
- `location_name` (positional, optional): City name, zip code, etc. If not provided, you can use `--location-id` or the `WEATHER_LOCATION_ID` environment variable.
|
|
147
|
+
|
|
148
|
+
### Options
|
|
149
|
+
|
|
150
|
+
- `--location-id`, `-l` : 64-character-hex code for location (from Weather.com)
|
|
151
|
+
- `--lang`, `-L` : Language (e.g., `pt-BR`, `fr-FR`). Defaults to system locale if not set.
|
|
152
|
+
- `--output`, `-o` : Output format. One of `console`, `json`, or `waybar`. Default: `console`.
|
|
153
|
+
- `--keep-open`, `-k` : Keep open and refresh every 5 minutes (only makes sense for `console` output).
|
|
154
|
+
- `--icons`, `-i` : Icon set. `fa` for Font-Awesome, `emoji` for emoji icons. Default: `emoji`.
|
|
155
|
+
- `--log` : Set logging level. One of `debug`, `info`, `warning`, `error`, `critical`. Default: `critical`.
|
|
156
|
+
|
|
157
|
+
### Environment Variables
|
|
158
|
+
|
|
159
|
+
- `LANG` : Used as default language if `--lang` is not set.
|
|
160
|
+
- `WEATHER_LOCATION_ID` : Used as default location if neither `location_name` nor `--location-id` is set.
|
|
161
|
+
|
|
162
|
+
### Example Usage
|
|
163
|
+
|
|
164
|
+
```sh
|
|
165
|
+
weathergrabber "London" --output console --lang en-GB
|
|
166
|
+
weathergrabber --location-id 1234567890abcdef... --output json
|
|
167
|
+
weathergrabber "Paris" -o waybar -i fa
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Or as a Python module:
|
|
171
|
+
|
|
172
|
+
```sh
|
|
173
|
+
python -m weathergrabber.cli "London" --output console --lang en-GB
|
|
174
|
+
python -m weathergrabber.cli --location-id 1234567890abcdef... --output json
|
|
175
|
+
python -m weathergrabber.cli "Paris" -o waybar -i fa
|
|
176
|
+
```
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
weathergrabber/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
weathergrabber/cli.py,sha256=7-sjeYPVTXZpwvD2VjvBSMA7R9yd6Y_z7NIO5NR62_4,1966
|
|
3
|
+
weathergrabber/core.py,sha256=TiZ2utmYKf9lkIXWv8YBfSdiHZXJZXuHS8B-dBDvevw,1138
|
|
4
|
+
weathergrabber/weathergrabber_application.py,sha256=axNwAKqBVrEZMo98JG-6sEcvf1yjhurzo1zV9WrD4ME,3989
|
|
5
|
+
weathergrabber/adapter/client/weather_api.py,sha256=2i7fosML6yBO4PxD3Z2t4Rv5RfJTQhHNq9Bp-llEwUw,960
|
|
6
|
+
weathergrabber/adapter/client/weather_search_api.py,sha256=1oy7JitHcmwkkhFlD0eIt5A7a4cGbf7LMNi26tR8z5o,1724
|
|
7
|
+
weathergrabber/adapter/tty/console_tty.py,sha256=B5BaAEqpP7aXIxzzBQv47qWcQFRSWo47tQ1Lxa9cbq0,4972
|
|
8
|
+
weathergrabber/adapter/tty/json_tty.py,sha256=ZRZSfVhFYsb8AXD1qjb1GBpaRMxHGNaMXW5VN8AvdaU,647
|
|
9
|
+
weathergrabber/adapter/tty/waybar_tty.py,sha256=_lyeA4bOidGzlz9xBKCkZPEssscQaO8SAPn-eryvm_I,5294
|
|
10
|
+
weathergrabber/domain/air_quality_index.py,sha256=eH1glUdedtbRCZMcw5_zB5ybJQUcyY-EBMxL8vVJ6zA,2791
|
|
11
|
+
weathergrabber/domain/city_location.py,sha256=usXijW59uI1RhgmR9QO6L9ttIkbzwqtwJAsRgaTbYaw,1209
|
|
12
|
+
weathergrabber/domain/color.py,sha256=dkWZnQ2BjXUV8qTfZnmNGng_cqKtKuDdekYw4R2NJ9U,1080
|
|
13
|
+
weathergrabber/domain/current_conditions.py,sha256=S14j2lL_gc2pds0wbl5M4_72PbVdubOuX8lnE_tZVTg,1477
|
|
14
|
+
weathergrabber/domain/daily_predictions.py,sha256=8pzmaU-yugS2Hx_6MBWAk24wlSISe1OQIRrCK_lfvbw,1618
|
|
15
|
+
weathergrabber/domain/day_night.py,sha256=7geOuVH9xDb1ecLKnZmj6eqp3H3kp9G0YY497cEyPtg,1442
|
|
16
|
+
weathergrabber/domain/forecast.py,sha256=CO0VTdMI0kjuwbpwwDNRCf_82oNtw5QkN-9GgobPet8,2399
|
|
17
|
+
weathergrabber/domain/health_activities.py,sha256=wCG82-lBfrnIwxptgnMZt-fXHJsBnIl4YLpoeFOmC48,1490
|
|
18
|
+
weathergrabber/domain/hourly_predictions.py,sha256=PFiGuo7mBoQHLR-bDirV--R126YAOzkwIHgaeRGFQSA,2179
|
|
19
|
+
weathergrabber/domain/label_value.py,sha256=LvrvZbSrcEUUpxvKAmYkFnpDFWsa6LFmGA9O8i6HB84,446
|
|
20
|
+
weathergrabber/domain/moon_phase.py,sha256=sEHRi8yPIHTzaEcOkz556uNYsKLq4YmYa5qDncSX44c,587
|
|
21
|
+
weathergrabber/domain/moon_phase_enum.py,sha256=MXxFjBjy4bq4SML4L-5DRStKJMWtvAjTtcYMO3nvAw0,2091
|
|
22
|
+
weathergrabber/domain/precipitation.py,sha256=eXrpwMOsEJWGqV4bEBhN9niWYXalgdZRLG4-I39JZ2A,466
|
|
23
|
+
weathergrabber/domain/search.py,sha256=j3BzskyPl0hDWV02XTOC4tJonV5RHxr5Rop_rYMKUtA,387
|
|
24
|
+
weathergrabber/domain/sunrise_sunset.py,sha256=wNTk01NIuLbQ7gN_giAFv4f3FaRx9khul-mj19g57vE,1207
|
|
25
|
+
weathergrabber/domain/temperature_hight_low.py,sha256=PQOJ5uDtfMRBR5yMxXA46xuorJC08jva2C0-WAV5yxs,909
|
|
26
|
+
weathergrabber/domain/timestamp.py,sha256=tbUdTiWUf1mKhVq9SESeFGlu_sZ2FdmQCb1gr7u9I0c,1032
|
|
27
|
+
weathergrabber/domain/today_details.py,sha256=EUlV7xerYw5QhEsBfvO5m6-9Ghm4nPkXJz9zCmSYTbA,2398
|
|
28
|
+
weathergrabber/domain/uv_index.py,sha256=lYZnHAWwnu_clOh_T5j-OKDcmLJALa24ROY9L1d8lO8,1422
|
|
29
|
+
weathergrabber/domain/weather_icon_enum.py,sha256=xAyZxEDjIBHzy4WIgcWEw3vhDlL8uVJIL3piG9dNcr0,2309
|
|
30
|
+
weathergrabber/domain/wind.py,sha256=wTDz3X1rYsnw_eNoDi1miwaomxwhiJkY_q6xrdZtLak,789
|
|
31
|
+
weathergrabber/domain/adapter/icon_enum.py,sha256=YxGYS5vBRV2AiAfeuPOdqaQOHixAssiMbOzQnTmdSBg,84
|
|
32
|
+
weathergrabber/domain/adapter/output_enum.py,sha256=61iR10ppY8DNALPKV-vLnDQni5HxEzpoRNZbdBdRygk,117
|
|
33
|
+
weathergrabber/domain/adapter/params.py,sha256=4ts2m0cQ0LpgWXS81G36aRDER4e5LEKXad2sIcxbuWY,1668
|
|
34
|
+
weathergrabber/domain/adapter/mapper/air_quality_index_mapper.py,sha256=buAiqYDzHiUVVo18tzRoRG5lFkM26c07qIKPc9RDwGQ,454
|
|
35
|
+
weathergrabber/domain/adapter/mapper/city_location_mapper.py,sha256=NkcsrZ9nEJhqs4y07_VsiagslfsIWnd6AtoYem9o01U,239
|
|
36
|
+
weathergrabber/domain/adapter/mapper/color_mapper.py,sha256=ZU945MVCxL7Z9BS_ztR5iE1O5mR4ZG5PHdfB6fxv8s4,243
|
|
37
|
+
weathergrabber/domain/adapter/mapper/current_conditions_mapper.py,sha256=5UOT8gChe8FmerAXuzch8qN-GNgRxpzCayrtdRKBv_Q,899
|
|
38
|
+
weathergrabber/domain/adapter/mapper/daily_predictions_mapper.py,sha256=mYTilW4N8nrE7CT2VNiAayPuAj0sGXr7Rj36azGQc2E,933
|
|
39
|
+
weathergrabber/domain/adapter/mapper/day_night_mapper.py,sha256=M1dyVTThxJLgRhfASPIVCfAQ9E60c3kqtsyeI-Bt-d4,377
|
|
40
|
+
weathergrabber/domain/adapter/mapper/forecast_mapper.py,sha256=dEow0pR35Tmfo0nmJBOoaK85kB2ZSzWkGWq78eupHGw,1558
|
|
41
|
+
weathergrabber/domain/adapter/mapper/health_activities_mapper.py,sha256=ABhprwPx9LSiwsSmSdvSdf_hXxnNq7MUTDUNCy1__8A,259
|
|
42
|
+
weathergrabber/domain/adapter/mapper/hourly_predictions_mapper.py,sha256=56S_D_7NZDIffMuTZzI_6Lbh1y0MNmRsCVAps_LVzTE,1013
|
|
43
|
+
weathergrabber/domain/adapter/mapper/label_value_mapper.py,sha256=_hmXtVEFkARUhgn-qbQVwF8asi9pQZnOfnZAIJxXQyE,180
|
|
44
|
+
weathergrabber/domain/adapter/mapper/moon_phase_mapper.py,sha256=aYMPKICqKAX50wKnPNZc5krSlafxwJXnAwd5Nz4mo6I,291
|
|
45
|
+
weathergrabber/domain/adapter/mapper/precipitation_mapper.py,sha256=qTtUkOz-TBuhLSVFtSiHpQrOMcZDSwP6wHK0ClQOctM,199
|
|
46
|
+
weathergrabber/domain/adapter/mapper/search_mapper.py,sha256=yadBVjhh6uUgLZwSa0aeIJ239QOr_CGkxMF36dVbTnI,180
|
|
47
|
+
weathergrabber/domain/adapter/mapper/sunrise_sunset_mapper.py,sha256=OaviTRaY9EhNdjVaTBxTksW8WN3KJhP3bbXETt5ytqA,568
|
|
48
|
+
weathergrabber/domain/adapter/mapper/temperature_high_low_mapper.py,sha256=EDJ6WgIzHbQSUY4Yn9-5bS7UcfTQY8yBMQvhlp7I6NU,240
|
|
49
|
+
weathergrabber/domain/adapter/mapper/timestamp_mapper.py,sha256=2g0PqENQJ1FkGapkV2ojhQu0GzRU5ThQ4tmaSbmA3bY,193
|
|
50
|
+
weathergrabber/domain/adapter/mapper/today_details_mapper.py,sha256=y9F5b3IQXIvbt5PEjLW8M2RGy8vW9EPUpICAp3x87D0,1413
|
|
51
|
+
weathergrabber/domain/adapter/mapper/uv_index_mapper.py,sha256=K3AdRnAPv1Yqudc3eKcw_EBQidNPbHbLcG4lYrQvOOw,230
|
|
52
|
+
weathergrabber/domain/adapter/mapper/weather_icon_enum_mapper.py,sha256=YC7juvt38Ehtb3Y-iQFM77s1EQAv4qNHd6vGOqws6HI,249
|
|
53
|
+
weathergrabber/domain/adapter/mapper/wind_mapper.py,sha256=nXyYwqTvLLMyKtSey27GaGvBV8xVhB_Y3HU0sbmIe_E,149
|
|
54
|
+
weathergrabber/service/extract_aqi_service.py,sha256=IT3S9zZmThdqPtJDBF_G8LDUNlwUYoBh49z_WUlB-tk,1285
|
|
55
|
+
weathergrabber/service/extract_current_conditions_service.py,sha256=5lmnOL1TFA5KH7b8uX87TysPg1i5ufsR_lE1rVlfLYU,2130
|
|
56
|
+
weathergrabber/service/extract_daily_forecast_oldstyle_service.py,sha256=DiKfaGbMnIIBnxI-IYG8HmfLHNAW5AGew5NjFSMqBk4,2190
|
|
57
|
+
weathergrabber/service/extract_daily_forecast_service.py,sha256=ZN61_neEAdn7RPedLvmmFKH7Pq1EQGhG9A9Dy8o09zM,2709
|
|
58
|
+
weathergrabber/service/extract_health_activities_service.py,sha256=2qJ4tEEz5uV6EwHUIFhWM8zol_BbDW3omEohf8kDg3M,1014
|
|
59
|
+
weathergrabber/service/extract_hourly_forecast_oldstyle_service.py,sha256=U5O2tG9YF8wrUUoTimDr7oqMIb670j9oeVjoIpCKKLs,2100
|
|
60
|
+
weathergrabber/service/extract_hourly_forecast_service.py,sha256=MhVweMlvJNQjPdVqw0AAvni3f8TVTq_0PD76NDUUQHM,3079
|
|
61
|
+
weathergrabber/service/extract_temperature_service.py,sha256=46nRO3Izj1QmG4BNTh8flsODsovHyWPzZzOnkl1Gbj4,634
|
|
62
|
+
weathergrabber/service/extract_today_details_service.py,sha256=QAwF7EzVaL1STGNDyxve9r7oQTjvNKhYQ53d_68lbKA,4126
|
|
63
|
+
weathergrabber/service/read_weather_service.py,sha256=7_B8E9IN1KCwOhpuS5PfWazI1sCrDyYrZhkV2R38bhc,649
|
|
64
|
+
weathergrabber/service/search_location_service.py,sha256=tZmVgO45hjwoa4cl5bKPjMBmYlGxJiH_I9Ymb5pwEwU,1422
|
|
65
|
+
weathergrabber/usecase/use_case.py,sha256=wmNmJSiy0t00-KaxQqSryiu0keX0kI4PcLI2hBBHT_0,4460
|
|
66
|
+
weathergrabber-0.0.1.dist-info/METADATA,sha256=gsF7Hub32I8AQGL1aQDbvwW7InBXrpJ91kBQklNhE4k,5375
|
|
67
|
+
weathergrabber-0.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
68
|
+
weathergrabber-0.0.1.dist-info/entry_points.txt,sha256=m2P9a4mrJDTzuNaiTU438NA60GxCfaw7VKvruWw43N8,63
|
|
69
|
+
weathergrabber-0.0.1.dist-info/top_level.txt,sha256=P3NMDJJYRIvQujf994Vb4gZrobkKWkL2gh3NF_ajQWM,15
|
|
70
|
+
weathergrabber-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
weathergrabber
|