def dump(self, obj, *, many=None): dumped = Schema.dump(self, obj, many=many) # TODO This is hacky, but the other option I can think of is to generate a different schema # depending on dump and load, which is even more hacky # The only problem is the catch all field, we can't statically create a schema for it # so we just update the dumped dict if many: for i, _obj in enumerate(obj): dumped[i].update(_handle_undefined_parameters_safe(cls=_obj, kvs={}, usage="dump")) else: dumped.update(_handle_undefined_parameters_safe(cls=obj, kvs={}, usage="dump")) return dumped
def _asdict(obj, encode_json=False): """ A re-implementation of `asdict` (based on the original in the `dataclasses` source) to support arbitrary Collection and Mapping types. """ if _is_dataclass_instance(obj): result = [] for field in fields(obj): value = _asdict(getattr(obj, field.name), encode_json=encode_json) result.append((field.name, value)) result = _handle_undefined_parameters_safe(cls=obj, kvs=dict(result), usage="to") return _encode_overrides(dict(result), _user_overrides(obj), encode_json=encode_json) elif isinstance(obj, Mapping): return dict((_asdict(k, encode_json=encode_json), _asdict(v, encode_json=encode_json)) for k, v in obj.items()) elif isinstance(obj, Collection) and not isinstance(obj, str) \ and not isinstance(obj, bytes): return list(_asdict(v, encode_json=encode_json) for v in obj) else: return copy.deepcopy(obj)
def _asdict(obj, encode_json=False, include_cls=True): """ A re-implementation of `asdict` (based on the original in the `dataclasses` source) to support arbitrary Collection and Mapping types. """ if _is_dataclass_instance(obj): result = [] for field in fields(obj): value = _asdict(getattr(obj, field.name), encode_json=encode_json) result.append((field.name, value)) result = _handle_undefined_parameters_safe(cls=obj, kvs=dict(result), usage="to") # >>> INSERTED HERE if include_cls and hasattr(obj.__class__, "__pai_dataclass__"): result = dict(result) result["__cls__"] = obj.__class__.__module__ + ":" + obj.__class__.__name__ # <<< END return _encode_overrides(dict(result), _user_overrides_or_exts(obj), encode_json=encode_json) elif isinstance(obj, Mapping): return dict((_asdict(k, encode_json=encode_json), _asdict(v, encode_json=encode_json)) for k, v in obj.items()) elif isinstance(obj, Collection) and not isinstance(obj, str) and not isinstance(obj, bytes): return list(_asdict(v, encode_json=encode_json) for v in obj) else: return copy.deepcopy(obj)
def _process_class(cls, letter_case, undefined): if letter_case is not None or undefined is not None: cls.dataclass_json_config = config( letter_case=letter_case, undefined=undefined)['dataclasses_json'] cls.to_json = DataClassJsonMixin.to_json # unwrap and rewrap classmethod to tag it to cls rather than the literal # DataClassJsonMixin ABC cls.from_json = classmethod(DataClassJsonMixin.from_json.__func__) cls.to_dict = DataClassJsonMixin.to_dict cls.from_dict = classmethod(DataClassJsonMixin.from_dict.__func__) cls.schema = classmethod(DataClassJsonMixin.schema.__func__) cls.__init__ = _handle_undefined_parameters_safe(cls, kvs=(), usage="init") # register cls as a virtual subclass of DataClassJsonMixin DataClassJsonMixin.register(cls) return cls
def _decode_dataclass(cls, kvs, infer_missing): if isinstance(kvs, cls): return kvs overrides = _user_overrides_or_exts(cls) kvs = {} if kvs is None and infer_missing else kvs field_names = [field.name for field in fields(cls)] decode_names = _decode_letter_case_overrides(field_names, overrides) kvs = {decode_names.get(k, k): v for k, v in kvs.items()} missing_fields = {field for field in fields(cls) if field.name not in kvs} for field in missing_fields: if field.default is not MISSING: kvs[field.name] = field.default elif field.default_factory is not MISSING: kvs[field.name] = field.default_factory() elif infer_missing: kvs[field.name] = None # Perform undefined parameter action kvs = _handle_undefined_parameters_safe(cls, kvs, usage="from") init_kwargs = {} types = get_type_hints(cls) for field in fields(cls): # The field should be skipped from being added # to init_kwargs as it's not intended as a constructor argument. if not field.init: continue field_value = kvs[field.name] field_type = types[field.name] if field_value is None and not _is_optional(field_type): warning = (f"value of non-optional type {field.name} detected " f"when decoding {cls.__name__}") if infer_missing: warnings.warn( f"Missing {warning} and was defaulted to None by " f"infer_missing=True. " f"Set infer_missing=False (the default) to prevent this " f"behavior.", RuntimeWarning) else: warnings.warn(f"`NoneType` object {warning}.", RuntimeWarning) init_kwargs[field.name] = field_value continue while True: if not _is_new_type(field_type): break field_type = field_type.__supertype__ if (field.name in overrides and overrides[field.name].decoder is not None): # FIXME hack if field_type is type(field_value): init_kwargs[field.name] = field_value else: init_kwargs[field.name] = overrides[field.name].decoder( field_value) elif is_dataclass(field_type): # FIXME this is a band-aid to deal with the value already being # serialized when handling nested marshmallow schema # proper fix is to investigate the marshmallow schema generation # code if is_dataclass(field_value): value = field_value else: value = _decode_dataclass(field_type, field_value, infer_missing) init_kwargs[field.name] = value elif _is_supported_generic(field_type) and field_type != str: init_kwargs[field.name] = _decode_generic(field_type, field_value, infer_missing) else: init_kwargs[field.name] = _support_extended_types(field_type, field_value) return cls(**init_kwargs)