Python新手的FastAPI之旅3:FastAPI建構路由
回顧一下,到目前為止我們已經對FastAPI有了初步的認識,我們曾經介紹了如何輕鬆掌握它的基本特性,並開始FastAPI的學習旅程。
還記得上一篇曾經討論過的說明文件和SwaggerUI的那些重要元素嗎?例如「狀態碼」、「標籤」、「摘要」、「描述」等,這些都是我們一步步探索FastAPI的足跡。
接下來,我們將進入到一個新的領域,那就是「路由」。當我們的應用程式像雪球一樣越滾越大,需要更多更豐富的功能時,「路由」就成了我們的好夥伴,它可以將程式碼切分為各種小模組,如此一來,我們就能更有效地管理程式碼了。
為了讓大家能夠更容易理解路由,我們將會在這篇文章中深入解析,看看「路由」到底是什麼,它在FastAPI裡有哪些不可或缺的角色。此外,我們也會介紹如何在FastAPI裡使用路由,讓您的程式架構更加清晰。最後,當我們的應用程式需要更多的功能,我們又該如何在已有的路由下,增加更多的端點呢?這一切,都會在本篇文章中詳細揭曉。
讓我們一起揭開「路由」的神秘面紗,探索它在FastAPI應用程式中的重要角色。準備好了嗎?讓我們馬上開始吧!
何謂路由
「路由」究竟是什麼?
想像一下現實世界的道路。當我們想要從家裡移動到學校,平時應該會走一條特定的路線,這條路線就像是路由,指引我們如何從一個地點到達另一個地點。在程式設計中,「路由」也是一樣的概念。
在網路的世界中,路由就像是一個路標,它告訴程式應該如何把使用者的請求(request)導向正確的處理程序。換句話說,當您在網頁上點擊一個連結或送出一個表單時,這個請求就會被送到伺服器,然後路由就會根據這個請求來決定該如何處理。
FastAPI中的路由
FastAPI中的路由是透過裝飾器(Decorator)的方式來定義的。裝飾器是Python的一種語法,可以讓工程師在不修改函式定義的情況下,增加函式的功能。在FastAPI中,我們通常會在函式上方加上一行裝飾器,用來定義該函式對應的路由。
舉例來說,假設我們有一個用來顯示首頁的函式,可以寫成下面的程式碼:
@app.get("/")
def read_root():
return {"Hello": "World"}
在這個例子中,@app.get(“/”)
就是一個裝飾器,它告訴FastAPI,當使用者造訪網站的根目錄(也就是”/”
)時,應該執行read_root
這個函式。
所以,當我們談到路由,就是在談論如何將使用者的請求導向正確的處理函式。每個路由都對應到一個特定的函式,而這個函式就是用來處理使用者的請求,並返回結果。
當路由變多時
如果您的API只有少數幾個路由的話,僅透過上面方式處理,放在同一個檔案中,並不會有什麼問題。然而,當您的業務蒸蒸日上,家大業大,服務的端點越來越多的時候,可以想像的是,這個存放路由的檔案,將會越來越膨脹越來越肥大。
因此引申出下一個問題:亦即當API越來越大的時候,該如何處理才可以讓眾多的路由們可以井然有序的排排站?在FastAPI中我們要怎麼來實現這些呢?以下分成兩個步驟來說明。
重構的第一步
以下是拆分路由的方式:可以依照應用程式的功能來拆分路由。例如:我們依照性質將應用程式拆分為blog、news、user、product四個大單元,每個單元裡面又分為兩種不同功能的路由(一種是get,另一種是post)。
以下,將基於上面的假設來說明路由的拆分方式。我們可以透過路由前綴來處理。
以blog來說,有一個叫作「blog」的路由前綴。這表示在這條路由下的所有動作都會以斜線和 'blog' 這個字作為開頭(就是這樣:prefix='/blog'
)。因此,所有的動作都會被歸在 blog
這個路由前綴下。換句話說,上面的blog_get與blog_post都會被歸在blog
前綴下。
對此,我們會以下面的程式碼來實現它:
router = APIRouter(prefix='/blog', tags=['blog'])
此外,也可以用標籤(tags=['blog']
),再把這些動作分成不同的類別,這樣的分類可以在 SwaggerUI 上面看到。
然後,我們就可以像以前一樣,在這個路由上面定義所有的動作,像這樣:
@route.get('/')
所以,你的程式碼可能會像下面這樣:
from fastapi import APIRouter
router = APIRouter(prefix='/blog', tags=['blog'])
@route.get('/')
def index():
return ....
@route.get('/all_blog')
def get_all_blog():
return ....
要透過網路來造訪這個路由時,需要連的位置已經不是原來的 http://localhost/ 了,而是加上前綴blog的新位置。(前面的localhost可以替換為您的伺服器url位置)
http://localhost/blog/
或者是
http://localhost/blog/all_blog
重構的第二步
完成上面的步驟後,還無法透過網際網路來造訪這些路由。因為這些路由還沒有與我們的主程式聯結在一起。
接下來,我們要把它加入到您的app中。在主程式碼檔案(檔案裡面有宣告FastAPI實例者,例如:main.py
裡面有 app = FastAPI()
)中,您需要從路由的資料夾中將路由模組匯入。
# 從router中匯入blog
from router import blog
我們在這裡匯入了 'blog' 模組,然後透過 include_router
的方式把它加入到我們的應用程式app中。在這裡,我們的blog router就與主程式完成聯結。
app.include_router(blog.router)
你會看到像下面這樣的程式碼:
from router import blog
app = FastAPI()
app.include_router(blog.router)
透過上述的兩個步驟,就可以完成路由拆分的動作。
實作1:原程式改寫
現在,我們可以用剛才學到的方法,實際改寫自己的程式碼了。把您程式碼中的各個路由,分成不同的檔案和元件。透過這樣的方式,可以更容易地管理您的應用程式。例如,可以把已經建立的相關端點(end point),歸類在同一個資料夾裡面。若採用這樣的結構,檔案管理起來會比較清楚與方便。
1.建立資料夾與檔案
基於這個想法,我們先建立一個router資料夾,在這個資料夾裡面建立第一個檔案blog_get.py。
透過這個方式,未來還可以在同樣的資料夾裡依照不同的功能建立不一樣的檔案,來分別管理。
2.搬移程式碼
接下來,我們把原來放在main.py的檔案中,開頭為get的函式集中到(搬移到)blog_get.py裏面。
# 原來的main.py檔案
from fastapi import FastAPI
app = FastAPI()
# 留下這個路由
@app.get("/")
def index():
return {"Hello": "FastAPI"}
# 需要搬移到blog_get.py之中----
@app.get("/blog/get_all")
...略...
main.py檔案中所有的函式都搬移到blog_get.py之中,只留下一個主要的路由( ‘/’
):index函式。
到目前為止,這只是暫時的狀態,待會還需要對這個檔案進行連結的處理。
3.匯入APIRouter套件
在搬移程式碼時,我們需要先在目的地檔案(也就是blog_get.py檔案)的前面匯入APIRouter。
from fastapi import APIRouter
匯入APIRouter後,並且需要實體化APIRouter,指定給一個變數(例如:router)。
router = APIRouter()
在這個APIRouter實體中(router
),我們可以加入前綴(prefix)與標籤(tags)作為參數。
router = APIRouter(
prefix="/blog",
tags=["blogs"],
)
完成之後,就可以將其他與blog get路由相關的程式碼搬移過來,貼上這裡。
4.修改路由
因為我們已經設定了前綴(prefix)。之後,每次我們使用路由時,會自動替我們加上前綴(prefix)的內容。
因為加了前綴的關係,原本我們在路由中寫的 blog(@app.get("blog/all")
)會變成重複的字串@app.get("blogblog/all")
。如果不修改原來搬過來的路由,將變成了重複的字串,而無法正常連結,我們必須將它們移除。
另外,在 blog_get.py 裏面,我們必須將原來的 @app.get
改為 @route.get
。改為使用先前建立的APIRouter實體(route)。
最後,再把原本每一個路由裡面標註的 tags=["blogs"]
移除,因為tag已經在建立APIRouter實體的時候預先設定好了,一樣的標籤不用再重複設定到個別的路由中。
因此,我們把原來寫在個別函式裡面的 tags=["blogs"]
刪除,僅保留get_comment中的comments Tag( tags=["comments"]
)。因為這個comments
標籤,只存在於get_comment裏面。
至此,與get相關的函式都搬移到這個blog_get.py檔案之中。經過這番搬移後的blog_get.py檔案將如下面的呈現:
from typing import Optional
from fastapi import APIRouter, status, Response
from enum import Enum
router = APIRouter(
prefix="/blog",
tags=["blogs"],
)
# /blog/all
@router.get(
"/all",
summary="取得所有的 blogs",
description="取得所有的 blogs,並且可以指定分頁的資料",
response_description="所有的 blogs 資料"
)
def get_blogs_all(page=1, page_size: Optional[int] = None):
return {"message": f"所有的 blogs: 來自第 {page} 頁, 總共有 {page_size} 筆資料"}
# /blog/{id}/comments/{comment_id}
@router.get("/{id}/comments/{comment_id}", tags=["comments"])
def get_comment(
id: int,
comment_id: int,
valid: bool = True,
username: Optional[str] = None
):
"""
取得 blog 的 comment 資料
- **id**: blog 的 id
- **comment_id**: comment 的 id
- **valid**: comment 是否有效
- **username**: 使用者名稱
"""
return {"message": f"Blog 的 id 是:{id}, comment 的 id 是:{comment_id}, valid 是:{valid}"}
class BlogType(str, Enum):
business = "business"
story = "story"
qa = "qa"
# /blog/type/{type}
@router.get("/type/{type}")
def get_blog_type(type: BlogType):
return {"message": f"Blog 的資料型態是 {type}"}
# /blog/{id}
@router.get("/{id}", status_code=status.HTTP_200_OK)
def get_blog(id: int, response: Response):
if id > 5:
response.status_code = status.HTTP_404_NOT_FOUND
return {"error": f"找不到 Blog 的 id :{id}"}
else:
response.status_code = status.HTTP_200_OK
return {"message": f"Blog 的 id 是:{id}"}
5.與主檔案連結
修改至此,我們若從SwaggerUI檢視,會發現路由只剩下一個(index),原本從SwaggerUI裏面看得到的其他路由,此時都消失了。
這是因為我們把那些路由從main.py 裡面移出去了。單純從main.py看起來,是找不到其他路由的。因此,我們不僅需要處理blog_get.py檔案,還需要將它與main.py進行連結。下面說明連結的方式。
首先,要將blog_get匯入這個檔案中:
from router import blog_get
然後,再透過 include_router
將blog_get中的路由include進來。
app.include_router(blog_get.router)
下面是搬移之後的main.py檔案,這是最後的狀態:
from fastapi import FastAPI
from router import blog_get
app = FastAPI()
app.include_router(blog_get.router)
@app.get("/")
def index():
return {"Hello": "FastAPI"}
6.驗證
修改完畢之後,即可進入SwaggerUI檢視。這時您會發現原本不見的路由,現在都重新出現了。並且分成三個類別blogs、comments與default。
實作2:加入新路由
前面的實作中,我們已經將原有的程式碼改寫完畢。接下來就可以試試看這ˊ種將檔案拆分的狀況,是否真的便於管理?
由於之前改寫的部分是get路由,尚未見建立post相關的路由,假設我們想要加入另外一組post路由。在這樣的架構下,該如何添加新路由?
1.建立檔案
因為建立post路由的目的是要將資料新增進blog中,在功能上雖然與前面的get不同,但性質上仍然屬於blog的一員。因此這個功能仍然要放在以blog為開頭的檔案中。
基於此,我們要在router資料夾裡面增加一個檔案,取名為blog_post.py。
2.匯入APIRouter
接下來,打開 blog_post.py
檔案,與前面 blog_get.py
的做法一樣,在 blog_post.py
裡面匯入APIRouter。
from fastapi import APIRouter
3.建立實體
APIRouter匯入完畢之後,同樣的需要實體化APIRouter,並且加入prefix與tags參數。到目前為止,都與 blog_get.py
的做法一樣。
router = APIRouter(
prefix="/blog",
tags=["blogs"],
)
4.加入路由
接著,就可以在blog_post.py裡面,加入想要設定的post路由。
# blog/new
@router.post('/new')
def create_blog():
程式碼...
5.與主檔案連結
完成路由的設計後,我們需要將這個檔案與主檔案進行連結,這與前面get的做法是一樣的。
連結需要兩個步驟。首先,要在main.py加入下面的程式碼匯入blog_post。
# 從router匯入blog_post
from router import blog_post
接下來,將blog_post include進來,加入app。
# 將blog_post加入app
app.include_router(blog_post.router)
最後,我們可以看到新建立的blog_post.py檔案與修改的main.py 檔案如下:
這是blog_post.py檔案:
from fastapi import APIRouter
router = APIRouter(
prefix="/blog",
tags=["blogs"],
)
@router.post('/new')
def create_blog():
pass
這是main.py檔案:
from fastapi import FastAPI
from router import blog_get
from router import blog_post
app = FastAPI()
# blog_get
app.include_router(blog_get.router)
# blog_post
app.include_router(blog_post.router)
@app.get("/")
def index():
return {"Hello": "FastAPI"}
6.驗證
最後,我們一樣可以透過SwaggerUI檢視修改的結果。這時,您會發現在blogs的分類中,出現了POST項目(下圖綠色的部分)。
結論
透過本篇的學習,我們了解到FastAPI不僅功能強大,而且在架構應用程式上也極具彈性。尤其是藉由「路由」的概念,讓我們可以將龐大的應用程式有效地拆分並管理。就如同一座城市的道路系統,每個路由就如同一條連接各處的道路,讓使用者可以輕易地獲得他們需要的資訊。當我們的應用程式發展到一定規模時,不論是在開發、維護、或者是管理上,路由都能提供非常好的幫助。就像把大城市的地圖會分成各個區域一樣,每一個路由依其性質獨立出來,讓我們能專注於每一個特定區域的開發與改進。
從之前幾篇的FastAPI的基本特性、到SwaggerUI文件部分,再到本篇的路由使用方式,可以看見一個功能強大且易於使用的Python網路框架展現在我們眼前。無論您是初學者,或者是已有豐富的網頁開發經驗者,相信FastAPI都能成為您的好幫手。