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 py_serializable
28from py_serializable import ViewType, XmlArraySerializationType, XmlStringSerializationType
29from py_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
100class BookEditionHelper(BaseHelper):
101
102 @classmethod
103 def serialize(cls, o: Any) -> Optional[int]:
104 return o \
105 if isinstance(o, int) and o > 0 \
106 else None
107
108 @classmethod
109 def deserialize(cls, o: Any) -> int:
110 try:
111 return int(o)
112 except Exception:
113 return 1
114
115
116@py_serializable.serializable_class
117class Chapter:
118
119 def __init__(self, *, number: int, title: str) -> None:
120 self._number = number
121 self._title = title
122
123 @property
124 def number(self) -> int:
125 return self._number
126
127 @property
128 @py_serializable.xml_string(XmlStringSerializationType.TOKEN)
129 def title(self) -> str:
130 return self._title
131
132 def __eq__(self, other: Any) -> bool:
133 if isinstance(other, Chapter):
134 return hash(other) == hash(self)
135 return False
136
137 def __hash__(self) -> int:
138 return hash((self.number, self.title))
139
140
141@py_serializable.serializable_class
142class Publisher:
143
144 def __init__(self, *, name: str, address: Optional[str] = None, email: Optional[str] = None) -> None:
145 self._name = name
146 self._address = address
147 self._email = email
148
149 @property
150 def name(self) -> str:
151 return self._name
152
153 @property
154 @py_serializable.view(SchemaVersion2)
155 @py_serializable.view(SchemaVersion4)
156 def address(self) -> Optional[str]:
157 return self._address
158
159 @property
160 @py_serializable.include_none(SchemaVersion2)
161 @py_serializable.include_none(SchemaVersion3, 'RUBBISH')
162 def email(self) -> Optional[str]:
163 return self._email
164
165 def __eq__(self, other: object) -> bool:
166 if isinstance(other, Publisher):
167 return hash(other) == hash(self)
168 return False
169
170 def __hash__(self) -> int:
171 return hash((self.name, self.address, self.email))
172
173
174@unique
175class BookType(Enum):
176 FICTION = 'fiction'
177 NON_FICTION = 'non-fiction'
178
179
180@py_serializable.serializable_class(name='edition')
181class BookEdition:
182
183 def __init__(self, *, number: int, name: str) -> None:
184 self._number = number
185 self._name = name
186
187 @property
188 @py_serializable.xml_attribute()
189 @py_serializable.type_mapping(BookEditionHelper)
190 def number(self) -> int:
191 return self._number
192
193 @property
194 @py_serializable.xml_name('.')
195 def name(self) -> str:
196 return self._name
197
198 def __eq__(self, other: object) -> bool:
199 if isinstance(other, BookEdition):
200 return hash(other) == hash(self)
201 return False
202
203 def __hash__(self) -> int:
204 return hash((self.number, self.name))
205
206
207@py_serializable.serializable_class
208class BookReference:
209
210 def __init__(self, *, ref: str, references: Optional[Iterable['BookReference']] = None) -> None:
211 self.ref = ref
212 self.references = set(references or {})
213
214 @property
215 @py_serializable.json_name('reference')
216 @py_serializable.xml_attribute()
217 @py_serializable.xml_string(XmlStringSerializationType.TOKEN)
218 def ref(self) -> str:
219 return self._ref
220
221 @ref.setter
222 def ref(self, ref: str) -> None:
223 self._ref = ref
224
225 @property
226 @py_serializable.json_name('refersTo')
227 @py_serializable.type_mapping(ReferenceReferences)
228 @py_serializable.xml_array(py_serializable.XmlArraySerializationType.FLAT, 'reference')
229 def references(self) -> Set['BookReference']:
230 return self._references
231
232 @references.setter
233 def references(self, references: Iterable['BookReference']) -> None:
234 self._references = set(references)
235
236 def __eq__(self, other: object) -> bool:
237 if isinstance(other, BookReference):
238 return hash(other) == hash(self)
239 return False
240
241 def __hash__(self) -> int:
242 return hash((self.ref, tuple(self.references)))
243
244 def __repr__(self) -> str:
245 return f'<BookReference ref={self.ref}, targets={len(self.references)}>'
246
247
248@py_serializable.serializable_class
249class StockId(py_serializable.helpers.BaseHelper):
250
251 def __init__(self, id: str) -> None:
252 self._id = id
253
254 @property
255 @py_serializable.json_name('.')
256 @py_serializable.xml_name('.')
257 def id(self) -> str:
258 return self._id
259
260 @classmethod
261 def serialize(cls, o: Any) -> str:
262 if isinstance(o, StockId):
263 return str(o)
264 raise Exception(
265 f'Attempt to serialize a non-StockId: {o!r}')
266
267 @classmethod
268 def deserialize(cls, o: Any) -> 'StockId':
269 try:
270 return StockId(id=str(o))
271 except ValueError as err:
272 raise Exception(
273 f'StockId string supplied does not parse: {o!r}'
274 ) from err
275
276 def __eq__(self, other: Any) -> bool:
277 if isinstance(other, StockId):
278 return hash(other) == hash(self)
279 return False
280
281 def __lt__(self, other: Any) -> bool:
282 if isinstance(other, StockId):
283 return self._id < other._id
284 return NotImplemented
285
286 def __hash__(self) -> int:
287 return hash(self._id)
288
289 def __repr__(self) -> str:
290 return f'<StockId {self._id}>'
291
292 def __str__(self) -> str:
293 return self._id
294
295
296@py_serializable.serializable_class(name='bigbook',
297 ignore_during_deserialization=['something_to_be_ignored', 'ignore_me', 'ignored'])
298class Book:
299
300 def __init__(self, title: str, isbn: str, publish_date: date, authors: Iterable[str],
301 publisher: Optional[Publisher] = None, chapters: Optional[Iterable[Chapter]] = None,
302 edition: Optional[BookEdition] = None, type: BookType = BookType.FICTION,
303 id: Optional[UUID] = None, references: Optional[Iterable[BookReference]] = None,
304 rating: Optional[Decimal] = None, stock_ids: Optional[Iterable[StockId]] = None) -> None:
305 self._id = id or uuid4()
306 self._title = title
307 self._isbn = isbn
308 self._edition = edition
309 self._publish_date = publish_date
310 self._authors = set(authors)
311 self._publisher = publisher
312 self.chapters = list(chapters or [])
313 self._type = type
314 self.references = set(references or [])
315 self.rating = Decimal('NaN') if rating is None else rating
316 self._stock_ids = set(stock_ids or [])
317
318 @property
319 @py_serializable.xml_sequence(1)
320 def id(self) -> UUID:
321 return self._id
322
323 @property
324 @py_serializable.xml_sequence(2)
325 @py_serializable.type_mapping(TitleMapper)
326 @py_serializable.xml_string(XmlStringSerializationType.TOKEN)
327 def title(self) -> str:
328 return self._title
329
330 @property
331 @py_serializable.json_name('isbn_number')
332 @py_serializable.xml_attribute()
333 @py_serializable.xml_name('isbn_number')
334 def isbn(self) -> str:
335 return self._isbn
336
337 @property
338 @py_serializable.xml_sequence(3)
339 def edition(self) -> Optional[BookEdition]:
340 return self._edition
341
342 @property
343 @py_serializable.xml_sequence(4)
344 @py_serializable.type_mapping(Iso8601Date)
345 def publish_date(self) -> date:
346 return self._publish_date
347
348 @property
349 @py_serializable.xml_array(XmlArraySerializationType.FLAT, 'author')
350 @py_serializable.xml_string(XmlStringSerializationType.NORMALIZED_STRING)
351 @py_serializable.xml_sequence(5)
352 def authors(self) -> Set[str]:
353 return self._authors
354
355 @property
356 @py_serializable.xml_sequence(7)
357 def publisher(self) -> Optional[Publisher]:
358 return self._publisher
359
360 @property
361 @py_serializable.xml_array(XmlArraySerializationType.NESTED, 'chapter')
362 @py_serializable.xml_sequence(8)
363 def chapters(self) -> List[Chapter]:
364 return self._chapters
365
366 @chapters.setter
367 def chapters(self, chapters: Iterable[Chapter]) -> None:
368 self._chapters = list(chapters)
369
370 @property
371 @py_serializable.xml_sequence(6)
372 def type(self) -> BookType:
373 return self._type
374
375 @property
376 @py_serializable.view(SchemaVersion4)
377 @py_serializable.xml_array(py_serializable.XmlArraySerializationType.NESTED, 'reference')
378 @py_serializable.xml_sequence(7)
379 def references(self) -> Set[BookReference]:
380 return self._references
381
382 @references.setter
383 def references(self, references: Iterable[BookReference]) -> None:
384 self._references = set(references)
385
386 @property
387 @py_serializable.xml_sequence(20)
388 def rating(self) -> Decimal:
389 return self._rating
390
391 @rating.setter
392 def rating(self, rating: Decimal) -> None:
393 self._rating = rating
394
395 @property
396 @py_serializable.view(SchemaVersion4)
397 @py_serializable.xml_array(XmlArraySerializationType.FLAT, 'stockId')
398 @py_serializable.xml_sequence(21)
399 def stock_ids(self) -> Set[StockId]:
400 return self._stock_ids
401
402
403# region ThePhoenixProject_v2
404
405
406ThePhoenixProject_v1 = Book(
407 title='The Phoenix Project',
408 isbn='978-1942788294',
409 publish_date=date(year=2018, month=4, day=16),
410 authors=['Gene Kim', 'Kevin Behr', 'George Spafford'],
411 publisher=Publisher(name='IT Revolution Press LLC'),
412 edition=BookEdition(number=5, name='5th Anniversary Limited Edition'),
413 id=UUID('f3758bf0-0ff7-4366-a5e5-c209d4352b2d'),
414 rating=Decimal('9.8')
415)
416
417ThePhoenixProject_v1.chapters.append(Chapter(number=1, title='Tuesday, September 2'))
418ThePhoenixProject_v1.chapters.append(Chapter(number=2, title='Tuesday, September 2'))
419ThePhoenixProject_v1.chapters.append(Chapter(number=3, title='Tuesday, September 2'))
420ThePhoenixProject_v1.chapters.append(Chapter(number=4, title='Wednesday, September 3'))
421
422# endregion ThePhoenixProject_v2
423
424# region ThePhoenixProject_v2
425
426ThePhoenixProject_v2 = Book(
427 title='The Phoenix Project',
428 isbn='978-1942788294',
429 publish_date=date(year=2018, month=4, day=16),
430 authors=['Gene Kim', 'Kevin Behr', 'George Spafford'],
431 publisher=Publisher(name='IT Revolution Press LLC', address='10 Downing Street'),
432 edition=BookEdition(number=5, name='5th Anniversary Limited Edition'),
433 id=UUID('f3758bf0-0ff7-4366-a5e5-c209d4352b2d'),
434 rating=Decimal('9.8'),
435 stock_ids=[StockId('stock-id-1'), StockId('stock-id-2')]
436)
437
438ThePhoenixProject_v2.chapters.append(Chapter(number=1, title='Tuesday, September 2'))
439ThePhoenixProject_v2.chapters.append(Chapter(number=2, title='Tuesday, September 2'))
440ThePhoenixProject_v2.chapters.append(Chapter(number=3, title='Tuesday, September 2'))
441ThePhoenixProject_v2.chapters.append(Chapter(number=4, title='Wednesday, September 3'))
442
443SubRef1 = BookReference(ref='sub-ref-1')
444SubRef2 = BookReference(ref='sub-ref-2')
445SubRef3 = BookReference(ref='sub-ref-3')
446
447Ref1 = BookReference(ref='my-ref-1')
448Ref2 = BookReference(ref='my-ref-2', references=[SubRef1, SubRef3])
449Ref3 = BookReference(ref='my-ref-3', references=[SubRef2])
450
451ThePhoenixProject_v2.references = {Ref3, Ref2, Ref1}
452
453# endregion ThePhoenixProject_v2
454
455ThePhoenixProject = ThePhoenixProject_v2
456
457# region ThePhoenixProject_unnormalized
458
459# a case where the `normalizedString` and `token` transformation must come into play
460ThePhoenixProject_unnormalized = Book(
461 title='The \n Phoenix Project ',
462 isbn='978-1942788294',
463 publish_date=date(year=2018, month=4, day=16),
464 authors=['Gene Kim', 'Kevin\r\nBehr', 'George\tSpafford'],
465 publisher=Publisher(name='IT Revolution Press LLC', address='10 Downing Street'),
466 edition=BookEdition(number=5, name='5th Anniversary Limited Edition'),
467 id=UUID('f3758bf0-0ff7-4366-a5e5-c209d4352b2d'),
468 rating=Decimal('9.8'),
469 stock_ids=[StockId('stock-id-1'), StockId('stock-id-2')]
470)
471
472ThePhoenixProject_unnormalized.chapters.append(Chapter(number=1, title='Tuesday, September 2'))
473ThePhoenixProject_unnormalized.chapters.append(Chapter(number=2, title='Tuesday,\tSeptember 2'))
474ThePhoenixProject_unnormalized.chapters.append(Chapter(number=3, title='Tuesday,\r\nSeptember 2'))
475ThePhoenixProject_unnormalized.chapters.append(Chapter(number=4, title='Wednesday,\rSeptember\n3'))
476
477SubRef1 = BookReference(ref=' sub-ref-1 ')
478SubRef2 = BookReference(ref='\rsub-ref-2\t')
479SubRef3 = BookReference(ref='\nsub-ref-3\r\n')
480
481Ref1 = BookReference(ref='\r\nmy-ref-1')
482Ref2 = BookReference(ref='\tmy-ref-2', references=[SubRef1, SubRef3])
483Ref3 = BookReference(ref=' my-ref-3\n', references=[SubRef2])
484
485ThePhoenixProject_unnormalized.references = {Ref3, Ref2, Ref1}
486
487# endregion ThePhoenixProject_unnormalized
488
489# region ThePhoenixProject_attr_serialized_none
490
491# a case where an attribute is serialized to `None` and deserialized from it
492ThePhoenixProject_attr_serialized_none = Book(
493 title='The Phoenix Project',
494 isbn='978-1942788294',
495 publish_date=date(year=2018, month=4, day=16),
496 authors=['Gene Kim', 'Kevin Behr', 'George Spafford'],
497 publisher=Publisher(name='IT Revolution Press LLC'),
498 edition=BookEdition(number=0, name='Preview Edition'),
499 id=UUID('f3758bf0-0ff7-4366-a5e5-c209d4352b2d'),
500 rating=Decimal('9.8')
501)
502
503# endregion ThePhoenixProject_attr_serialized_none
504
505if __name__ == '__main__':
506 tpp_as_xml = ThePhoenixProject.as_xml() # type:ignore[attr-defined]
507 tpp_as_json = ThePhoenixProject.as_json() # type:ignore[attr-defined]
508 print(tpp_as_xml, tpp_as_json, sep='\n\n')
509
510 import io
511 import json
512
513 tpp_from_xml = ThePhoenixProject.from_xml( # type:ignore[attr-defined]
514 io.StringIO(tpp_as_xml))
515 tpp_from_json = ThePhoenixProject.from_json( # type:ignore[attr-defined]
516 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 py_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'))
py_serializable.logger.addHandler(my_log_handler)
py_serializable.logger.setLevel(my_log_handler.level)
py_serializable.logger.propagate = False
@py_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())