Skip to content

Instantly share code, notes, and snippets.

@kashewnuts
Last active October 6, 2024 12:56
Show Gist options
  • Save kashewnuts/d27e63b2072f029b1d21fe40c0cbc63d to your computer and use it in GitHub Desktop.
Save kashewnuts/d27e63b2072f029b1d21fe40c0cbc63d to your computer and use it in GitHub Desktop.
DjangoNinja導入検討memo

django-ninja調査MEMO

django-ninjaとは

  • 公式ドキュメント: Django Ninja
  • FastAPIに強く影響を受けた、DjangoのREST API作成用フレームワーク
  • Python の型ヒントを利用して Web API のスキーマを定義でき、バリデーションも実行できる

チュートリアル

拡張: Django Ninja Extra

  • 公式ドキュメント: Django Ninja Extra
  • クラスベースの実装をしたい場合はDjango Ninjaはサポートしていないため、Django Ninja Extraの導入を検討する。
    • 既存の処理がクラスベースで実装しているので、この実装を活かす場合はこちらの選択になる。

各機能について

以下の機能はデフォルトでサポートしている。

Pydantic v2 サポート

  • 今回は入出力の指定にSchemaを定義するときにPydanticが使われる。

OpenAPIドキュメント: API Docs

  • RequestとResponseのSchemaを書いて指定すればRedoc形式でいい感じに表示できる。
  • Responseを他の仕組みで(例: bpmappers)処理している場合、二度手間に感じるかもしれない。

OAuth2やAPI Keyでの認証対応: Authentication

Throttling: v1.2.0でサポート

サンプルスクリプト

# main.py
import time

import requests

# NOTE: nicknameは適時書き換え
url = "https://example.com/api/v1/user/?nickname=kashew_local"
requests_per_second = 100
interval = 1 / requests_per_second


def send_requests(url):
    while True:
        response = requests.get(url)
        print(f"Status Code: {response.status_code}, Response: {response.text}")
        time.sleep(interval)


if __name__ == "__main__":
    send_requests(url)

レスポンス

