विषय पर बढ़ें

Errors को हैंडल करना

🌐 एआई और मनुष्यों द्वारा किया गया अनुवाद

यह अनुवाद मनुष्यों के मार्गदर्शन में एआई द्वारा किया गया है। 🤝

इसमें मूल अर्थ को गलत समझने या अप्राकृतिक लगने आदि जैसी गलतियाँ हो सकती हैं। 🤖

आप हमें एआई LLM को बेहतर मार्गदर्शन करने में मदद करके इस अनुवाद को बेहतर बना सकते हैं।

अंग्रेज़ी संस्करण

ऐसी कई स्थितियाँ होती हैं जिनमें आपको अपनी API का उपयोग कर रहे client को error report करना पड़ता है।

यह client frontend वाला कोई browser, किसी और का code, कोई IoT device आदि हो सकता है।

आपको client को यह बताने की ज़रूरत पड़ सकती है कि:

  • client के पास उस operation के लिए पर्याप्त privileges नहीं हैं।
  • client के पास उस resource का access नहीं है।
  • जिस item को client access करने की कोशिश कर रहा था, वह मौजूद नहीं है।
  • आदि।

इन मामलों में, आप सामान्यतः 400 की range (400 से 499 तक) में एक HTTP status code return करेंगे।

यह 200 HTTP status codes (200 से 299 तक) जैसा ही है। वे "200" status codes का मतलब है कि request में किसी तरह "success" हुआ था।

400 range के status codes का मतलब है कि client की तरफ़ से कोई error था।

वे सभी "404 Not Found" errors (और jokes) याद हैं?

HTTPException का उपयोग करें

Client को errors वाली HTTP responses return करने के लिए आप HTTPException का उपयोग करते हैं।

HTTPException import करें

from fastapi import FastAPI, HTTPException

app = FastAPI()

items = {"foo": "The Foo Wrestlers"}


@app.get("/items/{item_id}")
async def read_item(item_id: str):
    if item_id not in items:
        raise HTTPException(status_code=404, detail="Item not found")
    return {"item": items[item_id]}

अपने code में HTTPException raise करें

HTTPException APIs के लिए प्रासंगिक अतिरिक्त data के साथ एक सामान्य Python exception है।

क्योंकि यह एक Python exception है, आप इसे return नहीं करते, आप इसे raise करते हैं।

इसका यह भी मतलब है कि अगर आप किसी utility function के अंदर हैं जिसे आप अपनी path operation function के अंदर call कर रहे हैं, और आप उस utility function के अंदर से HTTPException raise करते हैं, तो यह path operation function में बाकी code नहीं चलाएगा, यह उस request को तुरंत समाप्त कर देगा और HTTPException से HTTP error client को भेज देगा।

किसी value को return करने के बजाय exception raise करने का लाभ Dependencies और Security वाले section में अधिक स्पष्ट होगा।

इस example में, जब client किसी ऐसे ID से item request करता है जो मौजूद नहीं है, तो 404 के status code के साथ exception raise करें:

from fastapi import FastAPI, HTTPException

app = FastAPI()

items = {"foo": "The Foo Wrestlers"}


@app.get("/items/{item_id}")
async def read_item(item_id: str):
    if item_id not in items:
        raise HTTPException(status_code=404, detail="Item not found")
    return {"item": items[item_id]}

परिणामी response

अगर client http://example.com/items/foo (एक item_id "foo") request करता है, तो उस client को 200 का HTTP status code और यह JSON response मिलेगा:

{
  "item": "The Foo Wrestlers"
}

लेकिन अगर client http://example.com/items/bar (एक non-existent item_id "bar") request करता है, तो उस client को 404 का HTTP status code ("not found" error) और यह JSON response मिलेगा:

{
  "detail": "Item not found"
}

सुझाव

HTTPException raise करते समय, आप detail parameter के रूप में ऐसी कोई भी value pass कर सकते हैं जिसे JSON में convert किया जा सकता हो, केवल str ही नहीं।

आप dict, list आदि pass कर सकते हैं।

इन्हें FastAPI अपने आप handle करता है और JSON में convert करता है।

custom headers जोड़ें

कुछ स्थितियाँ ऐसी होती हैं जहाँ HTTP error में custom headers जोड़ पाना उपयोगी होता है। उदाहरण के लिए, कुछ प्रकार की security के लिए।

आपको शायद अपने code में सीधे इसका उपयोग करने की ज़रूरत नहीं होगी।

लेकिन अगर किसी advanced scenario में आपको इसकी ज़रूरत पड़े, तो आप custom headers जोड़ सकते हैं:

from fastapi import FastAPI, HTTPException

app = FastAPI()

items = {"foo": "The Foo Wrestlers"}


