7. Basic error handling
Not every API call will succeed. Servers go down. Wi-Fi drops. URLs have typos. Requests time out when services are slow.
If you don't handle these situations, your program crashes the moment something goes wrong. Professional developers expect failures and build in error handling so their applications respond gracefully instead of blowing up.
What happens with no protection at all
Before we add a safety net, let's see what happens without one. We'll deliberately request a URL that returns a 404, then call response.raise_for_status(). Watch how the program stops at the error and never reaches the line that comes afterwards.
Save this as handle_errors.py:
import requests
response = requests.get("https://httpbin.org/status/404", timeout=5)
response.raise_for_status() # This will crash the program
print("This line never executes")
Run it:
$ python handle_errors.py
Traceback (most recent call last):
File "handle_errors.py", line 4, in <module>
response.raise_for_status()
requests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://httpbin.org/status/404
The program crashed with an HTTPError. The print() never ran because Python stopped as soon as raise_for_status() raised the exception.
Adding the safety net
Now we'll catch that exception and respond gracefully. Replace the contents of handle_errors.py with this:
import requests
try:
response = requests.get("https://httpbin.org/status/404", timeout=5)
response.raise_for_status()
print("Success!")
except requests.exceptions.HTTPError as e:
print(f"HTTP error occurred: {e}")
except requests.exceptions.Timeout:
print("Request timed out")
except requests.exceptions.ConnectionError:
print("Could not connect to the server")
except requests.exceptions.RequestException as e:
print(f"An error occurred: {e}")
Run it:
$ python handle_errors.py
HTTP error occurred: 404 Client Error: Not Found for url: https://httpbin.org/status/404
Now, instead of crashing, the program catches the error, prints a clear message, and can continue running. That's the difference between a fragile script and code that's on its way to being production-ready.
The common exceptions and the order they go in
HTTPError, the server returned an error status code (4xx or 5xx).Timeout, the server didn't respond within the timeout period.ConnectionError, a network problem prevented the connection.RequestException, the base exception that catches anyrequests-related error.
The first three are siblings: HTTPError, Timeout, and ConnectionError all inherit directly from RequestException, so you can list them in any order. The rule that matters is keeping RequestException last, because it catches everything its subclasses do. Put it before any of them and the more specific handlers above never fire. By catching the named exceptions first, you can give tailored messages for different failure scenarios; the base class then mops up anything you didn't anticipate.
Practising with different status codes
httpbin lets you simulate any HTTP status code using the /status/CODE endpoint. That's perfect for testing how your error handling behaves. We'll write a small helper function that tries several status codes and prints how each is handled.
Save this as test_status_codes.py:
import requests
def test_status_code(code):
"""Test how your code handles different status codes."""
try:
url = f"https://httpbin.org/status/{code}"
response = requests.get(url, timeout=5)
response.raise_for_status()
print(f"Status {code}: Success")
except requests.exceptions.HTTPError as e:
print(f"Status {code}: {e}")
# Test different scenarios
test_status_code(200) # Success
test_status_code(404) # Not Found
test_status_code(500) # Server Error
test_status_code(429) # Rate Limited
Run it:
$ python test_status_codes.py
Status 200: Success
Status 404: 404 Client Error: Not Found for url: https://httpbin.org/status/404
Status 500: 500 Server Error: Internal Server Error for url: https://httpbin.org/status/500
Status 429: 429 Client Error: Too Many Requests for url: https://httpbin.org/status/429
This kind of hands-on testing builds confidence. You see how different status codes behave and what exceptions they raise. Later, when you encounter these codes in real APIs, you'll recognise them on sight and have a pattern ready for each one.
Next, we'll look at query parameters: the standard way to add extra information (a city, a user ID, a search term) to your request. Plus the safer way to build them than concatenating strings.