weathergrabber 0.0.8b3__tar.gz → 0.0.8b5__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 (83) hide show
  1. {weathergrabber-0.0.8b3/weathergrabber.egg-info → weathergrabber-0.0.8b5}/PKG-INFO +1 -1
  2. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/pyproject.toml +1 -1
  3. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/tests/test_cli.py +9 -0
  4. weathergrabber-0.0.8b5/tests/test_main.py +11 -0
  5. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/__init__.py +1 -1
  6. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/__main__.py +1 -1
  7. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/adapter/tty/console_tty.py +2 -2
  8. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/adapter/tty/waybar_tty.py +6 -6
  9. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/cli.py +0 -3
  10. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/air_quality_index.py +17 -19
  11. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/city_location.py +5 -4
  12. weathergrabber-0.0.8b5/weathergrabber/domain/color.py +55 -0
  13. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/health_activities.py +3 -3
  14. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/moon_phase_enum.py +1 -0
  15. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/uv_index.py +3 -3
  16. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/weather_icon_enum.py +1 -0
  17. weathergrabber-0.0.8b5/weathergrabber/service/extract_aqi_service.py +27 -0
  18. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5/weathergrabber.egg-info}/PKG-INFO +1 -1
  19. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber.egg-info/SOURCES.txt +1 -0
  20. weathergrabber-0.0.8b3/weathergrabber/domain/color.py +0 -43
  21. weathergrabber-0.0.8b3/weathergrabber/service/extract_aqi_service.py +0 -30
  22. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/LICENSE +0 -0
  23. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/README.md +0 -0
  24. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/setup.cfg +0 -0
  25. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/tests/test_cli_version.py +0 -0
  26. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/tests/test_core.py +0 -0
  27. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/adapter/client/weather_api.py +0 -0
  28. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/adapter/client/weather_search_api.py +0 -0
  29. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/adapter/tty/json_tty.py +0 -0
  30. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/core.py +0 -0
  31. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/adapter/icon_enum.py +0 -0
  32. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/adapter/mapper/air_quality_index_mapper.py +0 -0
  33. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/adapter/mapper/city_location_mapper.py +0 -0
  34. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/adapter/mapper/color_mapper.py +0 -0
  35. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/adapter/mapper/current_conditions_mapper.py +0 -0
  36. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/adapter/mapper/daily_predictions_mapper.py +0 -0
  37. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/adapter/mapper/day_night_mapper.py +0 -0
  38. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/adapter/mapper/forecast_mapper.py +0 -0
  39. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/adapter/mapper/health_activities_mapper.py +0 -0
  40. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/adapter/mapper/hourly_predictions_mapper.py +0 -0
  41. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/adapter/mapper/label_value_mapper.py +0 -0
  42. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/adapter/mapper/moon_phase_mapper.py +0 -0
  43. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/adapter/mapper/precipitation_mapper.py +0 -0
  44. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/adapter/mapper/search_mapper.py +0 -0
  45. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/adapter/mapper/sunrise_sunset_mapper.py +0 -0
  46. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/adapter/mapper/temperature_high_low_mapper.py +0 -0
  47. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/adapter/mapper/timestamp_mapper.py +0 -0
  48. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/adapter/mapper/today_details_mapper.py +0 -0
  49. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/adapter/mapper/uv_index_mapper.py +0 -0
  50. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/adapter/mapper/weather_icon_enum_mapper.py +0 -0
  51. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/adapter/mapper/wind_mapper.py +0 -0
  52. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/adapter/output_enum.py +0 -0
  53. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/adapter/params.py +0 -0
  54. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/current_conditions.py +0 -0
  55. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/daily_predictions.py +0 -0
  56. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/day_night.py +0 -0
  57. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/forecast.py +0 -0
  58. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/hourly_predictions.py +0 -0
  59. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/label_value.py +0 -0
  60. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/moon_phase.py +0 -0
  61. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/precipitation.py +0 -0
  62. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/search.py +0 -0
  63. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/sunrise_sunset.py +0 -0
  64. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/temperature_hight_low.py +0 -0
  65. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/timestamp.py +0 -0
  66. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/today_details.py +0 -0
  67. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/domain/wind.py +0 -0
  68. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/service/extract_current_conditions_service.py +0 -0
  69. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/service/extract_daily_forecast_oldstyle_service.py +0 -0
  70. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/service/extract_daily_forecast_service.py +0 -0
  71. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/service/extract_health_activities_service.py +0 -0
  72. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/service/extract_hourly_forecast_oldstyle_service.py +0 -0
  73. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/service/extract_hourly_forecast_service.py +0 -0
  74. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/service/extract_temperature_service.py +0 -0
  75. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/service/extract_today_details_service.py +0 -0
  76. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/service/read_weather_service.py +0 -0
  77. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/service/search_location_service.py +0 -0
  78. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/usecase/use_case.py +0 -0
  79. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber/weathergrabber_application.py +0 -0
  80. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber.egg-info/dependency_links.txt +0 -0
  81. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber.egg-info/entry_points.txt +0 -0
  82. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/weathergrabber.egg-info/requires.txt +0 -0
  83. {weathergrabber-0.0.8b3 → weathergrabber-0.0.8b5}/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.8b3