@app.get("/items-header/{item_id}")
async def read_item_header(item_id: str):
    if item_id not in items:
        raise HTTPException(
            status_code=404,
            detail="Item not found",
            headers={"X-Error": "There goes my error"},
        )
    return {"item": items[item_id]}

custom exception handlers install करें

आप Starlette से वही exception utilities के साथ custom exception handlers जोड़ सकते हैं।

मान लीजिए आपके पास एक custom exception UnicornException है जिसे आप (या कोई library जिसका आप उपयोग करते हैं) raise कर सकते हैं।

और आप इस exception को FastAPI के साथ globally handle करना चाहते हैं।

आप @app.exception_handler() के साथ custom exception handler जोड़ सकते हैं:

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse


class UnicornException(Exception):
    def __init__(self, name: str):
        self.name = name


app = FastAPI()


@app.exception_handler(UnicornException)
async def unicorn_exception_handler(request: Request, exc: UnicornException):
    return JSONResponse(
        status_code=418,
        content={"message": f"Oops! {exc.name} did something. There goes a rainbow..."},
    )


@app.get("/unicorns/{name}")
async def read_unicorn(name: str):
    if name == "yolo":
        raise UnicornException(name=name)
    return {"unicorn_name": name}

यहाँ, अगर आप /unicorns/yolo request करते हैं, तो path operation एक UnicornException raise करेगा।

लेकिन इसे unicorn_exception_handler द्वारा handle किया जाएगा।

इसलिए, आपको 418 के HTTP status code और इस JSON content के साथ एक साफ़ error मिलेगा:

{"message": "Oops! yolo did something. There goes a rainbow..."}

Technical Details

आप from starlette.requests import Request और from starlette.responses import JSONResponse का भी उपयोग कर सकते हैं।

FastAPI आपकी, developer की, सुविधा के लिए starlette.responses को fastapi.responses के रूप में उपलब्ध कराता है। लेकिन उपलब्ध अधिकांश responses सीधे Starlette से आते हैं। Request के साथ भी यही है।

default exception handlers को override करें

FastAPI में कुछ default exception handlers होते हैं।

ये handlers default JSON responses return करने के लिए ज़िम्मेदार होते हैं, जब आप HTTPException raise करते हैं और जब request में invalid data होता है।

आप इन exception handlers को अपने खुद के handlers से override कर सकते हैं।

request validation exceptions को override करें

जब किसी request में invalid data होता है, तो FastAPI internally एक RequestValidationError raise करता है।

और इसमें इसके लिए एक default exception handler भी शामिल होता है।

इसे override करने के लिए, RequestValidationError import करें और exception handler को decorate करने के लिए इसे @app.exception_handler(RequestValidationError) के साथ उपयोग करें।

Exception handler को एक Request और exception मिलेगा।

from fastapi import FastAPI, HTTPException
from fastapi.exceptions import RequestValidationError
from fastapi.responses import PlainTextResponse
from starlette.exceptions import HTTPException as StarletteHTTPException

app = FastAPI()


@app.exception_handler(StarletteHTTPException)
async def http_exception_handler(request, exc):
    return PlainTextResponse(str(exc.detail), status_code=exc.status_code)


@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc: RequestValidationError):
    message = "Validation errors:"
    for error in exc.errors():
        message += f"\nField: {error['loc']}, Error: {error['msg']}"
    return PlainTextResponse(message, status_code=400)


@app.get("/items/{item_id}")
async def read_item(item_id: int):
    if item_id == 3:
        raise HTTPException(status_code=418, detail="Nope! I don't like 3.")
    return {"item_id": item_id}

अब, अगर आप /items/foo पर जाते हैं, तो default JSON error पाने के बजाय:

{
    "detail": [
        {
            "loc": [
                "path",
                "item_id"
            ],
            "msg": "value is not a valid integer",
            "type": "type_error.integer"
        }
    ]
}

आपको text version मिलेगा, जिसमें होगा:

Validation errors:
Field: ('path', 'item_id'), Error: Input should be a valid integer, unable to parse string as an integer

HTTPException error handler को override करें

उसी तरह, आप HTTPException handler को override कर सकते हैं।

उदाहरण के लिए, आप इन errors के लिए JSON के बजाय plain text response return करना चाह सकते हैं:

from fastapi import FastAPI, HTTPException
from fastapi.exceptions import RequestValidationError
from fastapi.responses import PlainTextResponse
from starlette.exceptions import HTTPException as StarletteHTTPException

app = FastAPI()


@app.exception_handler(StarletteHTTPException)
async def http_exception_handler(request, exc):
    return PlainTextResponse(str(exc.detail), status_code=exc.status_code)


