lodum.toml

  1# SPDX-FileCopyrightText: 2025-present Michael R. Bernstein <zopemaven@gmail.com>
  2#
  3# SPDX-License-Identifier: Apache-2.0
  4try:
  5    import tomllib  # type: ignore[import-not-found]
  6except ImportError:
  7    try:
  8        import tomli as tomllib  # type: ignore[no-redef, import-not-found]
  9    except ImportError:
 10        tomllib = None  # type: ignore
 11
 12try:
 13    import tomli_w
 14except ImportError:
 15    tomli_w = None  # type: ignore
 16from typing import Any, Dict, Iterator, Type, TypeVar
 17
 18from .core import Loader, BaseDumper, BaseLoader
 19from .exception import DeserializationError
 20from .internal import dump, load, DEFAULT_MAX_SIZE, generate_schema
 21
 22T = TypeVar("T")
 23
 24# --- Public API ---
 25
 26
 27def dumps(obj: Any) -> str:
 28    """
 29    Encodes a Python object to a TOML string.
 30
 31    Args:
 32        obj: The object to encode. Must be lodum-enabled or a supported type.
 33
 34    Returns:
 35        A TOML string representation of the object.
 36
 37    Raises:
 38        ImportError: If tomli-w is not installed.
 39    """
 40    if tomli_w is None:
 41        raise ImportError(
 42            "tomli-w is required for TOML serialization. Install it with 'pip install lodum[toml]'."
 43        )
 44    dumper = TomlDumper()
 45    dumped_data = dump(obj, dumper)
 46    return tomli_w.dumps(dumped_data)
 47
 48
 49def loads(cls: Type[T], toml_string: str, max_size: int = DEFAULT_MAX_SIZE) -> T:
 50    """
 51    Decodes a TOML string into a Python object of the specified type.
 52
 53    Args:
 54        cls: The class to instantiate.
 55        toml_string: The TOML data to decode.
 56        max_size: Maximum allowed size of the input string in bytes.
 57
 58    Returns:
 59        An instance of cls populated with the decoded data.
 60
 61    Raises:
 62        DeserializationError: If the input is invalid or exceeds max_size.
 63        ImportError: If tomllib (or tomli) is not installed.
 64    """
 65    if len(toml_string) > max_size:
 66        raise DeserializationError(
 67            f"Input size ({len(toml_string)}) exceeds maximum allowed ({max_size})"
 68        )
 69
 70    if tomllib is None:
 71        raise ImportError(
 72            "tomllib (or tomli) is required for TOML deserialization. Install it with 'pip install lodum[toml]'."
 73        )
 74    try:
 75        data = tomllib.loads(toml_string)
 76    except Exception as e:
 77        if isinstance(e, DeserializationError):
 78            raise e
 79        raise DeserializationError(f"Failed to parse TOML: {e}")
 80    loader = TomlLoader(data)
 81    return load(cls, loader)
 82
 83
 84def schema(cls: Type[Any]) -> Dict[str, Any]:
 85    """Generates a JSON Schema for a given lodum-enabled class."""
 86    return generate_schema(cls)
 87
 88
 89# --- TOML Dumper Implementation ---
 90
 91
 92class TomlDumper(BaseDumper):
 93    def dump_bytes(self, value: bytes) -> Any:
 94        import base64
 95
 96        return base64.b64encode(value).decode("ascii")
 97
 98
 99# --- TOML Loader Implementation ---
100
101
102class TomlLoader(BaseLoader):
103    def load_list(self) -> Iterator["Loader"]:
104        if not isinstance(self._data, list):
105            raise DeserializationError(
106                f"Expected list, got {type(self._data).__name__}"
107            )
108        return (TomlLoader(item) for item in self._data)
109
110    def load_dict(self) -> Iterator[tuple[str, "Loader"]]:
111        if not isinstance(self._data, dict):
112            raise DeserializationError(
113                f"Expected dict, got {type(self._data).__name__}"
114            )
115        return ((k, TomlLoader(v)) for k, v in self._data.items())
116
117    def load_bytes_value(self, value: Any) -> bytes:
118        if not isinstance(value, str):
119            raise DeserializationError(f"Expected str, got {type(value).__name__}")
120        import base64
121
122        try:
123            return base64.b64decode(value)
124        except Exception as e:
125            raise DeserializationError(f"Failed to decode base64: {e}")
def dumps(obj: Any) -> str:
28def dumps(obj: Any) -> str:
29    """
30    Encodes a Python object to a TOML string.
31
32    Args:
33        obj: The object to encode. Must be lodum-enabled or a supported type.
34
35    Returns:
36        A TOML string representation of the object.
37
38    Raises:
39        ImportError: If tomli-w is not installed.
40    """
41    if tomli_w is None:
42        raise ImportError(
43            "tomli-w is required for TOML serialization. Install it with 'pip install lodum[toml]'."
44        )
45    dumper = TomlDumper()
46    dumped_data = dump(obj, dumper)
47    return tomli_w.dumps(dumped_data)

