こんにちは。
エンジニアのノブシです。
Webシステム開発にPythonを採用するチームが増えてきていますが、最近プロジェクトで利用した FastAPIについてご紹介します。
FastAPIはPythonのフレームワークの中ではかなり新しいものですが、勢いはあります。
Python Software Foundationの開発者アンケートでもFlask、Djangoに次いで3位となっています。(だいぶ開きはあるようですが)

出典:Python Software Foundation、JetBrains「2020 年度 Python 開発者アンケートの結果」
https://www.jetbrains.com/ja-jp/lp/python-developers-survey-2020/

FastAPIの特徴

軽量

FastAPIはパフォーマンスで注目されることが多く、自分ではベンチマークまでは行えていないのですが、色々なサイトで評価されている方のベンチマークを見ていると、パフォーマンスはpythonのフレームワークではトップクラスで、Flask、Djangoの10倍出る場合もあるようです。

出典:TechEmpower Framework Benchmarks
https://www.techempower.com/benchmarks/

APIに特化

FastAPIはデフォルトではテンプレートエンジンを含みません。
なのでシングルページアプリケーションのバックエンドAPIサーバーを構築するようなケースでは余計なものがなくていいかもしれません。
また、ORMも含まれていません。
これらがデフォルトで入っていないので好きなものを使える、というのも利点かも知れません。

OpenAPIと自動ドキュメント生成

OpenAPIの機能が組み込まれていて、デコレータに情報を記述するとそれを元にOpenAPIのドキュメントが生成できます。
また、ドキュメント生成機能が組み込まれており、動的にSwaggerUIのドキュメントが生成できます。
ドキュメントの生成は /docs にアクセスするだけです。

このUIはAPIの実行機能も備わっていますので動作確認やインタラクティブなテストにそのまま活用可能です。

DI

DIの機能も備えます。
下記のコードでは /info APIが呼び出された時に _get_some_info 関数も呼び出されます。もちろんテスト時にはモックに差し替えることも可能です。

class Info(BaseModel):
    some_info: str

async def _get_some_info() -> str:
    return "some-info"

@app.get(
    "/info",
    response_model=Info,
)
def get_info(
    info_id: int,
    some_info: str = Depends(_get_some_info),
) -> Info:
    return Info(some_info=some_info)

tokenの認証を行う場合は基本的にこの仕組みを使うことになります。
ただ、基本的にはここ(エントリーポイント)でしか使えないため、モデル等でDIを使いたい場合はInjector等、別途ライブラリを導入する必要があります。

導入に至った経緯

導入したプロジェクトは新規のプロジェクトで依存するような他のプロジェクトも無かったため、しがらみが一切無い状態でした。
なのでどうせなら今時のフレームワークを使ってみようという割と軽いノリで導入してみました。
バージョンが1.0に達していないというあたりはちょっと不安要因でしたが。

導入方法

導入は公式サイトの説明の通りにやれば問題ないはずですが、せっかくなので以下を使う前提で説明しようと思います。

-Dockerコンテナで構築します
-公式では pip を使っていますが、 Poetry を使います
-公式ではUvicornを使っていますが、HTTP/2に対応している Hypercorn を使います。

Dockerが使える環境で、任意のディレクトリに以下の3ファイルを配置してください。

/
  Dockerfile
  pyproject.toml
  main.py

Dockerfile

FROM python:3.10-alpine3.15

WORKDIR /app

CMD ["hypercorn", "main:app", "--bind", "0.0.0.0:80", "--access-logfile", "-", "--error-logfile", "-"]

ENV PYTHONPYCACHEPREFIX=/var/cache/python
ENV PYTHONPATH=/app

EXPOSE 80

ARG POETRY_VERSION=1.1.12

# Install Poetry
RUN apk update --no-cache &&\
    apk add --no-cache curl autoconf g++ libtool make libffi-dev &&\
    curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py |\
    POETRY_HOME=/opt/poetry POETRY_VERSION=${POETRY_VERSION} python &&\
    cd /usr/local/bin &&\
    ln -s /opt/poetry/bin/poetry &&\
    poetry config virtualenvs.create false &&\
    apk del --no-cache curl autoconf g++ libtool make &&\
    rm -rf /tmp/*

# Install Libraries
COPY ./pyproject.toml ./poetry.lock* /app/
RUN apk update --no-cache &&\
    apk add --no-cache autoconf g++ libtool make &&\
    poetry install --no-root &&\
    apk del --no-cache autoconf g++ libtool make &&\
    rm -rf /tmp/*

# Uninstall Poetry
RUN apk update --no-cache &&\
    apk add --no-cache curl autoconf g++ libtool make libffi-dev &&\
    unlink /usr/local/bin/poetry &&\
    curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py |\
    POETRY_HOME=/opt/poetry POETRY_VERSION=${POETRY_VERSION} python - --uninstall &&\
    apk del --no-cache curl autoconf g++ libtool make &&\
    rm -rf /tmp/*

COPY . /app

pythonのコンテナをベースにして、FastAPIも含め、必要なライブラリはPoetryでインストールするようにしています。
実行時にはPoetryは不要なのでライブラリインストール後はアンインストールしています。
コンテナサイズは小さい方が良いですからね。
pyproject.tomlかpoetry.lockが変更された場合はライブラリインストールからやり直す設定です。
実運用時にはマルチステージビルドを使って --no-dev オプションを使い分けた方が良いでしょう。

pyproject.toml

[tool.poetry]
name = "fastapi-sample"
version = "0.1.0"
description = ""
authors = []

[tool.poetry.dependencies]
python = "3.10.*"
fastapi = "0.73.*"
Hypercorn = "0.13.*"

[tool.poetry.dev-dependencies]

[build-system]
requires = ["poetry-core=1.1.*"]
build-backend = "poetry.core.masonry.api"

Poetryのプロジェクトファイルです。
サンプルではpoetry.lockを省略していますが、実運用ではpoetry.lockも作成しておくべきです。

main.py

from fastapi import Depends
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Info(BaseModel):
    some_info: str

async def _get_some_info() -> str:
    return "some-info"

@app.get(
    "/info/{info_id}",
    response_model=Info,
)
def get_info(
    info_id: int,
    some_info: str = Depends(_get_some_info),
) -> Info:
    return Info(some_info=some_info)




アプリケーション本体です。
固定のjsonを返すだけのアプリです。

実行

実行してみましょう。

sudo docker build -t sample .
sudo docker run -p 80:80 --name sample -d sample
curl http://localhost/info/1
{"some_info":"some-info"}

jsonが返ってきました。

使ってみた所感

余計な機能が無いので設定等が必要なく、始めるまではとても簡単です。
またドキュメント自動生成がとても便利で、テストチームの簡単なテストはそこでやって貰えば良いので楽ちんです。
FastAPIはasync/awaitもデフォルトでサポートしていて、それも売りの一つなんですが、
今のプロジェクトではORMライブラリとの相性が悪くほとんど使えていません。
そこももっと活用してみたいですね。

SHARE

  • facebook
  • twitter

SQRIPTER

AGEST Engineers

AGEST

記事一覧

AGESTのエンジニアが情報発信してます!

株式会社AGEST

Sqriptsはシステム開発における品質(Quality)を中心に、エンジニアが”理解しやすい”Scriptに変換して情報発信するメディアです

  • 新規登録/ログイン
  • 株式会社AGEST