finished hw 4
This commit is contained in:
251
HW 4/hw4.py
Normal file
251
HW 4/hw4.py
Normal 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())
|
||||
Reference in New Issue
Block a user