Skip to content

Commit

Permalink
feat: ability for custom type_mapping to take lower priority than `…
Browse files Browse the repository at this point in the history
…xml_array`

Signed-off-by: Paul Horton <paul.horton@owasp.org>
  • Loading branch information
madpah committed Jan 27, 2023
1 parent 2cfc44d commit fc0bb22
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 17 deletions.
15 changes: 7 additions & 8 deletions serializable/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ def _from_json(cls: Type[_T], data: Dict[str, Any]) -> object:
if decoded_k not in klass_properties:
for p, pi in klass_properties.items():
# TODO
if pi.custom_names.get(SerializationType.JSON, None) == decoded_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 @@ -387,19 +387,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 @@ -412,6 +406,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
42 changes: 42 additions & 0 deletions tests/fixtures/the-phoenix-project-camel-case-1-v4.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?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>
</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 reference="my-ref-2">
<reference reference="sub-ref-1" />
<reference reference="sub-ref-3" />
</reference>
<reference reference="my-ref-3">
<reference reference="sub-ref-2" />
</reference>
<reference reference="my-ref-1" />
</references>
</book>
55 changes: 55 additions & 0 deletions tests/fixtures/the-phoenix-project-camel-case-references.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
2 changes: 1 addition & 1 deletion tests/fixtures/the-phoenix-project-camel-case-v4.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
{
"reference": "my-ref-2",
"refersTo": [
"sub-ref-1", "sub-ref-2"
"sub-ref-1", "sub-ref-3"
]
},
{
Expand Down
38 changes: 33 additions & 5 deletions tests/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

import serializable
from serializable import ViewType, XmlArraySerializationType
from serializable.helpers import Iso8601Date
from serializable.helpers import BaseHelper, Iso8601Date

"""
Model classes used in unit tests.
Expand All @@ -47,6 +47,26 @@ class SchemaVersion4(ViewType):
pass


class ReferenceReferences(BaseHelper):

@classmethod
def serialize(cls, o: object) -> List[str]:
if isinstance(o, list):
return list(map(lambda i: str(i.reference), o))

raise ValueError(f'Attempt to serialize a non-date: {o.__class__}')

@classmethod
def deserialize(cls, o: object) -> List["BookReference"]:
references: List["BookReference"] = []
if isinstance(o, list):
for v in o:
references.append(BookReference(reference=v))
return references

raise ValueError(f'Attempt to deserialize a non-list: {o.__class__}')


@serializable.serializable_class
class Chapter:

Expand Down Expand Up @@ -100,7 +120,7 @@ def __eq__(self, other: object) -> bool:
return False

def __hash__(self) -> int:
return hash((self.name, self.address))
return hash((self.name, self.address, self.email))


@unique
Expand Down Expand Up @@ -153,6 +173,7 @@ def reference(self, reference: str) -> None:

@property # type: ignore[misc]
@serializable.json_name('refersTo')
@serializable.type_mapping(ReferenceReferences)
@serializable.xml_array(serializable.XmlArraySerializationType.FLAT, 'reference')
def references(self) -> List["BookReference"]:
return self._references
Expand All @@ -161,6 +182,11 @@ def references(self) -> List["BookReference"]:
def references(self, references: Iterable["BookReference"]) -> None:
self._references = list(references)

def __eq__(self, other: object) -> bool:
if isinstance(other, BookReference):
return hash(other) == hash(self)
return False

def __hash__(self) -> int:
return hash((self.reference, tuple(self.references)))

Expand All @@ -175,7 +201,7 @@ class Book:
def __init__(self, title: str, isbn: str, publish_date: date, authors: Iterable[str],
publisher: Optional[Publisher] = None, chapters: Optional[Iterable[Chapter]] = None,
edition: Optional[BookEdition] = None, type_: BookType = BookType.FICTION,
id_: Optional[UUID] = None) -> None:
id_: Optional[UUID] = None, references: Optional[List[BookReference]] = None) -> None:
self._id_ = id_ or uuid4()
self._title = title
self._isbn = isbn
Expand All @@ -185,6 +211,7 @@ def __init__(self, title: str, isbn: str, publish_date: date, authors: Iterable[
self._publisher = publisher
self.chapters = list(chapters or [])
self._type_ = type_
self.references = list(references or [])

@property # type: ignore[misc]
@serializable.xml_sequence(1)
Expand Down Expand Up @@ -242,13 +269,14 @@ def type_(self) -> BookType:

@property # type: ignore[misc]
@serializable.view(SchemaVersion4)
@serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'reference')
@serializable.xml_sequence(7)
def references(self) -> Set[BookReference]:
def references(self) -> List[BookReference]:
return self._references

@references.setter
def references(self, references: Iterable[BookReference]) -> None:
self._references = set(references)
self._references = list(references)


ThePhoenixProject_v1 = Book(
Expand Down
18 changes: 17 additions & 1 deletion tests/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
SnakeCasePropertyNameFormatter,
)
from tests.base import FIXTURES_DIRECTORY, BaseTestCase
from tests.model import Book, SchemaVersion2, SchemaVersion3, ThePhoenixProject, ThePhoenixProject_v1, SchemaVersion4
from tests.model import Book, SchemaVersion2, SchemaVersion3, SchemaVersion4, ThePhoenixProject, ThePhoenixProject_v1


class TestJson(BaseTestCase):
Expand Down Expand Up @@ -63,6 +63,22 @@ def test_deserialize_tfp_cc(self) -> None:
self.assertEqual(ThePhoenixProject_v1.authors, book.authors)
self.assertEqual(ThePhoenixProject_v1.publisher, book.publisher)
self.assertEqual(ThePhoenixProject_v1.chapters, book.chapters)
self.assertEqual(ThePhoenixProject_v1.references, book.references)

def test_deserialize_tfp_cc_with_references(self) -> None:
CurrentFormatter.formatter = CamelCasePropertyNameFormatter
with open(os.path.join(FIXTURES_DIRECTORY, 'the-phoenix-project-camel-case-references.json')) as input_json:
book: Book = Book.from_json(data=json.loads(input_json.read()))
self.assertEqual(str(ThePhoenixProject.id_), 'f3758bf0-0ff7-4366-a5e5-c209d4352b2d')
self.assertEqual(ThePhoenixProject.title, book.title)
self.assertEqual(ThePhoenixProject.isbn, book.isbn)
self.assertEqual(ThePhoenixProject.edition, book.edition)
self.assertEqual(ThePhoenixProject.publish_date, book.publish_date)
self.assertEqual(ThePhoenixProject.authors, book.authors)
self.assertEqual(ThePhoenixProject.publisher, book.publisher)
self.assertEqual(ThePhoenixProject.chapters, book.chapters)
self.assertEqual(3, len(book.references))
self.assertEqual(ThePhoenixProject.references, book.references)

def test_deserialize_tfp_cc_with_ignored(self) -> None:
CurrentFormatter.formatter = CamelCasePropertyNameFormatter
Expand Down
15 changes: 13 additions & 2 deletions tests/test_xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) Paul Horton. All Rights Reserved.

import logging
import os
from xml.etree import ElementTree

Expand All @@ -27,7 +28,11 @@
SnakeCasePropertyNameFormatter,
)
from tests.base import FIXTURES_DIRECTORY, BaseTestCase
from tests.model import Book, SchemaVersion2, SchemaVersion3, ThePhoenixProject, ThePhoenixProject_v1
from tests.model import Book, SchemaVersion2, SchemaVersion3, SchemaVersion4, ThePhoenixProject, ThePhoenixProject_v1

logger = logging.getLogger('serializable')
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')


class TestXml(BaseTestCase):
Expand All @@ -47,6 +52,11 @@ def test_serialize_tfp_cc1_v3(self) -> None:
with open(os.path.join(FIXTURES_DIRECTORY, 'the-phoenix-project-camel-case-1-v3.xml')) as expected_xml:
self.assertEqualXml(expected_xml.read(), ThePhoenixProject.as_xml(SchemaVersion3))

def test_serialize_tfp_cc1_v4(self) -> None:
CurrentFormatter.formatter = CamelCasePropertyNameFormatter
with open(os.path.join(FIXTURES_DIRECTORY, 'the-phoenix-project-camel-case-1-v4.xml')) as expected_xml:
self.assertEqualXml(expected_xml.read(), ThePhoenixProject.as_xml(SchemaVersion4))

def test_serialize_tfp_kc1(self) -> None:
CurrentFormatter.formatter = KebabCasePropertyNameFormatter
with open(os.path.join(FIXTURES_DIRECTORY, 'the-phoenix-project-kebab-case-1.xml')) as expected_xml:
Expand Down Expand Up @@ -82,7 +92,8 @@ def test_deserialize_tfp_cc1_v2(self) -> None:
self.assertEqual(ThePhoenixProject.isbn, book.isbn)
self.assertEqual(ThePhoenixProject.edition, book.edition)
self.assertEqual(ThePhoenixProject.publish_date, book.publish_date)
self.assertEqual(ThePhoenixProject.publisher, book.publisher)
self.assertEqual(ThePhoenixProject.publisher.name, book.publisher.name)
self.assertEqual(ThePhoenixProject.publisher.address, book.publisher.address)
self.assertEqual(ThePhoenixProject.authors, book.authors)
self.assertEqual(ThePhoenixProject.chapters, book.chapters)

Expand Down

0 comments on commit fc0bb22

Please sign in to comment.