gedcom-x 0.5.7__py3-none-any.whl → 0.5.9__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.
Files changed (69) hide show
  1. {gedcom_x-0.5.7.dist-info → gedcom_x-0.5.9.dist-info}/METADATA +1 -1
  2. gedcom_x-0.5.9.dist-info/RECORD +56 -0
  3. gedcomx/Extensions/rs10/rsLink.py +110 -60
  4. gedcomx/TopLevelTypeCollection.py +1 -1
  5. gedcomx/__init__.py +43 -42
  6. gedcomx/address.py +217 -0
  7. gedcomx/{Agent.py → agent.py} +107 -34
  8. gedcomx/attribution.py +115 -0
  9. gedcomx/{Conclusion.py → conclusion.py} +120 -51
  10. gedcomx/{Converter.py → converter.py} +261 -116
  11. gedcomx/coverage.py +64 -0
  12. gedcomx/{Date.py → date.py} +43 -9
  13. gedcomx/{Document.py → document.py} +60 -12
  14. gedcomx/{Event.py → event.py} +88 -31
  15. gedcomx/evidence_reference.py +20 -0
  16. gedcomx/{Fact.py → fact.py} +81 -74
  17. gedcomx/{Gedcom.py → gedcom.py} +10 -0
  18. gedcomx/{Gedcom5x.py → gedcom5x.py} +31 -21
  19. gedcomx/gedcom7/Exceptions.py +9 -0
  20. gedcomx/gedcom7/GedcomStructure.py +94 -0
  21. gedcomx/gedcom7/Specification.py +347 -0
  22. gedcomx/gedcom7/__init__.py +26 -0
  23. gedcomx/gedcom7/g7interop.py +205 -0
  24. gedcomx/gedcom7/gedcom7.py +160 -0
  25. gedcomx/gedcom7/logger.py +19 -0
  26. gedcomx/{GedcomX.py → gedcomx.py} +109 -106
  27. gedcomx/gender.py +91 -0
  28. gedcomx/group.py +72 -0
  29. gedcomx/{Identifier.py → identifier.py} +48 -21
  30. gedcomx/{LoggingHub.py → logging_hub.py} +19 -0
  31. gedcomx/{Mutations.py → mutations.py} +59 -30
  32. gedcomx/{Name.py → name.py} +88 -47
  33. gedcomx/note.py +105 -0
  34. gedcomx/online_account.py +19 -0
  35. gedcomx/{Person.py → person.py} +61 -41
  36. gedcomx/{PlaceDescription.py → place_description.py} +71 -23
  37. gedcomx/{PlaceReference.py → place_reference.py} +32 -10
  38. gedcomx/{Qualifier.py → qualifier.py} +20 -4
  39. gedcomx/relationship.py +156 -0
  40. gedcomx/resource.py +112 -0
  41. gedcomx/serialization.py +794 -0
  42. gedcomx/source_citation.py +37 -0
  43. gedcomx/source_description.py +401 -0
  44. gedcomx/{SourceReference.py → source_reference.py} +56 -21
  45. gedcomx/subject.py +122 -0
  46. gedcomx/textvalue.py +89 -0
  47. gedcomx/{Translation.py → translation.py} +4 -4
  48. gedcomx/uri.py +273 -0
  49. gedcom_x-0.5.7.dist-info/RECORD +0 -49
  50. gedcomx/Address.py +0 -131
  51. gedcomx/Attribution.py +0 -91
  52. gedcomx/Coverage.py +0 -37
  53. gedcomx/EvidenceReference.py +0 -11
  54. gedcomx/Gender.py +0 -65
  55. gedcomx/Group.py +0 -37
  56. gedcomx/Note.py +0 -73
  57. gedcomx/OnlineAccount.py +0 -10
  58. gedcomx/Relationship.py +0 -97
  59. gedcomx/Resource.py +0 -85
  60. gedcomx/Serialization.py +0 -816
  61. gedcomx/SourceCitation.py +0 -25
  62. gedcomx/SourceDescription.py +0 -314
  63. gedcomx/Subject.py +0 -59
  64. gedcomx/TextValue.py +0 -35
  65. gedcomx/URI.py +0 -105
  66. {gedcom_x-0.5.7.dist-info → gedcom_x-0.5.9.dist-info}/WHEEL +0 -0
  67. {gedcom_x-0.5.7.dist-info → gedcom_x-0.5.9.dist-info}/top_level.txt +0 -0
  68. /gedcomx/{Exceptions.py → exceptions.py} +0 -0
  69. /gedcomx/{ExtensibleEnum.py → extensible_enum.py} +0 -0
