FastAPI + SQLModel + Strawberryで簡易GraphQLサーバーを作ってみた(Query編)
この記事は
PythonのフレームワークであるFastAPIを使ってGraphQLサーバーを立てるにはどうすれば良いのだろうかと思い、各種ドキュメントを読んで実際に作ってみたのでそのご紹介&備忘録として記事にしました。
使っている主なライブラリとしては以下です。
- FastAPI(フレームワーク)
- SQLModel(ORマッパー)
- Strawberry(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
実装
DB(SQLite)への接続処理の実装
# database.py
from sqlmodel import Session, create_engine
engine = create_engine("sqlite:///database.db")
def create_session():
return Session(engine)
ModelとTypeの実装
返したいデータの型を実装するため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の実装
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の実装
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の作成&サーバーの実装
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