Examples

Models used in Unit Tests

  1# encoding: utf-8
  2
  3# This file is part of py-serializable
  4#
  5# Licensed under the Apache License, Version 2.0 (the "License");
  6# you may not use this file except in compliance with the License.
  7# You may obtain a copy of the License at
  8#
  9#     http://www.apache.org/licenses/LICENSE-2.0
 10#
 11# Unless required by applicable law or agreed to in writing, software
 12# distributed under the License is distributed on an "AS IS" BASIS,
 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14# See the License for the specific language governing permissions and
 15# limitations under the License.
 16#
 17# SPDX-License-Identifier: Apache-2.0
 18# Copyright (c) Paul Horton. All Rights Reserved.
 19
 20import re
 21from datetime import date
 22from decimal import Decimal
 23from enum import Enum, unique
 24from typing import Any, Dict, Iterable, List, Optional, Set, Type
 25from uuid import UUID, uuid4
 26
 27import serializable
 28from serializable import ViewType, XmlArraySerializationType
 29from serializable.helpers import BaseHelper, Iso8601Date
 30
 31"""
 32Model classes used in unit tests and examples.
 33"""
 34
 35
 36class SchemaVersion1(ViewType):
 37    pass
 38
 39
 40class SchemaVersion2(ViewType):
 41    pass
 42
 43
 44class SchemaVersion3(ViewType):
 45    pass
 46
 47
 48class SchemaVersion4(ViewType):
 49    pass
 50
 51
 52SCHEMAVERSION_MAP: Dict[int, Type[ViewType]] = {
 53    1: SchemaVersion1,
 54    2: SchemaVersion2,
 55    3: SchemaVersion3,
 56    4: SchemaVersion4,
 57}
 58
 59
 60class ReferenceReferences(BaseHelper):
 61
 62    @classmethod
 63    def serialize(cls, o: Any) -> Set[str]:
 64        if isinstance(o, set):
 65            return set(map(lambda i: str(i.ref), o))
 66
 67        raise ValueError(f'Attempt to serialize a non-set: {o.__class__}')
 68
 69    @classmethod
 70    def deserialize(cls, o: Any) -> Set['BookReference']:
 71        print(f'Deserializing {o} ({type(o)})')
 72        references: Set['BookReference'] = set()
 73        if isinstance(o, list):
 74            for v in o:
 75                references.add(BookReference(ref=v))
 76            return references
 77
 78        raise ValueError(f'Attempt to deserialize a non-set: {o.__class__}')
 79
 80
 81class TitleMapper(BaseHelper):
 82
 83    @classmethod
 84    def json_serialize(cls, o: str) -> str:
 85        return f'{{J}} {o}'
 86
 87    @classmethod
 88    def json_deserialize(cls, o: str) -> str:
 89        return re.sub(r'^\{J} ', '', o)
 90
 91    @classmethod
 92    def xml_serialize(cls, o: str) -> str:
 93        return f'{{X}} {o}'
 94
 95    @classmethod
 96    def xml_deserialize(cls, o: str) -> str:
 97        return re.sub(r'^\{X} ', '', o)
 98
 99