3
+ Version: 0.0.8b5
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "weathergrabber"
3
- version = "0.0.8b3"
3
+ version = "0.0.8b5"
4
4
  description = "A grabber for weather.com data with various output formats."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -1,3 +1,4 @@
1
+ from asyncio import subprocess
1
2
  import pytest
2
3
  from unittest.mock import patch, MagicMock
3
4
  import sys
@@ -73,3 +74,11 @@ def test_cli_location_id_env(monkeypatch, mock_main):
73
74
  main_cli()
74
75
  args = mock_main.call_args[1]
75
76
  assert args["location_id"] == "envlocationid"
77
+
78
+ def test_cli_log_level(monkeypatch, mock_main):
79
+ test_args = ["weathergrabber", "Tokyo", "--log", "debug"]
80
+ monkeypatch.setattr(sys, "argv", test_args)
81
+ from weathergrabber.cli import main_cli
82
+ main_cli()
83
+ args = mock_main.call_args[1]
84
+ assert args["log_level"] == "debug"
@@ -0,0 +1,11 @@
1
+ import subprocess
2
+ import sys
3
+
4
+ def test_main_entrypoint():
5
+ result = subprocess.run(
6
+ [sys.executable, "-m", "weathergrabber", "--version"],
7
+ capture_output=True,
8
+ text=True
9
+ )
10
+ assert result.returncode == 0
11
+ assert "Weathergrabber" in result.stdout
@@ -4,7 +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.8b3"
7
+ __version__ = "0.0.8b5"
8
8
 
9
9
  def get_version():
10
10
  return __version__
@@ -1,4 +1,4 @@
1
- """weathergrabber CLI entry point."""
2
1
  from .cli import main_cli
2
+
3
3
  if __name__ == "__main__":
4
4
  main_cli()
@@ -82,7 +82,7 @@ class ConsoleTTY:
82
82
  # Hourly predictions and daily predictions