@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc: RequestValidationError):
    message = "Validation errors:"
    for error in exc.errors():
        message += f"\nField: {error['loc']}, Error: {error['msg']}"
    return PlainTextResponse(message, status_code=400)


@app.get("/items/{item_id}")
async def read_item(item_id: int):
    if item_id == 3:
        raise HTTPException(status_code=418, detail="Nope! I don't like 3.")
    return {"item_id": item_id}

Technical Details

आप from starlette.responses import PlainTextResponse का भी उपयोग कर सकते हैं।

FastAPI आपकी, developer की, सुविधा के लिए starlette.responses को fastapi.responses के रूप में उपलब्ध कराता है। लेकिन उपलब्ध अधिकांश responses सीधे Starlette से आते हैं।

चेतावनी

ध्यान रखें कि RequestValidationError में file name और उस line की जानकारी होती है जहाँ validation error होता है, ताकि अगर आप चाहें तो relevant जानकारी के साथ उसे अपने logs में दिखा सकें।

लेकिन इसका मतलब है कि अगर आप इसे केवल string में convert करके वह जानकारी सीधे return कर देते हैं, तो आप अपने system के बारे में थोड़ी जानकारी leak कर सकते हैं, इसलिए यहाँ code हर error को अलग-अलग extract करके दिखाता है।

RequestValidationError body का उपयोग करें

RequestValidationError में वह body होता है जो इसे invalid data के साथ मिला था।

आप अपनी app develop करते समय body को log और debug करने, user को return करने आदि के लिए इसका उपयोग कर सकते हैं।

from fastapi import FastAPI, Request
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from pydantic import BaseModel

app = FastAPI()


@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
    return JSONResponse(
        status_code=422,
        content=jsonable_encoder({"detail": exc.errors(), "body": exc.body}),
    )


class Item(BaseModel):
    title: str
    size: int


@app.post("/items/")
async def create_item(item: Item):
    return item

अब ऐसा invalid item भेजकर देखें:

{
  "title": "towel",
  "size": "XL"
}

आपको एक response मिलेगा जो बताता है कि data invalid है और जिसमें received body शामिल होगा:

{
  "detail": [
    {
      "loc": [
        "body",
        "size"
      ],
      "msg": "value is not a valid integer",
      "type": "type_error.integer"
    }
  ],
  "body": {
    "title": "towel",
    "size": "XL"
  }
}

FastAPI का HTTPException बनाम Starlette का HTTPException

FastAPI का अपना HTTPException है।

और FastAPI की HTTPException error class, Starlette की HTTPException error class से inherit करती है।

केवल अंतर यह है कि FastAPI का HTTPException, detail field के लिए कोई भी JSON-able data accept करता है, जबकि Starlette का HTTPException इसके लिए केवल strings accept करता है।

इसलिए, आप अपने code में सामान्य रूप से FastAPI का HTTPException raise करते रह सकते हैं।

लेकिन जब आप exception handler register करते हैं, तो आपको उसे Starlette के HTTPException के लिए register करना चाहिए।

इस तरह, अगर Starlette के internal code का कोई हिस्सा, या कोई Starlette extension या plug-in, Starlette HTTPException raise करता है, तो आपका handler उसे catch और handle कर पाएगा।

इस example में, एक ही code में दोनों HTTPExceptions रखने के लिए, Starlette के exceptions को StarletteHTTPException नाम दिया गया है:

from starlette.exceptions import HTTPException as StarletteHTTPException

FastAPI के exception handlers का फिर से उपयोग करें

अगर आप FastAPI के उन्हीं default exception handlers के साथ exception का उपयोग करना चाहते हैं, तो आप fastapi.exception_handlers से default exception handlers import करके उनका फिर से उपयोग कर सकते हैं:

from fastapi import FastAPI, HTTPException
from fastapi.exception_handlers import (
    http_exception_handler,
    request_validation_exception_handler,
)
from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException as StarletteHTTPException

app = FastAPI()


@app.exception_handler(StarletteHTTPException)
async def custom_http_exception_handler(request, exc):
    print(f"OMG! An HTTP error!: {repr(exc)}")
    return await http_exception_handler(request, exc)


@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
    print(f"OMG! The client sent invalid data!: {exc}")
    return await request_validation_exception_handler(request, exc)


@app.get("/items/{item_id}")
async def read_item(item_id: int):
    if item_id == 3:
        raise HTTPException(status_code=418, detail="Nope! I don't like 3.")
    return {"item_id": item_id}

इस example में आप केवल error को बहुत expressive message के साथ print कर रहे हैं, लेकिन आप बात समझ गए। आप exception का उपयोग कर सकते हैं और फिर बस default exception handlers का फिर से उपयोग कर सकते हैं।