Сьогодні ми перейдемо до цікавої теми: користувацькі помилки. Це ті помилки, які виникають не через роботу веб-сервера, а тому, що ваш користувач або не виконав бізнес-логіку, або припустився помилки (ну або просто вирішив зламати ваш API, хто знає?).
Перш ніж зануритись у код, давайте трохи поговоримо про те, що взагалі означає термін "користувацька помилка".
Уявіть супермаркети в реальному житті (ті самі, де є каси й візки). Якщо покупець підходить до каси з банківською карткою, на якій недостатньо коштів, це не "помилка каси". Це помилка покупця з точки зору бізнес-логіки системи — покупка неможлива. Перенісши цю логіку в веб-додаток, ми можемо віддати користувачу помилку типу: "Недостатньо коштів на рахунку".
У FastAPI користувацькі помилки дозволяють нам:
- Керувати логікою додатка.
- Повідомляти користувачу, що саме пішло не так (людською мовою).
- Гарантувати повернення передбачуваного формату помилки.
Отже, користувацькі помилки — це не страшні повідомлення про аварії, а корисні сигнали, які допомагають користувачу й розробнику зрозуміти, що саме сталося.
Обробка користувацьких помилок в FastAPI
FastAPI надає зручний спосіб обробки користувацьких помилок через механізм вбудованих HTTP-винятків і підтримку кастомних помилок.
Використання вбудованого HTTPException
FastAPI має вбудований виняток HTTPException, який можна легко використати для повернення помилок з певними статус-кодами та повідомленнями. Ось приклад:
from fastapi import FastAPI, HTTPException
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: int):
if item_id == 42:
raise HTTPException(
status_code=404,
detail="Item not found"
)
return {"item_id": item_id, "name": "Super Item"}
У цьому коді, якщо користувач спробує запитати item_id=42, ми викинемо помилку 404 Not Found з повідомленням "Item not found".
Тепер він не буде марно копирсатись із пустим JSON, не розуміючи причину, а отримає лаконічне пояснення.
Що робить HTTPException "під капотом"?
Коли ми кидаємо виняток HTTPException, FastAPI:
- Перехоплює його.
- Генерує правильну HTTP-відповідь із вказаним статус-кодом.
- Вставляє поле
detailв JSON-відповідь, щоб користувач зрозумів, що пішло не так.
Додаємо кастомні дані в HTTPException
Якщо треба відправити додаткову інформацію, таку як код помилки або рекомендації для вирішення, HTTPException дозволяє це зробити:
from fastapi import FastAPI, HTTPException
app = FastAPI()
@app.get("/users/{username}")
async def get_user(username: str):
if username != "admin":
raise HTTPException(
status_code=403,
detail="Access forbidden: you need admin privileges.",
headers={"X-Error": "AdminRequired"}
)
return {"username": username, "role": "Administrator"}
У цьому випадку, якщо користувач — не адмін, ми повертаємо помилку 403 (Forbidden) з додатковим заголовком X-Error: AdminRequired. Це корисно, якщо ваш фронтенд хоче аналізувати заголовки відповіді.
Створення власних користувацьких помилок
Інколи вбудованих винятків замало, і хочеться кастомізувати поведінку додатка. На допомогу приходять користувацькі класи винятків.
Користувацькі винятки на базі Python
Користувацькі помилки можна реалізувати за допомогою стандартного механізму винятків Python:
class BusinessLogicError(Exception):
def __init__(self, name: str):
self.name = name
Тепер ми можемо "кидати" цей виняток у коді:
from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse
app = FastAPI()
class BusinessLogicError(Exception):
def __init__(self, name: str):
self.name = name
@app.exception_handler(BusinessLogicError)
async def business_logic_error_handler(request, exc: BusinessLogicError):
return JSONResponse(
status_code=400,
content={"error": f"Business logic violated: {exc.name}"}
)
@app.get("/validate")
async def validate_action(action: str):
if action != "allowed":
raise BusinessLogicError(name=f"Action '{action}' is not permitted.")
return {"msg": "Success"}
Тут ми:
- Створили користувацький виняток
BusinessLogicError. - Налаштували його хендлер за допомогою
@app.exception_handler. - Повертаємо зрозумілу користувачу відповідь з HTTP-статусом 400 Bad Request.
Тепер, якщо користувач викличе /validate з забороненою командою, він отримає JSON на кшталт:
{
"error": "Business logic violated: Action 'disallowed' is not permitted."
}
Практичний приклад з банківською системою
А тепер створимо невеликий ендпоінт, який перевірятиме, чи достатньо коштів на рахунку.
from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse
app = FastAPI()
class InsufficientFundsError(Exception):
def __init__(self, account_id: int, balance: float, required: float):
self.account_id = account_id
self.balance = balance
self.required = required
@app.exception_handler(InsufficientFundsError)
async def insufficient_funds_handler(request, exc: InsufficientFundsError):
return JSONResponse(
status_code=400,
content={
"account_id": exc.account_id,
"balance": exc.balance,
"required": exc.required,
"error": "Insufficient funds"
}
)
@app.post("/withdraw/{account_id}")
async def withdraw(account_id: int, amount: float):
# Приклад даних для рахунку
fake_account_data = {"balance": 100.0} # Замість цього ви б використали базу даних
if amount > fake_account_data["balance"]:
raise InsufficientFundsError(
account_id=account_id,
balance=fake_account_data["balance"],
required=amount
)
# Тут логіка для успішного зняття грошей
return {"status": "Withdrawal successful"}
Приблизна відповідь при нестачі коштів:
{
"account_id": 1,
"balance": 100.0,
"required": 200.0,
"error": "Insufficient funds"
}
Помилки, які роблять життя цікавішим
Важливо пам'ятати, що при киданні користувацьких помилок ми маємо:
- Чітко розуміти, який HTTP-код відповідатиме нашій помилці (не робіть
403для всіх випадків). - Інформативно називати поле
detailабо інші змістовні поля. - Логувати такі помилки, щоб згодом ви могли допомогти користувачам або відладити свою бізнес-логіку.
Також слідкуйте, щоб кастомні хендлери помилок не впроваджували нові баги. Наприклад, не перехоплюйте всі винятки підряд, інакше ризикуєте сховати справжні помилки вашої програми.
Резюме
Обробка користувацьких помилок стане в пригоді вам у реальному житті, коли потрібно дати людині зрозуміти, що вона зробила не так. Це також демонструє зрілість вашого програмного рішення, бо користувачі цінують зрозумілість і прозорість, а роботодавці — вашу здатність керувати складною логікою через передбачувані процеси.
На співбесідах цей досвід може стати хорошим аргументом на користь вашої кваліфікації як розробника.
Про обробники помилок можна прочитати детальніше в офіційній документації FastAPI.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