Layer 7 Session 43 Real World

Handling Errors and Edge Cases Gracefully

A real network call can fail in many ways: no connection, a timeout, a malformed response. We build proper loading and error feedback instead of a program that silently hangs or crashes.

! try raised caught, handled

Every way a network call can fail, handled on purpose.

Estimated time: 35โ€“40 minutes  ยท  Pre-quiz โ†’ Concept โ†’ Lab โ†’ Commit โ†’ Post-quiz

1. Learning Objective

By the end of this session you will be able to:

  • Set a timeout on a network request and handle it when it fires
  • Catch requests-specific exceptions distinctly from generic ones
  • Design a simple three-state loading/success/error model for a network operation
  • Provide clear, actionable feedback for each of those three states
  • Bring every error-handling technique from the whole curriculum together into one cohesive, real operation

2. Pre-Coding Quiz

Answer these before reading the concept explanation or writing any code. It is fine to get these wrong โ€” that is the point. You need 4/5 to proceed to the lab.

Question 1 of 5

Why should a network request always specify a timeout, e.g. requests.get(url, timeout=5)?

Without a timeout, a request to a slow or unresponsive server could hang forever, freezing the entire program. Specifying timeout=5 guarantees the request either succeeds or fails within a bounded time, raising a catchable exception if it does not.
Question 2 of 5

What does requests.exceptions.ConnectionError typically indicate, as opposed to requests.exceptions.Timeout?

These are distinct failure modes the requests library exposes as separate exception types โ€” being able to distinguish "we could not even connect" from "we connected but it took too long" lets you give the user more specific, useful feedback.
Question 3 of 5

Why is a three-state model (loading, success, error) more useful for a network operation than a program that just silently waits and then either works or crashes?

This mirrors the "loading and error states" concept from the original React course this curriculum is modeled on โ€” any operation that takes time and can fail benefits from explicitly communicating its current state, rather than leaving the user guessing during a silent pause.
Question 4 of 5

A network call might fail from a connection error, a timeout, OR return a non-200 status code (Session 42). Should all three be handled with a single, generic except Exception?

This is Session 11's "catch narrowly" principle applied at full scale to a real, multi-failure-mode operation: connection errors, timeouts, and bad status codes are all distinct, specific situations that deserve their own specific handling and messaging, not one catch-all.
Question 5 of 5

How does this session's error handling relate to everything else built across the curriculum, particularly Sessions 11, 24, 29, and 41?

This session is deliberately a synthesis point โ€” every defensive technique built up over the whole curriculum (specific exception catching, validating untrusted input, safe resource handling) comes together here, applied to the single most unpredictable operation the project performs.

3. The Concept โ€” Robust Network Error Handling

CONNECTIONERRORno internetTIMEOUTtoo slowBAD STATUSserver error

Each distinct network failure mode gets its own specific handling and message, per Session 11's catch-narrowly discipline.

Always set a timeout

Without a timeout, a request to an unresponsive server can hang the entire program indefinitely. A timeout guarantees the request either succeeds or fails within a bounded time.

import requests

try:
    response = requests.get(
        "https://www.apicountries.com/countries",
        timeout=5,  # give up after 5 seconds
    )
except requests.exceptions.Timeout:
    print("The request took too long and was cancelled.")

Catching distinct network failure modes specifically

