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.
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
...
```
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
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}')
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')
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'])
No split tab
Cookie preferences
We use necessary cookies to run the site. With your permission, we can also save your site preferences and use analytics and advertising cookies to understand usage and support the project.
Open tools in tabs.Exercises, IDE tools, and trainers stay available as site tabs.
Switch without losing context.Move between explanations, code, and utilities while keeping your place.
Use the sidebar as your map.The left panels hold navigation, settings, files, libraries, and tool controls.
PythonJavaScriptSQLite
One IDE, three practical modes
Python in the browser.Run small scripts, try libraries, and practice API requests without installing anything.
JavaScript for quick experiments.Test browser-friendly code and compare ideas next to your learning materials.
SQLite for data practice.Open the database explorer to inspect tables, write queries, and learn SQL workflows locally.
TopicIDE
Work side by side with split tabs
Keep instructions visible.Open an exercise or reference page beside the IDE instead of jumping back and forth.
Compare tools while you learn.Place regex checks, explanations, and code experiments next to each other when the task needs it.
Close the split when you are done.The workspace returns to a single focused tab, and your open site tabs remain available.
Code Typing Trainer
Or plain text
This trainer is designed for a physical keyboard.Open this section on a laptop or desktop with a wide screen. Touch typing practice will not work correctly on a phone.
Speed: 0 chars/min
0 words/min
Best speed (60s): 0 chars/min
0 words/min
Errors: 0
Total time: 0.0 s
To practice touch typing, avoid looking at your physical keyboard.