本記事は東京学芸大学 櫨山研究室 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
.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
# 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
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
FROM mongo:4.2
mongo express
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
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
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
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
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
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-compose.yml
で設定しています.
MongoDBはNoSQLなので,作成されていないものは勝手に作成してくれます.
pymongoを使用することにより簡単に記述することができます.
使用するデータベース名は以下で指定します.
db = client.first_test #database名がfirst_test
2 アプリケーションの実行
以下の順番でコマンドをうち,アプリケーションを起動してください.
今回は
- Create
- Read (id無/有)
- Update
- Delete
の順番で進めていきます.
$ make setup
$ make start
http://localhost:8000/docs
にアクセスしSwagger UIを確認します.
3. CRUD操作
3.1 Create
API実行
以下のfast_api/sample-json/sample.json
をコピーしRequest bodyに入れExecuteします.
{
"payload":{
"title" : "初投稿!",
"text" : "hogehoge"
}
}
MongoDBの確認
http://localhost:8081
にアクセスしMngoDBを確認します.
[first_test] → [posts] → [作成されてたpost]の順に進むと以下のようなDocumentが作成されていると思います.
3.2 Read(id無/有)
id無しの場合
id有りの場合
[さきほど作成したpost](## 2.1 Create)のオブジェクトID(投稿確認時は5fe1661f03100427fb1e8cd3
)を引数に実行します.
3.3 Update
同じくオブジェクトID(投稿確認時は5fe1661f03100427fb1e8cd3
)を引数に実行しますが,今回はjsonの中に入れて実行していきますので,fast_api/sample-json/update.json
の_id
の部分を変更して実行します.
{
"payload":{
"_id" : "5fe1661f03100427fb1e8cd3",
"title" : "投稿の編集",
"text" : "fugefuge"
}
}
mongo expressの方で変更されているかを確認します.
3.4 Delete
同じくオブジェクトID(投稿確認時は5fe1661f03100427fb1e8cd3
)を引数に実行します
mongo expressの方で削除されているかを確認します.
おわり
本記事ではFastAPIでMongoDBを使用しCRUD操作について扱いました.
参考になれば幸いです.