nikkie-ftnextの日記

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

Pythonでリソースファイルと一緒にパッケージをインストールする指定は3つある 〜うち、include_package_dataとpackage_dataについて〜

はじめに

サァン!!! nikkieです。

Pythonのパッケージに.py以外のファイルを含める設定について先日アウトプットしました。

知ったことを自作パッケージに適用しようとしたところ、ほかの設定方法もあることが分かりました。
深まった理解を元にアウトプットします。

※タイトルのリソースファイルは、setuptoolsのドキュメントではデータファイルと理解しています。
用語の揺れがありますが、「リソースファイル」と「データファイル」、同じものを指すとして使っていきます。

目次

結論

指定方法3つは、以下に端的なまとめがあります

  • include_package_data
  • package_data
  • exclude_package_data
    • 含め ない データファイルのパターンを辞書で指定する
    • 利用例:include_package_data=Trueとセットで使う
    • package_dataと一緒に使った場合はexclude_package_dataが優先される

この記事ではinclude_package_datapackage_dataを扱います

経緯:include_package_data=TrueとMANIFEST.inを使わなくても、パッケージングできてんじゃん!(package_data)

include_package_data=TrueとMANIFEST.inが必要と学んだnikkie氏。
過去に書いていたsetup.pyにも適用しようと思い立ちます。
https://github.com/ftnext/bring-script-more-users/blob/bringscript0.1.0/bringscript/setup.py

このsetup.pyはPyCon APAC 2020のワークショップの自作ヘルパーライブラリのものです1
テンプレートエンジンを使ってスキャフォールディングするライブラリを作りました。
テンプレートファイルは.pyではないので、データファイルです。

ワークショップ中にテンプレートファイルが見つからないエラーが報告されています。
「これってsetup.pyでデータファイルの設定ができていなかったからなのかな」と小さな心残りとなりました。
このたび自信を持ってinclude_package_data=TrueとMANIFEST.inで設定できそうなので、このsetup.pyに立ち戻りました。

すると、データファイルがインストールされない事象は手元では再現しないんですよね(macOSやDockerのpythonイメージを動かして検証)。
MANIFEST.inは置けてないですし、include_package_data=Trueも指定していない のですが、以下の設定がありました。

setup(
    ...,
    package_data={"bringscript": ["templates/*/*.jinja"]},
)

「このpackage_dataって何よ? include_package_data=Trueと何が違うのよ?」というのが今回の記事の出発点です。

setuptoolsのUser guide 「Data Files Support」

パッケージングでデータファイルをサポートする方法3つが紹介されるページです。

include_package_data

  1. setup関数でinclude_package_data=Trueを指定
  2. MANIFEST.inにデータファイルを指定する
    • パージョン管理システムプラグインを代わりに使ってもよい(nikkie素振り材料)

include_package_data=Trueが意味するのは、パッケージディレクトリにある.pyでないすべてのファイルをデータファイルとみなすということのようです2
だから、TrueまたはFalseというフラグなんですねー(みなすか、みなさないか)。
Trueでみなす指定をした上で、具体的なデータファイルはMANIFEST.in(やバージョン管理プラグイン)で指定します

package_data

include_package_dataよりもきめ細やかにデータファイルの制御をするのがpackage_data3
ここには辞書を指定します。

例:{"mypkg": ["*.txt"]}

  • キー:パッケージ名
    • 空文字列は全てのパッケージを意味する(複数のパッケージがあるときの例より)
  • 値:データファイルのパターン(文字列)のリスト

package_dataを使って指定したデータファイルはMANIFEST.inを必要としませんバージョン管理システムプラグインも必要としないそうです)。

これが、PyCon APAC 2020 自作ライブラリのsetup.pyの指定だったというわけですね!(謎は全て解けた!)

設定例

パッケージ構造

project_root_directory
├── setup.py
├── MANIFEST.in  # include_package_dataで必要、package_dataでは不要
└── mypkg
    ├── __init__.py
    ├── data1.rst
    ├── data2.rst
    ├── data1.txt
    └── data2.txt

include_package_data

※先日のエントリと同様の例です

setup.py

from setuptools import setup, find_packages
setup(
    # ...,
    packages=find_packages(),
    include_package_data=True
)

MANIFEST.in

include mypkg/*.txt
include mypkg/*.rst

package_data

setup.py

from setuptools import setup, find_packages
setup(
    # ...,
    packages=find_packages(),
    package_data={"mypkg": ["*.txt", "*.rst"]}
)

(MANIFEST.inは不要です)

終わりに

パッケージのリソースファイルまわりのアウトプットでした。
設定に使う組み合わせ例です:

  • setupのinclude_package_data=Trueと、MANIFEST.inファイル
  • setupのpackage_dataのみ

ここで取り上げた2020年のヘルパーライブラリでは、ぶっちゃけよく分からずにpackage_dataと書いていました(きっとStackOverflowか何かから拾ってきたのです)。
include_package_data=TrueとMANIFEST.inを知って立ち返ったところ疑問にぶつかり、ドキュメントに当たる中でpackage_dataでも指定できることを知りました。
include_package_dataはフラグなので具体的なファイルの指定が必要で、package_dataは具体的なファイルの指定込みと理解し、しっくり来ています🙌


  1. ワークショップのリポジトリです
  2. package_dataの項より。「By default, include_package_data considers all non .py files found inside the package directory (src/mypkg in this case) as data files,
  3. package_dataの項より。「If you want finer-grained control over what files are included, then you can also use the package_data keyword.