@@ -1,6 +1,37 @@
1
- from .Gedcom5x import Gedcom5xRecord
2
- from .Fact import Fact, FactType
3
- from .Event import Event, EventType
1
+
2
+ """
3
+ ======================================================================
4
+ Project: Gedcom-X
5
+ File: mutations.py
6
+ Author: David J. Cartwright
7
+ Purpose: Objects used to convert TAGs/Structues/Types from GEDCOM Versions
8
+ when simple parsing will not work. (complex or ambiguous structures)
9
+
10
+ Created: 2025-08-25
11
+ Updated:
12
+ - 2025-08-31: cleaned up imports and documentation
13
+ - 2025-09-01: filename PEP8 standard, imports changed accordingly
14
+
15
+ ======================================================================
16
+ """
17
+
18
+ """
19
+ ======================================================================
20
+ GEDCOM Module Types
21
+ ======================================================================
22
+ """
23
+ from .gedcom5x import Gedcom5xRecord
24
+ from .fact import Fact, FactType
25
+ from .event import Event, EventType
26
+ from .logging_hub import hub, logging
27
+ """
28
+ ======================================================================
29
+ Logging
30
+ ======================================================================
31
+ """
32
+ log = logging.getLogger("gedcomx")
33
+ serial_log = "gedcomx.serialization"
34
+ #=====================================================================
4
35
 
