Python · API · Продвинутый уровень
Обработка ошибок
Надёжно обрабатывайте HTTP-ошибки, сетевые сбои и таймауты в API-скриптах.
Краткое введение в тему и пояснения перед упражнениями (упражнения ниже):
Практические паттерны обработки ошибок
#Справочник обработки ошибок
#Упражнения:
Использование raise_for_status()
#Сделайте GET к /api/products/99999/ (несуществующий продукт). Без raise_for_status() выведите код статуса. Затем добавьте raise_for_status() и обработайте исключение HTTPError, выведя сообщение об ошибке.
import requests
BASE_URL = 'https://apilearn.tukas.dev'
# Часть 1: без raise_for_status — проверьте код статуса
response = requests.get(f'{BASE_URL}/api/products/99999/')
print(response.status_code)
# Часть 2: с raise_for_status и обработкой HTTPError
Решение
import requests
BASE_URL = 'https://apilearn.tukas.dev'
# Часть 1: без raise_for_status
response = requests.get(f'{BASE_URL}/api/products/99999/')
print(response.status_code) # 404
# Часть 2: с raise_for_status
try:
response = requests.get(f'{BASE_URL}/api/products/99999/')
response.raise_for_status()
except requests.exceptions.HTTPError as e:
print(f'HTTP ошибка: {e.response.status_code}')
Обработка ошибки авторизации
#Попробуйте получить /api/users/profile/ БЕЗ заголовка авторизации. Обработайте HTTPError и выведите специальное сообщение, если статус 401.
import requests
BASE_URL = 'https://apilearn.tukas.dev'
# GET профиля без авторизации — обработайте 401 отдельно
try:
response = requests.get(f'{BASE_URL}/api/users/profile/')
response.raise_for_status()
except requests.exceptions.HTTPError as e:
# Выведите разные сообщения для 401 и других кодов
pass
Решение
import requests
BASE_URL = 'https://apilearn.tukas.dev'
try:
response = requests.get(f'{BASE_URL}/api/users/profile/')
response.raise_for_status()
except requests.exceptions.HTTPError as e:
if e.response.status_code == 401:
print('Ошибка авторизации — предоставьте действительный токен')
else:
print(f'HTTP ошибка: {e.response.status_code}')
Добавить timeout к запросу
#Сделайте GET к /api/products/ с timeout=5. Обработайте Timeout и ConnectionError отдельно. Выведите список продуктов при успехе или сообщение об ошибке.
import requests BASE_URL = 'https://apilearn.tukas.dev' # GET с timeout и обработкой сетевых ошибок
Решение
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'Получено {len(products)} продуктов')
except requests.exceptions.Timeout:
print('Ошибка: запрос превысил время ожидания')
except requests.exceptions.ConnectionError:
print('Ошибка: не удалось подключиться к API')
except requests.exceptions.HTTPError as e:
print(f'HTTP ошибка: {e.response.status_code}')
Функция safe_get()
#Напишите функцию safe_get(url, **kwargs), которая делает GET с timeout=5, вызывает raise_for_status() и возвращает JSON при успехе или None при любой ошибке (выводя сообщение). Протестируйте с валидным и несуществующим URL.
import requests
BASE_URL = 'https://apilearn.tukas.dev'
def safe_get(url, **kwargs):
# Реализуйте с timeout, raise_for_status и обработкой всех ошибок
pass
# Тест 1: валидный URL
result = safe_get(f'{BASE_URL}/api/products/')
if result:
print(f'Продуктов: {len(result["results"])}')
# Тест 2: несуществующий эндпоинт
result = safe_get(f'{BASE_URL}/api/products/99999/')
print(result)
Решение
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.Timeout:
print(f'Ошибка: timeout при запросе к {url}')
except requests.exceptions.ConnectionError:
print(f'Ошибка: нет соединения с {url}')
except requests.exceptions.HTTPError as e:
print(f'HTTP {e.response.status_code} для {url}')
return None
result = safe_get(f'{BASE_URL}/api/products/')
if result:
print(f'Продуктов: {len(result["results"])}')
result = safe_get(f'{BASE_URL}/api/products/99999/')
print(result)
Retry с backoff
#Напишите функцию retry_get(url, retries=3), которая повторяет запрос при кодах 429 или 503, ожидая 2**attempt секунд между попытками. При других ошибках — сразу бросать исключение. Выведите сообщение перед каждым ожиданием.
import requests
import time
BASE_URL = 'https://apilearn.tukas.dev'
def retry_get(url, retries=3, **kwargs):
# Повторяйте при 429/503, exponential backoff, бросайте при других ошибках
pass
# Тест с рабочим эндпоинтом (не активирует retry)
result = retry_get(f'{BASE_URL}/api/products/')
print(result['count'])
Решение
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 + 1} неудачна ({e.response.status_code}), ждём {wait}s...')
time.sleep(wait)
else:
raise
return None
result = retry_get(f'{BASE_URL}/api/products/')
print(result['count'])