7rikazhexde’s tech log

技術的な興味関心、備忘録、アウトプットなどを書いています。

TOMLファイルのコメントを保持して読み込みと書き込み可能なtomlkitの使い方について

経緯

Pythonでプロジェクトのパッケージ管理ツールとしてPoetryを導入して以降、TOMLファイルを使用することが増えました。

TOMLファイルは主にプロジェクトの設定情報を管理して、その設定を読み込むことが主な使用方法ですが、設定を更新したい場合もあります。

TOMLファイルを使用する実行環境として、Pythonでは3.11でTOML向けの標準ライブラリとしてtomlibがサポートされましたが、tomlibは読み込みのみサポートし、書き込みは非サポートという状況のため、tomlファイルの読み込みと書き込みが可能なtomlをインストールして使用していました。

tomlを暫く使用して問題はなかったのですが、唯一不満として、
ファイルへの書き込み後にコメントが消えてしまうことがありました。

TOMLの利点は、書きやすさ、リストを使用できること、そして、コメントを書けることだと思います。ファイル書き込み時にコメントが消えてしまうのは残念だと感じていました。

そこで、TOMLファイルのコメントを残せるライブラリがないか調べたところ、コメント、インデント、空白、内部要素の順序を保持するパーサーをサポートしたtomlkitというライブラリがあることがわかりました。

本記事では実際にインストールして調べてわかったtomlkitの情報、使い方、注意点について紹介します。

補足

tomlkitは書き込み用の代替ライブラリとして公式でも紹介されていました。

This module provides an interface for parsing TOML (Tom’s Obvious Minimal Language, https://toml.io). This module does not support writing TOML.

See also The Tomli-W package is a TOML writer that can be used in conjunction with this module, providing a write API familiar to users of the standard library marshal and pickle modules.

See also The TOML Kit package is a style-preserving TOML library with both read and write capability. It is a recommended replacement for this module for editing already existing TOML files.

tomlkitの使い方

ファイルの読み込みと編集、ファイルへの書き込み方法は以下の通りです。

仕様はAPI ReferenceのTOML Fileに記載されています。

使用するtomlファイル例
TOMLファイルを扱うメソッド(read/write)の使用例
import

ファイルからTOMLファイルを扱う場合はTOMLFileクラスをimportします。

from tomlkit.toml_file import TOMLFile
ファイル読み込み:read()メソッド
toml = TOMLFile("./test.toml")
toml_data = toml.read()
データの代入

get()メソッドとkeyによる指定で値を代入します。

# dictクラスのgetメソッドを使用してkeyに対するvalueを取得する
toml_get_data = toml_data.get('key1')
# 代入
toml_get_data['list'][0] = 5
toml_get_data['value'] = 2
ファイルの書き込み(出力)

write()メソッドを使用します。 新規ファイルを作成する場合はTOMLFileクラスのインスタンスを生成します。

# 既存ファイル更新(上書き保存)
toml.write(toml_data)

# 新規ファイル作成
toml = TOMLFile("./update_test.toml")
toml.write(toml_data)
書き込み後のTOMLファイル(test.toml / update_test.toml)

実行するとtest.tomlから代入した値に変わっていることが確認できます。
また、ファイルを確認するとコメント、空白、改行が維持されていることが確認できます。

title = "test TOML file"

# Comment Sample1
[key1] # Comment Sample2
# list
list = [ 5, 2, 3,]
# value(int)
value = 2

# Comment Sample3
[key2]
# value(str)
value = "foo"
データの代入(非推奨)

以下の形式でもデータを編集することができますが、mypyで型チェックを実行するとエラーになります。container.pyのコードを見る限りisinstance関数でオブジェクトの型チェックをしていますが、戻り値は複数の型のオブジェクトを取り得るようです。

この場合は上記の通りget()メソッドを使用すればエラーは発生しません。typingモジュールまたは、isinstance関数で型定義を明示すればエラーは回避できるかと思いますが、クラスが深く、データ構造も複雑なため、エラーの解消方法まで明確にすることはできませんでした。

静的解析をする場合は以下の形式ではなく、get()メソッドを使用した方が良いと思います。

item()メソッドを使用する場合
toml_item_data = toml_data.item("key1")
toml_item_data1 = toml_item_data["list"][0]
mypy実行結果
error: Value of type "Item" is not indexable  [index]
id[key]形式の場合
test_read_data = toml_data["key1"]
test_read_data1 = test_read_data["list"][0]
mypy実行結果
error: Value of type "Union[Item, Container]" is not indexable  [index]
error: Value of type "Union[Any, Item, Container]" is not indexable  [index]
error: Invalid index type "int" for "Union[Any, Item, Container]"; expected type "Union[Key, str]"  [index]

参考

以下の記事を参考にさせていただきました。

tomlkitのドキュメント
https://tomlkit.readthedocs.io/en/latest/

Python Type Hints - How to Use typing.cast()
https://adamj.eu/tech/2021/07/06/python-type-hints-how-to-use-typing-cast/

Tips: Unionでの型エラー
https://ohke.hateblo.jp/entry/2020/10/03/230000#Tips-Union%E3%81%A7%E3%81%AE%E5%9E%8B%E3%82%A8%E3%83%A9%E3%83%BC