当サイトは、アフィリエイト広告を利用しています
PythonのフレームワークであるFastAPIの開発環境を
dockerを使ってコンテナで構築し、VScodeからコンテナ内で
FastAPI製の基本的なREST APIを実装、動作させる方法をまとめる
この記事では基本となる
についてを実際に手順を追って解説していく
Pythonのパッケージ管理はpipではなくPoetryを使って行う。
Poetryを使う場合は
を作る必要があるのでコンテナ作成時に
dockerfile内でスクリプトを実行して作成するようにした。
※pyproject.tomlがある場合は上書きはしない
また当記事内で使用するPoetryのコマンドは下記記事で解説している
requirements.txtからパッケージをpoetryでインストールする方法は
下記記事でまとめている
REST APIをPythonで実装する場合、選択肢として有力なのは
の二つになると思う。
※Djangoはフルスタックフレームワークなので少しオーバースペックのため除外
FastAPIを選択する利点は下記のようなものがある
FastAPIは非同期処理をネイティブにサポートしてるため
ASGI(Asynchronous Server Gateway Interface)サーバーとして
非同期対応の高速なサーバーであるUvicornで実行できる。
そのためパフォーマンスに優れている。
特にI/Oバウンドな処理(データベースやAPI呼び出しなど)においては
Flaskよりもパフォーマンスが高い。
Uvicornについては下記参照
FastAPIはエンドポイントに基づいてOpenAPI仕様に基づいたAPIドキュメントを自動生成してくれる。
開発者が手動でドキュメントを作成する必要がなく、APIの使い方を確認したり、テストしたりするのが非常に簡単になる
OpneAPIについては下記参照
Pythonの型ヒントに基づいた自動バリデーションが可能で、
Pydanticを使って入力データのバリデーションがシンプルに行える
このような利点があるためFaskApiを選択した。
ただ作るアプリケーションによってはケースバイケースだと思う
flaskで作る場合についての詳細は下記でまとめています
下記の環境で行う
Docker for Windowsのインストール方法については下記記事で 紹介しています
全体的なプロジェクト構成は下記のようにする
.|-- .vscode| |-- launch.json| `-- tasks.json|-- app| |-- __init__.py| |-- asgi.py| |-- endpoints.py| `-- main.py|-- docker-compose.yml|-- dockerfile|-- poetry.lock|-- pyproject.toml`-- script|-- entrypoint.sh|-- run_uvicorn.sh`-- vscode_ex_install.sh
「pyproject.toml」と「poetry.lock」については
コンテナ作成時に「poetry init」コマンドを実行して作成するにで最初はなくていい。
VSCodeからdocker-composeを使ってコンテナを作成する
# ベースイメージとしてPythonを使用FROM python:3.12 as python-base# 作業ディレクトリを作成WORKDIR /workspace# pipを更新RUN pip install --upgrade pip# PoetryをインストールRUN pip install poetry# スクリプトをコピーCOPY script/entrypoint.sh /workspace/script/# スクリプトを実行するために権限を変更RUN chmod +x /workspace/script/entrypoint.sh# エントリーポイントとしてスクリプトを設定ENTRYPOINT ["/workspace/script/entrypoint.sh"]
REST APIの実装に必要な依存関係をインストールしたDockerイメージを作成する
依存関係のインストールはPoetryを使って行う
エントリーポイントでPoetryコマンドのスクリプトを実行している
#!/bin/sh# pyproject.toml が存在するかチェックif [ ! -f pyproject.toml ]; thenecho "pyproject.toml が存在しないため、poetry init を実行します..."# pyproject.toml が存在しない場合、poetry init を実行poetry init -n --name fastapi-restapi --dependency fastapi --dependency uvicorn[standard]echo "poetry init が完了しました。"elseecho "pyproject.toml が既に存在します。poetry init は実行されませんでした。"fi# 依存関係をインストールecho "依存関係をインストールしています..."poetry install --no-rootecho "依存関係のインストールが完了しました。"# # uvicorn サーバーを起動# echo "仮想環境でuvicorn サーバーを起動しています..."# sh /workspace/script/run_uvicorn.shtail コマンドでコンテナが終了しないようにするtail -f /dev/null
スクリプトでPoetryコマンドを実行している
実行するコマンドは
の二つ。
ただ「pyproject.toml」がある場合は「poetry init」する必要がないので
条件分岐を書いている
コメントアウトを解除するとコンテナ作成と同時にuvicornサーバーで
REST APIが起動する。
デフォルトで起動すると止める時が面倒なので一旦は起動しないようにしておく。
Dockerのコンテナには「プロセス単位で動作する」という特性がある
そのためコンテナの中で実行されているプロセスが終了すると、そのコンテナも終了してしまう。
そのため「tail -f /dev/null」コマンドを使うことで、コンテナが停止するのを防いでいる。
「uvicorn サーバーを起動」部分をコメントアウト解除した場合は
コンテナでプロセスが起動していることになるので
「tail -f /dev/null」部分は必要なくなる。
#!/bin/shpoetry run uvicorn app.asgi:app --reload --host 0.0.0.0 --port 8000
poetryの仮想環境でuvicornを起動するコマンドをスクリプトにしたもの サーバーを起動する時にこのスクリプトを読み込んで起動する
コマンドを少し詳しく解説する
仮想環境内で指定したコマンドを実行する
FastApiアプリケーションインスタンスを指定してuvicornを起動
「app.asgi:app」はFastAPIアプリケーションインスタンスまでのパスを示してる
※app/asgi.py/app
uvicorn起動時のオプション
起動中にソースが変更された場合に再起動する。
※いわゆるホットリロード
dockerのコンテナは、コンテナがそれぞれ独立した環境を持っているため
デフォルトではホストOSからアクセスできない状態になっている
そしてuvicornサーバーがデフォルトで待ち受けるIPアドレスが127.0.0.1(localhost)
のためIP、ポート番号なしで起動してもホストからはアクセスできない。
そのため起動時に
を指定して起動することでホストからアクセス可能になる。
ホストの8000番ポートとコンテナの8000番ポートをバインドさせるため
8000番で起動する
version: "3"services:fastapi:container_name: "fastapi"build:context: .dockerfile: Dockerfilevolumes:- .:/workspacetty: trueports:- 8000:8000
dockerfileからdockerイメージを作成してコンテナを作成する
ホストマシンのポート8000を、docker内のポート8000に接続する
ここまでのファイルでVSCodeからコンテナを作成する
VSCodeのターミナルで下記コマンドを実行する
# コンテナ作成実行docker-compose up -d# コンテナ作成確認docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES8e5ac4313646 faskapi_restapi-fastapi "/workspace/script/e…" 28 hours ago Up 9 seconds 0.0.0.0:8000->8000/tcp fastapi
コンテナが作成されていることがわかる。
pyproject.tomlがない状態で実行した場合
プロジェクト直下に
が作成される
コンテナのログを確認すると
$ docker logs fastapipyproject.toml が存在しないため、poetry init を実行します...Using version ^0.114.1 for fastapiUsing version ^0.30.6 for uvicornpoetry init が完了しました。依存関係をインストールしています...Creating virtualenv fastapi-restapi-xS3fZVNL-py3.12 in /root/.cache/pypoetry/virtualenvsUpdating dependenciesResolving dependencies... (6.5s)Package operations: 18 installs, 0 updates, 0 removals- Installing idna (3.8)- Installing sniffio (1.3.1)- Installing typing-extensions (4.12.2)- Installing annotated-types (0.7.0)- Installing anyio (4.4.0)- Installing pydantic-core (2.23.3)- Installing click (8.1.7)- Installing h11 (0.14.0)- Installing httptools (0.6.1)- Installing pydantic (2.9.1)- Installing python-dotenv (1.0.1)- Installing pyyaml (6.0.2)- Installing starlette (0.38.5)- Installing uvloop (0.20.0)- Installing watchfiles (0.24.0)- Installing websockets (13.0.1)- Installing fastapi (0.114.1)- Installing uvicorn (0.30.6)Writing lock file依存関係のインストールが完了しました。tail: cannot open 'コマンドでコンテナが終了しないようにする' for reading: No such file or directory
entrypoint.shが実行されていることがわかる。
作成されたpyproject.tomlは下記のようになっている
[tool.poetry]name = "fastapi-restapi"version = "0.1.0"description = ""authors = ["Your Name <you@example.com>"]readme = "README.md"[tool.poetry.dependencies]python = "^3.12"fastapi = "^0.114.1"uvicorn = {extras = ["standard"], version = "^0.30.6"}[build-system]requires = ["poetry-core"]build-backend = "poetry.core.masonry.api"
依存関係としてfastapiとuvicornが設定されている
※entrypoint.shでpoetry init実行時のオプションで指定しているため
開発はVSCodeからリモートでコンテナに接続しコンテナ上で行う。
VSCodeの拡張機能である。
のどちらかを使用すれば、コンテナ内をVSCodeから操作できるので開発しやすい。
※この拡張機能はホスト側のVSCodeでインストールして使う
ちなみに自分はdocker-composeでコンテナを作った後
Docker(ms-azuretools.vscode-docker)でコンテナにアタッチしている
下記のような感じ
別ウィンドウでコンテナにVSCodeからリモート接続することができる
Dev Containersの使い方については下記でまとめています。
リモートでつないだコンテナで下記を実行して必要な拡張機能を一括インストールする
#!/bin/bash# 拡張機能のIDリストextensions=("ms-python.python")# 各拡張機能をインストールfor extension in "${extensions[@]}"; docode --install-extension $extensiondone
詳細は下記参照
簡単なREST APIを実装する。
REST API内で持っているdictに対してCRUDを行った
結果を返すREST APIを実装する
HTTPメソッドとしては
のエンドポイントを定義したREST APIにする
実装していく前にFastAPIがデフォルトで行ってくれる処理についてまとめる
※pydanticを使って行える機能についてはここでは省く
FastAPIでは、明示的に Responseオブジェクトやstatus_code を指定しない場合、
デフォルトでステータスコードは 200 OK になる。
@router.get("/test")async def test():return {"message": "Success!"}
この場合、curlコマンドで確認すると
$ curl -i -X GET http://localhost:8000/testHTTP/1.1 200 OKdate: Sat, 14 Sep 2024 07:58:24 GMTserver: uvicorncontent-length: 22content-type: application/json{"message":"Success!"}
200が設定されていることがわかる
また
@router.get("/test", status_code=201)async def test():return {"message": "Success!"}
のようにすればデフォルトを変更できる
$ curl -i -X GET http://localhost:8000/testHTTP/1.1 201 Createddate: Sat, 14 Sep 2024 07:59:56 GMTserver: uvicorncontent-length: 22content-type: application/json{"message":"Success!"}
201に変更できる
もしくは
@router.get("/test", status_code=201)async def test():return Response(status_code=202, content=json.dumps({"message": "Success!"}), media_type="application/json")
Responseクラスを使えば自由に設定ができる。
$ curl -i -X GET http://localhost:8000/testHTTP/1.1 202 Accepteddate: Sat, 14 Sep 2024 08:02:15 GMTserver: uvicorncontent-length: 23content-type: application/json{"message":"Success!"}
優先順位は
の順になる
FastAPIのエンドポイントが返すレスポンスデータを自動でJSONに変換してくれる
@router.get("/test", status_code=201)async def test():users_dict = [{"user_id": "1", "name": "Tujimura", "age": 11},{"user_id": "2", "name": "mori", "age": 20},]return users_dict
ディクショナリをそのままレスポンスとして返却してもJSONに変換してくれる
$ curl -i -X GET http://localhost:8000/testHTTP/1.1 201 Createddate: Sun, 15 Sep 2024 07:07:16 GMTserver: uvicorncontent-length: 83content-type: application/json[{"user_id":"1","name":"Tujimura","age":11},{"user_id":"2","name":"mori","age":20}]
FastAPIでは型ヒントを使って自動パースをしてくれる
@router.get("/test/{user_id}", status_code=201)async def test(user_id: int):users_dict = [{"user_id": 1, "name": "Tujimura", "age": 11},{"user_id": 2, "name": "mori", "age": 20},]user = next((user for user in users_dict if user['user_id'] == user_id), None)return user
パスパラメータで数値を指定した場合
$ curl -i -X GET http://localhost:8000/test/1HTTP/1.1 201 Createddate: Sun, 15 Sep 2024 07:18:20 GMTserver: uvicorncontent-length: 40content-type: application/json{"user_id":1,"name":"Tujimura","age":11}
パスパラメータで指定した値を取得できる
パスパラメータで文字列を指定した場合
$ curl -i -X GET http://localhost:8000/test/abcHTTP/1.1 422 Unprocessable Entitydate: Sun, 15 Sep 2024 07:19:23 GMTserver: uvicorncontent-length: 152content-type: application/json{"detail":[{"type":"int_parsing","loc":["path","user_id"],"msg":"Input should be a valid integer, unable to parse string as an integer","input":"abc"}]}
FastAPIは自動的にエラーを返す
クエリパラメータでも同様のことができる
@router.get("/test", status_code=201)async def test(age: int):users_dict = [{"user_id": 1, "name": "Tujimura", "age": 11},{"user_id": 2, "name": "mori", "age": 20},]user = next((user for user in users_dict if user['age'] == age), None)return user
クエリパラメータとしてageを受け取り検索する
クエリパラメータで数値を指定した場合
$ curl -i -X GET http://localhost:8000/test?age=20HTTP/1.1 201 Createddate: Sun, 15 Sep 2024 07:32:53 GMTserver: uvicorncontent-length: 36content-type: application/json{"user_id":2,"name":"mori","age":20}
クエリパラメータで指定した値を取得できる
クエリパラメータで文字列を指定した場合
$ curl -i -X GET http://localhost:8000/test?age=abcHTTP/1.1 422 Unprocessable Entitydate: Sun, 15 Sep 2024 07:33:09 GMTserver: uvicorncontent-length: 149content-type: application/json{"detail":[{"type":"int_parsing","loc":["query","age"],"msg":"Input should be a valid integer, unable to parse string as an integer","input":"abc"}]}
同様にFastAPIは自動的にエラーを返す
リクエストボディとレスポンスデータの自動検証についてpydanticのデータモデルを使う必要がある。
厳密にいうとレスポンスデータの検証はデータモデルなしでも可能だが、データ検証する場合は
データモデルと併用するのが一般的であるためここでは割愛する。
pydanticは型ヒントを使用してデータのバリデーションを行うライブラリのことで
FastApiはPydanticをネイティブに統合している
※pydanticをインストールなしで使える
FastApiでpydanticを使ったリクエストとレスポンスのバリデーション方法については
下記記事で詳しくまとめています
エンドポイントを管理するモジュール
動作を確認するのが目的のため下記のような仕様にした
from fastapi import APIRouter, Requestfrom fastapi.responses import JSONResponseimport copyrouter = APIRouter()# ディクショナリusers = [{"user_id": "1", "name": "Tujimura", "age": 11},{"user_id": "2", "name": "mori", "age": 20},{"user_id": "3", "name": "shimada", "age": 50},{"user_id": "4", "name": "kyogoku", "age": 70}]# デフォルト動作検証用のためコメントアウト# @router.get("/test/{user_id}", response_model=List[dict])# async def test(user_id: int):# users_dict = [# {"user_id": 1, "name": "Tujimura", "age": 11},# {"user_id": 2, "name": "mori", "age": 20},# ]# user = next((user for user in users_dict if user['user_id'] == user_id), None)# return user# 参照# ディクショナリの一覧を取得@router.get("/")async def get_users():return JSONResponse(status_code=200, content=users)# 条件指定参照# ディクショナリの一覧からid指定で取得@router.get("/{user_id}")async def get_user_by_id(user_id: str):user = next((user for user in users if user['user_id'] == user_id), None)if user:return JSONResponse(status_code=200, content= user)return JSONResponse(status_code=404, content={"error": "User not found"})# 登録# ディクショナリに登録@router.post("/")async def post_user(request: Request):user = await request.json()res_users = copy.deepcopy(users)res_users.append(user)return JSONResponse(status_code=201, content=res_users)# 更新# ディクショナリをid指定で更新@router.put("/{user_id}")async def put_user(user_id: str, request: Request):updated_user = await request.json()res_users = copy.deepcopy(users)for idx, user in enumerate(res_users):if user['user_id'] == user_id:res_users[idx] = updated_userreturn JSONResponse(status_code=200, content=res_users)return JSONResponse(status_code=404, content={"error": "User not found"})# 削除# ディクショナリからid指定で削除@router.delete("/{user_id}")async def delete_user(user_id: str):res_users = list(filter(lambda user: user['user_id'] != user_id, users))return JSONResponse(status_code=200, content=res_users)
エンドポイントとしては
を定義し、HTTPメソッドごとに処理を行う。
上記コードの要点を解説する
FastAPIはasync/awaitをサポートしており、エンドポイント関数を非同期にすることで、高いパフォーマンスを出せる。
そのためエンドポイントのメソッドには「async」をつけている。
※非同期が必要ない場合はasyncを外せば、同期処理をさせることができる
asyncありなしの処理の違いについて少し例を使ってまとめる。
仮に一つのエンドポイントに対して10件のリクエストが来た場合
非同期処理では、データベースアクセスや外部API呼び出しなどのI/O待ちの間、次のリクエストを処理することができる。 具体的には下記ようなイメージ
待ち時間が発生するような処理の場合、その待ち時間を利用して他のリクエストを処理することで
複数のリクエストを効率的に処理することができるので非同期を使うとシステムのスループットが向上する
仮に一つのエンドポイントに対して10件のリクエストが来た場合
結果として、10件のリクエストは順番に1つずつ処理されることになる。 具体的には下記ようなイメージ
処理待ち時間が発生しないような単純な処理であればasyncをつけない方がシンプルで
パフォーマンスにも問題ない。
※今回書いたapp/endpoints.pyのようなコードでは待ち時間が発生する処理はないので実際は必要ない
APIRouter は、FastAPIで複数のエンドポイント(ルート)をまとめて管理するためのクラス。
アプリケーションの初期化処理を行うモジュールとエンドポイントを管理するモジュールを
わけているためここで設定したエンドポイントはアプリケーションの初期化処理で
FastAPIアプリケーションインスタンスに設定する
JSON形式のレスポンスに特化したクラス。
FastAPIは、デフォルトでJSONレスポンスを返すが、「JSONResponse」を明示的に使うことで、
を自動的でしてくれる
特にカスタムステータスコードやヘッダーを設定したい場合に使う。
通常のAPI開発ではJSONResponseがよく使われる
例えばGETの処理の場合
# 参照# ディクショナリの一覧を取得@router.get("/")async def get_users():return JSONResponse(status_code=200, content=users)
とした場合
をして返却してくれる
注意点として、JSONResponseは
は自動的に JSON形式に変換するが、Pydanticモデルのオブジェクト(クラスのインスタンスなど)は
自動でJSON形式に変換してくれないので手動で対応する必要がある。
ちょっとサンプルを書くと
from fastapi.responses import JSONResponsefrom pydantic import BaseModel# データモデル定義class Users(BaseModel):user_id: strname: strage: int# 参照# ディクショナリの一覧を取得@router.get("/")async def get_users():# データモデルのインスタンス作成invalid_user = Users(user_id="123", name="Add", age=30)# インスタンスを戻り値に設定return JSONResponse(status_code=200, content=invalid_user)
のようにデータモデルのインスタンスを直接、contentに設定しても
JSONResponseはJSONに変換してくれず
「TypeError: Object of type Users is not JSON serializable」が発生する
下記のように辞書形式に変換すればOK
from fastapi.responses import JSONResponsefrom pydantic import BaseModel# データモデル定義class Users(BaseModel):user_id: strname: strage: int# 参照# ディクショナリの一覧を取得@router.get("/")async def get_users():# データモデルのインスタンス作成invalid_user = Users(user_id="123", name="Add", age=30)# インスタンスを辞書形式に変換して戻り値に設定return JSONResponse(status_code=200, content=invalid_user.model_dump())
JSONResponseを含めたFastApiでよく使うレスポンスの実装パターンについては
下記記事でまとめています
アプリケーションの初期化処理を行うモジュール。
アプリケーションの初期化プロセスを関数にまとめる。
アプリケーションファクトリパターン(FlaskやFastAPIのプロジェクトで一般的に使われる設計パターン)で実装する
from fastapi import FastAPIimport app.endpoints as endpointsdef create_app():# アプリケーションインスタンスの作成app = FastAPI()# ルーティング設定app.include_router(endpoints.router)return app
FastApiのアプリケーションインスタンスを生成して
エンドポイントを設定後、返却する関数を定義する。
アプリケーションファクトリパターンで実装することで
複数の設定や異なる構成に応じて、異なるアプリケーションインスタンスを作成することが容易になる
FastApiアプリケーションをASGIサーバーで起動するための エントリーポイントとなるモジュール
from app.main import create_appapp = create_app()
ファクトリ関数で生成したFaskApiアプリケーションインスタンスを取得し保持させる。
Uvicorn起動時にこのFastApiアプリケーションインスタンスまでのパスを指定して
起動することでUvicornでFaskApiアプリケーションを動かすことができる。
Uvicornを起動する際のスクリプトで下記のように指定している
#!/bin/shpoetry run uvicorn app.asgi:app --reload --host 0.0.0.0 --port 8000
このスクリプトでは、uvicornコマンドで app/asgi.py 内の app というFastAPIアプリケーションインスタンスを指定して起動している。
poetry runは、Poetryが管理する仮想環境内で uvicornを実行するためのコマンド。
これにより、Poetryで管理されている依存関係を使って uvicornを実行する。
実装したREST APIをUvicornで起動する
起動はPoetryが管理する仮想環境内で行う
コマンドは
poetry run uvicorn app.asgi:app --reload --host 0.0.0.0 --port 8000
で起動できる
ターミナルで毎回コマンド入力したり、スクリプトを実行するのは面倒なので
VSCodeのタスクに登録しておく。手順は下記の通り
上記を実行すると.vscode/task.jsonが作成されるので
下記ように書き換える
{"version": "2.0.0","tasks": [{"label": "Run Uvicorn with Poetry","type": "shell","command": "./script/run_uvicorn.sh", // 外部スクリプトを指定"problemMatcher": [],"isBackground": true,"group": {"kind": "build","isDefault": true}}]}
「script/run_uvicorn.sh」を読み込んで実行をタスクにしておく。
タスク登録後は「F1」でコマンドパレットを開き「タスク:タスクの実行」
を選択すると「Run Uvicorn with Poetry」というタスクが選択できるので
押すと実行できる
下記のようなイメージ
実行がうまくできるとターミナルに下記が表示される
* 実行するタスク: ./script/run_uvicorn.shINFO: Will watch for changes in these directories: ['/workspace']INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)INFO: Started reloader process [876] using WatchFilesINFO: Started server process [919]INFO: Waiting for application startup.INFO: Application startup complete.
uvicornは、デフォルトで標準出力にログを出力する
ホストからcurlでリクエストを投げて確認する
$ curl -i -X GET "http://localhost:8000/" -H "Content-Type: application/json"HTTP/1.1 200 OKdate: Mon, 16 Sep 2024 07:17:59 GMTserver: uvicorncontent-length: 190content-type: application/json[{"user_id": "1", "name": "Tujimura", "age": 11}, {"user_id": "2", "name": "mori", "age": 20}, {"user_id": "3", "name": "shimada", "age": 50}, {"user_id": "4", "name": "kyogoku", "age": 70}]
一覧が取得できる
$ curl -i -X GET "http://localhost:8000/1" -H "Content-Type: application/json"HTTP/1.1 200 OKdate: Mon, 16 Sep 2024 07:18:20 GMTserver: uvicorncontent-length: 47content-type: application/json{"user_id": "1", "name": "Tujimura", "age": 11}
user_idで指定したデータを取得できている
$ curl -i -X POST "http://localhost:8000/" -H "Content-Type: application/json" -d '{"user_id": "5", "name": "yamada", "age": 30}'HTTP/1.1 201 Createddate: Mon, 16 Sep 2024 07:18:38 GMTserver: uvicorncontent-length: 237content-type: application/json[{"user_id": "1", "name": "Tujimura", "age": 11}, {"user_id": "2", "name": "mori", "age": 20}, {"user_id": "3", "name": "shimada", "age": 50}, {"user_id": "4", "name": "kyogoku", "age": 70}, {"user_id": "5", "name": "yamada", "age": 30}]
登録したデータ込みの一覧を取得できている
$ curl -i -X PUT "http://localhost:8000/1" -H "Content-Type: application/json" -d '{"user_id": "1", "name": "Tujimura", "age": 12}'HTTP/1.1 200 OKdate: Mon, 16 Sep 2024 07:19:06 GMTserver: uvicorncontent-length: 190content-type: application/json[{"user_id": "1", "name": "Tujimura", "age": 12}, {"user_id": "2", "name": "mori", "age": 20}, {"user_id": "3", "name": "shimada", "age": 50}, {"user_id": "4", "name": "kyogoku", "age": 70}]
user_idが1のデータのageが更新されている
$ curl -i -X DELETE "http://localhost:8000/1" -H "Content-Type: application/json"HTTP/1.1 200 OKdate: Mon, 16 Sep 2024 07:19:44 GMTserver: uvicorncontent-length: 141content-type: application/json[{"user_id": "2", "name": "mori", "age": 20}, {"user_id": "3", "name": "shimada", "age": 50}, {"user_id": "4", "name": "kyogoku", "age": 70}]
user_idで指定したデータを削除した一覧を取得できている
VSCodeからコンテナの仮想環境上のUvicorn上で起動しているREST APIをデバッグする
デバッグ実行もVSCodeからリモートでコンテナに接続しコンテナ上で行う
VScodeでデバッグをするための設定ファイル「launch.json」を作成する
{"version": "0.2.0","configurations": [{"name": "Python: FastAPI","type": "debugpy","request": "launch","module": "uvicorn","args": ["app.asgi:app","--reload","--host","0.0.0.0","--port","8000"],"jinja": true,"justMyCode": true}]}
Pythonデバッガーは直接シェルスクリプトをサポートしていないため
スクリプト(script/run_uvicorn.sh)を読み込んで実行はできないので手動で設定する
またPoetryの仮想環境で動かすためインタープリンタに
Poetryの仮想環境を設定しておく
ターミナルで確認
fastapi-restapi-py3.12root@b87372f9e936:/workspace# poetry env listfastapi-restapi-xS3fZVNL-py3.12 (Activated)
「fastapi-restapi-xS3fZVNL-py3.12」という仮想環境がActivatedになっている
仮想環境を指定 「poetry env list」コマンドで確認した仮想環境を選択する
ブレークポイントを打ってデバッグする
デバッグの設定で8000番ポートを指定しているため
Uvicornの起動ポートと重複しているので、Uvicornの起動は停止しておく
下記ような感じでポート重複でエラーになる
root@b87372f9e936:/workspace# cd /workspace ; /usr/bin/env /root/.cache/pypoetry/virtualenvs/fastapi-restapi-xS3fZVNL-py3.12/bin/python /root/.vscode-server/extensions/ms-python.debugpy-2024.10.0-linux-x64/bundled/libs/debugpy/adapter/../../debugpy/launcher 59589 -- -m uvicorn app.asgi:app --reload --host 0.0.0.0 --port 8000INFO: Will watch for changes in these directories: ['/workspace']ERROR: [Errno 98] Address already in useroot@b87372f9e936:/workspace#
またはデバッグの設定を8000番以外にする
FastAPIでは、アプリケーションを起動すると、OpenAPIドキュメントが自動生成され、
Webインターフェースで確認することができます。
ブラウザから「http://localhost:8000/docs」で Swagger UI形式のOpenAPIドキュメントのビジュアルインターフェースを確認できる。
ブラウザから「http://localhost:8000/redoc」で ReDoc形式のOpenAPIドキュメントのビジュアルインターフェースを確認できる。
VSCodeを使ってdockerコンテナ上でFastApiのREST APIを作ってみた。
今回は開発環境と動作環境に重きを置いたため、実際のREST API自体はシンプルになっている。
今まではpythonではflaskを使っていたが
非同期に対応できていて、かつドキュメントも自動生成できるFastApiも
かなり使いやすいと感じた。