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}")
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.
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.
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.
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.
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.
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}")