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/source_citation.py
CHANGED
@@ -1,4 +1,13 @@
|
|
1
1
|
from typing import Optional
|
2
|
+
from .logging_hub import hub, logging
|
3
|
+
"""
|
4
|
+
======================================================================
|
5
|
+
Logging
|
6
|
+
======================================================================
|
7
|
+
"""
|
8
|
+
log = logging.getLogger("gedcomx")
|
9
|
+
serial_log = "gedcomx.serialization"
|
10
|
+
#=====================================================================
|
2
11
|
|
3
12
|
class SourceCitation:
|
4
13
|
identifier = 'http://gedcomx.org/v1/SourceCitation'
|
@@ -11,13 +20,16 @@ class SourceCitation:
|
|
11
20
|
# ...existing code...
|
12
21
|
|
13
22
|
@classmethod
|
14
|
-
def _from_json_(cls, data: dict):
|
23
|
+
def _from_json_(cls, data: dict, context = None):
|
15
24
|
"""
|
16
25
|
Create a SourceCitation instance from a JSON-dict (already parsed).
|
17
26
|
"""
|
18
|
-
|
19
|
-
|
20
|
-
|
27
|
+
object_data = {}
|
28
|
+
if (lang := data.get('lang')) is not None:
|
29
|
+
object_data['lang'] = lang
|
30
|
+
if (value := data.get('value')) is not None:
|
31
|
+
object_data['value'] = value
|
32
|
+
return cls(**object_data)
|
21
33
|
|
22
34
|
@property
|
23
35
|
def _as_dict_(self):
|
gedcomx/source_description.py
CHANGED
@@ -5,6 +5,7 @@ from typing import Any, Dict, List, Optional, TYPE_CHECKING
|
|
5
5
|
if TYPE_CHECKING:
|
6
6
|
from .document import Document
|
7
7
|
|
8
|
+
|
8
9
|
"""
|
9
10
|
======================================================================
|
10
11
|
Project: Gedcom-X
|
@@ -31,12 +32,21 @@ from .attribution import Attribution
|
|
31
32
|
from .coverage import Coverage
|
32
33
|
from .date import Date
|
33
34
|
from .identifier import Identifier, IdentifierList, make_uid
|
35
|
+
from .logging_hub import hub, logging
|
34
36
|
from .note import Note
|
35
37
|
from .resource import Resource
|
36
38
|
from .source_citation import SourceCitation
|
37
39
|
from .source_reference import SourceReference
|
38
40
|
from .textvalue import TextValue
|
39
41
|
from .uri import URI
|
42
|
+
"""
|
43
|
+
======================================================================
|
44
|
+
Logging
|
45
|
+
======================================================================
|
46
|
+
"""
|
47
|
+
log = logging.getLogger("gedcomx")
|
48
|
+
serial_log = "gedcomx.serialization"
|
49
|
+
deserial_log = "gedcomx.deserialization"
|
40
50
|
#=====================================================================
|
41
51
|
|
42
52
|
|
@@ -157,6 +167,8 @@ class SourceDescription:
|
|
157
167
|
self.max_note_count = max_note_count
|
158
168
|
|
159
169
|
self.uri = URI(fragment=id) if id else None #TODO Should i take care of this in the collections?
|
170
|
+
|
171
|
+
self._place_holder = False
|
160
172
|
|
161
173
|
@property
|
162
174
|
def publisher(self) -> Resource | Agent | None:
|
@@ -195,7 +207,7 @@ class SourceDescription:
|
|
195
207
|
return False
|
196
208
|
self.notes.append(note_to_add)
|
197
209
|
|
198
|
-
def
|
210
|
+
def add_source_reference(self, source_to_add: SourceReference):
|
199
211
|
if source_to_add and isinstance(object,SourceReference):
|
200
212
|
for current_source in self.sources:
|
201
213
|
if current_source == source_to_add:
|
@@ -213,102 +225,177 @@ class SourceDescription:
|
|
213
225
|
raise ValueError(f"Cannot add title of type {type(title_to_add)}")
|
214
226
|
|
215
227
|
@property
|
216
|
-
def _as_dict_(self) -> Dict[str, Any]:
|
228
|
+
def _as_dict_(self) -> Dict[str, Any] | None:
|
217
229
|
from .serialization import Serialization
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
type_as_dict
|
222
|
-
if self.about:
|
223
|
-
type_as_dict['about'] = self.about._as_dict_
|
224
|
-
if self.resourceType:
|
225
|
-
type_as_dict['resourceType'] = getattr(self.resourceType, 'value', self.resourceType)
|
226
|
-
if self.citations:
|
227
|
-
type_as_dict['citations'] = [c._as_dict_ for c in self.citations if c]
|
228
|
-
if self.mediaType:
|
229
|
-
type_as_dict['mediaType'] = self.mediaType
|
230
|
-
if self.mediator:
|
231
|
-
type_as_dict['mediator'] = self.mediator._as_dict_
|
232
|
-
if self.publisher:
|
233
|
-
type_as_dict['publisher'] = self.publisher._as_dict_ #TODO Resource this
|
234
|
-
if self.authors:
|
235
|
-
type_as_dict['authors'] = [a._as_dict_ for a in self.authors if a]
|
236
|
-
if self.sources:
|
237
|
-
type_as_dict['sources'] = [s._as_dict_ for s in self.sources if s]
|
238
|
-
if self.analysis:
|
239
|
-
type_as_dict['analysis'] = self.analysis._as_dict_
|
240
|
-
if self.componentOf:
|
241
|
-
type_as_dict['componentOf'] = self.componentOf._as_dict_
|
242
|
-
if self.titles:
|
243
|
-
type_as_dict['titles'] = [t._as_dict_ for t in self.titles if t]
|
244
|
-
if self.notes:
|
245
|
-
type_as_dict['notes'] = [n._as_dict_ for n in self.notes if n]
|
246
|
-
if self.attribution:
|
247
|
-
type_as_dict['attribution'] = self.attribution._as_dict_
|
248
|
-
if self.rights:
|
249
|
-
type_as_dict['rights'] = [r._as_dict_ for r in self.rights if r]
|
250
|
-
if self.coverage:
|
251
|
-
type_as_dict['coverage'] = [c._as_dict_ for c in self.coverage if c]
|
252
|
-
if self.descriptions:
|
253
|
-
type_as_dict['descriptions'] = [d for d in self.descriptions if d]
|
254
|
-
if self.identifiers:
|
255
|
-
type_as_dict['identifiers'] = self.identifiers._as_dict_
|
256
|
-
if self.created is not None:
|
257
|
-
type_as_dict['created'] = self.created
|
258
|
-
if self.modified is not None:
|
259
|
-
type_as_dict['modified'] = self.modified
|
260
|
-
if self.published is not None:
|
261
|
-
type_as_dict['published'] = self.published
|
262
|
-
if self.repository:
|
263
|
-
type_as_dict['repository'] = self.repository._as_dict_ #TODO Resource this
|
264
|
-
if self.uri and self.uri.value:
|
265
|
-
type_as_dict['uri'] = self.uri.value
|
230
|
+
return Serialization.serialize(self)
|
231
|
+
with hub.use(serial_log):
|
232
|
+
log.debug(f"Serializing 'SourceDescription' with id: {self.id}")
|
233
|
+
type_as_dict = {}
|
266
234
|
|
267
|
-
|
268
|
-
|
269
|
-
|
235
|
+
if self.id:
|
236
|
+
type_as_dict['id'] = self.id
|
237
|
+
if self.about:
|
238
|
+
type_as_dict['about'] = self.about._as_dict_
|
239
|
+
if self.resourceType:
|
240
|
+
if isinstance(self.resourceType,str):
|
241
|
+
log.warning(f"'SourceDescription.resourceType' should not be a string {self.resourceType}")
|
242
|
+
type_as_dict['resourceType'] = self.resourceType
|
243
|
+
else:
|
244
|
+
type_as_dict['resourceType'] = self.resourceType.value
|
245
|
+
if self.citations:
|
246
|
+
type_as_dict['citations'] = [c._as_dict_ for c in self.citations if c]
|
247
|
+
if self.mediaType:
|
248
|
+
type_as_dict['mediaType'] = self.mediaType
|
270
249
|
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
250
|
+
if self.mediator:
|
251
|
+
type_as_dict['mediator'] = self.mediator._as_dict_
|
252
|
+
if self.publisher:
|
253
|
+
type_as_dict['publisher'] = self.publisher._as_dict_ #TODO Resource this
|
254
|
+
if self.authors:
|
255
|
+
type_as_dict['authors'] = [a._as_dict_ for a in self.authors if a]
|
256
|
+
if self.sources:
|
257
|
+
type_as_dict['sources'] = [s._as_dict_ for s in self.sources if s]
|
258
|
+
|
259
|
+
|
260
|
+
if self.analysis:
|
261
|
+
type_as_dict['analysis'] = self.analysis._as_dict_
|
262
|
+
if self.componentOf:
|
263
|
+
type_as_dict['componentOf'] = self.componentOf._as_dict_
|
264
|
+
if self.titles:
|
265
|
+
type_as_dict['titles'] = [t._as_dict_ for t in self.titles if t]
|
266
|
+
if self.notes:
|
267
|
+
type_as_dict['notes'] = [n._as_dict_ for n in self.notes if n]
|
268
|
+
if self.attribution:
|
269
|
+
type_as_dict['attribution'] = self.attribution._as_dict_
|
270
|
+
if self.rights:
|
271
|
+
type_as_dict['rights'] = [r._as_dict_ for r in self.rights if r]
|
272
|
+
|
273
|
+
if self.coverage:
|
274
|
+
type_as_dict['coverage'] = [c._as_dict_ for c in self.coverage if c]
|
275
|
+
|
276
|
+
if self.descriptions:
|
277
|
+
if not (isinstance(self.descriptions, list) and all(isinstance(x, TextValue) for x in self.descriptions)):
|
278
|
+
assert False
|
279
|
+
type_as_dict['descriptions'] = [d._as_dict_ for d in self.descriptions if d]
|
280
|
+
|
281
|
+
if self.identifiers:
|
282
|
+
type_as_dict['identifiers'] = self.identifiers._as_dict_
|
283
|
+
|
284
|
+
if self.created is not None:
|
285
|
+
type_as_dict['created'] = self.created
|
286
|
+
if self.modified is not None:
|
287
|
+
type_as_dict['modified'] = self.modified
|
288
|
+
if self.published is not None:
|
289
|
+
type_as_dict['published'] = self.published
|
290
|
+
|
291
|
+
if self.repository:
|
292
|
+
type_as_dict['repository'] = self.repository._as_dict_ #TODO Resource this
|
293
|
+
|
294
|
+
log.debug(f"'SourceDescription' serialized with fields: '{type_as_dict.keys()}'")
|
295
|
+
if type_as_dict == {}: log.warning("serializing and empty 'SourceDescription'")
|
296
|
+
return type_as_dict if type_as_dict != {} else None
|
295
297
|
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
)
|
298
|
+
|
299
|
+
@classmethod
|
300
|
+
def _from_json_(cls, data: Dict[str, Any], context: Any = None) -> "SourceDescription":
|
301
|
+
if not isinstance(data, dict):
|
302
|
+
raise TypeError(f"{cls.__name__}._from_json_ expected dict, got {type(data)}")
|
303
|
+
|
304
|
+
source_description_data: Dict[str, Any] = {}
|
305
|
+
|
306
|
+
# ── Scalars ──────────────────────────────────────────────────────────────
|
307
|
+
if (id_ := data.get("id")) is not None:
|
308
|
+
source_description_data["id"] = id_
|
309
|
+
if (media_type := data.get("mediaType")) is not None:
|
310
|
+
source_description_data["mediaType"] = media_type
|
311
|
+
if (mnc := data.get("max_note_count")) is not None:
|
312
|
+
source_description_data["max_note_count"] = int(mnc)
|
313
|
+
|
314
|
+
# resourceType (enum or similar)
|
315
|
+
if (rt := data.get("resourceType")) is not None:
|
316
|
+
source_description_data["resourceType"] = ResourceType(rt)
|
317
|
+
|
318
|
+
# about: URI (accept string or dict)
|
319
|
+
if (about := data.get("about")) is not None:
|
320
|
+
source_description_data["about"] = URI.from_url(about) if isinstance(about, str) else URI._from_json_(about, context)
|
321
|
+
|
322
|
+
# mediator / publisher can be Agent or Resource
|
323
|
+
if (mediator := data.get("mediator")) is not None:
|
324
|
+
if isinstance(mediator, dict) and any(k in mediator for k in ("names", "emails", "accounts", "addresses", "person")):
|
325
|
+
source_description_data["mediator"] = Agent._from_json_(mediator, context)
|
326
|
+
else:
|
327
|
+
source_description_data["mediator"] = Resource._from_json_(mediator, context)
|
328
|
+
|
329
|
+
if (publisher := data.get("publisher")) is not None:
|
330
|
+
if isinstance(publisher, dict) and any(k in publisher for k in ("names", "emails", "accounts", "addresses", "person")):
|
331
|
+
source_description_data["publisher"] = Agent._from_json_(publisher, context)
|
332
|
+
else:
|
333
|
+
source_description_data["publisher"] = Resource._from_json_(publisher, context)
|
334
|
+
|
335
|
+
# repository (Agent)
|
336
|
+
if (repo := data.get("repository")) is not None:
|
337
|
+
source_description_data["repository"] = Agent._from_json_(repo, context)
|
338
|
+
|
339
|
+
# analysis: Document | Resource (prefer Document when possible)
|
340
|
+
if (analysis := data.get("analysis")) is not None:
|
341
|
+
try:
|
342
|
+
source_description_data["analysis"] = Document._from_json_(analysis, context)
|
343
|
+
except Exception:
|
344
|
+
source_description_data["analysis"] = Resource._from_json_(analysis, context)
|
345
|
+
|
346
|
+
# componentOf: SourceReference
|
347
|
+
if (component := data.get("componentOf")) is not None:
|
348
|
+
source_description_data["componentOf"] = SourceReference._from_json_(component, context)
|
349
|
+
|
350
|
+
# identifiers
|
351
|
+
if (identifiers := data.get("identifiers")) is not None:
|
352
|
+
source_description_data["identifiers"] = IdentifierList._from_json_(identifiers, context)
|
353
|
+
|
354
|
+
# timestamps
|
355
|
+
if (created := data.get("created")) is not None:
|
356
|
+
source_description_data["created"] = created
|
357
|
+
if (modified := data.get("modified")) is not None:
|
358
|
+
source_description_data["modified"] = modified
|
359
|
+
if (published := data.get("published")) is not None:
|
360
|
+
source_description_data["published"] = published
|
361
|
+
|
362
|
+
# ── Lists ───────────────────────────────────────────────────────────────
|
363
|
+
if (citations := data.get("citations")) is not None:
|
364
|
+
source_description_data["citations"] = [SourceCitation._from_json_(c, context) for c in citations]
|
365
|
+
|
366
|
+
if (authors := data.get("authors")) is not None:
|
367
|
+
source_description_data["authors"] = [Resource._from_json_(a, context) for a in authors]
|
368
|
+
|
369
|
+
if (sources := data.get("sources")) is not None:
|
370
|
+
source_description_data["sources"] = [SourceReference._from_json_(s, context) for s in sources]
|
371
|
+
|
372
|
+
if (titles := data.get("titles")) is not None:
|
373
|
+
source_description_data["titles"] = [
|
374
|
+
(TextValue._from_json_(t, context) if isinstance(t, dict) else TextValue(t))
|
375
|
+
for t in titles
|
376
|
+
]
|
377
|
+
|
378
|
+
if (notes := data.get("notes")) is not None:
|
379
|
+
source_description_data["notes"] = [Note._from_json_(n, context) for n in notes]
|
380
|
+
|
381
|
+
if (rights := data.get("rights")) is not None:
|
382
|
+
source_description_data["rights"] = [Resource._from_json_(r, context) for r in rights]
|
383
|
+
|
384
|
+
if (coverage := data.get("coverage")) is not None:
|
385
|
+
source_description_data["coverage"] = [Coverage._from_json_(c, context) for c in coverage]
|
386
|
+
|
387
|
+
if (descriptions := data.get("descriptions")) is not None:
|
388
|
+
for d in descriptions:
|
389
|
+
if not isinstance(d, dict): assert False
|
390
|
+
source_description_data["descriptions"] = [
|
391
|
+
(TextValue._from_json_(d, context) if isinstance(d, dict) else TextValue(value="somethings fucked up"))
|
392
|
+
for d in descriptions
|
393
|
+
]
|
394
|
+
|
395
|
+
# attribution
|
396
|
+
if (attr := data.get("attribution")) is not None:
|
397
|
+
source_description_data["attribution"] = Attribution._from_json_(attr, context) if isinstance(attr, dict) else attr
|
398
|
+
|
399
|
+
return cls(**source_description_data)
|
313
400
|
|
314
401
|
|
gedcomx/source_reference.py
CHANGED
@@ -10,6 +10,15 @@ from .qualifier import Qualifier
|
|
10
10
|
from .resource import Resource
|
11
11
|
|
12
12
|
from .uri import URI
|
13
|
+
from .logging_hub import hub, logging
|
14
|
+
"""
|
15
|
+
======================================================================
|
16
|
+
Logging
|
17
|
+
======================================================================
|
18
|
+
"""
|
19
|
+
log = logging.getLogger("gedcomx")
|
20
|
+
serial_log = "gedcomx.serialization"
|
21
|
+
#=====================================================================
|
13
22
|
|
14
23
|
from collections.abc import Sized
|
15
24
|
|
@@ -52,7 +61,7 @@ class SourceReference:
|
|
52
61
|
version = 'http://gedcomx.org/conceptual-model/v1'
|
53
62
|
|
54
63
|
def __init__(self,
|
55
|
-
description:
|
64
|
+
description: SourceDescription | URI | None = None,
|
56
65
|
descriptionId: Optional[str] = None,
|
57
66
|
attribution: Optional[Attribution] = None,
|
58
67
|
qualifiers: Optional[List[Qualifier]] = None
|
@@ -85,23 +94,49 @@ class SourceReference:
|
|
85
94
|
|
86
95
|
@property
|
87
96
|
def _as_dict_(self):
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
97
|
+
with hub.use(serial_log):
|
98
|
+
log.debug(f"Serializing 'SourceReference' with 'descriptionId': '{self.descriptionId}'")
|
99
|
+
type_as_dict = {}
|
100
|
+
if self.description is not None:
|
101
|
+
type_as_dict['description']= URI(target=self.description)._as_dict_ if self.description is not None else None
|
102
|
+
if self.descriptionId is not None:
|
103
|
+
type_as_dict['descriptionId']= self.descriptionId.replace("\n"," ").replace("\r"," ") if self.descriptionId else None
|
104
|
+
if self.attribution is not None:
|
105
|
+
type_as_dict['attribution'] = self.attribution._as_dict_ if self.attribution else None
|
106
|
+
if self.qualifiers is not None and self.qualifiers != []:
|
107
|
+
type_as_dict['qualifiers'] = [qualifier.__as_dict__ for qualifier in self.qualifiers] if (self.qualifiers and len(self.qualifiers) > 0) else None
|
108
|
+
log.debug(f"'SourceReference' serialized with fields: {type_as_dict.keys()}")
|
109
|
+
if type_as_dict == {}: log.warning("serializing and empty 'SourceReference'")
|
110
|
+
return type_as_dict if type_as_dict != {} else None
|
96
111
|
|
97
112
|
@classmethod
|
98
|
-
def _from_json_(cls, data: dict):
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
""
|
103
|
-
|
104
|
-
|
113
|
+
def _from_json_(cls, data: dict, context=None) -> "SourceReference":
|
114
|
+
ref = {}
|
115
|
+
|
116
|
+
# Scalars
|
117
|
+
if (descriptionId := data.get("descriptionId")) is not None:
|
118
|
+
ref["descriptionId"] = descriptionId
|
119
|
+
|
120
|
+
# Objects (description could be URI or SourceDescription)
|
121
|
+
if (description := data.get("description")) is not None:
|
122
|
+
# if description is just a string, assume URI
|
123
|
+
if isinstance(description, str):
|
124
|
+
ref["description"] = URI(description)
|
125
|
+
elif isinstance(description, dict):
|
126
|
+
print(">>",description)
|
127
|
+
ref["description"] = Resource._from_json_(description, context)
|
128
|
+
assert False
|
129
|
+
else:
|
130
|
+
pass #TODO
|
131
|
+
#print(ref["descriptionId"])
|
132
|
+
|
133
|
+
if (attribution := data.get("attribution")) is not None:
|
134
|
+
ref["attribution"] = Attribution._from_json_(attribution, context)
|
135
|
+
|
136
|
+
if (qualifiers := data.get("qualifiers")) is not None:
|
137
|
+
ref["qualifiers"] = [Qualifier._from_json_(q, context) for q in qualifiers]
|
138
|
+
|
139
|
+
return cls(**ref)
|
105
140
|
|
106
141
|
|
107
142
|
|
gedcomx/subject.py
CHANGED
@@ -9,7 +9,7 @@ from typing import List, Optional
|
|
9
9
|
|
10
10
|
Created: 2025-08-25
|
11
11
|
Updated:
|
12
|
-
- 2025-
|
12
|
+
- 2025-09-03: _from_json_ refactor
|
13
13
|
|
14
14
|
======================================================================
|
15
15
|
"""
|
@@ -22,11 +22,21 @@ GEDCOM Module Types
|
|
22
22
|
from .attribution import Attribution
|
23
23
|
from .conclusion import ConfidenceLevel, Conclusion
|
24
24
|
from .evidence_reference import EvidenceReference
|
25
|
-
from .Extensions.rs10.rsLink import
|
25
|
+
from .Extensions.rs10.rsLink import _rsLinks
|
26
26
|
from .identifier import Identifier, IdentifierList
|
27
|
+
from .logging_hub import hub, logging
|
27
28
|
from .note import Note
|
28
29
|
from .resource import Resource
|
29
30
|
from .source_reference import SourceReference
|
31
|
+
from. uri import URI
|
32
|
+
"""
|
33
|
+
======================================================================
|
34
|
+
Logging
|
35
|
+
======================================================================
|
36
|
+
"""
|
37
|
+
log = logging.getLogger("gedcomx")
|
38
|
+
serial_log = "gedcomx.serialization"
|
39
|
+
#=====================================================================
|
30
40
|
|
31
41
|
|
32
42
|
class Subject(Conclusion):
|
@@ -46,7 +56,7 @@ class Subject(Conclusion):
|
|
46
56
|
media: Optional[List[SourceReference]] = [],
|
47
57
|
identifiers: Optional[IdentifierList] = None,
|
48
58
|
uri: Optional[Resource] = None,
|
49
|
-
links: Optional[
|
59
|
+
links: Optional[_rsLinks] = None) -> None:
|
50
60
|
super().__init__(id, lang, sources, analysis, notes, confidence, attribution,links=links)
|
51
61
|
self.extracted = extracted
|
52
62
|
self.evidence = evidence
|
@@ -63,15 +73,50 @@ class Subject(Conclusion):
|
|
63
73
|
|
64
74
|
@property
|
65
75
|
def _as_dict_(self):
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
76
|
+
with hub.use(serial_log):
|
77
|
+
log.debug(f"Serializing 'Subject' with id: '{self.id}'")
|
78
|
+
type_as_dict = super()._as_dict_ # Start with base class fields
|
79
|
+
if type_as_dict is None: type_as_dict = {}
|
80
|
+
if self.extracted:
|
81
|
+
type_as_dict["extracted"] = self.extracted
|
82
|
+
if self.evidence:
|
83
|
+
type_as_dict["evidence"] = [evidence_ref for evidence_ref in self.evidence] if self.evidence else None
|
84
|
+
if self.media:
|
85
|
+
type_as_dict["media"] = [media for media in self.media] if self.media else None
|
86
|
+
if self.identifiers:
|
87
|
+
type_as_dict["identifiers"] = self.identifiers._as_dict_ if self.identifiers else None
|
88
|
+
log.debug(f"'Subject' serialized with fields: '{type_as_dict.keys()}'")
|
89
|
+
if type_as_dict == {} or len(type_as_dict.keys()) == 0: log.warning("serializing and empty 'Subject' Object")
|
75
90
|
|
76
|
-
}
|
77
|
-
|
91
|
+
return type_as_dict if type_as_dict != {} else None
|
92
|
+
|
93
|
+
|
94
|
+
@classmethod
|
95
|
+
def _dict_from_json_(cls, data: dict, context = None) -> dict:
|
96
|
+
subject_data = Conclusion._dict_from_json_(data,context)
|
97
|
+
|
98
|
+
# Bool
|
99
|
+
if (extracted := data.get("extracted")) is not None:
|
100
|
+
# cast to bool in case JSON gives "true"/"false" as string
|
101
|
+
if isinstance(extracted, str):
|
102
|
+
subject_data["extracted"] = extracted.lower() == "true"
|
103
|
+
else:
|
104
|
+
subject_data["extracted"] = bool(extracted)
|
105
|
+
|
106
|
+
# Lists
|
107
|
+
if (evidence := data.get("evidence")) is not None:
|
108
|
+
subject_data["evidence"] = [EvidenceReference._from_json_(e, context) for e in evidence]
|
109
|
+
|
110
|
+
if (media := data.get("media")) is not None:
|
111
|
+
subject_data["media"] = [SourceReference._from_json_(m, context) for m in media]
|
112
|
+
|
113
|
+
# Identifiers
|
114
|
+
if (identifiers := data.get("identifiers")) is not None:
|
115
|
+
subject_data["identifiers"] = IdentifierList._from_json_(identifiers, context)
|
116
|
+
|
117
|
+
# URI
|
118
|
+
if (uri := data.get("uri")) is not None:
|
119
|
+
subject_data["uri"] = URI(uri)
|
120
|
+
|
121
|
+
#return cls(**conclusion)
|
122
|
+
return subject_data
|
gedcomx/textvalue.py
CHANGED
@@ -1,35 +1,89 @@
|
|
1
|
-
from typing import Optional
|
1
|
+
from typing import Any, Optional, Dict
|
2
|
+
|
3
|
+
"""
|
4
|
+
======================================================================
|
5
|
+
Project: Gedcom-X
|
6
|
+
File: textvalue.py
|
7
|
+
Author: David J. Cartwright
|
8
|
+
Purpose:
|
9
|
+
|
10
|
+
Created: 2025-08-25
|
11
|
+
Updated:
|
12
|
+
- 2025-09-03 _from_json_ refactor
|
13
|
+
- 2025-09-04 added _str_ and _repr_ dunders
|
14
|
+
|
15
|
+
======================================================================
|
16
|
+
"""
|
17
|
+
|
18
|
+
"""
|
19
|
+
======================================================================
|
20
|
+
GEDCOM Module Types
|
21
|
+
======================================================================
|
22
|
+
"""
|
23
|
+
from .logging_hub import hub, logging
|
24
|
+
"""
|
25
|
+
======================================================================
|
26
|
+
Logging
|
27
|
+
======================================================================
|
28
|
+
"""
|
29
|
+
log = logging.getLogger("gedcomx")
|
30
|
+
serial_log = "gedcomx.serialization"
|
31
|
+
#=====================================================================
|
2
32
|
|
3
33
|
class TextValue:
|
4
34
|
identifier = 'http://gedcomx.org/v1/TextValue'
|
5
35
|
version = 'http://gedcomx.org/conceptual-model/v1'
|
6
36
|
|
7
|
-
def __init__(self, value: Optional[str] = None, lang: Optional[str] =
|
37
|
+
def __init__(self, value: Optional[str] = None, lang: Optional[str] = None) -> None:
|
8
38
|
self.lang = lang
|
9
39
|
self.value = value
|
10
40
|
|
11
41
|
def _append_to_value(self, value_to_append):
|
12
42
|
if not isinstance(value_to_append, str):
|
13
43
|
raise ValueError(f"Cannot append object of type {type(value_to_append)}.")
|
14
|
-
self.value
|
44
|
+
if self.value is None: self.value = value_to_append
|
45
|
+
else: self.value += ' ' + value_to_append
|
15
46
|
|
16
47
|
@property
|
17
48
|
def _as_dict_(self):
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
49
|
+
from .serialization import Serialization
|
50
|
+
return Serialization.serialize(self)
|
51
|
+
type_as_dict = {}
|
52
|
+
if self.lang is not None: type_as_dict['lang'] = self.lang
|
53
|
+
if self.value is not None: type_as_dict['value'] = self.value
|
54
|
+
return type_as_dict if type_as_dict != {} else None
|
22
55
|
|
23
56
|
def __str__(self):
|
24
57
|
return f"{self.value} ({self.lang})"
|
25
58
|
|
59
|
+
def __eq__(self, other: object) -> bool:
|
60
|
+
if isinstance(other,TextValue):
|
61
|
+
if self.value.strip() == other.value.strip():
|
62
|
+
return True
|
63
|
+
return False
|
64
|
+
|
26
65
|
# ...existing code...
|
27
66
|
|
28
67
|
@classmethod
|
29
|
-
def _from_json_(cls, data:
|
68
|
+
def _from_json_(cls, data: Any, context: Any = None) -> "TextValue":
|
30
69
|
"""
|
31
|
-
Create a TextValue
|
70
|
+
Create a TextValue from JSON.
|
71
|
+
- Accepts a dict like {"value": "...", "lang": "en"} or a plain string shorthand.
|
72
|
+
- Defaults lang to "en" if not provided.
|
32
73
|
"""
|
33
|
-
|
34
|
-
|
35
|
-
|
74
|
+
if not isinstance(data, dict):
|
75
|
+
raise TypeError(f"{cls.__name__}._from_json_ expected dict or str, got {type(data)}")
|
76
|
+
|
77
|
+
text_value_data: Dict[str, Any] = {}
|
78
|
+
|
79
|
+
if (v := data.get("value")) is None:
|
80
|
+
text_value_data["value"] = v
|
81
|
+
if (l := data.get("lang")) is None:
|
82
|
+
text_value_data["lang"] = l
|
83
|
+
|
84
|
+
return cls(**text_value_data)
|
85
|
+
|
86
|
+
def __repr__(self) -> str:
|
87
|
+
# Debug-friendly: unambiguous constructor-style representation
|
88
|
+
cls = self.__class__.__name__
|
89
|
+
return f"{cls}(value={self.value!r}, lang={self.lang!r})"
|