目次
1. はじめに
内容・目的
初心者が、docker上でpostgresqlに対してfastapi経由でデータの登録等ができるようにやったことをまとめた。
サクッと、dockerでpostgresとfastapiを動かしてみたいと初心者が思ったときに、
完全に合致する内容で書いてある記事があまりなかったので、過去の自分を助けるという意味合いも込めて書いた。
想定している対象
pythonをつかってwebアプリを作成してみたいけど、何から手をつければよいかわからない人向け
想定読了時間
読むだけなら10分程度
コードを動かしてみるなら30分程度
2.やること
ローカル環境上でFastAPI経由でデータベースにデータを登録、更新、削除する。
要件・仕様
取得
idを指定し、データベースからユーザー情報を取得する
ユーザーはidを入力
- 取得が成功した場合 返り値:200_ok,登録情報
- 取得が失敗した場合 返り値:400_error,"user_mail is not found"
登録時
データベースにユーザーを登録する。
ユーザーは、メールアドレス、PW、名前を入力。
- 登録が成功した場合 返り値:200_ok,登録情報
- 登録が失敗した場合 返り値:500_error,"user_mail is wrong"
削除時
メールアドレスおよびPWが入力し、正しい場合、ユーザーデータを削除する。
- 削除が成功した場合、返り値:200_ok,削除した情報
- 削除が失敗した場合、返り値:404_error ,"not match"
を返す
更新時
メールアドレスおよびPWを入力し、正しい場合、ユーザーデータのPWを更新できる
- 更新が成功した場合 返り値:200_ok,更新した情報
- 更新が失敗した場合 返り値:404_error,"user_mail is wrong"
を返す
3.前提条件
dockerを利用する環境構築完了していること。
実行環境について
docker desktop for windowsで実行。
- docker desktop for windowsのインストールはこちらから
4.言語や必要なフレームワーク
今回利用するもの一覧は下記の通り。
- Python
- docker
- FastAPI
- Postgresql
- SQLAlchemy
選定理由
Python
Qiita でもタグランキングで上位に位置し、初心者向きのため。
Docker
コンテナツールの中でデファクトスタンダードのため。
FastAPi
ドキュメントがしっかりしているため。
Postgresql
過去に触ったことがあったため。
~~ MySQLでもやってみます、いつか~~
SQLAlchemy
SQLツールキットのデファクトスタンダードであるため。
5.各種キーワード説明
docker
コンテナとは、ある環境から別の環境へと移動した際にアプリケーションを迅速かつ確実に実行できるように、コードとその依存関係をすべてパッケージ化したソフトウェアの標準単位である。Dockerkコンテナイメージは、軽量、スタンドアローンな実行可能なソフトウェアパッケージである。
引用:docker(https://www.docker.com/resources/what-container/)
Dockerは仮想化技術の一つで、コンテナ型に分類される。
仮想化技術は、ホスト型、ハイパーバイザー型、コンテナ型の三種類ある。
引用:(https://www.itmanage.co.jp/column/virtualization-server-integration/)
Dockerが属するコンテナ型は、ホスト型、ハイパーバイザー型よりも構築、破棄が容易なので、コンテナ型を採用。
FastAPI
pythonでAPIを構築するためのWebフレームワーク。
Postgresql
オープンソースのリレーショナルデータベース管理システム(RDBS)の一つ。
リレーショナルデータベース管理システム(RDBS)
データベースは、階層型、ネットワーク型、リレーショナル型の三つに分類される。
リレーショナルデータベースは列と行で構成されているもの。
イメージとしては、エクセルが近い。
SQLAlchemy
SQLAlchemyはpython SQKのツールキットで、Object Relational Mapperである。
要するに、postgresqlのデータベースを操作する(selectやdelete)のをpythonで実行できるようにしたツール
- Object Relational Mapperについて
Object Relational Mapper/Object Relational Mapping(ORM)はデータベースとオブジェクト指向プログラミング言語の間の非互換なデータを変換するプログラミング技法。
6.構成
構成図
ローカルマシンにdockerコンテナを作成する。
作成するコンテナはdatabase用とwepapp用の二つ
構成は下記のようになる。
データベース
Column | value |
---|---|
user_id | int |
user_mail | str |
user_name | str |
user_password | str |
7.手順
ディレクトリ構成
下記のようにディレクトリを構成する
--app--
| |-app
| | |-router
| | |-user.py
| |
| |-crud.py
| |-database.py
| |-Dockerfile
| |-main.py
| |-models.py
| |-requirements.txt
| |-schemas.py
|
|-docker-compose.yml
コンテナの準備
Dockerfile
FROM python:3
WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
RUN apt-get update && apt install -y tzdata && apt-get clean && rm -rf /var/lib/apt/lists/*
ENV TZ Asia/Tokyo
COPY . .
docker-compose.yml
version: '3'
services:
webapp_fastapi:
build:
context: ./app ## docker build コマンドを実行した時のカレントワーキングディレクリをビルドコンテキストと呼ぶ。
dockerfile: Dockerfile
volumes:
- .:/usr/src/app
- /var/run/docker.sock:/var/run/docker.sock
command: uvicorn main:app --reload --workers 1 --host 0.0.0.0 --port 8000
ports:
- 8000:8000
depends_on:
- postgres_fastapi
postgres_fastapi:
image: postgres:13.1-alpine
environment:
TZ: Asia/Tokyo
POSTGRES_DB: postgres_test
POSTGRES_USER: postgres_test
POSTGRES_PASSWORD: postgres_sample
PGDATA: /var/lib/postgresql/data/pgdata
volumes:
- ./app:/var/lib/postgresql/data
ports:
- 5432:5432
models.py
postgresqlのテーブルのカラムの設定は下記の通り
from sqlalchemy import String, Column, Integer
from sqlalchemy.ext.declarative import declarative_base
from database import Base
from uuid import uuid4
# データモデルの作成
# sqlalchemyによってデータベースのテーブルが生成
class User(Base):
__tablename__ = 'user'
user_id = Column(Integer, primary_key=True, autoincrement= True) # intで連番で採番される
user_mail = Column(String)
user_name = Column(String)
user_password = Column(String)
main.py
from typing import Optional
from fastapi import FastAPI
import models
import database
from sqlalchemy.orm import sessionmaker
from router import user
# DBコンテナとappコンテナが更新するために必要なもの
engine = database.engine
models.Base.metadata.create_all(engine)
DBsession = sessionmaker(bind=engine)
session = DBsession()
# パスオペレーションの設定
app = FastAPI()
app.include_router(user.router)
crud.py
from sqlalchemy.orm import Session
import models
from sqlalchemy.exc import DBAPIError
from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponse
from fastapi import HTTPException
# 概要
# dbに対して操作するコード
# user_idをキーにuserの情報を取得する
def get_user(session:Session, user_id:int) -> any:
response = session.query(models.User).filter(models.User.user_id == user_id).first()
if response is None:
raise HTTPException(status_code=404, detail="user_id is not found")
return response
# userを新規作成すeる
def create_user(session, user_name:str, user_mail:str ,user_password: str) -> any:
user_obj = models.User(
user_mail = user_mail,
user_name = user_name,
user_password = user_password
)
try:
session.add(user_obj)
except:
raise HTTPException(status_code=500, detail='error')
session.commit()
session.refresh(user_obj)
return user_obj
# userのpasswordを更新する。
def update_password(session:Session, user_id:int, user_mail:str,user_password:str) -> any:
response = get_user(session,user_id)
# user_idがある場合のみの処理でよい。
# user_idがない場合は、get_user関数でHTTPexceptionが返るので、この関数内では不要。
if response:
if response.user_mail == user_mail:
response.user_password = user_password
session.commit()
# コミットしたresponseを取得するには、再度sessionを取得する必要がある。
session.refresh(response)
return response
elif response.user_mail != user_mail:
raise HTTPException(status_code=404, detail='user_mail is wrong')
# userを削除する
def delete_user(session:Session, user_id:int,user_mail:str, user_password:str):
# user_idがデータベースに存在しているかを確認する
response = get_user(session, user_id)
# user_idがデータベースに存在している場合
if response:
# idのuserのデータが空ではなく、かつ、malとpasswordともに合致しているか
if response.user_mail == user_mail and response.user_password == user_password:
session.delete(response)
session.commit()
return response
else:
raise HTTPException(status_code=404, detail="not match")
user.py
from fastapi import APIRouter,Depends, HTTPException
from database import SessionLocal, engine
import models,crud
import schemas
from sqlalchemy.orm import Session
import typing
# 概要
# APIの受け口
# 依存関係を作成する
# リクエスト毎に独室したデータベースセッション(データベースへの接続)を持つ必要がある。
models.Base.metadata.create_all(bind = engine)
router = APIRouter()
def get_db():
db = SessionLocal()
try:
yield db # returnではなくyieldがを記載することで繰り返し利用することが可能。
finally:
db.close
# ユーザーを取得する
@router.get("/user/get")
def read_user(user_id:int, db:Session=Depends(get_db)) -> any:
return crud.get_user(db, user_id)
# idをキーに取得したデータを返す
# ユーザーを新規作成
@router.post("/user/create")
def create_user(user_name:str,user_mail:str, user_password:str, db:Session = Depends(get_db)):
return crud.create_user(db, user_name,user_mail, user_password)
# passwordを更新する。
@router.put("/user/update")
def update_password(user_id:int, user_mail:str, user_password:str, db:Session = Depends(get_db) ):
return crud.update_password(db,user_id, user_mail,user_password)
# ユーザーを削除する
@router.delete("/user/delete")
def delete_user(user_id:int, user_mail:str, user_password:str, db:Session = Depends(get_db)):
return crud.delete_user(db, user_id,user_mail, user_password)
8. 確認
作成
http:127.0.0.1/docsから下記内容でuserを作成する。
項目 | 内容 |
---|---|
name | nobunaga_oda |
nobunaga.oda@mail | |
password | owari |
キャプチャの通り、crud.py上で記載したレスポンスが返ってきている。
取得
成功する場合
idを引き数に作成したユーザー情報が出力されるかを確認する。
先ほど作成したidで実行し、ユーザー情報が返されている。
失敗する場合
データベースに登録されていないユーザーidを入力する。
[2_user_data]
下記の通り、レスポンスが返ってくる。
response bodyには。HHTPExcetptionのdetailが返ってくる
更新
passwordの更新を行う。
失敗する場合
- 間違ったmailを入力し、"user_mai wrong"が返ってくるかを確認
- 間違ったidを入力し、"user_id is not found"が返ってくるかを確認
成功する場合
正しい情報を入力し、PWが更新されるかを確認
削除
別途ユーザーを作成
削除するユーザー情報の確認
失敗する場合
- user_idがない場合
- mailが間違っている場合
- passwordが間違っている場合
成功する場合
- 削除成功のレスポンス
- 削除したuser_idでuser情報の取得。削除されているので見つからない。
まとめ
main.pyでfastapiの実行ファイルをルーティングしている。
今回は、routerディレクトリ配下のuser.pyのみ。
user.pyは、http://127.0.0.1/docsで表示される。
fastapiのuser.pyをうけて、crud.pyを実行する。
今回は、ユーザーのデータベースのみで、crud.pyからデータベースに対してアクセスし、削除、更新、作成を行う。
参考リンク
- docker(https://www.docker.com/resources/what-container/)
- 仮想化について:(https://www.itmanage.co.jp/column/virtualization-server-integration/)
- Fastapi:(https://fastapi.tiangolo.com/ja/)
- docker-composeの環境構築:(https://python-to.hateblo.jp/entry/2021/09/08/000000)
- https://qiita.com/AQUA651/items/b30eaffbc77a252c3c5b