weathergrabber 0.0.8b7__tar.gz → 0.0.9a0__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.
- {weathergrabber-0.0.8b7/weathergrabber.egg-info → weathergrabber-0.0.9a0}/PKG-INFO +32 -7
- {weathergrabber-0.0.8b7 → weathergrabber-0.0.9a0}/README.md +28 -6
- {weathergrabber-0.0.8b7 → weathergrabber-0.0.9a0}/pyproject.toml +14 -2
- {weathergrabber-0.0.8b7 → weathergrabber-0.0.9a0}/tests/test_core.py +9 -3
- {weathergrabber-0.0.8b7 → weathergrabber-0.0.9a0}/weathergrabber/__init__.py +1 -1
- weathergrabber-0.0.9a0/weathergrabber/adapter/repository/forecast_repository.py +135 -0
- {weathergrabber-0.0.8b7 → weathergrabber-0.0.9a0}/weathergrabber/adapter/tty/console_tty.py +4 -4
- {weathergrabber-0.0.8b7 → weathergrabber-0.0.9a0}/weathergrabber/adapter/tty/json_tty.py +3 -3
- weathergrabber-0.0.9a0/weathergrabber/adapter/tty/statistics_tty.py +35 -0
- {weathergrabber-0.0.8b7 → weathergrabber-0.0.9a0}/weathergrabber/adapter/tty/waybar_tty.py +3 -3
- {weathergrabber-0.0.8b7/weathergrabber/service → weathergrabber-0.0.9a0/weathergrabber/application/services}/extract_aqi_service.py +1 -1
- {weathergrabber-0.0.8b7/weathergrabber/service → weathergrabber-0.0.9a0/weathergrabber/application/services}/extract_current_conditions_service.py +5 -5
- {weathergrabber-0.0.8b7/weathergrabber/service → weathergrabber-0.0.9a0/weathergrabber/application/services}/extract_daily_forecast_oldstyle_service.py +4 -4
- {weathergrabber-0.0.8b7/weathergrabber/service → weathergrabber-0.0.9a0/weathergrabber/application/services}/extract_daily_forecast_service.py +6 -6
- {weathergrabber-0.0.8b7/weathergrabber/service → weathergrabber-0.0.9a0/weathergrabber/application/services}/extract_health_activities_service.py +1 -1
- {weathergrabber-0.0.8b7/weathergrabber/service → weathergrabber-0.0.9a0/weathergrabber/application/services}/extract_hourly_forecast_oldstyle_service.py +3 -3
- {weathergrabber-0.0.8b7/weathergrabber/service → weathergrabber-0.0.9a0/weathergrabber/application/services}/extract_hourly_forecast_service.py +5 -5
- {weathergrabber-0.0.8b7/weathergrabber/service → weathergrabber-0.0.9a0/weathergrabber/application/services}/extract_today_details_service.py +8 -8
- weathergrabber-0.0.9a0/weathergrabber/application/services/retrieve_forecast_from_cache_service.py +27 -0
- weathergrabber-0.0.9a0/weathergrabber/application/services/retrieve_statistics_service.py +20 -0
- weathergrabber-0.0.9a0/weathergrabber/application/services/save_forecast_to_cache_service.py +21 -0
- weathergrabber-0.0.9a0/weathergrabber/application/usecases/statistics_uc.py +19 -0
- weathergrabber-0.0.9a0/weathergrabber/application/usecases/weather_forecast_uc.py +123 -0
- weathergrabber-0.0.9a0/weathergrabber/application/weathergrabber_application.py +92 -0
- {weathergrabber-0.0.8b7 → weathergrabber-0.0.9a0}/weathergrabber/cli.py +6 -2
- {weathergrabber-0.0.8b7 → weathergrabber-0.0.9a0}/weathergrabber/core.py +7 -3
- weathergrabber-0.0.9a0/weathergrabber/domain/adapter/mappers/air_quality_index_mapper.py +25 -0
- weathergrabber-0.0.9a0/weathergrabber/domain/adapter/mappers/city_location_mapper.py +15 -0
- weathergrabber-0.0.9a0/weathergrabber/domain/adapter/mappers/color_mapper.py +17 -0
- weathergrabber-0.0.9a0/weathergrabber/domain/adapter/mappers/current_conditions_mapper.py +32 -0
- weathergrabber-0.0.9a0/weathergrabber/domain/adapter/mappers/daily_predictions_mapper.py +30 -0
- weathergrabber-0.0.9a0/weathergrabber/domain/adapter/mappers/day_night_mapper.py +23 -0
- weathergrabber-0.0.9a0/weathergrabber/domain/adapter/mappers/forecast_mapper.py +38 -0
- weathergrabber-0.0.9a0/weathergrabber/domain/adapter/mappers/health_activities_mapper.py +15 -0
- weathergrabber-0.0.9a0/weathergrabber/domain/adapter/mappers/hourly_predictions_mapper.py +38 -0
- weathergrabber-0.0.9a0/weathergrabber/domain/adapter/mappers/label_value_mapper.py +13 -0
- weathergrabber-0.0.9a0/weathergrabber/domain/adapter/mappers/moon_phase_mapper.py +16 -0
- weathergrabber-0.0.9a0/weathergrabber/domain/adapter/mappers/precipitation_mapper.py +13 -0
- weathergrabber-0.0.9a0/weathergrabber/domain/adapter/mappers/search_mapper.py +13 -0
- weathergrabber-0.0.9a0/weathergrabber/domain/adapter/mappers/statistics_mapper.py +25 -0
- weathergrabber-0.0.9a0/weathergrabber/domain/adapter/mappers/sunrise_sunset_mapper.py +23 -0
- weathergrabber-0.0.9a0/weathergrabber/domain/adapter/mappers/temperature_high_low_mapper.py +15 -0
- weathergrabber-0.0.9a0/weathergrabber/domain/adapter/mappers/timestamp_mapper.py +15 -0
- weathergrabber-0.0.9a0/weathergrabber/domain/adapter/mappers/today_details_mapper.py +42 -0
- weathergrabber-0.0.9a0/weathergrabber/domain/adapter/mappers/uv_index_mapper.py +17 -0
- weathergrabber-0.0.9a0/weathergrabber/domain/adapter/mappers/weather_icon_enum_mapper.py +11 -0
- weathergrabber-0.0.9a0/weathergrabber/domain/adapter/mappers/wind_mapper.py +16 -0
- {weathergrabber-0.0.8b7 → weathergrabber-0.0.9a0}/weathergrabber/domain/adapter/output_enum.py +1 -1
- {weathergrabber-0.0.8b7 → weathergrabber-0.0.9a0}/weathergrabber/domain/adapter/params.py +25 -4
- {weathergrabber-0.0.8b7/weathergrabber/domain → weathergrabber-0.0.9a0/weathergrabber/domain/entities}/moon_phase.py +1 -1
- weathergrabber-0.0.9a0/weathergrabber/domain/entities/statistics.py +46 -0
- {weathergrabber-0.0.8b7/weathergrabber/domain → weathergrabber-0.0.9a0/weathergrabber/domain/entities}/sunrise_sunset.py +0 -1
- {weathergrabber-0.0.8b7/weathergrabber/domain → weathergrabber-0.0.9a0/weathergrabber/domain/entities}/weather_icon_enum.py +1 -0
- {weathergrabber-0.0.8b7 → weathergrabber-0.0.9a0/weathergrabber.egg-info}/PKG-INFO +32 -7
- weathergrabber-0.0.9a0/weathergrabber.egg-info/SOURCES.txt +87 -0
- weathergrabber-0.0.9a0/weathergrabber.egg-info/requires.txt +6 -0
- weathergrabber-0.0.8b7/weathergrabber/domain/adapter/mapper/air_quality_index_mapper.py +0 -13
- weathergrabber-0.0.8b7/weathergrabber/domain/adapter/mapper/city_location_mapper.py +0 -8
- weathergrabber-0.0.8b7/weathergrabber/domain/adapter/mapper/color_mapper.py +0 -10
- weathergrabber-0.0.8b7/weathergrabber/domain/adapter/mapper/current_conditions_mapper.py +0 -16
- weathergrabber-0.0.8b7/weathergrabber/domain/adapter/mapper/daily_predictions_mapper.py +0 -15
- weathergrabber-0.0.8b7/weathergrabber/domain/adapter/mapper/day_night_mapper.py +0 -12
- weathergrabber-0.0.8b7/weathergrabber/domain/adapter/mapper/forecast_mapper.py +0 -20
- weathergrabber-0.0.8b7/weathergrabber/domain/adapter/mapper/health_activities_mapper.py +0 -8
- weathergrabber-0.0.8b7/weathergrabber/domain/adapter/mapper/hourly_predictions_mapper.py +0 -19
- weathergrabber-0.0.8b7/weathergrabber/domain/adapter/mapper/label_value_mapper.py +0 -7
- weathergrabber-0.0.8b7/weathergrabber/domain/adapter/mapper/moon_phase_mapper.py +0 -9
- weathergrabber-0.0.8b7/weathergrabber/domain/adapter/mapper/precipitation_mapper.py +0 -7
- weathergrabber-0.0.8b7/weathergrabber/domain/adapter/mapper/search_mapper.py +0 -7
- weathergrabber-0.0.8b7/weathergrabber/domain/adapter/mapper/sunrise_sunset_mapper.py +0 -13
- weathergrabber-0.0.8b7/weathergrabber/domain/adapter/mapper/temperature_high_low_mapper.py +0 -8
- weathergrabber-0.0.8b7/weathergrabber/domain/adapter/mapper/timestamp_mapper.py +0 -8
- weathergrabber-0.0.8b7/weathergrabber/domain/adapter/mapper/today_details_mapper.py +0 -21
- weathergrabber-0.0.8b7/weathergrabber/domain/adapter/mapper/uv_index_mapper.py +0 -9
- weathergrabber-0.0.8b7/weathergrabber/domain/adapter/mapper/weather_icon_enum_mapper.py +0 -8
- weathergrabber-0.0.8b7/weathergrabber/domain/adapter/mapper/wind_mapper.py +0 -7
- weathergrabber-0.0.8b7/weathergrabber/usecase/use_case.py +0 -87
- weathergrabber-0.0.8b7/weathergrabber/weathergrabber_application.py +0 -78
- weathergrabber-0.0.8b7/weathergrabber.egg-info/SOURCES.txt +0 -79
- weathergrabber-0.0.8b7/weathergrabber.egg-info/requires.txt +0 -2
- {weathergrabber-0.0.8b7 → weathergrabber-0.0.9a0}/LICENSE +0 -0
- {weathergrabber-0.0.8b7 → weathergrabber-0.0.9a0}/setup.cfg +0 -0
- {weathergrabber-0.0.8b7 → weathergrabber-0.0.9a0}/tests/test_cli.py +0 -0
- {weathergrabber-0.0.8b7 → weathergrabber-0.0.9a0}/tests/test_cli_version.py +0 -0
- {weathergrabber-0.0.8b7 → weathergrabber-0.0.9a0}/tests/test_main.py +0 -0
- {weathergrabber-0.0.8b7 → weathergrabber-0.0.9a0}/weathergrabber/__main__.py +0 -0
- {weathergrabber-0.0.8b7 → weathergrabber-0.0.9a0}/weathergrabber/adapter/client/weather_api.py +0 -0
- {weathergrabber-0.0.8b7 → weathergrabber-0.0.9a0}/weathergrabber/adapter/client/weather_search_api.py +0 -0
- {weathergrabber-0.0.8b7/weathergrabber/service → weathergrabber-0.0.9a0/weathergrabber/application/services}/extract_temperature_service.py +0 -0
- {weathergrabber-0.0.8b7/weathergrabber/service → weathergrabber-0.0.9a0/weathergrabber/application/services}/read_weather_service.py +0 -0
- {weathergrabber-0.0.8b7/weathergrabber/service → weathergrabber-0.0.9a0/weathergrabber/application/services}/search_location_service.py +0 -0
- {weathergrabber-0.0.8b7 → weathergrabber-0.0.9a0}/weathergrabber/domain/adapter/icon_enum.py +0 -0
- {weathergrabber-0.0.8b7/weathergrabber/domain → weathergrabber-0.0.9a0/weathergrabber/domain/entities}/air_quality_index.py +0 -0
- {weathergrabber-0.0.8b7/weathergrabber/domain → weathergrabber-0.0.9a0/weathergrabber/domain/entities}/city_location.py +0 -0
- {weathergrabber-0.0.8b7/weathergrabber/domain → weathergrabber-0.0.9a0/weathergrabber/domain/entities}/color.py +0 -0
- {weathergrabber-0.0.8b7/weathergrabber/domain → weathergrabber-0.0.9a0/weathergrabber/domain/entities}/current_conditions.py +0 -0
- {weathergrabber-0.0.8b7/weathergrabber/domain → weathergrabber-0.0.9a0/weathergrabber/domain/entities}/daily_predictions.py +0 -0
- {weathergrabber-0.0.8b7/weathergrabber/domain → weathergrabber-0.0.9a0/weathergrabber/domain/entities}/day_night.py +0 -0
- {weathergrabber-0.0.8b7/weathergrabber/domain → weathergrabber-0.0.9a0/weathergrabber/domain/entities}/forecast.py +0 -0
- {weathergrabber-0.0.8b7/weathergrabber/domain → weathergrabber-0.0.9a0/weathergrabber/domain/entities}/health_activities.py +0 -0
- {weathergrabber-0.0.8b7/weathergrabber/domain → weathergrabber-0.0.9a0/weathergrabber/domain/entities}/hourly_predictions.py +0 -0
- {weathergrabber-0.0.8b7/weathergrabber/domain → weathergrabber-0.0.9a0/weathergrabber/domain/entities}/label_value.py +0 -0
- {weathergrabber-0.0.8b7/weathergrabber/domain → weathergrabber-0.0.9a0/weathergrabber/domain/entities}/moon_phase_enum.py +0 -0
- {weathergrabber-0.0.8b7/weathergrabber/domain → weathergrabber-0.0.9a0/weathergrabber/domain/entities}/precipitation.py +0 -0
- {weathergrabber-0.0.8b7/weathergrabber/domain → weathergrabber-0.0.9a0/weathergrabber/domain/entities}/search.py +0 -0
- {weathergrabber-0.0.8b7/weathergrabber/domain → weathergrabber-0.0.9a0/weathergrabber/domain/entities}/temperature_hight_low.py +0 -0
- {weathergrabber-0.0.8b7/weathergrabber/domain → weathergrabber-0.0.9a0/weathergrabber/domain/entities}/timestamp.py +0 -0
- {weathergrabber-0.0.8b7/weathergrabber/domain → weathergrabber-0.0.9a0/weathergrabber/domain/entities}/today_details.py +0 -0
- {weathergrabber-0.0.8b7/weathergrabber/domain → weathergrabber-0.0.9a0/weathergrabber/domain/entities}/uv_index.py +0 -0
- {weathergrabber-0.0.8b7/weathergrabber/domain → weathergrabber-0.0.9a0/weathergrabber/domain/entities}/wind.py +0 -0
- {weathergrabber-0.0.8b7 → weathergrabber-0.0.9a0}/weathergrabber.egg-info/dependency_links.txt +0 -0
- {weathergrabber-0.0.8b7 → weathergrabber-0.0.9a0}/weathergrabber.egg-info/entry_points.txt +0 -0
- {weathergrabber-0.0.8b7 → weathergrabber-0.0.9a0}/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.
|
|
3
|
+
Version: 0.0.9a0
|
|
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
|
|
@@ -11,6 +11,9 @@ Description-Content-Type: text/markdown
|
|
|
11
11
|
License-File: LICENSE
|
|
12
12
|
Requires-Dist: pyquery>=1.4.3
|
|
13
13
|
Requires-Dist: requests>=2.32.4
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
16
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
|
|
14
17
|
Dynamic: license-file
|
|
15
18
|
|
|
16
19
|
# Weather Forecast CLI Script
|
|
@@ -34,6 +37,8 @@ This script fetches and parses weather forecast data from Weather.com and format
|
|
|
34
37
|
- **Console output**: Richly formatted weather data with icons.
|
|
35
38
|
- **Waybar JSON**: For integration with Waybar.
|
|
36
39
|
- Supports multiple languages for Weather.com data.
|
|
40
|
+
- **Offline support**: Automatically retrieves cached weather data when internet connection is unavailable.
|
|
41
|
+
- **Cache management**: SQLite-based caching system for storing weather forecasts and enabling offline access.
|
|
37
42
|
- Includes data such as:
|
|
38
43
|
- Current temperature and "feels-like" temperature.
|
|
39
44
|
- Wind speed, humidity, visibility, and air quality.
|
|
@@ -163,24 +168,42 @@ weathergrabber.main_cli()
|
|
|
163
168
|
|
|
164
169
|
### Options
|
|
165
170
|
|
|
166
|
-
- `--location-id`, `-l`
|
|
167
|
-
- `--lang`, `-L`
|
|
168
|
-
- `--output`, `-o`
|
|
169
|
-
- `--keep-open`, `-k`
|
|
170
|
-
- `--
|
|
171
|
-
- `--
|
|
171
|
+
- `--location-id`, `-l` : 64-character-hex code for location (from Weather.com)
|
|
172
|
+
- `--lang`, `-L` : Language (e.g., `pt-BR`, `fr-FR`). Defaults to system locale if not set.
|
|
173
|
+
- `--output`, `-o` : Output format. One of `console`, `json`, `waybar`, or `statistics`. Default: `console`.
|
|
174
|
+
- `--keep-open`, `-k` : Keep open and refresh every 5 minutes (only makes sense for `console` output).
|
|
175
|
+
- `--force-cache` : Retrieve weather data from cache regardless of internet connection availability. Useful for offline mode.
|
|
176
|
+
- `--cache-statistics` : Display cache database statistics including total forecasts, unique locations, unique search names, and database file path.
|
|
177
|
+
- `--icons`, `-i` : Icon set. `fa` for Font-Awesome, `emoji` for emoji icons. Default: `emoji`.
|
|
178
|
+
- `--log` : Set logging level. One of `debug`, `info`, `warning`, `error`, `critical`. Default: `critical`.
|
|
172
179
|
|
|
173
180
|
### Environment Variables
|
|
174
181
|
|
|
175
182
|
- `LANG` : Used as default language if `--lang` is not set.
|
|
176
183
|
- `WEATHER_LOCATION_ID` : Used as default location if neither `location_name` nor `--location-id` is set.
|
|
177
184
|
|
|
185
|
+
## Cache & Offline Support
|
|
186
|
+
|
|
187
|
+
The script maintains a SQLite cache database for storing weather forecasts. This enables several key features:
|
|
188
|
+
|
|
189
|
+
- **Automatic offline fallback**: When internet connection is unavailable, the script automatically retrieves the most recent cached weather data for the requested location.
|
|
190
|
+
- **Forced cache retrieval**: Use `--force-cache` to retrieve data exclusively from the cache, regardless of internet availability. This is useful for offline scenarios or reducing API calls.
|
|
191
|
+
- **Cache statistics**: Use `--cache-statistics` to display information about the cache database, including:
|
|
192
|
+
- Total number of cached forecasts
|
|
193
|
+
- Number of unique locations searched
|
|
194
|
+
- Number of unique search names used
|
|
195
|
+
- Database file path
|
|
196
|
+
|
|
197
|
+
By default, the cache database is stored in the system's temporary directory (`/tmp` on Linux/macOS).
|
|
198
|
+
|
|
178
199
|
### Example Usage
|
|
179
200
|
|
|
180
201
|
```sh
|
|
181
202
|
weathergrabber "London" --output console --lang en-GB
|
|
182
203
|
weathergrabber --location-id 1234567890abcdef... --output json
|
|
183
204
|
weathergrabber "Paris" -o waybar -i fa
|
|
205
|
+
weathergrabber "New York" --force-cache
|
|
206
|
+
weathergrabber --cache-statistics
|
|
184
207
|
```
|
|
185
208
|
|
|
186
209
|
Or as a Python module:
|
|
@@ -189,4 +212,6 @@ Or as a Python module:
|
|
|
189
212
|
python -m weathergrabber "London" --output console --lang en-GB
|
|
190
213
|
python -m weathergrabber --location-id 1234567890abcdef... --output json
|
|
191
214
|
python -m weathergrabber "Paris" -o waybar -i fa
|
|
215
|
+
python -m weathergrabber "Toronto" --force-cache
|
|
216
|
+
python -m weathergrabber --cache-statistics
|
|
192
217
|
```
|
|
@@ -19,6 +19,8 @@ This script fetches and parses weather forecast data from Weather.com and format
|
|
|
19
19
|
- **Console output**: Richly formatted weather data with icons.
|
|
20
20
|
- **Waybar JSON**: For integration with Waybar.
|
|
21
21
|
- Supports multiple languages for Weather.com data.
|
|
22
|
+
- **Offline support**: Automatically retrieves cached weather data when internet connection is unavailable.
|
|
23
|
+
- **Cache management**: SQLite-based caching system for storing weather forecasts and enabling offline access.
|
|
22
24
|
- Includes data such as:
|
|
23
25
|
- Current temperature and "feels-like" temperature.
|
|
24
26
|
- Wind speed, humidity, visibility, and air quality.
|
|
@@ -148,24 +150,42 @@ weathergrabber.main_cli()
|
|
|
148
150
|
|
|
149
151
|
### Options
|
|
150
152
|
|
|
151
|
-
- `--location-id`, `-l`
|
|
152
|
-
- `--lang`, `-L`
|
|
153
|
-
- `--output`, `-o`
|
|
154
|
-
- `--keep-open`, `-k`
|
|
155
|
-
- `--
|
|
156
|
-
- `--
|
|
153
|
+
- `--location-id`, `-l` : 64-character-hex code for location (from Weather.com)
|
|
154
|
+
- `--lang`, `-L` : Language (e.g., `pt-BR`, `fr-FR`). Defaults to system locale if not set.
|
|
155
|
+
- `--output`, `-o` : Output format. One of `console`, `json`, `waybar`, or `statistics`. Default: `console`.
|
|
156
|
+
- `--keep-open`, `-k` : Keep open and refresh every 5 minutes (only makes sense for `console` output).
|
|
157
|
+
- `--force-cache` : Retrieve weather data from cache regardless of internet connection availability. Useful for offline mode.
|
|
158
|
+
- `--cache-statistics` : Display cache database statistics including total forecasts, unique locations, unique search names, and database file path.
|
|
159
|
+
- `--icons`, `-i` : Icon set. `fa` for Font-Awesome, `emoji` for emoji icons. Default: `emoji`.
|
|
160
|
+
- `--log` : Set logging level. One of `debug`, `info`, `warning`, `error`, `critical`. Default: `critical`.
|
|
157
161
|
|
|
158
162
|
### Environment Variables
|
|
159
163
|
|
|
160
164
|
- `LANG` : Used as default language if `--lang` is not set.
|
|
161
165
|
- `WEATHER_LOCATION_ID` : Used as default location if neither `location_name` nor `--location-id` is set.
|
|
162
166
|
|
|
167
|
+
## Cache & Offline Support
|
|
168
|
+
|
|
169
|
+
The script maintains a SQLite cache database for storing weather forecasts. This enables several key features:
|
|
170
|
+
|
|
171
|
+
- **Automatic offline fallback**: When internet connection is unavailable, the script automatically retrieves the most recent cached weather data for the requested location.
|
|
172
|
+
- **Forced cache retrieval**: Use `--force-cache` to retrieve data exclusively from the cache, regardless of internet availability. This is useful for offline scenarios or reducing API calls.
|
|
173
|
+
- **Cache statistics**: Use `--cache-statistics` to display information about the cache database, including:
|
|
174
|
+
- Total number of cached forecasts
|
|
175
|
+
- Number of unique locations searched
|
|
176
|
+
- Number of unique search names used
|
|
177
|
+
- Database file path
|
|
178
|
+
|
|
179
|
+
By default, the cache database is stored in the system's temporary directory (`/tmp` on Linux/macOS).
|
|
180
|
+
|
|
163
181
|
### Example Usage
|
|
164
182
|
|
|
165
183
|
```sh
|
|
166
184
|
weathergrabber "London" --output console --lang en-GB
|
|
167
185
|
weathergrabber --location-id 1234567890abcdef... --output json
|
|
168
186
|
weathergrabber "Paris" -o waybar -i fa
|
|
187
|
+
weathergrabber "New York" --force-cache
|
|
188
|
+
weathergrabber --cache-statistics
|
|
169
189
|
```
|
|
170
190
|
|
|
171
191
|
Or as a Python module:
|
|
@@ -174,4 +194,6 @@ Or as a Python module:
|
|
|
174
194
|
python -m weathergrabber "London" --output console --lang en-GB
|
|
175
195
|
python -m weathergrabber --location-id 1234567890abcdef... --output json
|
|
176
196
|
python -m weathergrabber "Paris" -o waybar -i fa
|
|
197
|
+
python -m weathergrabber "Toronto" --force-cache
|
|
198
|
+
python -m weathergrabber --cache-statistics
|
|
177
199
|
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "weathergrabber"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.9a"
|
|
4
4
|
description = "A grabber for weather.com data with various output formats."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -14,6 +14,12 @@ dependencies = [
|
|
|
14
14
|
"requests>=2.32.4"
|
|
15
15
|
]
|
|
16
16
|
|
|
17
|
+
[project.optional-dependencies]
|
|
18
|
+
dev = [
|
|
19
|
+
"pytest>=8.0.0",
|
|
20
|
+
"pytest-cov>=4.1.0",
|
|
21
|
+
]
|
|
22
|
+
|
|
17
23
|
[build-system]
|
|
18
24
|
requires = ["setuptools>=61", "wheel"]
|
|
19
25
|
build-backend = "setuptools.build_meta"
|
|
@@ -23,4 +29,10 @@ weathergrabber = "weathergrabber.cli:main_cli"
|
|
|
23
29
|
|
|
24
30
|
[project.urls]
|
|
25
31
|
homepage = "https://github.com/cjuniorfox/weather"
|
|
26
|
-
repository = "https://github.com/cjuniorfox/weather"
|
|
32
|
+
repository = "https://github.com/cjuniorfox/weather"
|
|
33
|
+
|
|
34
|
+
[tool.pytest.ini_options]
|
|
35
|
+
filterwarnings = [
|
|
36
|
+
"ignore:unclosed database:ResourceWarning",
|
|
37
|
+
"ignore::DeprecationWarning:sqlite3",
|
|
38
|
+
]
|
|
@@ -12,7 +12,9 @@ def test_main_invokes_application(mock_app):
|
|
|
12
12
|
'lang': 'en-US',
|
|
13
13
|
'output': 'console',
|
|
14
14
|
'keep_open': False,
|
|
15
|
-
'icons': 'emoji'
|
|
15
|
+
'icons': 'emoji',
|
|
16
|
+
'force_cache': False,
|
|
17
|
+
'cache_statistics': False,
|
|
16
18
|
}
|
|
17
19
|
main(**params)
|
|
18
20
|
mock_app.assert_called_once()
|
|
@@ -25,7 +27,9 @@ def test_main_invokes_application(mock_app):
|
|
|
25
27
|
assert args[0].output_format.name.lower() == 'console'
|
|
26
28
|
assert args[0].keep_open is False
|
|
27
29
|
assert args[0].icons.name.lower() == 'emoji'
|
|
28
|
-
|
|
30
|
+
assert args[0].force_cache is False
|
|
31
|
+
assert args[0].cache_statistics is False
|
|
32
|
+
|
|
29
33
|
@patch('weathergrabber.core.WeatherGrabberApplication')
|
|
30
34
|
def test_main_sets_log_level(mock_app):
|
|
31
35
|
from weathergrabber.core import main
|
|
@@ -36,6 +40,8 @@ def test_main_sets_log_level(mock_app):
|
|
|
36
40
|
lang='fr-FR',
|
|
37
41
|
output='json',
|
|
38
42
|
keep_open=True,
|
|
39
|
-
icons='fa'
|
|
43
|
+
icons='fa',
|
|
44
|
+
force_cache=False,
|
|
45
|
+
cache_statistics=False,
|
|
40
46
|
)
|
|
41
47
|
assert logging.getLogger().level == logging.DEBUG
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import sqlite3
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import tempfile
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import Optional
|
|
7
|
+
import logging
|
|
8
|
+
from weathergrabber.domain.entities.forecast import Forecast
|
|
9
|
+
from weathergrabber.domain.adapter.mappers.forecast_mapper import forecast_to_dict, dict_to_forecast
|
|
10
|
+
|
|
11
|
+
class ForecastRepository:
|
|
12
|
+
def __init__(self, db_path: str = None):
|
|
13
|
+
# Use /tmp directory for ephemeral storage by default
|
|
14
|
+
if db_path is None:
|
|
15
|
+
db_path = os.path.join(tempfile.gettempdir(), "weather_forecasts.db")
|
|
16
|
+
|
|
17
|
+
self.logger = logging.getLogger(__name__)
|
|
18
|
+
self.logger.info(f"Initializing ForecastRepository with DB path: {db_path}")
|
|
19
|
+
self.db_path = db_path
|
|
20
|
+
self._initialize_database()
|
|
21
|
+
|
|
22
|
+
def _initialize_database(self):
|
|
23
|
+
self.logger.debug("Initializing database and creating tables if they do not exist.")
|
|
24
|
+
try:
|
|
25
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
26
|
+
cursor = conn.cursor()
|
|
27
|
+
cursor.execute('''
|
|
28
|
+
CREATE TABLE IF NOT EXISTS forecasts (
|
|
29
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
30
|
+
location_id TEXT NOT NULL,
|
|
31
|
+
search_name TEXT,
|
|
32
|
+
forecast_data TEXT NOT NULL,
|
|
33
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
34
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
35
|
+
)
|
|
36
|
+
''')
|
|
37
|
+
# Create indexes for fast retrieval
|
|
38
|
+
cursor.execute("CREATE INDEX IF NOT EXISTS idx_location_id ON forecasts (location_id)")
|
|
39
|
+
conn.commit()
|
|
40
|
+
self.logger.debug(f"Database initialized successfully at {self.db_path}")
|
|
41
|
+
except sqlite3.Error as e:
|
|
42
|
+
self.logger.error(f"Error initializing database: {e}")
|
|
43
|
+
raise
|
|
44
|
+
|
|
45
|
+
def save_forecast(self, location_id: str, search_name: str, forecast_data: Forecast) -> None:
|
|
46
|
+
forecast_dict_data = forecast_to_dict(forecast_data)
|
|
47
|
+
now = datetime.now().isoformat()
|
|
48
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
49
|
+
cursor = conn.cursor()
|
|
50
|
+
cursor.execute('''
|
|
51
|
+
INSERT INTO forecasts (location_id, search_name, forecast_data, created_at, updated_at)
|
|
52
|
+
VALUES (?, ?, ?, ?, ?)
|
|
53
|
+
''', (
|
|
54
|
+
location_id,
|
|
55
|
+
search_name,
|
|
56
|
+
json.dumps(forecast_dict_data),
|
|
57
|
+
now,
|
|
58
|
+
now
|
|
59
|
+
))
|
|
60
|
+
conn.commit()
|
|
61
|
+
|
|
62
|
+
def get_by_location_id(self, location_id: str) -> Optional[Forecast]:
|
|
63
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
64
|
+
cursor = conn.cursor()
|
|
65
|
+
cursor.execute('''
|
|
66
|
+
SELECT forecast_data FROM forecasts
|
|
67
|
+
WHERE location_id = ? ORDER BY created_at DESC
|
|
68
|
+
LIMIT 1
|
|
69
|
+
''', (location_id,))
|
|
70
|
+
row = cursor.fetchone()
|
|
71
|
+
forecast_dict_data = json.loads(row[0]) if row else None
|
|
72
|
+
return dict_to_forecast(forecast_dict_data) if forecast_dict_data else None
|
|
73
|
+
|
|
74
|
+
def get_by_search_name(self, search_name: str) -> Optional[Forecast]:
|
|
75
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
76
|
+
cursor = conn.cursor()
|
|
77
|
+
cursor.execute('''
|
|
78
|
+
SELECT forecast_data FROM forecasts
|
|
79
|
+
WHERE search_name = ? ORDER BY created_at DESC
|
|
80
|
+
LIMIT 1
|
|
81
|
+
''', (search_name,))
|
|
82
|
+
row = cursor.fetchone()
|
|
83
|
+
forecast_dict_data = json.loads(row[0]) if row else None
|
|
84
|
+
return dict_to_forecast(forecast_dict_data) if forecast_dict_data else None
|
|
85
|
+
|
|
86
|
+
def clear_cache(self) -> None:
|
|
87
|
+
"""Clear all cached forecasts from the database."""
|
|
88
|
+
try:
|
|
89
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
90
|
+
cursor = conn.cursor()
|
|
91
|
+
cursor.execute("DELETE FROM forecasts")
|
|
92
|
+
conn.commit()
|
|
93
|
+
self.logger.info("Cache cleared successfully")
|
|
94
|
+
except sqlite3.Error as e:
|
|
95
|
+
self.logger.error(f"Error clearing cache: {e}")
|
|
96
|
+
|
|
97
|
+
def get_cache_stats(self) -> dict:
|
|
98
|
+
"""Get statistics about the cached forecasts."""
|
|
99
|
+
try:
|
|
100
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
101
|
+
cursor = conn.cursor()
|
|
102
|
+
cursor.execute("SELECT COUNT(*) FROM forecasts")
|
|
103
|
+
total_count = cursor.fetchone()[0]
|
|
104
|
+
|
|
105
|
+
cursor.execute("""
|
|
106
|
+
SELECT COUNT(DISTINCT location_id) as unique_locations,
|
|
107
|
+
COUNT(DISTINCT search_name) as unique_search_names
|
|
108
|
+
FROM forecasts
|
|
109
|
+
""")
|
|
110
|
+
stats = cursor.fetchone()
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
'total_forecasts': total_count,
|
|
114
|
+
'unique_locations': stats[0],
|
|
115
|
+
'unique_search_names': stats[1],
|
|
116
|
+
'database_path': self.db_path
|
|
117
|
+
}
|
|
118
|
+
except sqlite3.Error as e:
|
|
119
|
+
self.logger.error(f"Error getting cache stats: {e}")
|
|
120
|
+
return {'error': str(e)}
|
|
121
|
+
|
|
122
|
+
def cleanup_old_forecasts(self, hours_old: int = 24) -> None:
|
|
123
|
+
"""Remove forecasts older than specified hours."""
|
|
124
|
+
try:
|
|
125
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
126
|
+
cursor = conn.cursor()
|
|
127
|
+
cursor.execute("""
|
|
128
|
+
DELETE FROM forecasts
|
|
129
|
+
WHERE created_at < datetime('now', '-{} hours')
|
|
130
|
+
""".format(hours_old))
|
|
131
|
+
deleted_count = cursor.rowcount
|
|
132
|
+
conn.commit()
|
|
133
|
+
self.logger.info(f"Cleaned up {deleted_count} old forecasts")
|
|
134
|
+
except sqlite3.Error as e:
|
|
135
|
+
self.logger.error(f"Error cleaning up old forecasts: {e}")
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
from weathergrabber.
|
|
1
|
+
from weathergrabber.application.usecases.weather_forecast_uc import WeatherForecastUC
|
|
2
2
|
from weathergrabber.domain.adapter.params import Params
|
|
3
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
|
|
4
|
+
from weathergrabber.domain.entities.weather_icon_enum import WeatherIconEnum
|
|
5
|
+
from weathergrabber.application.weathergrabber_application import WeatherGrabberApplication
|
|
6
6
|
import logging
|
|
7
7
|
|
|
8
8
|
class ConsoleTTY:
|
|
9
9
|
|
|
10
|
-
def __init__(self, use_case:
|
|
10
|
+
def __init__(self, use_case: WeatherForecastUC):
|
|
11
11
|
self.logger = logging.getLogger(__name__)
|
|
12
12
|
self.use_case = use_case
|
|
13
13
|
pass
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
from weathergrabber.
|
|
1
|
+
from weathergrabber.application.usecases.weather_forecast_uc import WeatherForecastUC
|
|
2
2
|
from weathergrabber.domain.adapter.params import Params
|
|
3
|
-
from weathergrabber.domain.adapter.
|
|
3
|
+
from weathergrabber.domain.adapter.mappers.forecast_mapper import forecast_to_dict
|
|
4
4
|
import logging
|
|
5
5
|
import json
|
|
6
6
|
|
|
7
7
|
class JsonTTY:
|
|
8
8
|
|
|
9
|
-
def __init__(self, use_case:
|
|
9
|
+
def __init__(self, use_case: WeatherForecastUC):
|
|
10
10
|
self.logger = logging.getLogger(__name__)
|
|
11
11
|
self.use_case = use_case
|
|
12
12
|
pass
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from weathergrabber.application.usecases.statistics_uc import StatisticsUC
|
|
2
|
+
from weathergrabber.domain.adapter.params import Params
|
|
3
|
+
from weathergrabber.domain.adapter.output_enum import OutputEnum
|
|
4
|
+
from weathergrabber.domain.adapter.mappers.statistics_mapper import statistics_to_dict
|
|
5
|
+
from weathergrabber.domain.entities.statistics import Statistics
|
|
6
|
+
import logging
|
|
7
|
+
import json
|
|
8
|
+
|
|
9
|
+
class StatisticsTTY:
|
|
10
|
+
def __init__(self, statistics_uc: StatisticsUC):
|
|
11
|
+
self.logger = logging.getLogger(__name__)
|
|
12
|
+
self.statistics_uc = statistics_uc
|
|
13
|
+
|
|
14
|
+
def execute(self, params : Params):
|
|
15
|
+
statistics = self.statistics_uc.execute(params)
|
|
16
|
+
print_value = self.json_print(statistics) if (params.output_format == OutputEnum.JSON) else self.tty_print(statistics)
|
|
17
|
+
print(print_value)
|
|
18
|
+
|
|
19
|
+
def tty_print(self, statistics: Statistics):
|
|
20
|
+
self.logger.info("Preparing TTY output for statistics")
|
|
21
|
+
lines = [
|
|
22
|
+
"",
|
|
23
|
+
"WeatherGrabber Statistics",
|
|
24
|
+
"=========================",
|
|
25
|
+
f"Total Forecasts: {statistics.total_forecasts}",
|
|
26
|
+
f"Unique Locations: {statistics.unique_locations}",
|
|
27
|
+
f"Unique Search Names: {statistics.unique_search_names}",
|
|
28
|
+
f"Database Path: {statistics.database_path}",
|
|
29
|
+
"",
|
|
30
|
+
]
|
|
31
|
+
return "\n".join(lines)
|
|
32
|
+
|
|
33
|
+
def json_print(self, statistics: Statistics):
|
|
34
|
+
self.logger.info("Preparing JSON output for statistics")
|
|
35
|
+
return json.dumps(statistics_to_dict(statistics))
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
from weathergrabber.
|
|
1
|
+
from weathergrabber.application.usecases.weather_forecast_uc import WeatherForecastUC
|
|
2
2
|
from weathergrabber.domain.adapter.params import Params
|
|
3
3
|
from weathergrabber.domain.adapter.icon_enum import IconEnum
|
|
4
|
-
from weathergrabber.domain.weather_icon_enum import WeatherIconEnum
|
|
4
|
+
from weathergrabber.domain.entities.weather_icon_enum import WeatherIconEnum
|
|
5
5
|
import logging
|
|
6
6
|
import json
|
|
7
7
|
|
|
8
8
|
class WaybarTTY:
|
|
9
9
|
|
|
10
|
-
def __init__(self, use_case:
|
|
10
|
+
def __init__(self, use_case: WeatherForecastUC):
|
|
11
11
|
self.logger = logging.getLogger(__name__)
|
|
12
12
|
self.use_case = use_case
|
|
13
13
|
pass
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from pyquery import PyQuery
|
|
3
|
-
from weathergrabber.domain.weather_icon_enum import WeatherIconEnum
|
|
4
|
-
from weathergrabber.domain.city_location import CityLocation
|
|
5
|
-
from weathergrabber.domain.timestamp import Timestamp
|
|
6
|
-
from weathergrabber.domain.day_night import DayNight
|
|
7
|
-
from weathergrabber.domain.current_conditions import CurrentConditions
|
|
3
|
+
from weathergrabber.domain.entities.weather_icon_enum import WeatherIconEnum
|
|
4
|
+
from weathergrabber.domain.entities.city_location import CityLocation
|
|
5
|
+
from weathergrabber.domain.entities.timestamp import Timestamp
|
|
6
|
+
from weathergrabber.domain.entities.day_night import DayNight
|
|
7
|
+
from weathergrabber.domain.entities.current_conditions import CurrentConditions
|
|
8
8
|
|
|
9
9
|
class ExtractCurrentConditionsService:
|
|
10
10
|
def __init__(self):
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from pyquery import PyQuery
|
|
3
|
-
from weathergrabber.domain.daily_predictions import DailyPredictions
|
|
4
|
-
from weathergrabber.domain.temperature_hight_low import TemperatureHighLow
|
|
5
|
-
from weathergrabber.domain.weather_icon_enum import WeatherIconEnum
|
|
6
|
-
from weathergrabber.domain.precipitation import Precipitation
|
|
3
|
+
from weathergrabber.domain.entities.daily_predictions import DailyPredictions
|
|
4
|
+
from weathergrabber.domain.entities.temperature_hight_low import TemperatureHighLow
|
|
5
|
+
from weathergrabber.domain.entities.weather_icon_enum import WeatherIconEnum
|
|
6
|
+
from weathergrabber.domain.entities.precipitation import Precipitation
|
|
7
7
|
from typing import List
|
|
8
8
|
|
|
9
9
|
class ExtractDailyForecastOldstyleService:
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from pyquery import PyQuery
|
|
3
|
-
from weathergrabber.domain.precipitation import Precipitation
|
|
4
|
-
from weathergrabber.domain.weather_icon_enum import WeatherIconEnum
|
|
5
|
-
from weathergrabber.domain.moon_phase_enum import MoonPhaseEnum
|
|
6
|
-
from weathergrabber.domain.moon_phase import MoonPhase
|
|
7
|
-
from weathergrabber.domain.temperature_hight_low import TemperatureHighLow
|
|
8
|
-
from weathergrabber.domain.daily_predictions import DailyPredictions
|
|
3
|
+
from weathergrabber.domain.entities.precipitation import Precipitation
|
|
4
|
+
from weathergrabber.domain.entities.weather_icon_enum import WeatherIconEnum
|
|
5
|
+
from weathergrabber.domain.entities.moon_phase_enum import MoonPhaseEnum
|
|
6
|
+
from weathergrabber.domain.entities.moon_phase import MoonPhase
|
|
7
|
+
from weathergrabber.domain.entities.temperature_hight_low import TemperatureHighLow
|
|
8
|
+
from weathergrabber.domain.entities.daily_predictions import DailyPredictions
|
|
9
9
|
|
|
10
10
|
from typing import List
|
|
11
11
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import logging
|
|
2
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.precipitation import Precipitation
|
|
3
|
+
from weathergrabber.domain.entities.hourly_predictions import HourlyPredictions
|
|
4
|
+
from weathergrabber.domain.entities.weather_icon_enum import WeatherIconEnum
|
|
5
|
+
from weathergrabber.domain.entities.precipitation import Precipitation
|
|
6
6
|
from typing import List
|
|
7
7
|
|
|
8
8
|
class ExtractHourlyForecastOldstyleService:
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import logging
|
|
2
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
|
|
3
|
+
from weathergrabber.domain.entities.hourly_predictions import HourlyPredictions
|
|
4
|
+
from weathergrabber.domain.entities.weather_icon_enum import WeatherIconEnum
|
|
5
|
+
from weathergrabber.domain.entities.uv_index import UVIndex
|
|
6
|
+
from weathergrabber.domain.entities.precipitation import Precipitation
|
|
7
|
+
from weathergrabber.domain.entities.wind import Wind
|
|
8
8
|
from typing import List
|
|
9
9
|
|
|
10
10
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import logging
|
|
2
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
|
|
3
|
+
from weathergrabber.domain.entities.today_details import TodayDetails
|
|
4
|
+
from weathergrabber.domain.entities.temperature_hight_low import TemperatureHighLow
|
|
5
|
+
from weathergrabber.domain.entities.uv_index import UVIndex
|
|
6
|
+
from weathergrabber.domain.entities.moon_phase import MoonPhase
|
|
7
|
+
from weathergrabber.domain.entities.moon_phase_enum import MoonPhaseEnum
|
|
8
|
+
from weathergrabber.domain.entities.label_value import LabelValue
|
|
9
|
+
from weathergrabber.domain.entities.sunrise_sunset import SunriseSunset
|
|
10
10
|
|
|
11
11
|
class ExtractTodayDetailsService:
|
|
12
12
|
def __init__(self):
|
|
@@ -57,7 +57,7 @@ class ExtractTodayDetailsService:
|
|
|
57
57
|
moon_phase_icon = icons.eq(7).attr('name') #'phase-2'
|
|
58
58
|
moon_phase_value = values.eq(7).text() #'Waxing Crescent'
|
|
59
59
|
|
|
60
|
-
self.logger.debug(
|
|
60
|
+
self.logger.debug("Creating domain objects for today details...")
|
|
61
61
|
|
|
62
62
|
sunrise_sunset = SunriseSunset(sunrise=sunrise, sunset=sunset)
|
|
63
63
|
high_low = TemperatureHighLow.from_string(high_low_value, label=high_low_label)
|
weathergrabber-0.0.9a0/weathergrabber/application/services/retrieve_forecast_from_cache_service.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from weathergrabber.adapter.repository.forecast_repository import ForecastRepository
|
|
2
|
+
from weathergrabber.domain.adapter.params import Params
|
|
3
|
+
from weathergrabber.domain.entities.forecast import Forecast
|
|
4
|
+
from typing import Optional
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
class RetrieveForecastFromCacheService:
|
|
8
|
+
def __init__(self, forecast_repository: ForecastRepository):
|
|
9
|
+
self.forecast_repository = forecast_repository
|
|
10
|
+
self.log = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
def execute(self, params: Params) -> Optional[Forecast]:
|
|
13
|
+
"""Retrieve forecast from cache based on parameters."""
|
|
14
|
+
if params.location.id:
|
|
15
|
+
forecast = self.forecast_repository.get_by_location_id(params.location.id)
|
|
16
|
+
elif params.location.search_name:
|
|
17
|
+
forecast = self.forecast_repository.get_by_search_name(params.location.search_name)
|
|
18
|
+
else:
|
|
19
|
+
self.log.debug("No location_id provided in params; cannot retrieve from cache.")
|
|
20
|
+
raise ValueError("Location ID must be provided to retrieve forecast from cache.")
|
|
21
|
+
|
|
22
|
+
if forecast:
|
|
23
|
+
self.log.debug("Forecast retrieved from cache successfully.")
|
|
24
|
+
return forecast
|
|
25
|
+
else:
|
|
26
|
+
self.log.debug("No forecast found in cache.")
|
|
27
|
+
raise ValueError("No forecast found in cache for the given location.")
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from weathergrabber.adapter.repository.forecast_repository import ForecastRepository
|
|
2
|
+
from weathergrabber.domain.entities.statistics import Statistics
|
|
3
|
+
from weathergrabber.domain.adapter.mappers.statistics_mapper import dict_to_statistics
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
class RetrieveStatisticsService:
|
|
7
|
+
def __init__(self, weather_repository: ForecastRepository):
|
|
8
|
+
self.log = logging.getLogger(__name__)
|
|
9
|
+
self.weather_repository = weather_repository
|
|
10
|
+
|
|
11
|
+
def execute(self) -> Statistics:
|
|
12
|
+
"""Retrieve and process database."""
|
|
13
|
+
self.log.debug("Retrieving statistics from repository...")
|
|
14
|
+
|
|
15
|
+
result = self.weather_repository.get_cache_stats()
|
|
16
|
+
statistics = dict_to_statistics(result)
|
|
17
|
+
|
|
18
|
+
self.log.debug(f"Statistics retrieved successfully: {statistics}")
|
|
19
|
+
return statistics
|
|
20
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from weathergrabber.adapter.repository.forecast_repository import ForecastRepository
|
|
2
|
+
from weathergrabber.domain.entities.forecast import Forecast
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SaveForecastToCacheService:
|
|
7
|
+
def __init__(self, forecast_repository: ForecastRepository):
|
|
8
|
+
self.forecast_repository = forecast_repository
|
|
9
|
+
self.logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
def execute(self, forecast : Forecast) -> None:
|
|
12
|
+
|
|
13
|
+
self.logger.info("Saving forecast to cache")
|
|
14
|
+
|
|
15
|
+
self.forecast_repository.save_forecast(
|
|
16
|
+
location_id=forecast.search.id,
|
|
17
|
+
search_name=forecast.search.search_name,
|
|
18
|
+
forecast_data=forecast
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
self.logger.debug("Forecast saved to cache successfully")
|