python3 main.py
Status Code: 200, Response: {"results_start": 1, "results_returned": 1, "results_available": 1, "users": [{"user_id": 578232, "nickname": "kashew_local", "display_name": "kashewkashewkashewkashewkashew", "description": "\u4eba\u751f\u3068\u3044\u3046\u58c1\u3092\u767b\u308bSoftwareDeveloper. \u4ed5\u4e8b\u3067\u306fPython\u3068\u304bDjango\u3068\u304b. \u6687\u3055\u3048\u3042\u308c\u3070Vim\u3063\u305f\u308a\u3001\u30dc\u30eb\u30c0\u30ea\u30f3\u30b0\u3057\u305f\u308a\u3059\u308b. Wish List: https://t.co/tA2Psk0Ot3", "user_url": "https://example.com/user/kashew_local/", "user_image_url": "https://media.dev-connpass.owo3.net/thumbs/4c/2f/4c2fa504a87486db29c73b5ab397d2c3.png", "created_at": "2023-10-13T10:50:41+09:00", "attended_event_count": 6, "organize_event_count": 27, "presenter_event_count": 1, "bookmark_event_count": 1}]}
Status Code: 200, Response: {"results_start": 1, "results_returned": 1, "results_available": 1, "users": [{"user_id": 578232, "nickname": "kashew_local", "display_name": "kashewkashewkashewkashewkashew", "description": "\u4eba\u751f\u3068\u3044\u3046\u58c1\u3092\u767b\u308bSoftwareDeveloper. \u4ed5\u4e8b\u3067\u306fPython\u3068\u304bDjango\u3068\u304b. \u6687\u3055\u3048\u3042\u308c\u3070Vim\u3063\u305f\u308a\u3001\u30dc\u30eb\u30c0\u30ea\u30f3\u30b0\u3057\u305f\u308a\u3059\u308b. Wish List: https://t.co/tA2Psk0Ot3", "user_url": "https://example.com/user/kashew_local/", "user_image_url": "https://media.dev-connpass.owo3.net/thumbs/4c/2f/4c2fa504a87486db29c73b5ab397d2c3.png", "created_at": "2023-10-13T10:50:41+09:00", "attended_event_count": 6, "organize_event_count": 27, "presenter_event_count": 1, "bookmark_event_count": 1}]}
Status Code: 200, Response: {"results_start": 1, "results_returned": 1, "results_available": 1, "users": [{"user_id": 578232, "nickname": "kashew_local", "display_name": "kashewkashewkashewkashewkashew", "description": "\u4eba\u751f\u3068\u3044\u3046\u58c1\u3092\u767b\u308bSoftwareDeveloper. \u4ed5\u4e8b\u3067\u306fPython\u3068\u304bDjango\u3068\u304b. \u6687\u3055\u3048\u3042\u308c\u3070Vim\u3063\u305f\u308a\u3001\u30dc\u30eb\u30c0\u30ea\u30f3\u30b0\u3057\u305f\u308a\u3059\u308b. Wish List: https://t.co/tA2Psk0Ot3", "user_url": "https://example.com/user/kashew_local/", "user_image_url": "https://media.dev-connpass.owo3.net/thumbs/4c/2f/4c2fa504a87486db29c73b5ab397d2c3.png", "created_at": "2023-10-13T10:50:41+09:00", "attended_event_count": 6, "organize_event_count": 27, "presenter_event_count": 1, "bookmark_event_count": 1}]}
Status Code: 200, Response: {"results_start": 1, "results_returned": 1, "results_available": 1, "users": [{"user_id": 578232, "nickname": "kashew_local", "display_name": "kashewkashewkashewkashewkashew", "description": "\u4eba\u751f\u3068\u3044\u3046\u58c1\u3092\u767b\u308bSoftwareDeveloper. \u4ed5\u4e8b\u3067\u306fPython\u3068\u304bDjango\u3068\u304b. \u6687\u3055\u3048\u3042\u308c\u3070Vim\u3063\u305f\u308a\u3001\u30dc\u30eb\u30c0\u30ea\u30f3\u30b0\u3057\u305f\u308a\u3059\u308b. Wish List: https://t.co/tA2Psk0Ot3", "user_url": "https://example.com/user/kashew_local/", "user_image_url": "https://media.dev-connpass.owo3.net/thumbs/4c/2f/4c2fa504a87486db29c73b5ab397d2c3.png", "created_at": "2023-10-13T10:50:41+09:00", "attended_event_count": 6, "organize_event_count": 27, "presenter_event_count": 1, "bookmark_event_count": 1}]}
Status Code: 200, Response: {"results_start": 1, "results_returned": 1, "results_available": 1, "users": [{"user_id": 578232, "nickname": "kashew_local", "display_name": "kashewkashewkashewkashewkashew", "description": "\u4eba\u751f\u3068\u3044\u3046\u58c1\u3092\u767b\u308bSoftwareDeveloper. \u4ed5\u4e8b\u3067\u306fPython\u3068\u304bDjango\u3068\u304b. \u6687\u3055\u3048\u3042\u308c\u3070Vim\u3063\u305f\u308a\u3001\u30dc\u30eb\u30c0\u30ea\u30f3\u30b0\u3057\u305f\u308a\u3059\u308b. Wish List: https://t.co/tA2Psk0Ot3", "user_url": "https://example.com/user/kashew_local/", "user_image_url": "https://media.dev-connpass.owo3.net/thumbs/4c/2f/4c2fa504a87486db29c73b5ab397d2c3.png", "created_at": "2023-10-13T10:50:41+09:00", "attended_event_count": 6, "organize_event_count": 27, "presenter_event_count": 1, "bookmark_event_count": 1}]}
Status Code: 429, Response: {"detail": "Request was throttled. Expected available in 1 second."}
Status Code: 429, Response: {"detail": "Request was throttled. Expected available in 1 second."}
Status Code: 429, Response: {"detail": "Request was throttled. Expected available in 1 second."}
Status Code: 429, Response: {"detail": "Request was throttled. Expected available in 1 second."}
....
  • デフォルトでLimitOffsetPaginationとPageNumberPaginationがあり、カスタマイズもできる
  • 入力値をlimitやoffsetでなく、startとcountにしたり、出力するフィールドを変更したい場合はカスタマイズする。

既存APIの載せ替えについて

  • 関数ベースの場合、django-ninjaで個別にrouterを定義してけば解決しそう。
  • クラスベースの場合、Django Ninja Extraを使って、基本はapi_controllerデコレーターをつけていき、routeを定義していけば解決しそう。
  • urls.pyでの定義を楽にするために api.add_routerはDjango-ninja-extraで使えるか?
    • https://django-ninja.dev/guides/routers/
    • api_controllerで定義していく仕組みなので使えない eadwinCode/django-ninja-extra#163 (comment)
    • api.auto_discover_controllers() なるものを使えば自動でrouteの定義を拾ってくるようだが、対象のモジュール名が固定(apiとcontrollers)で入っているため後からいれると怖いかも。そしてドキュメントには乗ってないなぞ。
  • cache
  • 既存の処理が壊れていないか?
    • DjangoのViewクラスを使った実装から乗せかえる場合、呼び出し方が変わるのでテストクライアントベースにテストコードを書き換える必要がある。
  • 内部向けと外部向けをどうやってわけるか→異なるバージョンとURLを指すNinjaAPI インスタンスを立てればよい

注意事項

参考URL

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment