nikkie-ftnextの日記

イベントレポートや読書メモを発信

Django Ninja 素振りの記:ReadとCreateを非同期ビュー・ORMの非同期APIに書き換える🥷

はじめに

七尾百合子さん、お誕生日 104日目 おめでとうございます! nikkieです。

オンラインで開催しているDjangoもくもく会の成果報告エントリです

目次

前回のDjango Ninja 🥷

書籍アプリのReadとCreateをシュババと実装しています

Djangoモデルを用意して、リクエストとレスポンスのスキーマを定義するだけで、同期のReadとCreateは実装できました

FastAPIと比較するなら async / await はどうなる?(宿題事項)

今回はReadとCreateを非同期にしていきます

Django NinjaのAsync support

見つけて積ん読にしていた、ズバリなドキュメントがあります

django-ninja.dev

今回の素振りでは Django 5.2 を使っていますが、asyncなORM APIがサポートされているので簡単に書き換えられました。
参考箇所:Using ORM (Django Ninja)

async viewへの書き換え

最終的なソースコードはこちらです

uvicorn導入

https://django-ninja.dev/guides/async-support/#run

python manage.py runserver ではなく、uvicornを使ってサーバーを起動します。

% uv run uvicorn myproject.asgi:application --reload
myproject/
├── book_api/
├── myproject/
└── manage.py

Create

before afterをdiffで示します。

@api.post("/books", response={201: BookOut})
-def create_book(request, payload: BookIn):
+async def create_book(request, payload: BookIn):
-    book = Book.objects.create(**payload.dict())
+    # book = await sync_to_async(Book.objects.create)(**payload.dict())
+    book = await Book.objects.acreate(**payload.dict())
    return book
  • def -> async def に変更(関数内でawaitが使えます)
  • ORM操作はcreate() -> 非同期acreate()に変更
  • Django 4.0まではsync_to_asyncを使っており、こちらも引き続き動作します

Read

@api.get("/books", response=list[BookOut])
-def get_books(request):
+async def get_books(request):
-    books_qs = Book.objects.all()
-    return books_qs
+    # all_books = await sync_to_async(list)(Book.objects.all())
+    all_books = [book async for book in Book.objects.all()]
+    return all_books
  • こちらもasync defに変更 *クエリセット(all()の返り値)がasync forでイテレート可能になったようです
  • sync_to_asyncを使う場合は、クエリセットが遅延評価のためにsync_to_async(list)というものが出てくるとのこと

以上を書き換えてE2Eは通り続けました!

終わりに

Django NinjaでReadとCreateをasync viewsで実装できました。
かつてはasgiref.syncsync_to_asyncを使っていましたが、Django 4.1以降多くのORM操作に非同期APIが用意され、より簡単に書けます!

uvicornについてはFastAPIで使っているので、その知見も活かせそうですね

P.S. Djangoは真に非同期ではない?

今回のもくもくで読んだ範囲のドキュメントには、「トランザクションのORMレベルでの非同期サポートはまだ」という記述がありました。
https://docs.djangoproject.com/ja/5.2/topics/async/#queries-the-orm

トランザクションは非同期モードではまだ機能しません。もしトランザクションの動作が必要なコードがある場合は、そのコードを1つの同期的な関数として書いて、sync_to_async() を使用して呼び出すことをおすすめします。

「非同期対応、まだ終わらないのかな?」という疑問には、DjangoCongress JP 2025の「The Async Django ORM: Where Is it?」という発表が回答になっていそうです

NotebookLMに入れたところ、そもそもORMの非同期API真の非同期ではないようです(トークを聞いて理解を深めたい)。
Django Ninja、書き味はかなりFastAPIですが、Django自体が真に非同期にできていないのならパフォーマンスに影響ありそうですね...