昨天的 branch 我們把
uvicorn
包裝成run.py
,並且加上.env
方便設定環境變數 可以直接透過python run.py --<mode>
來啟動 FastAPI 但我們還沒有講解main.py
中 FastAPI 到底做了什麼事情 !
在昨天的 main.py
中,我們其實只有做兩件事情
- 建立 FastAPI app 的 instance
- 定義 API
- HTTP method
- Python Typing
- Path parameter Typing
- Query parameter Typing
app
是一個 FastAPI 的 instance,我們可以由這個 instance 中定義我們所有的 API
並且 uvicorn
也是透過 import 這個 instance 來啟動 server
from fastapi import FastAPI
app = FastAPI()
如果有寫過 express
或是 flask
的朋友,應該對這個語法不陌生
@app.get("/")
這個 decorator 代表這個 function 會來處理進入 /
的 GET
request
@app.get("/")
def hello_world():
return "Hello World"
而另一段
@app.get("/users/{user_id}")
def get_users(user_id: int, qry: str = None):
return {"user_id": user_id, "query": qry }
代表這個 function 會來處理進入 /users/{user_id}
的 GET
request
並且 user_id
是一個 path parameter
, qry
是一個 query parameter
除了 get
之外,如果要使用其他 HTTP method ,可以使用以下的語法
@app.post("/route")
@app.put("/route")
@app.delete("/route")
@app.patch("/route")
@app.HTTP_METHOD("/route")
...etc
在 FastAPI 中,了解 Python Typing 是非常重要的一環
( 會是使用 Typing
可以說已經會 50% FastAPI 了 )
可以在 Day04 : syntax_review/typing.py 中看到範例
我們先來看一下為 Python function 加上 Typing 的好處
先來看一些範例 :
def add(x, y):
return x + y
def merge(a, b):
return f"{a} with {b}"
def insert(user , db ):
return db.insert(user)
以 add
來說,我們可以看到這個 function 的功能是將兩個變數相加
但 x
和 y
可能是 int
str
float
甚至是 list
而 function 的定義很簡單,所以我們用起來不太會有問題
但是來看一下 insert
這個 function ,我們可以看到這個 function 的功能是將 user
insert 到 db
中
但我們看不出來 user
是 dict
class
list
還是 str
同樣的 db
可能是 session
connection
class
等
在沒有加上 typing
的情況下,我們容易在使用這個 function 的時候出錯
- 在 function 的入參數加上 Typing
可以透過<var_name> : <type>
來定義 - 在 function 的回傳值加上 Typing
可以透過<fun_name> -> <type>
來定義
加上 Typing 後的 function 如下 :
def add(x: int, y: int) -> int:
return x + y
def merge(a: str, b: str) -> str:
return f"{a} with {b}"
def insert(user: dict , db: Session) -> None:
return db.insert(user)
在 FastAPI 中,我們可以透過 typing
來定義 API function 中
path parameter
、 query parameter
的型別
我們先來比較一下有沒有加上 Typing 的差別
# 有加上 Typing
@app.get("/users/{user_id}")
def get_users(user_id: int, qry: str = None):
return {"user_id": user_id, "query": qry }
# 沒有加上 Typing
@app.get("/items/{item_id}")
def get_items_without_typing(item_id, qry):
return {"item_id": item_id, "query": qry }
啟動 FastAPI 後,看一下 Swagger UI
可以看到 user_id
是一個 int
, query
是一個 str
如果我們 user_id
是用 string
在 Swagger UI 中會報錯
如果是使用 curl
來呼叫 API 會回傳 422 Unprocessable Entity
我們可以在 response
中看到 detail
中有 msg
代表 user_id
應該要是 int
能夠及時在定義 router
的時候就發現這個問題 !
可以看到 item_id
和 query
並沒有特別說型態
當我們在接完 DB 之後的測試階段才遇到報錯
就會很難 Debug 了 !
再次觀察剛剛兩個 API 的 Swagger UI 和 code
user :
@app.get("/users/{user_id}")
def get_users(user_id: int, qry: str = None):
return {"user_id": user_id, "query": qry }
item :
@app.get("/items/{item_id}")
def get_items_without_typing(item_id, qry):
return {"item_id": item_id, "query": qry }
可以看到 user 的 query parameter 有 = None
,但 item 的 query parameter 沒有
這代表 item 的 query parameter 是必填的,但 user 的 query parameter 可以不填
如果 item 的 query parameter 不填也會報 422 Unprocessable Entity
剛剛加上的 Typing 是用來定義 path parameter
和 query parameter
的型別
那如果我們想要定義 request body
的型別呢 ?
我們會在明天談到 FastAPI 中由 pydantic
提供
另一個也很重要的概念: Schema !