Pythonのconfigparserでセクションがない設定ファイルを(無理矢理)読む

背景

PythonJavaのMANIFEST.MFファイルを読んで表示するプログラムを書く必要がありました。

MANIFEST.MFファイルは以下のような形式です。

Manifest-Version: 1.0
Built-By: yucatio
Build-Jdk: 11.0.5
Created-By: Maven Integration for Eclipse
Implementation-Branch: feature/TOTTEMO_NAGAI_BRANCH_NAME/LONG_SUB_DIRECT
 ORY
Implementation-Build: 1.2.3
  • キー: バリューのようにセミコロンを区切り文字にしている。セミコロンの後には空白が1つ続くが、この空白は値には含まれない。
  • キーの命名規則はChain-Caseで各単語の先頭は大文字
  • 各行は72バイトまでで、それより長くなるときは、改行し行頭に1つ空白を開けてから続きを書く

この形式のファイルをPythonのconfigparserで読んでみます。

コード

ファイルパスを引数にとり、設定内容を辞書型で返すプログラムとその使い方です。

import configparser


def read_config_without_section(config_path):
    """引数に指定されたconfigファイルの内容をdict形式で返す"""
    with open(config_path, 'r') as f:
        # ファイルの内容の先頭に'[dummy_section]'をつける
        config_string = '[dummy_section]\n' + f.read()
    config = configparser.ConfigParser()
    config.read_string(config_string)

    # valueの改行コードを削除
    return {key: value.replace('\n', '') for key, value in config.items('dummy_section', raw=True)}


def main():
    properties = read_config_without_section('/path/to/MANIFEST.MF')

    for key, value in properties.items():
        print(f'{key}:{value}')

# [出力]
# manifest-version:1.0
# built-by:yucatio
# build-jdk:11.0.5
# created-by:Maven Integration for Eclipse
# implementation-branch:feature/TOTTEMO_NAGAI_BRANCH_NAME/LONG_SUB_DIRECTORY
# implementation-build:1.2.3

キーが全て小文字になることに注意が必要ですが、上記のMANIFEST.MFの内容が取得できることがわかりました。

コードの解説

MANIFEST.MFファイルのキーと値を読み込むときに、自前でsplit関数などを使用して自前で読みこむことも考えましたが、configparserのマルチライン機能が便利なのでこちらを使用することにしました。

configparser

configparserの公式ドキュメントはこちらです。使用する際はimportが必要です。

configparser --- 設定ファイルのパーサー — Python 3.7.6 ドキュメント

iniファイル

configparserは iniファイルを読み込むことを想定しています。iniファイルは以下のようにセクションヘッダ([]で囲まれた見出し)とそれに続くkey=valueで構成されます。

iniファイルの例

[Mail]
subject=Sample Mail
mail_from=noreply@yucatio.com

[Log]
log_dir=./log
log_prefix=application
log_rotation=daily

今回読み込もうとするファイルにはセクションヘッダがありません。 セクションヘッダがないファイルを以下のように読み込もうとすると

def read_config_without_section(config_path):
    config = configparser.ConfigParser()
    config.read(config_path)


def main():
    properties = read_config_without_section('/path/to/MANIFEST.MF')

以下のようなエラーが出ます。

# 前略
File "/usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/lib/python3.7/configparser.py", line 1079, in _read
    raise MissingSectionHeaderError(fpname, lineno, line)
configparser.MissingSectionHeaderError: File contains no section headers.
file: '/path/to/MANIFEST.MF', line: 1
'Manifest-Version: 1.0\n'

そこで、ダミーのセクションヘッダをつけます。一旦ファイルの内容を全て読み込み、先頭に[dummy_section]と改行を付けます。

    with open(config_path, 'r') as f:
        # ファイルの内容の先頭に'[dummy_section]'をつける
        config_string = '[dummy_section]\n' + f.read()

configオブジェクトには、read_stringという、文字列を受け取って設定データを読むメソッドがあります。こちらを利用します。

    config = configparser.ConfigParser()
    config.read_string(config_string)

これで、設定が読み込まれました。

キーとバリューの区切り文字

configparserでは、デフォルトでは=または:が区切り文字です。最初に出てきた区切り文字で区切られます。今回はデフォルトのまま使用します。

値のtrim

設定の読み込みの際、キーと値、それぞれの先頭と末尾の空白は取り除かれます。今回はこの性質があるので自分でtrimを書かずに済みました。

マルチライン対応

configparserでは、マルチラインが扱えます。 値の2行目以降は、キーよりも深くインデントする必要があります。幸い、今回読み込むファイルは、2行目が1つの空白でインデントされています。 複数行の値は、読み込まれるとき、2行目以降の先頭の空白は取り除かれ、各行の間には改行コードが入ります。

# 例えば、
key1=value1
  value2
# のとき、key1の値は
# "value1\nvalue2"
# になる(\nは改行を表す)

今回は、値が複数行に渡るときには改行は必要ないので、削除します。

まず、config.items('dummy_section', raw=True)で設定の内容を得ます。このメソッドは、dictのitems()と同様に'dummy_section'セクションの内容をタプルで返します。raw=Trueで変数展開をしないようにします。

タプルの各値をkey, valueで受け取り、value.replace('\n', '')で改行コードを削除しています。

# valueの改行コードを削除
return {key: value.replace('\n', '') for key, value in config.items('dummy_section', raw=True)}

以上でPythonのconfigparserでセクションがない設定ファイルを(無理矢理)読むことができました。

環境

参考