メインコンテンツへスキップ
見出し画像

Pythonライブラリ(API開発):FastAPI

    1.概要

     FastAPIはAPI開発用ライブラリであり特徴は下記の通りです。

    【Fast APIの特徴】
    ●型ヒント追加で入力値を制限することが出来るためエラー防止ができます。(Pydanticが内部で機能)
    ●APIドキュメントを自動生成できる(Swagger版とReDoc版)。

    2.APIとは/Fast APIで何をやるの?

     APIとは特定サービスにアクセスできる仕組みであり参考のサービスおよび事例記事は下記で紹介しています。

     個人的なFast APIの用途は2つあります。

    1.  Pythonで作成したAIモデルにExcelからアクセスすることでUIをExcelだけで完結できる。

    2.「Pythonの環境構築済みで|高スペック⦅GPU付き⦆のPC・マイコンにAIモデルを入れておき、他の低スペック・細かい環境構築無しPCからでもAIモデルを使用可能」な状態を作るためです。
     これによりAIモデルを簡単に社内でも共有できるようになります。

    【個人的な用途分類】
    ●Fast API:CLIでのシステム共有
    ●Streamlit:GUIでのシステム共有

    画像

    3.用語説明

     必要な用語の説明を記載します。※一応調べてますが自信なし

    【用語一覧】
    ●GETメソッド:情報取得用メソッド=>パラメータをURLで渡す
     ->渡すことできる入力値に制限がある(IEは2,048文字)
     ->Webサーバーに値を渡さない
     ->URLにパラメータ情報(クエリパラメータ)が見える(低セキュリティ)。
     ->URLにパラメータがあることで検索にかかりやすい(SEO対策)
    ●POSTメソッド:情報送信用メソッド=>入力値をHTTP通信のbodyに含める
     ->Webサーバーに値を渡す
     ->パラメータはURLでは送信しない(URLで直接操作できない)
     ->URLだけ入力しても同じ結果が返ってこない。
    ●rootディレクトリ:一番初めのフォルダを意味します。イメージですがWindowsならCドライブ、noteなら"https://note.com/"みたいな場所です。
    ●ルーティング:URLに応じてページを変える機能です(下図の青字)。
    ●
    クエリパラメータ(GETメソッドのみ):URLに直接渡すパラメータです。URLに渡すときは"?var1=値&var2=値2・・・"となります(下図赤字では変数q, context, modeにKIYO, note, searchパラメータを渡します)
    ●並行処理(Concurrent):処理を切り替えで同時に動いているように見せる
    ●並列処理(Parallel):同時に処理を実施

    画像

    4.Fast API基礎1:全般

     Fast APIは公式ドキュメントが分かりやすいためそちらに従います。ライブラリは"fastapi"と"uvicorn"をインストールします。これらをインストールすると後で使用する"pydantic"も自動でインストールされます。

    [Terminal]
    pip install fastapi
    pip install uvicorn

    4-1.アプリの起動:uvicorn

     FastAPIの起動はターミナルで"uvicorn filename:FastAPI()インスタンス名 --reload"を実行します。"--reload"を記載すると|ファイル更新時に自動で再起動します⦅更新ごとにファイルの立ち下げ不要⦆。なお製品向けに使用すると不要なリソースを食うため開発用として使用します(参考)。
     参考までにasyncは非同期処理(複数の処理をこなす際、ある処理は別の処理の終了を待たないような処理)を意味しています(詳細は下記記事参照)。

    [In] ※note_fastapi.pyに作成
    from fastapi import FastAPI
    
    app = FastAPI() #FastAPIインスタンスを生成
    
    #GETメソッド
    @app.get("/") #app.getメソッドでルーティングを設定
    async def root(): #async: 非同期処理を行う
        return {"Hello": "World"}
    
    @app.get("/KIYO")
    async def KIYO():
        return {"Hello": "KIYO"}
    
    @app.get("/users/{user_id}")
    async def username(user_id):
        return {"Hello": user_id}
    
    #POSTメソッド
    @app.post("/user") #app.postメソッドでルーティングを設定
    async def info_user(items): #async: 非同期処理を行う
        return {items}
    [Terminal]
    uvicorn note_fastapi:app --reload
    
    [Out]
    ブラウザで”http://127.0.0.1:8000”にアクセスするとroot関数の戻り値を確認できる。
    画像

     出力結果より下記が確認できました。

    【確認事項】
    ●root=”http://127.0.0.1:8000” であり、rootにアクセスすると@app.get("/")でデコレートしたroot()関数の戻り値が出力される
    ●@app.get("/path")でデコレートした値をURLに記載すると指定した関数の戻り値が出力される
    ●@app.post()はURLから直接処理されない(3章参照)

    4-2.起動時のhost・PORT設定

     FastAPI立ち上げ時のhostやportの設定は下記で実施できます。

    [Terminal]
    uvicorn note_fastapi:app --reload --host 0.0.0.0 --port 8000

    4-3.Fast APIの機能:データ型設定

     型ヒントとは変数・戻り値のデータ型を示す機能です。Pythonの型ヒントは強制力がなく別の型を入力してもエラーは出ません。

     しかしFast APIではデータ型を設定することで指定した型以外の入力値は受け付けないようにすることができます。留意点は下記のとおりです。

    【型ヒントの留意点】
    ●GETメソッド:通常通り変数に型ヒントを記載する
    ●POSTメソッド:pydanticのBaseModelを使用して自作する

    5.Fast API基礎2:APIドキュメント

     FastAPIは起動と同時にAPIドキュメントが自動で2種類作成されます。またブラウザ上で動作チェックをすることも可能です。

    ●自動対話型のAPIドキュメント(Swagger UI):"root/docs"でアクセス
    ●代替の API ドキュメント(ReDoc):"root/redoc"でアクセス

    [URL]
    http://127.0.0.1:8000/docs
    http://127.0.0.1:8000/redoc
    画像

    5-1.APIドキュメント:Swagger UI

     Swagger UIベースのAPIドキュメントは下記の通りであり、HTTPメソッド、パス、関数名が一覧で表示されております。

    画像

     「タブボタン」->「Try it out」->「Excecute」を実行することで、ブラウザ上で指定したメソッドでの関数の戻り値が確認できます。

    画像
    画像

     また入力値が必要な関数でも指定個所に入力すれば値に応じた戻り値を確認することができます。

    画像

    5-2.APIドキュメント:ReDoc

     ReDoc形式は下記の通りです。基本的な表示内容はSwagger UIと同じです。ただ戻り値の確認方法がわかりませんでした・・・

    画像

    6.Fast API基礎3:GETメソッド

     本章ではGETメソッドを説明します。GETメソッドの仕様URLで値を渡すよりパスパラメータ・クエリパラメータはGETメソッドのみで使用します。

    6-1.ルーティング:@app.get("url")

     Getメソッドのルーティング要領は下記の通りです。

    【GETメソッドのルーティング】
    ●関数の上に@app.get("url")を記載してデコレートする。
    ●ルートディレクトリは@app.get("/")と記載する。
    ●別のURLでGETメソッドを処理する場合は@app.get("/path")を記載する。

    [In]
    from fastapi import FastAPI
    
    app = FastAPI() #FastAPIインスタンスを生成
    
    #GETメソッド
    @app.get("/") #app.getメソッドでルーティングを設定
    async def root(): #async: 非同期処理を行う
        return {"Hello": "World"}
    
    @app.get("/KIYO") 
    async def hello():
        return {"Hello": "KIYO"}
    画像

    6-2.パラメータ設定1:パスパラメータ

     URLに記載した値を変数として使用する場合、その変数をパスパラメータと呼びます。設定はルーティング時のurlに{パスパラメータ}と記載します。

    [In]
    from fastapi import FastAPI
    
    app = FastAPI() #FastAPIインスタンスを生成
    
    #GETメソッド
    @app.get("/") #app.getメソッドでルーティングを設定
    async def root(): #async: 非同期処理を行う
        return {"Hello": "World"}
    
    @app.get("/{name}") #パスパラメータ:nameを設定
    async def hello(name):
        return {"Hello": name}
    
    [Out]
    URLの値に応じて出力が変化
    画像

    6-3.パラメータ設定2:クエリパラメータ

     パスパラメータ以外で関数の引数に渡した値はクエリパラメータになります(記法は3章参照)。なおエラーが出るため初期値はNoneを渡してます。

    [In]
    from fastapi import FastAPI
    
    app = FastAPI() #FastAPIインスタンスを生成
    
    #GETメソッド
    @app.get("/") #app.getメソッドでルーティングを設定
    async def root(): #async: 非同期処理を行う
        return {"Hello": "World"}
    
    @app.get("/{name}") #パスパラメータ:nameを設定
    async def hello(name, sex='man', age=None): #クエリパラメータage, sexを設定
        return {"Hello": [name, sex, age]}
    
    [Out]
    クエリパラメータに応じて戻り値が変化
    画像

    6-4.データ型設定:型ヒント

     パラメータの型を制限する場合はGETメソッドの関数に型ヒントを付けます。これにより指定型以外の入力があるとエラーを発生させられます。

    [In]
    from fastapi import FastAPI
    
    app = FastAPI() #FastAPIインスタンスを生成
    
    #GETメソッド
    @app.get("/") #app.getメソッドでルーティングを設定
    async def root(): #async: 非同期処理を行う
        return {"Hello": "World"}
    
    @app.get("/{name}") #パスパラメータ:nameを設定
    async def hello(name:str, sex:str ='man', age: int=None): #クエリパラメータage, sexを設定
        return {"Hello": [name, sex, age]}
    
    [Out]
    ageは整数以外は受け付けない。sexは数値・少数は文字列として認識される
    画像

     型指定が不要型が任意なら型ヒントを設定しなくても問題ないですがtypingモジュールのOptionalでも任意指定ができます。

    [In]
    from fastapi import FastAPI
    from typing import Optional
    
    app = FastAPI() #FastAPIインスタンスを生成
    
    @app.get("/{name}") #パスパラメータ:nameを設定
    async def hello(name:str, sex: Optional[str] ='man', age: int=None): #クエリパラメータage, sexを設定
        return {"Hello": [name, sex, age]}

    7.Fast APIの基礎4:POSTメソッド

    7-1.ルーティング:@app.post("url")

     POSTメソッドのルーティングはGETメソッドのget->postに変更するだけです。3章で説明の通りPOSTメソッドはURLだけで通信できません。戻り値を取得するためにはHTTPクライアントでPOSTメソッドを送る必要があるためrequestsライブラリを使用します。

    [In] Fast API
    from fastapi import FastAPI
    
    app = FastAPI() #FastAPIインスタンスを生成
    
    #POSTメソッド
    @app.post("/")
    async def helloWorld():
        return 'Hello world'
    [In] APIを叩くスクリプト 
    import requests
    
    url = 'http://127.0.0.1:8000'
    res = requests.post(url) #パラメータをPOST
    res.json()
    
    [Out]
    'Hello world'
    画像

    7-2.パラメータ設定1:個別設定

     POSTメソッドはパスパラメータやクエリパラメータはないため変数は通常通り関数に記載します。パラメータをPOSTメソッドで渡すときはDict型で{'変数名1':値1, '変数名2':値2・・・}とします。

    [In] Fast API
    from fastapi import FastAPI
    
    app = FastAPI() #FastAPIインスタンスを生成
    
    #POSTメソッド
    @app.post("/users")
    async def hellousers(username: str, age: int):
        return f'ユーザー:{username}、年齢:{age}'
    [In]
    import requests
    
    url = 'http://127.0.0.1:8000/users'
    params = {'username': 'KIYO',
              'age': 100}
    
    res = requests.post(url, params=params) #パラメータをPOST
    res.json()
    
    [Out]
    [Out]
    ・POSTメソッドでparamsの値を受け取って戻り値で出力
    ・ageは(文字列でも)整数なら受け付けるが小数点ならエラー
    画像

     APIドキュメントよりパラメータが個別入力であることが確認できます。

    画像

    7-3.パラメータ設定2:リクエストボディ(BaseModel)

     パラメータが多くなると個別ではコードが見にくくなります。POSTメソッドで渡すパラメータ専用の型(各変数の名前・型・初期値)を作成することができ、作成にはBasemodelを使用します。

    【BaseModel作成時の注意事項】
    ●request.post(url, data) でのurlの次の引数はdataです(paramsではない)
    ●dataはJSON形式の必要があるため辞書型データをjson.dumps()に渡す
    ●型ヒントが必須のため任意の変数にはOptionalを使用する

    [In]
    from fastapi import FastAPI
    from pydantic import BaseModel
    from typing import Optional, List
    
    app = FastAPI() #FastAPIインスタンスを生成
    
    class UserInfo(BaseModel):
        username: str
        age: int
        sex: Optional[str] #型は任意で変更可能
    
    #POSTメソッド
    @app.post("/users")
    async def hellousers(info: UserInfo):
        return f'ユーザー情報:{info}'
    [In]
    import requests
    import json
    
    url = 'http://127.0.0.1:8000/users'
    data = {'username': 'KIYO',
              'age': '100',
              'sex': 10.0}
    
    res = requests.post(url, data=json.dumps(data)) #パラメータをPOST
    res.json()
    
    [Out]
    出力は下記参照
    <class 'dict'> <class 'str'> # print(type(data), type(json.dumps(data)))
    画像

     APIドキュメントよりパラメータの入力形式が変更されています。

    画像

    7-4.バリデーション(条件設定):Field()

     入力値に制限をかける場合はpydanticのField()を使用します。下記では最小文字数制限だけかけました。

    [In]
    from fastapi import FastAPI
    from pydantic import BaseModel, Field
    from typing import Optional, List
    
    app = FastAPI() #FastAPIインスタンスを生成
    
    class UserInfo(BaseModel):
        username: str = Field(min_length=2) #2文字未満はエラー
        age: int
        sex: Optional[str] #型は任意で変更可能
    
    #POSTメソッド
    @app.post("/users")
    async def hellousers(info: UserInfo):
        return f'ユーザー情報:{info}'
    [Out]
    import requests
    import json
    
    url = 'http://127.0.0.1:8000/users'
    data = {'username': 'A',
              'age': '100',
              'sex': 10.0}
    
    res = requests.post(url, data=json.dumps(data)) #パラメータをPOST
    res.json()
    
    [Out]※usernameが1文字のためエラー発生
    {'detail': [{'loc': ['body', 'username'],
       'msg': 'ensure this value has at least 2 characters',
       'type': 'value_error.any_str.min_length',
       'ctx': {'limit_value': 2}}]}

    7-5.参考_クラス設定(ORマッパー):orm_mode=True

     FastAPIは通常JSON形式データしか受け付けませんが、SQLのO/RマッパーであるSQLAlchemyなどと連動させたい場合はormモード設定ができます。

    [In]※下記では使用しないため参考用
    from fastapi import FastAPI
    from pydantic import BaseModel, Field
    from typing import Optional, List
    
    app = FastAPI() #FastAPIインスタンスを生成
    
    class UserInfo(BaseModel):
        username: str = Field(min_length=2) #2文字未満はエラー
        age: int
        sex: Optional[str] #型は任意で変更可能
        
        class Config:
            orm_mode = True

    8.特殊ルーティング(Event)

     FastAPIではアプリの起動時/停止時にコード処理させることも可能です。

    8-1.起動時の実行@app.on_event("startup")

     起動処理はルーティング「@app.on_event("startup")」を使用します。

    [IN]
    from fastapi import FastAPI
    
    app = FastAPI()
    items = {}
    
    @app.on_event("startup")
    async def startup_event():
        items["foo"] = {"name": "Fighters"}
        items["bar"] = {"name": "Tenders"}
    
    @app.get("/items/{item_id}")
    async def read_items(item_id: str):
        return items[item_id]
    
    [OUT]
    起動時にitems = {'foo': {'name': 'Fighters'}, 'bar': {'name': 'Tenders'}}となる

    8-2.起動時の実行@app.on_event("shutdown")

     停止処理はルーティング「@app.on_event("shutdown")」を使用します

    [IN]
    from fastapi import FastAPI
    app = FastAPI()
    
    @app.on_event("shutdown")
    def shutdown_event():
        with open("log.txt", mode="a") as log:
            log.write("Application shutdown")
    
    
    @app.get("/items/")
    async def read_items():
        return [{"name": "Foo"}]
    
    [OUT]
    停止時にlog.txtに"Application shutdown"と記載する。

    9.その他応用

    9-1.戻り値の型ヒント:response_model

     APIを叩いた時の戻り値の型ヒントはapp.method("root", response_model)となります。なくても動きますがご参考までに。

    [In]
    from fastapi import FastAPI
    from typing import Dict
    
    app = FastAPI() # FastAPIインスタンスを生成
    
    #GETメソッド
    @app.get("/", response_model=Dict) # "/"(ルート)にアクセスしたときに実行
    async def read_root():
        return {"message": "Hello World"}

    10.他PCへ接続時の注意点

     専門の人は当たり前でも情報系出身でない私がはまった部分のメモです。

    10-1.アクセス時のIPアドレス

     開発時上記で接続に使用した"http://127.0.0.1:8000"はlocalhostと同じであり自分のPCを意味します。

     よって他PCに接続する場合は"http://接続PCのIPアドレス:port"と入力する必要があります。Windowsはコマンドプロンプトで"ipconfig"と入力すればipアドレスは確認できます。

    画像

    10-2.uvicorn立ち上げ時のhostIP設定

     Fast API起動時にhost IPの設定として"--host 0.0.0.0"のフラグを付ける必要があります。意味は1mmも理解できてないですがこれをつけないとwin10061エラーが解消されませんでした。

    [Terminal]
    uvicorn note_fastapi:app --reload --host 0.0.0.0 --port 8000

    コピペ用テンプレコード

    [In]
    from fastapi import FastAPI
    from pydantic import BaseModel, Field
    from typing import Optional, List, Dict
    
    class Info(BaseModel):
        inA: str
        inB: Optional[str] #型は任意で変更可能
    
    app = FastAPI() #FastAPIインスタンスを生成
    
    #GETメソッド
    @app.get("/") #app.getメソッドでルーティングを設定
    async def root(): #async: 非同期処理を行う
        return {"Hello": "World"}
    
    #POSTメソッド
    @app.post("/info") #app.postメソッドでルーティングを設定
    async def info_post(info: Info): #async: 非同期処理を行う
        return {info}

    参考資料


    あとがき

     とりあえず書きたい部分だけかけたので先出しします。やってみたシリーズや業務と合わせながら更新していきます。

    2022年2月追記:他PCへの接続でエラーが消えなかったので休日がつぶれた(すべて8章に記載)・・・・

     
     
     

    KIYO

     
     
    普段は製造業で企画/開発/設計しております。記事はプログラミング・機械学習、IoT関係の記事をメインで作成し、なるべく1つの記事で知りたいことを網羅していきます。内容は学術的より実装・アウトプット(ほしくなるもの)を重視して作成しています。 面白そうな仕事があればやりたいです!

    あなたへのおすすめ