weathergrabber 0.0.8b1__tar.gz → 0.0.8b3__tar.gz

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 (82) hide show
  1. {weathergrabber-0.0.8b1/weathergrabber.egg-info → weathergrabber-0.0.8b3}/PKG-INFO +2 -2
  2. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/README.md +1 -1
  3. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/pyproject.toml +1 -1
  4. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/tests/test_cli.py +1 -1
  5. weathergrabber-0.0.8b3/tests/test_cli_version.py +13 -0
  6. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/__init__.py +4 -1
  7. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/adapter/tty/json_tty.py +1 -1
  8. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/cli.py +3 -2
  9. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/moon_phase_enum.py +7 -7
  10. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/uv_index.py +3 -1
  11. weathergrabber-0.0.8b3/weathergrabber/service/extract_hourly_forecast_service.py +58 -0
  12. weathergrabber-0.0.8b3/weathergrabber/service/extract_today_details_service.py +80 -0
  13. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/usecase/use_case.py +7 -7
  14. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3/weathergrabber.egg-info}/PKG-INFO +2 -2
  15. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber.egg-info/SOURCES.txt +1 -0
  16. weathergrabber-0.0.8b1/weathergrabber/service/extract_hourly_forecast_service.py +0 -64
  17. weathergrabber-0.0.8b1/weathergrabber/service/extract_today_details_service.py +0 -85
  18. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/LICENSE +0 -0
  19. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/setup.cfg +0 -0
  20. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/tests/test_core.py +0 -0
  21. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/__main__.py +0 -0
  22. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/adapter/client/weather_api.py +0 -0
  23. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/adapter/client/weather_search_api.py +0 -0
  24. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/adapter/tty/console_tty.py +0 -0
  25. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/adapter/tty/waybar_tty.py +0 -0
  26. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/core.py +0 -0
  27. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/adapter/icon_enum.py +0 -0
  28. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/adapter/mapper/air_quality_index_mapper.py +0 -0
  29. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/adapter/mapper/city_location_mapper.py +0 -0
  30. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/adapter/mapper/color_mapper.py +0 -0
  31. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/adapter/mapper/current_conditions_mapper.py +0 -0
  32. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/adapter/mapper/daily_predictions_mapper.py +0 -0
  33. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/adapter/mapper/day_night_mapper.py +0 -0
  34. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/adapter/mapper/forecast_mapper.py +0 -0
  35. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/adapter/mapper/health_activities_mapper.py +0 -0
  36. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/adapter/mapper/hourly_predictions_mapper.py +0 -0
  37. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/adapter/mapper/label_value_mapper.py +0 -0
  38. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/adapter/mapper/moon_phase_mapper.py +0 -0
  39. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/adapter/mapper/precipitation_mapper.py +0 -0
  40. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/adapter/mapper/search_mapper.py +0 -0
  41. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/adapter/mapper/sunrise_sunset_mapper.py +0 -0
  42. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/adapter/mapper/temperature_high_low_mapper.py +0 -0
  43. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/adapter/mapper/timestamp_mapper.py +0 -0
  44. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/adapter/mapper/today_details_mapper.py +0 -0
  45. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/adapter/mapper/uv_index_mapper.py +0 -0
  46. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/adapter/mapper/weather_icon_enum_mapper.py +0 -0
  47. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/adapter/mapper/wind_mapper.py +0 -0
  48. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/adapter/output_enum.py +0 -0
  49. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/adapter/params.py +0 -0
  50. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/air_quality_index.py +0 -0
  51. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/city_location.py +0 -0
  52. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/color.py +0 -0
  53. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/current_conditions.py +0 -0
  54. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/daily_predictions.py +0 -0
  55. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/day_night.py +0 -0
  56. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/forecast.py +0 -0
  57. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/health_activities.py +0 -0
  58. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/hourly_predictions.py +0 -0
  59. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/label_value.py +0 -0
  60. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/moon_phase.py +0 -0
  61. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/precipitation.py +0 -0
  62. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/search.py +0 -0
  63. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/sunrise_sunset.py +0 -0
  64. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/temperature_hight_low.py +0 -0
  65. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/timestamp.py +0 -0
  66. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/today_details.py +0 -0
  67. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/weather_icon_enum.py +0 -0
  68. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/domain/wind.py +0 -0
  69. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/service/extract_aqi_service.py +0 -0
  70. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/service/extract_current_conditions_service.py +0 -0
  71. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/service/extract_daily_forecast_oldstyle_service.py +0 -0
  72. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/service/extract_daily_forecast_service.py +0 -0
  73. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/service/extract_health_activities_service.py +0 -0
  74. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/service/extract_hourly_forecast_oldstyle_service.py +0 -0
  75. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/service/extract_temperature_service.py +0 -0
  76. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/service/read_weather_service.py +0 -0
  77. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/service/search_location_service.py +0 -0
  78. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber/weathergrabber_application.py +0 -0
  79. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber.egg-info/dependency_links.txt +0 -0
  80. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber.egg-info/entry_points.txt +0 -0
  81. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber.egg-info/requires.txt +0 -0
  82. {weathergrabber-0.0.8b1 → weathergrabber-0.0.8b3}/weathergrabber.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: weathergrabber
