はじめに:Responderとは? 🤔
Responderは、Pythonコミュニティで有名な開発者であるKenneth Reitz氏によって開発されたWebサービスフレームワークです。彼は「Requests」や「Pipenv」といった人気ライブラリの作者としても知られています。Responderは、特に「人間にとって使いやすい」ことを目指して設計されました。
このフレームワークの基本的な考え方は、当時人気だったFlaskとFalconという二つのフレームワークの良いところを取り入れ、さらに新しいアイデアを加えて統合することでした。また、Requestsライブラリで培われたAPIの設計思想をWebフレームワークにも持ち込むことを目指していました。
ResponderはASGI (Asynchronous Server Gateway Interface) に準拠しています。ASGIは、WSGI (Web Server Gateway Interface) の後継として登場したインターフェースで、非同期処理を前提として設計されています。これにより、WebSocketやHTTP/2といったモダンなプロトコルを効率的に扱うことが可能になります。Responderは内部的にStarletteという高性能なASGIツールキットを利用しており、非同期処理を容易に実装できる点が大きな特徴です。
注意点: ResponderはKenneth Reitz氏自身によって「実験的なプロジェクト」であり、「学術的な演習」と位置付けられています。現在では、FastAPIのような、より成熟し、活発にメンテナンスされている代替フレームワークの使用が推奨されています。Responderは開発が活発ではなく、将来的なサポートに懸念があります。しかし、その設計思想やコードは学ぶべき点が多く、ASGIフレームワークの初期の試みとして興味深い存在です。
Responderの主な特徴 ✨
- シンプルなAPI:
import responder
だけで主要な機能を利用できます。 - クラスベースビュー: 継承なしでクラスを用いたビューを作成できます。
- ASGI準拠: Python Webサービスの未来であるASGIに完全対応しています。
- WebSocketサポート: リアルタイム双方向通信を簡単に実装できます。
- f-string形式のルーティング: Python 3.6以降でお馴染みのf-string構文で直感的にルートを定義できます。
- 変更可能なレスポンスオブジェクト: 各ビュー関数に
resp
オブジェクトが渡され、これを変更することでレスポンスを構築します。関数から値をreturn
する必要はありません。 - バックグラウンドタスク:
ThreadPoolExecutor
を利用して、レスポンス返却後に非同期でタスクを実行できます。 - GraphQLサポート: Grapheneライブラリと連携し、GraphQL APIを構築できます(GraphiQLインターフェース付き)。
- OpenAPIスキーマ自動生成: API定義から自動でOpenAPI(Swagger)仕様を生成し、インタラクティブなドキュメントを提供します。
- 静的ファイル配信: WhiteNoiseが組み込まれており、プロダクション環境での静的ファイル配信に対応しています。
- Jinja2テンプレート: 追加のインポートなしでJinja2テンプレートエンジンを利用できます。
- 組み込みWebサーバー: uvloopをベースにした高パフォーマンスなASGIサーバー (Uvicorn) が組み込まれており、自動的にgzip圧縮も行います。
- WSGI/ASGIアプリのマウント: FlaskやStarletteなど、他のWSGI/ASGIアプリケーションをサブルートにマウントできます。
インストール 💻
Responderを使用するには、Python 3.6以上が必要です(ドキュメントによっては3.7+と記載されている場合もありますが、初期は3.6+でした)。インストールはpipを使って簡単に行えます。
pip install responder
GraphQLやOpenAPIサポートなど、すべての拡張機能を含めてインストールする場合は、[full]
オプションを使用します。
pip install 'responder[full]'
特定の機能のみを追加することも可能です。
# GraphQLサポートのみ
pip install 'responder[graphql]'
# OpenAPIサポートのみ
pip install 'responder[openapi]'
開発環境の管理には、Kenneth Reitz氏が開発したPipenvを使うと便利です。
# Pipenvをインストール(未導入の場合)
brew install pipenv # macOSの場合 (他のOSでは適宜変更)
# プロジェクトディレクトリを作成し移動
mkdir myresponderapp
cd myresponderapp
# Python 3.7 (またはそれ以降) を指定して仮想環境を作成し、responderをインストール
pipenv install responder --python 3.7
基本的な使い方:Hello World! 👋
Responderで最も簡単なアプリケーションを作成してみましょう。以下のコードをapp.py
のような名前で保存します。
import responder
# Webサービス(API)オブジェクトを作成
api = responder.API()
# ルート "/" に対するビュー関数を定義
@api.route("/")
def hello_world(req, resp):
resp.text = "hello, world!"
# サーバーを起動 (スクリプトとして直接実行された場合)
if __name__ == "__main__":
api.run()
このコードは、まずresponder.API()
でAPIインスタンスを作成します。次に、@api.route("/")
デコレータを使って、ルートパス (/
) へのGETリクエストを処理する関数hello_world
を定義します。
ビュー関数は、req
(リクエストオブジェクト) と resp
(レスポンスオブジェクト) の2つの引数を受け取ります。Responderでは、関数内でresp
オブジェクトの属性(ここではresp.text
)を変更することでレスポンス内容を決定します。関数から何かをreturn
する必要はありません。
最後に、if __name__ == "__main__":
ブロック内でapi.run()
を呼び出すことで、開発用のWebサーバーを起動します。
ターミナルでこのファイルを実行します。
python app.py
デフォルトでは、http://127.0.0.1:5042
でサーバーが起動します。Webブラウザでこのアドレスにアクセスすると、”hello, world!”というテキストが表示されるはずです。ポート番号を変更したい場合は、api.run(port=8000)
のように指定できます。
ルーティング 🧭
Responderのルーティングは非常に直感的です。@api.route()
デコレータを使用し、パスとビュー関数を結びつけます。
パスパラメータ
URLの一部を動的に変化させたい場合は、f-stringのような構文を使用します。
import responder
api = responder.API()
@api.route("/hello/{who}")
def hello_to(req, resp, *, who):
resp.text = f"hello, {who}!"
if __name__ == "__main__":
api.run()
この例では、/hello/リポジトリ
のようなURLにアクセスすると、who
パラメータにリポジトリ
という文字列が渡され、レスポンスは “hello, リポジトリ!” となります。
パラメータには型ヒントを指定することも可能です。サポートされているのはstr
(デフォルト)、int
、float
です。
@api.route("/add/{a:int}/{b:int}")
async def add(req, resp, *, a, b):
result = a + b
resp.text = f"{a} + {b} = {result}"
/add/5/3
にアクセスすると、a
には整数5
、b
には整数3
が渡され、レスポンスは “5 + 3 = 8” となります。もし/add/hello/world
のように型に合わない値が渡された場合、Responderは自動的に404 Not Foundエラーを返します。
HTTPメソッド
デフォルトでは、@api.route()
はGETリクエストのみを受け付けます。他のHTTPメソッド(POST, PUT, DELETEなど)を処理するには、methods
引数を指定します。
@api.route("/items", methods=["POST"])
async def create_item(req, resp):
# POSTリクエストの処理 (データ受信については後述)
data = await req.media()
print(f"Received data: {data}")
resp.media = {"status": "item created", "data": data}
resp.status_code = 201 # Created
@api.route("/items/{item_id}", methods=["PUT", "DELETE"])
async def update_or_delete_item(req, resp, *, item_id):
if req.method == "put":
# PUTリクエストの処理
data = await req.media()
resp.media = {"status": f"item {item_id} updated", "data": data}
elif req.method == "delete":
# DELETEリクエストの処理
resp.media = {"status": f"item {item_id} deleted"}
resp.status_code = 204 # No Content
req.method
属性で、どのHTTPメソッドでリクエストされたかを確認できます。
クラスベースビュー
関連するHTTPメソッドの処理を一つのクラスにまとめることもできます。クラス内のメソッド名がHTTPメソッドに対応します(例: on_get
, on_post
)。
import responder
api = responder.API()
@api.route("/users/{user_id}")
class UserResource:
def on_get(self, req, resp, *, user_id):
resp.media = {"user_id": user_id, "name": f"User {user_id}"}
async def on_put(self, req, resp, *, user_id):
data = await req.media()
resp.media = {"status": f"user {user_id} updated", "data": data}
def on_delete(self, req, resp, *, user_id):
resp.status_code = 204 # No Content
if __name__ == "__main__":
api.run()
すべてのメソッド (GET, POST, PUT, DELETEなど) で共通の処理を行いたい場合は、on_request
メソッドを定義します。これはFalconフレームワークの考え方に似ています。
リクエストとレスポンス 📨📤
リクエスト (req)
ビュー関数に渡されるreq
オブジェクトを通して、受信したリクエストに関する情報にアクセスできます。
req.method
: HTTPメソッド (例: “GET”, “POST”)req.url
: 完全なURLreq.headers
: リクエストヘッダー (Requestsライブラリと同様のケースインセンシティブな辞書)req.query_params
: クエリパラメータ (例:/search?q=hello
の場合{'q': 'hello'}
)req.cookies
: クッキー
リクエストボディのデータ(フォームデータ、JSONなど)を受け取る場合は、ビュー関数をasync def
で定義し、await req.media()
を使用する必要があります。ResponderはContent-Typeヘッダーに基づいて自動的にデータをパースします。
@api.route("/submit", methods=["POST"])
async def handle_submission(req, resp):
# Content-Typeに応じてフォームデータ、JSON、YAMLを自動解析
data = await req.media()
# Content-Typeが 'application/x-www-form-urlencoded' または 'multipart/form-data' の場合
# form_data = await req.form()
# Content-Typeが 'application/json' の場合
# json_data = await req.media() or await req.json
# 生のバイト列としてボディを取得
# raw_body = await req.content
# テキストとしてボディを取得
# text_body = await req.text
resp.media = {"received": data}
req.media()
は、一般的なContent-Type (JSON, YAML, Form) を自動判別してPythonオブジェクト(通常は辞書)に変換します。特定の形式を期待する場合は、req.json
, req.yaml
, req.form
を直接使うこともできます。
レスポンス (resp)
resp
オブジェクトの属性を設定することで、クライアントに返すレスポンスを構築します。
resp.text = "..."
: テキスト (text/plain
) を返します。resp.html = "<html>...</html>"
: HTML (text/html
) を返します。resp.media = {...}
or[...]
: Pythonの辞書やリストをJSON (application/json
) またはYAML (application/x-yaml
、クライアントがAccept: application/x-yaml
ヘッダーを送った場合) に変換して返します。resp.content = b"..."
: バイト列 (application/octet-stream
など、Content-Typeは別途設定推奨) を返します。resp.status_code = ...
: HTTPステータスコードを設定します (例:200
,201
,404
)。デフォルトは200 OK
です。responder.status_codes
モジュールに定数が用意されています (例:api.status_codes.HTTP_201_CREATED
)。resp.headers["Header-Name"] = "Value"
: レスポンスヘッダーを設定します。resp.cookies["cookie_name"] = "value"
: クッキーを設定します。
例:JSONレスポンスを返す
@api.route("/info")
def get_info(req, resp):
info_data = {
"framework": "Responder",
"version": "2.0.7", # 例
"status": "Experimental"
}
resp.media = info_data
resp.headers["X-Powered-By"] = "Responder Framework"
例:リダイレクトを行う
@api.route("/old-page")
def redirect_to_new(req, resp):
resp.status_code = api.status_codes.HTTP_301_MOVED_PERMANENTLY
resp.headers["Location"] = "/new-page"
テンプレートエンジン (Jinja2) 🎨
ResponderはJinja2テンプレートエンジンを組み込みでサポートしており、HTMLを動的に生成するのに便利です。追加のインポートは基本的に不要です。
まず、プロジェクトルートにtemplates
というディレクトリを作成し、その中にHTMLテンプレートファイルを配置します(例: templates/index.html
)。
templates/index.html
:
<!DOCTYPE html>
<html>
<head>
<title>Hello {{ name }}!</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.1/css/bulma.min.css">
</head>
<body>
<section class="section">
<div class="container">
<h1 class="title">Hello, {{ name }}!</h1>
<p class="subtitle">This page is rendered by Responder with Jinja2.</p>
</div>
</section>
</body>
</html>
Pythonコード側では、resp.html
にapi.template()
メソッドの呼び出し結果を代入します。
import responder
api = responder.API()
@api.route("/greet/{name}")
def greet_html(req, resp, *, name):
# templates/index.html をレンダリングし、name変数を渡す
resp.html = api.template("index.html", name=name)
if __name__ == "__main__":
api.run()
/greet/あなたの名前
にアクセスすると、templates/index.html
がレンダリングされ、{{ name }}
の部分がURLで指定した名前に置き換えられたHTMLが表示されます。
非同期でテンプレートをレンダリングする必要がある場合(テンプレート内で非同期関数を呼び出すなど)は、Templates
クラスを明示的にインスタンス化し、render_async
を使用します。
from responder.templates import Templates
# 非同期レンダリングを有効にする
templates = Templates(directory="templates", enable_async=True)
@api.route("/async-greet/{name}")
async def async_greet_html(req, resp, *, name):
# 非同期でレンダリング
resp.html = await templates.render_async("index.html", name=name)
静的ファイル 🖼️📄
CSS、JavaScript、画像などの静的ファイルを配信するには、プロジェクトルートにstatic
というディレクトリを作成し、その中にファイルを配置します。ResponderはWhiteNoiseライブラリを内部で使用しており、特別な設定なしで/static/
パス以下でこれらのファイルを提供します。
例: static/css/style.css
を作成した場合
/* static/css/style.css */
body {
font-family: sans-serif;
background-color: #f0f0f0;
}
h1 {
color: #333;
}
HTMLテンプレートからは、/static/css/style.css
というパスで参照できます。
<head>
<link rel="stylesheet" href="/static/css/style.css">
</head>
静的ファイルを提供するディレクトリ名やURLパスを変更したい場合は、responder.API
の初期化時に引数を指定します。
api = responder.API(static_dir="assets", static_route="/files")
# この場合、'assets' ディレクトリ内のファイルが '/files/' パスで提供される
バックグラウンドタスク ⏳
リクエストへのレスポンスを返した後に、時間のかかる処理(メール送信、データ処理など)を実行したい場合があります。Responderでは、@api.background.task
デコレータを使ってバックグラウンドタスクを簡単に定義できます。
バックグラウンドタスクを実行する関数を定義し、ビュー関数内でその関数を呼び出します。バックグラウンドタスクを実行するビュー関数はasync def
である必要があります。
import responder
import time
api = responder.API()
@api.background.task
def process_data(data):
"""
時間のかかる処理をシミュレート(例: 3秒待機)
"""
print(f"Processing data in background: {data}")
time.sleep(3)
print("Background task finished.")
@api.route("/upload", methods=["POST"])
async def upload_file(req, resp):
# リクエストからデータを取得
data = await req.media()
# バックグラウンドタスクとして process_data を呼び出す
# この呼び出しはすぐに完了し、レスポンス処理に進む
process_data(data)
# クライアントにはすぐにレスポンスを返す
resp.media = {"message": "File upload accepted, processing in background."}
resp.status_code = 202 # Accepted
if __name__ == "__main__":
api.run()
この例では、/upload
エンドポイントにPOSTリクエストを送ると、データを受け取った後、process_data
関数がバックグラウンドで実行されます。クライアントにはすぐにHTTP 202 Acceptedレスポンスが返され、待たされることはありません。サーバーのコンソールには、バックグラウンドタスクの開始と終了のメッセージが(3秒の間隔をあけて)表示されます。
これはStarletteのBackgroundTasks
機能を利用しています。FastAPIなど他のStarletteベースのフレームワークでも同様の機能が提供されています。
WebSocket ↔️
ResponderはWebSocketをサポートしており、リアルタイムの双方向通信アプリケーションを構築できます。WebSocketのエンドポイントは@api.route
デコレータで定義しますが、ビュー関数ではなく、WebSocket接続を処理するクラス(またはASGIアプリケーション)を指定します。
内部的にはStarletteのWebSocketサポートを利用します。以下は簡単なPing-Pongの例です。
import responder
from starlette.websockets import WebSocket, WebSocketDisconnect
api = responder.API(debug=True) # デバッグモードで詳細ログ表示
@api.route("/ws", websocket=True)
async def websocket_endpoint(ws: WebSocket):
await ws.accept()
print("WebSocket connected")
try:
while True:
# クライアントからメッセージを受信
data = await ws.receive_text()
print(f"Received message: {data}")
# メッセージに応じて応答
if data == "ping":
await ws.send_text("pong")
else:
await ws.send_text(f"Message received: {data}")
except WebSocketDisconnect:
print("WebSocket disconnected")
except Exception as e:
print(f"WebSocket error: {e}")
await ws.close(code=1011) # Internal Server Error
if __name__ == "__main__":
# api.run() は開発用サーバーであり、WebSocketの安定性に限界がある場合がある
# プロダクションではUvicornを直接使うことを推奨
# 例: uvicorn app:api --reload
api.run()
この例では:
@api.route("/ws", websocket=True)
でWebSocketエンドポイントを定義します。- 関数
websocket_endpoint
は引数としてWebSocket
オブジェクト (Starletteのもの) を受け取ります。 await ws.accept()
でクライアントからの接続要求を受け入れます。while True
ループ内でawait ws.receive_text()
を呼び出し、クライアントからのテキストメッセージを待ち受けます。- 受信したメッセージが “ping” なら “pong” を、それ以外なら受信したメッセージを含む応答を
await ws.send_text()
でクライアントに送信します。 - クライアントが切断すると
WebSocketDisconnect
例外が発生し、ループを抜けます。 - 予期せぬエラーが発生した場合もエラーログを出力し、接続を閉じます。
このエンドポイントに接続するクライアント(例: JavaScript)を作成することで、リアルタイム通信が可能になります。
注意: api.run()
で起動する開発サーバーは、WebSocketの長時間接続や多数の同時接続には向いていない場合があります。プロダクション環境では、uvicorn app:api --reload
のようにUvicorn ASGIサーバーを直接使用することが推奨されます。
より複雑なケースでは、Starletteのドキュメントにあるように、WebSocketの接続、受信、送信、切断を処理するクラスを定義し、api.add_route("/ws", YourWebSocketClass)
のようにルートに追加する方法もあります。
テスト 🧪
Responderは、テスト用にRequestsライブラリに基づいたテストクライアントを提供します。これにより、実際のHTTPリクエストを送るのと同じような感覚でAPIをテストできます。
テストにはpytest
などのテストフレームワークを使用するのが一般的です。
例: test_app.py
import pytest
import app # app.py で api = responder.API() が定義されていると仮定
# pytestフィクスチャでAPIクライアントを準備
@pytest.fixture
def api_client():
return app.api.requests # responder APIオブジェクトからテストクライアントを取得
def test_hello_world(api_client):
""" "/" ルートが "hello, world!" を返すことをテスト """
response = api_client.get("/")
assert response.status_code == 200
assert response.text == "hello, world!"
def test_hello_to(api_client):
""" "/hello/{who}" ルートが正しく動作することをテスト """
response = api_client.get("/hello/tester")
assert response.status_code == 200
assert response.text == "hello, tester!"
def test_post_data(api_client):
""" POSTリクエストとJSONレスポンスをテスト """
payload = {"key": "value", "number": 123}
response = api_client.post("/submit", json=payload) # /submit がJSONを受け付けると仮定
assert response.status_code == 200
assert response.json()["received"] == payload
# 非同期エンドポイントのテストも同様に行える
# def test_async_endpoint(api_client):
# response = api_client.get("/async-route")
# assert response.status_code == 200
# # ... アサーション ...
テストを実行するには、pytest
コマンドを使用します。
# 開発依存ライブラリとしてpytestをインストール
pipenv install pytest --dev
# テストを実行
pipenv run pytest
api.requests
は、実際のRequestsライブラリと同様のインターフェース(.get()
, .post()
, .put()
, .delete()
など)を提供し、json=
やdata=
引数でペイロードを指定できます。レスポンスオブジェクトもRequestsのものと似ており、.status_code
, .text
, .json()
などで結果を確認できます。
デプロイ 🚀☁️
ResponderアプリケーションはASGIアプリケーションなので、Uvicorn, Hypercorn, DaphneなどのASGIサーバーを使ってデプロイする必要があります。api.run()
は開発用であり、プロダクション環境での使用は推奨されません。
Uvicornを使った基本的なデプロイ
最も一般的な方法はUvicornを使うことです。まずUvicornをインストールします。
pip install uvicorn
そして、app.py
(api = responder.API()
が含まれるファイル)があるディレクトリで以下のコマンドを実行します。
uvicorn app:api --host 0.0.0.0 --port 8000
app:api
:app.py
ファイル内のapi
という名前のASGIアプリケーションオブジェクトを指定します。--host 0.0.0.0
: すべてのネットワークインターフェースでリッスンします(外部からのアクセスを許可)。--port 8000
: ポート8000でリッスンします。--workers 4
: (オプション)ワーカープロセス数を指定してパフォーマンスを向上させます。--reload
: (開発時)コードが変更されたら自動的にリロードします。プロダクションでは使用しません。
Gunicorn + Uvicornワーカー
より堅牢なプロセス管理のために、Gunicornをプロセススーパーバイザーとして使用し、Uvicornをワーカークラスとして指定する方法もよく使われます。
pip install gunicorn uvicorn
gunicorn app:api -w 4 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000
-w 4
: ワーカープロセスの数を4つに設定します。CPUコア数などを参考に調整します。-k uvicorn.workers.UvicornWorker
: GunicornにUvicornワーカーを使用するよう指示します。--bind 0.0.0.0:8000
: リッスンするアドレスとポートを指定します。
Dockerを使ったデプロイ
アプリケーションをコンテナ化してデプロイすることも一般的です。以下は簡単なDockerfile
の例です。
# ベースイメージを選択 (Python 3.7以降)
FROM python:3.9-slim
# 作業ディレクトリを設定
WORKDIR /app
# 依存関係ファイルをコピー
COPY Pipfile Pipfile.lock ./
# または requirements.txt をコピー
# COPY requirements.txt ./
# Pipenvを使って依存関係をインストール (システム全体に)
RUN pip install pipenv && pipenv install --system --deploy --ignore-pipfile
# または pip を使う場合
# RUN pip install --no-cache-dir -r requirements.txt
# アプリケーションコードをコピー
COPY . .
# アプリケーションがリッスンするポートを指定
EXPOSE 8000
# コンテナ起動時にUvicornを実行
CMD ["uvicorn", "app:api", "--host", "0.0.0.0", "--port", "8000"]
このDockerfileをビルドし、実行することで、Responderアプリケーションをコンテナとしてデプロイできます。
PaaS (Herokuなど)
HerokuのようなPaaSプラットフォームにもデプロイできます。通常、Procfile
を作成してGunicorn + Uvicornの起動コマンドを指定します。
Procfile
:
web: gunicorn app:api -w 4 -k uvicorn.workers.UvicornWorker
依存関係はPipfile
やrequirements.txt
で管理します。
まとめと注意点 📌
Responderは、PythonのWebフレームワークの中でも、特に開発者の体験を重視して設計された、シンプルで使いやすいフレームワークでした。ASGIへの早期対応、直感的なルーティング、組み込みのテストクライアントやWebSocketサポートなど、モダンなWebアプリケーション開発に必要な機能を多く備えていました。
しかし、冒頭でも述べたように、Responderは現在活発に開発されておらず、作者自身もFastAPIの使用を推奨しています。FastAPIはResponderと同様にStarletteをベースにしており、型ヒントを活用したデータ検証や自動ドキュメント生成など、さらに多くの機能と高いパフォーマンスを提供し、コミュニティも非常に活発です。
これから新しいプロジェクトを始める場合、特にAPI開発や非同期処理が重要な場合は、FastAPIやStarlette、Django (バージョン3以降はASGIサポートあり)、Flask (ASGIサポートを追加可能) などを検討することをお勧めします。
Responderは、ASGIフレームワークの初期の設計思想や、Kenneth Reitz氏のAPI設計哲学を学ぶ上で依然として価値がありますが、プロダクション環境での新規採用は慎重に判断する必要があります。このガイドがResponderの理解の一助となれば幸いです 😊。
参考情報 📚
-
Responder 公式ドキュメント (最終バージョン): https://python-responder.org/en/latest/
(注意: ドキュメントサイトが利用できなくなっている可能性もあります。GitHubリポジトリ内のドキュメントを参照する必要があるかもしれません。)
-
Responder GitHubリポジトリ: https://github.com/kennethreitz/responder
(作者によるステータスについての言及や、最終更新日などを確認できます。)
-
Kenneth Reitz氏のResponderに関するページ: https://kennethreitz.org/projects/responder/
(FastAPIを推奨する旨などが記載されています。)
-
FastAPI 公式サイト: https://fastapi.tiangolo.com/
(現在推奨される代替フレームワーク。)
-
Starlette 公式サイト: https://www.starlette.io/
(ResponderやFastAPIの基盤となっているASGIツールキット。)
-
Qiita – はじめての Responder(Python の次世代 Web フレームワーク) (2019-10-03): https://qiita.com/nskydiving/items/41a4a510957d6240902b
(古い情報ですが、日本語での導入記事例。)
コメント