Build a Weather API with Redis Caching

Cache live weather API responses in Redis and measure the speed gains.

Introduction

30 Second Summary

Every time you check the weather on your phone, your app is calling a server somewhere in the world. If millions of people ask for the same city's forecast at the same time, that server gets overwhelmed. The solution is caching: storing a recent answer locally so you can serve it instantly without asking again.

In this project, you will build a Python command-line tool that fetches live weather data and caches it in Redis. You will see the speed difference between cached and uncached requests in real time.

What You'll Build

A Python script that calls a free weather API, stores responses in a Redis cache, and returns results instantly on repeat lookups.

By the end of this project, you'll have:

  • A running Redis server inside a Docker container that you can inspect and manage from the command line.
  • A cache module in Python with get, set, and delete operations, complete with automatic expiration via TTL.
  • A weather lookup script that proves caching works by showing measurable response time differences between cache hits and misses.
  • Secret Mission: Build a cache statistics dashboard that tracks hit rate, miss rate, and average response times.

Are there any prerequisites?

None. This project includes full setup instructions for every tool you need, including Docker, Python, and Redis. No prior experience with caching or Redis is required.

Before We Start

Before we build the weather caching tool, we need a few tools installed on your machine.

In this step, you will install Docker (to run a Redis server), Python 3 (to write the caching script), and the libraries that connect them together.

In this step, get ready to:
  • Install Docker so you can run Redis locally.
  • Install Python 3 and create a virtual environment.
  • Install the redis-py and requests libraries.
Install VS Code and Docker

You need a code editor to write Python files and Docker to run your Redis server.

  • Download and install VS Code if you do not already have a code editor.
  • Download and install Docker Desktop for your operating system.
  • Open Docker Desktop after installation and wait for it to finish starting up.

What is Docker?

Docker lets you run software in isolated containers on your machine. Instead of installing Redis directly (which is tricky on some operating systems), you will run it inside a Docker container with a single command.

  • Verify Docker is working by opening your terminal and running this command:
docker --version

✔️ I see a version number

Docker is installed and ready. Move on to the next section.

ⓧ Command not found

Docker is not installed yet. Follow these steps:

  • Go to docker.com/getdocker and download Docker Desktop for your operating system.
  • Run the installer and follow the prompts.
  • Open Docker Desktop once installation finishes.
  • Wait for the Docker icon in your system tray to show "running" status.
  • Run docker --version again in a new terminal window to confirm it works.

Windows users

Make sure Docker Desktop is set to use Linux containers (this is the default). The Redis image is Linux-based.

Install Python 3 and create a virtual environment

The redis-py library requires Python 3.10 or newer. Let's check which version you have.

  • Check your Python version by running this command:

macOS

python3 --version

Windows

python --version

✔️ I see version 3.10 or higher

Your Python version is compatible. Move on to creating the project folder below.

ⓧ I see an older version

You have Python installed, but the version is too old for redis-py. You need 3.10 or newer.

  • Go to python.org/downloads and download the latest Python 3 installer for your operating system.
  • Run the installer. On Windows, check the box that says "Add Python to PATH" before clicking Install.
  • Close and reopen your terminal after installation.
  • Run the version check command again to confirm you see 3.10 or higher.

macOS ships an older Python

macOS often defaults to an older system Python. After installing a newer version, you may need to use python3.12 (with the full version number) instead of just python3 to pick up the new install.

ⓧ Command not found

Python is not installed on your machine yet.

  • Go to python.org/downloads and download the latest Python 3 installer for your operating system.
  • Run the installer. On Windows, check the box that says "Add Python to PATH" before clicking Install.
  • Close and reopen your terminal after installation.
  • Run the version check command again to confirm Python is available.

Now create your project folder and a virtual environment to keep your libraries isolated.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir weather-cache
cd weather-cache
  • Confirm the folder exists by running:
ls

The folder should be empty for now. That is expected.

What is a virtual environment?

A virtual environment is an isolated space where Python installs libraries just for this project. It prevents conflicts with other Python projects on your machine.

  • Create a virtual environment inside your weather-cache folder by running this command:

macOS

python3 -m venv venv

Windows

python -m venv venv
  • Activate the virtual environment by running this command:

macOS

source venv/bin/activate

Windows

.\venv\Scripts\Activate.ps1

You should see (venv) appear at the start of your terminal prompt. This confirms the virtual environment is active.

