🐶

個人的FastAPI入門

2022/07/08に公開

はじめに

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:設定定義 今回は特別に設定することもないので記載しない。

ルート直下

ここでは、コンテナ利用を前提としています。

Dockerfile
# 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 
requirements.txt
FastAPI
pydantic
uvicorn[standard]
gunicorn
sqlalchemy # RDBMS使用時
pymysql # RDBMS使用時
app.py
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利用時の設定を例に挙げます。

./database/__init__.py
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で使用するので、変数名は適宜変更すること。

./tags/__init__.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