"""Thin wrapper around the dependency so we can maintain some stability while being able
to swap implementations (e.g. replace `tomlkit`_ with `toml`_).
`tomlkit`_ is preferred because it supports comments.
.. _tomlkit: https://github.com/sdispater/tomlkit
.. _toml: https://github.com/uiri/toml
"""
from typing import Any, List, MutableMapping, NewType, Tuple, TypeVar, Union, cast
import tomlkit
TOMLMapping = NewType("TOMLMapping", MutableMapping)
"""Abstraction on the value returned by :obj:`loads`.
This kind of object ideally should present a dict-like interface and be able to preserve
the formatting and comments of the original TOML file.
"""
T = TypeVar("T")
[docs]def loads(text: str) -> TOMLMapping:
"""Parse a string containing TOML into a dict-like object,
preserving style somehow.
"""
return TOMLMapping(cast(MutableMapping, tomlkit.loads(text)))
[docs]def dumps(obj: Union[TOMLMapping, dict]) -> str:
# and do not change the object
# TODO: If/when tomlkit allows serialising arbitrary Mapping objects, replace union
# with Mapping
"""Serialize a dict-like object into a TOML str,
If the object was generated via :obj:`loads`, then the style will be preserved.
"""
return tomlkit.dumps(obj) # type: ignore[arg-type]
# TODO: Once tomlkit improves dumps' type hints, remove type ignore comment
[docs]def setdefault(obj: MutableMapping, key: str, value: T) -> T:
# TODO: Use recursive type for obj, when available in mypy, and remove type: ignore
"""tomlkit seems to be tricky to use together with setdefault, this function is a
workaround for that.
When ``key`` is string containing ``'.'``, it will perform a nested setdefault.
"""
keys = key.split(".")
items: List[Tuple[str, Any]] = list(zip(keys, [{}] * len(keys)))
items[-1] = (keys[-1], value)
last = obj
for key, value in items:
if key not in last:
last[key] = value
last = last[key] # type: ignore
return cast(T, last)