from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from datetime import datetime, timedelta import asyncio import urllib.request import urllib.parse import urllib.error import json from app.database import get_db from app.models.settings import Settings from app.config import settings as app_settings from app.routers.auth import get_current_session router = APIRouter() _cache: dict = {} def _fetch_json(url: str) -> dict: with urllib.request.urlopen(url, timeout=10) as resp: return json.loads(resp.read().decode()) @router.get("/") async def get_weather( db: AsyncSession = Depends(get_db), current_user: Settings = Depends(get_current_session) ): # Get city from settings result = await db.execute(select(Settings)) settings_row = result.scalar_one_or_none() city = settings_row.weather_city if settings_row else None if not city: raise HTTPException(status_code=400, detail="No weather city configured") # Check cache (also invalidate if city changed) now = datetime.now() if _cache.get("expires_at") and now < _cache["expires_at"] and _cache.get("city") == city: return _cache["data"] api_key = app_settings.OPENWEATHERMAP_API_KEY if not api_key: raise HTTPException(status_code=500, detail="Weather API key not configured") try: loop = asyncio.get_event_loop() # Current weather + forecast in parallel via thread pool current_url = f"https://api.openweathermap.org/data/2.5/weather?q={urllib.parse.quote(city)}&units=metric&appid={api_key}" forecast_url = f"https://api.openweathermap.org/data/2.5/forecast?q={urllib.parse.quote(city)}&units=metric&cnt=8&appid={api_key}" current_data, forecast_data = await asyncio.gather( loop.run_in_executor(None, _fetch_json, current_url), loop.run_in_executor(None, _fetch_json, forecast_url), ) rain_chance = 0 for item in forecast_data.get("list", []): pop = item.get("pop", 0) if pop > rain_chance: rain_chance = pop weather_result = { "temp": round(current_data["main"]["temp"]), "temp_min": round(current_data["main"]["temp_min"]), "temp_max": round(current_data["main"]["temp_max"]), "description": current_data["weather"][0]["description"], "rain_chance": round(rain_chance * 100), "sunrise": current_data["sys"]["sunrise"], "sunset": current_data["sys"]["sunset"], "city": current_data["name"], } # Cache for 1 hour _cache["data"] = weather_result _cache["expires_at"] = now + timedelta(hours=1) _cache["city"] = city return weather_result except urllib.error.URLError: raise HTTPException(status_code=502, detail="Weather service unavailable") except (KeyError, json.JSONDecodeError): raise HTTPException(status_code=502, detail="Invalid weather data")