🚀 在 VS Code 中

Visual Studio Code 中的 FastAPI 教學

FastAPI 是一個現代化、高效能的 Web 框架,用於使用 Python 建置 API。它旨在讓建置 API 變得輕鬆且有效率,同時提供自動驗證、序列化和 API 文件等功能,使其成為建置 Web 服務和微服務的熱門選擇。

在本 FastAPI 教學課程中,我們將使用 FastAPI 建立一個雜貨清單應用程式。在本教學課程結束時,您將了解如何在 Visual Studio Code 終端機、編輯器和偵錯工具中使用 FastAPI。本教學課程並非 FastAPI 深入探討。如需深入探討,您可以參考 官方 FastAPI 文件

如果您是第一次使用 Python,我們建議您從我們的 Python 教學課程 開始,以熟悉該語言和 VS Code 的 Python 支援。本教學課程更適合那些已經熟悉 Python 並想學習如何在 VS Code 中使用 FastAPI 的人。

本 FastAPI 教學課程的完整程式碼專案可以在 GitHub 上找到:python-sample-vscode-fastapi-tutorial

如果您有任何問題,可以在 Python 擴充功能討論區問答 中搜尋答案或提出問題。

設定專案

您可以透過不同的方式設定本教學課程的專案。我們將涵蓋如何在 GitHub Codespaces本機電腦上的 VS Code 中進行設定。

GitHub Codespaces

您可以設定此專案以在 GitHub Codespaces 中開發,您可以在 codespace 中遠端編碼、偵錯和執行您的應用程式。Codespace 提供完全設定的開發環境,託管在雲端中,無需在本機進行設定。此環境包含專案的相依性、工具和擴充功能,確保一致且可重現的開發體驗。它透過提供即時編輯、整合的版本控制以及輕鬆存取偵錯和測試工具來簡化協作,同時保持專案的安全性和可靠性。

注意:所有 GitHub.com 帳戶在 Free 或 Pro 方案中都包含每月免費使用 GitHub Codespaces 的配額。如需更多資訊,請前往 關於 GitHub Codespaces 的計費

若要為本教學課程設定 codespace,請導覽至 此專案的 GitHub 儲存庫。此 codespace 包含所有必要的組態和相依性,可快速開始 FastAPI 開發。

對於本教學課程,請選取 dictionarybased 分支

dictionarybased branch selected in the python-sample-vscode-fastapi-tutorial GitHub repo

然後,選取 Code > Codespaces > 在 <dictionarybased> 分支上建立 Codespace 以建立並開啟專案的 codespace。

完成後,您可以繼續進行下方的 取代資料庫 區段。

本機 VS Code 中

若要成功完成 VS Code 中的本教學課程,您首先需要設定 Python 開發環境。具體而言,本教學課程需要

在本節中,我們將建立一個資料夾以在 VS Code 中開啟作為工作區、設定 Python 虛擬環境,並安裝專案的相依性。

  1. 在您的檔案系統中,為本教學課程建立一個專案資料夾,例如 groceries-plugin

  2. 在 VS Code 中開啟這個新資料夾 (檔案 > 開啟資料夾…)。

  3. 工作區信任 提示出現時,選取 是,我信任作者 以允許工作區存取必要的資源和擴充功能。您可以在文件中了解更多關於工作區信任的資訊。

現在,讓我們建立一個 requirements.txt 檔案,其中列出我們希望為應用程式安裝的相依性。requirements.txt 檔案是 Python 開發中的常見做法,用於指定專案依賴的程式庫及其版本。此檔案有助於確保任何參與專案的人員都可以重現類似的開發環境,使其成為維護一致性的便利元件。

我們將安裝 FastAPI 以建立應用程式、uvicorn 作為伺服器,以及 Redistype-redis 以處理資料儲存並與 Redis 資料庫互動。

  1. 在 VS Code 中建立一個新檔案 (檔案 > 新增文字檔⌘N (Windows, Linux Ctrl+N))。

  2. 將以下內容新增至其中

    fastapi
    redis
    types-redis
    uvicorn
    
  3. 儲存檔案 (⌘S (Windows, Linux Ctrl+S)) 並命名為 requirements.txt

  4. 透過開啟命令面板 (⇧⌘P (Windows, Linux Ctrl+Shift+P)) 並執行 Python: 建立環境 命令來建立虛擬環境。

    注意:此步驟可能需要幾分鐘才能完成。

  5. 當詢問環境類型時,選取 Venv

    Dropdown with "Venv" or "Conda" as options for environments that can be created with the Python: Create Environment command

  6. 然後選取您機器上可用的最新 Python 版本

    List of available global environments that can be used to create a virtual environment

  7. 從下拉式清單中選取 requirements.txt 檔案,以便自動安裝相依性,然後選取 確定

    Check box selected to install dependencies from requirements.txt file