Not seeing (venv) in your prompt?

Make sure you ran the activate command from inside the weather-cache folder. If you are on Windows using PowerShell and see an execution policy error, run Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser first, then try the activate command again.

help me activate my Python virtual environment

Install the project libraries

Your project needs two Python libraries: redis-py (to talk to Redis) and requests (to call the weather API).

  • Install both libraries by running this command:
pip install redis requests

You should see output showing both packages downloading and installing successfully.

What do these libraries do?

The redis library (redis-py) gives your Python code a way to connect to Redis and run cache operations like get, set, and delete.

The requests library makes it easy to call web APIs over HTTP and get the response as JSON.

Seeing a permissions error or pip not found?

Make sure your virtual environment is active. You should see (venv) in your terminal prompt. If pip is not found, try pip3 instead.

help me install redis and requests in my virtual environment

Your environment is ready. Next up, you will start Redis and explore how to interact with it from the command line.

Start Redis and Explore the CLI

Your Python environment is ready. Now you need an actual Redis server for your cache module to talk to.

Redis is an in-memory key-value store that holds data in RAM for ultra-fast reads and writes. In this step, you will spin up a Redis server inside a Docker container and explore it hands-on with the Redis CLI.

In this step, get ready to:
  • Run a Redis container with Docker and verify it is running.
  • Connect to Redis using redis-cli from inside the container.
  • Practice basic Redis commands: SET, GET, DEL, TTL, and KEYS.
Run a Redis container with Docker

Instead of installing Redis directly on your machine, you will run it inside a Docker container. This keeps your system clean and gives you a single command that works the same on macOS, Linux, and Windows.

  • Start a Redis container by running this command in your terminal:
docker run -d --name redis-cache -p 6379:6379 redis

What does this command do?

  • docker run creates and starts a new container.
  • -d runs the container in the background (detached mode) so you keep your terminal free.
  • --name redis-cache gives the container a friendly name you can reference later.
  • -p 6379:6379 maps port 6379 inside the container to port 6379 on your machine. This is the default Redis port.
  • redis is the official Redis image from Docker Hub.
  • Verify the container is running by running:
docker ps

You should see a row with redis-cache under the NAMES column and Up in the STATUS column.

Don't see your container listed?

  • Make sure Docker Desktop is running. You can check by looking for the Docker whale icon in your system tray or menu bar.
  • If you see an error about port 6379 already in use, another process is occupying that port. Stop it or change the host port (e.g. -p 6380:6379).
  • If the name redis-cache is already taken, remove the old container first with docker rm redis-cache and try again.

Why isn't my Redis Docker container starting?

Connect to Redis using redis-cli

Redis comes with a built-in command-line tool called redis-cli. You can access it directly inside the running container without installing anything else on your machine.

  • Open an interactive Redis CLI session inside your container by running:
docker exec -it redis-cache redis-cli

What does this command do?

docker exec -it runs a command inside an already-running container with an interactive terminal. redis-cache is the container name you chose, and redis-cli is the Redis command-line tool bundled inside the image.

  • Test the connection by typing:
ping

You should see PONG in the response. This confirms your connection to Redis is working.

Not seeing PONG?

  • Make sure you see a prompt like 127.0.0.1:6379> before typing. If you are still in your regular terminal, the exec command did not connect.
  • If the container stopped between commands, restart it with docker start redis-cache and try the exec command again.

Why can't I connect to redis-cli inside my Docker container?

Practice basic Redis commands

Redis stores data as key-value pairs. You give it a name (the key) and a value, and it holds them in memory for lightning-fast retrieval. Let's explore the core commands you will use in your caching module.

  • Store a value by typing this SET command:
SET greeting "hello world"

You should see OK confirming the value was stored.

  • Retrieve that value with GET:
GET greeting

You should see "hello world" returned instantly from memory.

What is a key-value store?

Think of it like a dictionary or phone book. You look up a name (the key) and get the information (the value) immediately. Redis keeps everything in RAM, so lookups take microseconds instead of milliseconds.

  • Now store a key with an expiration time using the EX option:
SET temperature "22C" EX 60

This stores the value "22C" and tells Redis to automatically delete it after 60 seconds.

  • Check how much time remains on that key with TTL:
TTL temperature

You should see a number showing the remaining seconds before the key expires. If you wait and run it again, the number decreases.

What is TTL?

