gedcom-x 0.5.8__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 (45) hide show
  1. {gedcom_x-0.5.8.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 +109 -59
  4. gedcomx/__init__.py +1 -1
  5. gedcomx/address.py +102 -16
  6. gedcomx/agent.py +81 -24
  7. gedcomx/attribution.py +52 -28
  8. gedcomx/conclusion.py +97 -45
  9. gedcomx/converter.py +209 -79
  10. gedcomx/coverage.py +10 -1
  11. gedcomx/date.py +42 -8
  12. gedcomx/document.py +37 -7
  13. gedcomx/event.py +77 -20
  14. gedcomx/evidence_reference.py +9 -0
  15. gedcomx/fact.py +53 -54
  16. gedcomx/gedcom.py +10 -0
  17. gedcomx/gedcom5x.py +30 -20
  18. gedcomx/gedcom7/__init__.py +1 -1
  19. gedcomx/gedcomx.py +95 -93
  20. gedcomx/gender.py +21 -9
  21. gedcomx/group.py +9 -0
  22. gedcomx/identifier.py +47 -20
  23. gedcomx/logging_hub.py +19 -0
  24. gedcomx/mutations.py +10 -5
  25. gedcomx/name.py +74 -33
  26. gedcomx/note.py +50 -18
  27. gedcomx/online_account.py +9 -0
  28. gedcomx/person.py +44 -26
  29. gedcomx/place_description.py +54 -8
  30. gedcomx/place_reference.py +30 -8
  31. gedcomx/qualifier.py +19 -3
  32. gedcomx/relationship.py +55 -14
  33. gedcomx/resource.py +45 -18
  34. gedcomx/serialization.py +400 -421
  35. gedcomx/source_citation.py +16 -4
  36. gedcomx/source_description.py +181 -94
  37. gedcomx/source_reference.py +51 -16
  38. gedcomx/subject.py +59 -14
  39. gedcomx/textvalue.py +66 -12
  40. gedcomx/translation.py +3 -3
  41. gedcomx/uri.py +155 -3
  42. gedcom_x-0.5.8.dist-info/RECORD +0 -56
  43. {gedcom_x-0.5.8.dist-info → gedcom_x-0.5.9.dist-info}/WHEEL +0 -0
  44. {gedcom_x-0.5.8.dist-info → gedcom_x-0.5.9.dist-info}/top_level.txt +0 -0
  45. /gedcomx/gedcom7/{Gedcom7.py → gedcom7.py} +0 -0
gedcomx/event.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from enum import Enum
2
- from typing import List, Optional
2
+ from typing import Any, Dict, List, Optional
3
3
 
4
4
  """
5
5
  ======================================================================
@@ -11,6 +11,8 @@ from typing import List, Optional
11
11
  Created: 2025-08-25
12
12
  Updated:
13
13
  - 2025-08-31: fixed mutible [] in init, replaced List[Identifer] with IdentifierList
14
+ - 2025-09-03: _from_json_ refactor
15
+ - 2025-09-04: changed _as_dict_ to std dict creation and removed call to serailization
14
16
 
15
17
  ======================================================================
16
18
  """
@@ -30,7 +32,14 @@ from .place_reference import PlaceReference
30
32
  from .resource import Resource
31
33
  from .source_reference import SourceReference
32
34
  from .subject import Subject
33
-
35
+ from .logging_hub import hub, logging
36
+ """
37
+ ======================================================================
38
+ Logging
39
+ ======================================================================
40
+ """
41
+ log = logging.getLogger("gedcomx")
42
+ serial_log = "gedcomx.serialization"
34
43
  #=====================================================================
35
44
 
36
45
  class EventRoleType(Enum):
@@ -103,7 +112,6 @@ class EventRole(Conclusion):
103
112
 
104
113
  @property
105
114
  def _as_dict_(self):
106
- from .serialization import Serialization
107
115
  type_as_dict = super()._as_dict_
108
116
  if self.person:
109
117
  type_as_dict['person'] = Resource(target=self.person)._as_dict_
@@ -112,8 +120,34 @@ class EventRole(Conclusion):
112
120
  if self.details:
113
121
  type_as_dict['details'] = self.details
114
122
 
115
- return Serialization.serialize_dict(type_as_dict)
123
+ return type_as_dict if type_as_dict != {} else None
124
+
125
+
126
+ @classmethod
127
+ def _from_json_(cls,data,context):
128
+
129
+ if not isinstance(data, dict):
130
+ print("Event: from:",data)
131
+ raise TypeError(f"{cls.__name__}._from_json_ expected dict or str, got {type(data)}")
132
+ event_role_data = Conclusion._dict_from_json_(data,context)
133
+ event_role_data: Dict[str, Any] = {}
134
+
135
+ # person: Person | Resource | URI string
136
+ if (p := data.get("person")) is not None:
137
+ event_role_data["person"] = Resource._from_json_(p, context)
138
+
139
+
140
+ # type: EventRoleType (enum-ish)
141
+ if (typ := data.get("type")) is not None:
142
+ event_role_data["type"] = EventRoleType(typ)
143
+
116
144
 
145
+ # details: TextValue | str
146
+ if (det := data.get("details")) is not None:
147
+ event_role_data["details"] = det # str or passthrough
148
+
149
+ return cls(**event_role_data)
150
+
117
151
  class EventType(Enum):
118
152
  Adoption = "http://gedcomx.org/Adoption"
119
153
  AdultChristening = "http://gedcomx.org/AdultChristening"
@@ -269,21 +303,44 @@ class Event(Subject):
269
303
  @property
270
304
  def _as_dict_(self):
271
305
  from .serialization import Serialization
272
- type_as_dict = super()._as_dict_
273
- type_as_dict.update({
274
- 'type': self.type.value if self.type else None,
275
- 'date': self.date,
276
- 'place': self.place._as_dict_ if self.place else None,
277
- 'roles': [role._as_dict_ for role in self.roles],
278
- })
279
-
280
- return Serialization.serialize_dict(type_as_dict)
306
+ type_as_dict = {}
307
+ if self.type:
308
+ type_as_dict['type'] = self.type.value
309
+ if self.date:
310
+ type_as_dict['date'] = self.date._as_dict_
311
+ if self.place:
312
+ type_as_dict['place'] = self.place._as_dict_
313
+ if self.roles:
314
+ #print(self.roles)
315
+ for role in self.roles:
316
+ if isinstance(role,list):
317
+ assert False
318
+ type_as_dict['roles'] = [role._as_dict_ for role in self.roles]
319
+
320
+ return type_as_dict if type_as_dict != {} else None
321
+
281
322
 
282
323
  @classmethod
283
- def _from_json_(cls, data: dict):
284
- """
285
- Create a Person instance from a JSON-dict (already parsed).
286
- """
287
- #type_as_dict = Serialization.get_class_fields('Event')
288
- from .serialization import Serialization
289
- return Serialization.deserialize(data, Event)
324
+ def _from_json_(cls, data: Dict[str, Any], context: Any = None) -> "Event":
325
+ if not isinstance(data, dict):
326
+ raise TypeError(f"{cls.__name__}._from_json_ expected dict, got {type(data)}")
327
+
328
+ event_data: Dict[str, Any] = Subject._dict_from_json_(data,context)
329
+
330
+ # Enum / type
331
+ if (typ := data.get("type")) is not None:
332
+ event_data["type"] = EventType(typ)
333
+
334
+
335
+ # Objects
336
+ if (dt := data.get("date")) is not None:
337
+ event_data["date"] = Date._from_json_(dt, context)
338
+
339
+ if (pl := data.get("place")) is not None:
340
+ event_data["place"] = PlaceReference._from_json_(pl, context)
341
+
342
+ # Lists
343
+ if (rls := data.get("roles")) is not None:
344
+ event_data["roles"] = [EventRole._from_json_(r, context) for r in rls]
345
+
346
+ return cls(**event_data)
@@ -2,6 +2,15 @@ from typing import Optional
2
2
 
3
3
  from .attribution import Attribution
4
4
  from .resource import Resource
5
+ from .logging_hub import hub, logging
6
+ """
7
+ ======================================================================
8
+ Logging
9
+ ======================================================================
10
+ """
11
+ log = logging.getLogger("gedcomx")
12
+ serial_log = "gedcomx.serialization"
13
+ #=====================================================================
5
14
 
6
15
  class EvidenceReference:
7
16
  identifier = 'http://gedcomx.org/v1/EvidenceReference'
gedcomx/fact.py CHANGED
@@ -12,7 +12,7 @@ from typing import List, Optional, Dict, Any
12
12
 
13
13
  Created: 2025-08-25
14
14
  Updated:
15
- - 2025-08-31:
15
+ - 2025-09-03: _from_json_ refactor
16
16
 
17
17
  ======================================================================
18
18
  """
@@ -26,12 +26,20 @@ from .attribution import Attribution
26
26
  from .conclusion import Conclusion, ConfidenceLevel
27
27
  from .date import Date
28
28
  from .document import Document
29
- from .Extensions.rs10.rsLink import _rsLinkList
29
+ from .Extensions.rs10.rsLink import _rsLinks
30
30
  from .note import Note
31
31
  from .place_reference import PlaceReference
32
32
  from .qualifier import Qualifier
33
33
  from .resource import Resource
34
34
  from .source_reference import SourceReference
35
+ from .logging_hub import hub, logging
36
+ """
37
+ ======================================================================
38
+ Logging
39
+ ======================================================================
40
+ """
41
+ log = logging.getLogger("gedcomx")
42
+ serial_log = "gedcomx.serialization"
35
43
  #=====================================================================
36
44
 
37
45
 
@@ -415,7 +423,7 @@ class Fact(Conclusion):
415
423
  place: Optional[PlaceReference] = None,
416
424
  value: Optional[str] = None,
417
425
  qualifiers: Optional[List[FactQualifier]] = None,
418
- links: Optional[_rsLinkList] = None):
426
+ links: Optional[_rsLinks] = None):
419
427
  super().__init__(id, lang, sources, analysis, notes, confidence, attribution, links=links)
420
428
  self.type = type
421
429
  self.date = date
@@ -436,60 +444,51 @@ class Fact(Conclusion):
436
444
 
437
445
  @property
438
446
  def _as_dict_(self):
439
- from .serialization import Serialization
440
- type_as_dcit = super()._as_dict_
441
- # Only add Relationship-specific fields
442
- if self.type:
443
- type_as_dcit['type'] = getattr(self.type, 'value', self.type)
444
- if self.date:
445
- type_as_dcit['date'] = self.date._as_dict_
446
- if self.place:
447
- type_as_dcit['place'] = self.place._as_dict_
448
- if self.value:
449
- type_as_dcit['value'] = self.value
450
- if self.qualifiers:
451
- type_as_dcit['qualifiers'] = [getattr(q, 'value', q) for q in self.qualifiers]
447
+ '''
448
+ Standard GedcomX Type JSON Serialization
449
+ Returns: dict that contains only field for which the object has data in
450
+ '''
451
+ with hub.use(serial_log):
452
+ log.debug(f"Serializing 'Fact' with id: {self.id}")
453
+ type_as_dict = super()._as_dict_
454
+ if type_as_dict is None:
455
+ log.debug(f"Subject had no fields, creating new dict")
456
+ type_as_dict ={}
457
+ # Only add Relationship-specific fields
458
+ if self.type:
459
+ type_as_dict['type'] = getattr(self.type, 'value', self.type)
460
+ if self.date:
461
+ type_as_dict['date'] = self.date._as_dict_
462
+ if self.place:
463
+ type_as_dict['place'] = self.place._as_dict_
464
+ if self.value:
465
+ type_as_dict['value'] = self.value
466
+ if self.qualifiers and self.qualifiers != []:
467
+ type_as_dict['qualifiers'] = [getattr(q, 'value', q) for q in self.qualifiers]
468
+ log.debug(f"'Fact' serialized with fields: {type_as_dict.keys()}")
469
+ if type_as_dict == {}: log.warning("serializing and empty 'Fact'")
452
470
 
453
- return Serialization.serialize_dict(type_as_dcit)
454
-
455
- @classmethod
456
- def _from_json_(cls, data: Dict[str, Any]) -> 'Fact':
471
+ return type_as_dict if type_as_dict != {} else None
457
472
 
458
- # Extract fields, no trailing commas!
459
- id_ = data.get('id')
460
- lang = data.get('lang', None)
461
- sources = [SourceReference._from_json_(s) for s in data.get('sources',[])]
462
- analysis = (Resource._from_json_(data['analysis'])
463
- if data.get('analysis') else None)
464
- notes = [Note._from_json_(n) for n in data.get('notes',[])]
465
- confidence = (ConfidenceLevel._from_json_(data['confidence'])
466
- if data.get('confidence') else None)
467
- attribution = (Attribution._from_json_(data['attribution']) if data.get('attribution') else None)
468
- fact_type = (FactType.from_value(data['type'])
469
- if data.get('type') else None)
470
- date = (Date._from_json_(data['date'])
471
- if data.get('date') else None)
472
- place = (PlaceReference._from_json_(data['place'])
473
- if data.get('place') else None)
474
- value = data.get('value')
475
- qualifiers = [Qualifier._from_json_(q) for q in data.get('qualifiers', [])]
476
- links = _rsLinkList._from_json_(data.get('links')) if data.get('links') else None
473
+ @classmethod
474
+ def _from_json_(cls, data: Dict[str, Any], context: Any = None) -> "Fact":
475
+ if not isinstance(data, dict):
476
+ raise TypeError(f"{cls.__name__}._from_json_ expected dict, got {type(data)}")
477
+ fact_data = Conclusion._dict_from_json_(data,context)
478
+ if (val := data.get("value")) is not None:
479
+ fact_data["value"] = val
480
+ if (date := data.get("date")) is not None:
481
+ fact_data["date"] = Date._from_json_(date, context)
482
+ if (place := data.get("place")) is not None:
483
+ fact_data["place"] = PlaceReference._from_json_(place, context)
484
+ if (ft := data.get("type")) is not None:
485
+ fact_data["type"] = FactType(ft)
486
+ if (quals := data.get("qualifiers")) is not None:
487
+ fact_data["qualifiers"] = [Qualifier._from_json_(q) for q in quals if q is not None]
488
+
489
+ return cls(**fact_data)
477
490
 
478
- return cls(
479
- id=id_,
480
- lang=lang,
481
- sources=sources,
482
- analysis=analysis,
483
- notes=notes,
484
- confidence=confidence,
485
- attribution=attribution,
486
- type=fact_type,
487
- date=date,
488
- place=place,
489
- value=value,
490
- qualifiers=qualifiers,
491
- links=links
492
- )
491
+
493
492
 
494
493
 
495
494
 
gedcomx/gedcom.py CHANGED
@@ -1,5 +1,15 @@
1
1
  import re
2
2
 
3
+ from .logging_hub import hub, logging
4
+ """
5
+ ======================================================================
6
+ Logging
7
+ ======================================================================
8
+ """
9
+ log = logging.getLogger("gedcomx")
10
+ serial_log = "gedcomx.serialization"
11
+ #=====================================================================
12
+
3
13
  class Gedcom():
4
14
  def __init__(self) -> None:
5
15
  pass
gedcomx/gedcom5x.py CHANGED
@@ -10,6 +10,15 @@ from typing import Iterable, Iterator, List, Optional, Tuple, Union
10
10
 
11
11
  import logging
12
12
  from .logging_hub import hub, ChannelConfig
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
  job_id = "gedcomx.parsing.GEDCOM5x"
15
24
 
@@ -64,18 +73,17 @@ from typing import List, Optional, Iterator, Union
64
73
 
65
74
 
66
75
  class Gedcom5xRecord():
67
- def __init__(
68
- self,
69
- line_num: Optional[int] = None,
70
- level: int = -1,
71
- tag: str | None = "NONR",
72
- xref: Optional[str] = None,
73
- value: Optional[str] = None,
74
- ) -> None:
76
+ def __init__(self,
77
+ line_num: Optional[int] = None,
78
+ level: int = -1,
79
+ tag: str | None = "NONR",
80
+ xref: Optional[str] = None,
81
+ value: Optional[str] = None,
82
+ ) -> None:
75
83
  self.line = line_num
76
84
  self._subRecords: List[Gedcom5xRecord] = []
77
85
  self.level = int(level)
78
- self.xref = xref
86
+ self.xref = xref.replace('@','') if xref else ''
79
87
  self.pointer: bool = False
80
88
  self.tag = str(tag).strip()
81
89
  self.value = value
@@ -144,7 +152,7 @@ class Gedcom5xRecord():
144
152
  # ───────────────────────────────
145
153
  def subRecord(self, tag: str):
146
154
  result = [r for r in self._subRecords if r.tag == tag]
147
- return [] if not result else result
155
+ return None if not result else result
148
156
 
149
157
  def subRecords(self, tag: str | None = None) -> List['Gedcom5xRecord']:
150
158
  if not tag:
@@ -367,7 +375,8 @@ class Gedcom5x():
367
375
  import json
368
376
  return json.dumps({'Individuals': [indi._as_dict_ for indi in self._individuals]},indent=4)
369
377
 
370
- def stats(self):
378
+ @property
379
+ def contents(self):
371
380
  def print_table(pairs):
372
381
 
373
382
  # Calculate the width of the columns
@@ -385,16 +394,16 @@ class Gedcom5x():
385
394
  for name, value in pairs:
386
395
  print(f"{name.ljust(name_width)} | {str(value).ljust(value_width)}")
387
396
 
388
- imports_stats = [
389
- ('Top Level Records', len(self.records)),
390
- ('Individuals', len(self.individuals)),
391
- ('Family Group Records', len(self.families)),
392
- ('Repositories', len(self.repositories)),
393
- ('Sources', len(self.sources)),
394
- ('Objects', len(self.objects))
395
- ]
397
+ imports_stats = {
398
+ 'Top Level Records': len(self.records),
399
+ 'Individuals': len(self.individuals),
400
+ 'Family Group Records': len(self.families),
401
+ 'Repositories': len(self.repositories),
402
+ 'Sources': len(self.sources),
403
+ 'Objects': len(self.objects)
404
+ }
396
405
 
397
- print_table(imports_stats)
406
+ return imports_stats
398
407
 
399
408
  @property
400
409
  def sources(self) -> List[Gedcom5xRecord]:
@@ -475,6 +484,7 @@ class Gedcom5x():
475
484
 
476
485
  level = int(match.group("level"))
477
486
  xref_id = match.group("xref")
487
+ xref_id = xref_id.strip('@') if xref_id else None
478
488
  tag = match.group("tag")
479
489
  value = match.group("value")
480
490
  if value == 'None': value = None
@@ -22,5 +22,5 @@ GEDCOM Module Types
22
22
  from .GedcomStructure import GedcomStructure
23
23
  from .logger import get_logger
24
24
  from .Specification import structure_specs
25
- from .Gedcom7 import Gedcom7
25
+ from .gedcom7 import Gedcom7
26
26
  __all__ = ["Gedcom7"]