Skip to content

Commit

Permalink
Merge pull request #3 from madpah/feat/recursive-parsing-differing-sc…
Browse files Browse the repository at this point in the history
…hemas

feat: `xml_array` has higher priority than `type_mapping`

feat: handle `ForwardRef` types
  • Loading branch information
madpah committed Feb 21, 2023
2 parents ccd610f + a66e700 commit 664f947
Show file tree
Hide file tree
Showing 8 changed files with 374 additions and 25 deletions.
81 changes: 64 additions & 17 deletions serializable/__init__.py
Expand Up @@ -256,7 +256,7 @@ def _from_json(cls: Type[_T], data: Dict[str, Any]) -> object:
new_key = None
if decoded_k not in klass_properties:
for p, pi in klass_properties.items():
if pi.custom_names.get(SerializationType.JSON, None) == k:
if pi.custom_names.get(SerializationType.JSON, None) in [decoded_k, k]:
new_key = p
else:
new_key = decoded_k
Expand Down Expand Up @@ -386,19 +386,13 @@ def _as_xml(self: _T, view_: Optional[Type[_T]] = None, as_string: bool = True,
new_key = CurrentFormatter.formatter.encode(property_name=new_key)
new_key = _namespace_element_name(tag_name=new_key, xmlns=xmlns)

if prop_info.custom_type:
if prop_info.is_helper_type():
ElementTree.SubElement(this_e, new_key).text = str(prop_info.custom_type.serialize(v))
else:
ElementTree.SubElement(this_e, new_key).text = str(prop_info.custom_type(v))
elif prop_info.is_array and prop_info.xml_array_config:
if prop_info.is_array and prop_info.xml_array_config:
_array_type, nested_key = prop_info.xml_array_config
nested_key = _namespace_element_name(tag_name=nested_key, xmlns=xmlns)
if _array_type and _array_type == XmlArraySerializationType.NESTED:
nested_e = ElementTree.SubElement(this_e, new_key)
else:
nested_e = this_e

for j in v:
if not prop_info.is_primitive_type() and not prop_info.is_enum:
nested_e.append(j.as_xml(view_=view_, as_string=False, element_name=nested_key, xmlns=xmlns))
Expand All @@ -411,6 +405,11 @@ def _as_xml(self: _T, view_: Optional[Type[_T]] = None, as_string: bool = True,
else:
# Assume type is str
ElementTree.SubElement(nested_e, nested_key).text = str(j)
elif prop_info.custom_type:
if prop_info.is_helper_type():
ElementTree.SubElement(this_e, new_key).text = str(prop_info.custom_type.serialize(v))
else:
ElementTree.SubElement(this_e, new_key).text = str(prop_info.custom_type(v))
elif prop_info.is_enum:
ElementTree.SubElement(this_e, new_key).text = str(v.value)
elif not prop_info.is_primitive_type():
Expand Down Expand Up @@ -522,12 +521,10 @@ def _from_xml(cls: Type[_T], data: Union[TextIOWrapper, ElementTree.Element],
raise ValueError(f'{decoded_k} is not a known Property for {cls.__module__}.{cls.__qualname__}')

try:
if prop_info.custom_type:
if prop_info.is_helper_type():
_data[decoded_k] = prop_info.custom_type.deserialize(child_e.text)
else:
_data[decoded_k] = prop_info.custom_type(child_e.text)
elif prop_info.is_array and prop_info.xml_array_config:

logger.debug(f'Handling {prop_info}')

if prop_info.is_array and prop_info.xml_array_config:
array_type, nested_name = prop_info.xml_array_config

if decoded_k not in _data:
Expand All @@ -542,12 +539,22 @@ def _from_xml(cls: Type[_T], data: Union[TextIOWrapper, ElementTree.Element],
else:
_data[decoded_k].append(prop_info.concrete_type(sub_child_e.text))
else:
if not prop_info.is_primitive_type():
if not prop_info.is_primitive_type() and not prop_info.is_enum:
_data[decoded_k].append(prop_info.concrete_type.from_xml(
data=child_e, default_namespace=default_namespace)
)
elif prop_info.custom_type:
if prop_info.is_helper_type():
_data[decoded_k] = prop_info.custom_type.deserialize(child_e)
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.deserialize(child_e.text)
else:
_data[decoded_k] = prop_info.custom_type(child_e.text)
elif prop_info.is_enum:
_data[decoded_k] = prop_info.concrete_type(child_e.text)
elif not prop_info.is_primitive_type():
Expand Down Expand Up @@ -650,7 +657,7 @@ class SerializableProperty:
(de-)serialization.
"""

_ARRAY_TYPES = ('List', 'Set', 'SortedSet')
_ARRAY_TYPES = {'List': List, 'Set': Set, 'SortedSet': Set}
_DEFAULT_XML_SEQUENCE = 100
_SORTED_CONTAINERS_TYPES = {'SortedList': List, 'SortedSet': Set}
_PRIMITIVE_TYPES = (bool, int, float, str)
Expand All @@ -661,6 +668,7 @@ def __init__(self, *, prop_name: str, prop_type: Any, custom_names: Dict[Seriali
views: Optional[Iterable[_Klass]] = None,
xml_array_config: Optional[Tuple[XmlArraySerializationType, str]] = None,
xml_sequence_: Optional[int] = None) -> None:

self._name = prop_name
self._custom_names = custom_names
self._type_ = None
Expand Down Expand Up @@ -777,7 +785,7 @@ def parse_type_deferred(self) -> None:
self._parse_type(type_=self._type_)

def _parse_type(self, type_: Any) -> None:
self._type_ = type_
self._type_ = type_ = self._handle_forward_ref(t_=type_)

if type(type_) == str:
type_to_parse = str(type_)
Expand Down Expand Up @@ -822,6 +830,39 @@ def _parse_type(self, type_: Any) -> None:

self._type_ = mapped_array_type[_k] # type: ignore
self._concrete_type = _k # type: ignore

elif results.get('array_type', None).replace('typing.', '') in self._ARRAY_TYPES:
mapped_array_type = self._ARRAY_TYPES.get(
str(results.get('array_type', None).replace('typing.', ''))
)
self._is_array = True
try:
# Will load any class already loaded assuming fully qualified name
self._type_ = eval(f'{mapped_array_type}[{results.get("array_of")}]')
self._concrete_type = eval(str(results.get("array_of")))
except NameError:
# Likely a class that is missing its fully qualified name
_l: Optional[Any] = None
for _k_name, _oml_sc in ObjectMetadataLibrary.klass_mappings.items():
if _oml_sc.name == results.get("array_of"):
_l = _oml_sc.klass

if _l is None:
# Perhaps a custom ENUM?
for _enum_klass in ObjectMetadataLibrary.custom_enum_klasses:
if _enum_klass.__name__ == results.get("array_of"):
_l = _enum_klass

if _l is None:
self._type_ = type_ # type: ignore
self._deferred_type_parsing = True
ObjectMetadataLibrary.defer_property_type_parsing(
prop=self, klasses=[str(results.get("array_of"))]
)
return

self._type_ = mapped_array_type[_l] # type: ignore
self._concrete_type = _l # type: ignore
else:
raise ValueError(f'Unable to handle Property with declared type: {type_}')
else:
Expand Down Expand Up @@ -851,6 +892,12 @@ def _parse_type(self, type_: Any) -> None:
if self._deferred_type_parsing:
self._deferred_type_parsing = False

def _handle_forward_ref(self, t_: Any) -> Any:
if 'ForwardRef' in str(t_):
return str(t_).replace('ForwardRef(\'', '"').replace('\')', '"')
else:
return t_

def __eq__(self, other: Any) -> bool:
if isinstance(other, ObjectMetadataLibrary.SerializableProperty):
return hash(other) == hash(self)
Expand Down
4 changes: 2 additions & 2 deletions serializable/helpers.py
Expand Up @@ -30,12 +30,12 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:

@classmethod
@abstractmethod
def serialize(cls, o: object) -> str:
def serialize(cls, o: object) -> Any:
raise NotImplementedError

@classmethod
@abstractmethod
def deserialize(cls, o: str) -> object:
def deserialize(cls, o: str) -> Any:
raise NotImplementedError


Expand Down
43 changes: 43 additions & 0 deletions tests/fixtures/the-phoenix-project-camel-case-1-v4.xml
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<book isbnNumber="978-1942788294">
<id>f3758bf0-0ff7-4366-a5e5-c209d4352b2d</id>
<title>The Phoenix Project</title>
<edition number="5">5th Anniversary Limited Edition</edition>
<publishDate>2018-04-16</publishDate>
<author>Kevin Behr</author>
<author>Gene Kim</author>
<author>George Spafford</author>
<type>fiction</type>
<publisher>
<name>IT Revolution Press LLC</name>
<address>10 Downing Street</address>
</publisher>
<chapters>
<chapter>
<number>1</number>
<title>Tuesday, September 2</title>
</chapter>
<chapter>
<number>2</number>
<title>Tuesday, September 2</title>
</chapter>
<chapter>
<number>3</number>
<title>Tuesday, September 2</title>
</chapter>
<chapter>
<number>4</number>
<title>Wednesday, September 3</title>
</chapter>
</chapters>
<references>
<reference ref="my-ref-2">
<reference ref="sub-ref-1" />
<reference ref="sub-ref-3" />
</reference>
<reference ref="my-ref-3">
<reference ref="sub-ref-2" />
</reference>
<reference ref="my-ref-1" />
</references>
</book>
55 changes: 55 additions & 0 deletions tests/fixtures/the-phoenix-project-camel-case-references.json
@@ -0,0 +1,55 @@
{
"id": "f3758bf0-0ff7-4366-a5e5-c209d4352b2d",
"title": "The Phoenix Project",
"isbnNumber": "978-1942788294",
"edition": {
"number": 5,
"name": "5th Anniversary Limited Edition"
},
"publishDate": "2018-04-16",
"type": "fiction",
"authors": [
"Kevin Behr",
"Gene Kim",
"George Spafford"
],
"publisher": {
"name": "IT Revolution Press LLC",
"address": "10 Downing Street"
},
"chapters": [
{
"number": 1,
"title": "Tuesday, September 2"
},
{
"number": 2,
"title": "Tuesday, September 2"
},
{
"number": 3,
"title": "Tuesday, September 2"
},
{
"number": 4,
"title": "Wednesday, September 3"
}
],
"references": [
{
"reference": "my-ref-3",
"refersTo": [
"sub-ref-2"
]
},
{
"reference": "my-ref-2",
"refersTo": [
"sub-ref-1", "sub-ref-3"
]
},
{
"reference": "my-ref-1"
}
]
}
55 changes: 55 additions & 0 deletions tests/fixtures/the-phoenix-project-camel-case-v4.json
@@ -0,0 +1,55 @@
{
"id": "f3758bf0-0ff7-4366-a5e5-c209d4352b2d",
"title": "The Phoenix Project",
"isbnNumber": "978-1942788294",
"edition": {
"number": 5,
"name": "5th Anniversary Limited Edition"
},
"publishDate": "2018-04-16",
"type": "fiction",
"authors": [
"Kevin Behr",
"Gene Kim",
"George Spafford"
],
"publisher": {
"name": "IT Revolution Press LLC",
"address": "10 Downing Street"
},
"chapters": [
{
"number": 1,
"title": "Tuesday, September 2"
},
{
"number": 2,
"title": "Tuesday, September 2"
},
{
"number": 3,
"title": "Tuesday, September 2"
},
{
"number": 4,
"title": "Wednesday, September 3"
}
],
"references": [
{
"reference": "my-ref-3",
"refersTo": [
"sub-ref-2"
]
},
{
"reference": "my-ref-2",
"refersTo": [
"sub-ref-1", "sub-ref-3"
]
},
{
"reference": "my-ref-1"
}
]
}

0 comments on commit 664f947

Please sign in to comment.