TTL stands for Time To Live. It is the number of seconds until Redis automatically removes a key. This is exactly how your weather cache will work. You will store API responses with a TTL so that stale data gets cleaned up without any manual intervention.

  • Delete a key manually with DEL:
DEL greeting

You should see (integer) 1 confirming one key was deleted. If you try GET greeting now, it returns (nil).

  • Finally, list all keys currently stored in Redis:
KEYS *

You should see temperature listed (if it has not expired yet). Once it expires, KEYS * returns an empty list.

  • Exit the Redis CLI by typing:
exit

This brings you back to your regular terminal. Your Redis container continues running in the background.

Keep this container running

Leave the redis-cache container running for the rest of this project. Your Python code will connect to it on localhost:6379 in the next steps. If you restart your machine, bring it back up with docker start redis-cache.

You now have a running Redis server and you know how to store, retrieve, expire, and delete keys. Next up, you will build a Python module that wraps these operations into reusable functions for your weather cache.

Build the Cache Module

Your Redis server is running and you've explored its commands directly in the CLI. Now it's time to connect Redis to Python so the rest of your project can use caching without writing raw Redis commands every time.

In this step, you'll build a reusable cache module. This module wraps Redis operations into clean Python functions that any part of your project can call.

In this step, get ready to:
  • Create a cache module with a Redis connection and get/set functions.
  • Add delete and exists utility functions.
  • Test the module by caching and retrieving a string.
Create the cache module with core functions

Every function in this module will talk to the same Redis instance. You'll start by creating the file and establishing that shared connection.

  • In the VS Code Explorer sidebar, right-click inside the weather-cache folder and select New File.
  • Name the file cache.py and press Enter.
  • Add the imports and Redis connection by pasting this code:
import redis
import json
import time

# Connect to Redis running in Docker on localhost:6379
r = redis.Redis(host='localhost', port=6379, decode_responses=True)

What does this code do?

The three imports bring in the libraries this module relies on. redis is the Python client for talking to your Redis server. json handles converting Python objects to strings for storage. time is imported for timing utilities used later in the project.

The r variable is your Redis client object. Setting decode_responses=True tells redis-py to return strings instead of raw bytes, which makes working with the data much easier.

  • Save cache.py.
  • You won't see a change yet. The next chunk adds the first function that uses this connection.

Next, you need a function that stores data in the cache with an expiration time. This is the "set" side of your cache.

  • In cache.py, add the following function below the connection line:
def cache_set(key, value, ttl=300):
    """Store a value in Redis with a TTL (default 5 minutes)."""
    data = json.dumps(value)
    r.setex(key, ttl, data)

What does this code do?

json.dumps(value) converts whatever Python object you pass (a string, dict, or list) into a JSON string that Redis can store.

r.setex(key, ttl, data) stores that string in Redis and sets a TTL (time to live) in seconds. After ttl seconds pass, Redis automatically deletes the key. The default of 300 seconds means cached data expires after 5 minutes.

  • Save cache.py.
  • You won't see a visible result yet. The next function completes the get/set pair.

Storing data is only useful if you can retrieve it. The "get" function checks Redis for a cached value and returns it if found.

  • In cache.py, add the following function below cache_set:
def cache_get(key):
    """Retrieve a value from Redis. Returns None if key does not exist."""
    data = r.get(key)
    if data is None:
        return None
    return json.loads(data)

What does this code do?

r.get(key) asks Redis for the value stored at that key. If the key does not exist (either it was never set or its TTL expired), Redis returns None.

When a value IS found, json.loads(data) converts the stored JSON string back into a Python object. This is the reverse of the json.dumps() call in cache_set.

  • Save cache.py.
  • You won't see a visible result yet. The next substep adds the remaining utility functions.

Seeing a red underline on the import?

Make sure your virtual environment is active in VS Code. Check the bottom-right corner of VS Code for the Python interpreter. It should point to the venv folder inside weather-cache.

If it shows a different interpreter, click on it and select the one inside your virtual environment. help me fix my Python interpreter in VS Code

Add delete and exists functions

A complete cache needs more than get and set. You also need the ability to manually remove entries and check whether a key is currently cached.

  • In cache.py, add these two functions below cache_get:
def cache_delete(key):
    """Delete a key from the cache."""
    r.delete(key)


def cache_exists(key):
    """Check if a key exists in the cache."""
    return r.exists(key)

What do these functions do?

