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())