VS Codeのエージェントモードを拡張するには、を試してください!

Visual Studio Code での FastAPI チュートリアル

FastAPI は、Python で API を構築するためのモダンで高性能な Web フレームワークです。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 拡張機能 Discussions Q&A で回答を検索したり、質問したりできます。

プロジェクトのセットアップ

このチュートリアルのプロジェクトをセットアップする方法はいくつかあります。ここでは、GitHub Codespacesローカルマシンの VS Code でセットアップする方法について説明します。

GitHub Codespaces

GitHub Codespaces でこのプロジェクトを開発用にセットアップできます。Codespaces を使用すると、コードの作成、デバッグ、アプリの実行をリモートで行うことができます。Codespaces は、クラウドでホストされる完全に構成済みの開発環境を提供するため、ローカルでのセットアップは不要です。この環境には、プロジェクトの依存関係、ツール、拡張機能が含まれており、一貫性のある再現可能な開発エクスペリエンスを保証します。リアルタイム編集、統合されたバージョン管理、デバッグおよびテストツールへの簡単なアクセスを提供することで共同作業を効率化し、プロジェクトのセキュリティと信頼性を維持します。

: すべての GitHub.com アカウントには、無料または Pro プランに含まれる GitHub Codespaces の月間無料使用割り当てがあります。詳細については、「GitHub Codespaces の課金について」を参照してください。

このチュートリアルの Codespace をセットアップするには、このプロジェクトの GitHub リポジトリに移動します。この Codespace には、FastAPI 開発をすぐに開始するために必要なすべての構成と依存関係が含まれています。

このチュートリアルでは、dictionarybased ブランチを選択します。

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

次に、[Code] > [Codespaces] > [Create Codespace on <dictionarybased>] ブランチを選択して、プロジェクトの Codespace を作成して開きます。

完了したら、以下のデータベースの置き換えセクションに進むことができます。

VS Code でローカルに

VS Code でこのチュートリアルを正常に完了するには、まず Python 開発環境をセットアップする必要があります。具体的には、このチュートリアルでは以下が必要です。

このセクションでは、VS Code でワークスペースとして開くフォルダーを作成し、Python 仮想環境をセットアップし、プロジェクトの依存関係をインストールします。

  1. ファイルシステムに、このチュートリアル用のプロジェクトフォルダー (groceries-plugin など) を作成します。

  2. この新しいフォルダーを VS Code で開きます ([ファイル] > [フォルダーを開く...])。

  3. ワークスペースの信頼プロンプトが表示されたら、[はい、作成者を信頼します] を選択して、ワークスペースが必要なリソースと拡張機能にアクセスできるようにします。ワークスペースの信頼の詳細については、ドキュメントを参照してください。

次に、アプリケーションにインストールしたい依存関係をリストする requirements.txt ファイルを作成します。requirements.txt ファイルは Python 開発で一般的な手法であり、プロジェクトが依存するライブラリとそのバージョンを指定するために使用されます。このファイルは、プロジェクトに取り組む誰もが同様の開発環境を再現できるようにするのに役立ち、一貫性を維持するための便利なコンポーネントになります。

アプリを作成するために FastAPI、サーバーとして機能するために uvicorn、データストレージの処理と Redis データベースとのやり取りのために Redistype-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 ファイルを選択すると、依存関係が自動的にインストールされます。次に、[OK] を選択します。

    Check box selected to install dependencies from requirements.txt file

仮想環境が作成され、依存関係が自動的にインストールされ、Python 拡張機能が使用するワークスペースの環境が選択されます。VS Code の右下隅を確認することで、選択されていることを確認できます。

Environment in the Status bar

: 新しく作成された環境情報がステータスバーに表示されない場合は、Python インタープリターインジケーターをクリックする (またはコマンドパレットから [Python: インタープリターの選択] コマンドを実行する) ことで、手動で仮想環境を選択できます。

コーディングの開始

アプリケーションを作成しましょう!

  1. [ファイル] > [新しいファイル...] を使用して新しい Python ファイルを作成し、[Python ファイル] を選択します。

  2. groceries-plugin フォルダーに main.py として保存します (⇧⌘S (Windows、Linux Ctrl+Shift+S))。

  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. ターミナルの http://127.0.0.1:8000/ URL を Ctrl+クリック して、デフォルトのブラウザーでそのアドレスを開きます。

    Hello World message displayed in the browser

    おめでとうございます! FastAPI アプリが起動しました!

  7. デバッグツールバーの [停止] ボタンを使用するか、⇧F5 (Windows、Linux Shift+F5) を使用してデバッガーを停止します。

食料品リスト項目のモデルを作成する

