Closed12

FastAPI

high-ghigh-g

モチベ

  • FastAPIに入門する

FastAPIとは

  • APIを構築する為のモダンで高速なPython Webフレームワーク
  • パフォーマンスが高速
  • 開発が高速
  • 2018年リリースPythonWebフレームワークの中では比較的新しい
  • 型ヒントを用いて開発を行う

FastAPIとは

  • シンプルで直感的な記述で、簡単にAPIが作成可能
  • 型ヒントを使用した安全な開発
  • 自動ドキュメント生成(OpenAPI標準に従ったドキュメント)
  • 後発ながらコミュニティが非常に活発
high-ghigh-g

型ヒント

  • Pythonは動的型付き言語なので、型を利用しない開発が基本だった
  • 型ヒントを活用することで可読性の向上やバグの早期発見などのメリット
  • Python3.5から公式サポート

int - 整数
float - 少数
str - 文字列
bool - 論理型
list[int] - リスト型 [1, 2, 3]の様なやつ
dict[str, bool] - 辞書型 {"flag": true} # Python3.9から導入された

optional

from typing import Optional
g: Optional[int] = 1 # or None

Annotated(型に注釈をつけることが出来る)

from typing import Annotated
Weight = Annotated[int, 'kg'] # 型, 説明
w: Weight = 20

一般的なAnnotatedは型の説明でしかないが、FastAPIの場合、DIに使用することが出来る。

関数・メソッドの型ヒント

def sum(a: int, b: int) -> int:
    return a + b
high-ghigh-g

環境構築

仮想環境作成

python -m venv fleamarket

環境のアクティベート

source fleamarket/bin/activate

モジュールインストール

pip install fastapi
pip install "uvicorn[standard]"

モジュール確認

pip list
high-ghigh-g

開発

ディレクトリ直下にmain.pyを作成
FastAPIを利用した記述を行っていく

main.py

from fastapi import FastAPI

app = FastAPI()

# デコレータをつけることでAPIにすることが出来る app.httpメソッド
@app.get('/')
async def example():
    return {"message": "Hello World"}

uvicornを利用してサーバを立てる

uvicorn main:app --reload

curl localhost:8000

high-ghigh-g

ドキュメント

自動的にドキュメントページが用意されている
http://localhost:8000/docs

uvicornでサーバを起動している限り、ソースコードの更新内容が自動的にdocsに反映される

OpenAPIの話
Try it outをクリック→Executeをクリック
レスポンスを確認できる

high-ghigh-g

CRUD - READ

curds/item.py

from enum import Enum
from typing import Optional


class ItemStatus(Enum):
    ON_SALE = "ON_SALE"
    SOLD_OUT = "SOLD_OUT"


class Item:
    def __init__(
        self,
        id: int,
        name: str,
        price: int,
        description: Optional[str],
        status: ItemStatus
    ):
        self.id = id
        self.name = name
        self.price = price
        self.description = description
        self.status = status


items = [
  Item(1, "pc", 100000, "備品です", ItemStatus.ON_SALE),
  Item(2, "スマートフォン", 50000, None, ItemStatus.ON_SALE),
  Item(3, "Python本", 1000, "使用感あり", ItemStatus.SOLD_OUT),
]


def find_all():
    return items

def find_by_id(id: int):
    for item in items:
        if item.id == id:
            return item
    return None

main.py

from fastapi import FastAPI
from cruds import item as item_cruds

app = FastAPI()

@app.get('/items')
async def find_all():
    return item_cruds.find_all()

@app.get('/items/{id}')
async def find_by_id(id: int):
    return item_cruds.find_by_id(id)
high-ghigh-g

クエリパラメータ

?name=fastapi

cruds/item.py

def find_by_name(name: str):
    filtered_items = []

    for item in items:
        if item.name == name:
            filtered_items.append(item)
    return filtered_items

main.py

@app.get('/items/') # 衝突すると上書きが発生する
async def find_by_name(name: str): # クエリパラメータを引数にするだけ
    return item_cruds.find_by_name(name)
high-ghigh-g

CRUD - Create

item.py

def create(item_create):
    new_item = Item(
        len(items) + 1,
        item_create.get('name'),
        item_create.get('price'),
        item_create.get('description'),
        ItemStatus.ON_SALE,
    )
    items.append(new_item)
    return new_item

main.py(fastapiからBodyをimportし、引数の初期値に定義)

from fastapi import FastAPI, Body

@app.post('/items')
async def create(item_create=Body()):
    return item_cruds.create(item_create)
high-ghigh-g

CRUD - UPDATE

item.py

def update(id: int, item_update):
    for item in items:
        if item.id == id:
            item.name = item_update.get('name', item.name)
            item.price = item_update.get('price', item.price)
            item.description = item_update.get('description', item.description)
            item.status = item_update.get('status', item.status)
            return item

main.py

@app.put('/items/{id}')
async def update(id: int, item_update=Body()):
    return item_cruds.update(id, item_update)
high-ghigh-g

CRUD - Delete

item.py

def delete(id: int):
    for i in range(len(items)):
        if items[i].id == id:
            delete_item = items.pop(i)
            return delete_item
    return None

main.py

@app.delete('/items/{id}')
async def delete(id: int):
    return item_cruds.delete(id)
high-ghigh-g

router

from fast import APIRouter を利用
@appを@routerに変更

routers/item.py

from fastapi import APIRouter, Body
from cruds import item as item_cruds

router = APIRouter()


@router.get('/items')
async def find_all():
    return item_cruds.find_all()


@router.get('/items/{id}')
async def find_by_id(id: int):
    return item_cruds.find_by_id(id)


@router.get('/items/')
async def find_by_name(name: str):
    return item_cruds.find_by_name(name)


@router.post('/items')
async def create(item_create=Body()):
    return item_cruds.create(item_create)


@router.put('/items/{id}')
async def update(id: int, item_update=Body()):
    return item_cruds.update(id, item_update)


@router.delete('/items/{id}')
async def delete(id: int):
    return item_cruds.delete(id)

main.pyではroutersをimportし、app.include_router()にわたす

main.py

from fastapi import FastAPI
from routers import item

app = FastAPI()
app.include_router(item.router)

これでルーティングの設定完了

APIRouterにprefixを追加することで、エンドポイントの共通パス部分をまとめることができる。

router = APIRouter(prefix="/items")

tagsを追加することで、OpenAPIで表示されているタイトル部分を指定でき、ドキュメントをまとめることが出来る。

router = APIRouter(prefix="/items", tags=["Items"])
high-ghigh-g

バリデーション

Pydantic

  • 型ヒントを使用するデータ検証ライブラリ
  • FastAPIと併せてインストールされる

schemas.py

from pydantic import BaseModel, Field
from typing import Optional


class ItemCreate(BaseModel):
    name: str = Field(min_length=2, max_length=20, examples=['pc'])
    price: int = Field(gt=0, examples=[10000])
    description: Optional[str] = Field(None, examples=['備品です'])

routers/item.py

from schemas import ItemCreate

@router.post('')
async def create(item_create: ItemCreate):
    return item_cruds.create(item_create)

cruds/item.py

from schemas import ItemCreate

def create(item_create: ItemCreate):
    new_item = Item(
        len(items) + 1,
        item_create.name,
        item_create.price,
        item_create.description,
        ItemStatus.ON_SALE,
    )
    items.append(new_item)
    return new_item
このスクラップは2024/11/06にクローズされました