python のパッケージ化について

今回 Azure Automation アカウント で python プログラムを実行するため、久しぶりに python に触れた。 その際、パッケージ化した時の挙動が良く分からず苦戦したのでメモしておく。

一から Automation 上でコードを書くのではなく、既にある モジュールを Automation で動作させる。 このプログラム構成を以下とする。 ポイントは、config ディレクトリの位置で、src ディレクトリ(モジュール)配下ではなく、 別のディレクトリに存在するパターンだ。

もともとのモジュール構成

C:.
│ setup.py
│
├─config
│    hoge.yml
└─src
    └─ saitama
          hoge.py
          __init__.py

既存のモジュールをパッケージ化する

setup.py の内容は以下である

from setuptools import setup

setup(
    name='saitama',
    version='1.0.0',
    include_package_data=True,
    packages=["saitama"],
    package_dir={'saitama': 'src/saitama'},
    # package_data={'saitama': ['config/hoge.yml']},
    # data_files=[('config', ['config/hoge.yml'])],
)

配布パッケージを作成する(whl 形式)

python setup.py bdist_wheel

配布パッケージを作成する(tar.gz や zip 形式)

python setup.py sdist --formats=gztar

なお、Automation で python パッケージをインポートできるのは、"whl" か "tar.gz" のみである

上記のまま実行すると、パッケージファイルには、config ディレクトリが含まれないが、 コメントアウトしている package_data や data_files を設定すると含めることができる。

package_data と data_files

これが分かりにくかった。 package_data をつけると、パッケージ内に存在するファイルを配布パッケージの中に含ませることができる。 注意点として、そもそもパッケージ内にファイルが存在する前提で、 src/saitama/config のように配置された状態でパッケージしなければならない。 外部のパスを指定しようとしても受け付けないので、モジュール外にある config ディレクトリは指定できない。

なお、モジュール内に存在する場合は指定できるが以下のように

{'saitama': ['config/hoge.yml']} 

としても

# 空文字
{'': ['config/hoge.yml']}
# パッケージに存在しないようなキー
{'chiba': ['config/hoge.yml']}

でもよい。 オブジェクトのキーに関係なく、そのパッケージ内に指定したパスでファイルがあればコピーされる。 動作としては、どのパッケージ内のファイルを含むかわかりやすくキー付けしているレベル。

次に、data_files であるが、これはパッケージ内でなく、パッケージ外にあるファイルも指定できる。 なので、最初の構成にあるような位置にある config ディレクトリを配布パッケージに含めることができる。 そして、package_data と同様、第一引数は重要ではない。重要ではないというか少し後で説明する。 data_files は配列にタプルで指定するが、タプルの第 2 引数であるパスはプロジェクトルートから実際に存在するパスでなければならない。

# 存在しない
['config/aho.yml']
# ルートからの相対パスではない
['hoge.yml']

これが正しくないとファイルが配布パッケージに含まれない。 また、package_data と同様に(正確には少し異なる)、以下でも動作する

data_files=[('', ['config/hoge.yml'])],

公式ドキュメントのここで説明されている。

2. setup スクリプトを書く — Python 3.11.3 ドキュメント

シーケンス中のそれぞれの (directory, files) ペアは、インストール先ディレクトリとそこにインストールするファイルのリストを指定します。

files リスト中の各ファイル名はパッケージのソースディストリビューションの最上位にある setup.py スクリプトからの相対パスとして解釈されます。データファイルがインストールされるディレクトリは指定できますが、データファイルの名前を変更することはできないことに注意してください。

directory は相対パスである必要があり、インストールプレフィックス (システムインストールの場合は Python の sys.prefix; ユーザーインストールの場合は site.USER_BASE) からの相対パスと解釈されます。 Distutils は directory に絶対パスを指定することができますが、この方法は wheel パッケージング形式と互換性がないため推奨されません。 files のディレクトリ情報はインストール先の決定には使われません; ファイル名だけが使われます。

data_files オプションは、ターゲットディレクトリを指定せずに、単にファイルの列を指定できます。しかし、このやり方は推奨されておらず、指定すると install コマンドが警告を出力します。ターゲットディレクトリにデータファイルを直接インストールしたいなら、ディレクトリ名として空文字列を指定してください。

配布パッケージ化した時は含まれていたのにインストールすると含まれない

