16
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

FastAPIでMongoDBを使用する

本記事は東京学芸大学 櫨山研究室 Advent Calendar 2020の22日目の記事になります.

はじめに

みなさんはFastAPIを使用していますか??
pythonのWebフレームワークではDjango, Flaskなどがよく使用されますが,今回はFastAPIについての記事になります.
RDSとの接続はSQLAlchemy(Mysql)というORMがdocumentで紹介されていますが今回はMongoDBとの接続をpymongoで行い,CRUD操作を行っていきます.
確認はMongoDBをGUI操作できるmongo express,FastAPIの自動生成ドキュメント(Swagger UI)で確認していきます.
実行はDockerでの構築になります.GitHubはこちらから.

開発環境

  • MacOs:Mac mini (2018) macOS Big Sur ver.11.1
  • Docker:20.10.0
  • docker-compose:1.27.4
  • MongoDB:4.2

1 準備

1.1 ディレクトリ構成

.
├── Makefile
├── README.md
├── docker
│   ├── docker-compose.yml
│   ├── fast_api
│   │   └── Dockerfile
│   ├── mongo-express
│   │   └── Dockerfile
│   ├── mongo_db
│   │   ├── Dockerfile
│   │   ├── configdb
│   │   ├── db
│   │   └── mongo-init.js
│   └── wait-for-it.sh
└── fast_api
    ├── Makefile
    ├── __init__.py
    ├── __pycache__
    ├── app.py
    ├── database.py
    ├── requirements.txt
    ├── routers
    │   ├── __init__.py
    │   ├── __pycache__
    │   └── posts.py
    ├── sample-json
    │   ├── sample.json
    │   └── update.json
    └── tests
        ├── __pycache__
        └── test_initial.py

dockerディレクトリに,docker-compose.ymlとそれぞれのDockerfileを配置します.mongoのdbデータなどもここに配置します.
また,docker-composeでの実行はルートディレクトリのMakefileで行います.

1.2 ファイルの準備

それぞれのファイルの中身は以下になります.

Makefile

Makefile
.PHONY: setup
setup:
    docker-compose -f docker/docker-compose.yml build
    $(MAKE) install

.PHONY: install
install:
    docker-compose -f docker/docker-compose.yml run --rm fast_api make setup

.PHONY: start
start:
    docker-compose -f docker/docker-compose.yml up --remove-orphans

.PHONY: start.background
start.background:
    docker-compose -f docker/docker-compose.yml up -d --remove-orphans

.PHONY: stop
stop:
    docker-compose -f docker/docker-compose.yml down

.PHONY: pytest
pytest:
    docker-compose -f docker/docker-compose.yml run --rm fast_api make test
fast_api/Makefile
# pipでの依存関係の解決 (.pipディレクトリに依存関係を全て入れる)
setup:
    pip3 install --upgrade pip
    pip3 install -r requirements.txt -t ./.pip

test:
    python -m pytest -v

使用コマンドは以下になります.
- setupでパッケージの取得
- startでアプリケーションを実行
- start.backgroundでバックグラウンドでアプリケーションを実行
- stopでアプリケーションを停止
- pytestでpytestの実行

Dockerfile(3つ)

FastAPI
docker/fast_api/Dockerfile
FROM python:3.7-alpine # alpineで軽量化

ENV LANG C.UTF-8
ENV TZ Asia/Tokyo

RUN apk add --update --no-cache make bash gcc g++ tzdata git\
  && pip install --upgrade pip \
  && pip install uvicorn==0.11.8

ENV PYTHONUNBUFFERED 1
ENV PYTHONPATH /app/.pip

ADD docker/wait-for-it.sh /usr/local/bin/wait-for-it.sh
RUN chmod +x /usr/local/bin/wait-for-it.sh
MongoDB
docker/mongo_db/Dockerfile
FROM mongo:4.2
mongo express
docker/mongo_express/Dockerfile
FROM mongo-express

ADD wait-for-it.sh /usr/local/bin/wait-for-it.sh
RUN chmod +x /usr/local/bin//wait-for-it.sh

docker-compose.yml

docker/docker-compose.yml
version: "3.7"