Encodes a Python object to a TOML string.

Args: obj: The object to encode. Must be lodum-enabled or a supported type.

Returns: A TOML string representation of the object.

Raises: ImportError: If tomli-w is not installed.

def loads(cls: Type[~T], toml_string: str, max_size: int = 10485760) -> ~T:
50def loads(cls: Type[T], toml_string: str, max_size: int = DEFAULT_MAX_SIZE) -> T:
51    """
52    Decodes a TOML string into a Python object of the specified type.
53
54    Args:
55        cls: The class to instantiate.
56        toml_string: The TOML data to decode.
57        max_size: Maximum allowed size of the input string in bytes.
58
59    Returns:
60        An instance of cls populated with the decoded data.
61
62    Raises:
63        DeserializationError: If the input is invalid or exceeds max_size.
64        ImportError: If tomllib (or tomli) is not installed.
65    """
66    if len(toml_string) > max_size:
67        raise DeserializationError(
68            f"Input size ({len(toml_string)}) exceeds maximum allowed ({max_size})"
69        )
70
71    if tomllib is None:
72        raise ImportError(
73            "tomllib (or tomli) is required for TOML deserialization. Install it with 'pip install lodum[toml]'."
74        )
75    try:
76        data = tomllib.loads(toml_string)
77    except Exception as e:
78        if isinstance(e, DeserializationError):
79            raise e
80        raise DeserializationError(f"Failed to parse TOML: {e}")
81    loader = TomlLoader(data)
82    return load(cls, loader)

Decodes a TOML string into a Python object of the specified type.

Args: cls: The class to instantiate. toml_string: The TOML data to decode. max_size: Maximum allowed size of the input string in bytes.

Returns: An instance of cls populated with the decoded data.

Raises: DeserializationError: If the input is invalid or exceeds max_size. ImportError: If tomllib (or tomli) is not installed.

def schema(cls: Type[Any]) -> Dict[str, Any]:
85def schema(cls: Type[Any]) -> Dict[str, Any]:
86    """Generates a JSON Schema for a given lodum-enabled class."""
87    return generate_schema(cls)

Generates a JSON Schema for a given lodum-enabled class.

class TomlDumper(lodum.core.BaseDumper):
93class TomlDumper(BaseDumper):
94    def dump_bytes(self, value: bytes) -> Any:
95        import base64
96
97        return base64.b64encode(value).decode("ascii")

Base implementation of the Dumper protocol to reduce duplication.

def dump_bytes(self, value: bytes) -> Any:
94    def dump_bytes(self, value: bytes) -> Any:
95        import base64
96
97        return base64.b64encode(value).decode("ascii")
class TomlLoader(lodum.core.BaseLoader):
103class TomlLoader(BaseLoader):
104    def load_list(self) -> Iterator["Loader"]:
105        if not isinstance(self._data, list):
106            raise DeserializationError(
107                f"Expected list, got {type(self._data).__name__}"
108            )
109        return (TomlLoader(item) for item in self._data)
110
111    def load_dict(self) -> Iterator[tuple[str, "Loader"]]:
112        if not isinstance(self._data, dict):
113            raise DeserializationError(
114                f"Expected dict, got {type(self._data).__name__}"
115            )
116        return ((k, TomlLoader(v)) for k, v in self._data.items())
117
118    def load_bytes_value(self, value: Any) -> bytes:
119        if not isinstance(value, str):
120            raise DeserializationError(f"Expected str, got {type(value).__name__}")
121        import base64
122
123        try:
124            return base64.b64decode(value)
125        except Exception as e:
126            raise DeserializationError(f"Failed to decode base64: {e}")

Base implementation of the Loader protocol to reduce duplication.

def load_list(self) -> Iterator[lodum.core.Loader]:
104    def load_list(self) -> Iterator["Loader"]:
105        if not isinstance(self._data, list):
106            raise DeserializationError(
107                f"Expected list, got {type(self._data).__name__}"
108            )
109        return (TomlLoader(item) for item in self._data)
def load_dict(self) -> Iterator[tuple[str, lodum.core.Loader]]:
111    def load_dict(self) -> Iterator[tuple[str, "Loader"]]:
112        if not isinstance(self._data, dict):
113            raise DeserializationError(
114                f"Expected dict, got {type(self._data).__name__}"
115            )
116        return ((k, TomlLoader(v)) for k, v in self._data.items())
def load_bytes_value(self, value: Any) -> bytes:
118    def load_bytes_value(self, value: Any) -> bytes:
119        if not isinstance(value, str):
120            raise DeserializationError(f"Expected str, got {type(value).__name__}")
121        import base64
122
123        try:
124            return base64.b64decode(value)
125        except Exception as e:
126            raise DeserializationError(f"Failed to decode base64: {e}")