Padroneggia la gestione degli errori di FastAPI con gestori di eccezioni personalizzati. Crea API robuste con risposte di errore significative per una migliore esperienza utente.
Gestione degli errori in Python FastAPI: Creazione di gestori di eccezioni personalizzati robusti
La gestione degli errori è un aspetto cruciale della creazione di API robuste e affidabili. In FastAPI di Python, puoi sfruttare i gestori di eccezioni personalizzati per gestire con grazia gli errori e fornire risposte informative ai client. Questo post del blog ti guiderà attraverso il processo di creazione di gestori di eccezioni personalizzati in FastAPI, consentendoti di creare applicazioni più resilienti e user-friendly.
Perché i gestori di eccezioni personalizzati?
FastAPI fornisce supporto integrato per la gestione delle eccezioni. Tuttavia, affidarsi esclusivamente alle risposte di errore predefinite può lasciare i client con informazioni vaghe o inutili. I gestori di eccezioni personalizzati offrono diversi vantaggi:
- Migliore esperienza utente: Fornisci messaggi di errore chiari e informativi, adattati a scenari di errore specifici.
- Gestione centralizzata degli errori: Consolida la logica di gestione degli errori in un unico posto, rendendo il tuo codice più manutenibile.
- Risposte di errore coerenti: Assicurati che le risposte di errore seguano un formato coerente, migliorando l'usabilità dell'API.
- Maggiore sicurezza: Impedisci che informazioni sensibili vengano esposte nei messaggi di errore.
- Logging personalizzato: Registra informazioni dettagliate sugli errori per il debug e il monitoraggio.
Comprensione della gestione delle eccezioni di FastAPI
FastAPI utilizza una combinazione dei meccanismi di gestione delle eccezioni integrati di Python e del proprio sistema di iniezione delle dipendenze per gestire gli errori. Quando viene sollevata un'eccezione all'interno di un percorso o di una dipendenza, FastAPI cerca un gestore di eccezioni appropriato per elaborarla.
I gestori di eccezioni sono funzioni decorate con @app.exception_handler() che prendono due argomenti: il tipo di eccezione e l'oggetto richiesta. Il gestore è responsabile della restituzione di una risposta HTTP appropriata.
Creazione di eccezioni personalizzate
Prima di definire gestori di eccezioni personalizzati, è spesso utile creare classi di eccezioni personalizzate che rappresentano condizioni di errore specifiche nella tua applicazione. Questo migliora la leggibilità del codice e rende più facile la gestione di diversi tipi di errori.
Ad esempio, diciamo che stai creando un'API di e-commerce e devi gestire i casi in cui un prodotto è esaurito. Puoi definire una classe di eccezione personalizzata chiamata OutOfStockError:
class OutOfStockError(Exception):
def __init__(self, product_id: int):
self.product_id = product_id
self.message = f"Il prodotto con ID {product_id} è esaurito."
Questa classe di eccezione personalizzata eredita dalla classe base Exception e include un attributo product_id e un messaggio di errore personalizzato.
Implementazione di gestori di eccezioni personalizzati
Ora, creiamo un gestore di eccezioni personalizzato per OutOfStockError. Questo gestore catturerà l'eccezione e restituirà una risposta HTTP 400 (Bad Request) con un corpo JSON contenente il messaggio di errore.
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse
app = FastAPI()
class OutOfStockError(Exception):
def __init__(self, product_id: int):
self.product_id = product_id
self.message = f"Il prodotto con ID {product_id} è esaurito."
@app.exception_handler(OutOfStockError)
async def out_of_stock_exception_handler(request: Request, exc: OutOfStockError):
return JSONResponse(
status_code=400,
content={"message": exc.message},
)
@app.get("/products/{product_id}")
async def get_product(product_id: int):
# Simula il controllo dello stock del prodotto
if product_id == 123:
raise OutOfStockError(product_id=product_id)
return {"product_id": product_id, "name": "Prodotto di esempio", "price": 29.99}
In questo esempio, il decoratore @app.exception_handler(OutOfStockError) registra la funzione out_of_stock_exception_handler per gestire le eccezioni OutOfStockError. Quando OutOfStockError viene sollevato nel percorso get_product, viene richiamato il gestore di eccezioni. Il gestore quindi restituisce un JSONResponse con un codice di stato 400 e un corpo JSON contenente il messaggio di errore.
Gestione di più tipi di eccezioni
Puoi definire più gestori di eccezioni per gestire diversi tipi di eccezioni. Ad esempio, potresti voler gestire le eccezioni ValueError che si verificano durante l'analisi dell'input dell'utente.
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
app = FastAPI()
@app.exception_handler(ValueError)
async def value_error_exception_handler(request: Request, exc: ValueError):
return JSONResponse(
status_code=400,
content={"message": str(exc)},
)
@app.get("/items/{item_id}")
async def get_item(item_id: int):
# Simula item_id non valido
if item_id < 0:
raise ValueError("L'ID dell'articolo deve essere un intero positivo.")
return {"item_id": item_id, "name": "Articolo di esempio"}
In questo esempio, la funzione value_error_exception_handler gestisce le eccezioni ValueError. Estrae il messaggio di errore dall'oggetto eccezione e lo restituisce nella risposta JSON.
Utilizzo di HTTPException
FastAPI fornisce una classe di eccezione integrata chiamata HTTPException che può essere utilizzata per sollevare errori specifici di HTTP. Questo può essere utile per gestire scenari di errore comuni come l'accesso non autorizzato o la risorsa non trovata.
from fastapi import FastAPI, HTTPException
app = FastAPI()
@app.get("/users/{user_id}")
async def get_user(user_id: int):
# Simula utente non trovato
if user_id == 999:
raise HTTPException(status_code=404, detail="Utente non trovato")
return {"user_id": user_id, "name": "Utente di esempio"}
In questo esempio, HTTPException viene sollevato con un codice di stato 404 (Not Found) e un messaggio di dettaglio. FastAPI gestisce automaticamente le eccezioni HTTPException e restituisce una risposta JSON con il codice di stato e il messaggio di dettaglio specificati.
Gestori di eccezioni globali
Puoi anche definire gestori di eccezioni globali che catturano tutte le eccezioni non gestite. Questo può essere utile per registrare gli errori o restituire un messaggio di errore generico al client.
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
import logging
app = FastAPI()
logger = logging.getLogger(__name__)
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
logger.exception(f"Eccezione non gestita: {exc}")
return JSONResponse(
status_code=500,
content={"message": "Errore interno del server"},
)
@app.get("/error")
async def trigger_error():
raise ValueError("Questo è un errore di prova.")
In questo esempio, la funzione global_exception_handler gestisce tutte le eccezioni che non vengono gestite da altri gestori di eccezioni. Registra l'errore e restituisce una risposta 500 (Internal Server Error) con un messaggio di errore generico.
Utilizzo del middleware per la gestione delle eccezioni
Un altro approccio alla gestione delle eccezioni è l'uso del middleware. Le funzioni middleware vengono eseguite prima e dopo ogni richiesta, consentendo di intercettare e gestire le eccezioni a un livello superiore. Questo può essere utile per attività come la registrazione di richieste e risposte o per l'implementazione di logica di autenticazione o autorizzazione personalizzata.
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
import logging
app = FastAPI()
logger = logging.getLogger(__name__)
@app.middleware("http")
async def exception_middleware(request: Request, call_next):
try:
response = await call_next(request)
except Exception as exc:
logger.exception(f"Eccezione non gestita: {exc}")
return JSONResponse(
status_code=500,
content={"message": "Errore interno del server"},
)
return response
@app.get("/error")
async def trigger_error():
raise ValueError("Questo è un errore di prova.")
In questo esempio, la funzione exception_middleware racchiude la logica di elaborazione della richiesta in un blocco try...except. Se viene sollevata un'eccezione durante l'elaborazione della richiesta, il middleware registra l'errore e restituisce una risposta 500 (Internal Server Error).
Esempio: internazionalizzazione (i18n) e messaggi di errore
Quando si creano API per un pubblico globale, considera l'internazionalizzazione dei tuoi messaggi di errore. Ciò implica fornire messaggi di errore in lingue diverse in base alle impostazioni locali dell'utente. Sebbene l'implementazione completa di i18n vada oltre lo scopo di questo articolo, ecco un esempio semplificato che dimostra il concetto:
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse
from typing import Dict
app = FastAPI()
# Dizionario di traduzione simulato (sostituire con una libreria i18n reale)
translations: Dict[str, Dict[str, str]] = {
"en": {
"product_not_found": "Product with ID {product_id} not found.",
"invalid_input": "Invalid input: {error_message}",
},
"fr": {
"product_not_found": "Produit avec l'ID {product_id} introuvable.",
"invalid_input": "Entrée invalide : {error_message}",
},
"es": {
"product_not_found": "Producto con ID {product_id} no encontrado.",
"invalid_input": "Entrada inválida: {error_message}",
},
"de": {
"product_not_found": "Produkt mit ID {product_id} nicht gefunden.",
"invalid_input": "Ungültige Eingabe: {error_message}",
}
}
def get_translation(locale: str, key: str, **kwargs) -> str:
"""Recupera una traduzione per una determinata impostazione locale e chiave.
Se l'impostazione locale o la chiave non vengono trovate, restituisce un messaggio predefinito.
"""
if locale in translations and key in translations[locale]:
return translations[locale][key].format(**kwargs)
return f"Traduzione mancante per la chiave '{key}' nell'impostazione locale '{locale}'."
@app.get("/products/{product_id}")
async def get_product(request: Request, product_id: int, locale: str = "en"):
# Simula la ricerca del prodotto
if product_id > 100:
message = get_translation(locale, "product_not_found", product_id=product_id)
raise HTTPException(status_code=404, detail=message)
if product_id < 0:
message = get_translation(locale, "invalid_input", error_message="L'ID del prodotto deve essere positivo")
raise HTTPException(status_code=400, detail=message)
return {"product_id": product_id, "name": "Prodotto di esempio"}
Miglioramenti chiave per l'esempio i18n:
- Parametro delle impostazioni locali: il percorso ora accetta un parametro di query
locale, che consente ai client di specificare la propria lingua preferita (impostazione predefinita su "en" per l'inglese). - Dizionario di traduzione: un dizionario
translations(simulato) memorizza i messaggi di errore per diverse impostazioni locali (inglese, francese, spagnolo, tedesco in questo caso). In un'applicazione reale, useresti una libreria i18n dedicata. - Funzione
get_translation: questa funzione helper recupera la traduzione appropriata in base alocaleekey. Supporta anche la formattazione delle stringhe per inserire valori dinamici (comeproduct_id). - Messaggi di errore dinamici:
HTTPExceptionviene ora sollevato con un messaggiodetailche viene generato dinamicamente utilizzando la funzioneget_translation.
Quando un client richiede /products/101?locale=fr, riceverà un messaggio di errore in francese (se la traduzione è disponibile). Quando si richiede /products/-1?locale=es, riceverà un messaggio di errore sull'ID negativo in spagnolo (se disponibile). Quando si richiede /products/200?locale=xx (un'impostazione locale senza traduzioni), riceverà il messaggio `Traduzione mancante`.
Best practice per la gestione degli errori
Ecco alcune best practice da tenere a mente quando si implementa la gestione degli errori in FastAPI:
- Usa eccezioni personalizzate: definisci classi di eccezioni personalizzate per rappresentare condizioni di errore specifiche nella tua applicazione.
- Fornisci messaggi di errore informativi: includi messaggi di errore chiari e concisi che aiutino i client a comprendere la causa dell'errore.
- Utilizza i codici di stato HTTP appropriati: restituisci i codici di stato HTTP che riflettono accuratamente la natura dell'errore. Ad esempio, utilizza 400 (Bad Request) per input non validi, 404 (Not Found) per risorse mancanti e 500 (Internal Server Error) per errori imprevisti.
- Evita di esporre informazioni sensibili: fai attenzione a non esporre informazioni sensibili come le credenziali del database o le chiavi API nei messaggi di errore.
- Registra gli errori: registra informazioni dettagliate sugli errori per il debug e il monitoraggio. Utilizza una libreria di logging come il modulo
loggingintegrato di Python. - Centralizza la logica di gestione degli errori: consolida la logica di gestione degli errori in un unico posto, ad esempio nei gestori di eccezioni personalizzati o nel middleware.
- Testa la gestione degli errori: scrivi unit test per assicurarti che la tua logica di gestione degli errori funzioni correttamente.
- Considera l'utilizzo di un servizio di monitoraggio degli errori dedicato: per gli ambienti di produzione, prendi in considerazione l'utilizzo di un servizio di monitoraggio degli errori dedicato come Sentry o Rollbar per monitorare e analizzare gli errori. Questi strumenti possono fornire informazioni preziose sull'integrità della tua applicazione e aiutarti a identificare e risolvere i problemi rapidamente.
Conclusione
I gestori di eccezioni personalizzati sono un potente strumento per la creazione di API robuste e user-friendly in FastAPI. Definendo classi e gestori di eccezioni personalizzati, puoi gestire con grazia gli errori, fornire risposte informative ai client e migliorare l'affidabilità e la manutenibilità complessive della tua applicazione. Combinando eccezioni personalizzate, HTTPExceptions e sfruttando i principi i18n, ove applicabile, la tua API è impostata per il successo globale.
Ricorda di considerare l'esperienza utente quando progetti la tua strategia di gestione degli errori. Fornisci messaggi di errore chiari e concisi che aiutino gli utenti a capire il problema e come risolverlo. Una gestione efficace degli errori è un caposaldo della creazione di API di alta qualità che soddisfano le esigenze di un pubblico globale diversificato.