Skip to content

Commit

Permalink
feat: allow custom (de)normalization (#32)
Browse files Browse the repository at this point in the history
Signed-off-by: Jan Kowalleck <jan.kowalleck@gmail.com>
  • Loading branch information
jkowalleck authored Oct 10, 2023
1 parent 0183a17 commit aeecd6b
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 15 deletions.
26 changes: 17 additions & 9 deletions serializable/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,8 @@ def default(self, o: Any) -> Any:

if prop_info.custom_type:
if prop_info.is_helper_type():
v = prop_info.custom_type.json_serialize(v)
v = prop_info.custom_type.json_normalize(
v, view=self._view, prop_info=prop_info, ctx=o.__class__)
else:
v = prop_info.custom_type(v)
elif prop_info.is_array:
Expand Down Expand Up @@ -298,17 +299,15 @@ def from_json(cls: Type[_T], data: Dict[str, Any]) -> Optional[_T]:
try:
if prop_info.custom_type:
if prop_info.is_helper_type():
_data[k] = prop_info.custom_type.json_deserialize(v)
_data[k] = prop_info.custom_type.json_denormalize(
v, prop_info=prop_info, ctx=klass)
else:
_data[k] = prop_info.custom_type(v)
elif prop_info.is_array:
items = []
for j in v:
if not prop_info.is_primitive_type() and not prop_info.is_enum:
try:
items.append(prop_info.concrete_type.from_json(data=j))
except AttributeError as e:
raise e
items.append(prop_info.concrete_type.from_json(data=j))
else:
items.append(prop_info.concrete_type(j))
_data[k] = items # type: ignore
Expand Down Expand Up @@ -438,7 +437,14 @@ def as_xml(self: Any, view_: Optional[Type[ViewType]] = None,
SubElement(nested_e, nested_key).text = str(j)
elif prop_info.custom_type:
if prop_info.is_helper_type():
SubElement(this_e, new_key).text = str(prop_info.custom_type.xml_serialize(v))
v_ser = prop_info.custom_type.xml_normalize(
v, view=view_, element_name=new_key, xmlns=xmlns, prop_info=prop_info, ctx=self.__class__)
if v_ser is None:
pass # skip the element
elif isinstance(v_ser, Element):
this_e.append(v_ser)
else:
SubElement(this_e, new_key).text = str(v_ser)
else:
SubElement(this_e, new_key).text = str(prop_info.custom_type(v))
elif prop_info.is_enum:
Expand Down Expand Up @@ -583,14 +589,16 @@ def strip_default_namespace(s: str) -> str:
)
elif prop_info.custom_type:
if prop_info.is_helper_type():
_data[decoded_k] = prop_info.custom_type.xml_deserialize(child_e)
_data[decoded_k] = prop_info.custom_type.xml_denormalize(
child_e, default_ns=default_namespace, prop_info=prop_info, ctx=klass)
else:
_data[decoded_k] = prop_info.custom_type(child_e.text)
else:
_data[decoded_k].append(prop_info.concrete_type(child_e.text))
elif prop_info.custom_type:
if prop_info.is_helper_type():
_data[decoded_k] = prop_info.custom_type.xml_deserialize(child_e.text)
_data[decoded_k] = prop_info.custom_type.xml_denormalize(
child_e, default_ns=default_namespace, prop_info=prop_info, ctx=klass)
else:
_data[decoded_k] = prop_info.custom_type(child_e.text)
elif prop_info.is_enum:
Expand Down
61 changes: 55 additions & 6 deletions serializable/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,24 @@
#
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) Paul Horton. All Rights Reserved.

import re
import warnings
from datetime import date, datetime
from typing import Any
from typing import TYPE_CHECKING, Any, Optional, Type, TypeVar, Union

if TYPE_CHECKING: # pragma: no cover
from xml.etree.ElementTree import Element

from . import ObjectMetadataLibrary, ViewType

_T = TypeVar('_T')


class BaseHelper:
"""Base Helper.
Inherit from this class and implement the needed functions!
Inherit from this class and implement/override the needed functions!
This class does not provide any functionality,
it is more like a Protocol with some fallback implementations.
Expand All @@ -34,7 +42,7 @@ class BaseHelper:
# region general/fallback

@classmethod
def serialize(cls, o: Any) -> Any:
def serialize(cls, o: Any) -> Union[Any, str]:
"""general purpose serializer"""
raise NotImplementedError()

Expand All @@ -48,10 +56,31 @@ def deserialize(cls, o: Any) -> Any:
# region json specific

@classmethod
def json_serialize(cls, o: Any) -> Any:
def json_normalize(cls, o: Any, *,
view: Optional[Type['ViewType']],
prop_info: 'ObjectMetadataLibrary.SerializableProperty',
ctx: Type[Any],
**kwargs: Any) -> Optional[Any]:
"""json specific normalizer"""
return cls.json_serialize(o)

@classmethod
def json_serialize(cls, o: Any) -> Union[str, Any]:
"""json specific serializer"""
return cls.serialize(o)

@classmethod
def json_denormalize(cls, o: Any, *,
prop_info: 'ObjectMetadataLibrary.SerializableProperty',
ctx: Type[Any],
**kwargs: Any) -> Any:
"""json specific denormalizer
:param tCls: the class that was desired to denormalize to
:param pCls: tha prent class - as context
"""
return cls.json_deserialize(o)

@classmethod
def json_deserialize(cls, o: Any) -> Any:
"""json specific deserializer"""
Expand All @@ -62,12 +91,32 @@ def json_deserialize(cls, o: Any) -> Any:
# endregion xml specific

@classmethod
def xml_serialize(cls, o: Any) -> Any:
def xml_normalize(cls, o: Any, *,
element_name: str,
view: Optional[Type['ViewType']],
xmlns: Optional[str],
prop_info: 'ObjectMetadataLibrary.SerializableProperty',
ctx: Type[Any],
**kwargs: Any) -> Optional[Union['Element', Any]]:
"""xml specific normalizer"""
return cls.xml_serialize(o)

@classmethod
def xml_serialize(cls, o: Any) -> Union[str, Any]:
"""xml specific serializer"""
return cls.serialize(o)

@classmethod
def xml_deserialize(cls, o: Any) -> Any:
def xml_denormalize(cls, o: 'Element', *,
default_ns: Optional[str],
prop_info: 'ObjectMetadataLibrary.SerializableProperty',
ctx: Type[Any],
**kwargs: Any) -> Any:
"""xml specific denormalizer"""
return cls.xml_deserialize(o.text)

@classmethod
def xml_deserialize(cls, o: Union[str, Any]) -> Any:
"""xml specific deserializer"""
return cls.deserialize(o)

Expand Down

0 comments on commit aeecd6b

Please sign in to comment.