2024 iThome 鐵人賽2024 iThome 鐵人賽

上一篇文章中,我們介紹了 Django Ninja 如何處理 HTTP 請求,並強調了它與 Python type hints 之間的緊密結合。

本篇將探討 Django Ninja 中,路徑參數(path parameters的應用與細節,這在處理 HTTP 請求時極為常見,尤其是在 RESTful API 中。

本文對範例專案的程式碼改動,都集中在這個 PR


一、什麼是 Path Parameters?

Path parameters 是構成 URL 的一部分,它們位於網址路徑(path)中的特定位置,根據不同的值(參數)來決定傳入的內容,用來動態指定資源

我們不妨先了解一下,path 在整個 URL 中的位置:(圖片取自維基百科

點圖可放大點圖可放大

從圖中可以看出,路徑(path)是 URL 的一部分——而且是必要部分

不過請注意,path parameters 只是 Django、Django Ninja 這類框架所提供的一種「功能」。對 URL 本身而言,path 就是 path,也就是單純的字串而已。

路徑參數例子

舉一個簡單的例子,讓我們可以更好地理解 path parameters 的概念。

1
@router.get(path='/posts/{post_id}/')  # {post_id} 就是路徑參數

實際請求時,123就是透過路徑參數,代表特定文章的 id:

1
GET /posts/123

可想而知,如果是456789,你會得到不同的結果。

這使得 API 具有彈性,可以針對不同的資源進行操作,而不必為每一個資源建立不同的路由——它們的端點與路由都是相同的,只是「參數」不同。


二、範例專案改動

接下來,讓我們透過範例專案的程式碼,一邊講解一邊演示本文內容。

但我們要先進行兩個改動。

改動一:取消一級路由前綴

取消一級路由前綴/posts//user/,讓 view 函式的 router 裝飾器上的路徑,更加完整、好讀。

本來是這樣:

1
@router.get(path='/{post_id}/')  # 前綴路由在專案一級路由定義了

現在是這樣:

1
@router.get(path='/posts/{post_id}/')  # 全部改在 app 的二級路由定義

注意,這是為了提升教學體驗,工作中我們通常不會這麼做,不然就失去了模組化路由的優勢。

改動二:新增 API

為了示範 path parameters,我們必須有一個實踐這個功能的 API。

我們新增一個「取得單一文章資訊」API。


好,先這樣,我們可以開始了解 path parameters 了。

以下程式碼皆取自於範例專案。

三、在 Django Ninja 中使用 Path Parameters

在 Django Ninja 中,定義路徑參數非常簡單。透過 router 裝飾器與 type hints,我們可以輕鬆處理這些參數,自動進行類型轉換。

定義「帶有路徑參數」的 API

讓我們看看如何在 Django Ninja 中定義一個帶有路徑參數的 API:

1
2
3
4
@router.get(path='/posts/{post_id}/')
def get_post(request: HttpRequest, post_id: int) -> Post:
post = Post.objects.get(id=post_id)
return post

範例中,{post_id}是一個路徑參數,整個 path 字串將被解析(parsing)並傳遞給get_post函式中post_id參數。

Django Ninja 會依照函式簽名中定義的類型,自動進行型別轉換。

例如,我們在 view 函式中標記了 post_id: int,Django Ninja 會自動將來自 URL 的字串參數轉換成int

換言之,處理 path parameters 的流程,同時具有兩種效果

  1. 參數型別驗證:避免前端傳來post_id內容是錯誤的型別,而在 view 函式內部還繼續嘗試處理這個值,直到發生錯誤。
  2. View 函式內部的自動型別轉換:省下在函式內自行轉換的功夫。

四、與 Django 原生 Path Converters 兼容

Django 在處理 URL 時,本來就提供了「path converters」來讓你對請求路徑進行「嚴格配對」。

配對成功,才會把 HTTP 請求「轉發」給特定的 view 函式

這裡的「嚴格」,指的是型別與 path converter 所定義的相符,才能夠配對成功

常見的 converters 類型包括strintslug,它們能夠限制 URL 中參數的格式:

1
path('posts/<int:post_id>/', views.get_post),

<int:post_id>就是一個 path converter,它要求post_id必須是一個整數。

值得強調的是,path converters 的主要目的不是為了型別轉換——這只是附帶的。而是為了端點路徑的「模式比對」(pattern matching)。

在模式不符合的情況下,根本不會配對成功,當然也不會進行轉型。

Django Ninja 中的 Path Converters

在 Django Ninja 中,這些原生的 path converters 仍然可以使用,而且進一步簡化了。

直接寫在router裝飾器的路徑字串中即可:

1
@router.get(path='/posts/{int:post_id}/')

如前所述,有了 path converter 後,如果post_id不是有效的int ,則 URL 模式配對將直接失敗——請求不會進入 view 函式,更不會有型別轉換。

如果沒有其他路徑被成功配對,Django 將直接返回「404 Not Found」。

個人認為,在 Django Ninja 中,path converters 的功能已部分被 type hints 所取代。如果要同時使用 path converters,須留意兩者的判斷順序(path converters 先判斷)與兩者設定的型別一定要相同。


五、請求的基本錯誤處理

當請求中的路徑參數不符合 type hints 定義的型別時,Django Ninja 會自動返回一個帶有錯誤訊息與提示內容的 HTTP 回應,狀態碼為 422。

比如說,如果用戶請求的路徑是 /posts/abc/post_id參數沒有給數字),將得到下列回應:

1
2
3
4
5
6
7
8
9
10
11
12
13
// http://127.0.0.1:8000/posts/abc/
{
"detail": [
{
"type": "int_parsing",
"loc": [
"path",
"post_id"
],
"msg": "Input should be a valid integer, unable to parse string as an integer"
}
]
}

這樣的自動錯誤處理機制,不僅提高了 API 的穩定性,同時簡化了開發者的錯誤處理邏輯

內建的 422 回應在 Django Ninja 中極為常見,省下了我們不少時間。


小結與下一步

路徑參數是 RESTful API 中的重要組成部分,Django Ninja 透過 type hints 和自動化的錯誤處理,讓我們能夠輕鬆處理路徑中的動態參數。

此外,它與 Django 原生的 path converters 保持了良好的相容性,提供高效、簡潔的開發體驗。

下一篇,我們將深入探討查詢參數query parameters),說明如何在 Django Ninja 中處理這些參數,進一步提升 API 的靈活性與功能。