5
36
  fact_event_table = {
6
37
  # Person Fact / Event Types
@@ -178,33 +209,34 @@ fact_event_table = {
178
209
  }
179
210
 
180
211
  class GedcomXObject:
181
- def __init__(self,record: Gedcom5xRecord | None = None) -> None:
182
- self.created_with_tag: str = record.tag if record and isinstance(record, Gedcom5xRecord) else None
183
- self.created_at_level: int = record.level if record and isinstance(record, Gedcom5xRecord) else None
184
- self.created_at_line_number: int = record.line_number if record and isinstance(record, Gedcom5xRecord) else None
212
+ def __init__(self,record: Gedcom5xRecord) -> None:
213
+ self.record = record
214
+ self.created_with_tag: str | None = record.tag if record and isinstance(record, Gedcom5xRecord) else None
215
+ self.created_at_level: int | None = record.level if record and isinstance(record, Gedcom5xRecord) else None
216
+ self.created_at_line_number: int | None = record.line if record and isinstance(record, Gedcom5xRecord) else None
185
217
 
186
218
  class GedcomXSourceOrDocument(GedcomXObject):
187
- def __init__(self,record: Gedcom5xRecord | None = None) -> None:
219
+ def __init__(self,record: Gedcom5xRecord) -> None:
188
220
  super().__init__(record)
189
- self.title: str = None
190
- self.citation: str = None
191
- self.page: str = None
192
- self.contributor: str = None
193
- self.publisher: str = None
194
- self.rights: str = None
195
- self.url: str = None
196
- self.medium: str = None
197
- self.type: str = None
198
- self.format: str = None
199
- self.created: str = None
200
- self.modified: str = None
201
- self.language: str = None
202
- self.relation: str = None
203
- self.identifier: str = None
204
- self.description: str = None
221
+ self.title: str | None = None
222
+ self.citation: str | None = None
223
+ self.page: str | None = None
224
+ self.contributor: str | None = None
225
+ self.publisher: str | None = None
226
+ self.rights: str | None = None
227
+ self.url: str | None = None
228
+ self.medium: str | None = None
229
+ self.type: str | None = None
230
+ self.format: str | None = None
231
+ self.created: str | None = None
232
+ self.modified: str | None = None
233
+ self.language: str | None = None
234
+ self.relation: str | None = None
235
+ self.identifier: str | None = None
236
+ self.description: str | None = None
205
237
 
206
238
  class GedcomXEventOrFact(GedcomXObject):
207
- def __new__(cls,record: Gedcom5xRecord | None = None, object_stack: dict | None = None) -> object:
239
+ def __new__(cls,record: Gedcom5xRecord, object_stack: dict | None = None) -> object:
208
240
  super().__init__(record)
209
241
  if record.tag in fact_event_table.keys():
210
242
 
@@ -219,10 +251,7 @@ class GedcomXEventOrFact(GedcomXObject):
219
251
  raise ValueError(f"{record.tag} not found in map")
220
252
 
221
253
  class GedcomXRelationshipBuilder(GedcomXObject):
222
- def __new__(cls,record: Gedcom5xRecord | None = None, object_stack: dict | None = None) -> object:
223
- last_relationship = object_stack.get('lastrelationship',None)
224
- last_relationship_data = object_stack.get('lastrelationshipdata',None)
225
- if not isinstance(last_relationship_data,dict):
226
- last_relationship_data = None
254
+ def __init__(self, record: Gedcom5xRecord) -> None:
255
+ super().__init__(record)
227
256
 
228
257
 
@@ -1,6 +1,5 @@
1
1
  from enum import Enum
2
2
  from typing import List,Optional
3
- from typing_extensions import Self
4
3
 
5
4
  """
6
5
  ======================================================================
@@ -12,6 +11,7 @@ from typing_extensions import Self
12
11
  Created: 2025-08-25
13
12
  Updated:
14
13
  - 2025-08-31: _as_dict_ to only create entries in dict for fields that hold data
14
+ - 2025-09-03: _from_json_ refactor
15
15
 
16
16
  ======================================================================
17
17
  """
@@ -22,13 +22,23 @@ GEDCOM Module Types
22
22
  ======================================================================
23
23
  """
24
24
  #======================================================================
25
- from .Attribution import Attribution
26
- from .Conclusion import Conclusion, ConfidenceLevel
27
- from .Date import Date
28
- from .Note import Note
29
- from .Resource import Resource
30
- from .SourceReference import SourceReference
31
- #======================================================================
25
+ from .attribution import Attribution
26
+ from .conclusion import Conclusion, ConfidenceLevel
27
+ from .date import Date
28
+ from .document import Document
29
+ from .Extensions.rs10.rsLink import _rsLinks
30
+ from .note import Note
31
+ from .resource import Resource
32
+ from .source_reference import SourceReference
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):
@@ -145,7 +156,7 @@ class NamePart:
145
156
 
146
157
  @property
147
158
  def _as_dict_(self):
148
- from .Serialization import Serialization
159
+ from .serialization import Serialization
149
160
  type_as_dict = {}
150
161
  if self.type:
151
162
  type_as_dict['type'] = self.type.value
@@ -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
- Serialization.serialize_dict(type_as_dict)
167
+ return type_as_dict if type_as_dict != {} else None
157
168
 
158
169
  @classmethod
159
- def _from_json_(cls,data):
160
- name_part =NamePart(type=NamePartType(data['type']) if 'type' in data else None,
161
- value=data.get('value'),
162
- qualifiers=[NamePartQualifier(q) for q in data.get('qualifiers')])
163
- return name_part
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):
@@ -226,24 +251,36 @@ class NameForm:
226
251
 
227
252
  @property
228
253
  def _as_dict_(self):
229
- from .Serialization import Serialization
254
+ from .serialization import Serialization
230
255
  type_as_dict = {}
231
256
  if self.lang:
232
257
  type_as_dict['lang'] = self.lang
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
- """Build a NameForm from JSON-like dict."""
242
- return cls(
243
- lang=data.get("lang", "en"),
244
- fullText=data.get("fullText"),
245
- parts=[NamePart._from_json_(p) for p in ensure_list(data.get("parts"))],
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
@@ -309,29 +346,29 @@ class Name(Conclusion):
309
346
  def __init__(self, id: Optional[str] = None,
310
347
  lang: Optional[str] = None,
311
348
  sources: Optional[List[SourceReference]] = None,
312
- analysis: Resource = None,
349
+ analysis: Optional[Document |Resource] = None,
313
350
  notes: Optional[List[Note]] = None,
314
351
  confidence: Optional[ConfidenceLevel] = None,
315
352
  attribution: Optional[Attribution] = None,
316
353
  type: Optional[NameType] = None,
317
354
  nameForms: Optional[List[NameForm]]= None,
318
- date: Optional[Date] = None) -> None:
319
- super().__init__(id, lang, sources, analysis, notes, confidence, attribution)
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
323
361
 
324
- def _add_name_part(self, namepart_to_add: NamePart):
325
- if namepart_to_add and isinstance(namepart_to_add, NamePart):
362
+ def _add_name_part(self, namepart: NamePart):
363
+ if namepart and isinstance(namepart, NamePart):
326
364
  for current_namepart in self.nameForms[0].parts:
327
- if namepart_to_add == current_namepart:
365
+ if namepart == current_namepart:
328
366
  return False
329
- self.nameForms[0].parts.append(namepart_to_add)
367
+ self.nameForms[0].parts.append(namepart)
330
368
 
331
369
  @property
332
370
  def _as_dict_(self):
333
- from .Serialization import Serialization
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 Serialization.serialize_dict(type_as_dict)
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
- return cls(
348
- id=data.get("id"),
349
- lang=data.get("lang", "en"),
350
- sources=[SourceReference._from_json_(s) for s in ensure_list(data.get("sources"))],
351
- analysis=Resource._from_json_(data["analysis"]) if data.get("analysis") else None,
352
- notes=[Note._from_json_(n) for n in ensure_list(data.get("notes"))],
353
- confidence=ConfidenceLevel._from_json_(data["confidence"]) if data.get("confidence") else None,
354
- attribution=Attribution._from_json_(data["attribution"]) if data.get("attribution") else None,
355
- type=NameType(data["type"]) if data.get("type") else None,
356
- nameForms=[NameForm._from_json_(nf) for nf in ensure_list(data.get("nameForms"))],
357
- date=Date._from_json_(data["date"]) if data.get("date") else None,
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 ADDED
@@ -0,0 +1,105 @@
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
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
+ """
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
+ #=====================================================================
31
+
32
+ class Note:
33
+ identifier = 'http://gedcomx.org/v1/Note'
34
+ version = 'http://gedcomx.org/conceptual-model/v1'
35
+
36
+ def __init__(self,lang: Optional[str] = 'en', subject: Optional[str] = None, text: Optional[str] = None, attribution: Optional[Attribution] = None) -> None:
37
+ self.lang = lang
38
+ self.subject = subject
39
+ self.text = text
40
+ self.attribution = attribution
41
+
42
+ def append(self, text_to_add: str):
43
+ if text_to_add and isinstance(text_to_add, str):
44
+ if self.text:
45
+ self.text = self.text + text_to_add
46
+ else:
47
+ self.text = text_to_add
48
+ else:
49
+ return #TODO
50
+ raise ValueError("The text to add must be a non-empty string.")
51
+
52
+ @property
53
+ def _as_dict_(self):
54
+ from .serialization import Serialization
55
+ type_as_dict = {}
56
+ if self.lang:
57
+ type_as_dict["lang"] = self.lang
58
+ if self.subject:
59
+ type_as_dict["subject"] = self.subject
60
+ if self.text:
61
+ type_as_dict["text"] = self.text
62
+ if self.attribution:
63
+ # If attribution exposes `_as_dict_` as a property, use it; otherwise include as-is
64
+ type_as_dict["attribution"] = getattr(self.attribution, "_as_dict_", self.attribution)
65
+ return type_as_dict if type_as_dict != {} else None
66
+ return Serialization.serialize_dict(type_as_dict)
67
+
68
+ def __eq__(self, other):
69
+ if not isinstance(other, Note):
70
+ return NotImplemented
71
+
72
+ def safe_str(val):
73
+ return val.strip() if isinstance(val, str) else ''
74
+
75
+ return (
76
+ #safe_str(self.lang) == safe_str(other.lang) and
77
+ #safe_str(self.subject) == safe_str(other.subject) and
78
+ safe_str(self.text) == safe_str(other.text) #and
79
+ # self.attribution == other.attribution # Assumes Attribution defines __eq__
80
+ )
81
+
82
+ @classmethod
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)
87
+
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)
104
+
105
+ return cls(**obj)
@@ -0,0 +1,19 @@
1
+ from typing import Optional
2
+
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
+ #=====================================================================
13
+
14
+ class OnlineAccount:
15
+ identifier = 'http://gedcomx.org/v1/OnlineAccount'
16
+ version = 'http://gedcomx.org/conceptual-model/v1'
17
+
18
+ def __init__(self, serviceHomepage: Resource, accountName: str) -> None:
19
+ pass
@@ -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
  """
@@ -20,19 +21,32 @@ from urllib.parse import urljoin
20
21
  GEDCOM Module Types
21
22
  ======================================================================
22
23
  """
