0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Python】PDM Scriptsでタスクランナーを作成する

Posted at

前回の記事でPDMのスクリプト機能(PDM Scripts)についてまとめましたので、これをタスクランナーとして活用するためのユースケースを確認しようと思います。

タスクランナーとして活用

今回は以下のCSV入出力をチェーンしていくプログラムスクリプトを用意します。

テストソース

  1. 行方向に数値が記述されたCSV(CSV-0)を横方向に変換したCSV(CSV-1)を出力するスクリプト

    ソースコード
    csv_conver.py
    import sys
    import pathlib
    import csv
    
    def Main(args):
        
        print(f'execute: {args[0]}')
        print(' args:', args)
        
        input_csv_path = pathlib.Path(args[1])
        print(' input file:', input_csv_path.resolve())
        
        output_csv_path = pathlib.Path(args[2])
        
        num_list = []
        with open(input_csv_path, 'r') as fs:
            reader = csv.reader(fs)
            for row in reader:
                num_list.append(row[0])
    
        with open(output_csv_path, 'w') as fs:
            writer = csv.writer(fs)
            writer.writerow(num_list)
            
        print(' output file:', output_csv_path.resolve())
    
        return
    
    if __name__ == '__main__':
        Main(sys.argv)
    
  2. CSV-1の数値にそれぞれ100を加えた値の行を追記したCSV(CSV-2)を出力するスクリプト

    ソースコード
    csv_adder.py
    import sys
    import pathlib
    import csv
    
    def Main(args):
        
        print(f'execute: {args[0]}')
        print(' args:', args)
        
        input_csv_path = pathlib.Path(args[1])
        print(' input file:', input_csv_path.resolve())
        
        output_csv_path = pathlib.Path(args[2])
        
        num_list = []
        with open(input_csv_path, 'r') as fs:
            reader = csv.reader(fs)
            for row in reader:
                num_list = [int(r) for r in row]  
                
        added_list = [n + 100 for n in num_list]
    
        with open(output_csv_path, 'w') as fs:
            writer = csv.writer(fs)
            writer.writerow(num_list)
            writer.writerow(added_list)
            
        print(' output file:', output_csv_path.resolve())
    
        return
    
    if __name__ == '__main__':
        Main(sys.argv)
    
  3. CSV-2の全ての数値に環境変数「MULTI_NUM」で定められた数値を乗算した結果のCSV(CSV-3)を出力するスクリプト

    ソースコード
    csv_multiplier.py
    import sys
    import pathlib
    import csv
    import os
    
    def Main(args):
        
        print(f'execute: {args[0]}')
        print(' args:', args)
        
        input_csv_path = pathlib.Path(args[1])
        print(' input file:', input_csv_path.resolve())
        
        output_csv_path = pathlib.Path(args[2])
        
        multi_num = int(os.environ['MULTI_NUM'])
        print(' multi num:', multi_num)
        
        ary = []
        with open(input_csv_path, 'r') as fs:
            reader = csv.reader(fs)
            updated_list = []
            for row in reader:
                ary.append([int(r) * multi_num for r in row])
    
        with open(output_csv_path, 'w') as fs:
            writer = csv.writer(fs)
            writer.writerows(ary)
            
        print(' output file:', output_csv_path.resolve())
    
        return
    
    if __name__ == '__main__':
        Main(sys.argv)
    

これらのコードを順番(csv_converter.py -> csv_adder.py -> csv_multiplier.py)に動かすための実行可能な定義を行いたいと思います。

もちろんshbatで事足りるかもしれませんが、これらで記述する場合は、定義の中に仮想環境のアクティベートを含めてる必要があるかもしれません。なによりOSごとに実行例を書くのはメンテナンス性が落ちます。これをPDM Scriptsで実現してみましょう。

pyproject.tomlの定義

以上のソースコードを順番に実行するためのPDM ScriptsPyproject.tomltool.pdm.scriptsセクションにて定義していきます。

csv_converter.pyを実行する一番手のスクリプト「converter」には、実行前に一時ファイル置き場を作成するプレスクリプトを定義しておきます。プレスクリプトは対象のスクリプト名に接頭辞「pre」をつけた名前のスクリプトを記述することで認識・定義されます。

pyproject.tomlの作成はpdm initで行いました。今回もアプリケーションとして初期化しています。

詳しくは公式ドキュメントや前回記事を参照してください。


