以下Qiitaの続きです。
以前中止した学習用WebAPIの作成に向けて、とりあえず(3)の方法の通り、学習用Djangoプロジェクトのasgi.pyをいじって以下のようにFastAPIを埋め込みました。
詳細
# Django の urls.py では WSGI ベースのアプリケーションを扱うため、
# FastAPI のような ASGI アプリを正しく組み込む必要があるらしい
import os
import django
from django.core.asgi import get_asgi_application
from fastapi import FastAPI
from starlette.middleware.wsgi import WSGIMiddleware
from starlette.routing import Mount
from starlette.applications import Starlette
# Django 環境変数を設定
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_portfolio.settings')
# Django を初期化
django.setup()
# Django ASGI アプリケーションを取得
django_app = get_asgi_application()
# FastAPI アプリを作成
fastapi_app = FastAPI()
@fastapi_app.get("/")
async def get_hello():
return {"message": "Hello World"}
# FastAPI のエンドポイントを `/fastapi/` にマウント
app = Starlette(routes=[
Mount("/fastapi", fastapi_app) # "/fastapi" に FastAPI をマウント
])
# Django と FastAPI を共存させる ASGI アプリ
async def application(scope, receive, send):
if scope["path"].startswith("/fastapi"):
await app(scope, receive, send) # FastAPI にルーティング
else:
await django_app(scope, receive, send) # Django にルーティング
※上記の「/fastapi/にマウント」とは何か
これは、以下のコマンドでASGI(Asynchronous Server Gateway Interface)サーバーuvicorn (https://www.uvicorn.org/) を起動後、「http://127.0.0.1:8000/fastapi/ というエンドポイントにアクセスすると、埋め込んだFastAPIにアクセスできる」という意味です。
※「/fastapi」がFastAPIにアクセスするときのルートプレフィックスになっています。 (なお、このfastapiの部分は別に「fastapi」である必要は無い)
uvicorn django_portfolio.asgi:application --reload
※このコマンドを起動すると以下のようにまず「http://127.0.0.1:8000/」 が表示されますが、こちらにアクセスすると(/fastapiを避ければ) 「async def application~」の処理で Django (django_app) にルーティングされるため、Django のルート (urls.py に定義されたエンドポイント) に従います。
※「/fastapi/」をつけてFastAPIにアクセスした様子
その後、依然全然何も分かってないので、Pythonの基礎の復習をしながら、主な参考資料①の内容をサンプルとして上記のDjangoプロジェクトに組み込む中で、Djangoにおけるコントローラーであるviews.pyに周りにはいくつか制約があることを知ったので、以下個人的な備忘録としてまとめました。(よく文章が冗長になりがちなので、urls.pyとかは省略します。そのへんは大丈夫なものとしてください)
①views.py内におけるdefから始まる処理(※)には、処理内に「request」となくとも引数に「request」を入れないとエラーになる。(※一般には「ビュー関数」と呼ぶようです。)
詳細
from django.http import HttpResponse
def Hello(request): 👈コイツ
return HttpResponse("Hello World!")
あまり深く考えずに学習を進めていたんですが、学習の過程で久しぶりにreturnの後ろに render以外のものを書いていて気づきました。ドキュメントにも以下のようにありました。クラス内におけるselfと似ていますが、request は HttpRequest のインスタンスであるのに対し、self は何らかの「クラス」のインスタンスであることが違います。(要は「クラス内のメソッド」の第1引数として書く)
例えば「request」一見処理に使ってないじゃんと思って
from django.http import HttpResponse
def Hello():
return HttpResponse("Hello World!")
などと書くとルーティングとか他は書けていても以下のようなエラーが出ます
②views.py内におけるビュー関数において、レスポンス処理として返すものはHttpResponseオブジェクトまたはそのサブクラスである必要がある
詳細
ドキュメントにも以下のようにありました。「要はreturnとか使ったらHttpresponseとかそのサブクラス(よく使うものだと「JsonResponse」)の中に最終的に入れて返り値を返せ」 的な意味です
※上記の公式ドキュメントURL
https://docs.djangoproject.com/ja/5.1/ref/request-response/#:~:text=JsonResponse%20%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88
エラーになる例(2) 文字列オブジェクトは返せない
from django.http import HttpResponse
def Hello(request):
return "Hello World!"
エラーになる例(3) 普段のprint()の書き方
from django.http import HttpResponse
def Hello(request):
print("Hello World!")
※注意 いずれの場合においてもPythonがインタープリター言語のためか、他の部分にエラーがなければ、開発用サーバーを起動すると、開発用サーバーは動きます。またその際、他のエンドポイントはそのエンドポイントにエラー無ければアクセスできます
なお、printをどうしても使いたい場合例えばこうすればいけるようです。
def Hello_World(request):
print("Hello World!",request.GET)
return HttpResponse() 👈やっぱりコイツ(かそのサブクラス)
なお、これのエンドポイントにアクセスすると、ブラウザは真っ白、ターミナルにprintの値が出力されます。(ここまで書いたらreturn HttpResponseに突っ込むと思いますが(-_-;))
③出力結果として返される何らかの配列からクヴォーテーションをどうしても取り除きたい場合、join()、f-strings、isliceなどを使ってなんとかしないといけない
地味にこれが結構厄介でした。
(※isliceはPythonの標準ライブラリーitertoolsに入っている関数です。詳しくは主な参考資料⑤を参照)
詳細
例えば、以下のビュー関数を出力すると次のようになります。
from django.http import JsonResponse,HttpResponse
def sample(request):
sample_list = ('hogehoge', "fugafuga")
sample_dict = {'a': 1, "b": 2, 'c': 3}
params = [
sample_list,
sample_dict
]
return HttpResponse(params)
〇とりあえず見やすさのために改行
def sample(request):
sample_list = ('hogehoge', "fugafuga")
sample_dict = {'a': 1, "b": 2, 'c': 3}
params = [
sample_list,
sample_dict
]
# 各要素を個別に文字列化し、改行(<br>)で結合
results = "<br>".join(str(param) for param in params) #👈
return HttpResponse(results) #👈
〇シングルクォーテーションを取り除いて「見かけ上」配列にする
def sample(request):
sample_list = ('hogehoge', "fugafuga")
sample_dict = {'a': 1, "b": 2, 'c': 3}
params = [
#sample_listを「見かけ上」シングルクォーテーションがないタブルになっていないように整形
f"({','.join(f'{value}' for value in sample_list)})", #👈
#sample_dictを「見かけ上」シングルクォーテーションがない辞書になっていないように整形
"{"+f"{','.join(f'{key}: {value}' for key, value in sample_dict.items())}"+"}" #👈
]
# 各要素を個別に文字列化し、改行(<br>)で結合
results = "<br>".join(str(param) for param in params)
return HttpResponse(results)
このときの出力結果(※あくまで文字列が連結されているものが出力されていることに注意)
〇特定のkeyをisliceを使って抽出(このとき、シングルクォーテーションは付かない)
def sample2(request):
sample_dict = {'a': 1, "b": 2, 'c': 3}
#islice(data.keys(), 1, 2) は、キーのリストからインデックス 2 から 3 の範囲をスライス
key_at_index_1 = next(islice(sample_dict.keys(), 2, 3))
# islice(iterable, start, stop)
# iterable: イテラブルオブジェクト(この場合、sample_dict.keys()
# start: 開始インデックス(この場合、2)
#stop: 終了インデックス(この場合、3)※ このインデックスは含まれない(👈重要)
#next()で最初の値を取得
result = f"要素の左から3番目の key は {key_at_index_1}"
return HttpResponse(result)
※基本中の基本だと思うのですが当然Pythonの配列も一番最初の要素のindexは「0」であることに気をつけてください。
※Pythonにおいて出力結果・取得結果にシングルクォーテーションが入っていると問題になりうるケース
Chat-GPTに尋ねてみたところ、以下のようなケースが挙げられました。
①SQLクエリ(SQLインジェクションのリスク)
②JSONフォーマット(JavaScriptでのパースエラー)
③シェルコマンド(コマンドインジェクションのリスク)
④URLパラメータ(エンコードしないと壊れる)
⑤CSVファイル(パースエラーの可能性)
冒頭の作成を再開しようとしているWebAPI(を組み込んだプログラム)は「短縮URL」で、当時(のモブプロ)は参考資料⑥のようにFlask & SQLiteでの開発を計画していました。上記③は「そこでもしかすると使うかも」と思い検証した結果です。実際使うかは?ですが、作成過程は別にまたQiitaに起こします。
主な参考資料
①Python FastAPI本格入門 樹下雅章(著) 技術評論社
②FastAPI × Uvicorn:最強のAPI高速化コンビ誕生! @Leapcell (leapcell)さんQiita
https://qiita.com/Leapcell/items/0c0bf5e0fe84b3356c82
③WebAPIについての説明 @busyoumono99 さんQiita
https://qiita.com/busyoumono99/items/9b5ffd35dd521bafce47
④Python 3: print と return について(超基礎) @orange_u (おれんじ) さんQiita
https://qiita.com/orange_u/items/5a769459dcaa5e06c15b
⑤isliceの紹介、具体例添え(pythonのitertoolsを使いこなすために) @shihono さんQiita
https://qiita.com/shihono/items/1613f7c6c1b096256bd3
⑥【初めてのAPI】短縮URLを発行するAPIの開発【Flask + SQLite】 @yurikomium (Yuriko Kikuchi)さん Qiita
https://qiita.com/yurikomium/items/6dc3b3d733b40e4b5f6a