Python · API · Advanced

Error Handling

5 tasks

Handle HTTP errors, network failures, and timeouts robustly in API scripts.

HTTP errors, network errors, and timeouts

#
Real-world API scripts fail. The server might return a 404, the network might drop, a request might time out. Handling these cases explicitly makes your scripts robust. There are two categories of failure: 1. HTTP errors — the server responded, but with an error status (4xx or 5xx). The request library does not raise by default — you get a Response with a bad status code and calling .json() may return an error payload instead of the data you wanted. 2. Network errors — the request never reached the server, or the server never replied. These raise exceptions: ConnectionError, Timeout, or the base RequestException. raise_for_status() bridges the gap for HTTP errors — it raises HTTPError if the status is 4xx or 5xx: ```python import requests response = requests.get('https://apilearn.tukas.dev/api/products/99999/') response.raise_for_status() # raises HTTPError on 404 data = response.json() # only runs if status was 2xx ``` For network errors, use try/except: ```python try: response = requests.get(url, timeout=5) response.raise_for_status() data = response.json() except requests.exceptions.Timeout: print('Request timed out') except requests.exceptions.ConnectionError: print('Network error — could not reach the server') except requests.exceptions.HTTPError as e: print(f'HTTP error: {e.response.status_code}') ``` The timeout= parameter sets a deadline in seconds. Without it, a hung server can stall your script forever: ```python # Raises Timeout if no response in 5 seconds response = requests.get(url, timeout=5) # Separate connect and read timeouts response = requests.get(url, timeout=(3, 10)) # (connect, read) ``` requests exception hierarchy (most specific to most general): ``` RequestException Base class for all requests exceptions ConnectionError Could not reach the server ProxyError Proxy-related connection error Timeout Request exceeded timeout ConnectTimeout Connection timeout ReadTimeout Read timeout HTTPError 4xx or 5xx status (raise_for_status) TooManyRedirects Redirect loop ``` Catching RequestException handles all of them at once — useful for simple scripts where the distinction does not matter.

Practical error handling patterns

#
Safe wrapper that returns data or None: ```python import requests BASE_URL = 'https://apilearn.tukas.dev' def safe_get(url, **kwargs): try: response = requests.get(url, timeout=5, **kwargs) response.raise_for_status() return response.json() except requests.exceptions.RequestException as e: print(f'Request failed: {e}') return None data = safe_get(f'{BASE_URL}/api/products/') if data: print(data['count']) # Try a URL that doesn't exist data = safe_get(f'{BASE_URL}/api/products/999999/') print(data) # None ``` Catch a 401 specifically and print a helpful message: ```python try: response = requests.get( f'{BASE_URL}/api/users/profile/', headers={'Authorization': 'Token invalid-token'}, ) response.raise_for_status() print(response.json()) except requests.exceptions.HTTPError as e: if e.response.status_code == 401: print('Authentication failed — check your token') else: print(f'HTTP error {e.response.status_code}') ``` Retry with exponential backoff: ```python import time def retry_get(url, retries=3, delay=1): for attempt in range(1, retries + 1): try: response = requests.get(url, timeout=5) response.raise_for_status() return response.json() except requests.exceptions.RequestException as e: print(f'Attempt {attempt} failed: {e}') if attempt < retries: time.sleep(delay * attempt) return None data = retry_get(f'{BASE_URL}/api/products/') ``` The delay grows with each attempt (1s, 2s, 3s) — this reduces the load on an already struggling server instead of hammering it with rapid retries.

Error handling reference

#
raise_for_status(): ```python response.raise_for_status() # raises HTTPError on 4xx or 5xx e.response.status_code # access status from the exception e.response.text # response body from the exception ``` timeout parameter: ```python requests.get(url, timeout=5) # both connect and read requests.get(url, timeout=(3, 10)) # (connect_timeout, read_timeout) ``` Exception hierarchy (import from requests.exceptions): ``` RequestException All errors ConnectionError Can't reach server Timeout Request took too long HTTPError 4xx/5xx (from raise_for_status) ``` Canonical try/except pattern: ```python try: r = requests.get(url, timeout=5) r.raise_for_status() data = r.json() except requests.exceptions.Timeout: ... except requests.exceptions.ConnectionError: ... except requests.exceptions.HTTPError as e: status = e.response.status_code ... ```
01

Catch a 404 with raise_for_status

#

Try to GET /api/products/99999/ (a non-existent product). Use raise_for_status() inside a try/except to catch the HTTPError and print the status code.

