|
6 | 6 | # app/runner.py |
7 | 7 | import json |
8 | 8 | import time |
| 9 | +from datetime import date |
9 | 10 |
|
10 | 11 | import httpx |
11 | 12 | from pydantic import ValidationError |
|
14 | 15 | FETCH_COUNTER, |
15 | 16 | FETCH_DURATION, |
16 | 17 | FETCH_ERRORS, |
17 | | - display_astronomical_data, |
18 | 18 | setup_logger, |
19 | 19 | weather_settings, |
20 | 20 | ) |
21 | 21 | from sample_python_app.exceptions import AppError |
22 | 22 | from sample_python_app.services import fetch_astronomical_data_from_api |
| 23 | +from sample_python_app.ui import display_astronomical_data |
23 | 24 |
|
24 | 25 | logger = setup_logger("normal") |
25 | 26 |
|
26 | 27 |
|
27 | | -def fetch_astro_data(*, exit_on_error: bool = True) -> None: |
28 | | - """Fetch and display astronomical data once, with error handling.""" |
29 | | - lat = weather_settings.LOCATION.latitude |
30 | | - lon = weather_settings.LOCATION.longitude |
31 | | - logger.info(f"Using latitude={lat} longitude={lon}") |
| 28 | +class AstroFetcher: |
| 29 | + """Fetches astronomical data and displays only once per day.""" |
32 | 30 |
|
33 | | - start = time.time() |
| 31 | + def __init__(self) -> None: |
| 32 | + """Initialize the AstroFetcher with no last displayed day.""" |
| 33 | + self._last_displayed_day: str | None = None |
34 | 34 |
|
35 | | - try: |
36 | | - astro = fetch_astronomical_data_from_api(lat, lon) |
37 | | - FETCH_COUNTER.inc() |
38 | | - except httpx.HTTPStatusError as exc: |
39 | | - _handle_fetch_error(exc, exit_on_error) |
40 | | - return |
41 | | - except httpx.RequestError as exc: |
42 | | - _handle_fetch_error(exc, exit_on_error) |
43 | | - return |
44 | | - except ValidationError as exc: |
45 | | - _handle_fetch_error(exc, exit_on_error) |
46 | | - return |
47 | | - except json.JSONDecodeError as exc: |
48 | | - _handle_fetch_error(exc, exit_on_error) |
49 | | - return |
50 | | - except AppError as exc: |
51 | | - _handle_fetch_error(exc, exit_on_error) |
52 | | - return |
53 | | - finally: |
54 | | - FETCH_DURATION.observe(time.time() - start) |
| 35 | + def fetch(self, *, exit_on_error: bool = True) -> None: |
| 36 | + """Fetch astronomical data and display if not already displayed today.""" |
| 37 | + lat = weather_settings.LOCATION.latitude |
| 38 | + lon = weather_settings.LOCATION.longitude |
| 39 | + logger.info(f"Using latitude={lat} longitude={lon}") |
| 40 | + start = time.time() |
| 41 | + try: |
| 42 | + astro = fetch_astronomical_data_from_api(lat, lon) |
| 43 | + FETCH_COUNTER.inc() |
| 44 | + except ( |
| 45 | + httpx.HTTPStatusError, |
| 46 | + httpx.RequestError, |
| 47 | + ValidationError, |
| 48 | + json.JSONDecodeError, |
| 49 | + AppError, |
| 50 | + ) as exc: |
| 51 | + self._handle_fetch_error(exc, exit_on_error) |
| 52 | + return |
| 53 | + finally: |
| 54 | + FETCH_DURATION.observe(time.time() - start) |
| 55 | + today_str = date.today().isoformat() |
| 56 | + if self._last_displayed_day != today_str: |
| 57 | + display_astronomical_data(astro) |
| 58 | + self._last_displayed_day = today_str |
55 | 59 |
|
56 | | - display_astronomical_data(astro) |
| 60 | + def reset_display(self): |
| 61 | + """Reset the last displayed day so display will occur again.""" |
| 62 | + self._last_displayed_day = None |
57 | 63 |
|
| 64 | + def _handle_fetch_error(self, exc: Exception, exit_on_error: bool) -> None: |
| 65 | + FETCH_ERRORS.inc() |
| 66 | + if isinstance(exc, httpx.HTTPStatusError): |
| 67 | + logger.error("HTTP status error: %s", exc) |
| 68 | + elif isinstance(exc, httpx.RequestError): |
| 69 | + logger.error("Network error: %s", exc) |
| 70 | + elif isinstance(exc, ValidationError): |
| 71 | + logger.error("Validation error: %s", exc) |
| 72 | + elif isinstance(exc, json.JSONDecodeError): |
| 73 | + logger.error("JSON decode error: %s", exc) |
| 74 | + else: |
| 75 | + logger.exception("Unexpected error") |
| 76 | + if exit_on_error: |
| 77 | + raise SystemExit(1) from exc |
| 78 | + raise AppError(str(exc)) from exc |
58 | 79 |
|
59 | | -def _handle_fetch_error(exc: Exception, exit_on_error: bool) -> None: |
60 | | - """Handle errors during the fetch operation. |
61 | 80 |
|
62 | | - Log appropriately and update metrics. |
63 | | - """ |
64 | | - FETCH_ERRORS.inc() |
65 | | - if isinstance(exc, httpx.HTTPStatusError): |
66 | | - logger.error("HTTP status error: %s", exc) |
67 | | - elif isinstance(exc, httpx.RequestError): |
68 | | - logger.error("Network error: %s", exc) |
69 | | - elif isinstance(exc, ValidationError): |
70 | | - logger.error("Validation error: %s", exc) |
71 | | - elif isinstance(exc, json.JSONDecodeError): |
72 | | - logger.error("JSON decode error: %s", exc) |
73 | | - else: |
74 | | - logger.exception("Unexpected error") |
75 | | - |
76 | | - if exit_on_error: |
77 | | - raise SystemExit(1) from exc |
78 | | - |
79 | | - raise AppError(str(exc)) from exc |
| 81 | +fetcher = AstroFetcher() |
0 commit comments