🚄

ハンズオンで学ぶ、初心者向けのFastAPI

2022/12/29に公開約7,000字

はじめに

今回は、React から離れて Python のフレームワークであるFastAPIに触れてみました。
色々と分かった事を自分の備忘録として、まとめていこうと思います。

参考にした記事

https://fastapi.tiangolo.com/ja/

触った理由

フロントの知識も少しずつ深まり、バックエンドの知識や技術を知る必要が今後必要だと感じて、色々ネットを漁った結果FastAPIにたどり着いたという感じです。
初心者が学ぶには丁度いいとのことで、今回学習してみました。まだまだFlaskの利用者が多いですが今後流行ればいいなと思います。

FastAPI の構造

今回使用する、FastAPI の仕組みについてです。FastAPI は、データの受け渡しに特化したフレームワークです。勘違いしてはいけないこととして、FastAPIはあくまでデータの受け渡しをしているだけであって、DB 側とのやり取り(SQL を投げる)はやっていないということです。
図で表すと、下のような感じです。

何度も言いますが、FastAPI はデータの受け渡しを行うだけで、DB とのやり取りに関しては他のフレームワークで補完しています。
ここでは、FastAPIが推奨しているsqlalchemyが DB とのやり取りの部分を補完しています。この部分に関しては、sqlalchemyでなくても恐らく問題ないと思います。

実践

環境

  • vscode(1.74.0)
  • python(3.10.8)
  • fastapi(0.87.0)

導入

とりあえず今回は、DB との接続部分は置いといて、色々触ってみます。
DB と接続して色々やるのは、次回に持ちこそうと思います。

まずは、インストールしましょう。

pip install "fastapi[all]"

インストールが完了したら、テキトーなフォルダに移動してmain.py__init__.pyの 2 つを作成しましょう。自分の場合は、以下のような感じです。

fastapi-sample
            ├─ main.py
            └─ __init__.py

では、main.pyに以下をコピペしてください。

from fastapi import FastAPI

app = FastAPI()

@app.get("/hello")
async def get():

    return {"Message":"Hello World"}

次に、コマンドラインで以下を実行してみましょう。(実行する際は、必ず今回作成したmain.pyと同じ階層で実行してください。)

uvicorn main:app --reload

