setup.pyに関する防備録



1.setup.pyにまとめる利点など

pythonスクリプトをsetup.pyの配布物として作成すると、配布物でのインストール時に標準モジュール以外の 外部モジュールも自動的にインストールされるので便利。OSなどの環境に依存せず、pythonの環境さえあれば再現 できるので管理にも便利。
pyinstallerなどで実行形式にまとめるとpythonに無縁の人には親切な反面、実行環境別に作成する必要があるので保守が面倒。

2.pythonの仮想環境

setup.pyの作成に必須ではないが、pythonの素の環境で作成する方が動作確認がしやすい。

(1)pythonの仮想環境の構築と運用
・前提
  <dirname>:作業ディレクトリ
  .venv:<dirname>内に作成される仮想環境用ディレクトリ名(.venv以外でもよい)
  <prompt>:仮想環境に入ったときのプロンプト(--prompt以降は任意)
・仮想環境の構築
  cd <dirname>
  python3 -m venv .venv --prompt <prompt>
・仮想環境への投入
  cd <dirname>
  source .venv/bin/activate
・仮想環境からの離脱
  deactivate
・仮想環境の削除
  .venvディレクトリを削除
・仮想環境の初期化
  python -m venv --clear .venv --prompt <prompt>
    一旦削除して再構築しているだけなので必要なら--prompt情報も入れる。

(2)作成したパッケージの仮想環境へのインストール(仮想環境モードで)
  pip install .
  -> カレントディレクトリのsetup.pyの内容で仮想環境にインストールされる
      setup.pyの確認をする場合はこちらの方法で。
  pip install -e .
  -> パッケージ用のディレクトリをそのままpythonに認識させるだけで仮想環境には
      インストールされない。そのため、setup.pyの記述に間違いがあってもわからない。

(3)インストールされたパッケージの所在の確認
  pip show <packagename>
  ※ただし、pip3 install -e . でインストールするとLocationに現在の
  プロジェクトフォルダのあるディレクトリが表示される。

(4)インストールされたパッケージの一覧
  pip list
  
(5)仮想環境に関する注意点
  異なるPC間の仮想環境用ディレクトリやegg-infoディレクトリを混在させないこと。混在により
  仮想環境に異常が発生した場合は、仮想環境やegg-infoディレクトリを一旦削除して仮想環境を
  再構築すること。
3.setup.pyに関する基礎知識
(1)2つのパッケージングツール
  distutils:パッケージングに関する初期ツールだが、現在でも各種ツールのコアとして活用されている。python標準
  setuptools:distutilsの改良版。python非標準

(2)配布形式
  bdist …… Built Distribution: ビルド済み配布物
    (作成コマンド:python setup.py bdist)
  sdist …… Source Distribution: ソースコード配布物
    (作成コマンド:python setup.py sdist)
    python setup.py sdist --formats=gztar,zip等で複数の形式で出力できる。
    --format=に出力する形式を所定の文字列で列挙する。
    指定しない場合はプラットフォーム依存の形式になる。(linuxなら.tar.gz、windowsならzip)
  
   sdist形式の配布物からのインストール方法:
     sdistで出力されたファイル:<package>.tar.gz or <package>.zipとすると
      解凍せずそのまま、pip install <sdistで出力されたファイル>
   
(3)bdist形式の標準
  setuptoolsでは当初egg形式であったが現在はwheel形式になりダウンロードした配布物を
  そのままインストールすればよくなった。

(4)sdist形式の標準
  pyproject.toml:ビルドに必要な依存関係を定義(ビルドに必要な依存関係はビルド用の環境に
  インストールされるので現在の環境には影響しない。)
  ※pyproject.tomlがなければsetuptoolsでsetup.pyが利用される。

(5)配布ファイルの推奨
  sdist形式とwheel形式で配布すべき。
4.必要十分なsetup.pyの書き方

ただし、モジュールではなくアプリケーション配布の場合に限定。
パッケージディレクトリに__init__.pyが必要だとか、setup.pyのあるディレクトリに空でもいいからtestsディレクトリを配置するとか、 README.mdファイルを配置するとかという基本的なことがらは既知のこととする。