cache_delete removes a key from Redis immediately, regardless of its remaining TTL. This is useful when you want to force-refresh stale data.

cache_exists returns an integer: 1 if the key exists in Redis, 0 if it does not. You can use this to check for cached data without actually retrieving it.

  • Save cache.py.
  • Your cache module is complete. The file should now have five sections: imports, connection, and four functions.

✔️ Awesome, I've got everything!

Great. Double check you've saved cache.py before moving on to testing.

ⓧ I'd like to double check the full code

import redis
import json
import time

# Connect to Redis running in Docker on localhost:6379
r = redis.Redis(host='localhost', port=6379, decode_responses=True)


def cache_set(key, value, ttl=300):
    """Store a value in Redis with a TTL (default 5 minutes)."""
    data = json.dumps(value)
    r.setex(key, ttl, data)


def cache_get(key):
    """Retrieve a value from Redis. Returns None if key does not exist."""
    data = r.get(key)
    if data is None:
        return None
    return json.loads(data)


def cache_delete(key):
    """Delete a key from the cache."""
    r.delete(key)


def cache_exists(key):
    """Check if a key exists in the cache."""
    return r.exists(key)
Test the module by caching a string

The best way to confirm your module works is to use it. You'll cache a simple string, retrieve it, check it exists, then delete it.

  • Make sure your terminal is in the weather-cache folder with your virtual environment active.
  • Run this test command:
python3 -c "
from cache import cache_set, cache_get, cache_exists, cache_delete

cache_set('greeting', 'hello redis')
print('GET:', cache_get('greeting'))
print('EXISTS:', cache_exists('greeting'))

cache_delete('greeting')
print('AFTER DELETE:', cache_get('greeting'))
"

What should I see?

Your terminal should print three lines:

  • GET: hello redis confirms the value was stored and retrieved.
  • EXISTS: 1 confirms the key exists (1 means yes, 0 means no).
  • AFTER DELETE: None confirms the key was removed.

Seeing a ConnectionError?

This means Python cannot reach your Redis server. Verify your Redis container is still running by checking:

docker ps

You should see redis-cache listed. If it's not there, restart it with:

docker start redis-cache

help me fix my Redis connection error in Python

Your cache module is built and tested. Next up, you'll write the function that fetches live weather data from an API. Then you'll connect it to this cache so repeated lookups return instantly.

Fetch Weather Data from the API

Your cache module is ready to store and retrieve data from Redis. Now you need something worth caching.

In this step, you'll write a Python script that fetches live weather data from the Open-Meteo API. This gives you a real HTTP request to measure and optimize with caching in the next step.

In this step, get ready to:
  • Write a function that calls the Open-Meteo weather API for a given city.
  • Parse the JSON response into a clean data structure.
  • Print weather data for multiple cities and measure response times.
Write the weather fetch function

Open-Meteo is a free weather API that requires no API key or signup. You send it latitude and longitude coordinates, and it returns current weather data in JSON format.

  • Create a new file called weather.py in your weather-cache folder.
  • Add the following code to define the API URL and the fetch function:
import time
import requests

# Open-Meteo API does not require an API key
OPEN_METEO_URL = "https://api.open-meteo.com/v1/forecast"


def get_weather(latitude, longitude, city_name):
    """Fetch weather for coordinates."""
    # Call the API
    print(f"Fetching weather for {city_name} from API...")
    start = time.time()

    params = {
        "latitude": latitude,
        "longitude": longitude,
        "current_weather": True
    }
    response = requests.get(OPEN_METEO_URL, params=params)
    response.raise_for_status()
    data = response.json()

    elapsed = time.time() - start
    print(f"API response took {elapsed:.3f} seconds")

    # Extract the weather info
    current = data["current_weather"]
    weather_data = {
        "city": city_name,
        "temperature": current["temperature"],
        "windspeed": current["windspeed"],
        "time": current["time"]
    }

    return weather_data

What does this code do?

OPEN_METEO_URL stores the API endpoint. Since Open-Meteo is free and keyless, you can call it directly without any authentication setup.

The get_weather function sends latitude and longitude as query parameters with current_weather=True to get real-time data. It then extracts temperature, wind speed, and time into a clean dictionary.

The time.time() calls measure how long the API request takes. This timing is key for proving that caching makes things faster.

  • Save weather.py.

You won't see output yet. The next chunk adds the main function that actually runs this.

