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,15 +1,16 @@
1
- from typing import List, Optional
1
+ from typing import Any, Dict, List, Optional
2
2
 
3
3
  """
4
4
  ======================================================================
5
5
  Project: Gedcom-X
6
- File: PlaceDescription.py
6
+ File: place_description.py
7
7
  Author: David J. Cartwright
8
- Purpose: Python Object representation of GedcomX PlaceDescription Type
8
+ Purpose:
9
9
 
10
10
  Created: 2025-08-25
11
11
  Updated:
12
- - 2025-08-31: _as_dict_ to only create entries in dict for fields that hold data
12
+ - 2025-09-01: filename PEP8 standard
13
+ - 2025-09-03: _from_json_ refactored
13
14
 
14
15
  ======================================================================
15
16
  """
@@ -19,17 +20,28 @@ from typing import List, Optional
19
20
  GEDCOM Module Types
20
21
  ======================================================================
21
22
  """
22
- from .Attribution import Attribution
23
- from .Conclusion import ConfidenceLevel
24
- from .Date import Date
25
- from .EvidenceReference import EvidenceReference
26
- from .Identifier import IdentifierList
27
- from .Note import Note
28
- from .Resource import Resource
29
- from .SourceReference import SourceReference
30
- from .Subject import Subject
31
- from .TextValue import TextValue
32
- from .URI import URI
23
+ from .attribution import Attribution
24
+ from .conclusion import ConfidenceLevel
25
+ from .date import Date
26
+ from .evidence_reference import EvidenceReference
27
+ from .Extensions.rs10.rsLink import _rsLinks #new
28
+ from .identifier import IdentifierList
29
+ from .note import Note
30
+ from .resource import Resource
31
+ from .source_reference import SourceReference
32
+ from .subject import Subject
33
+ from .textvalue import TextValue
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"
43
+ #=====================================================================
44
+
33
45
 
34
46
  class PlaceDescription(Subject):
35
47
  """PlaceDescription describes the details of a place in terms of
@@ -76,9 +88,11 @@ class PlaceDescription(Subject):
76
88
  latitude: Optional[float] = None,
77
89
  longitude: Optional[float] = None,
78
90
  temporalDescription: Optional[Date] = None,
79
- spatialDescription: Optional[Resource] = None,) -> None:
91
+ spatialDescription: Optional[Resource] = None,
92
+ links: Optional[_rsLinks] = None
93
+ ) -> None:
80
94
 
81
- 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)
82
96
  self.names = names
83
97
  self.type = type
84
98
  self.place = place
@@ -90,8 +104,8 @@ class PlaceDescription(Subject):
90
104
 
91
105
  @property
92
106
  def _as_dict_(self):
93
- from .Serialization import Serialization
94
- type_as_dict = super()._as_dict_
107
+ from .serialization import Serialization
108
+ type_as_dict = super()._as_dict_ or {}
95
109
 
96
110
  if self.names:
97
111
  type_as_dict["names"] = [n._as_dict_ for n in self.names if n]
@@ -110,12 +124,46 @@ class PlaceDescription(Subject):
110
124
  if self.spatialDescription:
111
125
  type_as_dict["spatialDescription"] = self.spatialDescription._as_dict_
112
126
 
127
+ return type_as_dict if type_as_dict != {} else None
113
128
  return Serialization.serialize_dict(type_as_dict)
114
129
 
115
130
  @classmethod
116
- def _from_json_(cls, data: dict):
131
+ def _from_json_(cls, data: Any, context: Any = None) -> "PlaceDescription":
117
132
  """
118
133
  Create a PlaceDescription instance from a JSON-dict (already parsed).
119
- """
120
- from .Serialization import Serialization
121
- return Serialization.deserialize(data, PlaceDescription)
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)
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
  from typing import Optional, TYPE_CHECKING
3
3
  if TYPE_CHECKING:
4
- from .PlaceDescription import PlaceDescription
4
+ from .place_description import PlaceDescription
5
5
 
6
6
  """
7
7
  ======================================================================
@@ -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
  """
@@ -22,7 +23,17 @@ if TYPE_CHECKING:
22
23
  GEDCOM Module Types
23
24
  ======================================================================