[project]
name = ""
version = ""
description = ""
authors = [
    {name = "quag-cactus", email = "quag.cactus@gmail.com"},
]
dependencies = []
requires-python = ">=3.9"
license = {text = "MIT"}

[tool.pdm.scripts]

# CSV-0 -> CSV-1
pre_converter.shell = "rm -r ./tmp/ && mkdir -p ./tmp/"
converter.shell = "python csv_converter.py {args} ./tmp/csv-1.csv"

# CSV-1 -> CSV-2
adder.shell = "python csv_adder.py ./tmp/csv-1.csv ./tmp/csv-2.csv"

# CSV-2 -> CSV3
multiplier.shell = "python csv_multiplier.py ./tmp/csv-2.csv csv-3.csv"
multiplier.env = {MULTI_NUM = "5"}

# タスクランナー
runner-all = {composite = [
    "converter {args}",
    "adder",
    "multiplier"
]}

プロジェクト全体のディレクトリ構成は以下のようになります。

# タスクランナー実行後のCSVファイルも含まれています。
$ tree
├── csv-0.csv
├── csv_adder.py
├── csv_converter.py
├── csv_multiplier.py
├── pyproject.toml
└── tmp
    ├── csv-1.csv
    └── csv-2.csv

ランナー実行

以下のようなCSVを入力として、pdm runコマンドでタスクランナーを実行します。

csv-0.csv
1
2
3
4
5
# タスクランナー実行
$ pdm run runner-all csv-0.csv         
         
execute: csv_converter.py
 args: ['csv_converter.py', 'csv-0.csv', './tmp/csv-1.csv']
 input file: /home/ec2-user/environment/pdm-scripts-tester/csv-0.csv
 output file: /home/ec2-user/environment/pdm-scripts-tester/tmp/csv-1.csv
execute: csv_adder.py
 args: ['csv_adder.py', './tmp/csv-1.csv', './tmp/csv-2.csv']
 input file: /home/ec2-user/environment/pdm-scripts-tester/tmp/csv-1.csv
 output file: /home/ec2-user/environment/pdm-scripts-tester/tmp/csv-2.csv
execute: csv_multiplier.py
 args: ['csv_multiplier.py', './tmp/csv-2.csv', 'csv-3.csv']
 input file: /home/ec2-user/environment/pdm-scripts-tester/tmp/csv-2.csv
 multi num: 5
 output file: /home/ec2-user/environment/pdm-scripts-tester/csv-3.csv
# 出力されたCSVを確認
$ cat csv-3.csv

5,10,15,20,25
505,510,515,520,525

最初の引数の指定のみで、開発者が意図したフローでCSVファイルのIOが実行されました。また、今回はプレスクリプトによって中間ファイルもtmp/に保存してあります。

multiplierスクリプトで設定した環境変数も、このスクリプトのスコープのみ有効なので、シェルセッションを汚さない点でもポイント高いですね。

まとめ

PDM Scriptscomposite機能を活用して、タスクランナーを作成してみました。

ちょっとしたプロジェクトで、開発者の意図した使い方を伝える点では効果的と思います。素早く試すことのできる簡単なプログラムと、堅牢で実用性のあるコマンドライン引数の構成は、しばしば相反関係となります。
可読性や拡張性の面でもPythonスクリプト本体が受ける引数を柔軟に保ちつつ、PDMスクリプトで実用上の使い方を示すことができます。

ただ、この機能は開発コマンドのエイリアスを念頭に実装されていると思われます。ガチガチのタスクランナーを組むことは想定されていないと思います。

個人的にはPDMの機能で実現できるタスクランナーをある種の基準として、もし実現できない場合、プロジェクトに比して起動引数や設定が複雑すぎないかどうか、確認することにしています。
複雑なタスクランナーが実際に必要であれば、Invokeなどを使いましょう。

もちろん、PDM「開発環境の管理ツール」であり、本番環境で使うものではありません。とはいえ、Pythonプログラムの実行をとりまき、避けては通れない仮想環境や依存パッケージ、さらにはCLIコマンドを一元的に管理することは、本番環境へのデプロイにも資することは間違いないと思います。

お勧めしているのかしていないのか、良く分からなくなってしまいましたが、開発者の想定・意向を即座に実行可能な形で伝えることは重要です。そのための一手段していかがでしょうか。

前回記事

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?