import json import sys from marshmallow import ValidationError, fields from marshmallow import utils as marsh_utils from typing import List, Tuple, Union class BulkField(fields.List): """Field for bulk data deserialization. Args: exclude_failed: bool - flag to exclude invalid items (even if it's possible to load them partially) from valid_data. raise: bool - flag to raise ValidationError in case of having invalid items or not. Returns: tuple of two lists: loaded valid items and errors. """ def __init__(self, *args, **kwargs): self._exclude_failed = kwargs.pop("exclude_failed", True) self._raise = kwargs.pop("raise", False) super().__init__(*args, **kwargs) def _deserialize(self, value, attr, data, **kwargs) -> Tuple[List[dict], List[dict]]: if not marsh_utils.is_collection(value): raise self.make_error("invalid") items_ok = [] items_failed = [] for idx, each in enumerate(value): try: items_ok.append({"index": idx, "data": self.inner.deserialize(each, **kwargs)}) except ValidationError as error: if not self._exclude_failed and error.valid_data is not None: items_ok.append({"index": idx, "data": error.valid_data}) items_failed.append({"index": idx, "error": {"type": "invalid_data", "data": error.messages}}) if self._raise and items_failed: raise ValidationError({i["index"]: i for i in items_failed}, valid_data=items_ok) return items_ok, items_failed class JsonField(fields.Dict): """Field for json-convertable data deserialization. Args: max_size: int - maximum data size in bytes. min_size: int - minimum data size in bytes. return_orig_data: bool - flag, if set returns original data, otherwise returns dumped (to str) data. Returns: dict or str """ def __init__(self, *args, **kwargs): self._max_size = kwargs.pop("max_size", None) self._min_size = kwargs.pop("min_size", None) self._return_orig_data = kwargs.pop("return_orig_data", False) super().__init__(*args, **kwargs) def _deserialize(self, value, attr, data, **kwargs) -> Union[str, dict]: result = super()._deserialize(value, attr, data, **kwargs) if not result: return result try: dumped = json.dumps(result, default=str) except TypeError as ex: raise ValidationError("Not json-serializable data.") from ex size = sys.getsizeof(dumped) if self._min_size and size < self._min_size or self._max_size and size > self._max_size: raise ValidationError( f"Data size {size} bytes is out of allowed range from {self._min_size} to {self._max_size}." ) return result if self._return_orig_data else dumped