23
- from .Attribution import Attribution
24
- from .Conclusion import ConfidenceLevel
25
- from .Date import Date
26
- from .EvidenceReference import EvidenceReference
27
- from .Extensions.rs10.rsLink import _rsLinkList
28
- from .Fact import Fact, FactType
29
- from .Gender import Gender, GenderType
30
- from .Identifier import IdentifierList
31
- from .Name import Name, QuickName
32
- from .Note import Note
33
- from .Resource import Resource
34
- from .SourceReference import SourceReference
35
- from .Subject import Subject
24
+ from .attribution import Attribution
25
+ from .conclusion import ConfidenceLevel
26
+ from .date import Date
27
+ from .evidence_reference import EvidenceReference
28
+ from .Extensions.rs10.rsLink import _rsLinks
29
+ from .fact import Fact, FactType
30
+ from .gender import Gender, GenderType
31
+ from .identifier import IdentifierList
32
+ from .logging_hub import hub, logging
33
+ from .name import Name, QuickName
34
+ from .note import Note
35
+ from .resource import Resource
36
+ from .source_reference import SourceReference
37
+ from .subject import Subject
38
+ from .logging_hub import hub, logging
39
+ """
40
+ ======================================================================
41
+ Logging
42
+ ======================================================================
43
+ """
44
+ log = logging.getLogger("gedcomx")
45
+ serial_log = "gedcomx.serialization"
46
+ deserial_log = "degedcomx.serialization"
47
+ #=====================================================================
48
+
49
+
36
50
 
