lodum.cbor API documentation

# lodum.cbor

View Source

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

def dumps(obj: Any) -> bytes: View Source

    20def dumps(obj: Any) -> bytes:
    21    """
    22    Encodes a Python object to CBOR bytes.
    23
    24    Args:
    25        obj: The object to encode. Must be lodum-enabled or a supported type.
    26
    27    Returns:
    28        The CBOR-encoded bytes.
    29
    30    Raises:
    31        ImportError: If cbor2 is not installed.
    32    """
    33    if cbor2 is None:
    34        raise ImportError(
    35            "cbor2 is required for CBOR serialization. Install it with 'pip install lodum[cbor]'."
    36        )
    37    dumper = CborDumper()
    38    dumped_data = dump(obj, dumper)
    39    return cbor2.dumps(dumped_data)

Encodes a Python object to CBOR bytes.

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

Returns: The CBOR-encoded bytes.

Raises: ImportError: If cbor2 is not installed.

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

    42def loads(cls: Type[T], cbor_bytes: bytes, max_size: int = DEFAULT_MAX_SIZE) -> T:
    43    """
    44    Decodes CBOR bytes into a Python object of the specified type.
    45
    46    Args:
    47        cls: The class to instantiate.
    48        cbor_bytes: The CBOR data to decode.
    49        max_size: Maximum allowed size of the input bytes.
    50
    51    Returns:
    52        An instance of cls populated with the decoded data.
    53
    54    Raises:
    55        DeserializationError: If the input is invalid or exceeds max_size.
    56        ImportError: If cbor2 is not installed.
    57    """
    58    if len(cbor_bytes) > max_size:
    59        raise DeserializationError(
    60            f"Input size ({len(cbor_bytes)}) exceeds maximum allowed ({max_size})"
    61        )
    62
    63    if cbor2 is None:
    64        raise ImportError(
    65            "cbor2 is required for CBOR deserialization. Install it with 'pip install lodum[cbor]'."
    66        )
    67    try:
    68        data = cbor2.loads(cbor_bytes)
    69    except Exception as e:
    70        raise DeserializationError(f"Failed to parse CBOR: {e}")
    71    loader = CborLoader(data)
    72    return load(cls, loader)

Decodes CBOR bytes into a Python object of the specified type.

Args: cls: The class to instantiate. cbor_bytes: The CBOR data to decode. max_size: Maximum allowed size of the input bytes.

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

Raises: DeserializationError: If the input is invalid or exceeds max_size. ImportError: If cbor2 is not installed.

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

    78class CborDumper(BaseDumper):
    79    pass

Base implementation of the Dumper protocol to reduce duplication.

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

     85class CborLoader(BaseLoader):
     86    def load_int(self) -> int:
     87        if not isinstance(self._data, int):
     88            raise DeserializationError(f"Expected int, got {type(self._data).__name__}")
     89        return self._data
     90
     91    def load_str(self) -> str:
     92        if not isinstance(self._data, str):
     93            raise DeserializationError(f"Expected str, got {type(self._data).__name__}")
     94        return self._data
     95
     96    def load_float(self) -> float:
     97        if not isinstance(self._data, (float, int)):
     98            raise DeserializationError(
     99                f"Expected float, got {type(self._data).__name__}"
    100            )
    101        return float(self._data)
    102
    103    def load_bool(self) -> bool:
    104        if not isinstance(self._data, bool):
    105            raise DeserializationError(
    106                f"Expected bool, got {type(self._data).__name__}"
    107            )
    108        return self._data
    109
    110    def load_list(self) -> Iterator["Loader"]:
    111        if not isinstance(self._data, list):
    112            raise DeserializationError(
    113                f"Expected list, got {type(self._data).__name__}"
    114            )
    115        return (CborLoader(item) for item in self._data)
    116
    117    def load_dict(self) -> Iterator[tuple[str, "Loader"]]:
    118        if not isinstance(self._data, dict):
    119            raise DeserializationError(
    120                f"Expected dict, got {type(self._data).__name__}"
    121            )
    122        return ((k, CborLoader(v)) for k, v in self._data.items())
    123
    124    def load_bytes_value(self, value: Any) -> bytes:
    125        if not isinstance(value, bytes):
    126            raise DeserializationError(f"Expected bytes, got {type(value).__name__}")
    127        return value

Base implementation of the Loader protocol to reduce duplication.

def load_int(self) -> int: View Source

    86    def load_int(self) -> int:
    87        if not isinstance(self._data, int):
    88            raise DeserializationError(f"Expected int, got {type(self._data).__name__}")
    89        return self._data

def load_str(self) -> str: View Source

    91    def load_str(self) -> str:
    92        if not isinstance(self._data, str):
    93            raise DeserializationError(f"Expected str, got {type(self._data).__name__}")
    94        return self._data

def load_float(self) -> float: View Source

     96    def load_float(self) -> float:
     97        if not isinstance(self._data, (float, int)):
     98            raise DeserializationError(
     99                f"Expected float, got {type(self._data).__name__}"
    100            )
    101        return float(self._data)

def load_bool(self) -> bool: View Source

    103    def load_bool(self) -> bool:
    104        if not isinstance(self._data, bool):
    105            raise DeserializationError(
    106                f"Expected bool, got {type(self._data).__name__}"
    107            )
    108        return self._data

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

    110    def load_list(self) -> Iterator["Loader"]:
    111        if not isinstance(self._data, list):
    112            raise DeserializationError(
    113                f"Expected list, got {type(self._data).__name__}"
    114            )
    115        return (CborLoader(item) for item in self._data)

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

    117    def load_dict(self) -> Iterator[tuple[str, "Loader"]]:
    118        if not isinstance(self._data, dict):
    119            raise DeserializationError(
    120                f"Expected dict, got {type(self._data).__name__}"
    121            )
    122        return ((k, CborLoader(v)) for k, v in self._data.items())

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

    124    def load_bytes_value(self, value: Any) -> bytes:
    125        if not isinstance(value, bytes):
    126            raise DeserializationError(f"Expected bytes, got {type(value).__name__}")
    127        return value