その後、コマンドに表示されるリンク(http://127.0.0.1:8000/)に飛んでみましょう。
以下のような画面が表示されるかと思います。

現在、この path にデータを返していないため、データがないと言われているだけですね。@app.get("/hello")この部分でデータを返す path を決めており、今回は(http://127.0.0.1:8000/hello)にデータを返しています。次に、(http://127.0.0.1:8000/docs)に移ってみてください。FastAPIが標準で搭載しているSwaggerUIに移るかと思います。以下の画面が表示されていれば問題ありません。


これを使って、簡単にデータのやり取りを行うことができます。いちいち、ブラウザの path を変えずに確認できるので、非常に便利です。使い方は、調べてみてください。ここまで、できたら実行してみましょう。以下が、データとして返ってきたら成功です。

{
  "Message": "Hello World"
}

次に、パスパラメータとクエリパラメータについてです。ここでは、実装方法のみを紹介するので、中身を知りたい方は以下の記事を参考にすると良いかと思います。
https://zenn.dev/eri_agri/articles/859a3362db8386
https://qiita.com/Marusoccer/items/7ccc7c959ccb5efc080f

パスパラメータ

パスパラメータについてです。以下が、パスパラメータのサンプルコードです。

@app.get("/countrys/{country}")
async def get(country:str):

    return {"Message": country}

まず、1 行目の/countrys/{country}の部分で URL の path を記述しています。path の{country}の部分がパスパラメータで、これが 2 行目で宣言している関数の引数として渡ります。パスパラメータは、URL の中で使える変数みたいなものと思って頂ければ問題ないかと思います。では、実行してみましょう。今回は、入力エリアにJapanと入力して、実行します。以下がデータとして、返ってくれば成功です。

{
  "Message": "Japan"
}

問題なく、入力した値がデータとして返ってきていることを確認できました。もちろん、path も{country}になっている部分は、Japanになっています。以上、パスパラメータの確認でした。

クエリパラメータ

クエリパラメータについてです。以下がサンプルコードになります。

@app.get("/countrys")
async def read_item(country_name:str, city_name:str):
    return {
        "country_name":country_name,
        "city_name":city_name
    }

パスパラメータとの時との違いは、path を記述する部分には変数を与えることなく、関数の引数に変数を渡している部分です。実際に、実行してみましょう。今回は、country_nameにはJapancity_nameにはtokyoを与えてみます。以下の内容が返ってくれば、成功です。

{
  "country_name": "japan",
  "city_name": "tokyo"
}

ちなみに、URL もhttp://127.0.0.1:8000/countrys?country_name=japan&city_name=tokyoと、クエリパラメータになっていることを確認できました。以上、クエリパラメータの確認でした。

モックを使ってデータを処理

ここでは、モックを扱ってfastapiの基本動作を確認します。main.pyと同じ配下に、以下のsample.jsonをコピペしてください。

sample.json
{
  "data": [
    { "Message": "Hello" },
    { "Message": "Hello2" },
    { "Message": "Hello3" }
  ]
}

ディレクトリ内の構造はこのようになっていれば大丈夫です。

ディレクトリ構造
fastapi-sample
            ├─ sample.json
            ├─ main.py
            └─ __init__.py

では、次にモックを読み込みたいと思います。以下がデータを読み込むためのサンプルコードです。

main.py
app = FastAPI()

temp = open('./sample.json','r')
sample_data = json.load(temp)

インスタンスを生成している次の行に書いて頂ければ結構です。このデータを使用して、CRUD 処理を行います。

データの取得

ここでは、データの取得を行います。全てのデータを取得する場合と、指定データの取得を行います。最初に、全データの取得を行います。
以下が、サンプルコードです。

@app.get("/items")
async def get_messages():

    return sample_data

解説です。まず、1 行目でパスを指定しています。今回は、/itemsgetのリクエストを送信を行います。2 行目で関数を宣言して、処理内容を記述します。辞書型の形式にしたデータsample_dataを返すだけですね。これで、実行してみましょう。以下がデータとして返ってくれば、成功です。

{
  "data": [
    {
      "Message": "Hello"
    },
    {
      "Message": "Hello2"
    },
    {
      "Message": "Hello3"
    }
  ]
}

これで、終了と言いたいところですが、fastapiには型定義を行うことができます。これを行うことで、よりエラーが出にくいコードへと仕上げることができます。main.pyと同階層にschema.pyを宣言しましょう。以下のような構造になれば大丈夫です。

ディレクトリ構造
fastapi-sample
            ├─ sample.json
            ├─ main.py
            ├─ schema.py
            └─ __init__.py

schema.pyに以下の内容を記述してください。

schema.py
from pydantic import BaseModel
from typing import List

class Item(BaseModel):
    Message: str

class Items(BaseModel):
    data:List[Item]

ここでは、class を作成しています。これらを利用することで先ほどのレスポンス結果に型を決めることができます。(class が分からない方は調べましょう。)
では、先ほど作成したデータの取得のコードに移ります。今回は、Itemsという型を返すことで型を合わせることができそうです。ということで、記述します。記述すると以下のようになります。

@app.get("/items",response_model=Items)
async def get_messages():

    return sample_data

変わった部分は、1 行目です。response_model=Itemsが追加されています。response_modelはレスポンスで返ってきたデータの型をチェックしています。もし、この型通りの結果が返ってこない場合は、エラーを出します。特にここは、書かずとも処理は動くみたいですが、エラーを極力減らすためにも書くべきですね。(思わぬデータが返って来た時が面倒ですね。。。)

ここまで、理解ができれば特定のデータの取得も特に難しくないです。可能であれば、一度挑戦してみましょう。今まで、やってきた内容を使えば簡単に実装ができます。以下に、答えのコードを貼っておきます。

答え合わせ
@app.get("/items/{id}",response_model=Item)
async def get_message(id:int)

    return sample_data["data"][id]

解説です。まず、今回は配列の中にある一つのデータのみを取得するためパスパラメータでidを受け取ります。あとは取得したidを使用して、配列の中身を参照した結果をデータとして返すだけです。また、今回は単一のデータを返すため方はItemになっています。

データの挿入

ここでは、データの挿入を行います。以下が、サンプルコードです。

@app.post("/items",response_model=Item)
async def create(item:Item):

    sample_data["data"].append({"Message":item.Message})

    return {"Message":item.Message}

見た目、ほとんどgetの処理と変わりませんね。ちなみにですが、データの更新と削除もほとんど変わりません。注目すべきは 1 行目が先ほどと異なりpostになっていることですね。データの挿入を行う際はpostメソッドを使用します。あとは、postクエリパラメータで受け取った値を、配列に追加して、追加したデータの内容をレスポンスとして返しているだけです。レスポンスで返す値は、任意で決めてもらって構いません。個人的に、入力した値を返すのがベストだと思い、ここではレスポンスとして入力したデータを返しています。

データの更新

ここでは、データの更新を行います。以下が、サンプルコードです。

@app.patch("/items/{id}",response_model=Item)
async def update(id:int, item: Item):

    sample_data["data"][id] = {"Message": item.Message}

    return sample_data["data"][id]

まず、データの更新の為patchメソッドを選択します。その後、パスパラメータで変更を行いたい箇所をidで受け取ります。そして、クエリパラメータで変更内容を受け取り、辞書型に変換したsample_dataデータの指定した index に更新を処理を行っています。その後、更新内容をレスポンスとして返しています。

データの削除

ここでは、データの削除を行います。以下が、サンプルコードです。

@app.delete("/items/{id}",response_model=Items)
async def delete(id:int):

    sample_data["data"].pop(id)

    return sample_data

まず、データの削除の為deleteメソッドを選択します。その後、パスパラメータで削除したい Index をidで受け取り、配列の処理と同様に、popメソッドに今回削除したい Index の id を引数に渡して削除は完了です。今回は、返り値として、全データを返すようにしています。

最後に

今回は、fastapiの触り部分をやってみました。学習方法についてですが、いきなり DB に接続してやるのも良いと思いますが、個人的には初心者の方は、まずfastapi役割をしっかりと抑えた上で DB 接続へと移った方が処理内容をより理解できるのではないかと思います。どれがどの役割だったか、中身の理解があやふやになってしまう可能性があります。以上、備忘録でした。

Discussion

ログインするとコメントできます