個人的FastAPI入門
はじめに
flaskを利用していたのですが、swaggerドキュメントを求められる場合が出てきたので、
可能であれば書きたくないと思いながら、探していた際にFastAPIを見つけました。
元々知ってはいましたが、書き方的にflaskとあまり変わらないし、前はswaggerドキュメントを
用意することはなかったので、採用を見送っていました。
ただよくよく調べると、デフォルトでCORS対応、バリデーションや前述のswaggerドキュメントの出力があったので、
今後は積極的に使用しようと思い、備忘録的に記述します。
公式でもちょこちょこフォルダ構造が示されていますが、こちらは個人的なものであることを留意してください。
フォルダ構造
./-
|-/database:データベース設定とクライアントインスタンス定義
| '-__init__.py
|-/dependencies:共通的に使用する依存関数定義
| '-__init__.py
|-/querys:共通的に使用するクエリ定義
| '-__init__.py
|-/tags:タグ設定
| '-__init__.py
|-/views:API定義
| '/test //テスト用API
| |-dependencies.py:限定的な依存関数定義
| |-models.py:データベース処理
| |-paths.py:限定的なクエリ定義
| |-router.py:ルート定義
| '-shcemas.py:スキーマ定義
|-app.py //メイン
|-Dockerfile
|-requirements.txt:ライブラリ定義
'-settings.py:設定定義 今回は特別に設定することもないので記載しない。
ルート直下
ここでは、コンテナ利用を前提としています。
# Use the official Python image.
# https://hub.docker.com/_/python
FROM python:3.10
# Copy local code to the container image.
ENV APP_HOME /app
WORKDIR $APP_HOME
# Install production dependencies.
COPY requirements.txt ./
COPY settings.py ./
COPY app.py ./
COPY dependencies/ ./dependencies
COPY views/ ./views
COPY database ./database
COPY tags/ ./tags
COPY querys/ ./querys
RUN pip install --no-cache-dir -r requirements.txt
ENV PORT 8080
CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 -k uvicorn.workers.UvicornWorker app:app
FastAPI
pydantic
uvicorn[standard]
gunicorn
sqlalchemy # RDBMS使用時
pymysql # RDBMS使用時
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from tags import tags_metadata
from views.test.router import router as test_router
app = FastAPI(
servers=[
{"url": "http://localhost:8080", "description": "test"},
],
root_path="",
root_path_in_servers=False,
openapi_tags=tags_metadata
)
app.add_middleware(
CORSMiddleware,
allow_origins=[
"http://localhost:8080",
],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(test_router)
databaseフォルダ
ここでは、データベースの設定やクライアント変数を定義します。
一応以下にRDBMS利用時の設定を例に挙げます。
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
DB_USER = ""
PASSWORD = ""
HOST = ""
DB_NAME = ""
SQLALCHEMY_DATABASE_URI = f"mysql+pymysql://{DB_USER}:{PASSWORD}@{HOST}/{DB_NAME}?charset=utf8"
engine = create_engine(
SQLALCHEMY_DATABASE_URI, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
dependenciesフォルダ
ここでは、FastAPIの「Depends」に関する記述をする。
一応content-typeの制限の例を示します。
from typing import Optional
from fastapi import Header, HTTPException
async def is_json(content_type: Optional[str] = Header(default=None)):
if content_type != "application/json":
raise HTTPException(status_code=400, detail="Invalid JSON")
querysフォルダ
ここでは、FastAPIの「Query」に関する記述をする。
from fastapi import Query
limit: int = Query(default=None)
tagsフォルダ
ここでは、swaggerドキュメントのタグに関する記述をする。
タグ情報はapp.pyで使用するので、変数名は適宜変更すること。
from enum import Enum
class Tags(Enum):
test = "test"
tags_metadata = [
{
"name": "test",
"description": "テスト"
}
]
viewsフォルダ
ここでは、具体的にAPIの定義します。増やす際は、テストで示すファイルをAPIごとに
dependencies.py:限定的な依存関数定義
ここでは、限定的に利用する依存関数を定義するところです。
定義するものは上記の「# dependenciesフォルダ」と同様です。
models.py:データベース処理
ここでは、データベース関連処理を記述します。
以下は、DBMSを利用した場合の例です。
from database import Base
from sqlalchemy import Column, Integer, String
class Test(Base):
__tablename__ = 'test'
testId = Column(
String(length=36), nullable=False, primary_key=True
)
name = Column(
String(length=36, default=""), nullable=False
)
class TestRepository():
def get(self):
pass
def add(self):
pass
def update(self):
pass
def delete(self):
pass
paths.py:限定的なクエリ定義
ここでは、限定的に利用すクエリを定義するところです。
定義するものは上記の「# querysフォルダ」と同様です。
from fastapi import Path
testId = Path(
default="", description="ID",
)
router.py:ルート定義
ここは肝心のエンドポイントを定義する場所です。
from fastapi import APIRouter, Depends
from views.test.schemas import Test, TestAdd
from tags import Tags
from query import limit
from dependencies import is_json
router = APIRouter(
prefix="/test",
tags=[Tags.test],
dependencies=[],
responses={404: {"description": "Not found"}},
)
@router.get(
"/",
summary="テストget",
description="テストget",
status_code=status.HTTP_200_OK
)
async def get(limit:int = limit):
return {"message": "hello world"}
@router.post(
"/",
summary="テストpost",
description="テストpost",
dependencies=[Depends(is_json)],
status_code=status.HTTP_200_OK
)
async def post(test: TestAdd):
return {}
shcemas.py:スキーマ定義
from pydantic import BaseModel, Field
class Test(BaseModel):
testId: str = Field(
description="ID"
)
name: str = Field(
default="",
description="名前"
)
class TestAdd(BaseModel):
name: str = Field(
default="",
description="名前"
)
おわりに
大まかの個人的なフォルダ構造および中身を書きました。ここで記載したものをそのままテストしているわけではないので、
いくらかタイプミス等あると思います。
もう少し公式ドキュメントを読んだ後、自分なりの使い方で更新いきます。
Discussion