Python 3.11 にて標準ライブラリに tomllib が追加され、toml ファイルを dict として読み込むことが可能になりました。
しかし、dict を toml として出力する機能がないため、これを実現したい場合は他の手段を検討する必要があります。
toml の構造を厳密に扱って出力したい場合は、外部ライブラリの tomlkit を利用するのが良いと思います。
ただし、dict をとりあえず toml 出力したい、という目的にはあまり向いていません。
この記事では、dict をそれっぽくお手軽に toml 出力するための実装例を紹介します。
参考:
dict を toml に変換するコードサンプル
ちょっと長いですが、dict を toml 文字列に変換するためのサンプルです。
dict を引数にして TomlStr.dumpsを呼び出すと、toml の文字列が返却されます。
dict の階層が深くても、toml_components の再帰でうまく処理させるようにしています。
from datetime import date, datetime, time
class TomlStr:
@classmethod
def dumps(cls, _dict):
return "\n".join(cls.toml_components(_dict, "", is_list=False))
@classmethod
def toml_components(cls, _dict, title, is_list):
single_line_items = []
multi_line_items = []
for k, v in _dict.items():
if v is None:
raise RuntimeError(f"None value is not allowed. key: {repr(k)}")
if cls.is_single_line_value(v):
single_line_items.append((k, v))
elif cls.is_multi_line_value(v):
multi_line_items.append((k, v))
else:
raise RuntimeError(f"unexpected value. key: {k}, value: {repr(v)}")
components = []
if single_line_items:
lines = []
if title:
if is_list:
lines.append(f"[[{title}]]")
else:
lines.append(f"[{title}]")
for k, v in single_line_items:
lines.append(f"{k} = {cls.value_str(v)}")
lines.append("")
components.append("\n".join(lines))
for k, v in multi_line_items:
if title:
full_key = title + "." + k
else:
full_key = k
components.extend(cls.container_components(full_key, v))
return components
@classmethod
def is_single_line_value(cls, v):
if isinstance(v, (str, int, bool, float, date, time, datetime)):
# bool は int を継承しているため、列挙せずとも問題ない
# datetime も date を継承していることから、列挙せずとも問題ない
# ここでは、分かりやすさのため冗長に列挙している
return True
if isinstance(v, list):
return all([cls.is_single_line_value(elem) for elem in v])
return False
@staticmethod
def is_multi_line_value(v):
if isinstance(v, dict):
return True
if isinstance(v, list):
return all([isinstance(elem, dict) for elem in v])
return False
@classmethod
def value_str(cls, v):
if isinstance(v, str):
return f'"{v}"'
if isinstance(v, bool):
return str(v).lower()
if isinstance(v, (int, float)):
return v
if isinstance(v, (date, time, datetime)):
return v.isoformat()
if isinstance(v, list):
return "[" + ", ".join([cls.value_str(elem) for elem in v]) + "]"
raise RuntimeError("unexpected error")
@classmethod
def container_components(cls, full_key, value):
if isinstance(value, dict):
return cls.toml_components(value, full_key, is_list=False)
if isinstance(value, list):
components = []
for _dict in value:
components.extend(cls.toml_components(_dict, full_key, is_list=True))
return components
raise RuntimeError("unexpected error")
実行例
読み込み用の toml サンプル
動作確認のため、それっぽい toml ファイルを用意します。
# toml の読み込みサンプル
item_str = "hello"
item_int = 123
item_float = 1.234
item_bool = true
item_date = 2024-03-31
item_time = 18:06:30.123
item_datetime = 2024-03-31T18:06:30.123
item_datetime_jst = 2024-03-31T18:06:30.123+09:00
item_list = ["1", "2", "3", "4"]
item_complex_list = [["1", "2"], ["3", "4"], "a", [[["abc"]]]]
item_table = { "a" = 1, "b" = 2, "c" = 3 }
[[child_list]]
item_str = "child0"
item_int = 0
[child_list.sub]
item_str = "child_sub"
item_int = 3
[child_list.sub.sub.sub]
item_str = "child_sub_sub_sub"
[[child_list]]
item_str = "child1"
item_int = 1
実行スクリプト
実行用の python スクリプトも準備します。
前述の toml を読み込んで dict に変換し、これを再度 str に変換します。
import tomllib
with open("sample.toml", "rb") as f:
data = tomllib.load(f)
toml_str = TomlStr.dumps(data)
print(toml_str)
なお、tomllib を使用するため、このスクリプトを実行するためには Python 3.11 以降である必要があります。
実行結果
スクリプトを実行すると、次のように toml 文字列が出力されます。
item_str = "hello"
item_int = 123
item_float = 1.234
item_bool = true
item_date = 2024-03-31
item_time = 18:06:30.123000
item_datetime = 2024-03-31T18:06:30.123000
item_datetime_jst = 2024-03-31T18:06:30.123000+09:00
item_list = ["1", "2", "3", "4"]
item_complex_list = [["1", "2"], ["3", "4"], "a", [[["abc"]]]]
[item_table]
a = 1
b = 2
c = 3
[[child_list]]
item_str = "child0"
item_int = 0
[child_list.sub]
item_str = "child_sub"
item_int = 3
[child_list.sub.sub.sub]
item_str = "child_sub_sub_sub"
[[child_list]]
item_str = "child1"
item_int = 1
当然ですが、dict を経由しているため、元ファイルのフォーマットは保全されません。
例えば、1行目のコメントは消えています。
また、インナーテーブルであった item_table 項目は、通常のテーブル形式に展開されています。
構造は気にしないから、それっぽく toml 出力したい!という場面ではある程度有用かと思います。
コメント