#!/usr/bin/env python3
import os
import sys
from typing import Optional, Tuple, Dict, Any
import requests
from fastapi import FastAPI, HTTPException
# ----- Geocoding using Nominatim -----
def get_coordinates(city_name: str) -> Optional[Tuple[float, float]]:
"""
Get latitude and longitude for a city using the Nominatim API.
Returns (lat, lon) or None if city not found or on error.
"""
url = "https://nominatim.openstreetmap.org/search"
params = {
"q": city_name,
"format": "json",
"limit": 1
}
headers = {
"User-Agent": "WeatherScript/1.0 (contact@example.com)"
}
try:
response = requests.get(url, params=params, headers=headers, timeout=10)
response.raise_for_status()
data = response.json()
if not data:
return None
lat = float(data[0]["lat"])
lon = float(data[0]["lon"])
return lat, lon
except requests.RequestException as e:
print(f"Error fetching coordinates: {e}")
return None
# ----- Weather using OpenWeather -----
def get_weather(lat: float, lon: float, api_key: str, units: str = "metric") -> Optional[Dict[str, Any]]:
"""
Query the OpenWeather current weather API for given coordinates.
units: 'metric' (Celsius) or 'imperial' (Fahrenheit).
Returns JSON dict on success, or None on error.
"""
url = "https://api.openweathermap.org/data/2.5/weather"
params = {
"lat": lat,
"lon": lon,
"appid": api_key,
"units": units
}
try:
response = requests.get(url, params=params, timeout=10)
if response.status_code != 200:
# Print response text for debugging if needed
print(f"Debug: Response status: {response.status_code}, Response text: {response.text}")
response.raise_for_status()
return response.json()
except requests.RequestException as e:
print(f"Error fetching weather data: {e}")
return None
# ----- CLI usage (prints Celsius) -----
def main_cli():
if len(sys.argv) != 2:
print("Usage: python3 weather.py \"City Name\"")
sys.exit(1)
city_name = sys.argv[1]
api_key = os.getenv("OPENWEATHER_API_KEY")
if not api_key:
print("Error: OPENWEATHER_API_KEY environment variable not set")
sys.exit(1)
coords = get_coordinates(city_name)
if not coords:
print(f"Error: City '{city_name}' not found")
sys.exit(1)
lat, lon = coords
weather_data = get_weather(lat, lon, api_key, units="metric")
if not weather_data:
print("Error: Failed to fetch weather data")
sys.exit(1)
temp_c = weather_data.get("main", {}).get("temp")
conditions = weather_data.get("weather", [{}])[0].get("description", "unknown")
print(f"City: {city_name}")
print(f"Coordinates: {lat:.4f}, {lon:.4f}")
if temp_c is not None:
print(f"Temperature: {temp_c:.2f}°C")
print(f"Conditions: {conditions}")
# ----- FastAPI application (returns Fahrenheit) -----
app = FastAPI(title="Weather API", description="Get weather information for cities")
@app.get("/weather/{city}")
async def get_weather_for_city(city: str):
"""
Return JSON:
{
"city": <city>,
"coordinates": { "latitude": <lat>, "longitude": <lon> },
"temperature": "<value>°F",
"conditions": "<description>"
}
"""
api_key = os.getenv("OPENWEATHER_API_KEY")
if not api_key:
raise HTTPException(status_code=500, detail="OpenWeather API key not configured")
coords = get_coordinates(city)
if not coords:
raise HTTPException(status_code=404, detail=f"City '{city}' not found")
lat, lon = coords
weather_data = get_weather(lat, lon, api_key, units="imperial")
if not weather_data:
raise HTTPException(status_code=500, detail="Failed to fetch weather data")
temp_f = weather_data.get("main", {}).get("temp")
conditions = weather_data.get("weather", [{}])[0].get("description", "unknown")
return {
"city": city,
"coordinates": {
"latitude": round(lat, 4),
"longitude": round(lon, 4)
},
"temperature": f"{temp_f:.2f}°F" if temp_f is not None else None,
"conditions": conditions
}
# Allow running the CLI when invoked directly
if __name__ == "__main__":
main_cli()