finished hw 4

This commit is contained in:
2026-03-12 10:23:51 -04:00
parent 6a20f6f7db
commit 504d3da228
4 changed files with 286 additions and 0 deletions

251
HW 4/hw4.py Normal file
View File

@@ -0,0 +1,251 @@
"""
HW 4: Open-Meteo Weather Utility
Benjamin Adovasio
API documentation: https://open-meteo.com/en/docs
Description:
This utility fetches current weather conditions from the Open-Meteo
Forecast API. You provide a latitude and longitude on the command line,
and the script prints a small weather report for that location.
How to run:
python3 hw4.py --lat 44.1004 --lon -70.2148
python3 hw4.py --lat 44.1004 --lon -70.2148 --temp-unit fahrenheit --wind-unit mph
python3 hw4.py --lat 44.1004 --lon -70.2148 --timezone America/New_York
"""
import sys
from typing import Dict, List, Optional
BASE_URL = "https://api.open-meteo.com/v1/forecast"
CURRENT_FIELDS = [
"temperature_2m",
"relative_humidity_2m",
"apparent_temperature",
"is_day",
"precipitation",
"weather_code",
"cloud_cover",
"wind_speed_10m",
"wind_direction_10m",
]
#weather codes coppied from api docs
WEATHER_CODES = {
0: "Clear sky",
1: "Mainly clear",
2: "Partly cloudy",
3: "Overcast",
45: "Fog",
48: "Depositing rime fog",
51: "Light drizzle",
53: "Moderate drizzle",
55: "Dense drizzle",
56: "Light freezing drizzle",
57: "Dense freezing drizzle",
61: "Slight rain",
63: "Moderate rain",
65: "Heavy rain",
66: "Light freezing rain",
67: "Heavy freezing rain",
71: "Slight snow fall",
73: "Moderate snow fall",
75: "Heavy snow fall",
77: "Snow grains",
80: "Slight rain showers",
81: "Moderate rain showers",
82: "Violent rain showers",
85: "Slight snow showers",
86: "Heavy snow showers",
95: "Thunderstorm",
96: "Thunderstorm with slight hail",
99: "Thunderstorm with heavy hail",
}
def print_help() -> None:
"""Print a help message describing how to use the utility."""
print(
"""Open-Meteo Weather Utility
Usage:
python3 hw4.py --lat LATITUDE --lon LONGITUDE [options]
Required arguments:
--lat LATITUDE Latitude for the weather lookup (-90 to 90)
--lon LONGITUDE Longitude for the weather lookup (-180 to 180)
Optional arguments found via api docs:
--temp-unit UNIT celsius or fahrenheit (default: celsius)
--wind-unit UNIT kmh, ms, mph, or kn (default: kmh)
--timezone ZONE Time zone name or auto (default: auto)
--help Show this help message
"""
)
def parse_args(argv: List[str]) -> Optional[Dict[str, str]]:
"""Parse command-line arguments using sys.argv-style input."""
if not argv or "--help" in argv:
print_help()
return None
option_map = {
"--lat": "latitude",
"--lon": "longitude",
"--temp-unit": "temperature_unit",
"--wind-unit": "wind_speed_unit",
"--timezone": "timezone",
}
parsed = {
"temperature_unit": "celsius",
"wind_speed_unit": "kmh",
"timezone": "auto",
}
index = 0
while index < len(argv):
flag = argv[index]
if flag not in option_map:
raise ValueError(f"Unknown argument: {flag}")
if index + 1 >= len(argv):
raise ValueError(f"Missing value after {flag}")
parsed[option_map[flag]] = argv[index + 1]
index += 2
if "latitude" not in parsed or "longitude" not in parsed:
raise ValueError("You must provide both --lat and --lon.")
try:
latitude = float(parsed["latitude"])
longitude = float(parsed["longitude"])
except ValueError as err:
raise ValueError("Latitude and longitude must be numbers.") from err
if not -90 <= latitude <= 90:
raise ValueError("Latitude must be between -90 and 90.")
if not -180 <= longitude <= 180:
raise ValueError("Longitude must be between -180 and 180.")
if parsed["temperature_unit"] not in {"celsius", "fahrenheit"}:
raise ValueError("Temperature unit must be celsius or fahrenheit.")
if parsed["wind_speed_unit"] not in {"kmh", "ms", "mph", "kn"}:
raise ValueError("Wind unit must be kmh, ms, mph, or kn.")
parsed["latitude"] = str(latitude)
parsed["longitude"] = str(longitude)
return parsed
def weather_description(code: int) -> str:
"""Return a readable weather description for an Open-Meteo WMO code."""
return WEATHER_CODES.get(code, "Unknown weather condition")
def fetch_weather(query_options: Dict[str, str]) -> Dict:
"""Send the GET request to Open-Meteo and return the JSON response."""
try:
import requests
except ModuleNotFoundError as err:
raise RuntimeError(
"The requests library is not installed. Install it with: "
"python3 -m pip install requests"
) from err
# Open-Meteo parameters used in this utility:
# - latitude / longitude: required coordinates for the requested location.
# - current: tells the API which current-condition fields to include.
# - temperature_unit: lets the user choose Celsius or Fahrenheit.
# - wind_speed_unit: lets the user choose km/h, m/s, mph, or knots.
# - timezone: returns times in the requested zone, or auto-detects the zone.
params = {
"latitude": query_options["latitude"],
"longitude": query_options["longitude"],
"current": ",".join(CURRENT_FIELDS),
"temperature_unit": query_options["temperature_unit"],
"wind_speed_unit": query_options["wind_speed_unit"],
"timezone": query_options["timezone"],
}
try:
response = requests.get(BASE_URL, params=params, timeout=10)
response.raise_for_status()
except requests.exceptions.RequestException as err:
raise ConnectionError(f"Request failed: {err}") from err
payload = response.json()
if payload.get("error"):
raise ValueError(payload.get("reason", "The API returned an unknown error."))
return payload
def display_weather_report(payload: Dict) -> None:
"""Print a readable weather report from the API response."""
current = payload["current"]
units = payload["current_units"]
code = current["weather_code"]
daylight = "Day" if current["is_day"] == 1 else "Night"
print("Open-Meteo Weather Report")
print(f"Coordinates: {payload['latitude']}, {payload['longitude']}")
print(f"Timezone: {payload['timezone']}")
print(f"Observed at: {current['time']}")
print(f"Condition: {weather_description(code)} (WMO code {code})")
print(f"Day/Night: {daylight}")
print(f"Temperature: {current['temperature_2m']} {units['temperature_2m']}")
print(
f"Feels like: {current['apparent_temperature']} "
f"{units['apparent_temperature']}"
)
print(
f"Humidity: {current['relative_humidity_2m']} "
f"{units['relative_humidity_2m']}"
)
print(f"Precipitation: {current['precipitation']} {units['precipitation']}")
print(f"Cloud cover: {current['cloud_cover']} {units['cloud_cover']}")
print(
f"Wind: {current['wind_speed_10m']} {units['wind_speed_10m']} "
f"at {current['wind_direction_10m']} {units['wind_direction_10m']}"
)
def main() -> int:
"""Coordinate argument parsing, API access, and report output."""
try:
query_options = parse_args(sys.argv[1:])
if query_options is None:
return 0
payload = fetch_weather(query_options)
display_weather_report(payload)
return 0
except ValueError as err:
print(f"Error: {err}")
print()
print_help()
return 1
except RuntimeError as err:
print(err)
return 1
except ConnectionError as err:
print(err)
return 1
except KeyError as err:
print(f"Unexpected API response. Missing field: {err}")
return 1
if __name__ == "__main__":
sys.exit(main())