37
51
  class Person(Subject):
38
52
  """A person in the system.
@@ -60,12 +74,12 @@ class Person(Subject):
60
74
  evidence: Optional[List[EvidenceReference]] = None,
61
75
  media: Optional[List[SourceReference]] = None,
62
76
  identifiers: Optional[IdentifierList] = None,
63
- private: Optional[bool] = False,
77
+ private: Optional[bool] = None,
64
78
  gender: Optional[Gender] = Gender(type=GenderType.Unknown),
65
79
  names: Optional[List[Name]] = None,
66
80
  facts: Optional[List[Fact]] = None,
67
- living: Optional[bool] = False,
68
- links: Optional[_rsLinkList] = None,
81
+ living: Optional[bool] = None,
82
+ links: Optional[_rsLinks] = None,
69
83
  uri: Optional[Resource] = None) -> None:
70
84
  # Call superclass initializer if needed
71
85
  super().__init__(id, lang, sources, analysis, notes, confidence, attribution, extracted, evidence, media, identifiers,links=links,uri=uri)
@@ -93,6 +107,7 @@ class Person(Subject):
93
107
  return False
94
108
  self.facts.append(fact_to_add)
95
109
  return True
110
+ return False
96
111
 
97
112
  def add_name(self, name_to_add: Name) -> bool:
98
113
  if len(self.names) > 5:
@@ -105,9 +120,10 @@ class Person(Subject):
105
120
  return False
106
121
  self.names.append(name_to_add)
107
122
  return True
123
+ return False
108
124
 
109
125
  def _add_relationship(self, relationship_to_add: object):
110
- from .Relationship import Relationship
126
+ from .relationship import Relationship
111
127
  if isinstance(relationship_to_add,Relationship):
112
128
  self._relationships.append(relationship_to_add)
113
129
  else:
@@ -127,34 +143,38 @@ class Person(Subject):
127
143
 
128
144
  @property
129
145
  def _as_dict_(self):
130
- from .Serialization import Serialization
131
- type_as_dict = super()._as_dict_
132
- if self.private is not None:
133
- type_as_dict['private'] = self.private
134
- if self.living is not None:
135
- type_as_dict['living'] = self.living
136
- if self.gender:
137
- type_as_dict['gender'] = self.gender._as_dict_
138
- if self.names:
139
- type_as_dict['names'] = [n._as_dict_ for n in self.names if n]
140
- if self.facts:
141
- type_as_dict['facts'] = [f._as_dict_ for f in self.facts if f]
142
- if self.uri:
143
- type_as_dict['uri'] = self.uri._as_dict_
144
-
145
- return Serialization.serialize_dict(type_as_dict)
146
+ from .serialization import Serialization
147
+ return Serialization.serialize(self)
148
+
146
149
 
147
150
  @classmethod
148
- def _from_json_(cls, data: dict):
149
- """
150
- Create a Person instance from a JSON-dict (already parsed).
151
- """
152
- from .Serialization import Serialization
153
- return Serialization.deserialize(data, Person)
151
+ def _from_json_(cls, data: dict, context = None) -> "Person":
152
+ def _to_bool(v):
153
+ if isinstance(v, str):
154
+ return v.strip().lower() in ("1", "true", "yes", "y", "t")
155
+ return bool(v)
156
+ with hub.use(deserial_log):
157
+ log.debug(f"Deserializing a Person")
158
+ person_data: dict = Subject._dict_from_json_(data,context)
159
+ if (private := data.get("private")) is not None:
160
+ person_data["private"] = _to_bool(private)
161
+ if (living := data.get("living")) is not None:
162
+ person_data["living"] = _to_bool(living)
163
+ if (gender := data.get("gender")) is not None:
164
+ person_data["gender"] = Gender._from_json_(gender, context)
165
+ if (names := data.get("names")) is not None:
166
+ person_data["names"] = [Name._from_json_(n, context) for n in names]
167
+ if (facts := data.get("facts")) is not None:
168
+ person_data["facts"] = [Fact._from_json_(f, context) for f in facts]
169
+
170
+ diff = data.keys() - person_data.keys()
171
+ log.debug(f"Desserialization found keys {diff} that are not in deserialization list")
172
+
173
+ return cls(**person_data)
154
174
 
155
175
  @classmethod
156
176
  def from_familysearch(cls, pid: str, token: str, *, base_url: Optional[str] = None):
157
- from .Serialization import Serialization
177
+ from .serialization import Serialization
158
178
  """
159
179
  Fetch a single person by PID from FamilySearch and return a Person.
160
180
  - pid: e.g. "KPHP-4B4"
@@ -184,7 +204,7 @@ class Person(Subject):
184
204
  raise ValueError(f"FamilySearch returned no person for PID {pid}")
185
205
 
186
206
  # Keep your existing deserialization helper
187
- return Serialization.deserialize(person_json, Person)
207
+ return Person._from_json_(person_json)
188
208
 
189
209
  class QuickPerson:
190
210
  """A GedcomX Person Data Type created with basic information.