python ConfigArgParse メモ ~まだ引数と設定ファイル別々に管理しているの？~

コマンドライン引数と設定ファイルのパラメータを別々に管理するのが面倒だったので両方まとめて管理できるパーサーを探したらよさそうなのがあったので調べたメモ。

ConfigArgParse
https://github.com/bw2/ConfigArgParse

## 使われてるのか？

あまりに使われていないとEOLしてしまいそうなので軽く調べる

```console
~$ apt-cache rdepends python3-configargparse
python3-configargparse
Reverse Depends:
  python3-certbot
  snakemake
  bdfproxy
  python3-azure-devtools
  prometheus-pgbouncer-exporter
  cloudprint
```
一番メジャーなのはLet's Encryptのクライアントである「cerbot」か、セキュリティな人々には「bdfproxy」もかな、まぁ大丈夫そうか。

## 対応フォーマット

対応しているフォーマットも確認する。

```
# how to specify a key-value pair (all of these are equivalent):
name value     # key is case sensitive: "Name" isn't "name"
name = value   # (.ini style)  (white space is ignored, so name = value same as name=value)
name: value    # (yaml style)
--name value   # (argparse style)
```
スペース区切り、.ini形式、yaml形式に標準で対応していることを確認。
できればjsonフォーマットもあれば嬉しかったが半径5m以内で一番使われているyaml風に対応しているのでよいこととする。

## インストール

試験環境はubuntu 19.04
最新版は2019年10月1日時点でupstreamでの最新は0.14.0(Jan 13,2019)だが運用的にセキュリティパッチなどを考慮したくないため上記ディストリビューションのパッケージを使用する。

```console
$ sudo apt install python3-configargparse
$ dpkg -l python3-configargparse
要望=(U)不明/(I)インストール/(R)削除/(P)完全削除/(H)保持
| 状態=(N)無/(I)インストール済/(C)設定/(U)展開/(F)設定失敗/(H)半インストール/(W)
|/ エラー?=(空欄)無/(R)要再インストール (状態,エラーの大文字=異常)
||/ 名前 バージョン アーキテクチ 説明
+++-======================-============-============-===========================
ii python3-configargparse 0.13.0-1 all replacement for argparse wi
lines 1-6/6 (END)
```

## テストコード

```py
#!/usr/bin/env python3

import configargparse

def parse_args():
    cp = configargparse.ArgumentParser(
        default_config_files=['./.arg_parse.yaml']
    )
    cp.add_argument(
        '-c', '--conf',
        dest='config',
        is_config_file=True,
        help='config file path',
    )
    cp.add_argument(
        '-f', '--flag',
        dest='flag',
        action="store_true",
        help='true or false flag'
    )
    cp.add_argument(
        '-s', '--str',
        type=str,
        required=True,
        dest='string',
        help='requirement string'
    )
    cp.add_argument(
        '-i', '--int',
        type=int,
        dest='intager',
        default=50,
        help='default intager',
    )
    cp.add_argument(
        '-a', '--array',
        dest='array',
        action='append',
        default=[],
        help='array parama',
    )
    return cp.parse_args()
              
def main():
    args = parse_args()
    print(args)

main()
```

設定ファイル(yaml形式例)

```console
$ cat .arg_parse.yaml
str: hogehoge
int: 3
array: [1, 12, 35, 40]
```

実行結果1
設定ファイルのみの状態

```console
$ ./test_conf.py
Namespace(array=['1', '12', '35', '40'], config=None, flag=False, intager=3, string='hogehoge')
```

実行結果2
設定ファイルとコマンドライン引数を両方利用した場合

```console
$ ./test_conf.py -i 1111 -s fugafuga -f
Namespace(array=['1', '12', '35', '40'], config=None, flag=True, intager=1111, string='fugafuga')
```

## いろいろ詰った点

1. 設定ファイルのパラメータ名は「--int」の「int」の箇所になりdestで指定した「intager」ではない。
2. int型を使用した場合は「type=int」を入れないと文字列として処理される。同様にstr型を指定したい場合は「type=str」を用いないといけない。のかと思ったが引数に数字を入れても文字列として処理される。
3. 引数にlistを用いる場合は「action='append'」が必要。
4. 引数に真偽値を用いる場合は「action='store_true'」もしくは「action='store_false'」が必要で、

「store_true」の場合がデフォルトで「False」、「store_false」の場合がデフォルトで「True」となる。逆じゃないのかと戸惑ったが、コマンドライン引数などで指定した場合にそれぞれTrue、Falseになるのだと考えるのがよさそう。


# 感想

もうargparseとconfigparse別々に管理してて、片方だけ変更してハマることがなくなるって素敵だなって思います。