虛擬環境將會建立,相依性會自動安裝,並且為您的工作區選取環境以供 Python 擴充功能使用。您可以透過檢查 VS Code 的右下角來確認它已被選取

Environment in the Status bar

注意:如果您在狀態列上找不到新建立的環境資訊,您可以按一下 Python 解譯器指示器 (或從命令面板執行 Python: 選取解譯器 命令) 並手動選取虛擬環境。

開始編碼

讓我們建立應用程式!

  1. 透過使用 檔案 > 新增檔案… 然後選取 Python 檔案 來建立新的 Python 檔案。

  2. 將其儲存為 main.py (⇧⌘S (Windows, Linux Ctrl+Shift+S)) 在 groceries-plugin 資料夾中。

  3. 將以下程式碼新增至 main.py 並儲存檔案

    from fastapi import FastAPI
    
    app = FastAPI()
    
    @app.get("/")
    def root():
        return {"message": "Hello World"}
    
  4. 透過啟動偵錯工具 (F5) 來執行程式碼。

  5. 從下拉式選單中,從清單中選取 FastAPI 組態選項

    Dropdown with debugger configuration options, with FastAPI being highlighted

    這會自動建立一個偵錯組態,該組態會調用 uvicorn 以透過偵錯工具啟動應用程式伺服器,並允許您逐步執行原始程式碼以檢查其行為。您應該會在終端機中看到類似以下內容

    Uvicorn server running message displayed in the terminal, with an URL to access the app

    提示:如果您的預設連接埠已被使用,請停止偵錯工具並開啟命令面板 (⇧⌘P (Windows, Linux Ctrl+Shift+P)),搜尋 偵錯:新增組態,選取 Python 偵錯工具,然後選取 FastAPI。這將在 .vscode/launch.json 中建立一個自訂組態檔,您可以編輯該檔案。將以下內容新增至 "args":[] 以設定自訂連接埠:"--port=5000"。儲存檔案,並使用 (F5) 重新啟動偵錯工具。

  6. Ctrl+按一下 終端機中的 http://127.0.0.1:8000/ URL,以在您的預設瀏覽器中開啟該網址

    Hello World message displayed in the browser

    恭喜!您的 FastAPI 應用程式已啟動並執行!

  7. 使用偵錯工具列中的 停止 按鈕,或透過 ⇧F5 (Windows, Linux Shift+F5) 來停止偵錯工具。

建立雜貨清單項目的模型

現在我們已經讓 FastAPI 應用程式運作,我們可以透過使用 Pydantic 來定義我們的雜貨清單項目,Pydantic 是一個資料驗證和剖析程式庫,可與 FastAPI 無縫整合。Pydantic 讓您可以使用 Python 類別和 類型提示 來定義資料模型,以便自動驗證和剖析 API 請求中的傳入資料 (稱為「酬載」)。

讓我們為我們的雜貨清單項目建立一個模型。我們將使用 ItemPayload 模型來定義要新增到雜貨清單的項目的資料結構。此模型將具有三個欄位:item_iditem_namequantity

  1. 使用 檔案 > 新增檔案… 然後選取 Python 檔案 來建立新的 Python 檔案。

  2. 將以下幾行新增至檔案中,然後將其儲存到 groceries-plugin 資料夾中,並命名為 models.py (⇧⌘S (Windows, Linux Ctrl+Shift+S))

    from typing import Optional
    from pydantic import BaseModel
    
    class ItemPayload(BaseModel):
        item_id: Optional[int]
        item_name: str
        quantity: int
    

Pylance,VS Code 中 Python 的預設語言伺服器,支援類型提示功能,這些功能對於使用 Pydantic 模型和 FastAPI 很有幫助。這是因為 Pylance 是建立在 Pyright 之上,Pyright 是一個 Python 的靜態類型檢查器,可以偵測程式碼中的類型錯誤,以防止錯誤並提高程式碼品質。

以下三個步驟是選用的,但鑑於 FastAPI 廣泛使用類型提示來提高程式碼可讀性和驗證,我們可以利用 Pylance 的類型檢查功能來儘早發現錯誤

  1. 開啟設定編輯器 (⌘, (Windows, Linux Ctrl+,))。

  2. 搜尋「python type checking mode」,並將其設定為 basic 以進行基本類型檢查。Pylance 現在將顯示診斷和警告,以捕捉簡單的類型相關錯誤。或者,您可以將其設定為 strict 以強制執行更進階的 類型檢查規則

    Python Analysis Type Checking Mode options (off, basic and strict) in Settings editor

  3. 接下來,搜尋「Python inlay type hints」,並為 變數類型函式傳回類型 啟用內嵌提示

    Two Python Analysis Type Hints settings being enabled in the Settings editor: for Function Return Types and for Variable Types

