-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrunner.py
More file actions
99 lines (77 loc) · 3.14 KB
/
Copy pathrunner.py
File metadata and controls
99 lines (77 loc) · 3.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
"""Runner module for the sample Python app.
Handles fetching, validation, and display of astronomical data.
"""
import time
from datetime import date
from loguru import logger
from sample_python_app.core import (
FETCH_COUNTER,
FETCH_DURATION,
weather_settings,
)
from sample_python_app.exceptions import AppError
from sample_python_app.services import (
CustomHTTPClient,
fetch_astronomical_data_from_api,
fetch_hourly_forecast_from_api,
set_next_hour_forecast_temperature,
)
from sample_python_app.ui import display_astronomical_data
class AstroFetcher:
"""Fetches astronomical data and displays only once per day.
Accepts an `HTTPClient` to use for all outbound requests so the
runner can own the client's lifecycle and tests can inject mocks.
"""
def __init__(self, client: CustomHTTPClient) -> None:
"""Initialize the AstroFetcher with an optional HTTP client."""
self._last_displayed_day: str | None = None
self.client = client
def fetch(self, *, exit_on_error: bool = True) -> None:
"""Fetch astronomical data and display if not already shown today."""
lat = weather_settings.LOCATION.latitude
lon = weather_settings.LOCATION.longitude
logger.info("Using latitude={} longitude={}", lat, lon)
start = time.time()
try:
astro = fetch_astronomical_data_from_api(lat, lon, client=self.client)
forecast = fetch_hourly_forecast_from_api(lat, lon, client=self.client)
set_next_hour_forecast_temperature(forecast, location=f"{lat},{lon}")
FETCH_COUNTER.inc()
except AppError as exc:
logger.error("Weather fetch failed: {}", exc)
if exit_on_error:
raise SystemExit(1) from None
return
finally:
FETCH_DURATION.observe(time.time() - start)
today = date.today().isoformat()
if self._last_displayed_day != today:
display_astronomical_data(astro, forecast)
self._last_displayed_day = today
def reset_display(self):
"""Reset the last displayed day so display will occur again."""
self._last_displayed_day = None
def close(self) -> None:
"""Close the associated HTTP client if present.
This allows the runner to delegate shutdown responsibility to the
fetcher when it owns the client's lifecycle.
"""
if hasattr(self, "client") and self.client is not None:
try:
self.client.close()
except Exception:
logger.exception("Error closing HTTP client in AstroFetcher")
runner_client = CustomHTTPClient(
headers=weather_settings.WEATHER_HEADERS,
base_url=weather_settings.WEATHER_API_BASE,
)
fetcher = AstroFetcher(client=runner_client)
def shutdown_runner() -> None:
"""Shutdown helper to close long-lived resources owned by the runner.
Call this from application shutdown hooks to ensure the HTTP client is
properly closed and connections are released.
"""
try:
fetcher.close()
except Exception:
logger.exception("Error during runner shutdown")