Source code for base.kafka.serialization

"""Deserialization behavior shared by Kafka consumer implementations."""

import json
from typing import Optional

import marshmallow_dataclass

from src.base.data_classes.batch import Batch
from src.base.kafka.records import ConsumedKafkaMessage


[docs] class KafkaSerializationMixin: """Decode consumed Kafka values while leaving transport logic separate."""
[docs] def consume_as_json( self, source_message: ConsumedKafkaMessage | None = None ) -> tuple[Optional[str], dict]: if source_message is None: key, value, _topic = self.consume() else: key, value = source_message.key, source_message.value if not key and not value: return None, {} try: decoded_data = json.loads(value) except Exception as exception: raise ValueError("Unknown data format") from exception if not isinstance(decoded_data, dict): raise ValueError("Unknown data format") return key, decoded_data
@staticmethod def _is_dicts(obj): return isinstance(obj, list) and all(isinstance(item, dict) for item in obj) @staticmethod def _decode_batch_data(data): if data is None: return [] if not isinstance(data, list): raise ValueError("Batch data must be a list.") decoded_data = [] for item in data: if isinstance(item, str): decoded_data.append(json.loads(item)) elif isinstance(item, (dict, list)): decoded_data.append(item) else: raise ValueError("Batch data contains unsupported item type.") return decoded_data
[docs] def consume_as_object( self, source_message: ConsumedKafkaMessage | None = None ) -> tuple[None | str, Batch]: if source_message is None: key, value, _topic = self.consume() else: key, value = source_message.key, source_message.value if not key and not value: return None, {} decoded_data: dict = json.loads(value) decoded_data["data"] = self._decode_batch_data(decoded_data.get("data")) batch_schema = marshmallow_dataclass.class_schema(Batch)() batch = batch_schema.load(decoded_data) if isinstance(batch, Batch): return key, batch raise ValueError("Unknown data format.")