nikkie-ftnextの日記

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

Django 4.1から環境変数PATHにblackが見つかれば、startprojectやstartappで作ったファイルがフォーマットされるようになってる〜〜!!!

はじめに

またね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.managementexecute_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

親クラスTemplateCommandhandleメソッド

startproject.pyのCommandクラスはdjango.core.management.templates.TemplateCommandを継承したクラスです2
handleメソッドの実装では、親クラスのhandleメソッドを呼び出します。

django.core.management.templates.TemplateCommandhandleメソッドの実装を見ていきましょう。
https://github.com/django/django/blob/4.2.5/django/core/management/templates.py#L86-L232
長いですが、今回の関心「フォーマット」に関連するように思うのは2箇所です

  1. find_formatters関数呼び出し
  2. 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

終わりに

Djangoの開発中にstartprojectコマンドでできるファイルに使われているのが、いつものシングルクォートではなくダブルクォートという点を深ぼったところ、blackでフォーマットされるようになっていることを知りました。
Django 4.1からPATHにblackが見つかれば、startprojectやstartappで作ったファイルがフォーマットされます!

手元ではDjango 4.2.4と4.2.5の違いがあったので、「このバグフィックスでフォーマットをサポートした!?!?」と気になったのですが、4.1からサポートしていてフォーマットが働くかはPATHに見つかるかどうかという条件で決まっていたのでした〜


  1. プロジェクトを作成しよう! · HonKit
  2. startapp.pyのCommandクラスも同様です
  3. 関数のデフォルト値にウォルラス(セイウチ)を使っているのが非常に興味深いです(宿題事項)