The Story That Explains FastAPI 🛂FastAPI समझाने वाली कहानी 🛂一个讲清 FastAPI 的故事 🛂
Now picture a modern e-Gate. You place your passport on a scanner. In a moment it reads the fields, checks them against the rules, matches your face and opens the gate. If a field is wrong, the screen tells you exactly which one and why.
FastAPI is the e-Gate of Python web frameworks. You describe what a request should look like with type hints, and FastAPI reads it, checks it, documents it and sends it to the right function — automatically.
अब एक आधुनिक e-Gate सोचिए। आप passport scanner पर रखते हैं। पल भर में यह fields पढ़ता है, नियमों से जाँचता है, चेहरा मिलाता है और gate खोल देता है। कोई field ग़लत हो, तो screen ठीक-ठीक बताती है कौन-सा और क्यों।
FastAPI Python web frameworks का e-Gate है। आप type hints से बताते हैं कि request कैसी होनी चाहिए, और FastAPI उसे पढ़ता, जाँचता, उसका दस्तावेज़ बनाता और सही function तक भेजता है — अपने-आप।
再想象一个现代的 自助闸机。你把护照放上扫描器,片刻之间它就 读取 各项信息,按规则 检查,比对你的面孔,然后开闸。哪一项有问题,屏幕会准确告诉你是哪一项、为什么。
FastAPI 就是 Python Web 框架中的自助闸机。你用类型提示描述请求应有的样子,FastAPI 就会自动读取它、检查它、为它生成文档,并把它交给正确的函数。
FastAPI is a modern web framework for building APIs with Python, based on standard type hints. It sits on top of Starlette (the web parts) and Pydantic (the data parts). You get async support, automatic validation and interactive docs — all from the function you would write anyway. Current versions need Python 3.10 or newer.
FastAPI Python में APIs बनाने का आधुनिक web framework है, जो सामान्य type hints पर आधारित है। यह Starlette (web वाला हिस्सा) और Pydantic (data वाला हिस्सा) के ऊपर बना है। आपको async support, अपने-आप validation और interactive docs मिलते हैं — उसी function से जो आप वैसे भी लिखते। मौजूदा versions को Python 3.10 या नया चाहिए।
FastAPI 是一个基于标准类型提示、用 Python 构建 API 的现代 Web 框架。它建立在 Starlette(Web 部分)和 Pydantic(数据部分)之上。你会得到异步支持、自动校验和交互式文档 — 全部来自你本来就要写的那个函数。当前版本需要 Python 3.10 或更新版本。
Type hints are the single source of truth.
Type hints ही सच का एक स्रोत हैं।
类型提示是唯一的事实来源。
In many frameworks you write the endpoint, then the validation, then the documentation, then the JSON conversion. FastAPI uses your Python type hints as the single source of truth — one declaration gives you all four. Less code, fewer bugs, docs that never fall behind.
कई frameworks में आप endpoint लिखते हैं, फिर validation, फिर documentation, फिर JSON conversion। FastAPI आपके Python type hints को सच का एक स्रोत मानता है — एक घोषणा से चारों मिलते हैं। कम code, कम bugs, docs जो कभी पीछे नहीं छूटते।
在许多框架里,你要先写接口,再写校验,再写文档,再写 JSON 转换。FastAPI 把你的 Python 类型提示 当作唯一的事实来源 — 一次声明,四样都有。代码更少,bug 更少,文档永不落后。
Look for the interactive boxes: Where Does Each Argument Come From? (Section 03), the Validation Playground (04), the Request Lifecycle (05), the CRUD Lab (06), the Concurrency Lab (14) and the Bug Doctor (19). Every output on this page is from a real run with FastAPI 0.162.4, Pydantic 2.13.5 and Uvicorn 0.53.0 on Python 3.13.16.
Interactive डिब्बे खोजें: हर Argument कहाँ से आता है? (Section 03), Validation Playground (04), Request Lifecycle (05), CRUD Lab (06), Concurrency Lab (14) और Bug Doctor (19)। इस page का हर output FastAPI 0.162.4, Pydantic 2.13.5 और Uvicorn 0.53.0 के साथ Python 3.13.16 पर असली run से है।
请留意这些互动框:每个参数从哪里来?(第 03 节)、校验游乐场(04)、请求生命周期(05)、CRUD 实验室(06)、并发实验室(14)和 Bug 医生(19)。本页每个输出都来自 FastAPI 0.162.4、Pydantic 2.13.5 和 Uvicorn 0.53.0 在 Python 3.13.16 上的真实运行。
The original said FastAPI works with Python 3.8+. Current FastAPI releases need Python 3.10 or newer (the version used here declares Requires-Python >=3.10). It also promised "near-Go/Node.js speed". FastAPI is among the fastest Python frameworks, but real speed depends on your code, database and server setup — treat benchmark claims with care.
मूल में कहा गया कि FastAPI Python 3.8+ के साथ चलता है। मौजूदा FastAPI releases को Python 3.10 या नया चाहिए (यहाँ इस्तेमाल version Requires-Python >=3.10 घोषित करता है)। इसने "Go/Node.js जैसी speed" का वादा भी किया। FastAPI सबसे तेज़ Python frameworks में से है, पर असली speed आपके code, database और server setup पर निर्भर है — benchmark दावों को सावधानी से लीजिए।
原文说 FastAPI 支持 Python 3.8+。当前的 FastAPI 版本需要 Python 3.10 或更新版本(这里使用的版本声明了 Requires-Python >=3.10)。原文还承诺"接近 Go/Node.js 的速度"。FastAPI 确实是最快的 Python 框架之一,但实际速度取决于你的代码、数据库和服务器配置 — 对基准测试的说法要谨慎看待。
Installation and Your First API 🚀Installation और आपका पहला API 🚀安装与第一个 API 🚀
You need Python 3.10+ and two things: fastapi (the framework) and an ASGI server such as uvicorn that runs it.
आपको Python 3.10+ और दो चीज़ें चाहिए: fastapi (framework) और uvicorn जैसा ASGI server जो उसे चलाता है।
你需要 Python 3.10+ 以及两样东西:fastapi(框架)和运行它的 ASGI 服务器,例如 uvicorn。
# Recommended: FastAPI + Uvicorn + the fastapi CLI + common extras
pip install "fastapi[standard]"
# Or a minimal install (no "fastapi dev" command)
pip install fastapi uvicorn
Hello World — The Shortest Possible APIHello World — सबसे छोटा APIHello World — 最短的 API
Below is main.py, then a real run: the server log, three requests with curl, and a check that /docs exists.
नीचे main.py है, फिर एक असली run: server log, curl से तीन requests, और /docs मौजूद होने की जाँच।
下面是 main.py,然后是一次真实运行:服务器日志、用 curl 发出的三个请求,以及对 /docs 是否存在的检查。
# file: main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"message": "Hello, FastAPI!"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str | None = None):
return {"item_id": item_id, "q": q}
Notice the third request: /items/abc never reached read_item. FastAPI answered with a 422 error that says which field failed and why. You can also start the server with fastapi dev main.py (included in fastapi[standard]).
तीसरी request देखिए: /items/abc कभी read_item तक नहीं पहुँची। FastAPI ने 422 error दिया जो बताता है कौन-सा field fail हुआ और क्यों। आप server fastapi dev main.py से भी चला सकते हैं (fastapi[standard] में शामिल)।
注意第三个请求:/items/abc 根本没有到达 read_item。FastAPI 直接返回 422 错误,说明哪个字段失败以及原因。你也可以用 fastapi dev main.py 启动服务器(包含在 fastapi[standard] 中)。
Open http://127.0.0.1:8000/docs and you already have an interactive Swagger UI where you can try every endpoint. /redoc shows the same API as a clean reference page. Both are generated from your code — no extra files.
http://127.0.0.1:8000/docs खोलिए और आपके पास पहले से interactive Swagger UI है जहाँ आप हर endpoint आज़मा सकते हैं। /redoc वही API साफ़ reference page की तरह दिखाता है। दोनों आपके code से बनते हैं — कोई अतिरिक्त files नहीं।
打开 http://127.0.0.1:8000/docs,你就已经拥有一个交互式的 Swagger UI,可以试用每个接口。/redoc 则把同一个 API 显示为简洁的参考页面。两者都由你的代码自动生成 — 无需额外文件。
The Three Pillars of Every Endpoint 🏛️हर Endpoint के तीन खंभे 🏛️每个接口的三大支柱 🏛️
Most data comes into an endpoint from three places. Understanding them is a big part of understanding FastAPI.
Endpoint में ज़्यादातर data तीन जगहों से आता है। इन्हें समझना FastAPI समझने का बड़ा हिस्सा है।
进入接口的数据大多来自三个地方。理解它们,就理解了 FastAPI 的一大半。
Name appears in the path → path parameter. Type is a Pydantic model → request body. Type is a simple value (int, str, float, bool, Enum, UUID, date…) → query parameter. Anything else — like list[str] or dict — is treated as body unless you mark it with Query(), Header() or Cookie().
नाम path में है → path parameter। Type Pydantic model है → request body। Type सरल value है (int, str, float, bool, Enum, UUID, date…) → query parameter। बाक़ी कुछ भी — जैसे list[str] या dict — body माना जाता है, जब तक आप उसे Query(), Header() या Cookie() से चिह्नित न करें।
名字出现在路径中 → 路径参数。类型是 Pydantic 模型 → 请求体。类型是简单值(int、str、float、bool、Enum、UUID、date…)→ 查询参数。其他任何类型 — 比如 list[str] 或 dict — 都会被当作 请求体,除非你用 Query()、Header() 或 Cookie() 标注。
The original rule said every argument that is not in the path and not a Pydantic model becomes a query parameter. That is not true for list[str]: without Query(), FastAPI expects it in the request body, and ?tags=a&tags=b fails with a 422 "body: Field required". Try signature 4 and 5 in the simulator below.
मूल नियम में कहा गया कि path में न होने वाला और Pydantic model न होने वाला हर argument query parameter बनता है। list[str] के लिए यह सच नहीं: Query() के बिना FastAPI उसे request body में ढूँढता है, और ?tags=a&tags=b 422 "body: Field required" से fail होता है। नीचे simulator में signature 4 और 5 आज़माइए।
原规则说:既不在路径中、也不是 Pydantic 模型的参数都会成为查询参数。对 list[str] 来说并非如此:没有 Query() 时,FastAPI 会到请求体里找它,?tags=a&tags=b 会以 422"body: Field required"失败。请在下面的模拟器中试试第 4 和第 5 个签名。
🧭 Simulator: Where Does Each Argument Come From?🧭 Simulator: हर Argument कहाँ से आता है?🧭 模拟器:每个参数从哪里来?
Pick a function signature. The coloured tags come from FastAPI's own OpenAPI schema for that endpoint, and the request below was really sent.
एक function signature चुनिए। रंगीन tags उस endpoint के लिए FastAPI के अपने OpenAPI schema से आते हैं, और नीचे की request सच में भेजी गई।
选择一个函数签名。彩色标签来自 FastAPI 为该接口生成的 OpenAPI 模式,下面的请求也是真实发送的。
Type Hints — The Heart of FastAPI ❤️Type Hints — FastAPI का दिल ❤️类型提示 — FastAPI 的核心 ❤️
item_id=abc when you asked for int, FastAPI never calls your function — it returns a clear 422 error saying exactly what went wrong.int की जगह item_id=abc भेजे, तो FastAPI आपका function बुलाता ही नहीं — यह साफ़ 422 error लौटाता है जो ठीक बताता है क्या ग़लत हुआ।int,客户端却发来 item_id=abc,FastAPI 根本不会调用你的函数 — 而是返回一个清晰的 422 错误,准确说明哪里出错。The Same Function — Step by Step More Typedवही Function — कदम-दर-कदम ज़्यादा Typed同一个函数 — 逐步加上类型
from typing import Annotated
from fastapi import FastAPI, Path, Query
app = FastAPI()
# Level 0 - no hints: item_id arrives as a plain str
@app.get("/v0/items/{item_id}")
def get_item_v0(item_id):
return {"id": item_id, "type": type(item_id).__name__}
# Level 1 - a type hint: FastAPI now converts AND validates
@app.get("/v1/items/{item_id}")
def get_item_v1(item_id: int):
return {"id": item_id, "type": type(item_id).__name__}
# Level 2 - constraints with Path and Query
@app.get("/v2/items/{item_id}")
def get_item_v2(
item_id: Annotated[int, Path(gt=0, le=1000)],
q: Annotated[str | None, Query(max_length=10)] = None,
):
return {"id": item_id, "q": q}
# ---- try it (TestClient sends real HTTP requests to the app, no server needed) ----
from fastapi.testclient import TestClient
client = TestClient(app)
def show(r):
u = r.request.url
path = u.path + ("?" + u.query.decode() if u.query else "")
body = r.json() if r.content else "(empty)"
if r.status_code == 422:
body = [".".join(map(str, e["loc"])) + ": " + e["msg"] for e in body["detail"]]
print(f"{r.request.method:6} {path:30} -> {r.status_code} {body}")
show(client.get("/v0/items/42"))
show(client.get("/v1/items/42"))
show(client.get("/v1/items/abc"))
show(client.get("/v2/items/0"))
show(client.get("/v2/items/7?q=far-too-long-text"))
Level 0 handed you the text "42". Level 1 converted it to the number 42 and refused "abc". Level 2 also enforced limits — all before your function ran.
Level 0 ने आपको text "42" दिया। Level 1 ने उसे संख्या 42 में बदला और "abc" को ठुकराया। Level 2 ने सीमाएँ भी लागू कीं — सब आपका function चलने से पहले।
第 0 级给你的是文本 "42"。第 1 级把它转换成数字 42,并拒绝了 "abc"。第 2 级还强制了范围限制 — 全都发生在你的函数运行之前。
🧪 Simulator: Validation Playground🧪 Simulator: Validation Playground🧪 模拟器:校验游乐场
Pick a type and an input. Each result is FastAPI's real answer to GET /check?v=… — including a few surprises.
एक type और एक input चुनिए। हर result GET /check?v=… पर FastAPI का असली जवाब है — कुछ हैरानियों के साथ।
选择一种类型和一个输入。每个结果都是 FastAPI 对 GET /check?v=… 的真实回答 — 其中有几个出人意料。
Some details of the original table were wrong or missing. int also accepts "42.0" and spaces around the number. bool also accepts on/off. float accepts "nan" — and then the response crashes with a 500, because NaN cannot be written as JSON (add allow_inf_nan=False to stop it). date rejects impossible dates like 2026-02-30. EmailStr needs an extra package: pip install "pydantic[email]".
मूल table के कुछ विवरण ग़लत या अधूरे थे। int "42.0" और संख्या के आसपास spaces भी स्वीकार करता है। bool on/off भी लेता है। float "nan" स्वीकार करता है — और फिर response 500 से crash होता है, क्योंकि NaN JSON में लिखा नहीं जा सकता (इसे रोकने के लिए allow_inf_nan=False जोड़िए)। date 2026-02-30 जैसी असंभव तारीख़ें ठुकराता है। EmailStr को अतिरिक्त package चाहिए: pip install "pydantic[email]"।
原表的一些细节有误或缺失。int 还接受 "42.0" 以及数字两边的空格。bool 还接受 on/off。float 会接受 "nan" — 然后响应 以 500 崩溃,因为 NaN 无法写成 JSON(加上 allow_inf_nan=False 可以阻止)。date 会拒绝 2026-02-30 这样不存在的日期。EmailStr 需要额外的包:pip install "pydantic[email]"。
Visual Diagram — The Request Lifecycle 🔄चित्र — Request का जीवन-चक्र 🔄图解 — 请求生命周期 🔄
Every HTTP request travels through the same pipeline. Knowing each stage helps you put middleware, dependencies and error handling in the right place.
हर HTTP request एक ही pipeline से गुज़रती है। हर stage जानने से आप middleware, dependencies और error handling सही जगह रख पाते हैं।
每个 HTTP 请求都经过同一条流水线。了解每个阶段,有助于你把中间件、依赖和错误处理放在正确的位置。
httptools C parser when it is installed, otherwise the pure-Python h11.ASGI server (Uvicorn / Hypercorn) connection स्वीकार करता है और method, path, headers और body parse करता है। Installed हो तो Uvicorn तेज़ httptools C parser लेता है, वरना pure-Python h11।ASGI 服务器(Uvicorn / Hypercorn)接受连接,解析方法、路径、请求头和请求体。安装了 httptools 时 Uvicorn 使用这个快速的 C 解析器,否则使用纯 Python 的 h11。Depends(...) tree — DB sessions, auth checks, settings — caching each result for the request. Any dependency can raise HTTPException to stop.FastAPI आपका Depends(...) tree चलाता है — DB sessions, auth checks, settings — हर result request के लिए cache करते हुए। कोई भी dependency रुकने के लिए HTTPException raise कर सकती है।FastAPI 运行你的 Depends(...) 依赖树 — 数据库会话、认证检查、配置 — 并在本次请求内缓存每个结果。任何依赖都可以抛出 HTTPException 来中止。def endpoints run in a thread pool; async def endpoints run on the event loop. You get typed Python objects to work with.def endpoints thread pool में चलते हैं; async def endpoints event loop पर। आपको काम के लिए typed Python objects मिलते हैं।def 接口在线程池中运行;async def 接口在事件循环上运行。你拿到的是带类型的 Python 对象。response_model (if set), turned into JSON, and passes back out through the middleware.आपकी return value response_model (अगर लगा हो) से जाँची और छानी जाती है, JSON में बदली जाती है, और middleware से होकर बाहर जाती है।你的返回值会经 response_model(若已设置)检查和过滤,转换成 JSON,再经过中间件返回。(1) HTTP parsing is "pure C" only when httptools is installed; otherwise Uvicorn uses the pure-Python h11. (2) Middleware does not run in registration order: the last one added runs first. (3) Dependencies and validation happen in one step, and dependencies run first: in the simulator, a bad item_id still ran the auth dependency before the 422 was returned.
(1) HTTP parsing तभी "pure C" है जब httptools installed हो; वरना Uvicorn pure-Python h11 लेता है। (2) Middleware registration के क्रम में नहीं चलता: आख़िर में जोड़ा गया पहले चलता है। (3) Dependencies और validation एक ही step में होते हैं, और dependencies पहले चलती हैं: simulator में ग़लत item_id पर भी 422 लौटने से पहले auth dependency चली।
(1) 只有安装了 httptools 时,HTTP 解析才是"纯 C";否则 Uvicorn 使用纯 Python 的 h11。(2) 中间件 不是 按注册顺序运行的:最后添加的最先运行。(3) 依赖和校验在同一步中进行,而且依赖先运行:在模拟器中,即使 item_id 有误,认证依赖也在返回 422 之前就运行了。
🔄 Simulator: Request Lifecycle🔄 Simulator: Request Lifecycle🔄 模拟器:请求生命周期
One small app with a middleware, an auth dependency and a response_model. Pick a request and watch how far it gets. The log on the right is what the app really recorded.
एक छोटा app जिसमें एक middleware, एक auth dependency और एक response_model है। एक request चुनिए और देखिए वह कहाँ तक पहुँचती है। दाईं ओर का log वही है जो app ने सच में दर्ज किया।
一个小应用,带一个中间件、一个认证依赖和一个 response_model。选择一个请求,看看它能走多远。右侧的日志是应用真实记录的内容。
HTTP Methods — CRUD in FastAPI 🛠️HTTP Methods — FastAPI में CRUD 🛠️HTTP 方法 — FastAPI 中的 CRUD 🛠️
A REST API maps HTTP methods to data operations: Create, Read, Update, Delete (CRUD). FastAPI has one decorator per method.
REST API HTTP methods को data operations से जोड़ता है: Create, Read, Update, Delete (CRUD)। FastAPI में हर method के लिए एक decorator है।
REST API 把 HTTP 方法映射到数据操作:创建、读取、更新、删除(CRUD)。FastAPI 为每种方法提供一个装饰器。
| HTTP methodHTTP methodHTTP 方法 | DecoratorDecorator装饰器 | CRUD | Typical purposeआम उद्देश्य典型用途 | Body?Body?请求体? |
|---|---|---|---|---|
GET | @app.get() | Readपढ़ना读取 | Fetch one resource or a listएक resource या सूची लाना获取单个资源或列表 | Noनहीं否 |
POST | @app.post() | Createबनाना创建 | Create a new resourceनया resource बनाना创建新资源 | Yesहाँ是 |
PUT | @app.put() | Update (full)बदलना (पूरा)更新(整体) | Replace a whole resourceपूरा resource बदलना替换整个资源 | Yesहाँ是 |
PATCH | @app.patch() | Update (part)बदलना (हिस्सा)更新(部分) | Change some fieldsकुछ fields बदलना修改部分字段 | Yesहाँ是 |
DELETE | @app.delete() | Deleteमिटाना删除 | Remove a resourceResource हटाना删除资源 | Rarelyकभी-कभार很少 |
Complete CRUD API — In-Memory Item Storeपूरा CRUD API — In-Memory Item Store完整的 CRUD API — 内存商品库
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, Field
from uuid import UUID, uuid4
app = FastAPI(title="Item API", version="1.0.0")
# -- Models ---------------------------------------------
class ItemIn(BaseModel):
name: str = Field(min_length=1, max_length=80)
price: float = Field(gt=0)
stock: int = Field(default=0, ge=0)
class ItemOut(ItemIn):
id: UUID
# -- Fake in-memory DB ----------------------------------
DB: dict[UUID, ItemOut] = {}
# -- CREATE ---------------------------------------------
@app.post("/items", response_model=ItemOut, status_code=status.HTTP_201_CREATED)
def create_item(payload: ItemIn):
new = ItemOut(id=uuid4(), **payload.model_dump())
DB[new.id] = new
return new
# -- READ (list) ----------------------------------------
@app.get("/items", response_model=list[ItemOut])
def list_items(skip: int = 0, limit: int = 20):
return list(DB.values())[skip : skip + limit]
# -- READ (one) -----------------------------------------
@app.get("/items/{item_id}", response_model=ItemOut)
def get_item(item_id: UUID):
if item_id not in DB:
raise HTTPException(404, "Item not found")
return DB[item_id]
# -- UPDATE ---------------------------------------------
@app.put("/items/{item_id}", response_model=ItemOut)
def update_item(item_id: UUID, payload: ItemIn):
if item_id not in DB:
raise HTTPException(404, "Item not found")
DB[item_id] = ItemOut(id=item_id, **payload.model_dump())
return DB[item_id]
# -- DELETE ---------------------------------------------
@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_item(item_id: UUID):
if DB.pop(item_id, None) is None:
raise HTTPException(404, "Item not found")
Trying it with real requestsअसली requests से आज़माना用真实请求试一试
Notice DELETE answered 204 with an empty body, and the bad POST listed both problems at once. The ids are random uuid4() values, so they change on every run.
देखिए DELETE ने ख़ाली body के साथ 204 दिया, और ग़लत POST ने दोनों समस्याएँ एक साथ बताईं। ids random uuid4() values हैं, इसलिए हर run पर बदलते हैं।
注意 DELETE 返回了空响应体的 204,而错误的 POST 一次列出了 两个 问题。id 是随机的 uuid4() 值,每次运行都会变。
🗃️ Simulator: CRUD Lab🗃️ Simulator: CRUD Lab🗃️ 模拟器:CRUD 实验室
Step through ten real requests to the Item API above. The table on the right is the in-memory DB after each request.
ऊपर के Item API पर दस असली requests step-by-step देखिए। दाईं ओर की table हर request के बाद in-memory DB है।
逐步查看发给上面 Item API 的十个真实请求。右侧表格是每个请求之后的内存 DB。
Path and Query Parameters — Deep Dive 🔬Path और Query Parameters — गहराई से 🔬路径参数与查询参数 — 深入 🔬
Path parameter validationPath parameter validation路径参数校验
from enum import Enum
from typing import Annotated
from fastapi import FastAPI, Path
app = FastAPI()
class Category(str, Enum):
electronics = "electronics"
books = "books"
clothing = "clothing"
@app.get("/products/{category}/{product_id}")
def get_product(
category: Category, # only 3 allowed values
product_id: Annotated[int, Path(ge=1, le=999999, # must be 1..999999
title="Product ID",
description="Unique product identifier")],
):
return {"category": category, "product_id": product_id}
# ---- try it (TestClient sends real HTTP requests to the app, no server needed) ----
from fastapi.testclient import TestClient
client = TestClient(app)
def show(r):
u = r.request.url
path = u.path + ("?" + u.query.decode() if u.query else "")
body = r.json() if r.content else "(empty)"
if r.status_code == 422:
body = [".".join(map(str, e["loc"])) + ": " + e["msg"] for e in body["detail"]]
print(f"{r.request.method:6} {path:30} -> {r.status_code} {body}")
show(client.get("/products/books/42"))
show(client.get("/products/toys/42"))
show(client.get("/products/books/0"))
The Enum limited category to three words — and the error message even lists them. We switched the original Path(..., ge=1) to the Annotated form, to match the advice below.
Enum ने category को तीन शब्दों तक सीमित किया — और error message उन्हें गिनाता भी है। नीचे की सलाह से मेल खाने के लिए हमने मूल Path(..., ge=1) को Annotated रूप में बदला।
Enum 把 category 限定为三个词 — 错误信息甚至把它们都列了出来。我们把原来的 Path(..., ge=1) 改成了 Annotated 写法,与下面的建议保持一致。
Query parameter patternsQuery parameter patterns查询参数的常见写法
from typing import Annotated
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/search")
def search(
# Required query param
q: Annotated[str, Query(min_length=3, max_length=50)],
# Optional with default
limit: Annotated[int, Query(ge=1, le=100)] = 10,
# Boolean flag
exact: Annotated[bool, Query()] = False,
# List - accept ?tag=a&tag=b&tag=c
tag: Annotated[list[str] | None, Query()] = None,
# Regex validation
sort_by: Annotated[str, Query(pattern="^(name|price|date)$")] = "name",
):
return {"q": q, "limit": limit, "exact": exact,
"tag": tag or [], "sort_by": sort_by}
# ---- try it (TestClient sends real HTTP requests to the app, no server needed) ----
from fastapi.testclient import TestClient
client = TestClient(app)
def show(r):
u = r.request.url
path = u.path + ("?" + u.query.decode() if u.query else "")
body = r.json() if r.content else "(empty)"
if r.status_code == 422:
body = [".".join(map(str, e["loc"])) + ": " + e["msg"] for e in body["detail"]]
print(f"{r.request.method:6} {path:30} -> {r.status_code} {body}")
show(client.get("/search?q=laptop"))
show(client.get("/search?q=laptop&limit=5&exact=yes&tag=new&tag=sale&sort_by=price"))
show(client.get("/search?q=tv&limit=500&sort_by=rating"))
show(client.get("/search"))
exact=yes became True, and two tag= values became a list. The third request broke three rules at once, and FastAPI reported all three.
exact=yes True बना, और दो tag= values एक list बनीं। तीसरी request ने एक साथ तीन नियम तोड़े, और FastAPI ने तीनों बताए।
exact=yes 变成了 True,两个 tag= 值变成了列表。第三个请求同时违反了三条规则,FastAPI 把三条都报告了出来。
Since FastAPI 0.95, Annotated[Type, Query(...)] is the recommended style instead of x: str = Query(...). It keeps the type and the extra rules apart, works well with type checkers, and lets you reuse one annotation in many endpoints.
FastAPI 0.95 से x: str = Query(...) की जगह Annotated[Type, Query(...)] सुझाया गया तरीका है। यह type और अतिरिक्त नियमों को अलग रखता है, type checkers के साथ अच्छा चलता है, और एक annotation को कई endpoints में दोबारा इस्तेमाल करने देता है।
自 FastAPI 0.95 起,推荐用 Annotated[Type, Query(...)] 代替 x: str = Query(...)。它把类型和额外规则分开,与类型检查器配合良好,还能在多个接口中复用同一个注解。
Request Body with Pydantic Models 📦Pydantic Models के साथ Request Body 📦使用 Pydantic 模型的请求体 📦
Pydantic is FastAPI's validation engine. Declare a class that inherits from BaseModel, and every incoming JSON body is parsed, checked and turned into a typed Python object.
Pydantic FastAPI का validation engine है। BaseModel से बनी class घोषित कीजिए, और हर आने वाली JSON body parse, जाँची और typed Python object में बदली जाती है।
Pydantic 是 FastAPI 的校验引擎。声明一个继承自 BaseModel 的类,每个传入的 JSON 请求体都会被解析、检查,并变成带类型的 Python 对象。
from datetime import date
from fastapi import FastAPI
from pydantic import BaseModel, Field, EmailStr, HttpUrl, field_validator
app = FastAPI()
class Address(BaseModel):
street: str
city: str
postal_code: str = Field(pattern=r"^\d{5,6}$")
country: str = "IN"
class UserRegistration(BaseModel):
username: str = Field(min_length=3, max_length=20)
email: EmailStr # needs: pip install "pydantic[email]"
password: str = Field(min_length=8)
birth_date: date
website: HttpUrl | None = None
address: Address # nested model
tags: list[str] = []
@field_validator("password")
@classmethod
def password_strength(cls, v: str) -> str:
if not any(c.isupper() for c in v):
raise ValueError("Password needs at least one uppercase letter")
if not any(c.isdigit() for c in v):
raise ValueError("Password needs at least one digit")
return v
@app.post("/register")
def register(user: UserRegistration):
# user is fully validated by the time we get here
return {"status": "created", "user": user.model_dump(exclude={"password"})}
# ---- try it (TestClient sends real HTTP requests to the app, no server needed) ----
from fastapi.testclient import TestClient
client = TestClient(app)
def show(r):
u = r.request.url
path = u.path + ("?" + u.query.decode() if u.query else "")
body = r.json() if r.content else "(empty)"
if r.status_code == 422:
body = [".".join(map(str, e["loc"])) + ": " + e["msg"] for e in body["detail"]]
print(f"{r.request.method:6} {path:30} -> {r.status_code} {body}")
good = {"username": "mohit", "email": "mohit@example.com", "password": "Secret123",
"birth_date": "1990-07-18", "address": {"street": "1 Mall Rd", "city": "Baddi",
"postal_code": "173205"}}
show(client.post("/register", json=good))
bad = {"username": "a", "email": "not-an-email", "password": "weak",
"birth_date": "32-13-2020", "address": {"street": "x", "city": "y", "postal_code": "12"}}
show(client.post("/register", json=bad))
show(client.post("/register", json={**good, "password": "longenough"}))
What happens on invalid inputग़लत input पर क्या होता है输入无效时会发生什么
| FieldField字段 | Sentभेजा发送值 |
|---|---|
| username | "a" |
| "not-an-email" | |
| password | "weak" |
| birth_date | "32-13-2020" |
| address.postal_code | "12" |
| FieldField字段 | MessageMessage信息 |
|---|---|
| username | String should have at least 3 characters |
| value is not a valid email address: An email address must have an @-sign. | |
| password | String should have at least 8 characters |
| birth_date | Input should be a valid date or datetime… |
| address.postal_code | String should match pattern '^\d{5,6}$' |
The original said the error for "weak" would be "Needs uppercase + digit". In the real run it is String should have at least 8 characters: the min_length=8 rule fails first, so the custom @field_validator never runs. Your validator only sees values that already passed the built-in rules — the third request shows it working on "longenough".
मूल में कहा गया कि "weak" का error "Needs uppercase + digit" होगा। असली run में यह String should have at least 8 characters है: min_length=8 नियम पहले fail होता है, इसलिए custom @field_validator कभी नहीं चलता। आपका validator सिर्फ़ वे values देखता है जो built-in नियम पहले ही पास कर चुकी हों — तीसरी request "longenough" पर इसे चलता दिखाती है।
原文说 "weak" 的错误会是"需要大写字母和数字"。真实运行中却是 String should have at least 8 characters:min_length=8 规则先失败,所以自定义的 @field_validator 根本不会运行。你的校验器只会看到已通过内置规则的值 — 第三个请求展示了它对 "longenough" 生效的情况。
Response Models and Status Codes 🎯Response Models और Status Codes 🎯响应模型与状态码 🎯
A response_model gives three things at once: it checks your output, filters out fields that should not leave the server, and documents the response.
response_model एक साथ तीन चीज़ें देता है: आपका output जाँचता है, server से बाहर न जाने वाले fields छाँटता है, और response का दस्तावेज़ बनाता है।
response_model 同时带来三件事:检查你的输出、过滤掉不应离开服务器的字段,并为响应生成文档。
from fastapi import FastAPI, status
from pydantic import BaseModel, EmailStr
app = FastAPI()
# Two versions: one with the password, one without
class UserIn(BaseModel):
username: str
email: EmailStr
password: str # client sends this
class UserOut(BaseModel):
username: str
email: EmailStr # NO password field
@app.post("/users",
response_model=UserOut,
status_code=status.HTTP_201_CREATED,
summary="Register a new user",
tags=["users"])
def create_user(user: UserIn) -> UserIn:
# Return the whole UserIn (with password) - FastAPI filters it
# out because response_model=UserOut has no password field.
return user
@app.post("/users-unsafe", status_code=201)
def create_user_unsafe(user: UserIn):
return user # no response_model -> everything goes out
# ---- try it (TestClient sends real HTTP requests to the app, no server needed) ----
from fastapi.testclient import TestClient
client = TestClient(app)
def show(r):
u = r.request.url
path = u.path + ("?" + u.query.decode() if u.query else "")
body = r.json() if r.content else "(empty)"
if r.status_code == 422:
body = [".".join(map(str, e["loc"])) + ": " + e["msg"] for e in body["detail"]]
print(f"{r.request.method:6} {path:30} -> {r.status_code} {body}")
show(client.post("/users", json={"username": "ada", "email": "ada@x.io", "password": "S3cret!"}))
show(client.post("/users-unsafe", json={"username": "ada", "email": "ada@x.io", "password": "S3cret!"}))
Same input, two endpoints. With response_model=UserOut the password was removed; without it, the password went straight back to the client.
एक ही input, दो endpoints। response_model=UserOut के साथ password हट गया; उसके बिना password सीधे client को वापस चला गया।
同样的输入,两个接口。有 response_model=UserOut 时密码被去掉了;没有它,密码就直接返回给了客户端。
| Status codeStatus code状态码 | ConstantConstant常量 | When to useकब लें何时使用 |
|---|---|---|
200 | HTTP_200_OK | Default for a successful GET / PUT / PATCHसफल GET / PUT / PATCH का default成功的 GET / PUT / PATCH 的默认值 |
201 | HTTP_201_CREATED | A POST that created somethingकुछ बनाने वाला POST创建了资源的 POST |
204 | HTTP_204_NO_CONTENT | Success with no body (often DELETE)बिना body सफलता (अक्सर DELETE)成功但没有响应体(常用于 DELETE) |
400 | HTTP_400_BAD_REQUEST | A business rule the client can fixClient द्वारा सुधारने लायक business नियम客户端可以修正的业务规则错误 |
401 | HTTP_401_UNAUTHORIZED | Missing or invalid credentialsCredentials ग़ायब या ग़लत凭据缺失或无效 |
403 | HTTP_403_FORBIDDEN | Logged in, but not allowedLogin है, पर अनुमति नहीं已登录但无权限 |
404 | HTTP_404_NOT_FOUND | The resource does not existResource मौजूद नहीं资源不存在 |
422 | HTTP_422_UNPROCESSABLE_CONTENT | Validation failed (FastAPI does this for you)Validation fail (FastAPI आपके लिए करता है)校验失败(FastAPI 自动处理) |
500 | HTTP_500_INTERNAL_SERVER_ERROR | An unhandled error in your codeआपके code में न संभाला error代码中未处理的异常 |
The current HTTP standard (RFC 9110) calls 422 "Unprocessable Content", and recent Starlette versions warn: 'HTTP_422_UNPROCESSABLE_ENTITY' is deprecated. Use 'HTTP_422_UNPROCESSABLE_CONTENT' instead. The number 422 is the same.
मौजूदा HTTP मानक (RFC 9110) 422 को "Unprocessable Content" कहता है, और नए Starlette versions चेतावनी देते हैं: 'HTTP_422_UNPROCESSABLE_ENTITY' is deprecated. Use 'HTTP_422_UNPROCESSABLE_CONTENT' instead. संख्या 422 वही है।
当前的 HTTP 标准(RFC 9110)把 422 称为"Unprocessable Content",较新的 Starlette 版本会警告:'HTTP_422_UNPROCESSABLE_ENTITY' is deprecated. Use 'HTTP_422_UNPROCESSABLE_CONTENT' instead. 数字 422 本身不变。
Dependency Injection — FastAPI's Secret Weapon 🧩Dependency Injection — FastAPI का गुप्त हथियार 🧩依赖注入 — FastAPI 的秘密武器 🧩
Depends() works the same way: your endpoint says "I need a database session, the current user and the page settings", and FastAPI prepares them before your function starts.Depends() भी ऐसे ही काम करता है: आपका endpoint कहता है "मुझे database session, मौजूदा user और page settings चाहिए", और FastAPI आपका function शुरू होने से पहले उन्हें तैयार कर देता है।Depends() 也是这样:你的接口说"我需要数据库会话、当前用户和分页设置",FastAPI 就会在你的函数开始前把它们准备好。Shared dependencies are solved once and reused.
साझा dependencies एक बार हल होकर दोबारा इस्तेमाल होती हैं।
共享的依赖只解析一次并被复用。
from typing import Annotated
from fastapi import FastAPI, Depends, HTTPException, Header
app = FastAPI()
CALLS = {"get_current_user": 0}
# -- A simple dependency -------------------------------
def get_pagination(skip: int = 0, limit: int = 10):
return {"skip": skip, "limit": min(limit, 100)}
# -- A dependency with a sub-dependency ----------------
def verify_api_key(x_api_key: Annotated[str, Header()]): # reads header X-Api-Key
if x_api_key != "secret-key-123":
raise HTTPException(401, "Invalid API key")
return x_api_key
def get_current_user(api_key: Annotated[str, Depends(verify_api_key)]):
CALLS["get_current_user"] += 1 # count how often it really runs
return {"id": "u_42", "name": "Alice"}
def get_audit(user: Annotated[dict, Depends(get_current_user)]):
return f"audit for {user['name']}" # ALSO needs the current user
# -- Yield-based dependency (setup + cleanup) ----------
def get_db():
db = {"conn": "fake-db-session"} # real app: SessionLocal()
print(" db: opened")
try:
yield db # handed to the endpoint
finally:
print(" db: closed") # always runs, even on error
# -- Consuming all of them ------------------------------
@app.get("/orders")
def list_orders(
pagination: Annotated[dict, Depends(get_pagination)],
user: Annotated[dict, Depends(get_current_user)],
audit: Annotated[str, Depends(get_audit)],
db: Annotated[dict, Depends(get_db)],
):
return {"user": user["name"], "page": pagination, "audit": audit}
# ---- try it (TestClient sends real HTTP requests to the app, no server needed) ----
from fastapi.testclient import TestClient
client = TestClient(app)
def show(r):
u = r.request.url
path = u.path + ("?" + u.query.decode() if u.query else "")
body = r.json() if r.content else "(empty)"
if r.status_code == 422:
body = [".".join(map(str, e["loc"])) + ": " + e["msg"] for e in body["detail"]]
print(f"{r.request.method:6} {path:30} -> {r.status_code} {body}")
show(client.get("/orders?limit=500", headers={"X-Api-Key": "secret-key-123"}))
print("get_current_user ran", CALLS["get_current_user"], "time(s) for that request")
show(client.get("/orders", headers={"X-Api-Key": "wrong"}))
show(client.get("/orders"))
Both list_orders and get_audit need get_current_user — yet it ran once for that request. FastAPI reuses the result within one request, so a tree of dependencies does not repeat the same database lookup. (Use Depends(fn, use_cache=False) if you really need a fresh call.)
list_orders और get_audit दोनों को get_current_user चाहिए — फिर भी वह उस request के लिए एक बार चली। FastAPI एक request में result दोबारा इस्तेमाल करता है, इसलिए dependencies का tree वही database lookup नहीं दोहराता। (अगर सच में नई call चाहिए तो Depends(fn, use_cache=False) लीजिए।)
list_orders 和 get_audit 都需要 get_current_user — 但它在那次请求中只运行了 一次。FastAPI 在同一请求内复用结果,所以依赖树不会重复同样的数据库查询。(如果确实需要重新调用,用 Depends(fn, use_cache=False)。)
The original used SessionLocal, Session and app without defining or importing them. Above, a small fake session stands in for the database, and the prints show the yield dependency opening and closing around the request. Notice two more real details: with a wrong key the request stopped at verify_api_key (401) before the database was opened, and with a missing header FastAPI still reported the error for each place the header is needed.
मूल ने SessionLocal, Session और app को define या import किए बिना इस्तेमाल किया। ऊपर एक छोटा नक़ली session database की जगह है, और prints दिखाते हैं कि yield dependency request के आसपास कैसे खुलती और बंद होती है। दो और असली बातें देखिए: ग़लत key पर request database खुलने से पहले verify_api_key (401) पर रुक गई, और header न होने पर FastAPI ने हर उस जगह का error बताया जहाँ header चाहिए।
原文使用了 SessionLocal、Session 和 app,却没有定义或导入它们。上面用一个小小的假会话代替数据库,打印内容展示了 yield 依赖如何在请求前后打开和关闭。还有两个真实细节:密钥错误时,请求在打开数据库之前就停在了 verify_api_key(401);缺少请求头时,FastAPI 会为每个需要该请求头的地方分别报告错误。
Authentication with JWT (OAuth2) 🔐JWT (OAuth2) के साथ Authentication 🔐使用 JWT(OAuth2)认证 🔐
FastAPI includes OAuth2 helpers. Combined with PyJWT (to create and check tokens) and pwdlib (to hash passwords with Argon2), you get a solid login flow in about 60 lines. This is the setup the FastAPI documentation uses today.
FastAPI में OAuth2 helpers शामिल हैं। PyJWT (tokens बनाने और जाँचने के लिए) और pwdlib (Argon2 से passwords hash करने के लिए) के साथ लगभग 60 lines में मज़बूत login flow मिलता है। FastAPI documentation आज यही setup लेता है।
FastAPI 自带 OAuth2 辅助工具。配合 PyJWT(创建和校验令牌)和 pwdlib(用 Argon2 哈希密码),大约 60 行就能写出可靠的登录流程。这正是 FastAPI 文档如今采用的方案。
Log in once; send the token with every request.
एक बार login; हर request के साथ token भेजिए।
登录一次;每个请求都带上令牌。
import os
from datetime import datetime, timedelta, timezone
from typing import Annotated
import jwt # pip install pyjwt
from jwt.exceptions import InvalidTokenError
from pwdlib import PasswordHash # pip install "pwdlib[argon2]"
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
SECRET_KEY = os.environ.get("SECRET_KEY", "dev-only-change-me-" + "x" * 32)
ALGORITHM = "HS256"
TOKEN_TTL = timedelta(minutes=30)
password_hash = PasswordHash.recommended() # Argon2
oauth2 = OAuth2PasswordBearer(tokenUrl="/token")
app = FastAPI()
# Fake user DB - real app: query PostgreSQL etc.
USERS = {"alice": {"username": "alice", "hashed_pw": password_hash.hash("wonderland")}}
def authenticate(username: str, password: str):
user = USERS.get(username)
if not user or not password_hash.verify(password, user["hashed_pw"]):
return None
return user
def create_token(data: dict, ttl: timedelta = TOKEN_TTL) -> str:
payload = data.copy()
payload["exp"] = datetime.now(timezone.utc) + ttl
return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
@app.post("/token")
def login(form: Annotated[OAuth2PasswordRequestForm, Depends()]):
user = authenticate(form.username, form.password)
if not user:
raise HTTPException(401, "Wrong username or password")
return {"access_token": create_token({"sub": user["username"]}), "token_type": "bearer"}
def get_current_user(token: Annotated[str, Depends(oauth2)]):
creds_exc = HTTPException(status.HTTP_401_UNAUTHORIZED, "Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"})
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username = payload.get("sub")
except InvalidTokenError:
raise creds_exc
if username is None or username not in USERS:
raise creds_exc
return USERS[username]
@app.get("/me")
def read_me(user: Annotated[dict, Depends(get_current_user)]):
return {"username": user["username"]}
# ---- try it (TestClient sends real HTTP requests to the app, no server needed) ----
from fastapi.testclient import TestClient
client = TestClient(app)
def show(r):
u = r.request.url
path = u.path + ("?" + u.query.decode() if u.query else "")
body = r.json() if r.content else "(empty)"
if r.status_code == 422:
body = [".".join(map(str, e["loc"])) + ": " + e["msg"] for e in body["detail"]]
print(f"{r.request.method:6} {path:30} -> {r.status_code} {body}")
show(client.post("/token", data={"username": "alice", "password": "nope"}))
r = client.post("/token", data={"username": "alice", "password": "wonderland"})
token = r.json()["access_token"]
print("token:", token[:36] + "...", "| parts:", token.count(".") + 1)
show(client.get("/me", headers={"Authorization": f"Bearer {token}"}))
show(client.get("/me"))
old = create_token({"sub": "alice"}, ttl=timedelta(seconds=-1)) # already expired
show(client.get("/me", headers={"Authorization": f"Bearer {old}"}))
show(client.get("/me", headers={"Authorization": f"Bearer {token[:-3]}abc"})) # tampered
print("stored hash:", USERS["alice"]["hashed_pw"][:30] + "...")
The run shows the whole story: a wrong password gets 401, the right one gets a three-part token, /me works with it, and three bad cases fail — no token, an expired token, and a token whose last characters were changed.
Run पूरी कहानी दिखाता है: ग़लत password पर 401, सही पर तीन हिस्सों वाला token, उसके साथ /me चलता है, और तीन ग़लत मामले fail होते हैं — कोई token नहीं, expired token, और ऐसा token जिसके आख़िरी अक्षर बदले गए।
这次运行展示了完整过程:密码错误得到 401,密码正确得到三段式令牌,用它访问 /me 成功,三种错误情况都失败了 — 没有令牌、令牌过期、令牌末尾字符被篡改。
The original used python-jose and passlib. Both are barely maintained, and the FastAPI docs moved to PyJWT and pwdlib. With the current bcrypt 5 package, the original passlib code does not even start — here is the real run:
मूल ने python-jose और passlib लिए थे। दोनों का रख-रखाव लगभग बंद है, और FastAPI docs PyJWT और pwdlib पर आ गए। मौजूदा bcrypt 5 package के साथ मूल passlib code शुरू भी नहीं होता — यह असली run है:
原文使用了 python-jose 和 passlib。两者几乎都已无人维护,FastAPI 文档也已改用 PyJWT 和 pwdlib。在当前的 bcrypt 5 版本下,原来的 passlib 代码甚至无法启动 — 下面是真实运行结果:
# The original setup: passlib + bcrypt (bcrypt 5.x installed)
from passlib.context import CryptContext
pwd_ctx = CryptContext(schemes=["bcrypt"], deprecated="auto")
print(pwd_ctx.hash("wonderland"))
Generate a real key with openssl rand -hex 32 and load it from an environment variable or a secrets manager, as the code above does with os.environ.get("SECRET_KEY"). If the key leaks, anyone can create valid tokens. Also remember: a JWT payload is only signed, not encrypted — never put passwords or private data in it.
openssl rand -hex 32 से असली key बनाइए और उसे environment variable या secrets manager से load कीजिए, जैसा ऊपर का code os.environ.get("SECRET_KEY") से करता है। Key leak हुई तो कोई भी मान्य tokens बना सकता है। यह भी याद रखिए: JWT payload सिर्फ़ signed है, encrypted नहीं — उसमें passwords या निजी data कभी मत रखिए।
用 openssl rand -hex 32 生成真正的密钥,并从环境变量或密钥管理器加载,就像上面代码用 os.environ.get("SECRET_KEY") 那样。密钥一旦泄露,任何人都能伪造有效令牌。还要记住:JWT 的载荷只是 签名,并没有加密 — 千万不要把密码或隐私数据放进去。
Database Integration with SQLAlchemy 🗄️SQLAlchemy के साथ Database Integration 🗄️与 SQLAlchemy 集成数据库 🗄️
# file: database.py
from sqlalchemy import create_engine, Integer, String, Float
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, sessionmaker
DATABASE_URL = "sqlite:///./app.db"
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autoflush=False, bind=engine)
class Base(DeclarativeBase):
pass
class Item(Base):
__tablename__ = "items"
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
name: Mapped[str] = mapped_column(String, nullable=False)
price: Mapped[float] = mapped_column(Float, nullable=False)
stock: Mapped[int] = mapped_column(Integer, default=0)
Base.metadata.create_all(bind=engine)
# file: main.py
from typing import Annotated
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from pydantic import BaseModel, ConfigDict
app = FastAPI()
def get_db():
db = SessionLocal()
try: yield db
finally: db.close()
DB = Annotated[Session, Depends(get_db)]
class ItemIn(BaseModel):
name: str
price: float
stock: int = 0
class ItemOut(ItemIn):
model_config = ConfigDict(from_attributes=True) # read ORM objects directly
id: int
@app.post("/items", response_model=ItemOut, status_code=201)
def create(payload: ItemIn, db: DB):
item = Item(**payload.model_dump())
db.add(item)
db.commit()
db.refresh(item) # load the new id from the database
return item
@app.get("/items/{item_id}", response_model=ItemOut)
def read(item_id: int, db: DB):
item = db.get(Item, item_id)
if not item:
raise HTTPException(404, "Item not found")
return item
# ---- try it (TestClient sends real HTTP requests to the app, no server needed) ----
from fastapi.testclient import TestClient
client = TestClient(app)
def show(r):
u = r.request.url
path = u.path + ("?" + u.query.decode() if u.query else "")
body = r.json() if r.content else "(empty)"
if r.status_code == 422:
body = [".".join(map(str, e["loc"])) + ": " + e["msg"] for e in body["detail"]]
print(f"{r.request.method:6} {path:30} -> {r.status_code} {body}")
show(client.post("/items", json={"name": "Pen", "price": 1.5, "stock": 40}))
show(client.get("/items/1"))
show(client.get("/items/99"))
(1) The original used class Config: from_attributes = True. That is the Pydantic v1 style; Pydantic 2 warns Support for class-based config is deprecated, use ConfigDict instead. We use model_config = ConfigDict(from_attributes=True). (2) The response schema had no id, so a client could never learn the id of what it just created. We split ItemIn / ItemOut. (3) db.query(...).filter(...).first() is the legacy 1.x API; db.get(Item, item_id) is the SQLAlchemy 2.0 way to load by primary key.
(1) मूल ने class Config: from_attributes = True लिया। यह Pydantic v1 का तरीका है; Pydantic 2 चेतावनी देता है Support for class-based config is deprecated, use ConfigDict instead। हम model_config = ConfigDict(from_attributes=True) लेते हैं। (2) Response schema में id नहीं था, इसलिए client कभी नहीं जान पाता कि अभी बनाई चीज़ का id क्या है। हमने ItemIn / ItemOut अलग किए। (3) db.query(...).filter(...).first() पुराना 1.x API है; primary key से load करने का SQLAlchemy 2.0 तरीका db.get(Item, item_id) है।
(1) 原文使用了 class Config: from_attributes = True。这是 Pydantic v1 的写法;Pydantic 2 会警告 Support for class-based config is deprecated, use ConfigDict instead。我们改用 model_config = ConfigDict(from_attributes=True)。(2) 原响应模式中没有 id,客户端永远无法得知刚创建对象的 id。我们拆分成了 ItemIn / ItemOut。(3) db.query(...).filter(...).first() 是旧的 1.x API;按主键加载在 SQLAlchemy 2.0 中的写法是 db.get(Item, item_id)。
Plain def endpoints run in a thread pool, so a SQLite connection may be used by a different thread than the one that opened it. SQLite refuses that by default; check_same_thread=False allows it. Each request still gets its own session from get_db().
सादे def endpoints thread pool में चलते हैं, इसलिए SQLite connection को उसे खोलने वाले से अलग thread इस्तेमाल कर सकता है। SQLite default रूप से इसे मना करता है; check_same_thread=False इसकी अनुमति देता है। फिर भी हर request को get_db() से अपना session मिलता है।
普通的 def 接口在线程池中运行,所以 SQLite 连接可能被另一个线程使用,而不是打开它的那个线程。SQLite 默认拒绝这样做;check_same_thread=False 允许这样做。每个请求仍然通过 get_db() 获得自己的会话。
Middleware and CORS 🧅Middleware और CORS 🧅中间件与 CORS 🧅
Middleware wraps every request — useful for logging, timing, CORS and compression. Think of an onion: the request goes in through each layer, and the response comes back out through the same layers in reverse.
Middleware हर request को लपेटता है — logging, timing, CORS और compression के लिए उपयोगी। प्याज़ जैसा सोचिए: request हर परत से अंदर जाती है, और response उन्हीं परतों से उल्टे क्रम में बाहर आता है।
中间件包裹着每个请求 — 适合做日志、计时、CORS 和压缩。可以想象成一个洋葱:请求穿过每一层进入,响应再按相反顺序穿过同样的层返回。
The middleware added last is the outermost layer.
सबसे आख़िर में जोड़ा middleware सबसे बाहरी परत है।
最后添加的中间件是最外层。
import time
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware
app = FastAPI()
LOG = []
# -- CORS - allow these browser origins to call your API -
app.add_middleware(
CORSMiddleware,
allow_origins=["https://myapp.com", "http://localhost:3000"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# -- GZip - compress large responses -------------------
app.add_middleware(GZipMiddleware, minimum_size=1000)
# -- Custom middlewares (registered in this order) -----
@app.middleware("http")
async def add_timing_header(request: Request, call_next):
LOG.append("timing: in")
start = time.perf_counter()
response = await call_next(request)
response.headers["X-Process-Time"] = f"{time.perf_counter() - start:.4f}"
LOG.append("timing: out")
return response
@app.middleware("http")
async def log_requests(request: Request, call_next):
LOG.append("logger: in")
response = await call_next(request)
LOG.append("logger: out")
return response
@app.get("/data")
def data():
LOG.append("endpoint")
return {"numbers": list(range(400))} # a large-ish JSON body
from fastapi.testclient import TestClient
client = TestClient(app)
r = client.get("/data", headers={"Accept-Encoding": "gzip", "Origin": "http://localhost:3000"})
print("order :", " -> ".join(LOG))
print("status :", r.status_code)
print("gzip :", r.headers.get("content-encoding"), "| bytes on the wire:", r.headers.get("content-length"),
"| JSON size:", len(r.content))
print("CORS header :", r.headers.get("access-control-allow-origin"))
print("timing hdr :", "X-Process-Time" in r.headers)
r = client.options("/data", headers={"Origin": "https://evil.example",
"Access-Control-Request-Method": "GET"})
print("preflight from evil.example ->", r.status_code, r.text)
The original said middleware runs "in registration order on the way in". The real log above says otherwise: timing was registered first and logger second, yet logger: in came first. Each new middleware wraps everything added before it, so the last one added is the outermost. Plan your order with that in mind — for example, add a timing or logging middleware last so that it wraps everything else.
मूल में कहा गया कि middleware "अंदर जाते समय registration के क्रम में" चलता है। ऊपर का असली log कुछ और कहता है: timing पहले और logger बाद में register हुआ, फिर भी logger: in पहले आया। हर नया middleware पहले जोड़ी गई हर चीज़ को लपेटता है, इसलिए सबसे आख़िर में जोड़ा गया सबसे बाहर होता है। क्रम यह ध्यान में रखकर तय कीजिए — जैसे, timing या logging middleware को आख़िर में जोड़िए ताकि वह बाक़ी सबको लपेटे।
原文说中间件"进入时按注册顺序"运行。上面的真实日志说明并非如此:timing 先注册,logger 后注册,但先出现的却是 logger: in。每个新中间件都会包裹之前添加的所有内容,所以 最后添加的位于最外层。安排顺序时要考虑这一点 — 例如,把计时或日志中间件最后添加,让它包裹住其他所有内容。
The run also shows the other middlewares working: the 1,503-byte JSON travelled as 748 gzip bytes, the allowed origin got its CORS header, and a preflight request from an origin not on the list was refused.
Run बाक़ी middlewares को भी काम करते दिखाता है: 1,503-byte JSON 748 gzip bytes बनकर गया, अनुमति वाले origin को CORS header मिला, और सूची में न होने वाले origin की preflight request ठुकरा दी गई।
这次运行也展示了其他中间件的作用:1,503 字节的 JSON 以 748 字节的 gzip 形式传输,被允许的来源拿到了 CORS 响应头,而来自名单外来源的预检请求被拒绝。
Sync vs Async — When Speed Matters ⚡Sync बनाम Async — जब Speed मायने रखे ⚡同步 vs 异步 — 速度要紧时 ⚡
| Aspectपहलू方面 | Detailविवरण细节 |
|---|---|
| SignatureSignature签名 | def get_data(): |
| Runs inकहाँ चलता है运行于 | A thread pool (40 threads by default)Thread pool (default 40 threads)线程池(默认 40 个线程) |
| While waiting on I/OI/O के इंतज़ार में等待 I/O 时 | Blocks its own thread onlyसिर्फ़ अपना thread रोकता है只阻塞自己的线程 |
| Best forकिसके लिए适合 | Blocking libraries, CPU workBlocking libraries, CPU काम阻塞库、CPU 计算 |
| Examplesउदाहरण例子 | requests, sqlite3, Pillow |
| Aspectपहलू方面 | Detailविवरण细节 |
|---|---|
| SignatureSignature签名 | async def get_data(): |
| Runs inकहाँ चलता है运行于 | The event loop (one thread)Event loop (एक thread)事件循环(单线程) |
| While waiting on I/OI/O के इंतज़ार में等待 I/O 时 | await frees the loop for othersawait loop को दूसरों के लिए छोड़ता हैawait 让出事件循环给别人 |
| Best forकिसके लिए适合 | Many waits: HTTP calls, async DB driversकई इंतज़ार: HTTP calls, async DB drivers大量等待:HTTP 调用、异步数据库驱动 |
| Examplesउदाहरण例子 | httpx, asyncpg, aiofiles |
import asyncio, time
import requests, httpx
from fastapi import FastAPI
app = FastAPI()
# -- SYNC - FastAPI runs it in a thread pool ------------
@app.get("/weather-sync/{city}")
def weather_sync(city: str):
r = requests.get(f"https://api.example-weather.com/{city}", timeout=5)
return r.json()
# -- ASYNC - awaits, so the event loop stays free --------
@app.get("/weather-async/{city}")
async def weather_async(city: str):
async with httpx.AsyncClient(timeout=5) as client:
r = await client.get(f"https://api.example-weather.com/{city}")
return r.json()
# -- WRONG - a blocking call inside async def -----------
@app.get("/weather-broken/{city}")
async def weather_broken(city: str):
r = requests.get(f"https://api.example-weather.com/{city}", timeout=5) # blocks the loop!
return r.json()
Those endpoints call an outside service, so here is the same idea measured for real: each endpoint waits 0.2 s, and 50 requests arrive at once.
वे endpoints बाहरी service बुलाते हैं, इसलिए यहाँ वही बात असल में मापी गई है: हर endpoint 0.2 s इंतज़ार करता है, और 50 requests एक साथ आती हैं।
这些接口会调用外部服务,所以下面用真实测量来说明同样的道理:每个接口等待 0.2 秒,50 个请求同时到达。
import asyncio, threading, time
import httpx, uvicorn
from fastapi import FastAPI
app = FastAPI()
@app.get("/sync") # def -> runs in the thread pool
def sync_endpoint():
time.sleep(0.2) # pretend: a blocking I/O call (requests, sqlite3)
return {"ok": True}
@app.get("/async") # async def + await -> runs on the event loop
async def async_endpoint():
await asyncio.sleep(0.2) # pretend: an awaited I/O call (httpx, asyncpg)
return {"ok": True}
@app.get("/broken") # async def + BLOCKING call
async def broken_endpoint():
time.sleep(0.2) # blocks the whole event loop!
return {"ok": True}
server = uvicorn.Server(uvicorn.Config(app, port=8012, log_level="warning"))
threading.Thread(target=server.run, daemon=True).start()
time.sleep(1.5)
async def hammer(path, n=50):
async with httpx.AsyncClient(base_url="http://127.0.0.1:8012", timeout=60) as c:
t0 = time.perf_counter()
await asyncio.gather(*(c.get(path) for _ in range(n)))
return time.perf_counter() - t0
for path in ["/sync", "/async", "/broken"]:
print(f"50 requests at once to {path:8s}: {asyncio.run(hammer(path)):5.2f} s")
server.should_exit = True
The original headers promised "Sync — 100 req/sec" and "Async — 5000 req/sec", and said sync endpoints "block on I/O". In the real run, the def endpoint handled 50 waiting requests in about half a second — it blocks only its own thread, and FastAPI has a pool of them. The async version was a little faster. The real danger is the third case: a blocking call inside async def served the requests one by one, about 40 times slower.
मूल headers ने "Sync — 100 req/sec" और "Async — 5000 req/sec" का वादा किया, और कहा कि sync endpoints "I/O पर block" करते हैं। असली run में def endpoint ने 50 इंतज़ार करती requests लगभग आधे सेकंड में संभालीं — यह सिर्फ़ अपना thread रोकता है, और FastAPI के पास ऐसे threads का pool है। Async version थोड़ा तेज़ था। असली ख़तरा तीसरा मामला है: async def के अंदर blocking call ने requests एक-एक करके संभालीं, लगभग 40 गुना धीमे।
原文的标题宣称"同步 — 100 请求/秒""异步 — 5000 请求/秒",还说同步接口"会在 I/O 上阻塞"。真实运行中,def 接口约半秒就处理完 50 个等待中的请求 — 它只阻塞自己的线程,而 FastAPI 有一个线程池。异步版本稍快一些。真正的危险是第三种情况:async def 里的阻塞调用让请求只能一个接一个处理,慢了大约 40 倍。
⏱️ Simulator: Concurrency Lab⏱️ Simulator: Concurrency Lab⏱️ 模拟器:并发实验室
Each dot is one request finishing, placed at its real finish time. Each endpoint waits 0.2 s. Change how many requests arrive at once.
हर बिंदु एक पूरी हुई request है, अपने असली ख़त्म होने के समय पर। हर endpoint 0.2 s इंतज़ार करता है। बदलिए कि एक साथ कितनी requests आती हैं।
每个圆点代表一个请求完成,位于它真实完成的时间点。每个接口等待 0.2 秒。改变同时到达的请求数试试。
requests.get() or time.sleep() inside an async def endpoint blocks the whole event loop — every other request waits. Use the async library (httpx, asyncio.sleep), wrap the call in await asyncio.to_thread(...), or simply write the endpoint as def — FastAPI then runs it in the thread pool.
async def endpoint के अंदर requests.get() या time.sleep() पूरे event loop को रोक देता है — हर दूसरी request इंतज़ार करती है। Async library (httpx, asyncio.sleep) लीजिए, call को await asyncio.to_thread(...) में लपेटिए, या endpoint को बस def लिखिए — तब FastAPI उसे thread pool में चलाता है।
在 async def 接口中调用 requests.get() 或 time.sleep() 会 阻塞整个事件循环 — 其他所有请求都得等。请使用异步库(httpx、asyncio.sleep),或把调用包进 await asyncio.to_thread(...),或者干脆把接口写成 def — FastAPI 会在线程池中运行它。
Testing FastAPI Applications 🧪FastAPI Applications की Testing 🧪测试 FastAPI 应用 🧪
FastAPI includes TestClient, built on httpx. It sends real requests to your app in memory — no server needed. Pair it with pytest for a fast test loop. Below, main.py is the CRUD app from Section 06.
FastAPI में httpx पर बना TestClient शामिल है। यह memory में आपके app को असली requests भेजता है — server की ज़रूरत नहीं। तेज़ test loop के लिए इसे pytest के साथ लीजिए। नीचे main.py Section 06 का CRUD app है।
FastAPI 自带基于 httpx 的 TestClient。它在内存中向你的应用发送真实请求 — 不需要服务器。配合 pytest,测试循环又快又稳。下面的 main.py 就是第 06 节的 CRUD 应用。
# file: test_main.py
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def test_create_item():
payload = {"name": "Notebook", "price": 2.99, "stock": 100}
r = client.post("/items", json=payload)
assert r.status_code == 201
data = r.json()
assert data["name"] == "Notebook"
assert data["price"] == 2.99
assert "id" in data
def test_create_item_validation_error():
payload = {"name": "", "price": -1} # invalid
r = client.post("/items", json=payload)
assert r.status_code == 422
errors = r.json()["detail"]
assert len(errors) >= 2 # name + price both wrong
def test_get_missing_item_returns_404():
r = client.get("/items/00000000-0000-0000-0000-000000000000")
assert r.status_code == 404
assert r.json()["detail"] == "Item not found"
The original output showed "Python 3.11.4, pytest-8.0.0" — a typed example, not a run. The output above is real (only the run time is hidden, because it changes each run). The same TestClient powers every "try it" block on this page.
मूल output में "Python 3.11.4, pytest-8.0.0" दिखाया गया — एक लिखा हुआ उदाहरण, run नहीं। ऊपर का output असली है (सिर्फ़ run का समय छिपाया गया, क्योंकि वह हर बार बदलता है)। इस page का हर "try it" block इसी TestClient से चलता है।
原输出显示的"Python 3.11.4, pytest-8.0.0"只是手写的示例,并不是真实运行。上面的输出是真实的(只隐藏了运行耗时,因为每次都不同)。本页所有"try it"代码块都由同一个 TestClient 驱动。
FastAPI vs Flask vs Django REST ⚖️FastAPI बनाम Flask बनाम Django REST ⚖️FastAPI vs Flask vs Django REST ⚖️
| Propertyगुण特性 | FastAPI | Flask | Django REST |
|---|---|---|---|
| Async viewsAsync views异步视图 | Yes — native (ASGI)हाँ — मूल (ASGI)是 — 原生(ASGI) | Since 2.0, on top of WSGI2.0 से, WSGI के ऊपर自 2.0 起,基于 WSGI | Partialआंशिक部分支持 |
| Request validationRequest validation请求校验 | Pydantic — built inPydantic — शामिलPydantic — 内置 | Manual or an extensionहाथ से या extension手写或扩展 | SerializersSerializers序列化器 |
| Interactive docsInteractive docs交互式文档 | Free at /docs/docs पर मुफ़्त在 /docs 免费提供 | Extension neededExtension चाहिए需要扩展 | drf-spectacular (add-on)drf-spectacular (add-on)drf-spectacular(附加包) |
| Driven by type hintsType hints से चलता由类型提示驱动 | Yes — core designहाँ — मूल design是 — 核心设计 | Noनहीं否 | Noनहीं否 |
| Speed for JSON APIsJSON APIs की speedJSON API 速度 | Among the fastest in PythonPython में सबसे तेज़ मेंPython 中最快之一 | Goodअच्छी良好 | Goodअच्छी良好 |
| Learning curveसीखना学习曲线 | Gentleआसान平缓 | Very gentleबहुत आसान非常平缓 | Steeperकठिन较陡 |
| Best forकिसके लिए最适合 | APIs, microservices, ML servingAPIs, microservices, ML servingAPI、微服务、机器学习服务 | Small apps, quick prototypesछोटे apps, जल्दी prototypes小应用、快速原型 | Full websites + adminपूरी websites + admin完整网站 + 后台 |
| Bundled ORMसाथ में ORM内置 ORM | No — pick your ownनहीं — ख़ुद चुनिए无 — 自行选择 | Noनहीं否 | Django ORMDjango ORMDjango ORM |
| Bundled adminसाथ में admin内置后台 | Noनहीं否 | Noनहीं否 | Django AdminDjango AdminDjango Admin |
The original table gave "~30k / ~8k / ~6k req/s" with no source. Such numbers depend completely on hardware, server, workers and what each endpoint does, so we removed them. It also said Flask's async was "bolted on (3.0+)": Flask has supported async def views since 2.0; they run on WSGI, one request per worker thread.
मूल table ने बिना स्रोत के "~30k / ~8k / ~6k req/s" दिए। ऐसे numbers पूरी तरह hardware, server, workers और हर endpoint के काम पर निर्भर हैं, इसलिए हमने हटा दिए। इसमें यह भी कहा गया कि Flask का async "bolted on (3.0+)" है: Flask 2.0 से async def views support करता है; वे WSGI पर चलते हैं, हर worker thread पर एक request।
原表格给出了"约 30k / 8k / 6k 请求/秒",却没有来源。这类数字完全取决于硬件、服务器、worker 数量以及每个接口做什么,所以我们删除了它们。原表还说 Flask 的异步是"3.0+ 后补上的":Flask 从 2.0 起就支持 async def 视图;它们运行在 WSGI 上,每个工作线程处理一个请求。
Choose FastAPI for JSON APIs, microservices and ML model serving — validation and docs come for free. Choose Django when you need a full website with admin, auth and templates out of the box. Choose Flask for the smallest, most familiar stack when you do not need automatic docs or validation.
JSON APIs, microservices और ML model serving के लिए FastAPI चुनिए — validation और docs मुफ़्त मिलते हैं। जब admin, auth और templates के साथ पूरी website तुरंत चाहिए, तो Django चुनिए। जब अपने-आप docs या validation नहीं चाहिए, तो सबसे छोटे और जाने-पहचाने stack के लिए Flask चुनिए।
JSON API、微服务和机器学习模型服务选 FastAPI — 校验和文档免费附送。需要开箱即用的后台、认证和模板的完整网站,选 Django。不需要自动文档或校验、想要最小最熟悉的技术栈时,选 Flask。
Deployment — From Laptop to Production 🚢Deployment — Laptop से Production तक 🚢部署 — 从笔记本到生产环境 🚢
Production server: Gunicorn + Uvicorn workersProduction server: Gunicorn + Uvicorn workers生产服务器:Gunicorn + Uvicorn worker
# A tiny app to deploy
from fastapi import FastAPI
app = FastAPI()
@app.get("/health")
def health():
return {"status": "ok"}
The original used -k uvicorn.workers.UvicornWorker. Uvicorn now warns: The uvicorn.workers module is deprecated. Please use uvicorn-worker package instead. Install uvicorn-worker and use -k uvicorn_worker.UvicornWorker, as above. Many deployments do not need Gunicorn at all: fastapi run --workers 4 or uvicorn main:app --workers 4 start several worker processes on their own.
मूल ने -k uvicorn.workers.UvicornWorker लिया। Uvicorn अब चेतावनी देता है: The uvicorn.workers module is deprecated. Please use uvicorn-worker package instead. uvicorn-worker install कीजिए और -k uvicorn_worker.UvicornWorker लीजिए, जैसा ऊपर। कई deployments को Gunicorn की ज़रूरत ही नहीं: fastapi run --workers 4 या uvicorn main:app --workers 4 ख़ुद कई worker processes शुरू करते हैं।
原文使用了 -k uvicorn.workers.UvicornWorker。Uvicorn 现在会警告:The uvicorn.workers module is deprecated. Please use uvicorn-worker package instead. 请安装 uvicorn-worker 并使用 -k uvicorn_worker.UvicornWorker,如上所示。很多部署根本不需要 Gunicorn:fastapi run --workers 4 或 uvicorn main:app --workers 4 自己就能启动多个 worker 进程。
DockerfileDockerfileDockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["fastapi", "run", "main.py", "--port", "8000", "--workers", "4"]
pydantic-settings makes this typed and easy.pydantic-settings इसे typed और आसान बनाता है।pydantic-settings 能让这件事带类型且简单。/health endpoint. Load balancers and Kubernetes use it to check your app./health endpoint रखिए। Load balancers और Kubernetes आपके app की जाँच इसी से करते हैं।/health 接口。负载均衡器和 Kubernetes 用它来检查你的应用。/docs and /redoc in production with FastAPI(docs_url=None, redoc_url=None).FastAPI(docs_url=None, redoc_url=None) से /docs और /redoc छिपा सकते हैं।FastAPI(docs_url=None, redoc_url=None) 隐藏 /docs 和 /redoc。When to Use FastAPI 🧭FastAPI कब लें 🧭何时使用 FastAPI 🧭
def endpoints or worker processes.)def endpoints या worker processes लीजिए।)def 接口或 worker 进程。)def endpoints, but Flask may be the simpler choice.def endpoints के साथ चलता है, पर Flask आसान विकल्प हो सकता है।def 接口工作,但 Flask 可能更简单。Bug Doctor, Golden Rules and Quiz 🏆Bug Doctor, सुनहरे नियम और Quiz 🏆Bug 医生、黄金法则与小测验 🏆
🩺 Simulator: Bug Doctor🩺 Simulator: Bug Doctor🩺 模拟器:Bug 医生
Six real FastAPI mistakes. Buggy code on the left, fixed code on the right, changed lines marked. Every output is from a real run.
छह असली FastAPI ग़लतियाँ। बाईं ओर buggy code, दाईं ओर ठीक किया हुआ, बदली lines चिह्नित। हर output असली run से है।
六个真实的 FastAPI 错误。左边是有 bug 的代码,右边是修正后的代码,改动的行已标出。每个输出都来自真实运行。
dict body loses validation, docs and editor help all at once.dict body से validation, docs और editor की मदद एक साथ खो जाती है।dict 做请求体,会同时失去校验、文档和编辑器提示。Annotated[Type, Query(...)] over the older default-value style — and remember that lists and dicts need Query() to be read from the URL.Annotated[Type, Query(...)] लीजिए — और याद रखिए कि lists और dicts को URL से पढ़ने के लिए Query() चाहिए।Annotated[Type, Query(...)],而不是旧的默认值写法 — 并记住,列表和字典要用 Query() 才会从 URL 中读取。async def. Mixing def and async def endpoints in one app is fine. Inside async def, only await non-blocking calls; for blocking libraries write a def endpoint or use asyncio.to_thread().async def के अंदर कभी block न करें। एक app में def और async def endpoints मिलाना ठीक है। async def के अंदर सिर्फ़ non-blocking calls को await कीजिए; blocking libraries के लिए def endpoint लिखिए या asyncio.to_thread() लीजिए।async def 中阻塞。同一个应用里混用 def 和 async def 接口没有问题。在 async def 中只 await 非阻塞调用;遇到阻塞库就写成 def 接口,或使用 asyncio.to_thread()。response_model (or a return type) for every public endpoint. It filters out fields like passwords and locks the shape of your response.response_model (या return type) लगाइए। यह passwords जैसे fields छाँटता है और आपके response का आकार तय करता है।response_model(或返回类型)。它会过滤掉密码之类的字段,并锁定响应的结构。Depends() for anything reusable — DB sessions, auth, pagination, settings. Dependencies are cached per request and easy to replace in tests.Depends() लीजिए — DB sessions, auth, pagination, settings। Dependencies हर request पर cache होती हैं और tests में आसानी से बदली जा सकती हैं।Depends() — 数据库会话、认证、分页、配置。依赖按请求缓存,测试时也容易替换。APIRouter(prefix="/users", tags=["users"]) in its own file, added with app.include_router(users.router). Put fixed paths like /users/me before /users/{id}.APIRouter(prefix="/users", tags=["users"]), app.include_router(users.router) से जोड़ा गया। /users/me जैसे तय paths को /users/{id} से पहले रखिए।APIRouter(prefix="/users", tags=["users"]),用 app.include_router(users.router) 引入。把 /users/me 这样的固定路径放在 /users/{id} 之前。fastapi run --workers N, or Gunicorn with uvicorn_worker.UvicornWorker. Let the proxy or load balancer handle TLS.fastapi run --workers N, या uvicorn_worker.UvicornWorker के साथ Gunicorn। TLS को proxy या load balancer संभालने दीजिए।fastapi run --workers N,或使用 uvicorn_worker.UvicornWorker 的 Gunicorn。让代理或负载均衡器处理 TLS。Rule 1 said skipping hints "turns FastAPI into a slow Flask" — the real effect is that values arrive as strings without checks (Section 04). Rule 4 said "choose async or sync per endpoint, never mix", which confused two ideas: mixing endpoint styles is fine, but blocking inside async def is not (Section 14). Rule 8 used the deprecated uvicorn.workers module (Section 17).
नियम 1 ने कहा कि hints छोड़ने से "FastAPI धीमा Flask बन जाता है" — असली असर यह है कि values बिना जाँच के strings बनकर आती हैं (Section 04)। नियम 4 ने कहा "हर endpoint के लिए async या sync चुनिए, कभी न मिलाइए", जिसने दो बातें उलझा दीं: endpoint शैलियाँ मिलाना ठीक है, पर async def के अंदर block करना नहीं (Section 14)। नियम 8 ने deprecated uvicorn.workers module लिया (Section 17)।
规则 1 说省略类型提示会"把 FastAPI 变成慢吞吞的 Flask"— 实际后果是值以字符串形式到达且不经检查(第 04 节)。规则 4 说"每个接口选异步或同步,绝不混用",把两件事混为一谈:混用接口风格没问题,但在 async def 中阻塞不行(第 14 节)。规则 8 使用了已弃用的 uvicorn.workers 模块(第 17 节)。
Quick Quiz — 8 Questionsछोटा Quiz — 8 सवाल小测验 — 8 道题
FastAPI turns type hints into parsing, validation, docs and JSON output. Path, query and body parameters are chosen from your signature; Pydantic models check bodies and filter responses; Depends() shares setup like auth and DB sessions. Keep async def free of blocking calls, test with TestClient, and deploy several workers behind a proxy.
FastAPI type hints को parsing, validation, docs और JSON output में बदलता है। Path, query और body parameters आपके signature से चुने जाते हैं; Pydantic models bodies जाँचते और responses छाँटते हैं; Depends() auth और DB sessions जैसा setup साझा करता है। async def को blocking calls से दूर रखिए, TestClient से test कीजिए, और proxy के पीछे कई workers deploy कीजिए।
FastAPI 把类型提示变成解析、校验、文档和 JSON 输出。路径、查询和请求体参数由函数签名决定;Pydantic 模型校验请求体并过滤响应;Depends() 共享认证、数据库会话等准备工作。让 async def 远离阻塞调用,用 TestClient 测试,并在代理后面部署多个 worker。