建立路由

現在我們需要一個地方來儲存雜貨清單項目。為了簡單起見,我們先從一個空的字典開始。

  1. 首先,讓我們匯入範例所需的所有套件。開啟 main.py 檔案並將第一個匯入行取代為以下幾行

    from fastapi import FastAPI, HTTPException
    
    from models import ItemPayload
    
  2. 現在在 app = FastAPI() 下方新增以下行

    grocery_list: dict[int, ItemPayload] = {}
    

    這會建立一個新的空字典,該字典接收 int 類型 (作為項目 ID) 和 ItemPayload 類型的值的鍵。

    我們現在將在我們的 FastAPI 應用程式中定義路由。在 Web 應用程式的上下文中,路由就像路徑,將特定的 URL 對應到處理它們的程式碼。這些路由充當我們應用程式中不同功能的進入點。當用戶端 (例如 Web 瀏覽器或其他程式) 向我們的應用程式傳送具有特定 URL 的請求時,FastAPI 會根據 URL 將該請求路由到適當的函式 (也稱為路由處理常式或檢視函式),並且該函式會處理請求並產生回應。

    讓我們繼續定義路由以新增和擷取個別項目,以及傳回雜貨清單中的所有項目。

  3. main.py 檔案的結尾新增以下路由

    # Route to add a item
    @app.post("/items/{item_name}/{quantity}")
    def add_item(item_name: str, quantity: int):
        if quantity <= 0:
            raise HTTPException(status_code=400, detail="Quantity must be greater than 0.")
        # if item already exists, we'll just add the quantity.
        # get all item names
        items_ids = {item.item_name: item.item_id if item.item_id is not None else 0 for item in grocery_list.values()}
        if item_name in items_ids.keys():
            # get index of item_name in item_ids, which is the item_id
            item_id = items_ids[item_name]
            grocery_list[item_id].quantity += quantity
    # otherwise, create a new item
        else:
            # generate an ID for the item based on the highest ID in the grocery_list
            item_id = max(grocery_list.keys()) + 1 if grocery_list else 0
            grocery_list[item_id] = ItemPayload(
                item_id=item_id, item_name=item_name, quantity=quantity
            )
    
        return {"item": grocery_list[item_id]}
    

    如果您已在上一節中啟用類型提示,您可能會注意到 Pylance 新增了函式傳回類型的內嵌提示,以及 item_idsitem_id 的類型。您可以選擇性地按兩下每個建議以將其插入程式碼中

    Inlay function return and variable type hints being displayed by Pylance throughout the sample code

    現在讓我們檢查此路由是否如預期般運作。最快的方法是同時使用 VS Code 的偵錯工具以及 FastAPI 的 /docs 端點,後者提供有關所有可用 API 路由的資訊,並讓您與 API 互動以探索其參數和回應。此文件是根據 FastAPI 應用程式中定義的中繼資料和類型提示動態產生的。

  4. 透過按一下行號的左邊界 (或 F9),在 if quantity <= 0 陳述式旁邊新增中斷點。偵錯工具將在執行該行之前停止,以便您可以逐行檢查程式碼。

    Breakpoint set next to the first line in the add_item function

  5. 啟動偵錯工具 (F5),然後導覽至瀏覽器中的 http://127.0.0.1:8000/docs

    應該會有一個 Swagger 介面,其中包含應用程式中可用的兩個端點:/items 和根目錄 (/)。

    Swagger UI displaying two endpoints: /items and /

  6. 選取 /items 路由旁邊的向下箭頭以展開它,然後選取右側出現的 Try it out 按鈕。

    "Try it out" button displayed next to the /items route in the Swagger UI

  7. 透過將字串傳遞到 item_name 欄位和數字傳遞到 quantity 來新增雜貨清單項目。例如,您可以將 apple 作為 item_name,將 2 作為 quantity

  8. 選取 Execute

    Execute button displayed below the /items route

  9. 再次開啟 VS Code 並注意偵錯工具已在您先前設定的中斷點停止。

    Debugger stopped at the breakpoint set in the add_item function

    在左側,此時定義的所有本機和全域變數都會顯示在 執行和偵錯 檢視下的 [變數] 視窗中。在我們的範例中,item_name 設定為 'apple',quantity 設定為本機變數檢視下的 2,以及全域變數檢視下的空 grocery_list 字典。

    Variables window displayed in the Run and Debug view, with the item and grocery_list variables highlighted

    現在讓我們使用 VS Code 的偵錯主控台進行一些探索。

  10. 選取 quantity <= 0 陳述式,在編輯器中按一下滑鼠右鍵,然後選取 在偵錯主控台中評估

    Evaluate in Debug Console option displayed in the context menu when right-clicking on a line of code

    這會開啟偵錯主控台並執行選取的運算式。正如我們的範例中所預期的,運算式評估為 False

    偵錯主控台可能是一個強大的工具,可以快速測試運算式並更好地了解中斷點時程式碼的狀態。您也可以使用它來執行任意程式碼,例如呼叫函式或列印變數。您可以在 Python 教學課程 中了解更多關於 VS Code 中的 Python 偵錯資訊。

    您現在可以透過選取 [偵錯檢視] 工具列中的 繼續,或按下 F5 來繼續執行程式碼。

    最後,讓我們為應用程式新增剩餘的路由,以便我們可以列出所有項目或特定項目,以及從我們的雜貨清單中移除它們。您可以讓偵錯工具保持執行狀態,因為當您儲存在下一步中所做的變更時,它將自動重新載入應用程式。

  11. main.py 中的內容取代為以下程式碼

    from fastapi import FastAPI, HTTPException
    
    from models import ItemPayload
    
    app = FastAPI()
    
    grocery_list: dict[int, ItemPayload] = {}
    
    # Route to add an item
    @app.post("/items/{item_name}/{quantity}")
    def add_item(item_name: str, quantity: int) -> dict[str, ItemPayload]:
        if quantity <= 0:
            raise HTTPException(status_code=400, detail="Quantity must be greater than 0.")
        # if item already exists, we'll just add the quantity.
        # get all item names
        items_ids: dict[str, int] = {
            item.item_name: item.item_id if item.item_id is not None else 0
            for item in grocery_list.values()
        }
        if item_name in items_ids.keys():
            # get index of item_name in item_ids, which is the item_id
            item_id: int = items_ids[item_name]
            grocery_list[item_id].quantity += quantity
        # otherwise, create a new item
        else:
            # generate an ID for the item based on the highest ID in the grocery_list
            item_id: int = max(grocery_list.keys()) + 1 if grocery_list else 0
            grocery_list[item_id] = ItemPayload(
                item_id=item_id, item_name=item_name, quantity=quantity
            )
    
        return {"item": grocery_list[item_id]}
    
    
    # Route to list a specific item by ID
    @app.get("/items/{item_id}")
    def list_item(item_id: int) -> dict[str, ItemPayload]:
        if item_id not in grocery_list:
            raise HTTPException(status_code=404, detail="Item not found.")
        return {"item": grocery_list[item_id]}
    
    
    # Route to list all items
    @app.get("/items")
    def list_items() -> dict[str, dict[int, ItemPayload]]:
        return {"items": grocery_list}
    
    
    # Route to delete a specific item by ID
    @app.delete("/items/{item_id}")
    def delete_item(item_id: int) -> dict[str, str]:
        if item_id not in grocery_list:
            raise HTTPException(status_code=404, detail="Item not found.")
        del grocery_list[item_id]
        return {"result": "Item deleted."}
    
    
    # Route to remove some quantity of a specific item by ID
    @app.delete("/items/{item_id}/{quantity}")
    def remove_quantity(item_id: int, quantity: int) -> dict[str, str]:
        if item_id not in grocery_list:
            raise HTTPException(status_code=404, detail="Item not found.")
        # if quantity to be removed is higher or equal to item's quantity, delete the item
        if grocery_list[item_id].quantity <= quantity:
            del grocery_list[item_id]
            return {"result": "Item deleted."}
        else:
            grocery_list[item_id].quantity -= quantity
        return {"result": f"{quantity} items removed."}
    
    
  12. 儲存檔案 (⌘S (Windows, Linux Ctrl+S))。應用程式應該會自動重新載入。

