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/name.py
CHANGED
@@ -11,6 +11,7 @@ from typing import List,Optional
|
|
11
11
|
Created: 2025-08-25
|
12
12
|
Updated:
|
13
13
|
- 2025-08-31: _as_dict_ to only create entries in dict for fields that hold data
|
14
|
+
- 2025-09-03: _from_json_ refactor
|
14
15
|
|
15
16
|
======================================================================
|
16
17
|
"""
|
@@ -25,10 +26,19 @@ from .attribution import Attribution
|
|
25
26
|
from .conclusion import Conclusion, ConfidenceLevel
|
26
27
|
from .date import Date
|
27
28
|
from .document import Document
|
29
|
+
from .Extensions.rs10.rsLink import _rsLinks
|
28
30
|
from .note import Note
|
29
31
|
from .resource import Resource
|
30
32
|
from .source_reference import SourceReference
|
31
|
-
|
33
|
+
from .logging_hub import hub, logging
|
34
|
+
"""
|
35
|
+
======================================================================
|
36
|
+
Logging
|
37
|
+
======================================================================
|
38
|
+
"""
|
39
|
+
log = logging.getLogger("gedcomx")
|
40
|
+
serial_log = "gedcomx.serialization"
|
41
|
+
#=====================================================================
|
32
42
|
|
33
43
|
|
34
44
|
class NameType(Enum):
|
@@ -39,6 +49,7 @@ class NameType(Enum):
|
|
39
49
|
AdoptiveName = "http://gedcomx.org/AdoptiveName"
|
40
50
|
FormalName = "http://gedcomx.org/FormalName"
|
41
51
|
ReligiousName = "http://gedcomx.org/ReligiousName"
|
52
|
+
Other = "other"
|
42
53
|
|
43
54
|
@property
|
44
55
|
def description(self):
|
@@ -153,14 +164,28 @@ class NamePart:
|
|
153
164
|
type_as_dict['value'] = self.value
|
154
165
|
if self.qualifiers:
|
155
166
|
type_as_dict['qualifiers'] = [q.value for q in self.qualifiers]
|
156
|
-
|
167
|
+
return type_as_dict if type_as_dict != {} else None
|
157
168
|
|
158
169
|
@classmethod
|
159
|
-
def _from_json_(cls,data):
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
170
|
+
def _from_json_(cls, data: dict, context=None) -> "NamePart":
|
171
|
+
if not isinstance(data, dict):
|
172
|
+
raise TypeError(f"{cls.__name__}._from_json_ expected dict, got {type(data)}")
|
173
|
+
|
174
|
+
name_part = {}
|
175
|
+
|
176
|
+
# Enum / type
|
177
|
+
if (typ := data.get("type")) is not None:
|
178
|
+
name_part["type"] = NamePartType(typ)
|
179
|
+
|
180
|
+
# String value
|
181
|
+
if (val := data.get("value")) is not None:
|
182
|
+
name_part["value"] = val
|
183
|
+
|
184
|
+
# List of qualifiers
|
185
|
+
if (quals := data.get("qualifiers")) is not None:
|
186
|
+
name_part["qualifiers"] = [NamePartQualifier(q) for q in quals]
|
187
|
+
|
188
|
+
return cls(**name_part)
|
164
189
|
|
165
190
|
def __eq__(self, other):
|
166
191
|
if not isinstance(other, NamePart):
|
@@ -233,17 +258,29 @@ class NameForm:
|
|
233
258
|
if self.fullText:
|
234
259
|
type_as_dict['fullText'] = self.fullText
|
235
260
|
if self.parts:
|
236
|
-
type_as_dict['parts'] = [part._as_dict_ for part in self.parts if part]
|
261
|
+
type_as_dict['parts'] = [part._as_dict_ for part in self.parts if part is not None]
|
262
|
+
return type_as_dict if type_as_dict != {} else None
|
237
263
|
return Serialization.serialize_dict(type_as_dict)
|
238
264
|
|
239
265
|
@classmethod
|
240
|
-
def _from_json_(cls, data: dict) -> "NameForm":
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
266
|
+
def _from_json_(cls, data: dict, context=None) -> "NameForm":
|
267
|
+
if not isinstance(data, dict):
|
268
|
+
raise TypeError(f"{cls.__name__}._from_json_ expected dict, got {type(data)}")
|
269
|
+
|
270
|
+
name_form = {}
|
271
|
+
|
272
|
+
# Scalars
|
273
|
+
if (lang := data.get("lang")) is not None:
|
274
|
+
name_form["lang"] = lang
|
275
|
+
|
276
|
+
if (full := data.get("fullText")) is not None:
|
277
|
+
name_form["fullText"] = full
|
278
|
+
|
279
|
+
# List of parts
|
280
|
+
if (parts := data.get("parts")) is not None:
|
281
|
+
name_form["parts"] = [NamePart._from_json_(p, context) for p in parts if p]
|
282
|
+
|
283
|
+
return cls(**name_form)
|
247
284
|
|
248
285
|
def _fulltext_parts(self):
|
249
286
|
pass
|
@@ -315,8 +352,9 @@ class Name(Conclusion):
|
|
315
352
|
attribution: Optional[Attribution] = None,
|
316
353
|
type: Optional[NameType] = None,
|
317
354
|
nameForms: Optional[List[NameForm]]= None,
|
318
|
-
date: Optional[Date] = None
|
319
|
-
|
355
|
+
date: Optional[Date] = None,
|
356
|
+
links: Optional[_rsLinks] = None) -> None:
|
357
|
+
super().__init__(id, lang, sources, analysis, notes, confidence, attribution,links=links)
|
320
358
|
self.type = type
|
321
359
|
self.nameForms = nameForms if nameForms else []
|
322
360
|
self.date = date
|
@@ -330,8 +368,7 @@ class Name(Conclusion):
|
|
330
368
|
|
331
369
|
@property
|
332
370
|
def _as_dict_(self):
|
333
|
-
|
334
|
-
type_as_dict = super()._as_dict_
|
371
|
+
type_as_dict = super()._as_dict_ or {}
|
335
372
|
if self.type:
|
336
373
|
type_as_dict['type'] = getattr(self.type, 'value', self.type)
|
337
374
|
if self.nameForms:
|
@@ -339,23 +376,27 @@ class Name(Conclusion):
|
|
339
376
|
if self.date:
|
340
377
|
type_as_dict['date'] = self.date._as_dict_
|
341
378
|
|
342
|
-
return
|
379
|
+
return type_as_dict if type_as_dict != {} else None
|
380
|
+
|
343
381
|
|
344
382
|
@classmethod
|
345
|
-
def _from_json_(cls, data: dict) -> "Name":
|
383
|
+
def _from_json_(cls, data: dict,context = None) -> "Name":
|
346
384
|
"""Build a Name from JSON-like dict."""
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
)
|
385
|
+
name = Conclusion._dict_from_json_(data)
|
386
|
+
|
387
|
+
# Enum
|
388
|
+
if (typ := data.get("type")) is not None:
|
389
|
+
name["type"] = NameType(typ)
|
390
|
+
|
391
|
+
# List
|
392
|
+
if (forms := data.get("nameForms")) is not None:
|
393
|
+
name["nameForms"] = [NameForm._from_json_(f, context) for f in forms]
|
394
|
+
|
395
|
+
# Object
|
396
|
+
if (date := data.get("date")) is not None:
|
397
|
+
name["date"] = Date._from_json_(date, context)
|
398
|
+
|
399
|
+
return cls(**name)
|
359
400
|
|
360
401
|
def __str__(self) -> str:
|
361
402
|
"""Return a human-readable string for the Name-like object."""
|
gedcomx/note.py
CHANGED
@@ -1,6 +1,33 @@
|
|
1
|
-
from typing import Optional
|
1
|
+
from typing import Any, Optional
|
2
|
+
"""
|
3
|
+
======================================================================
|
4
|
+
Project: Gedcom-X
|
5
|
+
File: note.py
|
6
|
+
Author: David J. Cartwright
|
7
|
+
Purpose: Python Object representation of GedcomX Name, NameType, NameForm, NamePart Types
|
2
8
|
|
9
|
+
Created: 2025-08-25
|
10
|
+
Updated:
|
11
|
+
- 2025-09-03: _from_json_ refactor
|
12
|
+
|
13
|
+
======================================================================
|
14
|
+
"""
|
15
|
+
|
16
|
+
"""
|
17
|
+
======================================================================
|
18
|
+
GEDCOM Module Types
|
19
|
+
======================================================================
|
20
|
+
"""
|
3
21
|
from .attribution import Attribution
|
22
|
+
from .logging_hub import hub, logging
|
23
|
+
"""
|
24
|
+
======================================================================
|
25
|
+
Logging
|
26
|
+
======================================================================
|
27
|
+
"""
|
28
|
+
log = logging.getLogger("gedcomx")
|
29
|
+
serial_log = "gedcomx.serialization"
|
30
|
+
#=====================================================================
|
4
31
|
|
5
32
|
class Note:
|
6
33
|
identifier = 'http://gedcomx.org/v1/Note'
|
@@ -35,6 +62,7 @@ class Note:
|
|
35
62
|
if self.attribution:
|
36
63
|
# If attribution exposes `_as_dict_` as a property, use it; otherwise include as-is
|
37
64
|
type_as_dict["attribution"] = getattr(self.attribution, "_as_dict_", self.attribution)
|
65
|
+
return type_as_dict if type_as_dict != {} else None
|
38
66
|
return Serialization.serialize_dict(type_as_dict)
|
39
67
|
|
40
68
|
def __eq__(self, other):
|
@@ -52,22 +80,26 @@ class Note:
|
|
52
80
|
)
|
53
81
|
|
54
82
|
@classmethod
|
55
|
-
def _from_json_(cls, data:
|
56
|
-
""
|
57
|
-
|
58
|
-
|
59
|
-
# Basic scalar fields
|
60
|
-
lang = data.get('lang', 'en')
|
61
|
-
text = data.get('text')
|
62
|
-
subject = data.get('subject')
|
63
|
-
# Add other fields as needed
|
83
|
+
def _from_json_(cls, data: Any, context=None) -> "Note":
|
84
|
+
# Allow shorthand: "some note text"
|
85
|
+
#if isinstance(data, str):
|
86
|
+
# return cls(text=data)
|
64
87
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
)
|
88
|
+
if not isinstance(data, dict):
|
89
|
+
raise TypeError(f"{cls.__name__}._from_json_ expected dict or str, got {type(data)}")
|
90
|
+
|
91
|
+
obj: dict[str, Any] = {}
|
92
|
+
|
93
|
+
# Scalars
|
94
|
+
if (lang := data.get("lang")) is not None:
|
95
|
+
obj["lang"] = lang
|
96
|
+
if (subject := data.get("subject")) is not None:
|
97
|
+
obj["subject"] = subject
|
98
|
+
if (text := data.get("text")) is not None:
|
99
|
+
obj["text"] = text
|
100
|
+
|
101
|
+
# Object
|
102
|
+
if (attr := data.get("attribution")) is not None:
|
103
|
+
obj["attribution"] = Attribution._from_json_(attr, context)
|
72
104
|
|
73
|
-
return
|
105
|
+
return cls(**obj)
|
gedcomx/online_account.py
CHANGED
@@ -1,6 +1,15 @@
|
|
1
1
|
from typing import Optional
|
2
2
|
|
3
3
|
from .resource import Resource
|
4
|
+
from .logging_hub import hub, logging
|
5
|
+
"""
|
6
|
+
======================================================================
|
7
|
+
Logging
|
8
|
+
======================================================================
|
9
|
+
"""
|
10
|
+
log = logging.getLogger("gedcomx")
|
11
|
+
serial_log = "gedcomx.serialization"
|
12
|
+
#=====================================================================
|
4
13
|
|
5
14
|
class OnlineAccount:
|
6
15
|
identifier = 'http://gedcomx.org/v1/OnlineAccount'
|
gedcomx/person.py
CHANGED
@@ -11,6 +11,7 @@ from urllib.parse import urljoin
|
|
11
11
|
Created: 2025-08-25
|
12
12
|
Updated:
|
13
13
|
- 2025-08-31: _as_dict_ to only create entries in dict for fields that hold data
|
14
|
+
- 2025-09-03: _from_json_ refactor
|
14
15
|
|
15
16
|
======================================================================
|
16
17
|
"""
|
@@ -24,17 +25,31 @@ from .attribution import Attribution
|
|
24
25
|
from .conclusion import ConfidenceLevel
|
25
26
|
from .date import Date
|
26
27
|
from .evidence_reference import EvidenceReference
|
27
|
-
from .Extensions.rs10.rsLink import
|
28
|
+
from .Extensions.rs10.rsLink import _rsLinks
|
29
|
+
from .extensible import Extensible
|
28
30
|
from .fact import Fact, FactType
|
29
31
|
from .gender import Gender, GenderType
|
30
32
|
from .identifier import IdentifierList
|
33
|
+
from .logging_hub import hub, logging
|
31
34
|
from .name import Name, QuickName
|
32
35
|
from .note import Note
|
33
36
|
from .resource import Resource
|
34
37
|
from .source_reference import SourceReference
|
35
38
|
from .subject import Subject
|
39
|
+
from .logging_hub import hub, logging
|
40
|
+
"""
|
41
|
+
======================================================================
|
42
|
+
Logging
|
43
|
+
======================================================================
|
44
|
+
"""
|
45
|
+
log = logging.getLogger("gedcomx")
|
46
|
+
serial_log = "gedcomx.serialization"
|
47
|
+
deserial_log = "degedcomx.serialization"
|
48
|
+
#=====================================================================
|
49
|
+
|
36
50
|
|
37
|
-
|
51
|
+
|
52
|
+
class Person(Extensible,Subject):
|
38
53
|
"""A person in the system.
|
39
54
|
|
40
55
|
Args:
|
@@ -60,12 +75,12 @@ class Person(Subject):
|
|
60
75
|
evidence: Optional[List[EvidenceReference]] = None,
|
61
76
|
media: Optional[List[SourceReference]] = None,
|
62
77
|
identifiers: Optional[IdentifierList] = None,
|
63
|
-
private: Optional[bool] =
|
78
|
+
private: Optional[bool] = None,
|
64
79
|
gender: Optional[Gender] = Gender(type=GenderType.Unknown),
|
65
80
|
names: Optional[List[Name]] = None,
|
66
81
|
facts: Optional[List[Fact]] = None,
|
67
|
-
living: Optional[bool] =
|
68
|
-
links: Optional[
|
82
|
+
living: Optional[bool] = None,
|
83
|
+
links: Optional[_rsLinks] = None,
|
69
84
|
uri: Optional[Resource] = None) -> None:
|
70
85
|
# Call superclass initializer if needed
|
71
86
|
super().__init__(id, lang, sources, analysis, notes, confidence, attribution, extracted, evidence, media, identifiers,links=links,uri=uri)
|
@@ -130,29 +145,33 @@ class Person(Subject):
|
|
130
145
|
@property
|
131
146
|
def _as_dict_(self):
|
132
147
|
from .serialization import Serialization
|
133
|
-
|
134
|
-
|
135
|
-
type_as_dict['private'] = self.private
|
136
|
-
if self.living is not None:
|
137
|
-
type_as_dict['living'] = self.living
|
138
|
-
if self.gender:
|
139
|
-
type_as_dict['gender'] = self.gender._as_dict_
|
140
|
-
if self.names:
|
141
|
-
type_as_dict['names'] = [n._as_dict_ for n in self.names if n]
|
142
|
-
if self.facts:
|
143
|
-
type_as_dict['facts'] = [f._as_dict_ for f in self.facts if f]
|
144
|
-
if self.uri:
|
145
|
-
type_as_dict['uri'] = self.uri._as_dict_
|
146
|
-
|
147
|
-
return Serialization.serialize_dict(type_as_dict)
|
148
|
+
return Serialization.serialize(self)
|
149
|
+
|
148
150
|
|
149
151
|
@classmethod
|
150
|
-
def _from_json_(cls, data: dict):
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
152
|
+
def _from_json_(cls, data: dict, context = None) -> "Person":
|
153
|
+
def _to_bool(v):
|
154
|
+
if isinstance(v, str):
|
155
|
+
return v.strip().lower() in ("1", "true", "yes", "y", "t")
|
156
|
+
return bool(v)
|
157
|
+
with hub.use(deserial_log):
|
158
|
+
log.debug(f"Deserializing a Person")
|
159
|
+
person_data: dict = Subject._dict_from_json_(data,context)
|
160
|
+
if (private := data.get("private")) is not None:
|
161
|
+
person_data["private"] = _to_bool(private)
|
162
|
+
if (living := data.get("living")) is not None:
|
163
|
+
person_data["living"] = _to_bool(living)
|
164
|
+
if (gender := data.get("gender")) is not None:
|
165
|
+
person_data["gender"] = Gender._from_json_(gender, context)
|
166
|
+
if (names := data.get("names")) is not None:
|
167
|
+
person_data["names"] = [Name._from_json_(n, context) for n in names]
|
168
|
+
if (facts := data.get("facts")) is not None:
|
169
|
+
person_data["facts"] = [Fact._from_json_(f, context) for f in facts]
|
170
|
+
|
171
|
+
diff = data.keys() - person_data.keys()
|
172
|
+
log.debug(f"Desserialization found keys {diff} that are not in deserialization list")
|
173
|
+
|
174
|
+
return cls(**person_data)
|
156
175
|
|
157
176
|
@classmethod
|
158
177
|
def from_familysearch(cls, pid: str, token: str, *, base_url: Optional[str] = None):
|
@@ -186,7 +205,7 @@ class Person(Subject):
|
|
186
205
|
raise ValueError(f"FamilySearch returned no person for PID {pid}")
|
187
206
|
|
188
207
|
# Keep your existing deserialization helper
|
189
|
-
return
|
208
|
+
return Person._from_json_(person_json)
|
190
209
|
|
191
210
|
class QuickPerson:
|
192
211
|
"""A GedcomX Person Data Type created with basic information.
|
gedcomx/place_description.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import List, Optional
|
1
|
+
from typing import Any, Dict, List, Optional
|
2
2
|
|
3
3
|
"""
|
4
4
|
======================================================================
|
@@ -10,6 +10,7 @@ from typing import List, Optional
|
|
10
10
|
Created: 2025-08-25
|
11
11
|
Updated:
|
12
12
|
- 2025-09-01: filename PEP8 standard
|
13
|
+
- 2025-09-03: _from_json_ refactored
|
13
14
|
|
14
15
|
======================================================================
|
15
16
|
"""
|
@@ -23,6 +24,7 @@ from .attribution import Attribution
|
|
23
24
|
from .conclusion import ConfidenceLevel
|
24
25
|
from .date import Date
|
25
26
|
from .evidence_reference import EvidenceReference
|
27
|
+
from .Extensions.rs10.rsLink import _rsLinks #new
|
26
28
|
from .identifier import IdentifierList
|
27
29
|
from .note import Note
|
28
30
|
from .resource import Resource
|
@@ -30,6 +32,14 @@ from .source_reference import SourceReference
|
|
30
32
|
from .subject import Subject
|
31
33
|
from .textvalue import TextValue
|
32
34
|
from .uri import URI
|
35
|
+
from .logging_hub import hub, logging
|
36
|
+
"""
|
37
|
+
======================================================================
|
38
|
+
Logging
|
39
|
+
======================================================================
|
40
|
+
"""
|
41
|
+
log = logging.getLogger("gedcomx")
|
42
|
+
serial_log = "gedcomx.serialization"
|
33
43
|
#=====================================================================
|
34
44
|
|
35
45
|
|
@@ -78,9 +88,11 @@ class PlaceDescription(Subject):
|
|
78
88
|
latitude: Optional[float] = None,
|
79
89
|
longitude: Optional[float] = None,
|
80
90
|
temporalDescription: Optional[Date] = None,
|
81
|
-
spatialDescription: Optional[Resource] = None,
|
91
|
+
spatialDescription: Optional[Resource] = None,
|
92
|
+
links: Optional[_rsLinks] = None
|
93
|
+
) -> None:
|
82
94
|
|
83
|
-
super().__init__(id, lang, sources, analysis, notes, confidence, attribution, extracted, evidence, media, identifiers)
|
95
|
+
super().__init__(id, lang, sources, analysis, notes, confidence, attribution, extracted, evidence, media, identifiers,links=links)
|
84
96
|
self.names = names
|
85
97
|
self.type = type
|
86
98
|
self.place = place
|
@@ -93,7 +105,7 @@ class PlaceDescription(Subject):
|
|
93
105
|
@property
|
94
106
|
def _as_dict_(self):
|
95
107
|
from .serialization import Serialization
|
96
|
-
type_as_dict = super()._as_dict_
|
108
|
+
type_as_dict = super()._as_dict_ or {}
|
97
109
|
|
98
110
|
if self.names:
|
99
111
|
type_as_dict["names"] = [n._as_dict_ for n in self.names if n]
|
@@ -112,12 +124,46 @@ class PlaceDescription(Subject):
|
|
112
124
|
if self.spatialDescription:
|
113
125
|
type_as_dict["spatialDescription"] = self.spatialDescription._as_dict_
|
114
126
|
|
127
|
+
return type_as_dict if type_as_dict != {} else None
|
115
128
|
return Serialization.serialize_dict(type_as_dict)
|
116
129
|
|
117
130
|
@classmethod
|
118
|
-
def _from_json_(cls, data:
|
131
|
+
def _from_json_(cls, data: Any, context: Any = None) -> "PlaceDescription":
|
119
132
|
"""
|
120
133
|
Create a PlaceDescription instance from a JSON-dict (already parsed).
|
121
|
-
"""
|
122
|
-
|
123
|
-
|
134
|
+
"""
|
135
|
+
if not isinstance(data, dict):
|
136
|
+
raise TypeError(f"{cls.__name__}._from_json_ expected dict or str, got {type(data)}")
|
137
|
+
|
138
|
+
person_data: Dict[str, Any] = Subject._dict_from_json_(data,context)
|
139
|
+
|
140
|
+
# names (allow both list and a single 'name' alias)
|
141
|
+
if (names := data.get("names")) is not None:
|
142
|
+
person_data["names"] = [TextValue._from_json_(n, context) for n in names]
|
143
|
+
|
144
|
+
# type (string for now; promote to enum later)
|
145
|
+
if (typ := data.get("type")) is not None:
|
146
|
+
person_data["type"] = typ
|
147
|
+
|
148
|
+
# place: URI (accept string or dict)
|
149
|
+
if (pl := data.get("place")) is not None:
|
150
|
+
person_data["place"] = URI(pl)
|
151
|
+
|
152
|
+
# jurisdiction: Resource | PlaceDescription
|
153
|
+
if (jur := data.get("jurisdiction")) is not None:
|
154
|
+
person_data["jurisdiction"] = Resource._from_json_(jur, context)
|
155
|
+
|
156
|
+
# coordinates
|
157
|
+
if (lat := data.get("latitude")) is not None:
|
158
|
+
person_data["latitude"] = float(lat)
|
159
|
+
if (lon := data.get("longitude")) is not None:
|
160
|
+
person_data["longitude"] = float(lon)
|
161
|
+
|
162
|
+
# temporal / spatial descriptions
|
163
|
+
if (td := data.get("temporalDescription")) is not None:
|
164
|
+
person_data["temporalDescription"] = Date._from_json_(td, context)
|
165
|
+
|
166
|
+
if (sd := data.get("spatialDescription")) is not None:
|
167
|
+
person_data["spatialDescription"] = Resource._from_json_(sd, context)
|
168
|
+
|
169
|
+
return cls(**person_data)
|
gedcomx/place_reference.py
CHANGED
@@ -13,6 +13,7 @@ if TYPE_CHECKING:
|
|
13
13
|
Created: 2025-08-25
|
14
14
|
Updated:
|
15
15
|
- 2025-08-31: _as_dict_ to only create entries in dict for fields that hold data
|
16
|
+
- 2025-09-03: _from_json refactored
|
16
17
|
|
17
18
|
======================================================================
|
18
19
|
"""
|
@@ -23,6 +24,16 @@ GEDCOM Module Types
|
|
23
24
|
======================================================================
|
24
25
|
"""
|
25
26
|
from .resource import Resource
|
27
|
+
from .logging_hub import hub, logging
|
28
|
+
from .uri import URI
|
29
|
+
"""
|
30
|
+
======================================================================
|
31
|
+
Logging
|
32
|
+
======================================================================
|
33
|
+
"""
|
34
|
+
log = logging.getLogger("gedcomx")
|
35
|
+
serial_log = "gedcomx.serialization"
|
36
|
+
#=====================================================================
|
26
37
|
|
27
38
|
class PlaceReference:
|
28
39
|
"""defines a reference to a PlaceDescription.
|
@@ -39,24 +50,35 @@ class PlaceReference:
|
|
39
50
|
|
40
51
|
def __init__(self,
|
41
52
|
original: Optional[str] = None,
|
42
|
-
description: Optional["
|
53
|
+
description: Optional["URI | PlaceDescription"] = None) -> None:
|
43
54
|
self.original = original
|
44
|
-
self.description = description
|
55
|
+
self.description = description # descriptionRef
|
45
56
|
|
46
57
|
@property
|
47
58
|
def _as_dict_(self):
|
48
|
-
|
59
|
+
|
49
60
|
type_as_dict = {}
|
50
61
|
if self.original:
|
51
62
|
type_as_dict['original'] = self.original
|
52
63
|
if self.description:
|
53
|
-
type_as_dict['description'] = self.description._as_dict_
|
54
|
-
return
|
64
|
+
type_as_dict['description'] = URI(target=self.description)._as_dict_
|
65
|
+
return type_as_dict if type_as_dict != {} else None
|
66
|
+
|
55
67
|
|
56
68
|
@classmethod
|
57
|
-
def _from_json_(cls, data):
|
58
|
-
|
59
|
-
|
69
|
+
def _from_json_(cls, data, context=None) -> "PlaceReference":
|
70
|
+
if not isinstance(data, dict):
|
71
|
+
raise TypeError(f"{cls.__name__}._from_json_ expected dict or str, got {type(data)}")
|
72
|
+
|
73
|
+
place_reference_data = {}
|
74
|
+
|
75
|
+
# Scalars
|
76
|
+
if (orig := data.get("original")) is not None:
|
77
|
+
place_reference_data["original"] = orig
|
78
|
+
if (desc := data.get("description")) is not None:
|
79
|
+
place_reference_data["description"] = URI._from_json_(desc, context)
|
80
|
+
|
81
|
+
return cls(**place_reference_data)
|
60
82
|
|
61
83
|
|
62
84
|
|
gedcomx/qualifier.py
CHANGED
@@ -11,9 +11,19 @@ from typing import Optional
|
|
11
11
|
Updated:
|
12
12
|
- 2025-08-31: _as_dict_ to only create entries in dict for fields that
|
13
13
|
hold data, updated _from_json
|
14
|
+
- 2025-09-03: _from_json_ refactor
|
14
15
|
|
15
16
|
======================================================================
|
16
17
|
"""
|
18
|
+
from .logging_hub import hub, logging
|
19
|
+
"""
|
20
|
+
======================================================================
|
21
|
+
Logging
|
22
|
+
======================================================================
|
23
|
+
"""
|
24
|
+
log = logging.getLogger("gedcomx")
|
25
|
+
serial_log = "gedcomx.serialization"
|
26
|
+
#=====================================================================
|
17
27
|
|
18
28
|
class Qualifier:
|
19
29
|
"""defines the data structure used to supply additional details, annotations,
|
@@ -45,10 +55,16 @@ class Qualifier:
|
|
45
55
|
if self.value:
|
46
56
|
type_as_dict["value"] = self.value
|
47
57
|
|
58
|
+
return type_as_dict if type_as_dict != {} else None
|
48
59
|
return Serialization.serialize_dict(type_as_dict)
|
49
60
|
|
50
61
|
@classmethod
|
51
|
-
def
|
52
|
-
|
53
|
-
|
62
|
+
def _from_json_(cls, data: dict, context=None) -> "Qualifier":
|
63
|
+
if not isinstance(data, dict):
|
64
|
+
raise TypeError(f"{cls.__name__}._from_json_ expected dict, got {type(data)}")
|
65
|
+
|
66
|
+
name = data.get("name")
|
67
|
+
value = data.get("value")
|
68
|
+
|
69
|
+
return cls(name=name, value=value)
|
54
70
|
|