24
25
  """
25
- from .Resource import Resource
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["Resource | PlaceDescription"] = None) -> None:
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
- from .Serialization import Serialization
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 Serialization.serialize_dict(type_as_dict)
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
- from .Serialization import Serialization
59
- return Serialization.deserialize(data, PlaceReference)
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
 
@@ -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,
@@ -37,7 +47,7 @@ class Qualifier:
37
47
 
38
48
  @property
39
49
  def __as_dict__(self):
40
- from .Serialization import Serialization
50
+ from .serialization import Serialization
41
51
 
42
52
  type_as_dict = {}
43
53
  if self.name:
@@ -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 _from_json(cls,data):
52
- qualifier = Qualifier(name=data.get('name',"ERROR: This Qualifier require a 'name' but has none."),value=data.get('value'))
53
- return qualifier
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
 
@@ -0,0 +1,156 @@
1
+ from enum import Enum
2
+ from typing import Any, Dict, Optional, List
3
+ """
4
+ ======================================================================
5
+ Project: Gedcom-X
6
+ File: relationship.py
7
+ Author: David J. Cartwright
8
+ Purpose:
9
+
10
+ Created: 2025-08-25
11
+ Updated:
12
+ - 2025-09-31: filename PEP8 standard
13
+ - 2025-09-03: _from_json_ refactor
14
+
15
+ ======================================================================
16
+ """
17
+
18
+ """
19
+ ======================================================================
20
+ GEDCOM Module Types
21
+ ======================================================================
22
+ """
23
+ from .attribution import Attribution
24
+ from .conclusion import ConfidenceLevel
25
+ from .evidence_reference import EvidenceReference
26
+ from .Extensions.rs10.rsLink import _rsLinks #new
27
+ from .fact import Fact
28
+ from .identifier import Identifier, make_uid
29
+ from .note import Note
30
+ from .person import Person
31
+ from .resource import Resource
32
+ from .source_reference import SourceReference
33
+ from .subject import Subject
34
+ from .logging_hub import hub, logging
35
+ """
36
+ ======================================================================
37
+ Logging
38
+ ======================================================================
39
+ """
40
+ log = logging.getLogger("gedcomx")
41
+ serial_log = "gedcomx.serialization"
42
+ #=====================================================================
43
+
44
+
45
+ class RelationshipType(Enum):
46
+ Couple = "http://gedcomx.org/Couple"
47
+ ParentChild = "http://gedcomx.org/ParentChild"
48
+
49
+ @property
50
+ def description(self):
51
+ descriptions = {
52
+ RelationshipType.Couple: "A relationship of a pair of persons.",
53
+ RelationshipType.ParentChild: "A relationship from a parent to a child."
54
+ }
55
+ return descriptions.get(self, "No description available.")
56
+
57
+ class Relationship(Subject):
58
+ """Represents a relationship between two Person(s)
59
+
60
+ Args:
61
+ type (RelationshipType): Type of relationship
62
+ person1 (Person) = First Person in Relationship
63
+ person2 (Person): Second Person in Relationship
64
+
65
+ Raises:
66
+
67
+ """
68
+ identifier = 'http://gedcomx.org/v1/Relationship'
69
+ version = 'http://gedcomx.org/conceptual-model/v1'
70
+
71
+ def __init__(self,
72
+ person1: Optional[Person | Resource] = None,
73
+ person2: Optional[Person | Resource] = None,
74
+ facts: Optional[List[Fact]] = None,
75
+ id: Optional[str] = None,
76
+ lang: Optional[str] = None,
77
+ sources: Optional[List[SourceReference]] = None,
78
+ analysis: Optional[Resource] = None,
79
+ notes: Optional[List[Note]] = None,
80
+ confidence: Optional[ConfidenceLevel] = None,
81
+ attribution: Optional[Attribution] = None,
82
+ extracted: Optional[bool] = None,
83
+ evidence: Optional[List[EvidenceReference]] = None,
84
+ media: Optional[List[SourceReference]] = None,
85
+ identifiers: Optional[List[Identifier]] = None,
86
+ type: Optional[RelationshipType] = None,
87
+ links: Optional[_rsLinks] = None,
88
+ ) -> None:
89
+
90
+ # Call superclass initializer if required
91
+ super().__init__(id, lang, sources, analysis, notes, confidence, attribution, extracted, evidence, media, identifiers,links=links)
92
+
93
+ self.id = id if id else make_uid()
94
+ self.type = type
95
+ self.person1 = person1
96
+ self.person2 = person2
97
+ self.facts = facts if facts else []
98
+
99
+ def add_fact(self,fact: Fact):
100
+ if (fact is not None) and isinstance(fact,Fact):
101
+ for existing_fact in self.facts:
102
+ if fact == existing_fact:
103
+ return
104
+ self.facts.append(fact)
105
+ else:
106
+ raise TypeError(f"Expected type 'Fact' recieved type {type(fact)}")
107
+
108
+ @property
109
+ def _as_dict_(self):
110
+ from .serialization import Serialization
111
+ return Serialization.serialize(self)
112
+
113
+ type_as_dict = (super()._as_dict_ or {}).copy()
114
+
115
+ extras = {
116
+ "type": getattr(self.type, "value", None),
117
+ "person1": Resource(target=self.person1)._as_dict_ if self.person1 else None,
118
+ "person2": Resource(target=self.person2)._as_dict_ if self.person2 else None,
119
+ "facts": [f._as_dict_ for f in self.facts if f] if getattr(self, "facts", None) else None,
120
+ }
121
+
122
+ # only keep non-empty values
123
+ type_as_dict.update({k: v for k, v in extras.items() if v not in (None, [], {}, ())})
124
+
125
+ return type_as_dict or None
126
+
127
+ @classmethod
128
+ def _from_json_(cls, data: Dict[str, Any], context: Any = None) -> "Relationship":
129
+ """
130
+ Create a Person instance from a JSON-dict (already parsed).
131
+ """
132
+ if not isinstance(data, dict):
133
+ raise TypeError(f"{cls.__name__}._from_json_ expected dict, got {type(data)}")
134
+
135
+ relationship_data: Dict[str, Any] = {}
136
+ relationship_data = Subject._dict_from_json_(data,context)
137
+
138
+ if (id_ := data.get("id")) is not None:
139
+ relationship_data["id"] = id_
140
+
141
+ if (type_ := data.get("type")) is not None:
142
+ relationship_data["type"] = RelationshipType(type_)
143
+
144
+ # person1 / person2
145
+ if (p1 := data.get("person1")) is not None:
146
+ relationship_data["person1"] = Resource._from_json_(p1,context)
147
+
148
+ if (p2 := data.get("person2")) is not None:
149
+ relationship_data["person2"] = Resource._from_json_(p2,context)
150
+
151
+ # facts
152
+ if (facts := data.get("facts")) is not None:
153
+ relationship_data["facts"] = [Fact._from_json_(f, context) for f in facts]
154
+
155
+ return cls(**relationship_data)
156
+
gedcomx/resource.py ADDED
@@ -0,0 +1,112 @@
1
+ from typing import Optional
2
+
3
+ """
4
+ ======================================================================
5
+ Project: Gedcom-X
6
+ File: Resource.py
7
+ Author: David J. Cartwright
8
+ Purpose: References TopLevel Types for Serialization
9
+
10
+ Created: 2025-08-25
11
+ Updated:
12
+ - 2025-08-31: working on target=Resource and deserialization issues
13
+ - 2025-09-03: _from_json_ refactor, arguments changed
14
+
15
+ ======================================================================
16
+ """
17
+
18
+ """
19
+ ======================================================================
20
+ GEDCOM Module Types
21
+ ======================================================================
22
+ """
23
+
24
+ from .uri import URI
25
+ from .logging_hub import hub, logging
26
+ """
27
+ ======================================================================
28
+ Logging
29
+ ======================================================================
30
+ """
31
+ log = logging.getLogger("gedcomx")
32
+ serial_log = "gedcomx.serialization"
33
+ #=====================================================================
34
+
35
+ class Resource:
36
+ """
37
+ Class used to track and resolve URIs and references between datastores.
38
+
39
+ Parameters
40
+ ----------
41
+
42
+ Raises
43
+ ------
44
+ ValueError
45
+ If `id` is not a valid UUID.
46
+ """
47
+ # TODO, Deal with a resouce being passed, as it may be unresolved.
48
+ def __init__(self,resource: URI | None = None,resourceId: str | None = None, target: object = None) -> None:
49
+
50
+ #if (resource is None) and (target is None): #TODO
51
+ # raise ValueError('Resource object must point to something.')
52
+
53
+ self.resource = resource
54
+ self.resourceId = resourceId
55
+ self.Id = None
56
+
57
+ self.type = None
58
+ self.resolved = False
59
+ self.target: object = target
60
+ self.remote: bool | None = None # is the resource pointed to persitent on a remote datastore?
61
+
62
+ if target:
63
+ if isinstance(target,Resource):
64
+ self.resource = target.resource
65
+ self.resourceId = target.resourceId
66
+ self.target = target.target
67
+ else:
68
+ log.debug(f"Target of type: {type(target)}, {target}")
69
+ if hasattr(target,'uri'):
70
+ self.resource = target.uri
71
+ else:
72
+ self.resourceId = URI(fragment=target.id)
73
+ self.resourceId = target.id
74
+
75
+ @property
76
+ def uri(self):
77
+ return self.resource
78
+
79
+ @property
80
+ def _as_dict_(self):
81
+ from .serialization import Serialization
82
+ return Serialization.serialize(self)
83
+ typ_as_dict = {}
84
+ if self.resource is not None:
85
+ typ_as_dict["resource"] = self.resource
86
+ if self.resourceId is not None:
87
+ typ_as_dict["resourceId"] = self.resourceId,
88
+
89
+ return typ_as_dict or None
90
+
91
+
92
+
93
+ @classmethod
94
+ def _from_json_(cls, data: dict, context=None) -> "Resource":
95
+ if not isinstance(data, dict):
96
+ raise TypeError(f"{cls.__name__}._from_json_ expected dict, got {type(data)} {data}")
97
+ resource = {}
98
+
99
+ # Scalars
100
+ if (res := data.get("resource")) is not None:
101
+ resource["resource"] = res
102
+
103
+ return cls(**resource)
104
+
105
+ def __repr__(self) -> str:
106
+ return f"Resource(resource={self.resource}, resourceId={self.resourceId}, target={self.target})"
107
+
108
+ def __str__(self) -> str:
109
+ return f"resource={self.resource}{f', resourceId={self.resourceId}' if self.resourceId else ''}"
110
+
111
+
112
+