import requests

BASE_URL = 'https://apilearn.tukas.dev'

# GET a non-existent product and handle the HTTPError
Solution
import requests

BASE_URL = 'https://apilearn.tukas.dev'

try:
    response = requests.get(f'{BASE_URL}/api/products/99999/')
    response.raise_for_status()
    print(response.json())
except requests.exceptions.HTTPError as e:
    print(f'HTTP error: {e.response.status_code}')
02

Write safe_get()

#

Write a safe_get(url) function that returns the parsed JSON on success or None on any error (HTTP or network). Test it with a valid URL and with /api/products/99999/.

import requests

BASE_URL = 'https://apilearn.tukas.dev'

def safe_get(url):
    # Return JSON on success, None on any error
    pass

print(safe_get(f'{BASE_URL}/api/products/') is not None)  # True
print(safe_get(f'{BASE_URL}/api/products/99999/'))          # None
Solution
import requests

BASE_URL = 'https://apilearn.tukas.dev'

def safe_get(url):
    try:
        response = requests.get(url, timeout=5)
        response.raise_for_status()
        return response.json()
    except requests.exceptions.RequestException:
        return None

print(safe_get(f'{BASE_URL}/api/products/') is not None)
print(safe_get(f'{BASE_URL}/api/products/99999/'))
03

Handle timeout and network errors

#

Make a GET to /api/products/ with timeout=5. Handle Timeout and ConnectionError separately. Print the product list on success or an error message for each exception type.

import requests

BASE_URL = 'https://apilearn.tukas.dev'

# GET with timeout and network error handling
Solution
import requests

BASE_URL = 'https://apilearn.tukas.dev'

try:
    response = requests.get(f'{BASE_URL}/api/products/', timeout=5)
    response.raise_for_status()
    products = response.json()['results']
    print(f'Got {len(products)} products')
except requests.exceptions.Timeout:
    print('Error: request timed out')
except requests.exceptions.ConnectionError:
    print('Error: could not connect to API')
except requests.exceptions.HTTPError as e:
    print(f'HTTP error: {e.response.status_code}')
04

Write retry_get()

#

Write retry_get(url, retries=3, delay=1) that retries a failed request up to retries times, waiting delay * attempt seconds between tries. Return the JSON on success or None if all retries fail. Test it with a valid URL.

import requests
import time

BASE_URL = 'https://apilearn.tukas.dev'

def retry_get(url, retries=3, delay=1):
    # Try up to retries times, sleeping delay*attempt seconds between tries
    pass

data = retry_get(f'{BASE_URL}/api/products/')
print(data['count'] if data else 'Failed')
Solution
import requests
import time

BASE_URL = 'https://apilearn.tukas.dev'

def retry_get(url, retries=3, delay=1):
    for attempt in range(1, retries + 1):
        try:
            response = requests.get(url, timeout=5)
            response.raise_for_status()
            return response.json()
        except requests.exceptions.RequestException as e:
            print(f'Attempt {attempt} failed: {e}')
            if attempt < retries:
                time.sleep(delay * attempt)
    return None

data = retry_get(f'{BASE_URL}/api/products/')
print(data['count'] if data else 'Failed')
05

Retry with backoff

#

Write a retry_get(url, retries=3) function that retries on 429 or 503 status codes, waiting 2**attempt seconds between tries. For other errors raise immediately. Print a message before each wait.

import requests
import time

BASE_URL = 'https://apilearn.tukas.dev'

def retry_get(url, retries=3, **kwargs):
    # Retry on 429/503, exponential backoff, raise on other errors
    pass

# Test with a working endpoint (does not trigger retry)
result = retry_get(f'{BASE_URL}/api/products/')
print(result['count'])
Solution
import requests
import time

BASE_URL = 'https://apilearn.tukas.dev'

def retry_get(url, retries=3, **kwargs):
    for attempt in range(retries):
        try:
            response = requests.get(url, timeout=5, **kwargs)
            response.raise_for_status()
            return response.json()
        except requests.exceptions.HTTPError as e:
            if e.response.status_code in (429, 503) and attempt < retries - 1:
                wait = 2 ** attempt
                print(f'Attempt {attempt + 1} failed ({e.response.status_code}), waiting {wait}s...')
                time.sleep(wait)
            else:
                raise
    return None

result = retry_get(f'{BASE_URL}/api/products/')
print(result['count'])