3
- Version: 0.0.8b1
3
+ Version: 0.0.8b3
4
4
  Summary: A grabber for weather.com data with various output formats.
5
5
  Author-email: Carlos Anselmo Mendes Junior <cjuniorfox@gmail.com>
6
6
  License: MIT
@@ -115,7 +115,7 @@ To integrate the script with Waybar:
115
115
  ## CI & Test Coverage
116
116
 
117
117
  ![Test Status](https://github.com/cjuniorfox/weathergrabber/actions/workflows/python-package.yml/badge.svg)
118
- [![codecov](https://codecov.io/gh/cjuniorfox/weathergrabber/branch/main/graph/badge.svg)](https://codecov.io/gh/cjuniorfox/weathergrabber)
118
+ [![codecov](https://codecov.io/gh/cjuniorfox/weathergrabber/graph/badge.svg?token=SC5CRMC3YW)](https://codecov.io/gh/cjuniorfox/weathergrabber)
119
119
 
120
120
  The test suite is run automatically on every push and pull request using GitHub Actions. Coverage results are uploaded to Codecov and displayed above.
121
121
 
@@ -100,7 +100,7 @@ To integrate the script with Waybar:
100
100
  ## CI & Test Coverage
101
101
 
102
102
  ![Test Status](https://github.com/cjuniorfox/weathergrabber/actions/workflows/python-package.yml/badge.svg)
103
- [![codecov](https://codecov.io/gh/cjuniorfox/weathergrabber/branch/main/graph/badge.svg)](https://codecov.io/gh/cjuniorfox/weathergrabber)
103
+ [![codecov](https://codecov.io/gh/cjuniorfox/weathergrabber/graph/badge.svg?token=SC5CRMC3YW)](https://codecov.io/gh/cjuniorfox/weathergrabber)
104
104
 
105
105
  The test suite is run automatically on every push and pull request using GitHub Actions. Coverage results are uploaded to Codecov and displayed above.
106
106
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "weathergrabber"
3
- version = "0.0.8b1"
3
+ version = "0.0.8b3"
4
4
  description = "A grabber for weather.com data with various output formats."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -4,7 +4,7 @@ import sys
4
4
 
5
5
  @pytest.fixture
6
6
  def mock_main():
7
- with patch('weathergrabber.cli.main') as m:
7
+ with patch('weathergrabber.cli.weathergrabber.main') as m:
8
8
  yield m
9
9
 
10
10
  def test_cli_location_name(monkeypatch, mock_main):
@@ -0,0 +1,13 @@
1
+ import subprocess
2
+ import sys
3
+ import weathergrabber
4
+
5
+
6
+ def test_cli_version_flag_prints_version():
7
+ result = subprocess.run([
8
+ sys.executable, "-m", "weathergrabber", "-v"
9
+ ], capture_output=True, text=True)
10
+ assert result.returncode in (0, 2)
11
+ output = (result.stdout or "") + (result.stderr or "")
12
+ assert str(weathergrabber.__version__) in output
13
+ assert "Weathergrabber" in output
@@ -4,4 +4,7 @@ from .core import main
4
4
  from .cli import main_cli
5
5
 
6
6
  __all__ = ["main", "main_cli"]
7
- __version__ = "0.0.8b1"
7
+ __version__ = "0.0.8b3"
8
+
9
+ def get_version():
10
+ return __version__
@@ -15,5 +15,5 @@ class JsonTTY:
15
15
  self.logger.info("Executing JSON output")
16
16
  forecast = self.use_case.execute(params)
17
17
  output: dict = forecast_to_dict(forecast)
18
- output_json = json.dumps(output, indent=4)
18
+ output_json = json.dumps(output)
19
19
  print(output_json)
@@ -1,6 +1,6 @@
1
1
  import argparse
2
2
  import os
3
- from weathergrabber import main
3
+ import weathergrabber
4
4
 
5
5
  def main_cli():
6
6
  ## Get current locale, or use the default one
@@ -11,6 +11,7 @@ def main_cli():
11
11
  parser.add_argument("--output", "-o", type=str, choices=['console','json','waybar'], default='console', help="Output format. console, json or waybar")
12
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
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("--version", "-v", action='version', version=f'Weathergrabber {weathergrabber.get_version()}', help="Show version and exit")
14
15
  parser.add_argument(
15
16
  "--log",
16
17
  default="critical",
@@ -25,7 +26,7 @@ def main_cli():
25
26
  if not args.location_id and not args.location_name:
26
27
  location_id = os.getenv('WEATHER_LOCATION_ID')
27
28
 
28
- main(
29
+ weathergrabber.main(
29
30
  log_level=args.log,
30
31
  location_name = args.location_name,
31
32
  location_id = location_id,
@@ -8,9 +8,9 @@ class MoonPhaseEnum(Enum):
8
8
  PHASE_2 = ("phase-2", "\uf186", "🌒")
9
9
  PHASE_3 = ("phase-3", "\uf186", "🌒")
10
10
  PHASE_4 = ("phase-4", "\uf186", "🌒")
11
- PHASE_5 = ("phase-5", "\uf186", "🌒")
12
- PHASE_6 = ("phase-6", "\uf186", "🌒")
13
11
  # First Quarter
12
+ PHASE_5 = ("phase-5", "\uf186", "🌓")
13
+ PHASE_6 = ("phase-6", "\uf186", "🌓")
14
14
  PHASE_7 = ("phase-7", "\uf186", "🌓")
15
15
  # Waxing Gibbous
16
16
  PHASE_8 = ("phase-8", "\uf186", "🌔")
@@ -21,8 +21,8 @@ class MoonPhaseEnum(Enum):
21
21
  PHASE_13 = ("phase-13", "\uf186", "🌕")
22
22
  # Full Moon
23
23
  PHASE_14 = ("phase-14", "\uf186", "🌕")
24
- # Waning Gibbous
25
24
  PHASE_15 = ("phase-15", "\uf186", "🌕")
25
+ # Waning Gibbous
26
26
  PHASE_16 = ("phase-16", "\uf186", "🌖")
27
27
  PHASE_17 = ("phase-17", "\uf186", "🌖")
28
28
  PHASE_18 = ("phase-18", "\uf186", "🌖")
@@ -30,10 +30,10 @@ class MoonPhaseEnum(Enum):
30
30
  PHASE_20 = ("phase-20", "\uf186", "🌖")
31
31
  # Last Quarter
32
32
  PHASE_21 = ("phase-21", "\uf186", "🌗")
33
- # Waning Crescent
34
- PHASE_22 = ("phase-22", "\uf186", "🌘")
35
- PHASE_23 = ("phase-23", "\uf186", "🌘")
36
- PHASE_24 = ("phase-24", "\uf186", "🌘")
33
+ PHASE_22 = ("phase-22", "\uf186", "🌗")
34
+ PHASE_23 = ("phase-23", "\uf186", "🌗")
35
+ PHASE_24 = ("phase-24", "\uf186", "🌗")
36
+
37
37
  PHASE_25 = ("phase-25", "\uf186", "🌘")
38
38
  PHASE_26 = ("phase-26", "\uf186", "🌘")
39
39
  PHASE_27 = ("phase-27", "\uf186", "🌘")
@@ -30,8 +30,10 @@ class UVIndex:
30
30
  elif len(parts) == 3:
31
31
  index, of, some = parts
32
32
  return cls(string_value = data, index=index.strip(), of=some.strip(), label=label)
33
+ elif len(data) == 0:
34
+ raise ValueError("UV Index string cannot be empty")
33
35
  else:
34
- raise ValueError(f"Cannot parse UV Index from string: {data}")
36
+ return cls(string_value = data, index="", of="", label=label)
35
37
 
36
38
  def __repr__(self) -> str:
37
39
  return f"UVIndex(string_value={self.string_value!r}, index={self.index!r}, of={self.of!r}, label={self.label!r})"
@@ -0,0 +1,58 @@
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
+ self.logger.debug("Extracting hourly forecast...")
18
+
19
+ data = weather_data.find("section[data-testid='HourlyForecast'] div[class*='Card'] details")
20
+
21
+ if len(data) == 0:
22
+ raise ValueError("There's no hourly forecast data available.")
23
+
24
+ details = [ {
25
+ "title": PyQuery(item).find("h2").text(),
26
+ "temperature" : PyQuery(item).find("div[data-testid='detailsTemperature']").text(),
27
+ "icon" : PyQuery(item).find("svg[class*='DetailsSummary']").attr("name"),
28
+ "summary" : PyQuery(item).find("span[class*='DetailsSummary--wxPhrase']").text(),
29
+ "precip-percentage": PyQuery(item).find("div[data-testid='Precip'] span[data-testid='PercentageValue']").text(),
30
+ "wind": PyQuery(item).find("span[data-testid='WindTitle']").next().eq(0).text(),
31
+ "feels-like" : PyQuery(item).find("span[data-testid='FeelsLikeTitle']").next().text(),
32
+ "humidity" : PyQuery(item).find("span[data-testid='HumidityTitle']").next().text(),
33
+ "uv-index" : PyQuery(item).find("span[data-testid='UVIndexValue']").text(),
34
+ "cloud-cover" : PyQuery(item).find("span[data-testid='CloudCoverTitle']").next().text(),
35
+ "rain-amount" : PyQuery(item).find("span[data-testid='AccumulationTitle']").next().text()
36
+ } for item in data ]
37
+
38
+ self.logger.debug("Extracted %s register(s)...",len(details))
39
+
40
+ hourly_forecasts = [HourlyPredictions(
41
+ title=item["title"],
42
+ temperature=item["temperature"],
43
+ icon=WeatherIconEnum.from_name(item["icon"]),
44
+ summary=item["summary"],
45
+ precipitation=Precipitation(
46
+ percentage=item["precip-percentage"],
47
+ amount=item["rain-amount"]
48
+ ),
49
+ wind=Wind.from_string(item["wind"]),
50
+ feels_like=item["feels-like"],
51
+ humidity=item["humidity"],
52
+ uv_index=UVIndex.from_string(item["uv-index"]),
53
+ cloud_cover=item["cloud-cover"]
54
+ ) for item in details]
55
+
56
+ self.logger.debug("Created hourly forecast list with %s registers", len(hourly_forecasts))
57
+
58
+ return hourly_forecasts
@@ -0,0 +1,80 @@
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
+ self.logger.debug("Extracting today's details...")
18
+
19
+ today_details_data = weather_data.find("div#todayDetails")
20
+ feelslike = PyQuery(today_details_data).find("div[data-testid='FeelsLikeSection'] span")
21
+ sunrise_sunset = PyQuery(today_details_data).find("div[data-testid='sunriseSunsetContainer'] div p[class*='TwcSunChart']")
22
+
23
+ feelslike_label = feelslike.eq(0).text() #'Feels Like'
24
+ feelslike_value = feelslike.eq(1).text() #'60°'
25
+
26
+ sunrise = sunrise_sunset.eq(0).text() #'6:12 AM'
27
+ sunset = sunrise_sunset.eq(1).text() #'7:45 PM'
28
+
29
+ icons = today_details_data.find('svg[class*="WeatherDetailsListItem--icon"]')
30
+ labels = today_details_data.find('div[class*="WeatherDetailsListItem--label"]')
31
+ values = today_details_data.find('div[data-testid="wxData"]')
32
+
33
+ self.logger.debug(f"Parsing today details values...")
34
+ high_low_label = labels.eq(0).text() #'High / Low'
35
+ high_low_value = values.eq(0).text() #'--/54°'
36
+
37
+ wind_label = labels.eq(1).text() #'Wind'
38
+ wind_value = values.eq(1).text() #'7\xa0mph'
39
+
40
+ humidity_label = labels.eq(2).text() #'Humidity'
41
+ humidity_value = values.eq(2).text() #'100%'
42
+
43
+ dew_point_label = labels.eq(3).text() #'Dew Point'
44
+ dew_point_value = values.eq(3).text() #'60°'
45
+
46
+ pressure_label = labels.eq(4).text() #'Pressure'
47
+ pressure_value = values.eq(4).text() #'30.31\xa0in'
48
+
49
+ uv_index_label = labels.eq(5).text() #'UV Index'
50
+ uv_index_value = values.eq(5).text() #'5 of 10'
51
+
52
+ visibility_label = labels.eq(6).text() #'Visibility'
53
+ visibility_value = values.eq(6).text() #'10.0 mi'
54
+
55
+ moon_phase_label = labels.eq(7).text() #'Moon Phase'
56
+ moon_phase_icon = icons.eq(7).attr('name') #'phase-2'
57
+ moon_phase_value = values.eq(7).text() #'Waxing Crescent'
58
+
59
+ self.logger.debug(f"Creating domain objects for today details...")
60
+
61
+ sunrise_sunset = SunriseSunset(sunrise=sunrise, sunset=sunset)
62
+ high_low = TemperatureHighLow.from_string(high_low_value, label=high_low_label)
63
+ uv_index = UVIndex.from_string(uv_index_value, label=uv_index_label)
64
+ moon_phase = MoonPhase(MoonPhaseEnum.from_name(moon_phase_icon), moon_phase_value, moon_phase_label)
65
+
66
+ today_details = TodayDetails(
67
+ feelslike=LabelValue(label=feelslike_label, value=feelslike_value),
68
+ sunrise_sunset=sunrise_sunset,
69
+ high_low=high_low,
70
+ wind=LabelValue(label=wind_label, value=wind_value),
71
+ humidity=LabelValue(label=humidity_label, value=humidity_value),
72
+ dew_point=LabelValue(label=dew_point_label, value=dew_point_value),
73
+ pressure=LabelValue(label=pressure_label, value=pressure_value),
74
+ uv_index=uv_index,
75
+ visibility=LabelValue(label=visibility_label, value=visibility_value),
76
+ moon_phase=moon_phase
77
+ )
78
+
79
+ self.logger.debug(f"Extracted today's details: {today_details}")
80
+ return today_details
@@ -57,16 +57,16 @@ class UseCase:
57
57
  health_activities = self.extract_health_activities_service.execute(weather_data)
58
58
 
59
59
  try:
60
- hourly_predictions = self.extract_hourly_forecast_service.execute(weather_data)
61
- except ValueError:
62
- self.logger.warning("Falling back to old style hourly forecast extraction")
63
60
  hourly_predictions = self.extract_hourly_forecast_oldstyle_service.execute(weather_data)
64
-
65
- try:
66
- daily_predictions = self.extract_daily_forecast_service.execute(weather_data)
67
61
  except ValueError:
68
- self.logger.warning("Falling back to old style daily forecast extraction")
62
+ self.logger.warning("Falling back to new style hourly forecast extraction")
63
+ hourly_predictions = self.extract_hourly_forecast_service.execute(weather_data)
64
+
65
+ try:
69
66
  daily_predictions = self.extract_daily_forecast_oldstyle_service.execute(weather_data)
67
+ except ValueError:
68
+ self.logger.warning("Falling back to new style daily forecast extraction")
69
+ daily_predictions = self.extract_daily_forecast_service.execute(weather_data)
70
70
 
71
71
  forecast = Forecast(
72
72
  search = Search(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: weathergrabber
3
- Version: 0.0.8b1
3
+ Version: 0.0.8b3
4
4
  Summary: A grabber for weather.com data with various output formats.
5
5
  Author-email: Carlos Anselmo Mendes Junior <cjuniorfox@gmail.com>
6
6
  License: MIT
@@ -115,7 +115,7 @@ To integrate the script with Waybar:
115
115
  ## CI & Test Coverage
116
116
 
117
117
  ![Test Status](https://github.com/cjuniorfox/weathergrabber/actions/workflows/python-package.yml/badge.svg)
118
- [![codecov](https://codecov.io/gh/cjuniorfox/weathergrabber/branch/main/graph/badge.svg)](https://codecov.io/gh/cjuniorfox/weathergrabber)
118
+ [![codecov](https://codecov.io/gh/cjuniorfox/weathergrabber/graph/badge.svg?token=SC5CRMC3YW)](https://codecov.io/gh/cjuniorfox/weathergrabber)
119
119
 
120
120
  The test suite is run automatically on every push and pull request using GitHub Actions. Coverage results are uploaded to Codecov and displayed above.
121
121
 
@@ -2,6 +2,7 @@ LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
4
  tests/test_cli.py
5
+ tests/test_cli_version.py
5
6
  tests/test_core.py
6
7
  weathergrabber/__init__.py
7
8
  weathergrabber/__main__.py
@@ -1,64 +0,0 @@
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
@@ -1,85 +0,0 @@
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