FastAPI アプリが動作するようになったので、FastAPI とシームレスに統合されるデータ検証および解析ライブラリである Pydantic を使用して食料品リスト項目を定義できます。Pydantic を使用すると、API リクエスト内の受信データ (「ペイロード」と呼ばれる) の自動検証と解析のために型ヒントを持つ Python クラスを使用してデータモデルを定義できます。

食料品リスト項目のモデルを作成しましょう。ItemPayload モデルを使用して、食料品リストに追加する項目のデータ構造を定義します。このモデルには、item_iditem_namequantity の 3 つのフィールドがあります。

  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
    

VS Code の Python のデフォルト言語サーバーである Pylance は、Pydantic モデルや FastAPI を使用する際に役立つ型ヒント機能をサポートしています。これは、Pylance が Pyright (コード内の型エラーを検出してバグを防ぎ、コード品質を向上させることができる Python の静的型チェッカー) の上に構築されているためです。

以下の 3 つのステップはオプションですが、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」を検索し、[Variable Types][Function Return Types] のインレイヒントを有効にします。

    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 エンドポイントの両方を使用することです。/docs エンドポイントは、利用可能なすべての API ルートに関する情報を提供し、API と対話してそのパラメーターとレスポンスを調査できます。このドキュメントは、FastAPI アプリケーションで定義されたメタデータと型ヒントに基づいて動的に生成されます。

  4. if quantity <= 0 ステートメントの横に行番号の左マージンをクリックして (または F9)、ブレークポイントを追加します。デバッガーはその行の実行前に停止するため、コードを一行ずつ検査できます。

    Breakpoint set next to the first line in the add_item function

  5. デバッガーを開始し (F5)、ブラウザーで http://127.0.0.1:8000/docs に移動します。

    アプリで利用可能な 2 つのエンドポイント、/items とルート (/) を持つ Swagger インターフェイスが表示されるはずです。

    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 に数値を渡して食料品リストの項目を追加します。たとえば、item_name に apple、quantity に 2 を指定できます。

  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 と評価されます。

    デバッグコンソールは、式をすばやくテストし、ブレークポイント時点でのコードの状態をよりよく理解するための強力なツールとなり得ます。また、関数の呼び出しや変数の出力など、任意のコードを実行するためにも使用できます。VS Code での Python デバッグの詳細については、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 の Dev Containers 拡張機能は、プロジェクト、その依存関係、および必要なすべてのツールを 1 つの整頓されたコンテナーに統合するための合理化されたアプローチを提供し、フル機能の開発環境を作成します。この拡張機能を使用すると、VS Code 内のコンテナー内でプロジェクトを開く (またはマウントする) ことができ、そこでその全機能セットを利用できます。

以下の手順では、マシンに次の要件がインストールされていることを確認してください。

要件

Dev コンテナー構成の作成

  1. コマンドパレットを開き、[Dev Containers: Dev コンテナー構成ファイルの追加...] を実行します。

  2. Python 3 を選択します。

    Python 3 option selected in the Dev Containers configuration files list

  3. デフォルトのバージョンを選択します。

  4. 追加機能として Redis Server を選択し、[OK] を押してから、[Keep Defaults] を選択します。

    オプションで、コンテナーに含める機能をインストールできます。このチュートリアルでは、Redis の適切な Dev コンテナー設定をインストールして追加するコミュニティ提供の機能である Redis Server をインストールします。

    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 やその他のライフサイクルスクリプトの詳細については、Development Containers Specification を参照してください。

    次に、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. 右下隅に表示される通知から [コンテナーで再度開く] を選択するか、コマンドパレットから [Dev Containers: コンテナーで再度開く] コマンドを実行します。

    : インターネットの速度とマシンのパフォーマンスによっては、コンテナーのビルドに数分かかる場合があります。

    dev コンテナーの構成の詳細については、Dev Containers ドキュメントを参照してください。

完了すると、Python 3 と Redis Server がインストールされた完全に構成された Linux ベースのワークスペースが完成します。

コンテナーのセットアップが完了すると、VS Code の左下隅にインジケーターが表示されます。

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

: 拡張機能ビュー (⇧⌘X (Windows、Linux Ctrl+Shift+X)) を開いて検索し、Python と Pylance の拡張機能がコンテナーに正常にインストールされていることを再確認してください。インストールされていない場合は、[Dev コンテナーにインストール] を実行してインストールできます。

選択された Python インタープリター情報は、右下のステータスバーで確認でき、devcontainer.json ファイルで指定されたバージョンと一致します。

Python interpreter selection

: ステータスバーに Python インタープリター情報が見つからない場合は、Python インタープリターインジケーターをクリックする (またはコマンドパレットから [Python: インタープリターの選択] コマンドを実行する) ことで、コンテナー内の Python インタープリターを手動で選択できます。

