はじめに
またね1幕、こんにちは2幕。nikkieです。
Djangoで開発していて気づいた小ネタです。
Django Girls Tutorial1で習ったdjango-admin startproject
コマンドで私はプロジェクトを作り始めるのですが、できあがるファイルがフォーマットされるようになっていることに気づきました
目次
環境変数PATHにblackが見つかればフォーマットされる
2つの仮想環境を用意して実験します。
blackをインストールしない仮想環境の場合
pip install django
して以下がインストールされました
asgiref==3.7.2 Django==4.2.5 sqlparse==0.4.4
blackはありません。
% which black black not found
django-admin startproject no_black_project
します。
no_black_project/no_black_project/settings.py
を確認すると、フォーマットはされていません(親の顔より見たシングルクォートのsettings.pyです)
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ]
blackもインストールした仮想環境の場合
pip install django black
して以下がインストールされました
asgiref==3.7.2 black==23.7.0 click==8.1.7 Django==4.2.5 mypy-extensions==1.0.0 packaging==23.1 pathspec==0.11.2 platformdirs==3.10.0 sqlparse==0.4.4
% which black /.../black_venv/bin/black
django-admin startproject black_project
して、black_project/black_project/settings.py
をエディタで開くと...
INSTALLED_APPS = [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", ]
ダブルクォート!
フォーマットされてる〜〜!!!!
PATHにblackを見つけたらフォーマットする実装
どう実現しているんだろう?と実装を覗きました。
django-admin
コマンド
まずdjango-admin
コマンドの実体はdjango.core.management
のexecute_from_command_line
関数です。
https://github.com/django/django/blob/4.2.5/setup.cfg#L49
execute_from_command_line
https://github.com/django/django/blob/4.2.5/django/core/management/__init__.py#L439-L442
この関数はDjangoでの開発中に何度も叩くpython manage.py
でも呼ばれています。
昨年のDjangoCongress JPに深ぼった発表がありました。
execute_from_command_line
まわりの実装はこちらの発表に譲り、ここではstartproject
コマンドを見ていきます。
https://github.com/django/django/blob/4.2.5/django/core/management/commands/startproject.py
親クラスTemplateCommand
のhandle
メソッド
startproject.pyのCommandクラスはdjango.core.management.templates.TemplateCommand
を継承したクラスです2。
handleメソッドの実装では、親クラスのhandleメソッドを呼び出します。
django.core.management.templates.TemplateCommand
のhandle
メソッドの実装を見ていきましょう。
https://github.com/django/django/blob/4.2.5/django/core/management/templates.py#L86-L232
長いですが、今回の関心「フォーマット」に関連するように思うのは2箇所です
find_formatters
関数呼び出し- 1の返り値も渡して
run_formatters
関数呼び出し
どちらもdjango.core.management.utils
にある関数です。
ちなみに、startproject
したときのテンプレートのファイル一式はdjango.conf
下に見つかりました。
https://github.com/django/django/tree/4.2.5/django/conf/project_template
settings.py-tpl
を覗くと、シングルクォートが使われています。
django.core.management.utils
find_formatters
関数は、shutil.which
を使ってblack
コマンドがPATHにあるか調べます。
https://github.com/django/django/blob/4.2.5/django/core/management/utils.py#L160-L161
shutil.which
https://docs.python.org/ja/3/library/shutil.html#shutil.which
cmd を実行しようとした時に実行される実行ファイルのパスを返します。 cmd を呼び出せない場合は None を返します。
run_formatters
関数は、subprocess.run
を使ってblack_path
引数を実行(すなわちファイルをフォーマット)します!3
https://github.com/django/django/blob/4.2.5/django/core/management/utils.py#L164-L175
以上により、PATHにblackが見つかれば、startprojectで作ったファイルがフォーマットされるわけですね!
この変更にまつわるドキュメント類
django-admin startproject
した結果がいつもと違う!と興味を持ち実装を見ましたが、ドキュメントでもアナウンスされていました。
Django 4.1のRelease Notes
https://docs.djangoproject.com/en/4.2/releases/4.1/#management-commands
Python files created by startproject, startapp, optimizemigration, makemigrations, and squashmigrations are now formatted using the black command if it is present on your PATH.
django-adminコマンドのページにblackに関する項目ができています
https://docs.djangoproject.com/ja/4.2/ref/django-admin/#black-formatting
- プルリクエスト https://github.com/django/django/pull/15412
- チケット https://code.djangoproject.com/ticket/33476
- Django Enhancement Proposals 0008: Formatting Code with Black
終わりに
Djangoの開発中にstartproject
コマンドでできるファイルに使われているのが、いつものシングルクォートではなくダブルクォートという点を深ぼったところ、blackでフォーマットされるようになっていることを知りました。
Django 4.1からPATHにblackが見つかれば、startprojectやstartappで作ったファイルがフォーマットされます!
手元ではDjango 4.2.4と4.2.5の違いがあったので、「このバグフィックスでフォーマットをサポートした!?!?」と気になったのですが、4.1からサポートしていてフォーマットが働くかはPATHに見つかるかどうかという条件で決まっていたのでした〜
- プロジェクトを作成しよう! · HonKit↩
- startapp.pyのCommandクラスも同様です↩
- 関数のデフォルト値にウォルラス(セイウチ)を使っているのが非常に興味深いです(宿題事項)↩