From 504d3da228d03bfb7f9cad757f204113b4537171 Mon Sep 17 00:00:00 2001 From: Benjamin Adovasio Date: Thu, 12 Mar 2026 10:23:51 -0400 Subject: [PATCH] finished hw 4 --- HW 4/__pycache__/hw4.cpython-314.pyc | Bin 0 -> 10560 bytes HW 4/hw4.py | 251 +++++++++++++++++++++++++++ modify.sh | 13 ++ setup_exam.sh | 22 +++ 4 files changed, 286 insertions(+) create mode 100644 HW 4/__pycache__/hw4.cpython-314.pyc create mode 100644 HW 4/hw4.py create mode 100644 modify.sh create mode 100644 setup_exam.sh diff --git a/HW 4/__pycache__/hw4.cpython-314.pyc b/HW 4/__pycache__/hw4.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f1701420462c35cedfa6958d45e5659a10335c4b GIT binary patch literal 10560 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 literal 0 HcmV?d00001 diff --git a/HW 4/hw4.py b/HW 4/hw4.py new file mode 100644 index 0000000..125f815 --- /dev/null +++ b/HW 4/hw4.py @@ -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()) diff --git a/modify.sh b/modify.sh new file mode 100644 index 0000000..a5b8e28 --- /dev/null +++ b/modify.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +BASE_DIR="$HOME/dcs211_exam" + +cd "$BASE_DIR"/data + +mv notes.txt lab_notes.txt + +mv "$BASE_DIR"/data/lab3.py "$BASE_DIR"/backup/lab3.py + +cp "$BASE_DIR"/data/lab1.py "$BASE_DIR"/scripts/lab1.py + +rm "$BASE_DIR"/data/lab2.py \ No newline at end of file diff --git a/setup_exam.sh b/setup_exam.sh new file mode 100644 index 0000000..cf3aaa4 --- /dev/null +++ b/setup_exam.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +BASE_DIR="$HOME/dcs211_exam" + +mkdir "$BASE_DIR"/data +mkdir "$BASE_DIR"/scripts +mkdir "$BASE_DIR"/backup + +cd "$BASE_DIR"/data + +touch lab1.py +touch lab2.py +touch lab3.py +touch notes.txt +touch image1.png +touch image2.jpg + +ls *.py + +ls *.png +ls *.jpg +ls *.gif \ No newline at end of file