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
230class StockId(serializable.helpers.BaseHelper):
231
232    def __init__(self, id: str) -> None:
233        self._id = id
234
235    @property
236    @serializable.json_name('.')
237    @serializable.xml_name('.')
238    def id(self) -> str:
239        return self._id
240
241    @classmethod
242    def serialize(cls, o: Any) -> str:
243        if isinstance(o, StockId):
244            return str(o)
245        raise Exception(
246            f'Attempt to serialize a non-StockId: {o!r}')
247
248    @classmethod
249    def deserialize(cls, o: Any) -> 'StockId':
250        try:
251            return StockId(id=str(o))
252        except ValueError as err:
253            raise Exception(
254                f'StockId string supplied does not parse: {o!r}'
255            ) from err
256
257    def __eq__(self, other: Any) -> bool:
258        if isinstance(other, StockId):
259            return hash(other) == hash(self)
260        return False
261
262    def __lt__(self, other: Any) -> bool:
263        if isinstance(other, StockId):
264            return self._id < other._id
265        return NotImplemented
266
267    def __hash__(self) -> int:
268        return hash(self._id)
269
270    def __repr__(self) -> str:
271        return f'<StockId {self._id}>'
272
273    def __str__(self) -> str:
274        return self._id
275
276
277@serializable.serializable_class(name='bigbook',
278                                 ignore_during_deserialization=['something_to_be_ignored', 'ignore_me', 'ignored'])
279class Book:
280
281    def __init__(self, title: str, isbn: str, publish_date: date, authors: Iterable[str],
282                 publisher: Optional[Publisher] = None, chapters: Optional[Iterable[Chapter]] = None,
283                 edition: Optional[BookEdition] = None, type: BookType = BookType.FICTION,
284                 id: Optional[UUID] = None, references: Optional[Iterable[BookReference]] = None,
285                 rating: Optional[Decimal] = None, stock_ids: Optional[Iterable[StockId]] = None) -> None:
286        self._id = id or uuid4()
287        self._title = title
288        self._isbn = isbn
289        self._edition = edition
290        self._publish_date = publish_date
291        self._authors = set(authors)
292        self._publisher = publisher
293        self.chapters = list(chapters or [])
294        self._type = type
295        self.references = set(references or [])
296        self.rating = Decimal('NaN') if rating is None else rating
297        self._stock_ids = set(stock_ids or [])
298
299    @property
300    @serializable.xml_sequence(1)
301    def id(self) -> UUID:
302        return self._id
303
304    @property
305    @serializable.xml_sequence(2)
306    @serializable.type_mapping(TitleMapper)
307    def title(self) -> str:
308        return self._title
309
310    @property
311    @serializable.json_name('isbn_number')
312    @serializable.xml_attribute()
313    @serializable.xml_name('isbn_number')
314    def isbn(self) -> str:
315        return self._isbn
316
317    @property
318    @serializable.xml_sequence(3)
319    def edition(self) -> Optional[BookEdition]:
320        return self._edition
321
322    @property
323    @serializable.xml_sequence(4)
324    @serializable.type_mapping(Iso8601Date)
325    def publish_date(self) -> date:
326        return self._publish_date
327
328    @property
329    @serializable.xml_array(XmlArraySerializationType.FLAT, 'author')
330    @serializable.xml_sequence(5)
331    def authors(self) -> Set[str]:
332        return self._authors
333
334    @property
335    @serializable.xml_sequence(7)
336    def publisher(self) -> Optional[Publisher]:
337        return self._publisher
338
339    @property
340    @serializable.xml_array(XmlArraySerializationType.NESTED, 'chapter')
341    @serializable.xml_sequence(8)
342    def chapters(self) -> List[Chapter]:
343        return self._chapters
344
345    @chapters.setter
346    def chapters(self, chapters: Iterable[Chapter]) -> None:
347        self._chapters = list(chapters)
348
349    @property
350    @serializable.xml_sequence(6)
351    def type(self) -> BookType:
352        return self._type
353
354    @property
355    @serializable.view(SchemaVersion4)
356    @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'reference')
357    @serializable.xml_sequence(7)
358    def references(self) -> Set[BookReference]:
359        return self._references
360
361    @references.setter
362    def references(self, references: Iterable[BookReference]) -> None:
363        self._references = set(references)
364
365    @property
366    @serializable.xml_sequence(20)
367    def rating(self) -> Decimal:
368        return self._rating
369
370    @rating.setter
371    def rating(self, rating: Decimal) -> None:
372        self._rating = rating
373
374    @property
375    @serializable.view(SchemaVersion4)
376    @serializable.xml_array(XmlArraySerializationType.FLAT, 'stockId')
377    @serializable.xml_sequence(21)
378    def stock_ids(self) -> Set[StockId]:
379        return self._stock_ids
380
381
382ThePhoenixProject_v1 = Book(
383    title='The Phoenix Project', isbn='978-1942788294', publish_date=date(year=2018, month=4, day=16),
384    authors=['Gene Kim', 'Kevin Behr', 'George Spafford'],
385    publisher=Publisher(name='IT Revolution Press LLC'),
386    edition=BookEdition(number=5, name='5th Anniversary Limited Edition'),
387    id=UUID('f3758bf0-0ff7-4366-a5e5-c209d4352b2d'),
388    rating=Decimal('9.8')
389)
390
391ThePhoenixProject_v1.chapters.append(Chapter(number=1, title='Tuesday, September 2'))
392ThePhoenixProject_v1.chapters.append(Chapter(number=2, title='Tuesday, September 2'))
393ThePhoenixProject_v1.chapters.append(Chapter(number=3, title='Tuesday, September 2'))
394ThePhoenixProject_v1.chapters.append(Chapter(number=4, title='Wednesday, September 3'))
395
396ThePhoenixProject_v2 = Book(
397    title='The Phoenix Project', isbn='978-1942788294', publish_date=date(year=2018, month=4, day=16),
398    authors=['Gene Kim', 'Kevin Behr', 'George Spafford'],
399    publisher=Publisher(name='IT Revolution Press LLC', address='10 Downing Street'),
400    edition=BookEdition(number=5, name='5th Anniversary Limited Edition'),
401    id=UUID('f3758bf0-0ff7-4366-a5e5-c209d4352b2d'),
402    rating=Decimal('9.8'),
403    stock_ids=[StockId('stock-id-1'), StockId('stock-id-2')]
404)
405
406ThePhoenixProject_v2.chapters.append(Chapter(number=1, title='Tuesday, September 2'))
407ThePhoenixProject_v2.chapters.append(Chapter(number=2, title='Tuesday, September 2'))
408ThePhoenixProject_v2.chapters.append(Chapter(number=3, title='Tuesday, September 2'))
409ThePhoenixProject_v2.chapters.append(Chapter(number=4, title='Wednesday, September 3'))
410
411SubRef1 = BookReference(ref='sub-ref-1')
412SubRef2 = BookReference(ref='sub-ref-2')
413SubRef3 = BookReference(ref='sub-ref-3')
414
415Ref1 = BookReference(ref='my-ref-1')
416Ref2 = BookReference(ref='my-ref-2', references=[SubRef1, SubRef3])
417Ref3 = BookReference(ref='my-ref-3', references=[SubRef2])
418
419ThePhoenixProject_v2.references = {Ref3, Ref2, Ref1}
420
421ThePhoenixProject = ThePhoenixProject_v2
422
423if __name__ == '__main__':
424    tpp_as_xml = ThePhoenixProject.as_xml()  # type:ignore[attr-defined]
425    tpp_as_json = ThePhoenixProject.as_json()  # type:ignore[attr-defined]
426    print(tpp_as_xml, tpp_as_json, sep='\n\n')
427
428    import io
429    import json
430
431    tpp_from_xml = ThePhoenixProject.from_xml(  # type:ignore[attr-defined]
432        io.StringIO(tpp_as_xml))
433    tpp_from_json = ThePhoenixProject.from_json(  # type:ignore[attr-defined]
434        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())