配布パッケージを作るだけなら、data_files の設定が正しければファイルは追加される。 しかし、以下のようにシステムにインストールしようとしたときにファイルが追加されない!

py -m pip install dist\saitama-1.0.0-py3-none-any.whl

data_files のポイント 2 つめは、第一引数は sys.prefix からの相対パスであること。 その場所にインストールされる。配布パッケージ内ではない! 言ってしまえば、別モジュールの扱いになるのだ。

例. モジュールのインストール先

C:\Users\YAMADA\AppData\Local\Programs\Python\Python311\Lib\site-packages

例. data_files で指定したファイルのインストール先

# sys.prefix 配下
C:\Users\YAMADA\AppData\Local\Programs\Python\Python311

そして、Azure Automation で python モジュールをインポートした時も同様と考えられ、一番最初の構成だと ファイルが参照できない。

python モジュールのインポートに成功すると以下のように参照できる

# インポートできる
import saitama

# ファイルを参照する
d = os.path.dirname(sys.modules["saitama"].__file__)
data = open(os.path.join(d, "config\hoge.yml"), 'rb').read()

最終的に採用した構成

config ディレクトリは、src 配下に移動した。

C:.
│ setup.py
└─src
    └─ saitama
        │  hoge.py
        │  __init__.py
        │
        └─config
              hoge.yml

setup.py

from setuptools import setup

setup(
    name='saitama',
    version='1.0.0',
    include_package_data=True,
    packages=["saitama"],
    package_dir={"saitama": "src/saitama"},
    package_data={'': ['config/hoge.yml']},
    # data_files=[('config', ['config/hoge.yml'])],
)

配布パッケージにする

python setup.py sdist --formats=gztar

Azure Automation の 共有リソース -> Python パッケージ から追加する

追加できたら、Runbook でテストコードを実行する

#!/usr/bin/env python3
import os
import pkgutil
import sys

import saitama

d = os.path.dirname(sys.modules["saitama"].__file__)
data = open(os.path.join(d, "config\hoge.yml"), 'rb').read()
print(data)

# こちらでも参照はできる
data = pkgutil.get_data('saitama', 'config/hoge.yml')
print(data)

sys.exit(0)

テスト

Djangoお試し

WindowsDjangoプロジェクト作成しようとしたらエラーになった。

C:\Users\Aho\app> pipenv run django-admin startproject hogeapp
Exception in thread Thread-2 (_readerthread):
Traceback (most recent call last):
  File "C:\Users\Aho\AppData\Local\Programs\Python\Python311\Lib\threading.py", line 1038, in _bootstrap_inner
    self.run()
  File "C:\Users\Aho\AppData\Local\Programs\Python\Python311\Lib\threading.py", line 975, in run
    self._target(*self._args, **self._kwargs)
  File "C:\Users\Aho\AppData\Local\Programs\Python\Python311\Lib\subprocess.py", line 1550, in _readerthread
    buffer.append(fh.read())
                  ^^^^^^^^^
  File "<frozen codecs>", line 322, in decode
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x8f in position 0: invalid start byte
'django-admin' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。

同一フォルダに".env"ファイルを配置して以下のように記述する(環境変数を設定する)

PYTHONUTF8=1

実行できた。

C:\Users\Aho\app> pipenv run django-admin startproject hogeapp
Loading .env environment variables...

思い出なんてただのゴミだ

久しぶりにgithubを利用してみたところ、pushができなかった(Git コマンドが通らなかった)

$ git push
Logon failed, use ctrl+c to cancel basic credential prompt.
Logon failed, use ctrl+c to cancel basic credential prompt.
remote: Support for password authentication was removed on August 13, 2021. Please use a personal access token instead.
remote: Please see https://github.blog/2020-12-15-token-authentication-requirements-for-git-operations/ for more information.

エラーメッセージのページを開くと2021/08/13以降パスワード認証ができなくなった説明が書かれている。

https://github.blog/2020-12-15-token-authentication-requirements-for-git-operations/

Beginning August 13, 2021, we will no longer accept account passwords when authenticating Git operations on GitHub.com.

で、利用したいときはpersonal access token使ってね!とのこと。

For developers, if you are using a password to authenticate Git operations with GitHub.com today, you must begin using a personal access token over HTTPS (recommended) or SSH key by August 13, 2021, to avoid disruption. 

personal access tokenの作り方は以下に書かれている。

https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token

