Introducción
Este tutorial te guiará a través del manejo de diferentes códigos de estado HTTP en las solicitudes de Python. Los códigos de estado HTTP son esenciales para comprender si una solicitud web tuvo éxito o falló, y cómo responder adecuadamente a diferentes escenarios. Al final de este laboratorio, aprenderás a implementar un manejo de errores robusto en tus aplicaciones Python que interactúan con servicios web.
Comenzarás comprendiendo los códigos de estado HTTP, luego desarrollarás progresivamente tus habilidades implementando técnicas básicas y avanzadas de manejo de errores. Este conocimiento es crucial para desarrollar aplicaciones web Python confiables que manejen con elegancia diversas respuestas del servidor.
Comprensión de los Códigos de Estado HTTP y Configuración
¿Qué son los Códigos de Estado HTTP?
Los códigos de estado HTTP son números de tres dígitos devueltos por un servidor en respuesta a la solicitud de un cliente. Indican si una solicitud HTTP específica se ha completado con éxito. Estos códigos se agrupan en cinco categorías:
- 1xx (Informativo): La solicitud fue recibida y el proceso continúa
- 2xx (Éxito): La solicitud fue recibida, entendida y aceptada con éxito
- 3xx (Redirección): Se debe tomar una acción adicional para completar la solicitud
- 4xx (Error del Cliente): La solicitud contiene una sintaxis incorrecta o no se puede cumplir
- 5xx (Error del Servidor): El servidor no pudo cumplir una solicitud aparentemente válida
Los códigos de estado comunes que encontrarás incluyen:
| Código de Estado | Nombre | Descripción |
|---|---|---|
| 200 | OK | La solicitud tuvo éxito |
| 201 | Creado | La solicitud tuvo éxito y el recurso fue creado |
| 400 | Bad Request | El servidor no puede procesar la solicitud |
| 401 | No Autorizado | Se requiere autenticación |
| 404 | No Encontrado | El recurso solicitado no se pudo encontrar |
| 500 | Internal Server Error | El servidor encontró una condición inesperada |
Configuración de tu Entorno Python
Antes de comenzar a manejar los códigos de estado HTTP, instalemos la biblioteca requests de Python y creemos un script de prueba simple.
- Abre una terminal en el entorno LabEx y ejecuta:
pip install requests
- Crea un nuevo directorio para los archivos de nuestro proyecto:
mkdir -p ~/project/http_status_lab
cd ~/project/http_status_lab
- Usando el WebIDE, crea un nuevo archivo llamado
test_request.pyen el directoriohttp_status_labcon este código básico:
import requests
def make_request(url):
"""Make a basic GET request to the specified URL."""
response = requests.get(url)
print(f"Status Code: {response.status_code}")
print(f"Response: {response.text[:100]}...") ## Print first 100 characters
return response
## Test with a working URL
response = make_request("https://httpbin.org/status/200")
print(f"Request was successful: {response.ok}")
- Ejecuta el script de prueba para verlo en acción:
python test_request.py
Deberías ver una salida similar a esta:
Status Code: 200
Response: ...
Request was successful: True
Esto confirma que has configurado con éxito el entorno Python y puedes realizar solicitudes HTTP básicas. En los siguientes pasos, aprenderás a manejar diferentes códigos de estado HTTP e implementar técnicas de manejo de errores más avanzadas.
Manejo de Códigos de Estado HTTP Básicos
Ahora que entiendes qué son los códigos de estado HTTP y has configurado tu entorno, implementemos el manejo básico de errores para los códigos de estado comunes.
Creación de una Herramienta de Prueba de Códigos de Estado
Primero, creemos una herramienta que nos permita probar diferentes códigos de estado HTTP. El sitio web httpbin.org proporciona endpoints que devuelven códigos de estado específicos, lo cual es perfecto para nuestras pruebas.
Crea un nuevo archivo llamado status_code_tester.py en tu directorio http_status_lab con el siguiente código:
import requests
import sys
def test_status_code(status_code):
"""Test a specific HTTP status code using httpbin.org."""
url = f"https://httpbin.org/status/{status_code}"
try:
response = requests.get(url)
print(f"Status Code: {response.status_code}")
print(f"Response OK: {response.ok}")
return response
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
return None
if __name__ == "__main__":
## Get status code from command line or use default
status_code = sys.argv[1] if len(sys.argv) > 1 else "200"
test_status_code(status_code)
Este script realiza una solicitud a httpbin.org con un código de estado específico e imprime el resultado. Puedes ejecutarlo con un argumento de línea de comandos para probar diferentes códigos de estado.
Prueba de Diferentes Códigos de Estado
Probemos nuestro script con diferentes códigos de estado para ver cómo la biblioteca requests los maneja:
- Prueba una solicitud exitosa (200 OK):
python status_code_tester.py 200
Salida esperada:
Status Code: 200
Response OK: True
- Prueba un error del cliente (404 Not Found):
python status_code_tester.py 404
Salida esperada:
Status Code: 404
Response OK: False
- Prueba un error del servidor (500 Internal Server Error):
python status_code_tester.py 500
Salida esperada:
Status Code: 500
Response OK: False
Manejo de Categorías de Códigos de Estado Básicas
Ahora, creemos un script más completo que maneje diferentes categorías de códigos de estado de manera apropiada. Crea un nuevo archivo llamado basic_handler.py en tu directorio http_status_lab:
import requests
def handle_response(url):
"""Handle different HTTP status codes with basic error handling."""
try:
response = requests.get(url)
## Check status code category
if 200 <= response.status_code < 300:
print(f"Success! Status code: {response.status_code}")
return response
elif 300 <= response.status_code < 400:
print(f"Redirection! Status code: {response.status_code}")
## For redirection, you might want to follow the redirect
return response
elif 400 <= response.status_code < 500:
print(f"Client error! Status code: {response.status_code}")
## Handle client errors
return response
elif 500 <= response.status_code < 600:
print(f"Server error! Status code: {response.status_code}")
## Handle server errors
return response
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
return None
## Test with different status codes
print("Testing 200 OK:")
handle_response("https://httpbin.org/status/200")
print("\nTesting 404 Not Found:")
handle_response("https://httpbin.org/status/404")
print("\nTesting 500 Internal Server Error:")
handle_response("https://httpbin.org/status/500")
Ejecuta el script para ver cómo maneja diferentes códigos de estado:
python basic_handler.py
Salida esperada:
Testing 200 OK:
Success! Status code: 200
Testing 404 Not Found:
Client error! Status code: 404
Testing 500 Internal Server Error:
Server error! Status code: 500
Este manejador básico agrupa los códigos de estado en categorías y toma diferentes acciones basadas en la categoría. Este es un patrón común en las aplicaciones web Python, lo que te permite manejar diferentes tipos de respuestas de manera apropiada.
Implementación del Manejo de Errores Avanzado
Ahora que entiendes los conceptos básicos del manejo de códigos de estado HTTP, implementemos técnicas de manejo de errores más avanzadas para tus aplicaciones Python.
Uso de raise_for_status()
La biblioteca requests proporciona un método conveniente llamado raise_for_status() que lanza una excepción para los códigos de estado 4xx y 5xx. Esta es una forma simple pero efectiva de manejar errores en tu código.
Crea un nuevo archivo llamado raise_for_status_example.py en tu directorio http_status_lab:
import requests
def fetch_data(url):
"""Fetch data from a URL with error handling using raise_for_status()."""
try:
response = requests.get(url)
## This will raise an HTTPError if the HTTP request returned an unsuccessful status code
response.raise_for_status()
## If we get here, the request was successful
print(f"Success! Status code: {response.status_code}")
return response.json() if 'application/json' in response.headers.get('Content-Type', '') else response.text
except requests.exceptions.HTTPError as e:
print(f"HTTP Error: {e}")
return None
except requests.exceptions.ConnectionError as e:
print(f"Connection Error: {e}")
return None
except requests.exceptions.Timeout as e:
print(f"Timeout Error: {e}")
return None
except requests.exceptions.RequestException as e:
print(f"Request Exception: {e}")
return None
## Test with different URLs
print("Testing successful request:")
data = fetch_data("https://httpbin.org/json")
print(f"Received data type: {type(data)}")
print("\nTesting 404 error:")
data = fetch_data("https://httpbin.org/status/404")
print(f"Received data: {data}")
print("\nTesting 500 error:")
data = fetch_data("https://httpbin.org/status/500")
print(f"Received data: {data}")
Ejecuta el script para ver cómo raise_for_status() maneja los errores:
python raise_for_status_example.py
La salida esperada mostrará que la biblioteca lanza automáticamente excepciones para los códigos de estado que no son 2xx, que tu código puede luego capturar y manejar apropiadamente.
Creación de Manejadores de Excepciones Personalizados
Para aplicaciones más complejas, es posible que desees crear manejadores de excepciones personalizados para proporcionar un manejo de errores más específico. Creemos un ejemplo más avanzado:
Crea un nuevo archivo llamado custom_exception_handler.py en tu directorio http_status_lab:
import requests
import logging
## Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
## Custom exceptions
class APIError(Exception):
"""Base class for API request exceptions."""
pass
class ClientError(APIError):
"""Exception raised for client errors (4xx)."""
pass
class ServerError(APIError):
"""Exception raised for server errors (5xx)."""
pass
class NotFoundError(ClientError):
"""Exception raised for 404 Not Found errors."""
pass
class UnauthorizedError(ClientError):
"""Exception raised for 401 Unauthorized errors."""
pass
def api_request(url, method="get", **kwargs):
"""Make an API request with comprehensive error handling."""
try:
## Make the request using the specified HTTP method
response = getattr(requests, method.lower())(url, **kwargs)
## Log the response status
logger.info(f"Request to {url} returned status code {response.status_code}")
## Handle different status codes
if 200 <= response.status_code < 300:
return response
elif response.status_code == 401:
raise UnauthorizedError(f"Authentication required: {response.text}")
elif response.status_code == 404:
raise NotFoundError(f"Resource not found: {url}")
elif 400 <= response.status_code < 500:
raise ClientError(f"Client error {response.status_code}: {response.text}")
elif 500 <= response.status_code < 600:
raise ServerError(f"Server error {response.status_code}: {response.text}")
else:
raise APIError(f"Unexpected status code {response.status_code}: {response.text}")
except requests.exceptions.RequestException as e:
logger.error(f"Request failed: {e}")
raise APIError(f"Request failed: {e}")
## Test the custom exception handler
def test_api_request(url):
"""Test the api_request function with error handling."""
try:
response = api_request(url)
print(f"Success! Status code: {response.status_code}")
print(f"Response: {response.text[:100]}...") ## Print first 100 characters
return response
except UnauthorizedError as e:
print(f"Authentication Error: {e}")
except NotFoundError as e:
print(f"Not Found Error: {e}")
except ClientError as e:
print(f"Client Error: {e}")
except ServerError as e:
print(f"Server Error: {e}")
except APIError as e:
print(f"API Error: {e}")
return None
## Test with different status codes
print("Testing 200 OK:")
test_api_request("https://httpbin.org/status/200")
print("\nTesting 404 Not Found:")
test_api_request("https://httpbin.org/status/404")
print("\nTesting 401 Unauthorized:")
test_api_request("https://httpbin.org/status/401")
print("\nTesting 500 Internal Server Error:")
test_api_request("https://httpbin.org/status/500")
Ejecuta el script para ver cómo funciona el manejo de excepciones personalizado:
python custom_exception_handler.py
Este ejemplo avanzado demuestra varios conceptos importantes:
- Clases de excepción personalizadas que extienden la clase base
Exception - Excepciones jerárquicas para diferentes tipos de errores
- Registro detallado para depuración y monitoreo
- Manejo específico para diferentes códigos de estado
Este enfoque te permite manejar diferentes tipos de errores de una manera más estructurada y mantenible, lo cual es esencial para aplicaciones más grandes.
Construyendo un Cliente de API Web Completo
En este paso final, reuniremos todo lo que has aprendido para construir un cliente de API web completo con un manejo de errores robusto. Crearemos un cliente para la API JSONPlaceholder, que es una API REST en línea gratuita que puedes usar para realizar pruebas.
Creación de un Cliente de API Simple
Creemos un archivo llamado api_client.py en tu directorio http_status_lab:
import requests
import json
import logging
## Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
class APIClient:
"""A simple API client with robust error handling."""
def __init__(self, base_url):
"""Initialize the API client with a base URL."""
self.base_url = base_url
self.session = requests.Session()
def request(self, endpoint, method="get", params=None, data=None, headers=None):
"""Make a request to the API with error handling."""
url = f"{self.base_url}{endpoint}"
## Default headers for JSON APIs
if headers is None:
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
## Convert data to JSON if it's a dictionary
json_data = None
if data and isinstance(data, dict):
json_data = data
data = None
try:
## Log the request
logger.info(f"Making {method.upper()} request to {url}")
## Make the request
response = self.session.request(
method=method,
url=url,
params=params,
data=data,
json=json_data,
headers=headers
)
## Log the response status
logger.info(f"Received response with status code {response.status_code}")
## Check for HTTP errors
response.raise_for_status()
## Parse JSON response if applicable
try:
return response.json()
except json.JSONDecodeError:
return response.text
except requests.exceptions.HTTPError as e:
status_code = e.response.status_code
error_message = f"HTTP Error: {status_code}"
try:
## Try to get more details from the response
error_data = e.response.json()
error_message = f"{error_message} - {error_data.get('message', str(error_data))}"
except (json.JSONDecodeError, AttributeError):
error_message = f"{error_message} - {e.response.text if hasattr(e, 'response') else str(e)}"
logger.error(error_message)
## Re-raise with more context
if 400 <= status_code < 500:
logger.error(f"Client error: {error_message}")
raise Exception(f"Client error (HTTP {status_code}): {error_message}")
elif 500 <= status_code < 600:
logger.error(f"Server error: {error_message}")
raise Exception(f"Server error (HTTP {status_code}): {error_message}")
else:
raise
except requests.exceptions.ConnectionError as e:
logger.error(f"Connection Error: {e}")
raise Exception(f"Connection Error: Could not connect to {url}")
except requests.exceptions.Timeout as e:
logger.error(f"Timeout Error: {e}")
raise Exception(f"Timeout Error: Request to {url} timed out")
except requests.exceptions.RequestException as e:
logger.error(f"Request Exception: {e}")
raise Exception(f"Request Error: {str(e)}")
def get(self, endpoint, params=None, headers=None):
"""Make a GET request to the API."""
return self.request(endpoint, method="get", params=params, headers=headers)
def post(self, endpoint, data=None, headers=None):
"""Make a POST request to the API."""
return self.request(endpoint, method="post", data=data, headers=headers)
def put(self, endpoint, data=None, headers=None):
"""Make a PUT request to the API."""
return self.request(endpoint, method="put", data=data, headers=headers)
def delete(self, endpoint, headers=None):
"""Make a DELETE request to the API."""
return self.request(endpoint, method="delete", headers=headers)
## Test the API client with JSONPlaceholder
def test_api_client():
"""Test the API client with JSONPlaceholder."""
client = APIClient("https://jsonplaceholder.typicode.com")
try:
## Get a list of posts
print("\nGetting posts:")
posts = client.get("/posts")
print(f"Retrieved {len(posts)} posts")
print(f"First post: {posts[0]}")
## Get a single post
print("\nGetting a single post:")
post = client.get("/posts/1")
print(f"Retrieved post: {post}")
## Get a non-existent post (404)
print("\nTrying to get a non-existent post:")
try:
client.get("/posts/999999")
except Exception as e:
print(f"Expected error: {e}")
## Create a new post
print("\nCreating a new post:")
new_post = client.post("/posts", {
"title": "My New Post",
"body": "This is the content of my new post.",
"userId": 1
})
print(f"Created post: {new_post}")
## Update a post
print("\nUpdating a post:")
updated_post = client.put("/posts/1", {
"id": 1,
"title": "Updated Title",
"body": "Updated content.",
"userId": 1
})
print(f"Updated post: {updated_post}")
## Delete a post
print("\nDeleting a post:")
delete_response = client.delete("/posts/1")
print(f"Delete response: {delete_response}")
except Exception as e:
print(f"An error occurred: {e}")
if __name__ == "__main__":
test_api_client()
Ejecuta el script para ver cómo nuestro cliente de API completo maneja diferentes escenarios:
python api_client.py
Prueba del Manejo de Errores
Creemos un archivo separado para probar las capacidades de manejo de errores de nuestro cliente de API. Crea un archivo llamado test_error_handling.py en tu directorio http_status_lab:
from api_client import APIClient
import time
def test_different_errors():
"""Test different error scenarios with our API client."""
## Test with a valid API
print("\n1. Testing with a valid API:")
valid_client = APIClient("https://jsonplaceholder.typicode.com")
try:
data = valid_client.get("/posts/1")
print(f"Success! Retrieved data: {data}")
except Exception as e:
print(f"Unexpected error: {e}")
## Test with a 404 error
print("\n2. Testing with a non-existent endpoint (404):")
try:
valid_client.get("/non_existent_endpoint")
except Exception as e:
print(f"Expected error: {e}")
## Test with an invalid host
print("\n3. Testing with an invalid host (Connection Error):")
invalid_client = APIClient("https://this-does-not-exist-123456789.com")
try:
invalid_client.get("/anything")
except Exception as e:
print(f"Expected error: {e}")
## Test with a timeout
print("\n4. Testing with a timeout:")
timeout_client = APIClient("https://httpbin.org")
try:
## httpbin.org/delay/5 will delay the response for 5 seconds
## but we'll set the timeout to 2 seconds
timeout_client.session.request = lambda **kwargs: timeout_client.session.request_original(
**{**kwargs, 'timeout': 2}
)
timeout_client.session.request_original = timeout_client.session.request
timeout_client.get("/delay/5")
except Exception as e:
print(f"Expected error: {e}")
if __name__ == "__main__":
test_different_errors()
El script anterior podría producir errores debido al manejo del tiempo de espera, ya que intenta modificar un método en tiempo de ejecución. Simplifiquémoslo para evitar esos problemas:
from api_client import APIClient
def test_different_errors():
"""Test different error scenarios with our API client."""
## Test with a valid API
print("\n1. Testing with a valid API:")
valid_client = APIClient("https://jsonplaceholder.typicode.com")
try:
data = valid_client.get("/posts/1")
print(f"Success! Retrieved data: {data}")
except Exception as e:
print(f"Unexpected error: {e}")
## Test with a 404 error
print("\n2. Testing with a non-existent endpoint (404):")
try:
valid_client.get("/non_existent_endpoint")
except Exception as e:
print(f"Expected error: {e}")
## Test with a server error
print("\n3. Testing with a server error (500):")
error_client = APIClient("https://httpbin.org")
try:
error_client.get("/status/500")
except Exception as e:
print(f"Expected error: {e}")
if __name__ == "__main__":
test_different_errors()
Ejecuta el script de prueba de errores:
python test_error_handling.py
Este script demuestra cómo nuestro cliente de API maneja diferentes escenarios de error, proporcionando una base sólida para aplicaciones del mundo real.
Conclusiones Clave
Al construir este cliente de API completo, has aprendido varias técnicas importantes:
- Creación de una clase de cliente de API reutilizable
- Implementación del manejo de errores integral
- Registro de solicitudes y respuestas para la depuración
- Análisis de diferentes formatos de respuesta
- Manejo de diferentes métodos HTTP (GET, POST, PUT, DELETE)
Estas habilidades son esenciales para construir aplicaciones Python robustas que interactúan con las API web, asegurando que tu código pueda manejar con elegancia varios escenarios de error y proporcionar mensajes de error significativos a los usuarios.
Resumen
En este laboratorio, aprendiste a manejar los códigos de estado HTTP en las solicitudes de Python, progresando de técnicas básicas a avanzadas. Ahora tienes las habilidades para:
- Comprender las diferentes categorías de códigos de estado HTTP y sus significados
- Implementar el manejo básico de errores con la verificación de códigos de estado
- Usar
raise_for_status()para un manejo de errores simple - Crear clases de excepción personalizadas para un manejo de errores más específico
- Construir un cliente de API completo con un manejo de errores integral
Estas habilidades son esenciales para desarrollar aplicaciones Python robustas que interactúan con servicios web. Al manejar correctamente los códigos de estado HTTP y los errores, puedes crear aplicaciones más confiables que proporcionen mejores experiencias de usuario y sean más fáciles de mantener.
Recuerda siempre considerar el manejo de errores cuando trabajes con solicitudes HTTP en tus aplicaciones Python. Es un aspecto crucial del desarrollo web que distingue el código profesional de las implementaciones amateur.



