📝

FastAPI + SQLModel + Strawberryで簡易GraphQLサーバーを作ってみた(Query編)

2023/01/15に公開

この記事は

PythonのフレームワークであるFastAPIを使ってGraphQLサーバーを立てるにはどうすれば良いのだろうかと思い、各種ドキュメントを読んで実際に作ってみたのでそのご紹介&備忘録として記事にしました。

使っている主なライブラリとしては以下です。

注意

Strawberryの仕様変更によりPydanticに関する機能に変更が生じる可能性があることを公式がアナウンスしています。
SQLModelはPydanticをベースにしており、その影響を受ける可能性があるのでご留意ください。

使用技術とバージョン

os: debian
sqlite3: 3.34.1

python: 3.10.8
fastapi: 0.88.0
sqlmodel: 0.0.8
strawberry-graphql: 0.142.2
uvicorn: 0.20.0

実装

https://github.com/YutaSawagami/fastapi_sqlmodel_graphql

DB(SQLite)への接続処理の実装

# database.py

from sqlmodel import Session, create_engine

engine = create_engine("sqlite:///database.db")


def create_session():
    return Session(engine)

ModelとTypeの実装

https://sqlmodel.tiangolo.com/#create-a-sqlmodel-model
https://strawberry.rocks/docs/integrations/pydantic
返したいデータの型を実装するためSQLModelのチュートリアルを参考にHeroクラスを作成します。

GraphQLで扱うにはTypeに変換する必要があるので
@strawberry.experimental.pydantic.typeにHeroクラスを渡して変換します。
その際all_fieldsにTrueを渡すと、Heroクラスで定義したフィールドをそのまま全て再利用できます。

# hero.py

from typing import Optional

import strawberry
from sqlmodel import Field, SQLModel


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: Optional[int] = None


@strawberry.experimental.pydantic.type(model=Hero, all_fields=True)
class HeroType:
    pass

Queryの実装

https://strawberry.rocks/docs/general/queries

In GraphQL you use queries to fetch data from a server. In Strawberry you can define the data your server provides by defining query types.
By default all the fields the API exposes are nested under a root Query type.

GraphQLはqueryを使ってデータを取得します。
StrawberryではrootのQueryを用意して、その配下にqueryを定義していくことでデータを取得することが出来るようです。

rootのQueryを定義して、先ほど定義したHeroTypeをクライアントが取得できるようにHeroの一覧を返すherosとidを基にHeroを返すheroというqueryをフィールドに追加します。

また、クライアントから送られてきたqueryを解決するためのresolver関数を指定します。

# query.py

from typing import Optional

import strawberry

from hero import HeroType
from resolver import get_hero_by_id, get_heros

@strawberry.type
class Query:
    heros: list[HeroType] = strawberry.field(resolver=get_heros)
    hero: Optional[HeroType] = strawberry.field(resolver=get_hero_by_id)

Resolverの実装

https://strawberry.rocks/docs/types/resolvers#resolvers

A resolver is a Python function that returns data.

Resolverはデータを返すための関数です。

# resolver.py

from sqlmodel import select

from database import create_session
from hero import Hero


def get_heros():
    """ヒーローの一覧を返す."""
    with create_session() as session:
        statement = select(Hero)
        return session.exec(statement).all()


def get_hero_by_id(id: int):
    """idを基にヒーローを返す."""
    with create_session() as session:
        statement = select(Hero).filter(Hero.id == id)
        return session.exec(statement).first()

schemaの作成&サーバーの実装

https://strawberry.rocks/docs/integrations/fastapi
https://strawberry.rocks/docs/types/schema
最後にmain.pyを作成して、サーバーを実装していきます。

先ほど実装したQueryを渡してschemaを作成し、さらにこのschemaを読み込ませてGraphQLRouterインスタンスを作成します。
そしてこれをFastAPIインスタンスのinclude_router関数に渡して、/graphqlにアクセスしたらGraphQLのページが開かれるようにします。

また、動作確認用としてサーバーの立ち上げ時にHeroをDBに登録する処理と終了時にHeroを削除する処理をapp.on_eventで設定します。

※ schemaとはGraphQL APIの仕様であり、どのようなqueryが使えるのか等が定義されるものだそうです(違っていたらすみません...)。

# main.py

import strawberry
from fastapi import FastAPI
from sqlmodel import select
from strawberry.fastapi import GraphQLRouter

from database import create_session
from hero import Hero
from query import Query


schema = strawberry.Schema(Query)
graphql_app = GraphQLRouter(schema)

app = FastAPI()
app.include_router(graphql_app, prefix="/graphql")


@app.on_event("startup")
def register_heros():
    """サーバー起動時にHeroを登録"""
    with create_session() as session:
        session.add_all(
            [
                Hero(name="Deadpond", secret_name="Dive Wilson"),
                Hero(name="Spider-Boy", secret_name="Pedro Parqueador"),
                Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48),
            ]
        )
        session.commit()


@app.on_event("shutdown")
def delete_heros():
    """サーバー終了時にHeroを削除"""
    with create_session() as session:
        statement = select(Hero)
        heros = session.exec(statement).all()
        for hero in heros:
            session.delete(hero)
        session.commit()

動作確認

以下のコマンドでサーバーを起動します

uvicorn main:app

起動を確認後、http://127.0.0.1:8000/graphql にアクセスするとGraphQL用のページが表示されるはずです

heros

左側に以下のクエリを書きCtrl + Enterを入力すると実行されます。
Heroのリストが返されることを確認できるはずです。

{
  heros{
    id,
    name,
    secretName,
    age
  }
}

hero

以下のクエリを書き、同様にCtrl + Enterで実行。
idが1のDeadpondのデータが返されたことを確認できるはずです。

{
  hero(id: 1){
    name,
    secretName,
    age
  }
}

まとめ

拙いですがFastAPIでGraphQLサーバーを実装することが出来ました!
今回はデータを取得するためのQueryの実装でしたが、次回はデータを作成したり更新するためのMutationの実装を載せようと思います。

Discussion