これで、次のセクションに進む準備ができました。ここではデータストレージを置き換えます。

データベースの置き換え

食料品リストの項目を保存する辞書がありますが、これを Redis データベースに置き換えたいと考えています。このチュートリアルでは、データを保存するために Redis ハッシュを使用します。これは、複数のキーと値のペアを保存できるデータ構造です。

ID を知らなくても項目を取得できる従来のデータベースとは異なり、Redis ハッシュから値を取得するには Redis ハッシュキーを知る必要があります。このチュートリアルでは、item_name_to_id というハッシュを作成して、名前で項目を取得し、それらを ID にマッピングします。さらに、ID で項目を取得するための他のハッシュを作成し、それらを名前と数量にマッピングします。各項目ハッシュは item_id:{item_id} という名前で、item_namequantity の 2 つのフィールドがあります。

まず、辞書を 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)
    

    Redis がまだインポートされていないため、Pylance はエラーメッセージを表示します。

  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+,)) で [Auto Import Completions] 設定を探して有効にすることで、Pylance が自動的にインポートを追加するように設定できます。

    これで、ローカルホスト (host="0.0.0.0") で実行され、ポート 6379 (port=6379) をリッスンしている Redis サーバーに接続する 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 メソッドが str または None (項目が存在しない場合) を返すためです。しかし、まだ置き換えていないコードの下の行は item_idint 型であることを期待しています。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 ハッシュは項目名を ID にマッピングするだけです。項目 ID を名前と数量にもマッピングするために、各項目に対して個別の Redis ハッシュを作成し、ID による取得を容易にするためにハッシュ名として "item_id:{item_id}" を使用します。また、これらの各ハッシュに item_namequantity フィールドを追加します。

  7. if ブロック内のコードを削除します。

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

    そして、item_idint に変換し、Redis の hincrby メソッドを呼び出して項目の数量をリクエストで指定された量 (quantity) だけ増やすために、以下を追加します。

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

    item_id_strNone の場合、つまり項目が存在しない場合のコードを置き換えるだけで済みます。この場合、新しい item_id を生成し、項目用に新しい Redis ハッシュを作成し、提供された項目名と数量を追加します。

    新しい item_id を生成するには、Redis の incr メソッドを使用し、"item_ids" という新しいハッシュを渡します。このハッシュは、最後に生成された ID を保存するために使用されるため、新しい項目を作成するたびにインクリメントして、すべてが一意の ID を持つようにします。

  8. 以下の内容を含む行を削除します。

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

    そして、以下を追加します。

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

    この incr 呼び出しが item_ids キーで初めて実行されると、Redis はキーを作成し、それを値 1 にマッピングします。その後、実行されるたびに、格納された値を 1 ずつインクリメントします。

    次に、hset メソッドを使用して、フィールド (item_iditem_namequantity) と値 (新しく作成された項目の ID、および提供された名前と数量) のマッピングを提供して、項目を Redis ハッシュに追加します。

  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,
                })
    

    これで、冒頭で参照したハッシュ 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 ルートとやり取りすることでこのアプリケーションをテストします。完了したらデバッガーを停止できます。

おめでとうございます! これで、食料品リストの項目を追加、リスト、削除するためのルートを備え、データが Redis データベースに永続化される動作中の FastAPI アプリケーションが完成しました。

任意: データベース削除のセットアップ

データが 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 プラグインの作成

ChatGPT プラグインを使用する場合、GitHub Codespaces を使用してテスト目的でアプリケーションをホストできます。ChatGPT プラグインは、ChatGPT が既存の API と対話して ChatGPT の能力を強化し、さまざまなアクションを実行できるようにするツールです。ChatGPT プラグインは現在公開されていませんが、待機リストに参加してアクセスできます。アクセスできたら、以下のライブストリームの録画に従って、ChatGPT 用の独自の食料品リストプラグインを作成できます。

: すべての個人用 GitHub.com アカウントには、無料または Pro プランに含まれる GitHub Codespaces の月間無料使用割り当てがあります。詳細については、「GitHub Codespaces の課金について」を参照してください。

次のステップ

このチュートリアルをご覧いただきありがとうございます! FastAPI と VS Code での使用方法について何か新しいことを学んでいただけたことを願っています。

このチュートリアルの完成したコードプロジェクトは GitHub で見つけることができます: python-sample-vscode-fastapi-tutorial

FastAPI の詳細については、公式ドキュメントをご覧ください。

本番の Web サイトでアプリを試すには、チュートリアルDocker コンテナーを使用して Python アプリを Azure App Service にデプロイするをご覧ください。

これらの他の VS Code Python 記事も確認できます。