コマンドに全てのオプションを設定で渡すのがだるいので、設定ファイルでデフォルトの設定を指定したかった
はじめに
# オプション引数がないとなんだかわからない myapp foo bar boo baz bee # オプション引数を指定できると楽 myapp --foo=foo --bar=bar --boo=boo --baz bee # でも、本当は以下の様にして使いたい myapp bee --config=~/.config/myapp
argparseなどでコマンドを作成した時に幾つかオプションを指定可能にする。 これはsys.argvなどを直接扱うよりはずっと使いやすい。 一方で指定するオプションの数が増えてくると次第にだるくなってくる。 すべてのオプションを指定可能にする一方でオプションで与えられるパラメーターについてのデフォルト値を別途設定したい。
雑にdelegationしてみる
結局、argparseで行うparserの設定は parse_args()
を使うためにある。
これでコマンドライン引数の内容が解析された結果がオブジェクトとなって返ってくる。
このオブジェクトを委譲を行うwrapperで包んであげれば良いような気がした。
以下のようなWrapperオブジェクトで包んで見る
class MixiedArgs(object): def __init__(self, args, defaults): self.args = args self.defaults = defaults def __getattr__(self, k): return getattr(self.args, k, None) or getattr(self.defaults, k) args = MixedArgs(a, b) args.name # a.nameが無かったら b.nameを探す
これをargparseを使ったものに組み込むと以下の様な形
parser = make_parser() # name messageは設定値 class DefaultConfig: name = "foo" message = "{name}: hai" # --nameが不足しているが、DefaultConfigに委譲される args = parser.parse_args(["--message", "{name}: hello"]) args = (MixiedArgs(args, DefaultConfig)) print(args.name) # -> "foo"
とりあえずデフォルトの設定値的なものとして利用する事はできそう
config(--profile)を読み込む
設定ファイルの在処をオプションで指定できるようにする。設定ファイルのフォーマットはなんでも良いがとりあえずjsonで書くことにした。 別途以下のようなアクションを作っておく。
class ObjectLikeDict(dict): __getattr__ = dict.get class LoadJSONConfigAction(argparse.Action): """parser.add_argument("--profile", action=LoadJSONConfigAction) などとして利用""" def __call__(self, parser, namespace, val, option_string=None): import json if val.startswith("file://"): with open(val.lstrip("file://")) as rf: data = json.load(rf, object_pairs_hook=ObjectLikeDict) else: data = json.loads(val, object_pairs_hook=ObjectLikeDict) setattr(namespace, self.dest, data)
これを使うと以下のように書ける
parser = make_parser() # 直接jsonで渡された場合 args = parser.parse_args(["--message", "{name}: bye", "--config", '{"name": "bar"}']) run(MixiedArgs(args, args.config)) # jsonのパスを指定する場合 args = parser.parse_args(["--config=file://config.json"]) run(MixiedArgs(args, args.config))