83
83
  hourly_predictions = [
84
84
  f"{h['title']}"
85
- f"{'\t' if len(h['title']) < 3 else ''}\t"
85
+ f"{'\t\t' if len(h['title']) < 3 else '\t'}"
86
86
  f"{h['temperature']}"
87
87
  "\t"
88
88
  f"{h['icon']}\t"
@@ -92,7 +92,7 @@ class ConsoleTTY:
92
92
 
93
93
  daily_predictions = [
94
94
  f"{d['title']}"
95
- f"{'\t' if len(d['title']) < 3 else ''}\t"
95
+ f"{'\t\t' if len(d['title']) < 3 else '\t'}"
96
96
  f"{d['high_low']}"
97
97
  f"\t"
98
98
  f"{d['icon']}\t"
@@ -62,12 +62,12 @@ class WaybarTTY:
62
62
 
63
63
  #Air quality index
64
64
  color = forecast.air_quality_index.color.hex
65
- aqi_category = f" <span color=\"{color}\">{forecast.air_quality_index.category}</span>"
65
+ aqi_category = f" <span color=\"#{color}\">{forecast.air_quality_index.category}</span>"
66
66
  aqi_acronym = forecast.air_quality_index.acronym
67
67
  aqi_value = forecast.air_quality_index.value
68
68
 
69
69
  hourly_predictions_format = [{
70
- 'title': h.title if len(h.title) < 10 else h.title[:9] + '.',
70
+ 'title': h.title if len(h.title) < 9 else h.title[:8] + '.',
71
71
  'temperature' : h.temperature,
72
72
  'icon': h.icon.fa_icon if is_fa else h.icon.emoji_icon,
73
73
  'precipitation': f"{h.precipitation.percentage if h.precipitation.percentage else ''}"
@@ -75,7 +75,7 @@ class WaybarTTY:
75
75
 
76
76
  daily_predictions_format = [
77
77
  {
78
- 'title': d.title if len(d.title) < 10 else d.title[:9] + '.',
78
+ 'title': d.title if len(d.title) < 9 else d.title[:8] + '.',
79
79
  'high_low': f"{d.high_low.high}/<span size='small'>{d.high_low.low}</span>",
80
80
  'icon': d.icon.fa_icon if is_fa else d.icon.emoji_icon,
81
81
  'precipitation': f"{d.precipitation.percentage}"
@@ -85,7 +85,7 @@ class WaybarTTY:
85
85
  # Hourly predictions and daily predictions
86
86
  hourly_predictions = [
87
87
  f"{h['title']}"
88
- f"{'\t\t' if len(h['title']) < 4 else '\t'}"
88
+ f"{'\t\t' if len(h['title']) < 5 else '\t'}"
89
89
  f"{h['temperature']}"
90
90
  "\t\t"
91
91
  f"{h['icon']}\t"
@@ -95,9 +95,9 @@ class WaybarTTY:
95
95
 
96
96
  daily_predictions = [
97
97
  f"{d['title']}"
98
- f"\t{'\t' if len(d['title']) < 6 else ' '}"
98
+ f"{'\t\t' if len(d['title']) < 5 else '\t'}"
99
99
  f"{d['high_low']}"
100
- f"\t{'\t' if len(d['high_low']) < 33 else ''}"
100
+ f"{'\t\t' if len(d['high_low']) < 32 else '\t'}"
101
101
  f"{d['icon']}\t"
102
102
  f"{rain_icon} {d['precipitation']}"
103
103
  for d in daily_predictions_format
@@ -35,6 +35,3 @@ def main_cli():
35
35
  keep_open=args.keep_open,
36
36
  icons=args.icons
37
37
  )
38
-
39
- if __name__ == "__main__":
40
- main_cli()
@@ -45,34 +45,32 @@ class AirQualityIndex:
45
45
  return f"Title: {self.title}. AQI: {self.value}, Category: {self.category}, Description: {self.description}, Acronym: {self.acronym}, Color: {self.color}"
46
46
 
47
47
  def __repr__(self) -> str:
48
- return f"AirQualityIndex(title={self.title}, value={self.value}, category={self.category}, description={self.description}, acronym:{self.acronym}, color={self.color})"
48
+ return f"AirQualityIndex(title='{self.title}', value={self.value}, category='{self.category}', description='{self.description}', acronym='{self.acronym}', color='{self.color}')"
49
49
 
50
50
  @staticmethod
51
51
  def _extract_aqi(data: str):
52
- parts = data.split('\n')
53
- title = parts[0].strip()
54
- aqi = int(parts[1].strip())
55
- category = parts[2].strip() if len(parts) > 2 else None
56
- description = parts[3].strip() if len(parts) > 3 else None
57
- acronym = ''.join(word[0].strip().upper() for word in title.split())
52
+ try:
53
+ parts = data.split('\n')
54
+ title = parts[0].strip()
55
+ aqi = int(parts[1].strip())
56
+ category = parts[2].strip() if len(parts) > 2 else None
57
+ description = parts[3].strip() if len(parts) > 3 else None
58
+ acronym = ''.join(word[0].strip().upper() for word in title.split())
58
59
 
59
- return title, aqi, category, description, acronym
60
+ return title, aqi, category, description, acronym
61
+ except (ValueError, IndexError) as e:
62
+ raise ValueError("Invalid AQI data format") from e
60
63
 
61
64
  # 'Air Quality Index\n26\nGood\nAir quality is considered satisfactory, and air pollution poses little or no risk.'
62
65
  @classmethod
63
66
  def from_string(cls, data: str) -> 'AirQualityIndex':
64
- try:
65
- title, aqi, category, description, acronym = AirQualityIndex._extract_aqi(data)
66
- return cls(title, aqi, category, description, acronym)
67
- except (ValueError, IndexError) as e:
68
- raise ValueError("Invalid AQI data format") from e
67
+ title, aqi, category, description, acronym = AirQualityIndex._extract_aqi(data)
68
+ return cls(title, aqi, category, description, acronym)
69
69
 
70
70
  @classmethod
71
71
  def aqi_color_from_string(cls, aqi_data: str, color_data: str):
72
- try:
73
- title, aqi, category, description, acronym = AirQualityIndex._extract_aqi(aqi_data)
74
- color = Color.from_string(color_data)
75
- return cls(title, aqi, category, description, acronym, color)
76
- except(ValueError, IndexError) as e:
77
- raise ValueError("Invalid AQI data format or color data format") from e
72
+ title, aqi, category, description, acronym = AirQualityIndex._extract_aqi(aqi_data)
73
+ color = Color.from_string(color_data)
74
+ return cls(title, aqi, category, description, acronym, color)
75
+
78
76
 
@@ -46,9 +46,12 @@ class CityLocation:
46
46
  country, state_province, city, location = None, None, None, None
47
47
  parts = data.split(", ")
48
48
 
49
+ if data.strip() == "":
50
+ raise ValueError("City location string cannot be empty")
51
+
49
52
  if len(parts) > 2:
50
53
  i = len(parts) - 1
51
- while i > 0:
54
+ while i >= 0:
52
55
  if not country:
53
56
  country = parts[i]
54
57
  elif not state_province:
@@ -66,6 +69,4 @@ class CityLocation:
66
69
  return cls(city=city, state_province=state_province)
67
70
  elif len(parts) == 1:
68
71
  city = parts[0]
69
- return cls(city=city)
70
- else:
71
- raise ValueError("Invalid city location string format")
72
+ return cls(city=city)
@@ -0,0 +1,55 @@
1
+ import re
2
+
3
+ class Color:
4
+
5
+ def __init__(self, red: str | int, green: str | int, blue: str | int):
6
+ self._red = self._int_or_hex(red)
7
+ self._green = self._int_or_hex(green)
8
+ self._blue = self._int_or_hex(blue)
9
+
10
+ def _int_or_hex(self, value: str | int) -> int:
11
+ if type(value) == int:
12
+ if not (0 <= value <= 255):
13
+ raise ValueError("RGB integer values must be between 0 and 255")
14
+ return value
15
+ return int(value, 16)
16
+
17
+ @property
18
+ def red(self) -> int:
19
+ return self._red
20
+
21
+ @property
22
+ def green(self) -> int:
23
+ return self._green
24
+
25
+ @property
26
+ def blue(self) -> int:
27
+ return self._blue
28
+
29
+ @classmethod
30
+ def from_string(cls,string_value: str) -> "Color":
31
+
32
+ color_pattern = r"#([0-9A-Fa-f]{6})"
33
+
34
+ try:
35
+ match = re.search(color_pattern, string_value)
36
+ color = f"#{match.group(1)}"
37
+ hex_color = color.lstrip('#')
38
+ r, g, b = hex_color[:2], hex_color[2:4], hex_color[4:]
39
+ return cls(r, g, b)
40
+ except (AttributeError, ValueError):
41
+ raise ValueError(f"Invalid color string: {string_value}")
42
+
43
+ @property
44
+ def hex(self) -> str:
45
+ return f"{self.red:02x}{self.green:02x}{self.blue:02x}".upper()
46
+
47
+ @property
48
+ def rgb(self) -> str:
49
+ return f"rgb({self.red}, {self.green}, {self.blue})"
50
+
51
+ def __str__(self):
52
+ return f"{self.hex}"
53
+
54
+ def __repr__(self):
55
+ return f"Color(red='{self.red}', green='{self.green}', blue='{self.blue}')"
@@ -23,8 +23,8 @@ class HealthActivities:
23
23
  return f"HealthActivities(category_name={self._category_name!r}, title={self._title!r}, description={self._description!r})"
24
24
 
25
25
  # 'Health & Activities\nGrass\nSeasonal Allergies and Pollen Count Forecast\nGrass pollen is low in your area'
26
- @staticmethod
27
- def from_text(text: str):
26
+ @classmethod
27
+ def from_text(cls, text: str):
28
28
  try:
29
29
  lines = text.split('\n')
30
30
  if len(lines) >= 4:
@@ -32,7 +32,7 @@ class HealthActivities:
32
32
  #Ignore the "grass" line
33
33
  title = lines[2].strip()
34
34
  description = ' '.join(line.strip() for line in lines[3:]).strip()
35
- return HealthActivities(category_name, title, description)
35
+ return cls(category_name, title, description)
36
36
  else:
37
37
  raise ValueError("Insufficient data to parse HealthActivities")
38
38
  except Exception as e:
@@ -38,6 +38,7 @@ class MoonPhaseEnum(Enum):
38
38
  PHASE_26 = ("phase-26", "\uf186", "🌘")
39
39
  PHASE_27 = ("phase-27", "\uf186", "🌘")
40
40
  PHASE_28 = ("phase-28", "\uf186", "🌘")
41
+ PHASE_29 = ("phase-29", "\uf186", "🌑")
41
42
  DEFAULT = ("default", "\uf186", "🌑")
42
43
 
43
44
  def __init__(self, name: str, fa_icon: str, emoji_icon: str):
@@ -24,14 +24,14 @@ class UVIndex:
24
24
 
25
25
  @classmethod
26
26
  def from_string(cls, data: str, label: str = None) -> 'UVIndex':
27
+ if not data:
28
+ raise ValueError("UV Index string cannot be empty")
27
29
  parts = data.split(' ')
28
30
  if len(parts) == 1:
29
31
  return cls(string_value = data, index= parts[0].strip(), of="", label=label)
30
32
  elif len(parts) == 3:
31
33
  index, of, some = parts
32
34
  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")
35
35
  else:
36
36
  return cls(string_value = data, index="", of="", label=label)
37
37
 
@@ -41,5 +41,5 @@ class UVIndex:
41
41
  def __str__(self) -> str:
42
42
  if self.string_value:
43
43
  return f"{self.label} {self.string_value}"
44
- elif self.index and self.of:
44
+ else:
45
45
  return f"{self.label} {self.index} {self.of}" if self.label else f"{self.index} {self.of}"
@@ -10,6 +10,7 @@ class WeatherIconEnum(Enum):
10
10
  DRIZZLE = ('drizzle', '\uf0e9', '🌦️')
11
11
  FEEL = ('feel', '\uf2c9', '🥵')
12
12
  FOGGY = ('foggy', '\uf74e', '🌫️')
13
+ HEAVY_RAIN = ('heavy-rain', '\uf0e9', '🌧️')
13
14
  HUMIDITY = ('humidity', '\uf043', '💧')
14
15
  ISOLATED_THUNDERSTORMS = ('isolated-thunderstorms', chr(0x26C8), '⛈️')
15
16
  MOSTLY_CLEAR_DAY = ('mostly-clear-day', chr(0xF0599), '☀️')
@@ -0,0 +1,27 @@
1
+ import logging
2
+ from weathergrabber.domain.air_quality_index import AirQualityIndex
3
+ from pyquery import PyQuery
4
+
5
+ class ExtractAQIService:
6
+
7
+ def __init__(self):
8
+ self.logger = logging.getLogger(__name__)
9
+ pass
10
+
11
+ def execute(self, weather_data: PyQuery) -> AirQualityIndex | None:
12
+
13
+ self.logger.debug("Extracting Air Quality Index (AQI)...")
14
+
15
+ # 'Air Quality Index\n27\nGood\nAir quality is considered satisfactory, and air pollution poses little or no risk.\nSee Details\nInfo'
16
+ aqi_data = weather_data("section[data-testid='AirQualityModule']").text()
17
+
18
+ # 'stroke-width:5;stroke-dasharray:10.021680564951442 172.78759594743863;stroke:#00E838'
19
+ color_data = weather_data("section[data-testid='AirQualityModule'] svg[data-testid='DonutChart'] circle:nth-of-type(2)").attr("style")
20
+
21
+ air_quality_index = AirQualityIndex.aqi_color_from_string(aqi_data,color_data)
22
+
23
+ self.logger.debug(f"Extracted AQI data: {air_quality_index}")
24
+
25
+ return air_quality_index
26
+
27
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: weathergrabber
3
- Version: 0.0.8b3
3
+ Version: 0.0.8b5
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
@@ -4,6 +4,7 @@ pyproject.toml
4
4
  tests/test_cli.py
5
5
  tests/test_cli_version.py
6
6
  tests/test_core.py
7
+ tests/test_main.py
7
8
  weathergrabber/__init__.py
8
9
  weathergrabber/__main__.py
9
10
  weathergrabber/cli.py
@@ -1,43 +0,0 @@
1
- import re
2
-
3
- class Color:
4
-
5
- def __init__(self, red: str, green: str, blue: str):
6
- self._red = red
7
- self._green = green
8
- self._blue = blue
9
-
10
- @property
11
- def red(self):
12
- return self._red
13
-
14
- @property
15
- def green(self):
16
- return self._green
17
-
18
- @property
19
- def blue(self):
20
- return self._blue
21
-
22
- @classmethod
23
- def from_string(cls,string_value: str) -> "Color":
24
-
25
- color_pattern = r"#([0-9A-Fa-f]{6})"
26
-
27
- match = re.search(color_pattern, string_value)
28
- color = f"#{match.group(1)}"
29
- hex_color = color.lstrip('#')
30
- r, g, b = int(hex_color[:2], 16), int(hex_color[2:4], 16), int(hex_color[4:], 16)
31
-
32
- return cls(r, g, b)
33
-
34
- @property
35
- def hex(self) -> str:
36
- return f"#{self.red:02x}{self.green:02x}{self.blue:02x}"
37
-
38
- @property
39
- def rgb(self) -> str:
40
- return f"rgb({self.red}, {self.green}, {self.blue})"
41
-
42
- def __repr__(self):
43
- return f"Color(red='{self.red}', green='{self.green}', blue='{self.blue}')"
@@ -1,30 +0,0 @@
1
- import logging
2
- from weathergrabber.domain.air_quality_index import AirQualityIndex
3
- from pyquery import PyQuery
4
-
5
- class ExtractAQIService:
6
-
7
- def __init__(self):
8
- self.logger = logging.getLogger(__name__)
9
- pass
10
-
11
- def execute(self, weather_data: PyQuery) -> AirQualityIndex | None:
12
-
13
- self.logger.debug("Extracting Air Quality Index (AQI)...")
14
-
15
- try:
16
- # 'Air Quality Index\n27\nGood\nAir quality is considered satisfactory, and air pollution poses little or no risk.\nSee Details\nInfo'
17
- aqi_data = weather_data("section[data-testid='AirQualityModule']").text()
18
-
19
- # 'stroke-width:5;stroke-dasharray:10.021680564951442 172.78759594743863;stroke:#00E838'
20
- color_data = weather_data("section[data-testid='AirQualityModule'] svg[data-testid='DonutChart'] circle:nth-of-type(2)").attr("style")
21
-
22
- air_quality_index = AirQualityIndex.aqi_color_from_string(aqi_data,color_data)
23
-
24
- self.logger.debug(f"Extracted AQI data: {air_quality_index}")
25
-
26
- return air_quality_index
27
- except Exception as e:
28
- self.logger.error(f"Error extracting AQI data: {e}")
29
- raise ValueError("Could not extract AQI data") from e
30
-