目次
1.はじめに
2.DB構築
3.アプリ構築
4.動作確認
5.まとめ
1. はじめに
以前の記事でPythonのフレームワーク(FastAPI)を用いて超シンプルなREST APIの作り方を記載した。そこではDockerコンテナの作成まで記したが、機能としてはHTTPリクエストを受け付けるだけの内容となっている。
本記事では、DBへ接続しSQL実行を行う処理とローカル環境での検証用にDBコンテナの構築をし、ローカルPC内で動作確認ができる状態までについて掲載する。
Docker Containerを複数立ち上げて疎通をする場合、他の方の記事だとdocker-composeを利用する内容が多いが、本記事では利用しない。知らないことを調べたら別の知らない内容が出てきたという再帰的サイクルはどこかでbreakしないと学ぶことを苦に感じてしまうからだ。
RDBMSはMySQLを採用。
個人的にはOracleやpostgresを使ってみたい所だが、AWSのRDB(Amazon RDS)のなかで無料で使えるものはMySQLぐらいしか無かったため、今後AWS上に構築する事を想定してローカル環境に閉じた本記事でもMySQLを使用している。
FastAPI自体にはRDB接続用のライブラリなどは無さそうなため、SQLAlchemyを採用。公式にもSQLAlchemyを利用したexampleがあるためこれを参考にした。
公式より引用
FastAPI doesn't require you to use a SQL (relational) database.
But you can use any relational database that you want.
Here we'll see an example using SQLAlchemy.
You can easily adapt it to any database supported by SQLAlchemy, like:
- PostgreSQL
- MySQL
- SQLite
- Oracle
- Microsoft SQL Server, etc.
本記事ではSQLAlchemyやMySQL自体の解説はしておらず、「とりあえずDBと繋げたい!」というゴールに向けた手順書にも似た記載なため、初学者向けかもしれない。
2. DB構築
まずは構成の説明。
.
└── db/
├── init.sql
└── Dockerfile
2-1. initialze sql
DBコンテナを立ち上げた際に、テーブル作成や初期データ投入を行うためにinit.sql
を作成しておく。
よくある内容を記載。
USE sample_db;
DROP TABLE IF EXISTS item;
CREATE TABLE item
(
item_id SERIAL PRIMARY KEY,
name VARCHAR(50),
price INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
INSERT INTO item (name, price) VALUES ('apple', 100);
INSERT INTO item (name, price) VALUES ('orange', 200);
順に解説する。「知ってるよ!」という方は是非読み飛ばして頂きたい。
MySQL用の内容のため他RDBでは記述が異なることに注意。
-
sample_db
という名のデータベースを利用する。USE sample_db;
- 既に
item
というテーブルが存在すれば削除する。DROP TABLE IF EXISTS item;
-
item
テーブルを作成する。CREATE TABLE item ( item_id SERIAL PRIMARY KEY, name VARCHAR(50), price INT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );
-
SERIAL
はBIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE
のこと。insertする時にVALUEを指定しなくとも自動で連番を振ってくれる。嬉しい。 -
DEFAULT CURRENT_TIMESTAMP
はinsertするときにVALUEを指定しなくともCURRENT_TIMESTAMP
を格納してくれる。便利。 -
ON UPDATE CURRENT_TIMESTAMP
はupdateするときにVALUEを指定しなくともCURRENT_TIMESTAMP
を格納してくれる。便利で嬉しい。
-
注意
現在日時の取得を、DB側で行うかアプリ側で行うかは要件や設計次第。
必ずしもDB側で現在日時を取得することをオススメしているわけではないのでご注意。
クラウド上の無料のDBを使用する場合はZoneの指定などできない可能性があり、アプリ側で現在日時を持ち回る方が良い場合もある。複数のアプリサーバーを使用するケースで全てのサーバーで全く同じ現在時刻を持ち回れない場合など、タイムサーバーとしてDBの現在日付を持ち回る方が良い場合もある。
- データを格納する
INSERT INTO item (name, price) VALUES ('apple', 100);
-
item_id
とcreated_at
とupdated_at
は自動で値が格納されるために値を指定していない。
-
2-2. MySQL Container
続いてDockerfileについて。
FROM mysql:latest
ENV MYSQL_DATABASE sample_db
ENV MYSQL_USER sample_user
ENV MYSQL_PASSWORD sample_password
ENV TZ "Asia/Tokyo"
ENV MYSQL_ROOT_PASSWORD p@assw0rd
COPY ./init.sql /docker-entrypoint-initdb.d/init.sql
細かな環境変数についての説明は公式のEnvironment Variablesを参照してください。
ここで定義している内容をまとめると下記。rootアカウントについては割愛。
項目 | 値 |
---|---|
DB名 | sample_db |
ユーザ名 | sample_user |
パスワード | sample_password |
タイムゾーン | Asia/Tokyo |
/docker-entrypoint-initdb.d
にinit.sqlを格納することで、起動時に実行してくれる。
では早速imageをbuild。
cd db # Dockerfileのあるディレクトリまで移動
docker build -t sample-mysql:1.0.0 .
確認。
docker images sample-mysql:1.0.0
REPOSITORY TAG IMAGE ID CREATED SIZE
sample-mysql 1.0.0 95380b8fbf50 6 minutes ago 519MB
いらっしゃる。次にコンテナも立ち上げてみる。
docker run -d --name sample-mysql-container -p 3306:3306 sample-mysql:1.0.0
確認
docker ps -f "name=sample-mysql-container"
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3c8f3c7429ba sample-mysql:1.0.0 "docker-entrypoint.s…" 32 seconds ago Up 31 seconds 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp sample-mysql-container
いた。次にコンテナ内に入り、init.sql
内で記載したinsert通りにデータが存在するか確認する。
- コンテナ内に入る。
docker exec -it $(docker ps -q -f "name=sample-mysql-container") /bin/bash
- dbに接続。
mysql sample_db -h localhost -u sample_user -psample_password
-
sample_db
が存在するか確認。mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | sample_db | +--------------------+ 2 rows in set (0.00 sec)
-
item
テーブルが存在するか確認。mysql> show tables from sample_db; +---------------------+ | Tables_in_sample_db | +---------------------+ | item | +---------------------+ 1 row in set (0.00 sec)
-
item
テーブルの定義を確認。mysql> SHOW COLUMNS FROM item FROM sample_db; +------------+-----------------+------+-----+-------------------+-----------------------------------------------+ | Field | Type | Null | Key | Default | Extra | +------------+-----------------+------+-----+-------------------+-----------------------------------------------+ | item_id | bigint unsigned | NO | PRI | NULL | auto_increment | | name | varchar(50) | YES | | NULL | | | price | int | YES | | NULL | | | created_at | timestamp | YES | | CURRENT_TIMESTAMP | DEFAULT_GENERATED | | updated_at | timestamp | YES | | CURRENT_TIMESTAMP | DEFAULT_GENERATED on update CURRENT_TIMESTAMP | +------------+-----------------+------+-----+-------------------+-----------------------------------------------+ 5 rows in set (0.01 sec)
- レコードを確認
mysql> select * from item; +---------+--------+-------+---------------------+---------------------+ | item_id | name | price | created_at | updated_at | +---------+--------+-------+---------------------+---------------------+ | 1 | apple | 100 | 2022-02-26 18:29:08 | 2022-02-26 18:29:08 | | 2 | orange | 200 | 2022-02-26 18:29:08 | 2022-02-26 18:29:08 | +---------+--------+-------+---------------------+---------------------+ 2 rows in set (0.00 sec)
無事構築が完了したので、exit
でmysql/containerのコンソールを抜けておこう。
後にアプリの構築が完了した後、sample-mysql-container
に接続するため立ち上げっぱなしでOK。
3. アプリ構築
まずは構成の説明。
.
├── db/
│ ├── init.sql
│ └── Dockerfile
└── api/
├── requirements.txt
├── Dockerfile
├── .env
└── app/
└── main.py
apiディレクトリを追加し、ここにアプリケーションを構築する、
3-1. requirements.txt
パッケージ一括管理用にrequirements.txt
を(使用する内容は少数だが)作成しておく。
sqlalchemy==1.4.31
mysqlclient==2.1.0
「fastapi
とuvicorn
は要らないの?」と思われるかもしれないが、今回使用するbaseimage(uvicorn-gunicorn-fastapi
)の中には既にインストールされているため記載不要。
3-2. App
早速コーディングしてみる。
順に説明を記載するが、一旦全量を見たい方はソースコード全量をどうぞ。
db接続
db接続周りの記述。
-
db engineの作成。
main.pyimport os from sqlalchemy import create_engine SQLALCHEMY_DATABASE_URI = 'mysql://{}:{}@{}:{}/{}'.format( os.environ.get("DB_USER"), os.environ.get("DB_PASSWORD"), os.environ.get("DB_HOST"), os.environ.get("DB_PORT"), os.environ.get("DB_NAME") ) engine = create_engine(SQLALCHEMY_DATABASE_URI)
- 環境変数から接続情報を取得している。
- 開発環境と本番環境でDBの接続情報が異なる場合がほとんど。
- 「開発で動いたのに本番で動かない!」を避けるために全ての環境でソースコードが同じだという保証が欲しい。
- 環境変数から接続情報を取得している。
-
sessionの作成。
main.pyfrom sqlalchemy.orm import sessionmaker Session = sessionmaker(autocommit=False, autoflush=False, bind=engine) def session(): db = Session() try: yield db finally: db.close()
- engineは上記で定義したもの。
- sessionはAPI実行後に都度閉じたいため、finallyにcloseするmethodを定義。
-
modelの作成
main.pyfrom sqlalchemy import Column, TIMESTAMP, Integer, String from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.schema import FetchedValue Base = declarative_base(bind=engine) class Item(Base): __tablename__ = "item" __table_args__ = {"autoload": True} item_id = Column(Integer, primary_key = True, nullable=False) name = Column(String(50), nullable=False) price = Column(Integer, nullable=False) created_at = Column(TIMESTAMP, FetchedValue()) updated_at = Column(TIMESTAMP, FetchedValue())
-
Base
: モデルのベースクラスを定義。 -
class Item(Base)
はORMで扱えるモデルクラスを定義。Itemテーブルに対してSQL文を書かずにCRUD処理を実現するために利用。 - sqlalchemyではテーブルの作成も可能なようだが、既に作成済みなテーブルと連携するため
__table_args__ = {"autoload": True}
を設定している。 -
FetchedValue()
: default valueが設定されている項目に対して、DB側の値を利用する場合に指定。この設定を行わないと insert時などに Nullが設定されてしまう。
-
Controller(logic)
HTTPリクエストを受け付け、CRUD処理を行いレスポンスを返す処理を記述する。
細かい設定については以前の記事の参照をオススメ。
-
GET : SELECTする
main.pyfrom fastapi import Depends, Query, status from fastapi.responses import JSONResponse from fastapi.encoders import jsonable_encoder @app.get("/item") def get_item(id: int = None, name: str = Query(None, max_length=50), db: Session = Depends(session)): if id is not None: result_set = db.query(Item).filter(Item.item_id == id).all() elif name is not None: result_set = db.query(Item).filter(Item.name == name).all() else: result_set = db.query(Item).all() response_body = jsonable_encoder({"list": result_set}) return JSONResponse(status_code=status.HTTP_200_OK, content=response_body)
-
name
は受付可能な最大文字数を50に設定。 -
db: Session = Depends(session)
: api実行時にdb sessionを生成する。 -
db.query(Item)
:item
テーブルへのQuery実行。-
.filter(Item.COL == vaule)
: COL列の値がvalueと一致するものを対象とする。 -
.all()
: 全件取得。
-
-
jsonable_encoder({"list": result_set})
: json形式へ変更。sample{ "list" : [ { //検索結果の1行 }, { //検索結果の1行 } ] }
-
-
POST : INSERTする。
main.pyfrom pydantic import BaseModel from fastapi import Depends, Query, status from fastapi.responses import JSONResponse from fastapi.encoders import jsonable_encoder class ItemRequest(BaseModel): name: str = Query(..., max_length=50) price: float @app.post("/item") def create_item(request: ItemRequest, db: Session = Depends(session)): item = Item( name = request.name, price = request.price ) db.add(item) db.commit() response_body = jsonable_encoder({"item_id" : item.item_id}) return JSONResponse(status_code=status.HTTP_200_OK, content=response_body)
-
class ItemRequest(BaseModel)
: postの際のrequest bodyを定義。 -
db.add(item)
: insert処理定義。 -
db.commit()
: commitする。ここで実際にinsertされる。また、item
にinsert結果が反映される。自動生成されたitem_id
などを取得することができる。
-
-
PUT : UPDATEする。
main.pyfrom pydantic import BaseModel from fastapi import Depends, Query, status from fastapi.responses import JSONResponse from fastapi.encoders import jsonable_encoder class ItemRequest(BaseModel): name: str = Query(..., max_length=50) price: float @app.put("/item/{id}") def update_item(id: int, request: ItemRequest, db: Session = Depends(session)): item = db.query(Item).filter(Item.item_id == id).first() if item is None: return JSONResponse(status_code=status.HTTP_404_NOT_FOUND) item.name = request.name item.price = request.price db.commit() return JSONResponse(status_code=status.HTTP_200_OK)
-
.first()
: 最初の1件をselectする。 - selectした内容を
item
に格納する。 - update対象が存在しなければ404を返すように設定。
- itemの値を更新後、
db.commit()
を行いDB側に反映することでupdateする。
-
-
DELETE : DELETEする。
main.pyfrom fastapi import Depends, Query, status from fastapi.responses import JSONResponse @app.delete("/item/{id}") def delete_item(id: int, db: Session = Depends(session)): db.query(Item).filter(Item.item_id == id).delete() db.commit() return JSONResponse(status_code=status.HTTP_200_OK)
-
.delete()
: 文字通りdeleteする。
-
以上で、CRUD処理のAPIの実装が完了した。
下にソースコードの全量を記す。
ソースコード全量
import os
from fastapi import FastAPI, Depends, Query, status
from fastapi.responses import JSONResponse
from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel
from sqlalchemy import Column, TIMESTAMP, Integer, String, create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.schema import FetchedValue
app = FastAPI()
SQLALCHEMY_DATABASE_URI = 'mysql://{}:{}@{}:{}/{}'.format(
os.environ.get("DB_USER"),
os.environ.get("DB_PASSWORD"),
os.environ.get("DB_HOST"),
os.environ.get("DB_PORT"),
os.environ.get("DB_NAME")
)
engine = create_engine(SQLALCHEMY_DATABASE_URI)
Session = sessionmaker(autocommit=False, autoflush=False, bind=engine)
def session():
db = Session()
try:
yield db
finally:
db.close()
Base = declarative_base(bind=engine)
# Entity Item
class Item(Base):
__tablename__ = "item"
__table_args__ = {"autoload": True}
item_id = Column(Integer, primary_key = True, nullable=False)
name = Column(String(50), nullable=False)
price = Column(Integer, nullable=False)
created_at = Column(TIMESTAMP, FetchedValue())
updated_at = Column(TIMESTAMP, FetchedValue())
# Request Body
class ItemRequest(BaseModel):
name: str = Query(..., max_length=50)
price: float
# GetItemByName
@app.get("/item")
def get_item(id: int = None, name: str = Query(None, max_length=50), db: Session = Depends(session)):
if id is not None:
result_set = db.query(Item).filter(Item.item_id == id).all()
elif name is not None:
result_set = db.query(Item).filter(Item.name == name).all()
else:
result_set = db.query(Item).all()
response_body = jsonable_encoder({"list": result_set})
return JSONResponse(status_code=status.HTTP_200_OK, content=response_body)
# CreateItem
@app.post("/item")
def create_item(request: ItemRequest, db: Session = Depends(session)):
item = Item(
name = request.name,
price = request.price
)
db.add(item)
db.commit()
response_body = jsonable_encoder({"item_id" : item.item_id})
return JSONResponse(status_code=status.HTTP_200_OK, content=response_body)
# UpdateItem
@app.put("/item/{id}")
def update_item(id: int, request: ItemRequest, db: Session = Depends(session)):
item = db.query(Item).filter(Item.item_id == id).first()
if item is None:
return JSONResponse(status_code=status.HTTP_404_NOT_FOUND)
item.name = request.name
item.price = request.price
db.commit()
return JSONResponse(status_code=status.HTTP_200_OK)
# DeleteItem
@app.delete("/item/{id}")
def delete_item(id: int, db: Session = Depends(session)):
db.query(Item).filter(Item.item_id == id).delete()
db.commit()
return JSONResponse(status_code=status.HTTP_200_OK)
3-4. 環境変数
次にコンテナに渡す環境変数を定義する。
DB_HOST=host.docker.internal
DB_PORT=3306
DB_NAME=sample_db
DB_USER=sample_user
DB_PASSWORD=sample_password
「DBのホスト名はlocalhostじゃないの?」と思うかも知れないが、上記の値はアプリコンテナ内で参照されるためlocalhostだと外に出れない。つまるところ、コンテナの中でのlocalhostは同コンテナ自身である。
host.docker.internal
はdocker for Macでサポートされているコンテナからホスト上のサービスに対して接続するドメイン。Windowsでの利用、いつまでサポートされているか不明なため要確認。
公式より引用
ホストの IP アドレスは変動します(あるいは、ネットワークへの接続がありません)。18.03 よりも前は、特定の DNS 名 host.docker.internal での接続を推奨していました。これはホスト上で内部の IP アドレスで名前解決します。これは開発用途であり、Docker Desktop forMac 外の本番環境では動作しません。
最新のversionだとオススメされていないのかしら?大人しくdocker composeを使用すれば明示的にホスト名を定義・指定できるが、主題ではないため割愛。
3-5. Api Container
次にDockerfile。
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9-slim
COPY ./requirements.txt /app/requirements.txt
RUN apt-get update && \
apt-get -y install gcc libmariadb-dev && \
pip install --no-cache-dir --upgrade -r /app/requirements.txt
COPY ./app /app
「gcc
とlibmariadb-dev
は何?」という細かい話は割愛するが、これが無いとmysqlclient
をインストール際にエラーとなってしまう。
実際に直面したエラーはこちら。
3.044 ERROR: Could not find a version that satisfies the requirement mysqlclient
docker container化せずローカルで遊んでみたいという方で、上記のようなエラーが出る場合もgcc libmariadb-dev
の導入を試して欲しい。
では実際に動かしてみる。
cd ../api # Dockerfileのあるディレクトリまで移動
docker build -t sample-api:1.0.0 .
確認する。
docker images sample-api:1.0.0
REPOSITORY TAG IMAGE ID CREATED SIZE
sample-api 1.0.0 7ccc69b2d334 11 minutes ago 426MB
いた。次にコンテナを立ち上げる。
コンテナ立ち上げの際に、環境変数定義ファイルを渡す。
docker run -d --env-file=.env --name sample-api-container -p 80:80 sample-api:1.0.0
しっかりと確認。
docker ps -f "name=sample-api-container"
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
bcb5939e0146 sample-api:1.0.0 "/start.sh" 1 second ago Up Less than a second 0.0.0.0:80->80/tcp, :::80->80/tcp sample-api-container
いらっしゃる。ログをtailしてみる。
docker logs --tail=-1 -f $(docker ps -q -f "name=sample-api-container")
#省略
[2022-02-26 11:30:36 +0000] [12] [INFO] Application startup complete.
起動している!
最後に、動作確認をする。
4. 動作確認
curlで動作確認を行う。
まずは全検索。
curl -v -X GET 'http://localhost:80/item'
# 省略
< HTTP/1.1 200 OK
< date: Sat, 26 Feb 2022 12:03:15 GMT
< server: uvicorn
< content-length: 233
< content-type: application/json
<
* Connection #0 to host localhost left intact
{"list":[{"created_at":"2022-02-26T21:02:50","item_id":1,"updated_at":"2022-02-26T21:02:50","price":100,"name":"apple"},{"created_at":"2022-02-26T21:02:50","item_id":2,"updated_at":"2022-02-26T21:02:50","price":200,"name":"orange"}]}* Closing connection 0
とれた。jsonが見づらい場合はjqコマンドを併用すると便利。
curl -s -X GET 'http://localhost:80/item' | jq
{
"list": [
{
"created_at": "2022-02-26T21:02:50",
"item_id": 1,
"updated_at": "2022-02-26T21:02:50",
"price": 100,
"name": "apple"
},
{
"created_at": "2022-02-26T21:02:50",
"item_id": 2,
"updated_at": "2022-02-26T21:02:50",
"price": 200,
"name": "orange"
}
]
}
綺麗、便利。
jqコマンドないよという方は | jq
がなくてもOK。ただ見やすい方がいいので導入しておくことをオススメする。
さて、init.sqlで実行されたレコードが2件存在することが確認できた。さらにinsert文では指定していないitem_id
とcreated_at
、update_at
が自動で生成されていることも確認できた。
次に id指定で検索してみる。
curl -s -X GET 'http://localhost:80/item?id=1' | jq
{
"list": [
{
"created_at": "2022-02-26T21:02:50",
"item_id": 1,
"updated_at": "2022-02-26T21:02:50",
"price": 100,
"name": "apple"
}
]
}
絞り込めた。
次に存在しないidを指定してみる。
curl -s -X GET 'http://localhost:80/item?id=999' | jq
{
"list": []
}
特にエラーがなく、空リストが返ってきた。REST設計としては404 Not Foundにしても良いかしら?とも考えたが、ここでは統一して200を返している。
名前での検索も同様に確認してみる。
curl -s -X GET 'http://localhost:80/item?name=orange' | jq
{
"list": [
{
"created_at": "2022-02-26T21:02:50",
"item_id": 2,
"updated_at": "2022-02-26T21:02:50",
"price": 200,
"name": "orange"
}
]
}
こちらも問題ない。
次に文字数を増やして50文字を超えてみる。
curl -s -X GET 'http://localhost:80/item?name=123456789022345678903234567890423456789052345678901' | jq
{
"detail": [
{
"loc": [
"query",
"name"
],
"msg": "ensure this value has at most 50 characters",
"type": "value_error.any_str.max_length",
"ctx": {
"limit_value": 50
}
}
]
}
エラーが返ってきた。しっかりvalidation checkされているようだ。
しかし、クライアントに対してデフォルトのエラーを返してしまうと「ははーん50文字以内なのだな?」と悪い人にバレるため(バレても良い場合もあるが)、共通処理としてValidationErrorの際のresponse定義をすることをオススメする。実装方法は別記事で紹介予定。
次に新規登録をする。
curl -X POST 'http://localhost:80/item' -H 'Content-Type: application/json' -d '{"name":"banana","price":123}'
{"item_id":3}
idは3が採番されたことを確認。
再び取得してみる。
curl -s -X GET 'http://localhost:80/item?name=banana' | jq
{
"list": [
{
"created_at": "2022-02-26T21:04:39",
"item_id": 3,
"updated_at": "2022-02-26T21:04:39",
"price": 123,
"name": "banana"
}
]
}
ばっちり取得できた。
次に更新してみる。
curl -s -X PUT "http://localhost:80/item/1" -H 'Content-Type: application/json' -d '{"name":"super apple","price":500}'
null
response bodyを定義していないためnullが返ってきた。あえて消したい場合は要修正。
再び取得してみる。
curl -s -X GET 'http://localhost:80/item?id=1' | jq
{
"list": [
{
"created_at": "2022-02-26T21:02:50",
"item_id": 1,
"updated_at": "2022-02-26T21:09:21",
"price": 500,
"name": "super apple"
}
]
}
無事に指定した値と合わせてupdated_at
も更新されたことを確認。
次に存在しないidに対して更新してみる。
curl -v -X PUT "http://localhost:80/item/999" -H 'Content-Type: application/json' -d '{"name":"super hoge","price":12345}'
# 省略
< HTTP/1.1 404 Not Found
< date: Sat, 26 Feb 2022 12:12:52 GMT
< server: uvicorn
< content-length: 4
< content-type: application/json
<
* Connection #0 to host localhost left intact
null* Closing connection 0
定義した通り、404が返ってきた。
次に削除してみる。
curl -s -X DELETE 'http://localhost:80/item/2'
null
次に全件取得してみる。
curl -s -X GET 'http://localhost:80/item' | jq
{
"list": [
{
"created_at": "2022-02-26T21:02:50",
"item_id": 1,
"updated_at": "2022-02-26T21:09:21",
"price": 500,
"name": "super apple"
},
{
"created_at": "2022-02-26T21:04:39",
"item_id": 3,
"updated_at": "2022-02-26T21:04:39",
"price": 123,
"name": "banana"
}
]
}
消えている。
コンテナを止めて終了!
docker kill sample-mysql-container
docker kill sample-api-container
5. まとめ
下記を実施した。
- ローカル上にMySQLのDockerContainerを構築
- 初期化sqlを定義
- ローカル上にAPIのDockerContainerを構築
- SQLAlchemyを用いてCRUD処理を実装
- DB接続情報は環境変数から取得
- FastAPIを用いてHTTPリクエストを受け付けるように実装
- SQLAlchemyを用いてCRUD処理を実装
この次のステップは何をすればいいの?という方に。
- 環境変数を書き換えて、クラウド上のDBなどに接続してみる。
- API RequestのValidation Errorの際の共通Responseを定義してみる。
- API実行時にExceptionが発生した場合のhandlingを実装してみる。
- logginのformat変更やfile出力をしてみる。
この辺りの記事を記載予定。
以上。