■パッケージ名とディレクトリ名の関係が紛らわしい。
  • モジュールをimportする場合に使われるのはディレクトリ名とモジュール名
  • パッケージ名とはpipで管理する際の識別名と考えた方がよい。
  • パッケージ名とパッケージディレクトリ名を同じにしてsetup.pyを作成した場合、
    import パッケージ.モジュール名のように見えるだけで、実態はimport パッケージディレクトリ名.モジュール名となっている。
  • pip install でインストールする際、指定されたファイルやディレクトリが見つからなくてもエラーにならず、実行時にエラーとなる。
■サンプルのsetup.pyのディレクトリ構成
<projectdir>
	├ <sampleapp>     パッケージディレクトリ
	│ ├ __init__.py
	│ ├ sampleapp_main.py  メインスクリプト
	│ ├ suport.py          メインスクリプトからimportされるスクリプト
	│ ├ sampleimage.jpg    メインスクリプトで利用する画像ファイル
	│ └ sampledata.dat     メインスクリプトで利用するデータファイル
	├ <tests>         空ディレクトリ
	├ README.md
	└ setup.py  (内容は以下のとおり)
以下、setup.pyの中身(赤字)とメモ
============================================ここから

import setuptools 

#README.mdを読み込んでsetup()の中の詳細な説明の内容に置き換えている。
with open("README.md", "r") as fh:
  long_description = fh.read()

setuptools.setup(
  #nameがパッケージ名になる。
  #パッケージ名にアンダースコアは使わないこと。勝手にハイフンに置き換えられる。
  name="sampleapp",
  version="0.0.1",

  #install_requireで指定したモジュールが環境になければインストール時にダウンロードされる。
  #標準モジュールに含まれないモジュールを指定
  install_requires=["module-name",],   #module-nameはダミー  

  #entry_pointを指定するとあたかも単独コマンドのように使えるようになる。単独使用しない場合は不要。
  #この例では端末からsampleappとタイプして起動することを想定している。
  #[]の中の書式は、起動コマンド名=ディレクトリ名.スクリプト名:関数名
  #エントリーポイントの関数を実行する前にグローバル領域の処理は実行される。
  #エントリーポイントを関数以外にする方法は無いものか?
  entry_points={'console_scripts': ['sampleapp=sampleapp.sampleapp_main:main',],},

  #authorに作成者名を入れるらしい
  author="name_of_author",  #name_of_authorはダミー

  #emailを公開するなら‥
  author_email="sampleapp@fogefoge.com",  #このアドレスはダミー

  #descriptionに簡単な説明を書く
  description="サンプルアプリケーション",

  #詳しい説明はREADME.mdから読み込まれる。本来markdown書式で書くものらしい    
  long_description=long_description,
  long_description_content_type="text/markdown",

  #urlに公開するページがあれば書く この例では指定しない。
  #url="https://fogefoge...",  #このurlはダミー

  #packagesは.find_package()で自動取得される。自動取得しないなら個別に指定
  packages=setuptools.find_packages(),  

  #package_dirに階層下にパッケージディレクトリが入っているディレクトリを指定する。
  #パッケージディレクトリを指定するのではないので、この例では指定しない。
  #package_dir=[],

  #py_modules:パッケージディレクトリにあるスクリプトを列挙しているが、
  #ここに列挙せずともパッケージディレクトリにあるスクリプトはインストールされるようだ。
  #なお、インストールしたパッケージのスクリプトで同じ階層の他のスクリプトをインポートする場合
  # from . import <script> とする必要がある。
  #逆にインストールせずに利用する場合はこのように記述するとエラーになる。 
  #一般的に複数のスクリプトに分けて作成すると思われるがsetupにまとめる場合だけ
  #importの記述を変更する必要があるのはいただけない。setup.pyの書き方でクリアできないものか?
  py_modules=['sampleapp_main,suport',],

  #スクリプト以外のデータファイルをインストールする場合はパッケージディレクトリ名と
  #その中のデータファイル名を列挙する。
  #なお、そのデータファイルをスクリプトから利用する場合はディレクトリパスを付加する必要がある。
  package_data={'sampleapp':['sampleimage.jpg','sampledata.dat',]},
    
  #classifiers:よくわからないからこのままで
  classifiers=[
    "Programming Language :: Python :: 3",
    "License :: OSI Approved :: MIT License",
    "Operating System :: OS Independent",
  ],

  #pythonのバージョンを指定したいなら‥
  python_requires='>=3.6',)
================================================ここまで