services:
  fast_api:
    container_name: fast_api
    build:
      context: ../.
      dockerfile: ./docker/fast_api/Dockerfile
    working_dir: /app
    volumes:
      - ../fast_api:/app:cached
    ports:
      - 8000:8000
    tty: true
    environment:
      MONGO_DATABASE_NAME: mongodb
      MONGO_DATABASE_USER: root
      MONGO_DATABASE_PASSWORD: root
      MONGO_DATABASE_CONTAINER_NAME: mongo_db
      MONGO_DATABASE_PORT: 27017

    command: wait-for-it.sh mongo_db:27017 --timeout=30 -- uvicorn app:app --reload --host 0.0.0.0 --port 8000
    networks:
      - fastapi-mongo-network

  mongodb:
    container_name: mongo_db
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: root
    build:
      context: .
      dockerfile: ./mongo_db/Dockerfile
    ports:
      - 27017:27017
    tty: true
    volumes:
      - ./mongo_db/db:/data/db
      - ./mongo_db/configdb:/data/configdb
      - ./mongo_db/mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js
    networks:
      - fastapi-mongo-network

  mongo-express:
    container_name: mongo_express
    build:
      context: .
      dockerfile: ./mongo-express/Dockerfile
    environment:
      ME_CONFIG_MONGODB_ADMINUSERNAME: root
      ME_CONFIG_MONGODB_ADMINPASSWORD: root
      ME_CONFIG_MONGODB_SERVER: mongo_db
    ports:
      - 8081:8081
    tty: true
    command: wait-for-it.sh mongo_db:27017 --timeout=30 -- node app
    restart: unless-stopped
    networks:
      - fastapi-mongo-network

networks:
  fastapi-mongo-network:
    driver: bridge

wait-for-it.sh

こちらから拝借しています.
他のコンテナの起動を待ってから起動ができます(depend onは順序だけ).

requirements.txt

fast_api/requirements.txt
aiofiles==0.6.0
fastapi==0.61.1
pymongo==3.11.0
pytest==6.1.0
python-dateutil==2.8.1
setuptools==49.2.0
uvicorn==0.11.8

必要なパッケージを記述します.

app.py

fast_api/app.py
import uvicorn
from fastapi import FastAPI
from routers import posts

app = FastAPI(prefix="/")

app.include_router(posts.router, prefix="/post") # routingを階層的にし読み込む

@app.get('/')
def get_hello():
    return {'message': 'Hello from FastAPI Server!'}

if __name__ == '__main__':
    uvicorn.run(app, host="0.0.0.0", port=8000)

アプリケーションの起動を行います.FlaskではBuleprintを使用し,ルーティングを階層的にしていましたが,FastAPIではinclude_routerを使用し,ルーティングの追加ができます.

posts.py

routers/posts.py

from fastapi import APIRouter, Body

from bson.objectid import ObjectId
from bson.json_util import dumps, loads

from database import db

router = APIRouter()
@router.post('')
def create_post(body=Body(...)):
    """postの作成

    ----------
    Parameters:

    body: body
        任意のjson
    """
    post = body['payload']
    db.posts.insert(post)
    return {'post': "ok"}

@router.get('')
def read_post():
    """postの取得

    ----------
    Parameters:

    なし
    """
    db_post = db.posts.find_one()
    return {'item': dumps(db_post)}

@router.get('/{id}')
def read_post_by_id(id: str):
    """postの取得(id)

    ----------
    Parameters:

    id: str
        オブジェクトID
    """
    db_post = db.posts.find_one({'_id': ObjectId(id)})
    print(db_post)
    return {'item': dumps(db_post)}

@router.put('')
def update_post(body=Body(...)):
    """postの更新(id)

    ----------
    Parameters:

    body: body
        任意のjson
    """
    post = body['payload']
    _id = post['_id']
    title = post['title']
    text = post['text']
    db.posts.update_one(
        {'_id': ObjectId(_id)},
        {'$set':
            {
                "title": title, 'text': text
            }
        }
    )
    return {'update': "ok"}

@router.delete('/')
def delete_post_by_id(id: str):
    """postの削除(id)

    ----------
    Parameters:

    id: str
        オブジェクトID
    """
    db.posts.delete_one(
        {'_id': ObjectId(id)}
    )
    return {'delete': "ok"}

