Source code for dataclass_wizard.errors

import json
from abc import ABC, abstractmethod
from dataclasses import Field, MISSING
from typing import (Any, Type, Dict, Tuple, ClassVar,
                    Optional, Union, Iterable)

from .utils.string_conv import normalize


# added as we can't import from `type_def`, as we run into a circular import.
JSONObject = Dict[str, Any]


[docs] class JSONWizardError(ABC, Exception): """ Base error class, for errors raised by this library. """ _TEMPLATE: ClassVar[str] @property @abstractmethod def message(self) -> str: """ Format and return an error message. """ def __str__(self): return self.message
[docs] class ParseError(JSONWizardError): """ Base error when an error occurs during the JSON load process. """ _TEMPLATE = ('Failure parsing field `{field}` in class `{cls}`. Expected ' 'a type {ann_type}, got {obj_type}.\n' ' value: {o!r}\n' ' error: {e!s}') def __init__(self, base_err: Exception, obj: Any, ann_type: Union[Type, Iterable], _default_class: Optional[type] = None, _field_name: Optional[str] = None, _json_object: Any = None, **kwargs): super().__init__() self.obj = obj self.obj_type = type(obj) self.ann_type = ann_type self.base_error = base_err self.kwargs = kwargs self._class_name = None self._default_class_name = self.name(_default_class) \ if _default_class else None self._field_name = _field_name self._json_object = _json_object @property def class_name(self) -> Optional[str]: return self._class_name or self._default_class_name @class_name.setter def class_name(self, cls: Optional[Type]): if self._class_name is None: self._class_name = self.name(cls) @property def field_name(self) -> Optional[str]: return self._field_name @field_name.setter def field_name(self, name: Optional[str]): if self._field_name is None: self._field_name = name @property def json_object(self): return self._json_object @json_object.setter def json_object(self, json_obj): if self._json_object is None: self._json_object = json_obj
[docs] @staticmethod def name(obj) -> str: """Return the type or class name of an object""" return getattr(obj, '__qualname__', getattr(obj, '__name__', obj))
@property def message(self) -> str: msg = self._TEMPLATE.format( cls=self.class_name, field=self.field_name, e=self.base_error, o=self.obj, ann_type=self.name(self.ann_type), obj_type=self.name(self.obj_type)) if self.json_object: self.kwargs['json_object'] = json.dumps(self.json_object) if self.kwargs: sep = '\n ' parts = sep.join(f'{k}: {v!r}' for k, v in self.kwargs.items()) msg = f'{msg}{sep}{parts}' return msg
[docs] class MissingFields(JSONWizardError): """ Error raised when unable to create a class instance (most likely due to missing arguments) """ _TEMPLATE = ('Failure calling constructor method of class `{cls}`. ' 'Missing values for required dataclass fields.\n' ' have fields: {fields!r}\n' ' missing fields: {missing_fields!r}\n' ' input JSON object: {json_string}\n' ' error: {e!s}') def __init__(self, base_err: Exception, obj: JSONObject, cls: Type, cls_kwargs: JSONObject, cls_fields: Tuple[Field], **kwargs): super().__init__() self.obj = obj self.fields = list(cls_kwargs.keys()) self.missing_fields = [f.name for f in cls_fields if f.name not in self.fields and f.default is MISSING and f.default_factory is MISSING] # check if any field names match, and where the key transform could be the cause # see https://github.com/rnag/dataclass-wizard/issues/54 for more info normalized_json_keys = [normalize(key) for key in obj] if next((f for f in self.missing_fields if normalize(f) in normalized_json_keys), None): from .enums import LetterCase from .loaders import get_loader key_transform = get_loader(cls).transform_json_field if isinstance(key_transform, LetterCase): key_transform = key_transform.value.f kwargs['key transform'] = f'{key_transform.__name__}()' kwargs['resolution'] = 'For more details, please see https://github.com/rnag/dataclass-wizard/issues/54' self.base_error = base_err self.kwargs = kwargs self.class_name: str = self.name(cls)
[docs] @staticmethod def name(obj) -> str: """Return the type or class name of an object""" return getattr(obj, '__qualname__', getattr(obj, '__name__', obj))
@property def message(self) -> str: msg = self._TEMPLATE.format( cls=self.class_name, json_string=json.dumps(self.obj), e=self.base_error, fields=self.fields, missing_fields=self.missing_fields) if self.kwargs: sep = '\n ' parts = sep.join(f'{k}: {v}' for k, v in self.kwargs.items()) msg = f'{msg}{sep}{parts}' return msg
[docs] class UnknownJSONKey(JSONWizardError): """ Error raised when an unknown JSON key is encountered in the JSON load process. Note that this error class is only raised when the `raise_on_unknown_json_key` flag is enabled in the :class:`Meta` class. """ _TEMPLATE = ('A JSON key is missing from the dataclass schema for class `{cls}`.\n' ' unknown key: {json_key!r}\n' ' dataclass fields: {fields!r}\n' ' input JSON object: {json_string}') def __init__(self, json_key: str, obj: JSONObject, cls: Type, cls_fields: Tuple[Field], **kwargs): super().__init__() self.json_key = json_key self.obj = obj self.fields = [f.name for f in cls_fields] self.kwargs = kwargs self.class_name: str = self.name(cls)
[docs] @staticmethod def name(obj) -> str: """Return the type or class name of an object""" return getattr(obj, '__qualname__', getattr(obj, '__name__', obj))
@property def message(self) -> str: msg = self._TEMPLATE.format( cls=self.class_name, json_string=json.dumps(self.obj), fields=self.fields, json_key=self.json_key) if self.kwargs: sep = '\n ' parts = sep.join(f'{k}: {v!r}' for k, v in self.kwargs.items()) msg = f'{msg}{sep}{parts}' return msg
[docs] class MissingData(ParseError): """ Error raised when unable to create a class instance, as the JSON object is None. """ _TEMPLATE = ('Failure loading class `{cls}`. ' 'Missing value for field (expected a dict, got None)\n' ' dataclass field: {field!r}\n' ' resolution: annotate the field as ' '`Optional[{nested_cls}]` or `{nested_cls} | None`') def __init__(self, nested_cls: Type, **kwargs): super().__init__(self, None, nested_cls, **kwargs) self.nested_class_name: str = self.name(nested_cls)
[docs] @staticmethod def name(obj) -> str: """Return the type or class name of an object""" return getattr(obj, '__qualname__', getattr(obj, '__name__', obj))
@property def message(self) -> str: msg = self._TEMPLATE.format( cls=self.class_name, nested_cls=self.nested_class_name, json_string=json.dumps(self.obj), field=self.field_name, o=self.obj, ) if self.kwargs: sep = '\n ' parts = sep.join(f'{k}: {v!r}' for k, v in self.kwargs.items()) msg = f'{msg}{sep}{parts}' return msg