weathergrabber 0.0.1__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 (77) hide show
  1. weathergrabber-0.0.1/PKG-INFO +176 -0
  2. weathergrabber-0.0.1/README.md +163 -0
  3. weathergrabber-0.0.1/pyproject.toml +22 -0
  4. weathergrabber-0.0.1/setup.cfg +4 -0
  5. weathergrabber-0.0.1/tests/test_cli.py +75 -0
  6. weathergrabber-0.0.1/tests/test_core.py +41 -0
  7. weathergrabber-0.0.1/weathergrabber/__init__.py +0 -0
  8. weathergrabber-0.0.1/weathergrabber/adapter/client/weather_api.py +27 -0
  9. weathergrabber-0.0.1/weathergrabber/adapter/client/weather_search_api.py +50 -0
  10. weathergrabber-0.0.1/weathergrabber/adapter/tty/console_tty.py +102 -0
  11. weathergrabber-0.0.1/weathergrabber/adapter/tty/json_tty.py +19 -0
  12. weathergrabber-0.0.1/weathergrabber/adapter/tty/waybar_tty.py +110 -0
  13. weathergrabber-0.0.1/weathergrabber/cli.py +37 -0
  14. weathergrabber-0.0.1/weathergrabber/core.py +31 -0
  15. weathergrabber-0.0.1/weathergrabber/domain/adapter/icon_enum.py +5 -0
  16. weathergrabber-0.0.1/weathergrabber/domain/adapter/mapper/air_quality_index_mapper.py +13 -0
  17. weathergrabber-0.0.1/weathergrabber/domain/adapter/mapper/city_location_mapper.py +8 -0
  18. weathergrabber-0.0.1/weathergrabber/domain/adapter/mapper/color_mapper.py +10 -0
  19. weathergrabber-0.0.1/weathergrabber/domain/adapter/mapper/current_conditions_mapper.py +16 -0
  20. weathergrabber-0.0.1/weathergrabber/domain/adapter/mapper/daily_predictions_mapper.py +15 -0
  21. weathergrabber-0.0.1/weathergrabber/domain/adapter/mapper/day_night_mapper.py +12 -0
  22. weathergrabber-0.0.1/weathergrabber/domain/adapter/mapper/forecast_mapper.py +20 -0
  23. weathergrabber-0.0.1/weathergrabber/domain/adapter/mapper/health_activities_mapper.py +8 -0
  24. weathergrabber-0.0.1/weathergrabber/domain/adapter/mapper/hourly_predictions_mapper.py +19 -0
  25. weathergrabber-0.0.1/weathergrabber/domain/adapter/mapper/label_value_mapper.py +7 -0
  26. weathergrabber-0.0.1/weathergrabber/domain/adapter/mapper/moon_phase_mapper.py +9 -0
  27. weathergrabber-0.0.1/weathergrabber/domain/adapter/mapper/precipitation_mapper.py +7 -0
  28. weathergrabber-0.0.1/weathergrabber/domain/adapter/mapper/search_mapper.py +7 -0
  29. weathergrabber-0.0.1/weathergrabber/domain/adapter/mapper/sunrise_sunset_mapper.py +13 -0
  30. weathergrabber-0.0.1/weathergrabber/domain/adapter/mapper/temperature_high_low_mapper.py +8 -0
  31. weathergrabber-0.0.1/weathergrabber/domain/adapter/mapper/timestamp_mapper.py +8 -0
  32. weathergrabber-0.0.1/weathergrabber/domain/adapter/mapper/today_details_mapper.py +21 -0
  33. weathergrabber-0.0.1/weathergrabber/domain/adapter/mapper/uv_index_mapper.py +9 -0
  34. weathergrabber-0.0.1/weathergrabber/domain/adapter/mapper/weather_icon_enum_mapper.py +8 -0
  35. weathergrabber-0.0.1/weathergrabber/domain/adapter/mapper/wind_mapper.py +7 -0
  36. weathergrabber-0.0.1/weathergrabber/domain/adapter/output_enum.py +6 -0
  37. weathergrabber-0.0.1/weathergrabber/domain/adapter/params.py +58 -0
  38. weathergrabber-0.0.1/weathergrabber/domain/air_quality_index.py +78 -0
  39. weathergrabber-0.0.1/weathergrabber/domain/city_location.py +37 -0
  40. weathergrabber-0.0.1/weathergrabber/domain/color.py +43 -0
  41. weathergrabber-0.0.1/weathergrabber/domain/current_conditions.py +53 -0
  42. weathergrabber-0.0.1/weathergrabber/domain/daily_predictions.py +58 -0
  43. weathergrabber-0.0.1/weathergrabber/domain/day_night.py +50 -0
  44. weathergrabber-0.0.1/weathergrabber/domain/forecast.py +68 -0
  45. weathergrabber-0.0.1/weathergrabber/domain/health_activities.py +39 -0
  46. weathergrabber-0.0.1/weathergrabber/domain/hourly_predictions.py +76 -0
  47. weathergrabber-0.0.1/weathergrabber/domain/label_value.py +19 -0
  48. weathergrabber-0.0.1/weathergrabber/domain/moon_phase.py +22 -0
  49. weathergrabber-0.0.1/weathergrabber/domain/moon_phase_enum.py +65 -0
  50. weathergrabber-0.0.1/weathergrabber/domain/precipitation.py +20 -0
  51. weathergrabber-0.0.1/weathergrabber/domain/search.py +15 -0
  52. weathergrabber-0.0.1/weathergrabber/domain/sunrise_sunset.py +40 -0
  53. weathergrabber-0.0.1/weathergrabber/domain/temperature_hight_low.py +32 -0
  54. weathergrabber-0.0.1/weathergrabber/domain/timestamp.py +39 -0
  55. weathergrabber-0.0.1/weathergrabber/domain/today_details.py +79 -0
  56. weathergrabber-0.0.1/weathergrabber/domain/uv_index.py +43 -0
  57. weathergrabber-0.0.1/weathergrabber/domain/weather_icon_enum.py +58 -0
  58. weathergrabber-0.0.1/weathergrabber/domain/wind.py +28 -0
  59. weathergrabber-0.0.1/weathergrabber/service/extract_aqi_service.py +30 -0
  60. weathergrabber-0.0.1/weathergrabber/service/extract_current_conditions_service.py +47 -0
  61. weathergrabber-0.0.1/weathergrabber/service/extract_daily_forecast_oldstyle_service.py +51 -0
  62. weathergrabber-0.0.1/weathergrabber/service/extract_daily_forecast_service.py +56 -0
  63. weathergrabber-0.0.1/weathergrabber/service/extract_health_activities_service.py +25 -0
  64. weathergrabber-0.0.1/weathergrabber/service/extract_hourly_forecast_oldstyle_service.py +50 -0
  65. weathergrabber-0.0.1/weathergrabber/service/extract_hourly_forecast_service.py +64 -0
  66. weathergrabber-0.0.1/weathergrabber/service/extract_temperature_service.py +18 -0
  67. weathergrabber-0.0.1/weathergrabber/service/extract_today_details_service.py +85 -0
  68. weathergrabber-0.0.1/weathergrabber/service/read_weather_service.py +23 -0
  69. weathergrabber-0.0.1/weathergrabber/service/search_location_service.py +35 -0
  70. weathergrabber-0.0.1/weathergrabber/usecase/use_case.py +85 -0
  71. weathergrabber-0.0.1/weathergrabber/weathergrabber_application.py +78 -0
  72. weathergrabber-0.0.1/weathergrabber.egg-info/PKG-INFO +176 -0
  73. weathergrabber-0.0.1/weathergrabber.egg-info/SOURCES.txt +75 -0
  74. weathergrabber-0.0.1/weathergrabber.egg-info/dependency_links.txt +1 -0
  75. weathergrabber-0.0.1/weathergrabber.egg-info/entry_points.txt +2 -0
  76. weathergrabber-0.0.1/weathergrabber.egg-info/requires.txt +2 -0
  77. weathergrabber-0.0.1/weathergrabber.egg-info/top_level.txt +1 -0
