Weather API

Completion Date: 2025

Weather API

This site themes itself based on Atlanta's real-time weather and time of day. Here's how that works under the hood.

The API

The site uses the OpenWeatherMap Current Weather endpoint:

GET https://api.openweathermap.org/data/2.5/weather
    ?lat=33.749&lon=-84.388&appid={API_KEY}

Atlanta's coordinates are hardcoded. The response is a JSON object that includes the current temperature, a weather condition ID, and Unix timestamps for today's sunrise and sunset.

Time of Day

The response includes sys.sunrise and sys.sunset as Unix timestamps. These get converted to local hours and used to divide the day into six periods:

Period Condition
night After sunset+2h or before sunrise-1h
dawn Within 1 hour of sunrise
morning Sunrise+1h through noon
noon 12pm – 2pm
afternoon 2pm through sunset-1h
dusk Last hour before sunset+2h

Using actual sunrise/sunset data means the transitions shift with the seasons — dawn in December starts later than in June.

Weather Condition

OWM uses a numeric ID system for weather conditions. The site maps ranges of those IDs to six buckets:

ID Range Condition
200–299 storm
300–599 rain
600–699 snow
700–799 fog
800 clear
801–899 clouds

Caching

The API is only called once every 30 minutes. The result is stored in a simple in-memory dict with a time.monotonic() expiry:

_cache = {"data": None, "expires_at": 0}
CACHE_TTL = 1800  # seconds

def get_theme_data():
    now = time.monotonic()
    if _cache["data"] and now < _cache["expires_at"]:
        return _cache["data"]
    # ... fetch and store
    _cache.update({"data": result, "expires_at": now + CACHE_TTL})

This avoids hitting the API on every page load. The tradeoff is that the theme can lag up to 30 minutes behind actual conditions.

Fallback

If the API key is missing or the request fails, the site falls back to a time-only estimate using fixed hour boundaries (no sunrise/sunset data), and defaults to clear for the weather condition. The site still themes correctly, just without live weather data.

Injecting into Templates

Flask's @app.context_processor makes the result available in every template automatically — no per-route changes needed:

@app.context_processor
def inject_theme():
    data = get_theme_data()
    return {"theme_data": data}

The body class is then set in base.html:

<body class="time-{{ theme_data.time_period }} weather-{{ theme_data.weather_condition }}">

All CSS theme variables and canvas effects key off those two body classes.