gedcom-x 0.5.8__py3-none-any.whl → 0.5.10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {gedcom_x-0.5.8.dist-info → gedcom_x-0.5.10.dist-info}/METADATA +1 -1
- gedcom_x-0.5.10.dist-info/RECORD +58 -0
- gedcomx/Extensions/rs10/rsLink.py +109 -59
- gedcomx/__init__.py +4 -1
- gedcomx/address.py +102 -16
- gedcomx/agent.py +81 -24
- gedcomx/attribution.py +52 -28
- gedcomx/conclusion.py +98 -46
- gedcomx/converter.py +209 -79
- gedcomx/coverage.py +10 -1
- gedcomx/date.py +42 -8
- gedcomx/document.py +37 -7
- gedcomx/event.py +77 -20
- gedcomx/evidence_reference.py +9 -0
- gedcomx/extensible.py +86 -0
- gedcomx/fact.py +53 -54
- gedcomx/gedcom.py +10 -0
- gedcomx/gedcom5x.py +30 -20
- gedcomx/gedcom7/GedcomStructure.py +1 -3
- gedcomx/gedcom7/__init__.py +2 -2
- gedcomx/gedcom7/{Gedcom7.py → gedcom7.py} +3 -3
- gedcomx/gedcom7/specification.py +4817 -0
- gedcomx/gedcomx.py +95 -93
- gedcomx/gender.py +21 -9
- gedcomx/group.py +9 -0
- gedcomx/identifier.py +47 -20
- gedcomx/logging_hub.py +19 -0
- gedcomx/mutations.py +10 -5
- gedcomx/name.py +74 -33
- gedcomx/note.py +50 -18
- gedcomx/online_account.py +9 -0
- gedcomx/person.py +46 -27
- gedcomx/place_description.py +54 -8
- gedcomx/place_reference.py +30 -8
- gedcomx/qualifier.py +19 -3
- gedcomx/relationship.py +55 -14
- gedcomx/resource.py +45 -18
- gedcomx/schemas.py +328 -0
- gedcomx/serialization.py +400 -421
- gedcomx/source_citation.py +16 -4
- gedcomx/source_description.py +181 -94
- gedcomx/source_reference.py +51 -16
- gedcomx/subject.py +59 -14
- gedcomx/textvalue.py +66 -12
- gedcomx/translation.py +3 -3
- gedcomx/uri.py +155 -3
- gedcom_x-0.5.8.dist-info/RECORD +0 -56
- gedcomx/gedcom7/Specification.py +0 -347
- {gedcom_x-0.5.8.dist-info → gedcom_x-0.5.10.dist-info}/WHEEL +0 -0
- {gedcom_x-0.5.8.dist-info → gedcom_x-0.5.10.dist-info}/top_level.txt +0 -0
gedcomx/serialization.py
CHANGED
@@ -1,11 +1,14 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
from collections.abc import Sized
|
3
2
|
from functools import lru_cache
|
4
|
-
from typing import Any, Callable, Dict, ForwardRef, List, Set, Tuple, Union, get_args, get_origin
|
5
3
|
|
6
4
|
import enum
|
7
5
|
import logging
|
8
6
|
import types
|
7
|
+
from collections.abc import Sized
|
8
|
+
from typing import Any, Dict, List, Set, Tuple, Union, Annotated, ForwardRef, get_args, get_origin
|
9
|
+
from typing import Any, Callable, Mapping, List, Dict, Tuple, Set
|
10
|
+
from typing import List, Optional
|
11
|
+
from time import perf_counter
|
9
12
|
|
10
13
|
"""
|
11
14
|
======================================================================
|
@@ -17,7 +20,6 @@ import types
|
|
17
20
|
Created: 2025-08-25
|
18
21
|
Updated:
|
19
22
|
- 2025-08-31: cleaned up imports and documentation
|
20
|
-
- 2025-09-01: filename PEP8 standard, imports changed accordingly
|
21
23
|
|
22
24
|
======================================================================
|
23
25
|
"""
|
@@ -35,7 +37,7 @@ from .date import Date
|
|
35
37
|
from .document import Document, DocumentType, TextType
|
36
38
|
from .evidence_reference import EvidenceReference
|
37
39
|
from .event import Event, EventType, EventRole, EventRoleType
|
38
|
-
from .Extensions.rs10.rsLink import
|
40
|
+
from .Extensions.rs10.rsLink import _rsLinks, rsLink
|
39
41
|
from .fact import Fact, FactType, FactQualifier
|
40
42
|
from .gender import Gender, GenderType
|
41
43
|
from .identifier import IdentifierList, Identifier
|
@@ -58,14 +60,8 @@ from .uri import URI
|
|
58
60
|
log = logging.getLogger("gedcomx")
|
59
61
|
deserialization = "gedcomx.deserialization"
|
60
62
|
|
61
|
-
|
62
|
-
|
63
|
-
name=deserialization,
|
64
|
-
path=f"logs/{deserialization}.log",
|
65
|
-
level=logging.DEBUG,
|
66
|
-
rotation="size:10MB:3", # rotate by size, keep 3 backups
|
67
|
-
)
|
68
|
-
)
|
63
|
+
serial_log = "gedcomx.serialization"
|
64
|
+
deserial_log = "gedcomx.deserialization"
|
69
65
|
|
70
66
|
_PRIMITIVES = (str, int, float, bool, type(None))
|
71
67
|
|
@@ -75,7 +71,58 @@ def _has_parent_class(obj) -> bool:
|
|
75
71
|
class Serialization:
|
76
72
|
|
77
73
|
@staticmethod
|
78
|
-
def
|
74
|
+
def serialize(obj: object):
|
75
|
+
if obj is not None:
|
76
|
+
with hub.use(serial_log):
|
77
|
+
|
78
|
+
if isinstance(obj, (str, int, float, bool, type(None))):
|
79
|
+
return obj
|
80
|
+
if isinstance(obj, dict):
|
81
|
+
return {k: Serialization.serialize(v) for k, v in obj.items()}
|
82
|
+
if isinstance(obj, URI):
|
83
|
+
return obj.value
|
84
|
+
if isinstance(obj, (list, tuple, set)):
|
85
|
+
l = [Serialization.serialize(v) for v in obj]
|
86
|
+
if len(l) == 0: return None
|
87
|
+
return l
|
88
|
+
if type(obj).__name__ == 'Collection':
|
89
|
+
l= [Serialization.serialize(v) for v in obj]
|
90
|
+
if len(l) == 0: return None
|
91
|
+
return l
|
92
|
+
if isinstance(obj, enum.Enum):
|
93
|
+
return Serialization.serialize(obj.value)
|
94
|
+
|
95
|
+
log.debug(f"Serializing a '{type(obj).__name__}'")
|
96
|
+
type_as_dict = {}
|
97
|
+
fields = Serialization.get_class_fields(type(obj).__name__)
|
98
|
+
if fields:
|
99
|
+
for field_name, type_ in fields.items():
|
100
|
+
if hasattr(obj,field_name):
|
101
|
+
if (v := getattr(obj,field_name)) is not None:
|
102
|
+
if type_ == Resource:
|
103
|
+
log.error(f"Refering to a {type(obj).__name__} with a '{type_}'")
|
104
|
+
res = Resource(target=v)
|
105
|
+
sv = Serialization.serialize(res)
|
106
|
+
type_as_dict[field_name] = sv
|
107
|
+
elif type_ == URI:
|
108
|
+
log.error(f"Refering to a {type(obj).__name__} with a '{type_}'")
|
109
|
+
uri = URI(target=v)
|
110
|
+
sv = Serialization.serialize(uri)
|
111
|
+
type_as_dict[field_name] = sv
|
112
|
+
elif (sv := Serialization.serialize(v)) is not None:
|
113
|
+
type_as_dict[field_name] = sv
|
114
|
+
else:
|
115
|
+
log.error(f"{type(obj).__name__} did not have field '{field_name}'")
|
116
|
+
if type_as_dict == {}: log.error(f"Serialized a '{type(obj).__name__}' with empty fields: '{fields}'")
|
117
|
+
else: log.debug(f"Serialized a '{type(obj).__name__}' with fields '{type_as_dict})'")
|
118
|
+
#return Serialization._serialize_dict(type_as_dict)
|
119
|
+
return type_as_dict if type_as_dict != {} else None
|
120
|
+
else:
|
121
|
+
log.error(f"Could not find fields for {type(obj).__name__}")
|
122
|
+
return None
|
123
|
+
|
124
|
+
@staticmethod
|
125
|
+
def _serialize_dict(dict_to_serialize: dict) -> dict:
|
79
126
|
"""
|
80
127
|
Walk a dict and serialize nested GedcomX objects to JSON-compatible values.
|
81
128
|
- Uses `_as_dict_` on your objects when present
|
@@ -85,9 +132,9 @@ class Serialization:
|
|
85
132
|
def _serialize(value):
|
86
133
|
if isinstance(value, (str, int, float, bool, type(None))):
|
87
134
|
return value
|
88
|
-
if
|
135
|
+
if (fields := Serialization.get_class_fields(type(value).__name__)) is not None:
|
89
136
|
# Expect your objects expose a snapshot via _as_dict_
|
90
|
-
return value
|
137
|
+
return Serialization.serialize(value)
|
91
138
|
if isinstance(value, dict):
|
92
139
|
return {k: _serialize(v) for k, v in value.items()}
|
93
140
|
if isinstance(value, (list, tuple, set)):
|
@@ -155,302 +202,356 @@ class Serialization:
|
|
155
202
|
inst._resource_setters = []
|
156
203
|
|
157
204
|
# --- your deserialize with setters --------------------------------------
|
205
|
+
|
158
206
|
@classmethod
|
159
207
|
def deserialize(
|
160
208
|
cls,
|
161
|
-
data:
|
209
|
+
data: dict[str, Any],
|
162
210
|
class_type: type,
|
163
211
|
*,
|
164
|
-
resolver: Callable[[Any], Any] | None = None,
|
165
|
-
queue_setters: bool = True
|
212
|
+
resolver: Callable[[Any], Any] | None = None,
|
213
|
+
queue_setters: bool = True,
|
166
214
|
) -> Any:
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
def make_setter(attr_name: str, raw_value: Any):
|
181
|
-
# capture references to the *exact* object we just built for this attribute
|
182
|
-
def _setter(instance: Any, _resolver: Callable[[Any], Any]) -> None:
|
183
|
-
resolved = cls._resolve_structure(raw_value, _resolver)
|
184
|
-
setattr(instance, attr_name, resolved)
|
185
|
-
return _setter
|
186
|
-
pending_setters.append(make_setter(name, coerced))
|
187
|
-
|
188
|
-
# build the instance
|
189
|
-
inst = class_type(**result)
|
190
|
-
|
191
|
-
# apply now, if resolver provided
|
192
|
-
if resolver is not None and pending_setters:
|
193
|
-
for set_fn in pending_setters:
|
194
|
-
set_fn(inst, resolver)
|
195
|
-
|
196
|
-
# optionally store for later (gives you a real attribute assignment later)
|
197
|
-
if queue_setters:
|
198
|
-
# merge if already present
|
199
|
-
existing = getattr(inst, "_resource_setters", [])
|
200
|
-
inst._resource_setters = [*existing, *pending_setters]
|
215
|
+
|
216
|
+
with hub.use(deserial_log):
|
217
|
+
t0 = perf_counter()
|
218
|
+
class_fields = cls.get_class_fields(class_type.__name__)
|
219
|
+
|
220
|
+
result: dict[str, Any] = {}
|
221
|
+
pending: list[tuple[str, Any]] = []
|
222
|
+
|
223
|
+
# bind hot callables
|
224
|
+
_coerce = cls._coerce_value
|
225
|
+
_hasres = cls._has_resource_value
|
226
|
+
|
227
|
+
log.debug("deserialize[%s]: keys=%s", class_type.__name__, list(data.keys()))
|
201
228
|
|
229
|
+
for name, typ in class_fields.items():
|
230
|
+
raw = data.get(name, None)
|
231
|
+
if raw is None:
|
232
|
+
continue
|
233
|
+
try:
|
234
|
+
val = _coerce(raw, typ)
|
235
|
+
except Exception:
|
236
|
+
log.exception("deserialize[%s]: coercion failed for field '%s' raw=%r",
|
237
|
+
class_type.__name__, name, raw)
|
238
|
+
raise
|
239
|
+
result[name] = val
|
240
|
+
if _hasres(val):
|
241
|
+
pending.append((name, val))
|
242
|
+
|
243
|
+
# instantiate
|
244
|
+
try:
|
245
|
+
inst = class_type(**result)
|
246
|
+
except TypeError:
|
247
|
+
log.exception("deserialize[%s]: __init__ failed with kwargs=%s",
|
248
|
+
class_type.__name__, list(result.keys()))
|
249
|
+
raise
|
250
|
+
|
251
|
+
# resolve now (optional)
|
252
|
+
if resolver and pending:
|
253
|
+
for attr, raw in pending:
|
254
|
+
try:
|
255
|
+
resolved = cls._resolve_structure(raw, resolver)
|
256
|
+
setattr(inst, attr, resolved)
|
257
|
+
except Exception:
|
258
|
+
log.exception("deserialize[%s]: resolver failed for '%s'", class_type.__name__, attr)
|
259
|
+
raise
|
260
|
+
|
261
|
+
# queue setters (store (attr, raw) tuples) — preserves your later-resolution behavior
|
262
|
+
if queue_setters and pending:
|
263
|
+
existing = getattr(inst, "_resource_setters", [])
|
264
|
+
inst._resource_setters = [*existing, *pending]
|
265
|
+
|
266
|
+
|
267
|
+
log.debug("deserialize[%s]: done in %.3f ms (resolved=%d, queued=%d)",
|
268
|
+
class_type.__name__, (perf_counter() - t0) * 1000,
|
269
|
+
int(bool(resolver)) * len(pending), len(pending))
|
202
270
|
return inst
|
203
271
|
|
204
272
|
@staticmethod
|
273
|
+
@lru_cache(maxsize=None)
|
205
274
|
def get_class_fields(cls_name) -> Dict:
|
206
275
|
# NOTE: keep imports local to avoid circulars
|
207
276
|
|
208
277
|
|
209
278
|
fields = {
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
279
|
+
"Agent": {
|
280
|
+
"id": str,
|
281
|
+
"identifiers": IdentifierList,
|
282
|
+
"names": List[TextValue],
|
283
|
+
"homepage": URI,
|
284
|
+
"openid": URI,
|
285
|
+
"accounts": List[OnlineAccount],
|
286
|
+
"emails": List[URI],
|
287
|
+
"phones": List[URI],
|
288
|
+
"addresses": List[Address],
|
289
|
+
"person": object | Resource, # intended Person | Resource
|
290
|
+
"attribution": object, # GEDCOM5/7 compatibility
|
291
|
+
"uri": URI | Resource,
|
292
|
+
},
|
293
|
+
"Attribution": {
|
294
|
+
"contributor": Resource,
|
295
|
+
"modified": str,
|
296
|
+
"changeMessage": str,
|
297
|
+
"creator": Resource,
|
298
|
+
"created": str,
|
299
|
+
},
|
300
|
+
"Conclusion": {
|
301
|
+
"id": str,
|
302
|
+
"lang": str,
|
303
|
+
"sources": List["SourceReference"],
|
304
|
+
"analysis": Document | Resource,
|
305
|
+
"notes": List[Note],
|
306
|
+
"confidence": ConfidenceLevel,
|
307
|
+
"attribution": Attribution,
|
308
|
+
"uri": "Resource",
|
309
|
+
"max_note_count": int,
|
310
|
+
"links": _rsLinks,
|
311
|
+
},
|
312
|
+
"Date": {
|
313
|
+
"original": str,
|
314
|
+
"formal": str,
|
315
|
+
"normalized": str,
|
316
|
+
},
|
317
|
+
"Document": {
|
318
|
+
"id": str,
|
319
|
+
"lang": str,
|
320
|
+
"sources": List[SourceReference],
|
321
|
+
"analysis": Resource,
|
322
|
+
"notes": List[Note],
|
323
|
+
"confidence": ConfidenceLevel,
|
324
|
+
"attribution": Attribution,
|
325
|
+
"type": DocumentType,
|
326
|
+
"extracted": bool,
|
327
|
+
"textType": TextType,
|
328
|
+
"text": str,
|
329
|
+
},
|
330
|
+
"Event": {
|
331
|
+
"id": str,
|
332
|
+
"lang": str,
|
333
|
+
"sources": List[SourceReference],
|
334
|
+
"analysis": Resource,
|
335
|
+
"notes": List[Note],
|
336
|
+
"confidence": ConfidenceLevel,
|
337
|
+
"attribution": Attribution,
|
338
|
+
"extracted": bool,
|
339
|
+
"evidence": List[EvidenceReference],
|
340
|
+
"media": List[SourceReference],
|
341
|
+
"identifiers": List[Identifier],
|
342
|
+
"type": EventType,
|
343
|
+
"date": Date,
|
344
|
+
"place": PlaceReference,
|
345
|
+
"roles": List[EventRole],
|
346
|
+
},
|
347
|
+
"EventRole": {
|
348
|
+
"id:": str,
|
349
|
+
"lang": str,
|
350
|
+
"sources": List[SourceReference],
|
351
|
+
"analysis": Resource,
|
352
|
+
"notes": List[Note],
|
353
|
+
"confidence": ConfidenceLevel,
|
354
|
+
"attribution": Attribution,
|
355
|
+
"person": Resource,
|
356
|
+
"type": EventRoleType,
|
357
|
+
"details": str,
|
358
|
+
},
|
359
|
+
"Fact": {
|
360
|
+
"id": str,
|
361
|
+
"lang": str,
|
362
|
+
"sources": List[SourceReference],
|
363
|
+
"analysis": Resource | Document,
|
364
|
+
"notes": List[Note],
|
365
|
+
"confidence": ConfidenceLevel,
|
366
|
+
"attribution": Attribution,
|
367
|
+
"type": FactType,
|
368
|
+
"date": Date,
|
369
|
+
"place": PlaceReference,
|
370
|
+
"value": str,
|
371
|
+
"qualifiers": List[FactQualifier],
|
372
|
+
"links": _rsLinks,
|
373
|
+
},
|
374
|
+
"GedcomX": {
|
375
|
+
"persons": List[Person],
|
376
|
+
"relationships": List[Relationship],
|
377
|
+
"sourceDescriptions": List[SourceDescription],
|
378
|
+
"agents": List[Agent],
|
379
|
+
"places": List[PlaceDescription]
|
380
|
+
},
|
381
|
+
"Gender": {
|
382
|
+
"id": str,
|
383
|
+
"lang": str,
|
384
|
+
"sources": List[SourceReference],
|
385
|
+
"analysis": Resource,
|
386
|
+
"notes": List[Note],
|
387
|
+
"confidence": ConfidenceLevel,
|
388
|
+
"attribution": Attribution,
|
389
|
+
"type": GenderType,
|
390
|
+
},
|
391
|
+
"KnownSourceReference": {
|
392
|
+
"name": str,
|
393
|
+
"value": str,
|
394
|
+
},
|
395
|
+
"Name": {
|
396
|
+
"id": str,
|
397
|
+
"lang": str,
|
398
|
+
"sources": List[SourceReference],
|
399
|
+
"analysis": Resource,
|
400
|
+
"notes": List[Note],
|
401
|
+
"confidence": ConfidenceLevel,
|
402
|
+
"attribution": Attribution,
|
403
|
+
"type": NameType,
|
404
|
+
"nameForms": List[NameForm], # use string to avoid circulars if needed
|
405
|
+
"date": Date,
|
406
|
+
},
|
407
|
+
"NameForm": {
|
408
|
+
"lang": str,
|
409
|
+
"fullText": str,
|
410
|
+
"parts": List[NamePart], # use "NamePart" as a forward-ref to avoid circulars
|
411
|
+
},
|
412
|
+
"NamePart": {
|
413
|
+
"type": NamePartType,
|
414
|
+
"value": str,
|
415
|
+
"qualifiers": List["NamePartQualifier"], # quote if you want to avoid circulars
|
416
|
+
},
|
417
|
+
"Note":{"lang":str,
|
418
|
+
"subject":str,
|
419
|
+
"text":str,
|
420
|
+
"attribution": Attribution},
|
421
|
+
"Person": {
|
422
|
+
"id": str,
|
423
|
+
"lang": str,
|
424
|
+
"sources": List[SourceReference],
|
425
|
+
"analysis": Resource,
|
426
|
+
"notes": List[Note],
|
427
|
+
"confidence": ConfidenceLevel,
|
428
|
+
"attribution": Attribution,
|
429
|
+
"extracted": bool,
|
430
|
+
"evidence": List[EvidenceReference],
|
431
|
+
"media": List[SourceReference],
|
432
|
+
"identifiers": IdentifierList,
|
433
|
+
"private": bool,
|
434
|
+
"gender": Gender,
|
435
|
+
"names": List[Name],
|
436
|
+
"facts": List[Fact],
|
437
|
+
"living": bool,
|
438
|
+
"links": _rsLinks,
|
439
|
+
},
|
440
|
+
"PlaceDescription": {
|
441
|
+
"id": str,
|
442
|
+
"lang": str,
|
443
|
+
"sources": List[SourceReference],
|
444
|
+
"analysis": Resource,
|
445
|
+
"notes": List[Note],
|
446
|
+
"confidence": ConfidenceLevel,
|
447
|
+
"attribution": Attribution,
|
448
|
+
"extracted": bool,
|
449
|
+
"evidence": List[EvidenceReference],
|
450
|
+
"media": List[SourceReference],
|
451
|
+
"identifiers": List[IdentifierList],
|
452
|
+
"names": List[TextValue],
|
453
|
+
"type": str,
|
454
|
+
"place": URI,
|
455
|
+
"jurisdiction": Resource,
|
456
|
+
"latitude": float,
|
457
|
+
"longitude": float,
|
458
|
+
"temporalDescription": Date,
|
459
|
+
"spatialDescription": Resource,
|
460
|
+
},
|
461
|
+
"PlaceReference": {
|
462
|
+
"original": str,
|
463
|
+
"description": URI,
|
464
|
+
},
|
465
|
+
"Qualifier": {
|
466
|
+
"name": str,
|
467
|
+
"value": str,
|
468
|
+
},
|
469
|
+
"_rsLinks": {
|
470
|
+
"person":rsLink,
|
471
|
+
"portrait":rsLink},
|
472
|
+
"rsLink": {
|
473
|
+
"href": URI,
|
474
|
+
"template": str,
|
475
|
+
"type": str,
|
476
|
+
"accept": str,
|
477
|
+
"allow": str,
|
478
|
+
"hreflang": str,
|
479
|
+
"title": str,
|
480
|
+
},
|
481
|
+
"Relationship": {
|
482
|
+
"id": str,
|
483
|
+
"lang": str,
|
484
|
+
"sources": List[SourceReference],
|
485
|
+
"analysis": Resource,
|
486
|
+
"notes": List[Note],
|
487
|
+
"confidence": ConfidenceLevel,
|
488
|
+
"attribution": Attribution,
|
489
|
+
"extracted": bool,
|
490
|
+
"evidence": List[EvidenceReference],
|
491
|
+
"media": List[SourceReference],
|
492
|
+
"identifiers": IdentifierList,
|
493
|
+
"type": RelationshipType,
|
494
|
+
"person1": Resource,
|
495
|
+
"person2": Resource,
|
496
|
+
"facts": List[Fact],
|
497
|
+
},
|
498
|
+
"Resource": {
|
499
|
+
"resource": str,
|
500
|
+
"resourceId": str,
|
501
|
+
},
|
502
|
+
"SourceDescription": {
|
503
|
+
"id": str,
|
504
|
+
"resourceType": ResourceType,
|
505
|
+
"citations": List[SourceCitation],
|
506
|
+
"mediaType": str,
|
507
|
+
"about": URI,
|
508
|
+
"mediator": Resource,
|
509
|
+
"publisher": Resource, # forward-ref to avoid circular import
|
510
|
+
"authors": List[Resource],
|
511
|
+
"sources": List[SourceReference], # SourceReference
|
512
|
+
"analysis": Resource, # analysis is typically a Document (kept union to avoid cycle)
|
513
|
+
"componentOf": SourceReference, # SourceReference
|
514
|
+
"titles": List[TextValue],
|
515
|
+
"notes": List[Note],
|
516
|
+
"attribution": Attribution,
|
517
|
+
"rights": List[Resource],
|
518
|
+
"coverage": List[Coverage], # Coverage
|
519
|
+
"descriptions": List[TextValue],
|
520
|
+
"identifiers": IdentifierList,
|
521
|
+
"created": Date,
|
522
|
+
"modified": Date,
|
523
|
+
"published": Date,
|
524
|
+
"repository": Agent, # forward-ref
|
525
|
+
"max_note_count": int,
|
526
|
+
},
|
527
|
+
"SourceReference": {
|
528
|
+
"description": Resource,
|
529
|
+
"descriptionId": str,
|
530
|
+
"attribution": Attribution,
|
531
|
+
"qualifiers": List[Qualifier],
|
532
|
+
},
|
533
|
+
"Subject": {
|
534
|
+
"id": str,
|
535
|
+
"lang": str,
|
536
|
+
"sources": List["SourceReference"],
|
537
|
+
"analysis": Resource,
|
538
|
+
"notes": List["Note"],
|
539
|
+
"confidence": ConfidenceLevel,
|
540
|
+
"attribution": Attribution,
|
541
|
+
"extracted": bool,
|
542
|
+
"evidence": List[EvidenceReference],
|
543
|
+
"media": List[SourceReference],
|
544
|
+
"identifiers": IdentifierList,
|
545
|
+
"uri": Resource,
|
546
|
+
"links": _rsLinks,
|
547
|
+
},
|
548
|
+
"TextValue":{"lang":str,"value":str},
|
549
|
+
"URI": {
|
550
|
+
"value": str,
|
551
|
+
},
|
552
|
+
|
553
|
+
}
|
554
|
+
|
454
555
|
return fields.get(cls_name, {})
|
455
556
|
|
456
557
|
|
@@ -495,7 +596,7 @@ class Serialization:
|
|
495
596
|
if T is Resource:
|
496
597
|
log.debug("COERCE str->Resource: %r", value)
|
497
598
|
try:
|
498
|
-
ret = Resource(
|
599
|
+
ret = Resource(resourceId=value)
|
499
600
|
log.debug("COERCE str->Resource: built %r", ret)
|
500
601
|
return ret
|
501
602
|
except Exception:
|
@@ -517,7 +618,7 @@ class Serialization:
|
|
517
618
|
if T is Resource and isinstance(value, dict):
|
518
619
|
log.debug("COERCE dict->Resource: %r", value)
|
519
620
|
try:
|
520
|
-
ret = Resource(
|
621
|
+
ret = Resource(resource=value.get("resource"), resourceId=value.get("resourceId"))
|
521
622
|
log.debug("COERCE dict->Resource: built %r", ret)
|
522
623
|
return ret
|
523
624
|
except Exception:
|
@@ -637,129 +738,7 @@ class Serialization:
|
|
637
738
|
return value
|
638
739
|
|
639
740
|
|
640
|
-
|
641
|
-
if T is Resource and isinstance(value, dict):
|
642
|
-
log.debug("COERCE dict->Resource: %r", value)
|
643
|
-
try:
|
644
|
-
ret = Resource(uri=value.get("resource"), id=value.get("resourceId"))
|
645
|
-
log.debug("COERCE dict->Resource: built %r", ret)
|
646
|
-
return ret
|
647
|
-
except Exception:
|
648
|
-
log.exception("COERCE dict->Resource: failed for %r", value)
|
649
|
-
return value
|
650
|
-
|
651
|
-
# IdentifierList special
|
652
|
-
if T is IdentifierList:
|
653
|
-
log.debug("COERCE IdentifierList: %r", value)
|
654
|
-
try:
|
655
|
-
ret = IdentifierList._from_json_(value)
|
656
|
-
log.debug("COERCE IdentifierList: built %r", ret)
|
657
|
-
return ret
|
658
|
-
except Exception:
|
659
|
-
log.exception("COERCE IdentifierList: _from_json_ failed for %r", value)
|
660
|
-
return value
|
661
|
-
|
662
|
-
# Containers
|
663
|
-
if self._is_list_like(T):
|
664
|
-
elem_t = args[0] if args else Any
|
665
|
-
log.debug("COERCE list-like: len=%s, elem_t=%r", len(value or []), elem_t)
|
666
|
-
try:
|
667
|
-
ret = [self._coerce_value(v, elem_t) for v in (value or [])]
|
668
|
-
log.debug("COERCE list-like: result sample=%r", ret[:3] if isinstance(ret, list) else ret)
|
669
|
-
return ret
|
670
|
-
except Exception:
|
671
|
-
log.exception("COERCE list-like: failed for value=%r elem_t=%r", value, elem_t)
|
672
|
-
return value
|
673
|
-
|
674
|
-
if self._is_set_like(T):
|
675
|
-
elem_t = args[0] if args else Any
|
676
|
-
log.debug("COERCE set-like: len=%s, elem_t=%r", len(value or []), elem_t)
|
677
|
-
try:
|
678
|
-
ret = {self._coerce_value(v, elem_t) for v in (value or [])}
|
679
|
-
log.debug("COERCE set-like: result size=%d", len(ret))
|
680
|
-
return ret
|
681
|
-
except Exception:
|
682
|
-
log.exception("COERCE set-like: failed for value=%r elem_t=%r", value, elem_t)
|
683
|
-
return value
|
684
|
-
|
685
|
-
if self._is_tuple_like(T):
|
686
|
-
log.debug("COERCE tuple-like: value=%r, args=%r", value, args)
|
687
|
-
try:
|
688
|
-
if not value:
|
689
|
-
log.debug("COERCE tuple-like: empty/None -> ()")
|
690
|
-
return tuple(value or ())
|
691
|
-
if len(args) == 2 and args[1] is Ellipsis:
|
692
|
-
elem_t = args[0]
|
693
|
-
ret = tuple(self._coerce_value(v, elem_t) for v in (value or ()))
|
694
|
-
log.debug("COERCE tuple-like variadic: size=%d", len(ret))
|
695
|
-
return ret
|
696
|
-
ret = tuple(self._coerce_value(v, t) for v, t in zip(value, args))
|
697
|
-
log.debug("COERCE tuple-like fixed: size=%d", len(ret))
|
698
|
-
return ret
|
699
|
-
except Exception:
|
700
|
-
log.exception("COERCE tuple-like: failed for value=%r args=%r", value, args)
|
701
|
-
return value
|
702
|
-
|
703
|
-
if self._is_dict_like(T):
|
704
|
-
k_t = args[0] if len(args) >= 1 else Any
|
705
|
-
v_t = args[1] if len(args) >= 2 else Any
|
706
|
-
log.debug("COERCE dict-like: keys=%s, k_t=%r, v_t=%r", len((value or {}).keys()), k_t, v_t)
|
707
|
-
try:
|
708
|
-
ret = {
|
709
|
-
self._coerce_value(k, k_t): self._coerce_value(v, v_t)
|
710
|
-
for k, v in (value or {}).items()
|
711
|
-
}
|
712
|
-
log.debug("COERCE dict-like: result size=%d", len(ret))
|
713
|
-
return ret
|
714
|
-
except Exception:
|
715
|
-
log.exception("COERCE dict-like: failed for value=%r k_t=%r v_t=%r", value, k_t, v_t)
|
716
|
-
return value
|
717
|
-
|
718
|
-
# Objects via registry
|
719
|
-
if isinstance(T, type) and isinstance(value, dict):
|
720
|
-
fields = self.get_class_fields(T.__name__) or {}
|
721
|
-
log.debug(
|
722
|
-
"COERCE object: class=%s, input_keys=%s, registered_fields=%s",
|
723
|
-
T.__name__, list(value.keys()), list(fields.keys())
|
724
|
-
)
|
725
|
-
if fields:
|
726
|
-
kwargs = {}
|
727
|
-
present = []
|
728
|
-
for fname, ftype in fields.items():
|
729
|
-
if fname in value:
|
730
|
-
resolved = self._resolve_forward(self._unwrap(ftype))
|
731
|
-
log.debug("COERCE object.field: %s.%s -> %r, raw=%r", T.__name__, fname, resolved, value[fname])
|
732
|
-
try:
|
733
|
-
coerced = self._coerce_value(value[fname], resolved)
|
734
|
-
kwargs[fname] = coerced
|
735
|
-
present.append(fname)
|
736
|
-
log.debug("COERCE object.field: %s.%s coerced -> %r", T.__name__, fname, coerced)
|
737
|
-
except Exception:
|
738
|
-
log.exception("COERCE object.field: %s.%s failed", T.__name__, fname)
|
739
|
-
unknown = [k for k in value.keys() if k not in fields]
|
740
|
-
if unknown:
|
741
|
-
log.debug("COERCE object: %s unknown keys ignored: %s", T.__name__, unknown)
|
742
|
-
try:
|
743
|
-
log.debug("COERCE object: instantiate %s(**%s)", T.__name__, present)
|
744
|
-
ret = T(**kwargs)
|
745
|
-
log.debug("COERCE object: success -> %r", ret)
|
746
|
-
return ret
|
747
|
-
except TypeError as e:
|
748
|
-
log.warning("COERCE object: instantiate %s failed with kwargs=%s: %s", T.__name__, list(kwargs.keys()), e)
|
749
|
-
log.debug("COERCE object: returning partially coerced dict")
|
750
|
-
return kwargs
|
751
|
-
|
752
|
-
# Already correct type?
|
753
|
-
try:
|
754
|
-
if isinstance(value, T):
|
755
|
-
log.debug("COERCE passthrough: value already instance of %r", T)
|
756
|
-
return value
|
757
|
-
except TypeError:
|
758
|
-
log.debug("COERCE isinstance not applicable: T=%r", T)
|
759
|
-
|
760
|
-
log.debug("COERCE fallback: returning original value=%r (type=%s)", value, type(value).__name__)
|
761
|
-
return value
|
762
|
-
|
741
|
+
|
763
742
|
# -------------------------- TYPE HELPERS --------------------------
|
764
743
|
|
765
744
|
@staticmethod
|