diff --git a/serializable/__init__.py b/serializable/__init__.py index 2f84825..8b72861 100644 --- a/serializable/__init__.py +++ b/serializable/__init__.py @@ -319,6 +319,8 @@ def from_json(cls: Type[_T], data: Dict[str, Any]) -> Optional[_T]: if global_klass_name in ObjectMetadataLibrary.klass_mappings: _data[k] = prop_info.concrete_type.from_json(data=v) else: + if prop_info.concrete_type is Decimal: + v = str(v) _data[k] = prop_info.concrete_type(v) except AttributeError as e: logging.error(f'There was an AttributeError deserializing JSON to {cls}.{os.linesep}' diff --git a/tests/base.py b/tests/base.py index 0b505cb..687b010 100644 --- a/tests/base.py +++ b/tests/base.py @@ -75,12 +75,17 @@ def assertDeepEqual(self: Union[TestCase, 'DeepCompareMixin'], self.maxDiff = _omd def __deepDict(self, o: Any) -> Any: - if isinstance(o, (list, tuple)): + if isinstance(o, tuple): return tuple(self.__deepDict(i) for i in o) + if isinstance(o, list): + return list(self.__deepDict(i) for i in o) if isinstance(o, dict): - return {k: self.__deepDict(v) for k, v in o} + return {k: self.__deepDict(v) for k, v in o.items()} if isinstance(o, set): - return tuple(sorted((self.__deepDict(i) for i in o), key=repr)) + # this method returns dict. `dict` is not hashable, so use `tuple` instead. + return tuple(self.__deepDict(i) for i in sorted(o, key=hash)) + ('%conv:%set',) if hasattr(o, '__dict__'): - return {k: self.__deepDict(v) for k, v in vars(o).items() if not (k.startswith('__') and k.endswith('__'))} + d = {a: self.__deepDict(v) for a, v in o.__dict__.items() if '__' not in a} + d['%conv'] = str(type(o)) + return d return o diff --git a/tests/fixtures/the-phoenix-project-camel-case-1-v1.xml b/tests/fixtures/the-phoenix-project-camel-case-1-v1.xml index f1eae01..8ed748d 100644 --- a/tests/fixtures/the-phoenix-project-camel-case-1-v1.xml +++ b/tests/fixtures/the-phoenix-project-camel-case-1-v1.xml @@ -30,4 +30,5 @@ + 9.8 \ No newline at end of file diff --git a/tests/fixtures/the-phoenix-project-camel-case-1-v2.xml b/tests/fixtures/the-phoenix-project-camel-case-1-v2.xml index 8b168c4..0014b1a 100644 --- a/tests/fixtures/the-phoenix-project-camel-case-1-v2.xml +++ b/tests/fixtures/the-phoenix-project-camel-case-1-v2.xml @@ -31,4 +31,5 @@ Wednesday, September 3 + 9.8 \ No newline at end of file diff --git a/tests/fixtures/the-phoenix-project-camel-case-1-v3.xml b/tests/fixtures/the-phoenix-project-camel-case-1-v3.xml index 896cc9b..3af595b 100644 --- a/tests/fixtures/the-phoenix-project-camel-case-1-v3.xml +++ b/tests/fixtures/the-phoenix-project-camel-case-1-v3.xml @@ -30,4 +30,5 @@ Wednesday, September 3 + 9.8 \ No newline at end of file diff --git a/tests/fixtures/the-phoenix-project-camel-case-1-v4.xml b/tests/fixtures/the-phoenix-project-camel-case-1-v4.xml index 846f82c..c06f7ba 100644 --- a/tests/fixtures/the-phoenix-project-camel-case-1-v4.xml +++ b/tests/fixtures/the-phoenix-project-camel-case-1-v4.xml @@ -40,4 +40,5 @@ + 9.8 \ No newline at end of file diff --git a/tests/fixtures/the-phoenix-project-camel-case-1.xml b/tests/fixtures/the-phoenix-project-camel-case-1.xml index 4cb8003..e396285 100644 --- a/tests/fixtures/the-phoenix-project-camel-case-1.xml +++ b/tests/fixtures/the-phoenix-project-camel-case-1.xml @@ -29,4 +29,5 @@ Wednesday, September 3 + 9.8 \ No newline at end of file diff --git a/tests/fixtures/the-phoenix-project-camel-case-references.json b/tests/fixtures/the-phoenix-project-camel-case-references.json index 4eabd6f..08dfe0e 100644 --- a/tests/fixtures/the-phoenix-project-camel-case-references.json +++ b/tests/fixtures/the-phoenix-project-camel-case-references.json @@ -2,6 +2,7 @@ "id": "f3758bf0-0ff7-4366-a5e5-c209d4352b2d", "title": "{J} The Phoenix Project", "isbnNumber": "978-1942788294", + "rating": 9.8, "edition": { "number": 5, "name": "5th Anniversary Limited Edition" diff --git a/tests/fixtures/the-phoenix-project-camel-case-v2.json b/tests/fixtures/the-phoenix-project-camel-case-v2.json index 1eb94d0..efdfcf9 100644 --- a/tests/fixtures/the-phoenix-project-camel-case-v2.json +++ b/tests/fixtures/the-phoenix-project-camel-case-v2.json @@ -2,6 +2,7 @@ "id": "f3758bf0-0ff7-4366-a5e5-c209d4352b2d", "title": "{J} The Phoenix Project", "isbnNumber": "978-1942788294", + "rating": 9.8, "edition": { "number": 5, "name": "5th Anniversary Limited Edition" diff --git a/tests/fixtures/the-phoenix-project-camel-case-v3.json b/tests/fixtures/the-phoenix-project-camel-case-v3.json index baf8381..3eadc15 100644 --- a/tests/fixtures/the-phoenix-project-camel-case-v3.json +++ b/tests/fixtures/the-phoenix-project-camel-case-v3.json @@ -2,6 +2,7 @@ "id": "f3758bf0-0ff7-4366-a5e5-c209d4352b2d", "title": "{J} The Phoenix Project", "isbnNumber": "978-1942788294", + "rating": 9.8, "edition": { "number": 5, "name": "5th Anniversary Limited Edition" diff --git a/tests/fixtures/the-phoenix-project-camel-case-v4.json b/tests/fixtures/the-phoenix-project-camel-case-v4.json index 4eabd6f..08dfe0e 100644 --- a/tests/fixtures/the-phoenix-project-camel-case-v4.json +++ b/tests/fixtures/the-phoenix-project-camel-case-v4.json @@ -2,6 +2,7 @@ "id": "f3758bf0-0ff7-4366-a5e5-c209d4352b2d", "title": "{J} The Phoenix Project", "isbnNumber": "978-1942788294", + "rating": 9.8, "edition": { "number": 5, "name": "5th Anniversary Limited Edition" diff --git a/tests/fixtures/the-phoenix-project-camel-case-with-ignored.json b/tests/fixtures/the-phoenix-project-camel-case-with-ignored.json index c2a3bc2..103632e 100644 --- a/tests/fixtures/the-phoenix-project-camel-case-with-ignored.json +++ b/tests/fixtures/the-phoenix-project-camel-case-with-ignored.json @@ -1,6 +1,7 @@ { "something_to_be_ignored": "some_value", "title": "{J} The Phoenix Project", + "rating": 9.8, "isbnNumber": "978-1942788294", "edition": { "number": 5, diff --git a/tests/fixtures/the-phoenix-project-camel-case-with-ignored.xml b/tests/fixtures/the-phoenix-project-camel-case-with-ignored.xml index 017d98a..d0b8896 100644 --- a/tests/fixtures/the-phoenix-project-camel-case-with-ignored.xml +++ b/tests/fixtures/the-phoenix-project-camel-case-with-ignored.xml @@ -29,4 +29,5 @@ Wednesday, September 3 + 9.8 \ No newline at end of file diff --git a/tests/fixtures/the-phoenix-project-camel-case.json b/tests/fixtures/the-phoenix-project-camel-case.json index 4cd7880..4c5fe66 100644 --- a/tests/fixtures/the-phoenix-project-camel-case.json +++ b/tests/fixtures/the-phoenix-project-camel-case.json @@ -1,6 +1,7 @@ { "id": "f3758bf0-0ff7-4366-a5e5-c209d4352b2d", "title": "{J} The Phoenix Project", + "rating": 9.8, "isbnNumber": "978-1942788294", "edition": { "number": 5, diff --git a/tests/fixtures/the-phoenix-project-defaultNS-isset-v4.xml b/tests/fixtures/the-phoenix-project-defaultNS-isset-v4.xml index a02cb91..4b351ad 100644 --- a/tests/fixtures/the-phoenix-project-defaultNS-isset-v4.xml +++ b/tests/fixtures/the-phoenix-project-defaultNS-isset-v4.xml @@ -40,4 +40,5 @@ Wednesday, September 3 + 9.8 \ No newline at end of file diff --git a/tests/fixtures/the-phoenix-project-defaultNS-isset.SNAPSHOT.xml b/tests/fixtures/the-phoenix-project-defaultNS-isset.SNAPSHOT.xml index a3115c1..5965f20 100644 --- a/tests/fixtures/the-phoenix-project-defaultNS-isset.SNAPSHOT.xml +++ b/tests/fixtures/the-phoenix-project-defaultNS-isset.SNAPSHOT.xml @@ -1 +1 @@ -f3758bf0-0ff7-4366-a5e5-c209d4352b2d{X} The Phoenix Project5th Anniversary Limited Edition2018-04-16Karl RanseierfictionIT Revolution Press LLC1Tuesday, September 22Tuesday, September 23Tuesday, September 24Wednesday, September 3 \ No newline at end of file +f3758bf0-0ff7-4366-a5e5-c209d4352b2d{X} The Phoenix Project5th Anniversary Limited Edition2018-04-16Karl RanseierfictionIT Revolution Press LLC1Tuesday, September 22Tuesday, September 23Tuesday, September 24Wednesday, September 39.8 \ No newline at end of file diff --git a/tests/fixtures/the-phoenix-project-defaultNS-mixed-v4.xml b/tests/fixtures/the-phoenix-project-defaultNS-mixed-v4.xml index 80242e1..c51e2d8 100644 --- a/tests/fixtures/the-phoenix-project-defaultNS-mixed-v4.xml +++ b/tests/fixtures/the-phoenix-project-defaultNS-mixed-v4.xml @@ -41,4 +41,5 @@ Wednesday, September 3 + 9.8 \ No newline at end of file diff --git a/tests/fixtures/the-phoenix-project-defaultNS-unset-v4.xml b/tests/fixtures/the-phoenix-project-defaultNS-unset-v4.xml index 4d497ce..2ba1a97 100644 --- a/tests/fixtures/the-phoenix-project-defaultNS-unset-v4.xml +++ b/tests/fixtures/the-phoenix-project-defaultNS-unset-v4.xml @@ -40,4 +40,5 @@ Wednesday, September 3 + 9.8 \ No newline at end of file diff --git a/tests/fixtures/the-phoenix-project-defaultNS-unset.SNAPSHOT.xml b/tests/fixtures/the-phoenix-project-defaultNS-unset.SNAPSHOT.xml index dc4d923..90bc7e8 100644 --- a/tests/fixtures/the-phoenix-project-defaultNS-unset.SNAPSHOT.xml +++ b/tests/fixtures/the-phoenix-project-defaultNS-unset.SNAPSHOT.xml @@ -1 +1 @@ -f3758bf0-0ff7-4366-a5e5-c209d4352b2d{X} The Phoenix Project5th Anniversary Limited Edition2018-04-16Karl RanseierfictionIT Revolution Press LLC1Tuesday, September 22Tuesday, September 23Tuesday, September 24Wednesday, September 3 \ No newline at end of file +f3758bf0-0ff7-4366-a5e5-c209d4352b2d{X} The Phoenix Project5th Anniversary Limited Edition2018-04-16Karl RanseierfictionIT Revolution Press LLC1Tuesday, September 22Tuesday, September 23Tuesday, September 24Wednesday, September 39.8 \ No newline at end of file diff --git a/tests/fixtures/the-phoenix-project-kebab-case-1-v2.xml b/tests/fixtures/the-phoenix-project-kebab-case-1-v2.xml index 62c9b10..def407e 100644 --- a/tests/fixtures/the-phoenix-project-kebab-case-1-v2.xml +++ b/tests/fixtures/the-phoenix-project-kebab-case-1-v2.xml @@ -31,4 +31,5 @@ Wednesday, September 3 + 9.8 \ No newline at end of file diff --git a/tests/fixtures/the-phoenix-project-kebab-case-1.xml b/tests/fixtures/the-phoenix-project-kebab-case-1.xml index e001ae0..0ed1be9 100644 --- a/tests/fixtures/the-phoenix-project-kebab-case-1.xml +++ b/tests/fixtures/the-phoenix-project-kebab-case-1.xml @@ -29,4 +29,5 @@ Wednesday, September 3 + 9.8 \ No newline at end of file diff --git a/tests/fixtures/the-phoenix-project-kebab-case.json b/tests/fixtures/the-phoenix-project-kebab-case.json index 6194c75..8e05baf 100644 --- a/tests/fixtures/the-phoenix-project-kebab-case.json +++ b/tests/fixtures/the-phoenix-project-kebab-case.json @@ -2,6 +2,7 @@ "id": "f3758bf0-0ff7-4366-a5e5-c209d4352b2d", "title": "{J} The Phoenix Project", "isbn-number": "978-1942788294", + "rating": 9.8, "edition": { "number": 5, "name": "5th Anniversary Limited Edition" diff --git a/tests/fixtures/the-phoenix-project-snake-case-1.xml b/tests/fixtures/the-phoenix-project-snake-case-1.xml index 76711aa..48ef17d 100644 --- a/tests/fixtures/the-phoenix-project-snake-case-1.xml +++ b/tests/fixtures/the-phoenix-project-snake-case-1.xml @@ -29,4 +29,5 @@ Wednesday, September 3 + 9.8 \ No newline at end of file diff --git a/tests/fixtures/the-phoenix-project-snake-case.json b/tests/fixtures/the-phoenix-project-snake-case.json index 6403bb5..35e2d6e 100644 --- a/tests/fixtures/the-phoenix-project-snake-case.json +++ b/tests/fixtures/the-phoenix-project-snake-case.json @@ -1,6 +1,7 @@ { "id": "f3758bf0-0ff7-4366-a5e5-c209d4352b2d", "title": "{J} The Phoenix Project", + "rating": 9.8, "isbn_number": "978-1942788294", "edition": { "number": 5, diff --git a/tests/model.py b/tests/model.py index 141fed6..d8cd537 100644 --- a/tests/model.py +++ b/tests/model.py @@ -19,6 +19,7 @@ import re from datetime import date +from decimal import Decimal from enum import Enum, unique from typing import Any, Dict, Iterable, List, Optional, Set, Type from uuid import UUID, uuid4 @@ -232,7 +233,8 @@ 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, references: Optional[Iterable[BookReference]] = None) -> None: + id: Optional[UUID] = None, references: Optional[Iterable[BookReference]] = None, + rating: Optional[Decimal] = None) -> None: self._id = id or uuid4() self._title = title self._isbn = isbn @@ -243,6 +245,7 @@ def __init__(self, title: str, isbn: str, publish_date: date, authors: Iterable[ self.chapters = list(chapters or []) self._type = type self.references = set(references or []) + self.rating = Decimal('NaN') if rating is None else rating @property @serializable.xml_sequence(1) @@ -310,13 +313,23 @@ def references(self) -> Set[BookReference]: def references(self, references: Iterable[BookReference]) -> None: self._references = set(references) + @property + @serializable.xml_sequence(20) + def rating(self) -> Decimal: + return self._rating + + @rating.setter + def rating(self, rating: Decimal) -> None: + self._rating = rating + ThePhoenixProject_v1 = Book( title='The Phoenix Project', isbn='978-1942788294', publish_date=date(year=2018, month=4, day=16), authors=['Gene Kim', 'Kevin Behr', 'George Spafford'], publisher=Publisher(name='IT Revolution Press LLC'), edition=BookEdition(number=5, name='5th Anniversary Limited Edition'), - id=UUID('f3758bf0-0ff7-4366-a5e5-c209d4352b2d') + id=UUID('f3758bf0-0ff7-4366-a5e5-c209d4352b2d'), + rating=Decimal('9.8') ) ThePhoenixProject_v1.chapters.append(Chapter(number=1, title='Tuesday, September 2')) @@ -329,7 +342,8 @@ def references(self, references: Iterable[BookReference]) -> None: authors=['Gene Kim', 'Kevin Behr', 'George Spafford'], publisher=Publisher(name='IT Revolution Press LLC', address='10 Downing Street'), edition=BookEdition(number=5, name='5th Anniversary Limited Edition'), - id=UUID('f3758bf0-0ff7-4366-a5e5-c209d4352b2d') + id=UUID('f3758bf0-0ff7-4366-a5e5-c209d4352b2d'), + rating=Decimal('9.8') ) ThePhoenixProject_v2.chapters.append(Chapter(number=1, title='Tuesday, September 2')) diff --git a/tests/test_json.py b/tests/test_json.py index f45a5e3..a23549f 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -64,6 +64,7 @@ def test_deserialize_tfp_cc(self) -> None: self.assertEqual(ThePhoenixProject_v1.publisher, book.publisher) self.assertEqual(ThePhoenixProject_v1.chapters, book.chapters) self.assertEqual(ThePhoenixProject_v1.references, book.references) + self.assertEqual(ThePhoenixProject_v1.rating, book.rating) def test_deserialize_tfp_cc_with_references(self) -> None: CurrentFormatter.formatter = CamelCasePropertyNameFormatter @@ -79,6 +80,7 @@ def test_deserialize_tfp_cc_with_references(self) -> None: self.assertEqual(ThePhoenixProject.chapters, book.chapters) self.assertEqual(3, len(book.references)) self.assertEqual(ThePhoenixProject.references, book.references) + self.assertEqual(ThePhoenixProject.rating, book.rating) def test_deserialize_tfp_cc_with_ignored(self) -> None: CurrentFormatter.formatter = CamelCasePropertyNameFormatter @@ -91,6 +93,7 @@ def test_deserialize_tfp_cc_with_ignored(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.rating, book.rating) def test_serialize_tfp_kc(self) -> None: CurrentFormatter.formatter = KebabCasePropertyNameFormatter @@ -108,6 +111,7 @@ def test_deserialize_tfp_kc(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.rating, book.rating) def test_serialize_tfp_sc(self) -> None: CurrentFormatter.formatter = SnakeCasePropertyNameFormatter @@ -125,3 +129,4 @@ def test_deserialize_tfp_sc(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.rating, book.rating) diff --git a/tests/test_xml.py b/tests/test_xml.py index 6af0172..185ffa5 100644 --- a/tests/test_xml.py +++ b/tests/test_xml.py @@ -75,7 +75,7 @@ def test_serialize_tfp_kc1_v2(self) -> None: def test_serialize_tfp_sc1(self) -> None: CurrentFormatter.formatter = SnakeCasePropertyNameFormatter - with open(os.path.join(FIXTURES_DIRECTORY, 'the-phoenix-project-snake-case-1.xml')) as expected_xml: + with open(os.path.join(FIXTURES_DIRECTORY, 'the-phoenix-project-snake-case-1.xml'), 'r') as expected_xml: self.assertEqualXml(expected_xml.read(), ThePhoenixProject.as_xml()) def test_serializable_no_defaultNS(self) -> None: @@ -130,6 +130,7 @@ def test_deserialize_tfp_cc1(self) -> None: self.assertEqual(ThePhoenixProject_v1.publisher, book.publisher) self.assertEqual(ThePhoenixProject_v1.authors, book.authors) self.assertEqual(ThePhoenixProject_v1.chapters, book.chapters) + self.assertEqual(ThePhoenixProject_v1.rating, book.rating) def test_deserialize_tfp_cc1_v2(self) -> None: CurrentFormatter.formatter = CamelCasePropertyNameFormatter @@ -144,6 +145,7 @@ def test_deserialize_tfp_cc1_v2(self) -> None: self.assertEqual(ThePhoenixProject.authors, book.authors) self.assertEqual(ThePhoenixProject.chapters, book.chapters) self.assertSetEqual(set(), book.references) + self.assertEqual(ThePhoenixProject.rating, book.rating) def test_deserialize_tfp_cc1_v3(self) -> None: CurrentFormatter.formatter = CamelCasePropertyNameFormatter @@ -158,6 +160,7 @@ def test_deserialize_tfp_cc1_v3(self) -> None: self.assertEqual(ThePhoenixProject_v1.authors, book.authors) self.assertEqual(ThePhoenixProject_v1.chapters, book.chapters) self.assertEqual(ThePhoenixProject_v1.references, book.references) + self.assertEqual(ThePhoenixProject_v1.rating, book.rating) def test_deserialize_tfp_cc1_v4(self) -> None: CurrentFormatter.formatter = CamelCasePropertyNameFormatter @@ -172,6 +175,7 @@ def test_deserialize_tfp_cc1_v4(self) -> None: self.assertEqual(ThePhoenixProject.authors, book.authors) self.assertEqual(ThePhoenixProject.chapters, book.chapters) self.assertEqual(ThePhoenixProject.references, book.references) + self.assertEqual(ThePhoenixProject.rating, book.rating) def test_deserialize_tfp_cc1_with_ignored(self) -> None: CurrentFormatter.formatter = CamelCasePropertyNameFormatter @@ -184,6 +188,7 @@ def test_deserialize_tfp_cc1_with_ignored(self) -> None: self.assertEqual(ThePhoenixProject_v1.publisher, book.publisher) self.assertEqual(ThePhoenixProject_v1.authors, book.authors) self.assertEqual(ThePhoenixProject_v1.chapters, book.chapters) + self.assertEqual(ThePhoenixProject_v1.rating, book.rating) def test_deserialize_tfp_kc1(self) -> None: CurrentFormatter.formatter = KebabCasePropertyNameFormatter @@ -196,6 +201,7 @@ def test_deserialize_tfp_kc1(self) -> None: self.assertEqual(ThePhoenixProject_v1.publisher, book.publisher) self.assertEqual(ThePhoenixProject_v1.authors, book.authors) self.assertEqual(ThePhoenixProject_v1.chapters, book.chapters) + self.assertEqual(ThePhoenixProject_v1.rating, book.rating) def test_deserialize_tfp_sc1(self) -> None: CurrentFormatter.formatter = SnakeCasePropertyNameFormatter @@ -208,6 +214,7 @@ def test_deserialize_tfp_sc1(self) -> None: self.assertEqual(ThePhoenixProject_v1.publisher, book.publisher) self.assertEqual(ThePhoenixProject_v1.authors, book.authors) self.assertEqual(ThePhoenixProject_v1.chapters, book.chapters) + self.assertEqual(ThePhoenixProject_v1.rating, book.rating) def test_deserializable_with_defaultNS(self) -> None: """regression test for https://github.com/madpah/serializable/issues/11"""