您現在可以再次開啟 /docs 頁面並測試新的路由,使用偵錯工具和偵錯主控台來更好地了解程式碼執行。完成後,您可以停止偵錯工具 (⇧F5 (Windows, Linux Shift+F5))。您也可以透過按一下中斷點來移除我們在步驟 4 中新增的中斷點。

恭喜!您現在有一個可運作的 FastAPI 應用程式,其中包含用於新增、列出和刪除雜貨清單中項目的路由。

設定資料儲存

此時,您已經擁有具有基本功能的應用程式的可運作版本。本節將引導您完成設定資料儲存以實現持久性,但如果您對已學到的知識感到滿意,您可以選擇跳過它。

到目前為止,我們將資料儲存在字典中,這並不理想,因為當應用程式重新啟動時,所有資料都會遺失。

為了持久保存資料,我們將使用 Redis,它是一個開放原始碼的記憶體內資料結構儲存區。由於其速度和多功能性,Redis 通常被用作各種應用程式中的資料儲存系統,包括 Web 應用程式、即時分析系統、快取層、本教學課程等等。

如果您已經在使用 GitHub Codespaces 和我們現有的範本,您可以直接跳到 取代資料庫 區段。

如果您使用的是 Windows,您可以透過設定 Docker 容器GitHub Codespace 來使用 Redis。在本教學課程中,我們將使用 Docker 容器,但您可以參考 上方區段 以取得有關如何設定 GitHub Codespace 的說明。