Add the main function and run the script

The fetch function exists, but nothing calls it yet. You need a main function that defines a list of cities and loops through them to print weather data.

  • In weather.py, add the following code below the get_weather function:
def main():
    # Example cities with their coordinates
    cities = [
        {"name": "London", "lat": 51.51, "lon": -0.13},
        {"name": "New York", "lat": 40.71, "lon": -74.01},
        {"name": "Tokyo", "lat": 35.68, "lon": 139.69},
    ]

    print("=== Weather Lookup with Redis Caching ===\n")

    for city in cities:
        start = time.time()
        weather = get_weather(city["lat"], city["lon"], city["name"])
        total_time = time.time() - start

        print(f"  {weather['city']}: {weather['temperature']}°C, "
              f"Wind: {weather['windspeed']} km/h")
        print(f"  Total time: {total_time:.3f}s\n")


if __name__ == "__main__":
    main()

What does this code do?

The cities list stores coordinates for London, New York, and Tokyo. Each entry has a name, latitude, and longitude.

The loop calls get_weather for each city, measures the total time including function overhead, and prints the temperature and wind speed.

The if __name__ == "__main__" block ensures main() only runs when you execute the script directly, not when importing from another file.

  • Save weather.py.
  • Run the script by entering this command in your terminal:
python weather.py

You should see weather data for all three cities printed in your terminal, with each API response time displayed in seconds.

Not seeing weather data?

  • If you see ModuleNotFoundError: No module named 'requests', make sure your virtual environment is activated. You should see the environment name in your terminal prompt.
  • If you see a connection error, check that your internet connection is working. Open-Meteo requires network access.
  • If you see NameError, make sure the main() function is placed below get_weather in the file.

Help me debug my weather.py script

✔️ Awesome, I've got everything!

Great. Double check you've saved your file.

ⓧ I'd like to double check the full code

Here is the complete weather.py file for comparison:

import time
import requests

# Open-Meteo API does not require an API key
OPEN_METEO_URL = "https://api.open-meteo.com/v1/forecast"


def get_weather(latitude, longitude, city_name):
    """Fetch weather for coordinates."""
    # Call the API
    print(f"Fetching weather for {city_name} from API...")
    start = time.time()

    params = {
        "latitude": latitude,
        "longitude": longitude,
        "current_weather": True
    }
    response = requests.get(OPEN_METEO_URL, params=params)
    response.raise_for_status()
    data = response.json()

    elapsed = time.time() - start
    print(f"API response took {elapsed:.3f} seconds")

    # Extract the weather info
    current = data["current_weather"]
    weather_data = {
        "city": city_name,
        "temperature": current["temperature"],
        "windspeed": current["windspeed"],
        "time": current["time"]
    }

    return weather_data


def main():
    # Example cities with their coordinates
    cities = [
        {"name": "London", "lat": 51.51, "lon": -0.13},
        {"name": "New York", "lat": 40.71, "lon": -74.01},
        {"name": "Tokyo", "lat": 35.68, "lon": 139.69},
    ]

    print("=== Weather Lookup with Redis Caching ===\n")

    for city in cities:
        start = time.time()
        weather = get_weather(city["lat"], city["lon"], city["name"])
        total_time = time.time() - start

        print(f"  {weather['city']}: {weather['temperature']}°C, "
              f"Wind: {weather['windspeed']} km/h")
        print(f"  Total time: {total_time:.3f}s\n")


if __name__ == "__main__":
    main()

Notice how each request takes a noticeable fraction of a second. Next up, you'll wire in your cache module so that repeated lookups for the same city return instantly.

Connect the Cache to the API

You have a working cache module and a working weather API call. Right now they exist as separate pieces with no connection between them.

In this step, you will wire them together so every API response gets stored in Redis. Repeated lookups for the same city will skip the network entirely and return instantly from cache.

In this step, get ready to:
  • Add cache-first logic to the weather function.
  • Store API responses in Redis with a 5-minute TTL.
  • Run the script twice and see the speed difference between cached and uncached requests.
Add cache logic to the weather function

The pattern you are implementing is called cache-aside. Before calling the API, your code checks Redis for a stored result. If the key exists (a cache hit), it returns that data immediately. If the key is missing (a cache miss), it calls the API and stores the fresh result for next time.

  • In weather.py, find the import lines at the top of the file:
import time
import requests
  • Add a new import line below import requests, so the imports look like this:
import time
import requests
from cache import cache_set, cache_get

Now you need to rewrite the get_weather function to use these imported cache functions.

  • Replace the entire get_weather function (from def get_weather down to return weather_data) with this updated version:
def get_weather(latitude, longitude, city_name):
    """Fetch weather for coordinates, using Redis cache."""
    cache_key = f"weather:{city_name}"

    # Check cache first
    cached = cache_get(cache_key)
    if cached is not None:
        print(f"CACHE HIT for {city_name}")
        return cached

    # Cache miss - call the API
    print(f"CACHE MISS for {city_name} - fetching from API...")
    start = time.time()

    params = {
        "latitude": latitude,
        "longitude": longitude,
        "current_weather": True
    }
    response = requests.get(OPEN_METEO_URL, params=params)
    response.raise_for_status()
    data = response.json()

    elapsed = time.time() - start
    print(f"API response took {elapsed:.3f} seconds")

    # Extract the weather info
    current = data["current_weather"]
    weather_data = {
        "city": city_name,
        "temperature": current["temperature"],
        "windspeed": current["windspeed"],
        "time": current["time"]
    }

    # Store in cache with 5-minute TTL
    cache_set(cache_key, weather_data, ttl=300)

    return weather_data

What does this code do?

  • cache_key = f"weather:{city_name}" creates a unique Redis key for each city. London's weather gets stored under the key weather:London.
  • cache_get(cache_key) checks Redis for existing data. If found, the function returns immediately without ever touching the network.
  • cache_set(cache_key, weather_data, ttl=300) stores the API response in Redis for 300 seconds (5 minutes). After 5 minutes, Redis automatically deletes the key so stale weather data does not persist forever.

Why a 5-minute TTL?

Weather changes gradually, so 5 minutes of staleness is acceptable. A shorter TTL would hit the API more often. A longer TTL risks showing outdated temperatures.

In production, you would tune the TTL based on how often the source data changes and how much staleness your users tolerate.

  • Save weather.py.

You won't see a change yet. The next chunk adds a second pass through the cities so you can observe the cache in action.

Seeing a red underline on the import?

Make sure cache.py is in the same folder as weather.py. Python looks for modules in the same directory first.

Check that the filename is exactly cache.py (not Cache.py or cache_module.py).

help me fix my cache import in weather.py

Run the script twice and compare response times

To see caching in action, the script needs to request the same cities twice. The first pass will produce cache misses (the data is not stored yet). The second pass will produce cache hits (Redis already has the data).

  • In weather.py, scroll down to the main() function. Find the end of the first for city in cities: loop (after the last print statement inside it).
  • Add this second loop below the first one, still inside the main() function:
    # Run again to demonstrate cache hits
    print("--- Running again (should be cached) ---\n")

    for city in cities:
        start = time.time()
        weather = get_weather(city["lat"], city["lon"], city["name"])
        total_time = time.time() - start

        print(f"  {weather['city']}: {weather['temperature']}°C, "
              f"Wind: {weather['windspeed']} km/h")
        print(f"  Total time: {total_time:.3f}s\n")

What does this code do?

This second loop calls get_weather for the exact same cities. Because the first loop already stored each result in Redis, the second loop triggers the cache hit path every time. The response time drops from hundreds of milliseconds to nearly zero.

  • Save weather.py.
  • Run the script by entering this command in your terminal:
python weather.py

You should see output showing CACHE MISS for each city on the first pass, followed by CACHE HIT for each city on the second pass. The total time for each cached request should be dramatically faster (close to 0.001 seconds compared to 0.2-0.5 seconds for the API calls).

Seeing ConnectionError or ConnectionRefusedError?

Your Redis container might not be running. Check its status by running docker ps in your terminal. If you do not see redis-cache listed, restart it with docker start redis-cache.

Also confirm your virtual environment is active. You should see (venv) at the start of your terminal prompt.

help me debug my Redis connection error

✔️ Awesome, I've got everything!

Great. Double check you have saved weather.py and that your terminal showed cache misses on the first pass and cache hits on the second pass.

ⓧ I'd like to double check the full code

Here is the full weather.py file at this point:

import time
import requests
from cache import cache_set, cache_get

# Open-Meteo API does not require an API key
OPEN_METEO_URL = "https://api.open-meteo.com/v1/forecast"