@@ -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
+ ![Weather widget on Waybar](image.png)
23
+
24
+ ### Terminal Console
25
+
26
+ ![Weather widget on terminal console](image-1.png)
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
+ ![Test Status](https://github.com/cjuniorfox/weather/actions/workflows/python-package.yml/badge.svg)
116
+ [![codecov](https://codecov.io/gh/cjuniorfox/weather/branch/main/graph/badge.svg)](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,163 @@
1
+ # Weather Forecast CLI Script
2
+
3
+ ## Overview
4
+
5
+ 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.
6
+
7
+ ### Waybar widget
8
+
9
+ ![Weather widget on Waybar](image.png)
10
+
11
+ ### Terminal Console
12
+
13
+ ![Weather widget on terminal console](image-1.png)
14
+
15
+ ## Features
16
+
17
+ - Retrieves current weather and forecasts for a specified location.
18
+ - Displays weather data in different formats:
19
+ - **Console output**: Richly formatted weather data with icons.
20
+ - **Waybar JSON**: For integration with Waybar.
21
+ - Supports multiple languages for Weather.com data.
22
+ - Includes data such as:
23
+ - Current temperature and "feels-like" temperature.
24
+ - Wind speed, humidity, visibility, and air quality.
25
+ - Hourly and daily forecasts with icons and precipitation chances.
26
+
27
+ ## Requirements
28
+
29
+ Requires Python 3 or newer.
30
+
31
+ Install the package and all dependencies with:
32
+
33
+ ```sh
34
+ pip install .
35
+ ```
36
+
37
+ Or, for development (editable install with dev dependencies):
38
+
39
+ ```sh
40
+ pip install -e .[dev]
41
+ ```
42
+
43
+ ### Output Formats
44
+
45
+ #### Console Output
46
+
47
+ The script displays a formatted weather summary, including:
48
+
49
+ - Current weather status.
50
+ - Temperature (current, max/min).
51
+ - Wind, humidity, visibility, and air quality.
52
+ - Hourly and daily forecasts with icons.
53
+
54
+ #### Waybar JSON
55
+
56
+ The JSON includes:
57
+
58
+ - `text`: Current weather icon and temperature.
59
+ - `alt`: Weather status.
60
+ - `tooltip`: Detailed weather information.
61
+ - `class`: Status code for further customization.
62
+
63
+ ### JSON General output
64
+
65
+ Here the following [JSON Schema](schema.json) for this output.
66
+
67
+ The key values for this json is:
68
+
69
+ - `temperature`: An object containing the temperature information with the following fields:
70
+ - `current`: The current temperature.
71
+ - `feel`: The temperature feel
72
+ - `max` : The maximum temperature
73
+ - `min` : The minimum temperature
74
+
75
+ There's also other fields like `hourly_predictions` and `daily_predictions` containing lists of predictions informations. More defaults on [JSON Schema](schema.json).
76
+
77
+ ### Integration with Waybar
78
+
79
+ To integrate the script with Waybar:
80
+
81
+ 1. Add a custom script module in Waybar's configuration:
82
+
83
+ ```json
84
+ {
85
+ "modules-left": ["custom/weather"],
86
+ "custom/weather": {
87
+ "exec": "weathergrabber --output waybar",
88
+ "interval": 600
89
+ }
90
+ }
91
+ ```
92
+
93
+ 2. Reload Waybar to apply the changes.
94
+
95
+ ## Error Handling
96
+
97
+ - Validates `weather_id` and `lang` inputs.
98
+ - Handles HTTP errors gracefully, including 404 errors for invalid locations.
99
+
100
+ ## CI & Test Coverage
101
+
102
+ ![Test Status](https://github.com/cjuniorfox/weather/actions/workflows/python-package.yml/badge.svg)
103
+ [![codecov](https://codecov.io/gh/cjuniorfox/weather/branch/main/graph/badge.svg)](https://codecov.io/gh/cjuniorfox/weather)
104
+
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
+
107
+ To run tests and check coverage locally:
108
+
109
+ ```sh
110
+ pytest --cov=weathergrabber --cov-report=term
111
+ ```
112
+
113
+ ## License
114
+
115
+ This script is open-source and available under the MIT License.
116
+
117
+ ## CLI Usage
118
+
119
+ You can run the CLI as an installed command:
120
+
121
+ ```sh
122
+ weathergrabber [location_name] [options]
123
+ ```
124
+
125
+ Or as a Python module:
126
+
127
+ ```sh
128
+ python -m weathergrabber.cli [location_name] [options]
129
+ ```
130
+
131
+ ### Arguments
132
+
133
+ - `location_name` (positional, optional): City name, zip code, etc. If not provided, you can use `--location-id` or the `WEATHER_LOCATION_ID` environment variable.
134
+
135
+ ### Options
136
+
137
+ - `--location-id`, `-l` : 64-character-hex code for location (from Weather.com)
138
+ - `--lang`, `-L` : Language (e.g., `pt-BR`, `fr-FR`). Defaults to system locale if not set.
139
+ - `--output`, `-o` : Output format. One of `console`, `json`, or `waybar`. Default: `console`.
140
+ - `--keep-open`, `-k` : Keep open and refresh every 5 minutes (only makes sense for `console` output).
141
+ - `--icons`, `-i` : Icon set. `fa` for Font-Awesome, `emoji` for emoji icons. Default: `emoji`.
142
+ - `--log` : Set logging level. One of `debug`, `info`, `warning`, `error`, `critical`. Default: `critical`.
143
+
144
+ ### Environment Variables
145
+
146
+ - `LANG` : Used as default language if `--lang` is not set.
147
+ - `WEATHER_LOCATION_ID` : Used as default location if neither `location_name` nor `--location-id` is set.
148
+
149
+ ### Example Usage
150
+
151
+ ```sh
152
+ weathergrabber "London" --output console --lang en-GB
153
+ weathergrabber --location-id 1234567890abcdef... --output json
154
+ weathergrabber "Paris" -o waybar -i fa
155
+ ```
156
+
157
+ Or as a Python module:
158
+
159
+ ```sh
160
+ python -m weathergrabber.cli "London" --output console --lang en-GB
161
+ python -m weathergrabber.cli --location-id 1234567890abcdef... --output json
162
+ python -m weathergrabber.cli "Paris" -o waybar -i fa
163
+ ```
@@ -0,0 +1,22 @@
1
+ [project]
2
+ name = "weathergrabber"
3
+ version = "0.0.1"
4
+ description = "A grabber for weather.com data with various output formats."
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ license = { text = "MIT" }
8
+ authors = [
9
+ { name = "Carlos Anselmo Mendes Junior", email = "cjuniorfox@gmail.com" }
10
+ ]
11
+
12
+ dependencies = [
13
+ "pyquery>=2.0.1",
14
+ "requests>=2.32.5"
15
+ ]
16
+
17
+ [project.scripts]
18
+ weathergrabber = "weathergrabber.cli:main_cli"
19
+
20
+ [project.urls]
21
+ homepage = "https://github.com/cjuniorfox/weather"
22
+ repository = "https://github.com/cjuniorfox/weather"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,75 @@
1
+ import pytest
2
+ from unittest.mock import patch, MagicMock
3
+ import sys
4
+
5
+ @pytest.fixture
6
+ def mock_main():
7
+ with patch('weathergrabber.cli.main') as m:
8
+ yield m
9
+
10
+ def test_cli_location_name(monkeypatch, mock_main):
11
+ test_args = ["weathergrabber", "London"]
12
+ monkeypatch.setattr(sys, "argv", test_args)
13
+ from weathergrabber.cli import main_cli
14
+ main_cli()
15
+ mock_main.assert_called_once()
16
+ args = mock_main.call_args[1]
17
+ assert args["location_name"] == "London"
18
+ assert args["output"] == "console"
19
+ assert args["icons"] == "emoji"
20
+
21
+
22
+ def test_cli_location_id(monkeypatch, mock_main):
23
+ test_args = ["weathergrabber", "--location-id", "abcdef123456"]
24
+ monkeypatch.setattr(sys, "argv", test_args)
25
+ from weathergrabber.cli import main_cli
26
+ main_cli()
27
+ args = mock_main.call_args[1]
28
+ assert args["location_id"] == "abcdef123456"
29
+
30
+
31
+ def test_cli_output_json(monkeypatch, mock_main):
32
+ test_args = ["weathergrabber", "Paris", "--output", "json"]
33
+ monkeypatch.setattr(sys, "argv", test_args)
34
+ from weathergrabber.cli import main_cli
35
+ main_cli()
36
+ args = mock_main.call_args[1]
37
+ assert args["output"] == "json"
38
+
39
+
40
+ def test_cli_icons_fa(monkeypatch, mock_main):
41
+ test_args = ["weathergrabber", "Berlin", "--icons", "fa"]
42
+ monkeypatch.setattr(sys, "argv", test_args)
43
+ from weathergrabber.cli import main_cli
44
+ main_cli()
45
+ args = mock_main.call_args[1]
46
+ assert args["icons"] == "fa"
47
+
48
+
49
+ def test_cli_keep_open(monkeypatch, mock_main):
50
+ test_args = ["weathergrabber", "Madrid", "--keep-open"]
51
+ monkeypatch.setattr(sys, "argv", test_args)
52
+ from weathergrabber.cli import main_cli
53
+ main_cli()
54
+ args = mock_main.call_args[1]
55
+ assert args["keep_open"] is True
56
+
57
+
58
+ def test_cli_lang_env(monkeypatch, mock_main):
59
+ test_args = ["weathergrabber", "Rome"]
60
+ monkeypatch.setattr(sys, "argv", test_args)
61
+ monkeypatch.setenv("LANG", "fr_FR.UTF-8")
62
+ from weathergrabber.cli import main_cli
63
+ main_cli()
64
+ args = mock_main.call_args[1]
65
+ assert args["lang"] == "fr-FR"
66
+
67
+
68
+ def test_cli_location_id_env(monkeypatch, mock_main):
69
+ test_args = ["weathergrabber"]
70
+ monkeypatch.setattr(sys, "argv", test_args)
71
+ monkeypatch.setenv("WEATHER_LOCATION_ID", "envlocationid")
72
+ from weathergrabber.cli import main_cli
73
+ main_cli()
74
+ args = mock_main.call_args[1]
75
+ assert args["location_id"] == "envlocationid"
@@ -0,0 +1,41 @@
1
+ import pytest
2
+ from unittest.mock import patch, MagicMock
3
+ import logging
4
+
5
+ @patch('weathergrabber.core.WeatherGrabberApplication')
6
+ def test_main_invokes_application(mock_app):
7
+ from weathergrabber.core import main
8
+ params = {
9
+ 'log_level': 'info',
10
+ 'location_name': 'London',
11
+ 'location_id': '123',
12
+ 'lang': 'en-US',
13
+ 'output': 'console',
14
+ 'keep_open': False,
15
+ 'icons': 'emoji'
16
+ }
17
+ main(**params)
18
+ mock_app.assert_called_once()
19
+ # Check Params object
20
+ args, kwargs = mock_app.call_args
21
+ assert hasattr(args[0], 'location')
22
+ assert args[0].location.search_name == 'London'
23
+ assert args[0].location.id == '123'
24
+ assert args[0].language == 'en-US'
25
+ assert args[0].output_format.name.lower() == 'console'
26
+ assert args[0].keep_open is False
27
+ assert args[0].icons.name.lower() == 'emoji'
28
+
29
+ @patch('weathergrabber.core.WeatherGrabberApplication')
30
+ def test_main_sets_log_level(mock_app):
31
+ from weathergrabber.core import main
32
+ main(
33
+ log_level='debug',
34
+ location_name='Paris',
35
+ location_id='456',
36
+ lang='fr-FR',
37
+ output='json',
38
+ keep_open=True,
39
+ icons='fa'
40
+ )
41
+ assert logging.getLogger().level == logging.DEBUG
File without changes
@@ -0,0 +1,27 @@
1
+ from pyquery import PyQuery
2
+ from urllib.error import HTTPError
3
+ import logging
4
+
5
+ class WeatherApi:
6
+
7
+ def __init__(self):
8
+ self.logger = logging.getLogger(__name__)
9
+ pass
10
+
11
+ def get_weather(self,language: str, location: str) -> PyQuery:
12
+ url = f"https://weather.com/{language}/weather/today/l/{location}"
13
+
14
+ if location == None:
15
+ url = f"https://weather.com/{language}/weather/today"
16
+ elif len(location) < 64 :
17
+ raise ValueError("Invalid location")
18
+
19
+ if language == None:
20
+ raise ValueError("language must be specified")
21
+
22
+ try:
23
+ self.logger.debug(f"Fetching weather data from URL: %s.", url)
24
+ return PyQuery(url=url)
25
+ except HTTPError as e:
26
+ self.logger.error("HTTP '%s' error when fetching weather data from URL: '%s'.", e.code, url)
27
+ raise ValueError(f"HTTP error {e.code} when fetching weather data.")
@@ -0,0 +1,50 @@
1
+ import logging
2
+ import requests
3
+
4
+ class WeatherSearchApi:
5
+ def __init__(self):
6
+ self.logger = logging.getLogger(__name__)
7
+ self.cache = {}
8
+
9
+ def search(self, location_name: str, lang: str = 'en-US'):
10
+
11
+ if not location_name or len(location_name) < 1:
12
+ raise ValueError("Location name must be provided and cannot be empty.")
13
+ if len(location_name) > 100:
14
+ raise ValueError("Location name is too long.")
15
+
16
+ key = (location_name, lang)
17
+
18
+ if key in self.cache:
19
+ self.logger.debug("Cache hit for location '%s' and language '%s'.", location_name, lang)
20
+ return self.cache[key]
21
+
22
+ url = "https://weather.com/api/v1/p/redux-dal"
23
+ headers = {"content-type": "application/json"}
24
+
25
+ payload = [
26
+ {
27
+ "name": "getSunV3LocationSearchUrlConfig",
28
+ "params": {
29
+ "query": location_name,
30
+ "language": lang,
31
+ "locationType": "locale"
32
+ }
33
+ }
34
+ ]
35
+
36
+ self.logger.debug("Sending request to Weather Search API '%s' for location '%s' with language '%s'...", url, location_name, lang)
37
+
38
+ resp = requests.post(url, json=payload, headers=headers)
39
+
40
+ if resp.status_code != 200:
41
+ self.logger.error("HTTP '%s' error when searching for location '%s' with language '%s'.", resp.status_code, location_name, lang)
42
+ raise ValueError(f"HTTP error {resp.status_code} when searching for location.")
43
+
44
+ self.logger.debug("Received successful response from Weather Search API.")
45
+
46
+ data = resp.json()
47
+
48
+ self.cache[key] = data
49
+
50
+ return data
@@ -0,0 +1,102 @@
1
+ from weathergrabber.usecase.use_case import UseCase
2
+ from weathergrabber.domain.adapter.params import Params
3
+ from weathergrabber.domain.adapter.icon_enum import IconEnum
4
+ from weathergrabber.domain.weather_icon_enum import WeatherIconEnum
5
+ from weathergrabber.weathergrabber_application import WeatherGrabberApplication
6
+ import logging
7
+
8
+ class ConsoleTTY:
9
+
10
+ def __init__(self, use_case: UseCase):
11
+ self.logger = logging.getLogger(__name__)
12
+ self.use_case = use_case
13
+ pass
14
+
15
+ def execute(self, params: Params) -> None:
16
+ self.logger.info("Executing Console output")
17
+
18
+ is_fa = params.icons == IconEnum.FA
19
+
20
+ forecast = self.use_case.execute(params)
21
+
22
+ rain_icon = WeatherIconEnum.RAIN.fa_icon if is_fa else WeatherIconEnum.RAIN.emoji_icon
23
+
24
+ city = forecast.current_conditions.location.city
25
+ state_province = forecast.current_conditions.location.state_province
26
+ icon = forecast.current_conditions.icon.fa_icon if is_fa else forecast.current_conditions.icon.emoji_icon
27
+ temperature = forecast.current_conditions.temperature
28
+
29
+ day_temp_label = WeatherIconEnum.DAY.fa_icon if is_fa else WeatherIconEnum.DAY.emoji_icon
30
+ day_temp_value = forecast.current_conditions.day_night.day.value
31
+ night_temp_label = WeatherIconEnum.NIGHT.fa_icon if is_fa else WeatherIconEnum.NIGHT.emoji_icon
32
+ night_temp_value = forecast.current_conditions.day_night.night.value
33
+
34
+ moon_icon = forecast.today_details.moon_phase.icon.fa_icon if is_fa else forecast.today_details.moon_phase.icon.emoji_icon
35
+ moon_phase = forecast.today_details.moon_phase.phase
36
+ summary = forecast.current_conditions.summary
37
+
38
+ feelslike_icon = WeatherIconEnum.FEEL.fa_icon if is_fa else WeatherIconEnum.FEEL.emoji_icon
39
+ feelslike = forecast.today_details.feelslike.value
40
+
41
+ sunrise_icon = forecast.today_details.sunrise_sunset.sunrise.icon.fa_icon if is_fa else forecast.today_details.sunrise_sunset.sunrise.icon.emoji_icon
42
+ sunset_icon = forecast.today_details.sunrise_sunset.sunset.icon.fa_icon if is_fa else forecast.today_details.sunrise_sunset.sunset.icon.emoji_icon
43
+
44
+ sunrise_value = forecast.today_details.sunrise_sunset.sunrise.value
45
+ sunset_value = forecast.today_details.sunrise_sunset.sunset.value
46
+
47
+ wind_icon = WeatherIconEnum.WIND.fa_icon if is_fa else WeatherIconEnum.WIND.emoji_icon
48
+ wind = forecast.today_details.wind.value
49
+
50
+ humidity_icon = WeatherIconEnum.HUMIDITY.fa_icon if is_fa else WeatherIconEnum.HUMIDITY.emoji_icon
51
+ humidity = forecast.today_details.humidity.value
52
+
53
+ pressure = forecast.today_details.pressure
54
+
55
+ uv_index = forecast.today_details.uv_index
56
+
57
+ visibility_icon = WeatherIconEnum.VISIBILITY.fa_icon if is_fa else WeatherIconEnum.VISIBILITY.emoji_icon
58
+ visibility = forecast.today_details.visibility.value
59
+
60
+ r, g, b = forecast.air_quality_index.color.red, forecast.air_quality_index.color.green, forecast.air_quality_index.color.blue
61
+ aqi_category = f"\033[38;2;{r};{g};{b}m{forecast.air_quality_index.category}\033[0m"
62
+ aqi_acronym = forecast.air_quality_index.acronym
63
+ aqi_value = forecast.air_quality_index.value
64
+
65
+ hourly_predictions = [
66
+ f"{h.title}\t{h.temperature}\t{h.icon.fa_icon if is_fa else h.icon.emoji_icon}\t{rain_icon if h.precipitation.percentage else ''} {h.precipitation.percentage}"
67
+ for h in forecast.hourly_predictions
68
+ ]
69
+ daily_predictions = [
70
+ f"{d.title}\t{d.high_low}\t{d.icon.fa_icon if is_fa else d.icon.emoji_icon}\t{rain_icon} {d.precipitation.percentage}"
71
+ for d in forecast.daily_predictions
72
+ ]
73
+
74
+ print_value = (
75
+ "\n"
76
+ f"{city}, {state_province}\n"
77
+ "\n"
78
+ f"{icon} {temperature}\n"
79
+ "\n"
80
+ f"{summary}\n"
81
+ f"{day_temp_label} {day_temp_value}/{night_temp_label} {night_temp_value}\t{feelslike_icon} {feelslike}\n"
82
+ "\n"
83
+ f"{sunrise_icon} {sunrise_value} • {sunset_icon} {sunset_value}\n"
84
+ "\n"
85
+ f"{moon_icon} {moon_phase}\n"
86
+ "\n"
87
+ f"{wind_icon} {wind}\t {uv_index}\n"
88
+ f"{humidity_icon} {humidity}\t\t {pressure}\n"
89
+ f"{visibility_icon} {visibility}\t {aqi_acronym} {aqi_category} {aqi_value}\n"
90
+ "\n"
91
+ f"{'\n'.join(hourly_predictions)}\n"
92
+ "\n"
93
+ f"{'\n'.join(daily_predictions)}\n"
94
+ "\n"
95
+ f"{forecast.current_conditions.timestamp}"
96
+ )
97
+
98
+ print(print_value)
99
+ if(params.keep_open):
100
+ lines_count = print_value.count("\n") + 1
101
+ ret_prev_line = f"\033[{lines_count}A"
102
+ print(ret_prev_line, end='') # Move cursor back to the beginning for overwriting, the application is responsable for executing again
@@ -0,0 +1,19 @@
1
+ from weathergrabber.usecase.use_case import UseCase
2
+ from weathergrabber.domain.adapter.params import Params
3
+ from weathergrabber.domain.adapter.mapper.forecast_mapper import forecast_to_dict
4
+ import logging
5
+ import json
6
+
7
+ class JsonTTY:
8
+
9
+ def __init__(self, use_case: UseCase):
10
+ self.logger = logging.getLogger(__name__)
11
+ self.use_case = use_case
12
+ pass
13
+
14
+ def execute(self, params: Params) -> None:
15
+ self.logger.info("Executing JSON output")
16
+ forecast = self.use_case.execute(params)
17
+ output: dict = forecast_to_dict(forecast)
18
+ output_json = json.dumps(output, indent=4)
19
+ print(output_json)