100@serializable.serializable_class
101class Chapter:
102
103    def __init__(self, *, number: int, title: str) -> None:
104        self._number = number
105        self._title = title
106
107    @property
108    def number(self) -> int:
109        return self._number
110
111    @property
112    def title(self) -> str:
113        return self._title
114
115    def __eq__(self, other: Any) -> bool:
116        if isinstance(other, Chapter):
117            return hash(other) == hash(self)
118        return False
119
120    def __hash__(self) -> int:
121        return hash((self.number, self.title))
122
123
124@serializable.serializable_class
125class Publisher:
126
127    def __init__(self, *, name: str, address: Optional[str] = None, email: Optional[str] = None) -> None:
128        self._name = name
129        self._address = address
130        self._email = email
131
132    @property
133    def name(self) -> str:
134        return self._name
135
136    @property
137    @serializable.view(SchemaVersion2)
138    @serializable.view(SchemaVersion4)
139    def address(self) -> Optional[str]:
140        return self._address
141
142    @property
143    @serializable.include_none(SchemaVersion2)
144    @serializable.include_none(SchemaVersion3, 'RUBBISH')
145    def email(self) -> Optional[str]:
146        return self._email
147
148    def __eq__(self, other: object) -> bool:
149        if isinstance(other, Publisher):
150            return hash(other) == hash(self)
151        return False
152
153    def __hash__(self) -> int:
154        return hash((self.name, self.address, self.email))
155
156
157@unique
158class BookType(Enum):
159    FICTION = 'fiction'
160    NON_FICTION = 'non-fiction'
161
162
163@serializable.serializable_class(name='edition')
164class BookEdition:
165
166    def __init__(self, *, number: int, name: str) -> None:
167        self._number = number
168        self._name = name
169
170    @property
171    @serializable.xml_attribute()
172    def number(self) -> int:
173        return self._number
174
175    @property
176    @serializable.xml_name('.')
177    def name(self) -> str:
178        return self._name
179
180    def __eq__(self, other: object) -> bool:
181        if isinstance(other, BookEdition):
182            return hash(other) == hash(self)
183        return False
184
185    def __hash__(self) -> int:
186        return hash((self.number, self.name))
187
188
189@serializable.serializable_class
190class BookReference:
191
192    def __init__(self, *, ref: str, references: Optional[Iterable['BookReference']] = None) -> None:
193        self.ref = ref
194        self.references = set(references or {})
195
196    @property
197    @serializable.json_name('reference')
198    @serializable.xml_attribute()
199    def ref(self) -> str:
200        return self._ref
201
202    @ref.setter
203    def ref(self, ref: str) -> None:
204        self._ref = ref
205
206    @property
207    @serializable.json_name('refersTo')
208    @serializable.type_mapping(ReferenceReferences)
209    @serializable.xml_array(serializable.XmlArraySerializationType.FLAT, 'reference')
210    def references(self) -> Set['BookReference']:
211        return self._references
212
213    @references.setter
214    def references(self, references: Iterable['BookReference']) -> None:
215        self._references = set(references)
216
217    def __eq__(self, other: object) -> bool:
218        if isinstance(other, BookReference):
219            return hash(other) == hash(self)
220        return False
221
222    def __hash__(self) -> int:
223        return hash((self.ref, tuple(self.references)))
224
225    def __repr__(self) -> str:
226        return f'<BookReference ref={self.ref}, targets={len(self.references)}>'
227
228
229@serializable.serializable_class(name='bigbook',
230                                 ignore_during_deserialization=['something_to_be_ignored', 'ignore_me', 'ignored'])
231class Book:
232
233    def __init__(self, title: str, isbn: str, publish_date: date, authors: Iterable[str],
234                 publisher: Optional[Publisher] = None, chapters: Optional[Iterable[Chapter]] = None,
235                 edition: Optional[BookEdition] = None, type: BookType = BookType.FICTION,
236                 id: Optional[UUID] = None, references: Optional[Iterable[BookReference]] = None,
237                 rating: Optional[Decimal] = None) -> None:
238        self._id = id or uuid4()
239        self._title = title
240        self._isbn = isbn
241        self._edition = edition
242        self._publish_date = publish_date
243        self._authors = set(authors)
244        self._publisher = publisher
245        self.chapters = list(chapters or [])
246        self._type = type
247        self.references = set(references or [])
248        self.rating = Decimal('NaN') if rating is None else rating
249
250    @property
251    @serializable.xml_sequence(1)
252    def id(self) -> UUID:
253        return self._id
254
255    @property
256    @serializable.xml_sequence(2)
257    @serializable.type_mapping(TitleMapper)
258    def title(self) -> str:
259        return self._title
260
261    @property
262    @serializable.json_name('isbn_number')
263    @serializable.xml_attribute()
264    @serializable.xml_name('isbn_number')
265    def isbn(self) -> str:
266        return self._isbn
267
268    @property
269    @serializable.xml_sequence(3)
270    def edition(self) -> Optional[BookEdition]:
271        return self._edition
272
273    @property
274    @serializable.xml_sequence(4)
275    @serializable.type_mapping(Iso8601Date)
276    def publish_date(self) -> date:
277        return self._publish_date
278
279    @property
280    @serializable.xml_array(XmlArraySerializationType.FLAT, 'author')
281    @serializable.xml_sequence(5)
282    def authors(self) -> Set[str]:
283        return self._authors
284
285    @property
286    @serializable.xml_sequence(7)
287    def publisher(self) -> Optional[Publisher]:
288        return self._publisher
289
290    @property
291    @serializable.xml_array(XmlArraySerializationType.NESTED, 'chapter')
292    @serializable.xml_sequence(8)
293    def chapters(self) -> List[Chapter]:
294        return self._chapters
295
296    @chapters.setter
297    def chapters(self, chapters: Iterable[Chapter]) -> None:
298        self._chapters = list(chapters)
299
300    @property
301    @serializable.xml_sequence(6)
302    def type(self) -> BookType:
303        return self._type
304
305    @property
306    @serializable.view(SchemaVersion4)
307    @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'reference')
308    @serializable.xml_sequence(7)
309    def references(self) -> Set[BookReference]:
310        return self._references
311
312    @references.setter
313    def references(self, references: Iterable[BookReference]) -> None:
314        self._references = set(references)
315
316    @property
317    @serializable.xml_sequence(20)
318    def rating(self) -> Decimal:
319        return self._rating
320
321    @rating.setter
322    def rating(self, rating: Decimal) -> None:
323        self._rating = rating
324
325
326ThePhoenixProject_v1 = Book(
327    title='The Phoenix Project', isbn='978-1942788294', publish_date=date(year=2018, month=4, day=16),
328    authors=['Gene Kim', 'Kevin Behr', 'George Spafford'],
329    publisher=Publisher(name='IT Revolution Press LLC'),
330    edition=BookEdition(number=5, name='5th Anniversary Limited Edition'),
331    id=UUID('f3758bf0-0ff7-4366-a5e5-c209d4352b2d'),
332    rating=Decimal('9.8')
333)
334
335ThePhoenixProject_v1.chapters.append(Chapter(number=1, title='Tuesday, September 2'))
336ThePhoenixProject_v1.chapters.append(Chapter(number=2, title='Tuesday, September 2'))
337ThePhoenixProject_v1.chapters.append(Chapter(number=3, title='Tuesday, September 2'))
338ThePhoenixProject_v1.chapters.append(Chapter(number=4, title='Wednesday, September 3'))
339
340ThePhoenixProject_v2 = Book(
341    title='The Phoenix Project', isbn='978-1942788294', publish_date=date(year=2018, month=4, day=16),
342    authors=['Gene Kim', 'Kevin Behr', 'George Spafford'],
343    publisher=Publisher(name='IT Revolution Press LLC', address='10 Downing Street'),
344    edition=BookEdition(number=5, name='5th Anniversary Limited Edition'),
345    id=UUID('f3758bf0-0ff7-4366-a5e5-c209d4352b2d'),
346    rating=Decimal('9.8')
347)
348
349ThePhoenixProject_v2.chapters.append(Chapter(number=1, title='Tuesday, September 2'))
350ThePhoenixProject_v2.chapters.append(Chapter(number=2, title='Tuesday, September 2'))
351ThePhoenixProject_v2.chapters.append(Chapter(number=3, title='Tuesday, September 2'))
352ThePhoenixProject_v2.chapters.append(Chapter(number=4, title='Wednesday, September 3'))
353
354SubRef1 = BookReference(ref='sub-ref-1')
355SubRef2 = BookReference(ref='sub-ref-2')
356SubRef3 = BookReference(ref='sub-ref-3')
357
358Ref1 = BookReference(ref='my-ref-1')
359Ref2 = BookReference(ref='my-ref-2', references=[SubRef1, SubRef3])
360Ref3 = BookReference(ref='my-ref-3', references=[SubRef2])
361
362ThePhoenixProject_v2.references = {Ref3, Ref2, Ref1}
363
364ThePhoenixProject = ThePhoenixProject_v2
365
366if __name__ == '__main__':
367    tpp_as_xml = ThePhoenixProject.as_xml()  # type:ignore[attr-defined]
368    tpp_as_json = ThePhoenixProject.as_json()  # type:ignore[attr-defined]
369    print(tpp_as_xml, tpp_as_json, sep='\n\n')
370
371    import io
372    import json
373    tpp_from_xml = ThePhoenixProject.from_xml(  # type:ignore[attr-defined]
374        io.StringIO(tpp_as_xml))
375    tpp_from_json = ThePhoenixProject.from_json(  # type:ignore[attr-defined]
376        json.loads(tpp_as_json))

Logging and log access

This library utilizes an own instance of Logger, which you may access and add handlers to.

Example: send all logs messages to stdErr
import sys
import logging
import serializable

my_log_handler = logging.StreamHandler(sys.stderr)
my_log_handler.setLevel(logging.DEBUG)
my_log_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
serializable.logger.addHandler(my_log_handler)
serializable.logger.setLevel(my_log_handler.level)
serializable.logger.propagate = False

@serializable.serializable_class
class Chapter:

   def __init__(self, *, number: int, title: str) -> None:
     self._number = number
     self._title = title

   @property
   def number(self) -> int:
     return self._number

   @property
   def title(self) -> str:
     return self._title


moby_dick_c1 = Chapter(number=1, title='Loomings')
print(moby_dick_c1.as_json())