山pの楽しいお勉強生活

勉強の成果を垂れ流していきます

Jupyter Notebook上でDjangoを動かす

はじめに

ちょっとQuerySetの動きをみたいとか、特定のコードを動かしたいとかあるよね?Django Shellでもいいのだけど、色々試したいからちょっと面倒なのでJupyter Notebookで動かしたい。 前職でも似たようなことをやっていてその時のスニペットをメモしていなくて地味に困っていたのだけど、時間ができたので再度作成しました。 もっと短かった気もするのだけど、とりあえず動くのでOK

コード

import os
import django

# https://docs.djangoproject.com/en/3.2/topics/async/#envvar-DJANGO_ALLOW_ASYNC_UNSAFE
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "api.settings")

django.setup()

上記のコードで設定が完了。次のセルでQuerySetなどが動きます。

from your_app_name.models import YourModelName

YourModelName.objects.all()

キャプチャ

jupyter notebook

DJANGO_ALLOW_ASYNC_UNSAFE について

  • コード上にコメント入れているけど、 DJANGO_ALLOW_ASYNC_UNSAFE を有効にしています
  • あくまでも開発時に少し試したいという時のコードなので問題はないはず
  • 詳細はドキュメント参照
  • 「はじめに」にも書いたけど前職のときのスニペットはこれは設定していなかった気がする
  • 設定しない場合には以下のエラーが発生する(ドキュメントの通り)
    • SynchronousOnlyOperation

ログ全文はこちらクリック

---------------------------------------------------------------------------
SynchronousOnlyOperation                  Traceback (most recent call last)
Cell In[1], line 12
      8 django.setup()
     10 from api.modules.user.models import User
---> 12 User.objects.filter(email="a@example.com").exists()

File /usr/local/lib/python3.9/site-packages/django/db/models/query.py:808, in QuerySet.exists(self)
    806 def exists(self):
    807     if self._result_cache is None:
--> 808         return self.query.has_results(using=self.db)
    809     return bool(self._result_cache)

File /usr/local/lib/python3.9/site-packages/django/db/models/sql/query.py:550, in Query.has_results(self, using)
    548 q = self.exists(using)
    549 compiler = q.get_compiler(using=using)
--> 550 return compiler.has_results()

File /usr/local/lib/python3.9/site-packages/django/db/models/sql/compiler.py:1145, in SQLCompiler.has_results(self)
   1140 def has_results(self):
   1141     """
   1142     Backends (e.g. NoSQL) can override this in order to use optimized
   1143     versions of "query has any results."
   1144     """
-> 1145     return bool(self.execute_sql(SINGLE))

File /usr/local/lib/python3.9/site-packages/django/db/models/sql/compiler.py:1173, in SQLCompiler.execute_sql(self, result_type, chunked_fetch, chunk_size)
   1171     cursor = self.connection.chunked_cursor()
   1172 else:
-> 1173     cursor = self.connection.cursor()
   1174 try:
   1175     cursor.execute(sql, params)

File /usr/local/lib/python3.9/site-packages/django/utils/asyncio.py:24, in async_unsafe.<locals>.decorator.<locals>.inner(*args, **kwargs)
     22     else:
     23         if event_loop.is_running():
---> 24             raise SynchronousOnlyOperation(message)
     25 # Pass onwards.
     26 return func(*args, **kwargs)

SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async.

sync_to_async を使うと DJANGO_ALLOW_ASYNC_UNSAFE が不要

import os
import django  # type: ignore
from asgiref.sync import sync_to_async  # type: ignore

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "api.settings")

django.setup()

from api.modules.user.models import User


@sync_to_async
def check_user_exists():
    return User.objects.filter(email="a@example.com").exists()
await check_user_exists()

上記のように sync_to_async を使用することで同期を正しくとって安全に動かすことも可能。っというかこちらがあるべきの実装になる。が、繰り返しになるが今回は開発時など一時的に使用することを目的とするため、こちらはあまり使うことはないかなと思った次第です。

docs.python.org

docs.djangoproject.com