def get_weather(latitude, longitude, city_name):
    """Fetch weather for coordinates, using Redis cache."""
    cache_key = f"weather:{city_name}"

    # Check cache first
    cached = cache_get(cache_key)
    if cached is not None:
        print(f"CACHE HIT for {city_name}")
        return cached

    # Cache miss - call the API
    print(f"CACHE MISS for {city_name} - fetching from API...")
    start = time.time()

    params = {
        "latitude": latitude,
        "longitude": longitude,
        "current_weather": True
    }
    response = requests.get(OPEN_METEO_URL, params=params)
    response.raise_for_status()
    data = response.json()

    elapsed = time.time() - start
    print(f"API response took {elapsed:.3f} seconds")

    # Extract the weather info
    current = data["current_weather"]
    weather_data = {
        "city": city_name,
        "temperature": current["temperature"],
        "windspeed": current["windspeed"],
        "time": current["time"]
    }

    # Store in cache with 5-minute TTL
    cache_set(cache_key, weather_data, ttl=300)

    return weather_data


def main():
    # Example cities with their coordinates
    cities = [
        {"name": "London", "lat": 51.51, "lon": -0.13},
        {"name": "New York", "lat": 40.71, "lon": -74.01},
        {"name": "Tokyo", "lat": 35.68, "lon": 139.69},
    ]

    print("=== Weather Lookup with Redis Caching ===\n")

    for city in cities:
        start = time.time()
        weather = get_weather(city["lat"], city["lon"], city["name"])
        total_time = time.time() - start

        print(f"  {weather['city']}: {weather['temperature']}°C, "
              f"Wind: {weather['windspeed']} km/h")
        print(f"  Total time: {total_time:.3f}s\n")

    # Run again to demonstrate cache hits
    print("--- Running again (should be cached) ---\n")

    for city in cities:
        start = time.time()
        weather = get_weather(city["lat"], city["lon"], city["name"])
        total_time = time.time() - start

        print(f"  {weather['city']}: {weather['temperature']}°C, "
              f"Wind: {weather['windspeed']} km/h")
        print(f"  Total time: {total_time:.3f}s\n")


if __name__ == "__main__":
    main()

You just proved that caching works. Your first request took a few hundred milliseconds to cross the network, and the second returned in under a millisecond from Redis. This is the exact pattern production backends use to handle thousands of requests per second without overloading their APIs.

Secret mission

Cache Statistics Dashboard

You have a working cache, but how do you know if it is actually helping? In production, engineers track cache hit rates to decide whether their caching strategy is effective. Build a statistics tracker that monitors hits, misses, and response times so you can prove your cache is pulling its weight.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally using Docker and free APIs, so there are no ongoing cloud costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • Redis Docker container (running on port 6379).
  • Python virtual environment (venv folder).
  • Project files (cache.py and weather.py).

Keep everything running

No action needed. Choose this if you are still actively building or plan to experiment further with Redis caching.

  • Your Redis container will keep running in the background until you restart your computer or stop Docker Desktop.
  • Your virtual environment stays on disk and can be reactivated any time.
  • Docker Desktop will use some memory while running.

Pause - I'll come back to this later

Stop your Redis container to free up memory. Your data and files stay on disk so you can pick up where you left off.

  • Stop the Redis container by running:
docker stop redis
  • Deactivate your virtual environment by running:
deactivate

To resume later, start the container again with docker start redis and reactivate your virtual environment.

Delete - I don't want to use this again

Remove all project resources and start fresh. This deletes your cached data and project files permanently.

  • Stop and remove the Redis container by running:
docker stop redis
docker rm redis
  • Deactivate your virtual environment (if active) by running:
deactivate
  • Delete the project folder and virtual environment. On macOS/Linux, run:
cd ~/Desktop
rm -rf weather-cache
  • On Windows, run:
cd ~\Desktop
rmdir /s /q weather-cache

That's a Wrap!

That's a Wrap!

Nice work! You built a caching layer from scratch and saw first-hand how Redis turns slow API calls into instant responses.

What you learned:

  • Set up and ran a Redis server using Docker and practiced core commands like SET, GET, DEL, and TTL.
  • Built a reusable cache module in Python with get, set, delete, and exists operations.
  • Integrated caching with a live weather API and measured the performance difference between cached and uncached requests.
  • Secret Mission: Built a cache statistics dashboard tracking hit rates and response times.

Ready to quiz yourself?