否則,如果您使用的是 Linux 或 macOS 機器,您可以按照 他們網站上的說明 安裝 Redis,然後跳到 取代資料庫 區段。

在 Windows 上設定 Docker 容器

VS Code 開發容器 擴充功能提供了一種簡化的方法,可將您的專案、其相依性和所有必要的工具整合到一個整潔的容器中,從而建立一個功能齊全的開發環境。此擴充功能可讓您在 VS Code 中的容器內 (或掛載到容器中) 開啟您的專案,您將在其中擁有其完整功能集。

對於以下步驟,請確保您的機器上已安裝以下需求

需求

建立開發容器組態

  1. 開啟命令面板並執行 開發容器:新增開發容器組態檔案…

  2. 選取 Python 3

    Python 3 option selected in the Dev Containers configuration files list

  3. 選取預設版本。

  4. 選取 Redis Server 作為要安裝的其他功能,按一下 確定,然後選取 保留預設值

    我們可以選擇性地安裝 功能 以包含在容器中。對於本教學課程,我們將安裝 Redis Server,這是一個社群貢獻的功能,可安裝 Redis 並新增適當的開發容器設定。

    Redis Server option selected in the Dev Containers configuration files list

    這會在您的工作區中建立一個 .devcontainer 資料夾,其中包含一個 devcontainer.json 檔案。讓我們對這個檔案進行一些編輯,以便容器設定包含安裝我們需要的 VS Code 擴充功能以及專案相依性等步驟。

  5. 開啟 devcontainer.json 檔案。

  6. "features" : { ... } 項目之後新增一個「,」,以便我們可以將更多設定新增至檔案。

    接下來,我們將必要的相依性安裝命令新增至 devcontainer.json 檔案中的 postCreateCommand 屬性,以便我們的應用程式在容器設定完成後即可執行。

  7. 找到以下內容並從該行移除註解 (//),以便在建立容器後可以安裝相依性

    "postCreateCommand": "pip3 install --user -r requirements.txt",
    

    您可以在 開發容器規格 中了解有關 postCreateCommand 和更多生命週期腳本的資訊。

    現在我們將使用 customizations 屬性來新增我們想要在容器中安裝的 VS Code 擴充功能。

  8. 將以下設定新增至 devcontainer.json

        // Use 'postCreateCommand' to run commands after the container is created.
        "postCreateCommand": "pip3 install --user -r requirements.txt",
    
        // Configure tool-specific properties.
        "customizations": {
            "vscode": {
                "extensions": [
                    "ms-python.python", //Python extension ID
                    "ms-python.vscode-pylance" //Pylance extension ID
                ]
            }
        }
    
  9. 儲存檔案。

  10. 從右下角顯示的通知中選取 在容器中重新開啟,或從命令面板執行 開發容器:在容器中重新開啟 命令。

    注意:建置容器可能需要幾分鐘時間,具體取決於網際網路速度和機器效能。

    您可以在 開發容器文件 中了解更多關於開發容器組態的資訊。

完成後,您將擁有一個完全設定的基於 Linux 的工作區,其中安裝了 Python 3 和 Redis Server。

容器設定完成後,您會注意到 VS Code 左下角的指示器

Dev Containers indicator displayed on the bottom left corner of VS Code

注意:透過開啟 [擴充功能] 檢視 (⇧⌘X (Windows, Linux Ctrl+Shift+X)) 並搜尋它們,來仔細檢查 Python 和 Pylance 擴充功能是否已成功安裝在容器中。如果沒有,您可以執行 在開發容器中安裝 來安裝它們。

選取的 Python 解譯器資訊可在右下角的狀態列上找到,與 devcontainer.json 檔案中指定的版本相符

Python interpreter selection

注意:如果您在狀態列上找不到 Python 解譯器資訊,您可以按一下 Python 解譯器指示器 (或從命令面板執行 Python: 選取解譯器 命令) 並手動選取容器中的 Python 解譯器。

我們現在已準備好繼續進行下一節,我們將在其中取代資料儲存。

取代資料庫

我們有一個儲存雜貨清單項目的字典,但我們想要將其取代為 Redis 資料庫。在本教學課程中,我們將使用 Redis 雜湊來儲存我們的資料,這是一種可以儲存多個鍵值對的資料結構。

與傳統資料庫不同,在傳統資料庫中,您可以在不知道項目 ID 的情況下擷取項目,您需要知道 Redis 雜湊鍵才能從中擷取值。在本教學課程中,我們將建立一個名為 item_name_to_id 的雜湊,以依名稱擷取項目,並將它們對應到其 ID。此外,我們將建立其他雜湊以依 ID 擷取項目,將它們對應到其名稱和數量。每個項目雜湊都命名為 item_id:{item_id},並具有兩個欄位:item_namequantity

首先,讓我們從將字典取代為連接到 Redis 伺服器的 Redis 用戶端物件開始。

  1. main.py 檔案中,將檔案開頭的 grocery_list: dict[int, ItemPayload] = {} 取代為以下幾行

    redis_client = redis.StrictRedis(host='0.0.0.0', port=6379, db=0, decode_responses=True)
    

    Pylance 將顯示錯誤訊息,因為 Redis 尚未匯入。

  2. 將游標放在編輯器中的「redis」上,然後按一下顯示的燈泡 (或 ⌘. (Windows, Linux Ctrl+.))。然後選取 新增 'import redis'

    Light bulb displayed next to the Redis variable, with the option to add the import statement

    提示:您可以透過在設定編輯器 (⌘, (Windows, Linux Ctrl+,)) 中尋找 自動匯入完成 設定並啟用它,來設定 Pylance 自動新增匯入。

    我們現在有一個 Redis 用戶端物件,它連接到在本機主機 (host="0.0.0.0") 上執行並在連接埠 6379 (port=6379) 上接聽的 Redis 伺服器。db 參數指定要使用的 Redis 資料庫。Redis 支援多個資料庫,在此程式碼中,我們將使用資料庫 0,這是預設資料庫。我們也傳遞了 decode_responses=True,以便將回應解碼為字串 (而不是位元組)。

    讓我們在第一個路由 add_item 中進行更多取代。與其查看字典中的所有鍵來尋找已提供的項目名稱,我們可以從 Redis 雜湊中直接擷取該資訊。

    我們假設 item_name_to_id 雜湊已存在,將項目名稱對應到其 ID (別擔心,我們稍後會新增此程式碼!)。然後,我們可以透過調用 Redis 中的 hget 方法來取得我們在請求中收到的項目名稱的 ID,如果請求的名稱已存在於雜湊中,則該方法將傳回項目 ID,如果不存在,則傳回 None

  3. 刪除包含以下內容的行

    items_ids = {item.item_name: item.item_id if item.item_id is not None else 0 for item in grocery_list.values()}
    

    並將其取代為

      item_id = redis_client.hget("item_name_to_id", item_name)
    

    請注意,Pylance 對此變更提出了問題。這是因為 hget 方法傳回 strNone (如果項目不存在)。但是,我們尚未取代的程式碼下方的行預期 item_id 的類型為 int。讓我們透過重新命名 item_id 符號來解決此警告。

  4. item_id 重新命名為 item_id_str

  5. 如果您已啟用內嵌提示,Pylance 應該會在 item_id_str 旁邊顯示變數類型提示。您可以選擇性地按兩下以接受它

    Variable type hint displayed next to the item_id_str variable

  6. 如果商品不存在,則 item_id_strNone。所以現在我們可以刪除包含以下內容的行

    if item_name in items_ids.keys():
    

    並將其取代為

    if item_id_str is not None:
    

    現在我們有了字串格式的商品 ID,我們需要將其轉換為 int 型別,並更新商品的數量。目前,我們的 Redis Hash 僅將商品名稱對應到它們的 ID。為了也將商品 ID 對應到它們的名稱和數量,我們將為每個商品建立一個獨立的 Redis Hash,使用 "item_id:{item_id}" 作為我們的 Hash 名稱,以便更輕鬆地通過 ID 檢索。我們還將為這些 Hash 中的每一個新增 item_namequantity 欄位。

  7. 刪除 if 區塊內的程式碼

    item_id: int = items_ids[item_name]
    grocery_list[item_id].quantity += quantity
    

    並新增以下內容,將 item_id 轉換為 int 型別,然後透過呼叫 Redis 的 hincrby 方法來增加商品的數量。此方法會將 "quantity" 欄位的值,依據請求中給定的數量 (quantity) 遞增。

    item_id = int(item_id_str)
    redis_client.hincrby(f"item_id:{item_id}", "quantity", quantity)
    

    現在我們只需要替換商品不存在時的程式碼,也就是當 item_id_strNone 時的情況。在這種情況下,我們會產生一個新的 item_id,為商品建立一個新的 Redis Hash,然後新增提供的商品名稱和數量。

    為了產生新的 item_id,讓我們使用 Redis 的 incr 方法,並傳遞一個名為 "item_ids" 的新 Hash。這個 Hash 用於儲存最後產生的 ID,因此我們可以在每次建立新商品時遞增它,確保它們都具有唯一的 ID。

  8. 刪除包含以下內容的行

    item_id: int = max(grocery_list.keys()) + 1 if grocery_list else 0
    

    並新增以下內容

    item_id: int = redis_client.incr("item_ids")
    

    當第一次使用 item_ids 鍵執行此 incr 呼叫時,Redis 會建立該鍵並將其對應到值 1。然後,每次後續執行時,它都會將儲存的值遞增 1。

    現在我們將商品新增到 Redis Hash 中,使用 hset 方法並提供欄位 (item_iditem_namequantity) 和值 (商品新建立的 ID,以及其提供的名稱和數量) 的對應。

  9. 刪除包含以下內容的行

    grocery_list[item_id] = ItemPayload(
            item_id=item_id, item_name=item_name, quantity=quantity
        )
    

    並將其替換為以下內容

    redis_client.hset(
                f"item_id:{item_id}",
                mapping={
                    "item_id": item_id,
                    "item_name": item_name,
                    "quantity": quantity,
                })
    

    現在我們只需要通過設定我們在開始時引用的 Hash,也就是 item_name_to_id,將新建立的 ID 對應到商品名稱。

  10. 將此行新增到路由的末尾,else 區塊內

    redis_client.hset("item_name_to_id", item_name, item_id)
    
  11. 刪除包含以下內容的行

    return {"item": grocery_list[item_id]}
    

    並將其取代為

    return {"item": ItemPayload(item_id=item_id, item_name=item_name, quantity=quantity)}
    
  12. 如果您願意,您可以嘗試為其他路由進行類似的替換。否則,您可以直接將檔案的全部內容替換為以下幾行

    import redis
    from fastapi import FastAPI, HTTPException
    
    from models import ItemPayload
    
    app = FastAPI()
    
    redis_client = redis.StrictRedis(host="0.0.0.0", port=6379, db=0, decode_responses=True)
    
    # Route to add an item
    @app.post("/items/{item_name}/{quantity}")
    def add_item(item_name: str, quantity: int) -> dict[str, ItemPayload]:
        if quantity <= 0:
            raise HTTPException(status_code=400, detail="Quantity must be greater than 0.")
    
        # Check if item already exists
        item_id_str: str | None = redis_client.hget("item_name_to_id", item_name)
    
        if item_id_str is not None:
            item_id = int(item_id_str)
            redis_client.hincrby(f"item_id:{item_id}", "quantity", quantity)
        else:
            # Generate an ID for the item
            item_id: int = redis_client.incr("item_ids")
            redis_client.hset(
                f"item_id:{item_id}",
                mapping={
                    "item_id": item_id,
                    "item_name": item_name,
                    "quantity": quantity,
                },
            )
            # Create a set so we can search by name too
            redis_client.hset("item_name_to_id", item_name, item_id)
    
        return {
            "item": ItemPayload(item_id=item_id, item_name=item_name, quantity=quantity)
        }
    
    
    # Route to list a specific item by ID but using Redis
    @app.get("/items/{item_id}")
    def list_item(item_id: int) -> dict[str, dict[str, str]]:
        if not redis_client.hexists(f"item_id:{item_id}", "item_id"):
            raise HTTPException(status_code=404, detail="Item not found.")
        else:
            return {"item": redis_client.hgetall(f"item_id:{item_id}")}
    
    
    @app.get("/items")
    def list_items() -> dict[str, list[ItemPayload]]:
        items: list[ItemPayload] = []
        stored_items: dict[str, str] = redis_client.hgetall("item_name_to_id")
    
        for name, id_str in stored_items.items():
            item_id: int = int(id_str)
    
            item_name_str: str | None = redis_client.hget(f"item_id:{item_id}", "item_name")
            if item_name_str is not None:
                item_name: str = item_name_str
            else:
                continue  # skip this item if it has no name
    
            item_quantity_str: str | None = redis_client.hget(
                f"item_id:{item_id}", "quantity"
            )
            if item_quantity_str is not None:
                item_quantity: int = int(item_quantity_str)
            else:
                item_quantity = 0
    
            items.append(
                ItemPayload(item_id=item_id, item_name=item_name, quantity=item_quantity)
            )
    
        return {"items": items}
    
    
    # Route to delete a specific item by ID but using Redis
    @app.delete("/items/{item_id}")
    def delete_item(item_id: int) -> dict[str, str]:
        if not redis_client.hexists(f"item_id:{item_id}", "item_id"):
            raise HTTPException(status_code=404, detail="Item not found.")
        else:
            item_name: str | None = redis_client.hget(f"item_id:{item_id}", "item_name")
            redis_client.hdel("item_name_to_id", f"{item_name}")
            redis_client.delete(f"item_id:{item_id}")
            return {"result": "Item deleted."}
    
    
    # Route to remove some quantity of a specific item by ID but using Redis
    @app.delete("/items/{item_id}/{quantity}")
    def remove_quantity(item_id: int, quantity: int) -> dict[str, str]:
        if not redis_client.hexists(f"item_id:{item_id}", "item_id"):
            raise HTTPException(status_code=404, detail="Item not found.")
    
        item_quantity: str | None = redis_client.hget(f"item_id:{item_id}", "quantity")
    
        # if quantity to be removed is higher or equal to item's quantity, delete the item
        if item_quantity is None:
            existing_quantity: int = 0
        else:
            existing_quantity: int = int(item_quantity)
        if existing_quantity <= quantity:
            item_name: str | None = redis_client.hget(f"item_id:{item_id}", "item_name")
            redis_client.hdel("item_name_to_id", f"{item_name}")
            redis_client.delete(f"item_id:{item_id}")
            return {"result": "Item deleted."}
        else:
            redis_client.hincrby(f"item_id:{item_id}", "quantity", -quantity)
            return {"result": f"{quantity} items removed."}
    
    
  13. 重新執行偵錯工具,透過與 /docs 路由互動來測試此應用程式。完成後您可以停止偵錯工具。

恭喜!您現在擁有一個可運作的 FastAPI 應用程式,其中包含新增、列出和刪除購物清單中商品的路由,並且資料會持久儲存在 Redis 資料庫中。

選用:設定資料庫刪除

由於資料現在由 Redis 持久儲存,您可能想要建立一個腳本來清除所有測試資料。為此,建立一個名為 flushdb.py 的新檔案,內容如下

import redis

redis_client = redis.StrictRedis(host='0.0.0.0', port=6379, db=0, decode_responses=True)
redis_client.flushdb()

然後,當您想要重設資料庫時,您可以在 VS Code 中開啟 flushdb.py 檔案,並選取編輯器右上角的執行按鈕,或從命令選取區執行 Python: 在終端機中執行 Python 檔案 命令。

請注意,這應該謹慎執行,因為它將刪除目前資料庫中的所有鍵,如果在生產環境中執行可能會導致資料遺失。

選用:建立 ChatGPT 外掛程式

使用 GitHub Codespaces,您可以在使用 ChatGPT 外掛程式 時託管您的應用程式以進行測試。ChatGPT 外掛程式是使 ChatGPT 能夠與現有 API 互動以增強 ChatGPT 能力的工具,使其能夠執行各種操作。ChatGPT 外掛程式目前尚未公開提供,但您可以加入他們的 候補名單 以取得存取權。一旦您取得存取權,您可以按照下面的直播錄影來建立您自己的 ChatGPT 購物清單外掛程式

注意:所有個人 GitHub.com 帳戶在免費或 Pro 方案中都包含每月免費使用 GitHub Codespaces 的配額。如需更多資訊,請前往 關於 GitHub Codespaces 的計費

後續步驟

感謝您跟隨本教學課程!我們希望您從中學到一些關於 FastAPI 以及如何將其與 VS Code 一起使用的新知識。

本教學課程的完整程式碼專案可以在 GitHub 上找到:python-sample-vscode-fastapi-tutorial

官方文件 中了解更多關於 FastAPI 的資訊。

若要在生產網站上試用該應用程式,請查看教學課程 使用 Docker 容器將 Python 應用程式部署到 Azure App Service

您也可以查看這些其他 VS Code Python 文章