44from pydantic import ValidationError
55
66from sample_python_app .core import setup_logger
7- from sample_python_app .models import AstronomicalData , WeatherGovFeature
7+ from sample_python_app .models import (
8+ AstronomicalData ,
9+ ForecastFeature ,
10+ WeatherGovFeature ,
11+ )
812
913
1014def fetch_astronomical_data_from_api (lat : float , lon : float ) -> AstronomicalData :
@@ -24,7 +28,7 @@ def fetch_astronomical_data_from_api(lat: float, lon: float) -> AstronomicalData
2428 """
2529 logger = setup_logger (mode = "silent" )
2630 url = f"https://api.weather.gov/points/{ lat } ,{ lon } "
27- headers = {"User-Agent" : "(myweatherapp.com, contact@myweatherapp .com)" }
31+ headers = {"User-Agent" : "(milsman2, milsman2@gmail .com)" }
2832 logger .info (f"Fetching astronomical data from URL: { url } " )
2933 logger .info (f"Request headers: { headers } " )
3034 try :
@@ -38,3 +42,80 @@ def fetch_astronomical_data_from_api(lat: float, lon: float) -> AstronomicalData
3842 except ValidationError as e :
3943 logger .error (f"Data validation error: { e } " )
4044 raise
45+
46+
47+ def fetch_hourly_forecast_from_api (lat : float , lon : float ) -> ForecastFeature :
48+ """Fetch hourly forecast Feature for the given coordinates.
49+
50+ This function first queries the `/points/{lat},{lon}` endpoint to resolve
51+ the appropriate grid URL, then requests the hourly forecast and validates
52+ it against `ForecastFeature`.
53+
54+ Args:
55+ lat (float): Latitude of the location.
56+ lon (float): Longitude of the location.
57+
58+ Returns:
59+ ForecastFeature: Parsed and validated hourly forecast feature.
60+
61+ Raises:
62+ httpx.HTTPError: If an HTTP request fails.
63+ ValidationError: If the response fails pydantic validation.
64+
65+ """
66+ logger = setup_logger (mode = "silent" )
67+ headers = {"User-Agent" : "(milsman2, milsman2@gmail.com)" }
68+
69+ # Resolve point metadata to find the hourly forecast URL
70+ points_url = f"https://api.weather.gov/points/{ lat } ,{ lon } "
71+ logger .info (f"Resolving grid for coordinates { lat } ,{ lon } : { points_url } " )
72+ resp = httpx .get (points_url , headers = headers )
73+ resp .raise_for_status ()
74+ points_data = resp .json ()
75+
76+ point_model = WeatherGovFeature .model_validate (points_data )
77+ forecast_url = point_model .properties .forecast_hourly
78+ logger .info (f"Fetching hourly forecast from: { forecast_url } " )
79+
80+ resp2 = httpx .get (forecast_url , headers = headers )
81+ resp2 .raise_for_status ()
82+ forecast_data = resp2 .json ()
83+
84+ forecast_model = ForecastFeature .model_validate (forecast_data )
85+ logger .info ("Hourly forecast fetched and validated." )
86+ return forecast_model
87+
88+
89+ def fetch_hourly_forecast_by_grid (
90+ grid_id : str , grid_x : int , grid_y : int
91+ ) -> ForecastFeature :
92+ """Fetch hourly forecast Feature directly from grid coordinates.
93+
94+ Builds the gridpoints URL (for example `HGX/59,98`) and fetches the
95+ hourly forecast Feature at
96+ `/gridpoints/{grid_id}/{grid_x},{grid_y}/forecast/hourly`.
97+
98+ Args:
99+ grid_id: Grid identifier (e.g. "HGX").
100+ grid_x: Grid X coordinate (e.g. 59).
101+ grid_y: Grid Y coordinate (e.g. 98).
102+
103+ Returns:
104+ ForecastFeature: Parsed and validated hourly forecast feature.
105+
106+ """
107+ logger = setup_logger (mode = "silent" )
108+ headers = {"User-Agent" : "(milsman2, milsman2@gmail.com)" }
109+
110+ url = (
111+ "https://api.weather.gov/gridpoints/ "
112+ f"{ grid_id } /{ grid_x } ,{ grid_y } /forecast/hourly"
113+ )
114+ logger .info (f"Fetching hourly forecast by grid from: { url } " )
115+ resp = httpx .get (url , headers = headers )
116+ resp .raise_for_status ()
117+ data = resp .json ()
118+
119+ model = ForecastFeature .model_validate (data )
120+ logger .info ("Hourly forecast (grid) fetched and validated." )
121+ return model
0 commit comments