跳到正文
DEV Community· kestrelfield·· 4 小時前AI 評分52

FastAPI 如何以型別提示建立具輸入檢查與文件的 API

How FastAPI turns type hints into a checked, documented API

AI 導讀

FastAPI 會根據 Python 型別提示建立 API 端點,並用同一組提示驗證輸入、提供編輯器提示及產生 API 文件。示例中,Pydantic 會把網址中的 5 轉成整數;若輸入 foo,則回傳 422,指出欄位和錯誤原因,文件則由同一套提示生成,並提供於 /docs、/redoc 和 /openapi.json。

正文

I follow one request through FastAPI to show how one set of type hints drives the input checks, the editor help and the docs at /docs.

A note on how this was written: an AI writing assistant drafted it from the sources listed at the end, and every claim was checked against those sources before it went out.

At a glance

  • You write a Python function and note the type of each input. FastAPI turns it into a web address that checks what comes in.
  • A wrong type gets a clear error before your code runs, and the same notes build the docs page.
  • FastAPI keeps nothing between requests, so more traffic means more copies of the app.

What FastAPI is for

FastAPI is a way to write a web API (a fixed way for one program to ask another for something, over the web) in Python. You write ordinary functions with type hints (notes in the code that say what type a value is, like name: str). FastAPI turns each one into an endpoint (one web address of the API) that checks its input, sends back JSON (a plain text format for data) and documents itself.

It is for anyone building in Python who needs an API that a web page, a phone app or another program can call. The docs also call it a good match for data science and machine learning APIs.

It works like a clinic's front desk with one paper form. The form says what each visitor must fill in, the desk checks it before anyone sees the doctor, and a copy hangs on the wall. Your type hints are that form: one declaration drives the checks, the help your code editor gives you, and the docs.

One request, step by step

I'll follow one request through the small example app in the FastAPI README (the front page of its GitHub project). Its function, read_item, sits under @app.get("/items/{item_id}"), a decorator (a line on top of a function that tells FastAPI which address it answers). The type hints say item_id is an int (a whole number) and q is optional text. The request asks for /items/5?q=somequery.

First, the request reaches Uvicorn (a fast server program that listens for requests). Uvicorn is a separate program, and FastAPI is the framework (code that calls your functions for you) that it runs. The two talk through ASGI (the standard plug between a Python web app and its server, made for async code, meaning code that can do other work while it waits).

Next, Starlette (a small, fast toolkit for Python web apps) does the routing (finding the function that owns this address) and handles the request and the reply. The FastAPI class is a subclass of Starlette's (built directly on top of it).

Then Pydantic (a Python library that checks and converts data using type hints) reads the hints. The 5 arrives as text, and Pydantic turns it into the number 5. Had the address said /items/foo, Pydantic would refuse it with a 422 (the status code for 'I understood the request, but the data in it is not valid'), naming the field and the reason. Your function would never run.

If read_item had dependencies (small functions that hand their results to your function), FastAPI would run them first. You attach one with Depends(). It is the place for shared steps, such as a database connection or the current user, written once. Only then does read_item run and return a dict (a set of names and values).

Where the waiting happens

An async def function (Python's mark for a function that can pause while it waits) runs on the event loop (the part of an async program that switches between tasks while each one waits). A plain def function (an ordinary Python function) runs in a thread pool (a few worker threads kept ready to run blocking functions without stopping the main loop). Either way, a function that waits on a disk or a network does not stop the server.

The docs explain this with a burger shop. A good cashier takes your order, gives you a number, and takes the next order while your burgers cook. The event loop is that cashier.

The trap is a slow blocking call (one that holds its thread until the answer comes back) inside async def. The docs note that most database libraries can't be called with await (the word that lets Python do other work while a call waits). Calling one from async def is like the cashier walking into the kitchen to cook your burgers while the line waits. It holds up every other request on that worker (one running copy of the app).

The fix is an async driver (a database library that supports await), or plain def, so the call runs in the thread pool. The docs' rule of thumb: if you don't know, use normal def.

What it keeps, and what goes out

FastAPI keeps nothing by itself between requests. If the app needs to remember anything, you bring the database.

Three things go out. First, the reply: FastAPI sends the dict back as JSON, {"item_id": 5, "q": "somequery"}. Second, an OpenAPI schema (the open standard file that describes every endpoint of an API, its inputs and its outputs), built from the same type hints and served at /openapi.json. Test tools and client generators (tools that write the code for calling an API) read it too.

Third, two docs pages built from that file: Swagger UI (a web page that lists an API's endpoints and lets you try them) at /docs, and ReDoc (a second docs page from the same file) at /redoc. Because the docs come from the same hints that check the data, they match the code.

The choices behind it, and ten times the traffic

The docs' alternatives page walks through the tools FastAPI learned from. Four choices stand out, each with a price.

Build on Starlette instead of writing a new web layer, since a fast, tested ASGI toolkit already handled routing, requests and responses. The cost: FastAPI moves with Starlette's releases.

Make standard type hints, read by Pydantic, the one source, instead of describing the data again in special classes or in notes inside each function. The docs name the old risk: change a parameter, forget the note, and the generated schema goes out of date. With one declaration, the checks, the editor help and the docs can't drift apart. The cost: your code needs type hints wherever it matters.

Use OpenAPI, with Swagger UI and ReDoc, instead of a custom schema, because other tools already read OpenAPI. The cost: whatever OpenAPI can't describe needs extra care.

Use dependency injection (the Depends() system) instead of copying setup into each endpoint. There is less repetition, and a dependency is easy to swap in tests. The cost is one more idea to learn.

At ten times the traffic, I'd run more worker processes (full copies of the app, side by side; Uvicorn has a --workers option) or more copies behind a load balancer (a server that spreads requests across copies). FastAPI keeps nothing between requests by itself, so copies are easy. It is like opening more checkout lanes: no lane needs to know about the last shopper. First, though, I'd hunt for any blocking call inside async def.

Check it yourself

First, install it. The README uses uv (a tool that installs Python packages): uv add "fastapi[standard]". With pip (the usual Python package installer), the README says to install fastapi[standard] inside a virtual environment (a private set of packages for one project).

Second, make main.py from the README's example. It imports FastAPI, sets app = FastAPI(), and has two functions: one answers the root address / with {"Hello": "World"}, and read_item takes item_id: int and q: str | None = None.

Third, run uv run fastapi dev in that folder. It finds the app in main.py and starts Uvicorn. Look for the line Uvicorn running on http://127.0.0.1:8000.

Fourth, open http://127.0.0.1:8000/items/5?q=somequery in a browser. You should see {"item_id": 5, "q": "somequery"}. Change the 5 to foo, and the reply becomes a 422 error that names item_id and the reason.

Last, open http://127.0.0.1:8000/docs, click Try it out on the items endpoint, fill in item_id and click Execute. Then look at /redoc, and at /openapi.json, the raw file both pages are built from.

The full sheet

The full sheet, with every box and decision, is on the Atlas. If you want one for a project you use or are building, ask for a sheet.

When you write a FastAPI endpoint, do you start with async def or plain def, and why?

Sources


More free tools for data and AI: tabrizianalytics.com

來源:DEV Community · dev.to