The requests library provides specific exception types for different failure modes โ€” catching them individually (Session 11's discipline) gives clearer, more actionable feedback than one generic catch.

import requests

def fetch_countries_safely():
    try:
        response = requests.get(
            "https://www.apicountries.com/countries",
            timeout=5,
        )
    except requests.exceptions.ConnectionError:
        return None, "Could not connect โ€” check your internet connection."
    except requests.exceptions.Timeout:
        return None, "The request took too long and was cancelled."
    except requests.exceptions.RequestException as e:
        return None, f"An unexpected network error occurred: {e}"

    if response.status_code != 200:
        return None, f"The API returned an error (status {response.status_code})."

    return response.json(), None

A three-state model: loading, success, error

Rather than a silent pause followed by an unexplained result, explicitly represent and communicate what is currently happening โ€” directly analogous to the loading/error states covered in the original React course this curriculum is modeled on.

def load_countries_with_feedback():
    print("Loading countries...")  # the "loading" state, communicated explicitly

    data, error = fetch_countries_safely()

    if error:
        print(f"Error: {error}")   # the "error" state, with a specific, useful message
        return []

    print(f"Loaded {len(data)} countries successfully.")  # the "success" state
    return data

Bringing it all together

This combines Session 11's exception discipline, Session 30's validation, Session 41's resource safety, and Session 42's API adaptation into one cohesive, defensively-built operation โ€” the most robust piece of code in the entire project.


4. Lab

Lab objective: Build a fully robust country-loading function combining timeouts, specific exception handling, and explicit loading/success/error feedback.

What you will build

A file called robust_loading_lab.py.

Step-by-step instructions

1

Create the file with a timeout-protected, specifically-handled fetch function

# robust_loading_lab.py
import requests

def fetch_countries_safely(url, timeout=5):
    try:
        response = requests.get(url, timeout=timeout)
    except requests.exceptions.ConnectionError:
        return None, "Could not connect โ€” check your internet connection."
    except requests.exceptions.Timeout:
        return None, "The request took too long and was cancelled."
    except requests.exceptions.RequestException as e:
        return None, f"An unexpected network error occurred: {e}"

    if response.status_code != 200:
        return None, f"The API returned an error (status {response.status_code})."

    return response.json(), None
2

Test the happy path with the real API

data, error = fetch_countries_safely("https://www.apicountries.com/countries")
if error:
    print("Error:", error)
else:
    print(f"Loaded {len(data)} countries")
3

Test the error path with a deliberately bad URL

This should trigger ConnectionError handling, not a crash.

data, error = fetch_countries_safely("https://this-domain-does-not-exist-12345.example")
print("Error:", error)
print("Data:", data)
4

Test the error path with an intentionally short timeout

Use timeout=0.001 against the real API to force a Timeout exception.

data, error = fetch_countries_safely(
    "https://www.apicountries.com/countries",
    timeout=0.001,
)
print("Error:", error)
5

Combine everything into a full loading pipeline with explicit state messages

Adapt the data (Session 42), validate a sample (Session 30), and build a CountryRepository, all with explicit loading/success/error feedback.

from country_explorer import CountryRepository, validate_country_record

def adapt_api_record(raw):
    return {
        "name": raw.get("name", "Unknown"),
        "region": raw.get("region", "Unknown"),
        "population": raw.get("population", 0),
    }

def load_country_repository(url):
    print("Loading countries...")
    raw_data, error = fetch_countries_safely(url)
    if error:
        print(f"Error: {error}")
        return CountryRepository(raw_data=[])

    adapted = [adapt_api_record(r) for r in raw_data]
    if adapted:
        validate_country_record(adapted[0])
    print(f"Loaded {len(adapted)} countries successfully.")
    return CountryRepository(raw_data=adapted)

repo = load_country_repository("https://www.apicountries.com/countries")
print(repo.get_all()[0].summary())

5. Expected Files Changed

FileActionWhy
robust_loading_lab.py Created A fully robust, defensively-built network loading pipeline with three-state feedback.
docs/sessions/session-43/index.html Created This session document.
If you find yourself editing any other file, stop. This session touches exactly 2 files.

6. Commit Checkpoint

Once the lab is complete and you can explain every line, make this exact commit:

git add robust_loading_lab.py docs/sessions/session-43/index.html
git commit -m "session-43: add timeouts, specific error handling, and loading/success/error feedback"
Do not commit until you can answer out loud: "Why does fetch_countries_safely catch ConnectionError, Timeout, and RequestException separately instead of one generic except Exception?"

7. Code Review Checklist

Go through your code line by line and check each item:


8. Post-Coding Quiz

Same 5 questions. Take it again now that you have written and run the code. You need 4/5 to mark this session complete.

Question 1 of 5

Why should a network request always specify a timeout, e.g. requests.get(url, timeout=5)?

Without a timeout, a request to a slow or unresponsive server could hang forever, freezing the entire program. Specifying timeout=5 guarantees the request either succeeds or fails within a bounded time, raising a catchable exception if it does not.
Question 2 of 5

What does requests.exceptions.ConnectionError typically indicate, as opposed to requests.exceptions.Timeout?

These are distinct failure modes the requests library exposes as separate exception types โ€” being able to distinguish "we could not even connect" from "we connected but it took too long" lets you give the user more specific, useful feedback.
Question 3 of 5

Why is a three-state model (loading, success, error) more useful for a network operation than a program that just silently waits and then either works or crashes?

This mirrors the "loading and error states" concept from the original React course this curriculum is modeled on โ€” any operation that takes time and can fail benefits from explicitly communicating its current state, rather than leaving the user guessing during a silent pause.
Question 4 of 5

A network call might fail from a connection error, a timeout, OR return a non-200 status code (Session 42). Should all three be handled with a single, generic except Exception?

This is Session 11's "catch narrowly" principle applied at full scale to a real, multi-failure-mode operation: connection errors, timeouts, and bad status codes are all distinct, specific situations that deserve their own specific handling and messaging, not one catch-all.
Question 5 of 5

How does this session's error handling relate to everything else built across the curriculum, particularly Sessions 11, 24, 29, and 41?

This session is deliberately a synthesis point โ€” every defensive technique built up over the whole curriculum (specific exception catching, validating untrusted input, safe resource handling) comes together here, applied to the single most unpredictable operation the project performs.

9. Reflection Questions

Think through these after the post-quiz. No right answer โ€” they are for discussion.

  1. Why does this session catch requests.exceptions.RequestException as a broader fallback AFTER the two more specific exceptions, rather than instead of them? What does exception ordering have to do with this?
  2. Trace through everything that would go wrong if fetch_countries_safely had no timeout at all and the API server happened to hang without ever responding.
  3. How many distinct sessions' worth of error-handling technique can you identify being used together in load_country_repository? List them.
  4. If you were building a real, user-facing application (not a console script) around this loading pipeline, what would the loading/success/error states actually look like to the user?

10. What Breaks If This Knowledge Is Missing?

  • A frozen, unresponsive program: Without a timeout, one unresponsive server could freeze the entire program indefinitely โ€” completely unacceptable for anything meant to be used interactively or run unattended.
  • Confusing, generic failures: Catching everything with one generic except Exception (rather than the specific types this session teaches) means a user experiencing a connection problem sees the exact same unhelpful message as someone hitting a slow timeout or a server error โ€” making the problem much harder to diagnose or explain to someone else.
  • The capstone review (Session 44): This session's robust loading pipeline is the single piece of code most representative of everything the curriculum has built toward โ€” the final session reviews it, and everything else, end to end.

11. What We Learned

Python concept mastered: Robust network error handling โ€” timeouts, specific exception types for distinct failure modes, and explicit loading/success/error state communication.

Unlocks: The Country Explorer can now handle real-world network unreliability gracefully, giving clear feedback instead of hanging or crashing โ€” the last new skill before the capstone review.

Next session: Session 44 โ€” Capstone Review. The capstone. We walk through the entire project end to end, review every architectural decision, and take a comprehensive quiz across all seven layers.