Settings → Developer settings → Personal access tokens → Generate new token

作成時に入力する項目は3つ。メモ、有効期限、許可するアクション。まあこれを設定するぐらいのことを考えている人には説明不要でしょう。

f:id:radio-keios:20220327033042p:plain

作成されたpersonal access tokenはパスワードとして使用するとコマンド使えます!

 

補足

personal access tokenは単語の頭文字をとって別名PAT。

デフォルト設定?では作成したpersonal access tokenで認証を一度行うとWindowsであれば資格情報に保存されて次にGitコマンドを実行するときは入力が不要。(personal access tokenを消したければgithub.comで無効にして、Windowsの資格情報マネージャで削除すればよい。この認証情報の管理状況については、git config credential.helperで確認できる。

 

Apacheの設定で押さえておくべき箇所

2.4系の話である。
いまだにApacheを利用する機会はなくならず、久々に利用したのでメモを残す。
以下、Directoryの設定に関するものである。

OptionsとAllowOverrideとRequireの設定は必ず理解しておく。

Options

Optionsはディレクトリに対する基本的な設定。
http://httpd.apache.org/docs/2.4/mod/core.html#options
デフォルトがALL。FollowSymLinks以外、利用する機会はほぼないと言える。
ExecCGI(今時CGIなど使わない)、
Includes(そもそもPHPなどでサイトを構築しているし、Server Side Includesを使う機会がない。)
Indexes(ファイル一覧を見せる必要はない)

AllowOverride

AllowOverrideは.htaccessに対する設定。
WordPressを利用する場合は関係してくる。
設定しなければデフォルトがALLで、これ以外を利用するケースはあまりないと思う。
つまりわざわざ書く必要はないが、以下のようにデフォルトで設定されていることから、
.htaccessを使用する場合は明示的に許可するように変更しなければならない。

<Directory />
    AllowOverride None
</Directory>
Require

Requireは認証に関する設定で、一部のユーザに対してのみ公開したいときに利用する。
AllowOverride同様に禁止されているので、設定を変更しなければアクセスができない。

<Directory />
    Require all denied
</Directory>

ローカル開発時など、特に制限する必要がなければ、Require all grantedでよい。


さて、以上を踏まえて以下に簡単な例を示す。
基本的にはVirtualHostを利用することが多いと思うが、以下のような記述を行うことになるだろう。

<VirtualHost *:80>
    DocumentRoot "/home/a/www/public"
    ServerName a.local
    ErrorLog "logs/a-error.log"
    CustomLog "logs/a-access.log" common
    <Directory "/home/a/www/public">
        Options FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

コンテナの保存

以前、save&loadが使えそうだということが頭にあったので実際に試そうとしたが
よくよく考えるとsaveコマンドはイメージを保存するもの。
コンテナを保存する場合はcommitをやはり利用することになるかねえ。


そうそう、data only containerではデータ用のコンテナはrunせずにcreateする。

$ sudo docker create -v /dbdata --name dbdata training/postgres
$ sudo docker run -d --volumes-from dbdata --name db1 training/postgres

https://docs.docker.com/userguide/dockervolumes/


Docker使ってると、次から次に新しいアイデアが生まれてきてちょこちょこ変更が発生して面倒だ。
鉄板パターンを作っておかないとブレブレになってしまう。

export/import

稼働中のコンテナをイメージとして保存したいときに以下の方法だと便利だが、
メタ情報が保存されない。(CMDが実行されなかった)

$ docker export コンテナ名|docker import - イメージ名

以下によれば、save&loadでうまくいくようだ。
https://github.com/docker/docker/issues/8334

Dockerイメージのoraclelinuxを利用するときもimportだとダメで
loadだとうまくできたのでこちらを基本使うようにした方がよさそう。

ものぐさ

JJUG CCC 2014 Springの講演資料まだ確認してないので、ブックマークしようと思ったらChromeJavaのカテゴリがない。
Googleアカウントいくつも使ってるし、会社で登録したものは同期されないからなあ。


2月からベンチャー企業でお世話になっているが、来てよかったと思う。
ただの業務委託ではあるけれども以前の会社だといろいろと制限があってほとんど何もできなかったし、新しい技術を学ぶ機会もなかった。


とはいえ、仕事自体は正直つまらないと感じるようになってしまった。
いま手をつけているプロジェクトがきっちりと終わればまた新しい旅に出るのだろう。