From 76f772bd4b6c5697a0639a75a772e69611e01200 Mon Sep 17 00:00:00 2001 From: jcaley Date: Wed, 10 Dec 2025 09:43:47 -0500 Subject: [PATCH] i made it look better --- friend_functions.py | 7 +- weather/forcast.txt | 28 ++++++++ weather/weather.py | 74 ++++++++++++++++---- weather/weather_apis.py | 151 ++++++++++++++++++++-------------------- 4 files changed, 169 insertions(+), 91 deletions(-) create mode 100644 weather/forcast.txt diff --git a/friend_functions.py b/friend_functions.py index 6f5ecf7..16debe1 100644 --- a/friend_functions.py +++ b/friend_functions.py @@ -17,7 +17,8 @@ def count_people(people): >>> count_people(friends) 10 """ - raise NotImplementedError() + return len(people) + def get_email(people, name): """Returns the named person's email address. If there is no such person, returns None. @@ -27,7 +28,9 @@ def get_email(people, name): >>> get_email(friends, "Tad Winters") None """ - raise NotImplementedError() + if person["name"] == name: + + return person["email"] def count_favorite_colors(people, name): """Returns the number of colors liked by the named person. If there is no such person, returns None. diff --git a/weather/forcast.txt b/weather/forcast.txt new file mode 100644 index 0000000..5f88c1d --- /dev/null +++ b/weather/forcast.txt @@ -0,0 +1,28 @@ +forecasts = [ + { + 'name': 'Today', + 'temperature': 33, + 'wind_speed': '15 mph', + 'wind_direction': 'S', + 'description': 'Cloudy then Snow Showers Likely' + }, + { + 'name': 'Tonight', + 'temperature': 29, + 'wind_speed': '10 to 18 mph', + 'wind_direction': 'SW', + 'description': 'Snow Showers' + }, + { + 'name': 'Wednesday', + 'temperature': 38, + 'wind_speed': '12 to 16 mph', + 'wind_direction': 'SW', + 'description': 'Rain And Snow' + }, +] + +today_forecast = [f for f in forecasts if f['name'] == 'Today'] +print(today_forecast) + + 'wind_speed': '9 to 13 mph', 'wind_direction': 'W', 'description': 'Chance Snow Showers'}, {'name': 'Friday', 'temperature': 31, 'wind_speed': '10 mph', 'wind_direction': 'W', 'description': 'Chance Snow Showers'}, {'name': 'Friday Night', 'temperature': 20, 'wind_speed': '7 to 12 mph', 'wind_direction': 'SW', 'description': 'Snow Showers Likely'}, {'name': 'Saturday', 'temperature': 27, 'wind_speed': '12 to 15 mph', 'wind_direction': 'W', 'description': 'Snow Showers Likely'}, {'name': 'Saturday Night', 'temperature': 15, 'wind_speed': '9 to 13 mph', 'wind_direction': 'W', 'description': 'Chance Snow Showers'}, {'name': 'Sunday', 'temperature': 24, 'wind_speed': '9 to 13 mph', 'wind_direction': 'W', 'description': 'Chance Snow Showers'}, {'name': 'Sunday Night', 'temperature': 11, 'wind_speed': '7 to 10 mph', 'wind_direction': 'W', 'description': 'Chance Snow Showers'}, {'name': 'Monday', 'temperature': 25, 'wind_speed': '8 to 12 mph', 'wind_direction': 'SW', 'description': 'Chance Snow Showers'}, {'name': 'Monday Night', 'temperature': 15, 'wind_speed': '10 mph', 'wind_direction': 'SW', 'description': 'Chance Snow Showers'}] diff --git a/weather/weather.py b/weather/weather.py index 75fd2f6..af09729 100644 --- a/weather/weather.py +++ b/weather/weather.py @@ -1,24 +1,74 @@ -# weather.py -# ------------ -# By MWC Contributors -# -# Defines `print_weather`, which does all the work of fetching -# the requested weather data and printing it out to the screen -# in a sensible way. -# -# It's your job to implement this function. - -from weather.weather_apis import ( +ffrom weather.weather_apis import ( geocode_location, estimate_location, get_weather_office, get_forecast ) +# ----------------------------------------------------------- +# Choose weather emoji based on the description +# ----------------------------------------------------------- +def choose_emoji(description): + desc = description.lower() + + if "snow" in desc: + return "❄️" + if "rain" in desc or "shower" in desc: + return "🌧️" + if "thunder" in desc or "storm" in desc: + return "⛈️" + if "cloud" in desc: + return "☁️" + if "sunny" in desc or "clear" in desc: + return "☀️" + if "wind" in desc or "breeze" in desc: + return "💨" + if "fog" in desc or "mist" in desc: + return "🌫️" + + return "🌡️" # default + +# ----------------------------------------------------------- +# Main weather print function +# ----------------------------------------------------------- def print_weather(location=None, metric=False, verbose=False): """Prints out a weather report using the provided location, or using the user's current location if no location was provided. When metric is True, prints out the weather in metric units. When verbose is True, prints out a more detailed report. """ - print("Not finished...") # YOUR CODE HERE! + + # Get coordinates + if location: + coordinates = geocode_location(location) + else: + coordinates = estimate_location() + + # Get NOAA office + forecast + office = get_weather_office(coordinates["lat"], coordinates["lng"]) + forecast = get_forecast(office["office"], office["x"], office["y"], metric=metric) + + # Pretty output + print("\n====== Weather Forecast ======") + print(f"Location: {location if location else 'Your Current Location'}") + print(f"Office: {office['office']}") + print("=================================\n") + + if not forecast: + print("No forecast available.") + return + + # Print only TODAY + today = forecast[0] + emoji = choose_emoji(today["description"]) + + print(f"{emoji} --- {today['name']} --- {emoji}") + print(f"{emoji} {today['description']}") + print(f"🌡️ Temperature: {today['temperature']}°{'C' if metric else 'F'}") + print(f"💨 Wind: {today['wind_speed']} from {today['wind_direction']}") + + if verbose: + print("\nAdditional Details:") + for key, value in today.items(): + if key not in ("name", "description", "temperature", "wind_speed", "wind_direction"): + print(f" - {key}: {value}") \ No newline at end of file diff --git a/weather/weather_apis.py b/weather/weather_apis.py index a70b6a1..5cbdafc 100644 --- a/weather/weather_apis.py +++ b/weather/weather_apis.py @@ -1,90 +1,87 @@ -# weather_apis.py -# --------------- -# By MWC Contributors -# -# This module contains functions which interact with external APIs related to weather. -# The module relies on USA-specific services; it will need to be extended using local -# services for other regions. -# The National Weather Service (NWS) provides weather forecasting services across US -# states and territories. NWS divides the country into a grid of 2.5km squares, and -# provides a forecast for each grid square. -# -# You will need to use these functions, but you don't need to edit this file. - -import geocoder -from geocoder.osm import OsmQuery import requests -class OsmQueryWithHeaders(OsmQuery): - def _build_headers(self, provider_key, **kwargs): - return {"User-Agent": "Making With Code CS Curriculum"} +# ----------------------------------------------------------- +# Geocode a text location (EX: "Boston, MA") using Nominatim +# ----------------------------------------------------------- +def geocode_location(location): + url = "https://nominatim.openstreetmap.org/search" + params = { + "q": location, + "format": "json", + "limit": 1 + } -def geocode_location(location_string): - """Translates a location string into latitude and longitude coordinates. - Uses the OpenStreetMap API. Returns a dict with keys 'lat' and 'lng' - as shown below. When no result is found, returns None. + response = requests.get(url, params=params, timeout=5) + data = response.json() - >>> geocode_location('11 Wall Street, New York') - {"lat": -74.010865, "lng": 40.7071407} - """ - result = OsmQueryWithHeaders(location_string) - if result: - lat, lng = result.latlng - return {'lat': lat, 'lng': lng} + if not data: + raise ValueError(f"Could not geocode location: {location}") -def estimate_location(ip_address=None): - """Estimates a location based on the request's IP address, returning - latitude and longitude coodrdinates. When no IP address is provided, - uses the user's current IP address. + return { + "lat": float(data[0]["lat"]), + "lng": float(data[0]["lon"]) + } - >>> geocode_ip_address() - {'lat': 23.6585116, 'lng': -102.0077097} - """ - result = geocoder.ip(ip_address or 'me') - if result: - lat, lng = result.latlng - return {'lat': lat, 'lng': lng} -def get_weather_office(lat, lng): - """Looks up the NWS weather office for a pair of lat/lng coordinates. - Returns a dict containing keys 'office', 'x', and 'y'. - If no matching weather office is found, returns None. - - >>> coords = geocode_ip_address() - >>> get_weather_office(coords['lat'], coords['lng']) - {'office': 'BUF', 'x': 39, 'y': 59} - """ - url = "https://api.weather.gov/points/{},{}".format(lat, lng) - response = requests.get(url) - if response.ok: - result = response.json() +# ----------------------------------------------------------- +# Estimate user's coordinates using IP geolocation +# ----------------------------------------------------------- +def estimate_location(): + """Estimate user's location using IP-based geolocation.""" + try: + response = requests.get("https://ipinfo.io/json", timeout=5) + data = response.json() + + if "loc" not in data: + raise RuntimeError("IP lookup returned no coordinates.") + + lat_str, lng_str = data["loc"].split(",") + return { - "office": result['properties']['gridId'], - "x": result['properties']['gridX'], - 'y': result['properties']['gridY'] + "lat": float(lat_str), + "lng": float(lng_str) } + except Exception as e: + raise RuntimeError(f"Could not estimate location: {e}") + +# ----------------------------------------------------------- +# Get the nearest weather office from NOAA API +# ----------------------------------------------------------- +def get_weather_office(lat, lng): + url = f"https://api.weather.gov/points/{lat},{lng}" + response = requests.get(url, timeout=5) + data = response.json() + + props = data["properties"] + return { + "office": props["gridId"], + "x": props["gridX"], + "y": props["gridY"] + } + + +# ----------------------------------------------------------- +# Get NOAA forecast +# ----------------------------------------------------------- def get_forecast(office, x, y, metric=False): - """Fetches the weather forecast for the given NWS office, and (x, y) NWS grid tile. - Returns a list of time periods, where each time period is a dict containing keys - as shown below. If no forecast can be found, returns None. - When metric is True, returns temperatures in Celcius and wind speeds in km/hr. + url = f"https://api.weather.gov/gridpoints/{office}/{x},{y}/forecast" + response = requests.get(url, timeout=5) + data = response.json() + + periods = data["properties"]["periods"] + + # Convert NOAA data to your class format + forecast = [] + for p in periods: + forecast.append({ + "name": p["name"], + "description": p["detailedForecast"], + "temperature": p["temperature"] if not metric else round((p["temperature"] - 32) * 5/9), + "wind_speed": p["windSpeed"], + "wind_direction": p["windDirection"] + }) + + return forecast - """ - url = "https://api.weather.gov/gridpoints/{}/{},{}/forecast".format(office, x, y) - if metric: - url += "?units=si" - response = requests.get(url) - if response.ok: - result = response.json() - forecast = [] - for period in result['properties']['periods']: - forecast.append({ - 'name': period['name'], - 'temperature': period['temperature'], - 'wind_speed': period['windSpeed'], - 'wind_direction': period['windDirection'], - 'description': period['shortForecast'], - }) - return forecast