postsのCRUDを記述します.今回は確認だけなのでエラーハンドリングは行っていません🙇‍♂️
書き方はFlask似ていて自分自身はとても書きやすい印象です.
Swagger UIでdocsが参照できるので記述しておくと良いです(書き方になれていません...)!
注意点としては,MongoDBから取得したオブジェクトはそのままではjson形式にできないので,dumpsを行っています.それぞれ好きな形にしてもらえればと思います.

database.py

fast_api/database.py
import os
from pymongo import MongoClient

MONGO_DATABASE_NAME = os.environ.get("MONGO_DATABASE_NAME")  # mongodb
MONGO_DATABASE_USER = os.environ.get("MONGO_DATABASE_USER")  # root
MONGO_DATABASE_PASSWORD = os.environ.get("MONGO_DATABASE_PASSWORD")  # root
MONGO_DATABASE_CONTAINER_NAME = os.environ.get(
    "MONGO_DATABASE_CONTAINER_NAME")  # mongo_db
MONGO_DATABASE_PORT = int(os.environ.get("MONGO_DATABASE_PORT"))  # 27017

DATABASE_URL = "%s://%s:%s@%s:%d" % (
    MONGO_DATABASE_NAME, MONGO_DATABASE_USER, MONGO_DATABASE_PASSWORD, MONGO_DATABASE_CONTAINER_NAME, MONGO_DATABASE_PORT)

client = MongoClient(DATABASE_URL)
db = client.first_test  #database名がfirst_test

MongoDBとの接続の設定を記述します.それぞれの環境変数はdocker-composer.ymlで設定しています.
MongoDBはNoSQLなので,作成されていないものは勝手に作成してくれます.
pymongoを使用することにより簡単に記述することができます.
使用するデータベース名は以下でしています.

db = client.first_test  #database名がfirst_test

2 アプリケーションの実行

以下の順番でコマンドをうち,アプリケーションを起動してください.
今回は
1. Create
2. Read (id無/有)
3. Update
4. Delete

の順番で進めていきます.

console
$ make setup
$ make start

http://localhost:8000/docsにアクセスしSwagger UIを確認します.
スクリーンショット 2020-12-22 12.14.59.png

3. CRUD操作

3.1 Create

API実行

以下のfast_api/sample-json/sample.jsonをコピーしRequest bodyに入れExecuteします.

fast_api/sample-json/sample.json
{
  "payload":{
    "title" : "初投稿!",
    "text" : "hogehoge"
  }
}

スクリーンショット 2020-12-22 12.18.50.png

MongoDBの確認

http://localhost:8081にアクセスしMngoDBを確認します.
[first_test] → [posts] → [作成されてたpost]の順に進むと以下のようなDocumentが作成されていると思います.

スクリーンショット 2020-12-22 12.24.11.png

3.2 Read(id無/有)

id無しの場合

スクリーンショット 2020-12-22 12.27.36.png

id有りの場合

さきほど作成したpostのオブジェクトID(投稿確認時は5fe1661f03100427fb1e8cd3)を引数に実行します.

スクリーンショット 2020-12-22 12.29.03.png

3.3 Update

同じくオブジェクトID(投稿確認時は5fe1661f03100427fb1e8cd3)を引数に実行しますが,今回はjsonの中に入れて実行していきますので,fast_api/sample-json/update.json_idの部分を変更して実行します.

fast_api/sample-json/update.json
{
  "payload":{
    "_id" : "5fe1661f03100427fb1e8cd3",
    "title" : "投稿の編集",
    "text" : "fugefuge"
  }
}

スクリーンショット 2020-12-22 12.36.21.png

mongo expressの方で変更されているかを確認します.

スクリーンショット 2020-12-22 12.37.06.png

3.4 Delete

同じくオブジェクトID(投稿確認時は5fe1661f03100427fb1e8cd3)を引数に実行します

スクリーンショット 2020-12-22 12.38.13.png

mongo expressの方で削除されているかを確認します.

スクリーンショット 2020-12-22 12.38.52.png

おわり

本記事ではFastAPIでMongoDBを使用しCRUD操作について扱いました.
参考になれば幸いです.

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
16
Help us understand the problem. What are the problem?