lodum.yaml API documentation

# lodum.yaml

View Source

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

yaml: Any = <ruamel.yaml.main.YAML object>
def dumps(obj: Any) -> str: View Source

    29def dumps(obj: Any) -> str:
    30    """
    31    Encodes a Python object to a YAML string.
    32
    33    Args:
    34        obj: The object to encode. Must be lodum-enabled or a supported type.
    35
    36    Returns:
    37        A YAML string representation of the object.
    38
    39    Raises:
    40        ImportError: If ruamel.yaml is not installed.
    41    """
    42    if not yaml_available:
    43        raise ImportError(
    44            "ruamel.yaml is required for YAML serialization. Install it with 'pip install lodum[yaml]'."
    45        )
    46
    47    dumper = YamlDumper()
    48    dumped_data = dump(obj, dumper)
    49
    50    with io.StringIO() as string_stream:
    51        yaml.dump(dumped_data, string_stream)
    52        return string_stream.getvalue()

Encodes a Python object to a YAML string.

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

Returns: A YAML string representation of the object.

Raises: ImportError: If ruamel.yaml is not installed.

def loads(cls: Type[~T], yaml_string: str, max_size: int = 10485760) -> ~T: View Source

    55def loads(cls: Type[T], yaml_string: str, max_size: int = DEFAULT_MAX_SIZE) -> T:
    56    """
    57    Decodes a YAML string into a Python object of the specified type.
    58
    59    Args:
    60        cls: The class to instantiate.
    61        yaml_string: The YAML data to decode.
    62        max_size: Maximum allowed size of the input string in bytes.
    63
    64    Returns:
    65        An instance of cls populated with the decoded data.
    66
    67    Raises:
    68        DeserializationError: If the input is invalid or exceeds max_size.
    69        ImportError: If ruamel.yaml is not installed.
    70    """
    71    if len(yaml_string) > max_size:
    72        raise DeserializationError(
    73            f"Input size ({len(yaml_string)}) exceeds maximum allowed ({max_size})"
    74        )
    75
    76    if not yaml_available:
    77        raise ImportError(
    78            "ruamel.yaml is required for YAML deserialization. Install it with 'pip install lodum[yaml]'."
    79        )
    80
    81    data = yaml.load(yaml_string)
    82    loader = YamlLoader(data)
    83    return load(cls, loader)

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

Args: cls: The class to instantiate. yaml_string: The YAML 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 ruamel.yaml is not installed.

def schema(cls: Type[Any]) -> Dict[str, Any]: View Source

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

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

class YamlDumper(lodum.core.BaseDumper): View Source

     94class YamlDumper(BaseDumper):
     95    """
     96    Encodes Python objects into a YAML-compatible intermediate representation.
     97    """
     98
     99    def dump_bytes(self, value: bytes) -> Any:
    100        # YAML can handle bytes natively if using certain tags,
    101        # but for simplicity and cross-format consistency, we'll use base64 like JSON.
    102        import base64
    103
    104        return base64.b64encode(value).decode("ascii")

Encodes Python objects into a YAML-compatible intermediate representation.

def dump_bytes(self, value: bytes) -> Any: View Source

     99    def dump_bytes(self, value: bytes) -> Any:
    100        # YAML can handle bytes natively if using certain tags,
    101        # but for simplicity and cross-format consistency, we'll use base64 like JSON.
    102        import base64
    103
    104        return base64.b64encode(value).decode("ascii")

class YamlLoader(lodum.core.BaseLoader): View Source

    110class YamlLoader(BaseLoader):
    111    """
    112    Decodes a YAML-compatible intermediate representation into Python objects.
    113    """
    114
    115    def load_list(self) -> Iterator["Loader"]:
    116        if not isinstance(self._data, list):
    117            raise DeserializationError(
    118                f"Expected list, got {type(self._data).__name__}"
    119            )
    120        return (YamlLoader(item) for item in self._data)
    121
    122    def load_dict(self) -> Iterator[tuple[str, "Loader"]]:
    123        if not isinstance(self._data, dict):
    124            raise DeserializationError(
    125                f"Expected dict, got {type(self._data).__name__}"
    126            )
    127        return ((k, YamlLoader(v)) for k, v in self._data.items())
    128
    129    def load_bytes_value(self, value: Any) -> bytes:
    130        if isinstance(value, bytes):
    131            return value
    132        if not isinstance(value, str):
    133            raise DeserializationError(f"Expected str, got {type(value).__name__}")
    134        import base64
    135
    136        try:
    137            return base64.b64decode(value)
    138        except Exception as e:
    139            raise DeserializationError(f"Failed to decode base64: {e}")

Decodes a YAML-compatible intermediate representation into Python objects.

def load_list(self) -> Iterator[lodum.core.Loader]: View Source

    115    def load_list(self) -> Iterator["Loader"]:
    116        if not isinstance(self._data, list):
    117            raise DeserializationError(
    118                f"Expected list, got {type(self._data).__name__}"
    119            )
    120        return (YamlLoader(item) for item in self._data)

def load_dict(self) -> Iterator[tuple[str, lodum.core.Loader]]: View Source

    122    def load_dict(self) -> Iterator[tuple[str, "Loader"]]:
    123        if not isinstance(self._data, dict):
    124            raise DeserializationError(
    125                f"Expected dict, got {type(self._data).__name__}"
    126            )
    127        return ((k, YamlLoader(v)) for k, v in self._data.items())

def load_bytes_value(self, value: Any) -> bytes: View Source

    129    def load_bytes_value(self, value: Any) -> bytes:
    130        if isinstance(value, bytes):
    131            return value
    132        if not isinstance(value, str):
    133            raise DeserializationError(f"Expected str, got {type(value).__name__}")
    134        import base64
    135
    136        try:
    137            return base64.b64decode(value)
    138        except Exception as e:
    139            raise DeserializationError(f"Failed to decode base64: {e}")