Source code for typedpy.serialization.serialization_wrappers

from typedpy.commons import wrap_val
from typedpy.structures import Structure, TypedPyDefaults, ADDITIONAL_PROPERTIES
from typedpy.fields import StructureClass, Map, String, OneOf, Boolean, FunctionCall
from .serialization import deserialize_structure, serialize


[docs]class Deserializer(Structure): """ A high level API for a deserializer: from a dict or anything else that could be sent as a JSON, to a :class:`Structure`. The advantage of this over the lower level function is that it is more explicit and self-validating. In other words, it prevents the user from creating an invalid mapper. Arguments: target_class(:class:`StructureClass`): A class extending the abstract :class:`Structure` that this deserializer is build for. Example: .. code-block:: python class Foo(Structure): id = Integer name = String Deserializer(target_class=Foo) mapper(dict): optional The key is the target attribute name. The value can either be a path of the value in the source dict using dot notation, for example: "aaa.bbb", or a :class:`FunctionCall`. In the latter case, the function is the used to preprocess the input prior to deserialization/validation. The args attribute in the function call is optional. If non provided, the input to the function is the value with the same key. Otherwise it is the keys of the values in the input that are injected to the provided function. See working examples in the tests link above. This class will ensure that the mapper is a valid one for its target_class. Example: .. code-block:: python class Foo(Structure): m = Map s = String i = Integer mapper = { "m": "a.b", "s": FunctionCall(func=lambda x: f'the string is {x}', args=['name.first']), 'i': FunctionCall(func=operator.add, args=['i', 'j']) } Deserializer(target_class=Foo, mapper = mapper).deserializer(the_input_dict) camel_case_convert(bool): Optional If true, will convert any camelCase key that does not have explicit mapping to a snake_case attribute name. Default is False. use_strict_mapping(bool): Optional If true, if a field is mapped to a different key name, then deserialization will never use the key that matches the original field name for that field. i.e.: .. code-block:: python class Foo(Structure): a: int _serialization_mapper = {"a": "b"} # This raises an exception Deserializer(Foo, use_strict_mapping=True).deserialize({"a": 5}) """ target_class = StructureClass mapper = Map[String, OneOf[String, FunctionCall, Map]] use_strict_mapping = Boolean(default=False) camel_case_convert = Boolean(default=False) _required = ["target_class"] def __validate__(self): valid_keys = set(getattr(self.target_class, "get_all_fields_by_name")().keys()) if self.mapper: for key in self.mapper: if key.split(".")[0] not in valid_keys: raise ValueError( f"Invalid key in mapper for class {self.target_class.__name__}: {key}. Keys must be one of " "the class fields. " ) def deserialize( self, input_data, *, keep_undefined=None, direct_trusted_mapping=False ): additional_props_allowed = getattr( self.target_class, ADDITIONAL_PROPERTIES, TypedPyDefaults.additional_properties_default, ) adjusted_keep_undefined = ( keep_undefined if keep_undefined is not None or additional_props_allowed else True ) return deserialize_structure( self.target_class, input_data, mapper=self.mapper, use_strict_mapping=self.use_strict_mapping, keep_undefined=adjusted_keep_undefined, camel_case_convert=self.camel_case_convert, direct_trusted_mapping=direct_trusted_mapping, )
[docs]class Serializer(Structure): """ A high level API for a serializer: from an instance of :class:`Structure`, to something that can be sent as a Json(usually a dict). The advantage of this over the lower level function is that it is more explicit andself-validating. In other words, it prevents the user from creating an invalid mapper. Arguments: source(:class:`Structure`): An instance of :class:`Structure` that this serializer is build for. Example: .. code-block:: python class Foo(Structure): i = Integer f = Float foo = Foo(f=5.5, i=999) Serializer(source=foo) mapper(dict): optional The key is the target key name. The value can either be a path of the value in the source object using dot notation, for example: "aaa.bbb", or a :class:`FunctionCall`. In the latter case, the function is the used to preprocess the input prior to deserialization/validation. \ The args attribute in the function call is optional. If non provided, the input to the function is the attribute with the same key. Otherwise it is the names of the attributes in the input that are injected to the provided function. \ See working examples in the tests link above. \ This class will ensure that the mapper is a valid one for its target_class. Example: .. code-block:: python class Foo(Structure): f = Float i = Integer foo = Foo(f=5.5, i=999) mapper = { 'output_floats': FunctionCall(func=lambda f: [int(f)], args=['i']), 'output_int': FunctionCall(func=lambda x: str(x), args=['f']) } assert Serializer(source=foo, mapper=mapper).serialize() == { 'output_floats': [999], 'output_int': '5.5' } """ source = Structure mapper = Map[String, OneOf[String, FunctionCall, Map]] _required = ["source"] def __validate__(self): def verify_key_in_mapper(key, valid_keys, source_class): if key.split(".")[0] not in valid_keys: raise ValueError( f"Invalid key in mapper for class {source_class.__name__}: {key}. Keys must be one of the class " "fields. " ) if isinstance(self.mapper[key], (FunctionCall,)): args = self.mapper[key].args if isinstance(args, (list,)): for arg in args: if arg not in valid_keys: raise ValueError( f"Mapper[{key}] has a function call with an invalid argument: {arg}" ) source_class = self.source.__class__ valid_keys = set(source_class.get_all_fields_by_name().keys()) if self.mapper: for key in self.mapper: verify_key_in_mapper(key, valid_keys, source_class) def serialize( self, compact: bool = None, camel_case_convert: bool = False, ): """ Arguments: compact(boolean): optional whether to use a compact form for Structure that is a simple wrapper of a field. for example: if a Structure has only one field of an int, if compact is True it will serialize the structure as an int instead of a dictionary. Default is False. camel_case_convert(dict): optional If True, convert any camel-case key that does not have a mapping in the mapper to a snake-case attribute. Default is False. """ compact = ( TypedPyDefaults.compact_serialization_default if compact is None else compact ) return serialize( self.source, mapper=self.mapper, compact=compact, camel_case_convert=camel_case_convert, )
def deserializer_by_discriminator(class_by_discriminator_value, keep_undefined=False): """ create deserialized based on discriminator value in the input. :param class_by_discriminator_value: a dictionary of the Structure class by the discriminator value :return: A function that should be used in a mapper, for which the first argument is the key for The discriminator field, and the second is the key for the data field that is deserialized based on the provided class_by_discriminator_value. """ _desererializer_by_type = { k: Deserializer(t) for (k, t) in class_by_discriminator_value.items() } def _get_content(discriminator, data): if discriminator not in _desererializer_by_type: raise ValueError( f"discriminator: got {wrap_val(discriminator)}; Expected one of {list(_desererializer_by_type.keys())}" ) return _desererializer_by_type[discriminator].deserialize( data, keep_undefined=keep_undefined ) return _get_content