gedcom-x 0.5.2__py3-none-any.whl → 0.5.5__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.2.dist-info → gedcom_x-0.5.5.dist-info}/METADATA +1 -1
- gedcom_x-0.5.5.dist-info/RECORD +43 -0
- gedcomx/Address.py +2 -0
- gedcomx/Agent.py +9 -2
- gedcomx/Attribution.py +10 -46
- gedcomx/Conclusion.py +85 -21
- gedcomx/Coverage.py +10 -0
- gedcomx/Date.py +2 -7
- gedcomx/Document.py +27 -6
- gedcomx/Event.py +20 -1
- gedcomx/Exceptions.py +6 -0
- gedcomx/Fact.py +7 -8
- gedcomx/Gedcom.py +38 -404
- gedcomx/Gedcom5x.py +558 -0
- gedcomx/GedcomX.py +37 -22
- gedcomx/Gender.py +6 -40
- gedcomx/Identifier.py +151 -97
- gedcomx/Mutations.py +228 -0
- gedcomx/Name.py +6 -0
- gedcomx/Person.py +49 -90
- gedcomx/PlaceDescription.py +23 -14
- gedcomx/PlaceReference.py +12 -15
- gedcomx/Relationship.py +23 -54
- gedcomx/Resource.py +17 -3
- gedcomx/Serialization.py +352 -31
- gedcomx/SourceDescription.py +6 -9
- gedcomx/SourceReference.py +20 -86
- gedcomx/Subject.py +4 -4
- gedcomx/Translation.py +219 -0
- gedcomx/URI.py +1 -0
- gedcomx/__init__.py +7 -1
- gedcom_x-0.5.2.dist-info/RECORD +0 -42
- gedcomx/_Links.py +0 -37
- gedcomx/g7interop.py +0 -205
- {gedcom_x-0.5.2.dist-info → gedcom_x-0.5.5.dist-info}/WHEEL +0 -0
- {gedcom_x-0.5.2.dist-info → gedcom_x-0.5.5.dist-info}/top_level.txt +0 -0
gedcomx/Mutations.py
ADDED
@@ -0,0 +1,228 @@
|
|
1
|
+
from .Gedcom5x import GedcomRecord
|
2
|
+
from .Fact import Fact, FactType
|
3
|
+
from .Event import Event, EventType
|
4
|
+
|
5
|
+
fact_event_table = {
|
6
|
+
# Person Fact / Event Types
|
7
|
+
"ADOP": {
|
8
|
+
"Fact": FactType.AdoptiveParent,
|
9
|
+
"Event": EventType.Adoption,
|
10
|
+
},
|
11
|
+
"CHR": {
|
12
|
+
"Fact": FactType.AdultChristening,
|
13
|
+
"Event": EventType.AdultChristening,
|
14
|
+
},
|
15
|
+
"EVEN": {
|
16
|
+
"Fact": FactType.Amnesty,
|
17
|
+
# no Event
|
18
|
+
},
|
19
|
+
"BAPM": {
|
20
|
+
"Fact": FactType.Baptism,
|
21
|
+
"Event": EventType.Baptism,
|
22
|
+
},
|
23
|
+
"BARM": {
|
24
|
+
"Fact": FactType.BarMitzvah,
|
25
|
+
"Event": EventType.BarMitzvah,
|
26
|
+
},
|
27
|
+
"BASM": {
|
28
|
+
"Fact": FactType.BatMitzvah,
|
29
|
+
"Event": EventType.BatMitzvah,
|
30
|
+
},
|
31
|
+
"BIRT": {
|
32
|
+
"Fact": FactType.Birth,
|
33
|
+
"Event": EventType.Birth,
|
34
|
+
},
|
35
|
+
"BIRT, CHR": {
|
36
|
+
"Fact": FactType.Birth,
|
37
|
+
"Event": EventType.Birth,
|
38
|
+
},
|
39
|
+
"BLES": {
|
40
|
+
"Fact": FactType.Blessing,
|
41
|
+
"Event": EventType.Blessing,
|
42
|
+
},
|
43
|
+
"BURI": {
|
44
|
+
"Fact": FactType.Burial,
|
45
|
+
"Event": EventType.Burial,
|
46
|
+
},
|
47
|
+
"CAST": {
|
48
|
+
"Fact": FactType.Caste,
|
49
|
+
# no Event
|
50
|
+
},
|
51
|
+
"CENS": {
|
52
|
+
"Fact": FactType.Census,
|
53
|
+
"Event": EventType.Census,
|
54
|
+
},
|
55
|
+
"CIRC": {
|
56
|
+
"Fact": FactType.Circumcision,
|
57
|
+
"Event": EventType.Circumcision,
|
58
|
+
},
|
59
|
+
"CONF": {
|
60
|
+
"Fact": FactType.Confirmation,
|
61
|
+
"Event": EventType.Confirmation,
|
62
|
+
},
|
63
|
+
"CREM": {
|
64
|
+
"Fact": FactType.Cremation,
|
65
|
+
"Event": EventType.Cremation,
|
66
|
+
},
|
67
|
+
"DEAT": {
|
68
|
+
"Fact": FactType.Death,
|
69
|
+
"Event": EventType.Death,
|
70
|
+
},
|
71
|
+
"EDUC": {
|
72
|
+
"Fact": FactType.Education,
|
73
|
+
"Event": EventType.Education,
|
74
|
+
},
|
75
|
+
"EMIG": {
|
76
|
+
"Fact": FactType.Emigration,
|
77
|
+
"Event": EventType.Emigration,
|
78
|
+
},
|
79
|
+
"FCOM": {
|
80
|
+
"Fact": FactType.FirstCommunion,
|
81
|
+
"Event": EventType.FirstCommunion,
|
82
|
+
},
|
83
|
+
"GRAD": {
|
84
|
+
"Fact": FactType.Graduation,
|
85
|
+
# no Event
|
86
|
+
},
|
87
|
+
"IMMI": {
|
88
|
+
"Fact": FactType.Immigration,
|
89
|
+
"Event": EventType.Immigration,
|
90
|
+
},
|
91
|
+
"MIL": {
|
92
|
+
"Fact": FactType.MilitaryService,
|
93
|
+
# no Event
|
94
|
+
},
|
95
|
+
"NATI": {
|
96
|
+
"Fact": FactType.Nationality,
|
97
|
+
# no Event
|
98
|
+
},
|
99
|
+
"NATU": {
|
100
|
+
"Fact": FactType.Naturalization,
|
101
|
+
"Event": EventType.Naturalization,
|
102
|
+
},
|
103
|
+
"OCCU": {
|
104
|
+
"Fact": FactType.Occupation,
|
105
|
+
# no Event
|
106
|
+
},
|
107
|
+
"ORDN": {
|
108
|
+
"Fact": FactType.Ordination,
|
109
|
+
"Event": EventType.Ordination,
|
110
|
+
},
|
111
|
+
"DSCR": {
|
112
|
+
"Fact": FactType.PhysicalDescription,
|
113
|
+
# no Event
|
114
|
+
},
|
115
|
+
"PROB": {
|
116
|
+
"Fact": FactType.Probate,
|
117
|
+
# no Event
|
118
|
+
},
|
119
|
+
"PROP": {
|
120
|
+
"Fact": FactType.Property,
|
121
|
+
# no Event
|
122
|
+
},
|
123
|
+
"RELI": {
|
124
|
+
"Fact": FactType.Religion,
|
125
|
+
# no Event
|
126
|
+
},
|
127
|
+
"RESI": {
|
128
|
+
"Fact": FactType.Residence,
|
129
|
+
# no Event
|
130
|
+
},
|
131
|
+
"WILL": {
|
132
|
+
"Fact": FactType.Will,
|
133
|
+
# no Event
|
134
|
+
},
|
135
|
+
|
136
|
+
# Couple Relationship Fact / Event Types
|
137
|
+
"ANUL": {
|
138
|
+
"Fact": FactType.Annulment,
|
139
|
+
"Event": EventType.Annulment,
|
140
|
+
},
|
141
|
+
"DIV": {
|
142
|
+
"Fact": FactType.Divorce,
|
143
|
+
"Event": EventType.Divorce,
|
144
|
+
},
|
145
|
+
"DIVF": {
|
146
|
+
"Fact": FactType.DivorceFiling,
|
147
|
+
"Event": EventType.DivorceFiling,
|
148
|
+
},
|
149
|
+
"ENGA": {
|
150
|
+
"Fact": FactType.Engagement,
|
151
|
+
"Event": EventType.Engagement,
|
152
|
+
},
|
153
|
+
"MARR": {
|
154
|
+
"Fact": FactType.Marriage,
|
155
|
+
"Event": EventType.Marriage,
|
156
|
+
},
|
157
|
+
"MARB": {
|
158
|
+
"Fact": FactType.MarriageBanns,
|
159
|
+
# no Event
|
160
|
+
},
|
161
|
+
"MARC": {
|
162
|
+
"Fact": FactType.MarriageContract,
|
163
|
+
# no Event
|
164
|
+
},
|
165
|
+
"MARL": {
|
166
|
+
"Fact": FactType.MarriageLicense,
|
167
|
+
# no Event
|
168
|
+
},
|
169
|
+
"MARS":{
|
170
|
+
"Fact":EventType.MarriageSettlment
|
171
|
+
|
172
|
+
},
|
173
|
+
"SEPA": {
|
174
|
+
"Fact": FactType.Separation,
|
175
|
+
# no Event
|
176
|
+
},
|
177
|
+
|
178
|
+
}
|
179
|
+
|
180
|
+
class GedcomXObject:
|
181
|
+
def __init__(self,record: GedcomRecord | None = None) -> None:
|
182
|
+
self.created_with_tag: str = record.tag if record and isinstance(record, GedcomRecord) else None
|
183
|
+
self.created_at_level: int = record.level if record and isinstance(record, GedcomRecord) else None
|
184
|
+
self.created_at_line_number: int = record.line_number if record and isinstance(record, GedcomRecord) else None
|
185
|
+
|
186
|
+
class GedcomXSourceOrDocument(GedcomXObject):
|
187
|
+
def __init__(self,record: GedcomRecord | None = None) -> None:
|
188
|
+
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
|
205
|
+
|
206
|
+
class GedcomXEventOrFact(GedcomXObject):
|
207
|
+
def __new__(cls,record: GedcomRecord | None = None, object_stack: dict | None = None) -> object:
|
208
|
+
super().__init__(record)
|
209
|
+
if record.tag in fact_event_table.keys():
|
210
|
+
|
211
|
+
if 'Fact' in fact_event_table[record.tag].keys():
|
212
|
+
obj = Fact(type=fact_event_table[record.tag]['Fact'])
|
213
|
+
return obj
|
214
|
+
elif 'Event' in fact_event_table[record.tag].keys():
|
215
|
+
obj = Event(type=fact_event_table[record.tag]['Fact'])
|
216
|
+
else:
|
217
|
+
raise ValueError
|
218
|
+
else:
|
219
|
+
raise ValueError(f"{record.tag} not found in map")
|
220
|
+
|
221
|
+
class GedcomXRelationshipBuilder(GedcomXObject):
|
222
|
+
def __new__(cls,record: GedcomRecord | 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
|
227
|
+
|
228
|
+
|
gedcomx/Name.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
from enum import Enum
|
2
2
|
from typing import List,Optional
|
3
|
+
from typing_extensions import Self
|
3
4
|
|
4
5
|
from .Attribution import Attribution
|
5
6
|
from .Conclusion import Conclusion, ConfidenceLevel
|
@@ -220,6 +221,11 @@ class Name(Conclusion):
|
|
220
221
|
|
221
222
|
return name_as_dict
|
222
223
|
|
224
|
+
class QuickName():
|
225
|
+
def __new__(cls,name: str) -> Name:
|
226
|
+
obj = Name(nameForms=[NameForm(fullText=name)])
|
227
|
+
return obj
|
228
|
+
|
223
229
|
def ensure_list(val):
|
224
230
|
if val is None:
|
225
231
|
return []
|
gedcomx/Person.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
from enum import Enum
|
2
2
|
from typing import List, Optional
|
3
|
+
from urllib.parse import urljoin
|
3
4
|
|
4
5
|
from .Attribution import Attribution
|
5
6
|
from .Conclusion import ConfidenceLevel
|
@@ -8,13 +9,14 @@ from .EvidenceReference import EvidenceReference
|
|
8
9
|
from .Fact import Fact, FactType
|
9
10
|
from .Gender import Gender, GenderType
|
10
11
|
from .Identifier import IdentifierList
|
11
|
-
from .Name import Name
|
12
|
+
from .Name import Name, QuickName
|
12
13
|
from .Note import Note
|
13
14
|
from .SourceReference import SourceReference
|
15
|
+
from .Serialization import Serialization
|
14
16
|
from .Subject import Subject
|
15
17
|
from .Resource import Resource
|
16
18
|
from collections.abc import Sized
|
17
|
-
from .
|
19
|
+
from .Extensions.rs10.rsLink import _rsLinkList
|
18
20
|
|
19
21
|
class Person(Subject):
|
20
22
|
"""A person in the system.
|
@@ -47,9 +49,10 @@ class Person(Subject):
|
|
47
49
|
names: Optional[List[Name]] = None,
|
48
50
|
facts: Optional[List[Fact]] = None,
|
49
51
|
living: Optional[bool] = False,
|
50
|
-
links: Optional[
|
52
|
+
links: Optional[_rsLinkList] = None,
|
53
|
+
uri: Optional[Resource] = None) -> None:
|
51
54
|
# Call superclass initializer if needed
|
52
|
-
super().__init__(id, lang, sources, analysis, notes, confidence, attribution, extracted, evidence, media, identifiers,links=links)
|
55
|
+
super().__init__(id, lang, sources, analysis, notes, confidence, attribution, extracted, evidence, media, identifiers,links=links,uri=uri)
|
53
56
|
|
54
57
|
# Initialize mutable attributes to empty lists if None
|
55
58
|
self.sources = sources if sources is not None else []
|
@@ -108,103 +111,59 @@ class Person(Subject):
|
|
108
111
|
|
109
112
|
@property
|
110
113
|
def _as_dict_(self):
|
111
|
-
|
112
|
-
if isinstance(value, (str, int, float, bool, type(None))):
|
113
|
-
return value
|
114
|
-
elif isinstance(value, dict):
|
115
|
-
return {k: _serialize(v) for k, v in value.items()}
|
116
|
-
elif isinstance(value, (list, tuple, set)):
|
117
|
-
return [_serialize(v) for v in value]
|
118
|
-
elif hasattr(value, "_as_dict_"):
|
119
|
-
return value._as_dict_
|
120
|
-
else:
|
121
|
-
return str(value) # fallback for unknown objects
|
122
|
-
|
123
|
-
subject_fields = super()._as_dict_ # Start with base class fields
|
114
|
+
type_as_dict = super()._as_dict_ # Start with base class fields
|
124
115
|
# Only add Relationship-specific fields
|
125
|
-
|
116
|
+
type_as_dict.update({
|
126
117
|
'private': self.private,
|
127
118
|
'living': self.living,
|
128
119
|
'gender': self.gender._as_dict_ if self.gender else None,
|
129
120
|
'names': [name._as_dict_ for name in self.names],
|
130
|
-
'facts': [fact for fact in self.facts],
|
131
|
-
'uri':
|
121
|
+
'facts': [fact._as_dict_ for fact in self.facts],
|
122
|
+
'uri': self.uri._as_dict_ if self.uri else None
|
132
123
|
|
133
124
|
})
|
134
125
|
|
135
|
-
|
136
|
-
|
137
|
-
if value is not None:
|
138
|
-
subject_fields[key] = _serialize(value)
|
139
|
-
|
140
|
-
# 3) merge and filter out None *at the top level*
|
141
|
-
return {
|
142
|
-
k: v
|
143
|
-
for k, v in subject_fields.items()
|
144
|
-
if v is not None and not (isinstance(v, Sized) and len(v) == 0)
|
145
|
-
}
|
146
|
-
|
147
|
-
|
126
|
+
return Serialization.serialize_dict(type_as_dict)
|
127
|
+
|
148
128
|
@classmethod
|
149
129
|
def _from_json_(cls, data: dict):
|
150
130
|
"""
|
151
131
|
Create a Person instance from a JSON-dict (already parsed).
|
152
132
|
"""
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
inst = cls(
|
188
|
-
id = id_,
|
189
|
-
lang = lang,
|
190
|
-
sources = sources,
|
191
|
-
analysis = analysis,
|
192
|
-
notes = notes,
|
193
|
-
confidence = confidence,
|
194
|
-
attribution = attribution,
|
195
|
-
extracted = extracted,
|
196
|
-
evidence = evidence,
|
197
|
-
media = media,
|
198
|
-
identifiers = identifiers,
|
199
|
-
private = private,
|
200
|
-
gender = gender,
|
201
|
-
names = names,
|
202
|
-
facts = facts,
|
203
|
-
living = living,
|
204
|
-
links = links
|
205
|
-
)
|
206
|
-
|
207
|
-
return inst
|
133
|
+
return Serialization.deserialize(data, Person)
|
134
|
+
|
135
|
+
@classmethod
|
136
|
+
def from_familysearch(cls, pid: str, token: str, *, base_url: Optional[str] = None):
|
137
|
+
"""
|
138
|
+
Fetch a single person by PID from FamilySearch and return a Person.
|
139
|
+
- pid: e.g. "KPHP-4B4"
|
140
|
+
- token: OAuth2 access token (Bearer)
|
141
|
+
- base_url: override API base (defaults to settings.FS_API_BASE or prod)
|
142
|
+
"""
|
143
|
+
import requests
|
144
|
+
default_base = "https://apibeta.familysearch.org/platform/"
|
145
|
+
|
146
|
+
base = (base_url or default_base).rstrip("/") + "/"
|
147
|
+
url = urljoin(base, f"tree/persons/{pid}")
|
148
|
+
|
149
|
+
headers = {
|
150
|
+
"Accept": "application/json",
|
151
|
+
"Authorization": f"Bearer {token}",
|
152
|
+
}
|
153
|
+
|
154
|
+
resp = requests.get(url, headers=headers, timeout=(5, 30))
|
155
|
+
resp.raise_for_status()
|
156
|
+
|
157
|
+
payload = resp.json()
|
158
|
+
persons = payload.get("persons") or []
|
159
|
+
|
160
|
+
# Prefer exact match on PID, else first item if present
|
161
|
+
person_json = next((p for p in persons if (p.get("id") == pid)), None) or (persons[0] if persons else None)
|
162
|
+
if not person_json:
|
163
|
+
raise ValueError(f"FamilySearch returned no person for PID {pid}")
|
164
|
+
|
165
|
+
# Keep your existing deserialization helper
|
166
|
+
return Serialization.deserialize(person_json, Person)
|
208
167
|
|
209
168
|
class QuickPerson:
|
210
169
|
"""A GedcomX Person Data Type created with basic information.
|
@@ -221,7 +180,7 @@ class QuickPerson:
|
|
221
180
|
Raises:
|
222
181
|
ValueError: If `id` is not a valid UUID.
|
223
182
|
"""
|
224
|
-
def __new__(cls, name:
|
183
|
+
def __new__(cls, name: str, dob: Optional[str] = None, dod: Optional[str] = None):
|
225
184
|
# Build facts from args
|
226
185
|
facts = []
|
227
186
|
if dob:
|
@@ -230,4 +189,4 @@ class QuickPerson:
|
|
230
189
|
facts.append(Fact(type=FactType.Death, date=Date(original=dod)))
|
231
190
|
|
232
191
|
# Return the different class instance
|
233
|
-
return Person(facts=facts, names=[name] if name else None)
|
192
|
+
return Person(facts=facts, names=[QuickName(name=name)] if name else None)
|
gedcomx/PlaceDescription.py
CHANGED
@@ -17,17 +17,18 @@ class PlaceDescription(Subject):
|
|
17
17
|
identifier = "http://gedcomx.org/v1/PlaceDescription"
|
18
18
|
version = 'http://gedcomx.org/conceptual-model/v1'
|
19
19
|
|
20
|
-
def __init__(self, id: str =None,
|
20
|
+
def __init__(self, id: Optional[str] =None,
|
21
21
|
lang: str = 'en',
|
22
|
-
sources: Optional[List[SourceReference]] =
|
23
|
-
analysis: Resource = None,
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
22
|
+
sources: Optional[List[SourceReference]] = None,
|
23
|
+
analysis: Optional[Resource] = None,
|
24
|
+
notes: Optional[List[Note]] =None,
|
25
|
+
confidence: Optional[ConfidenceLevel] = None,
|
26
|
+
attribution: Optional[Attribution] = None,
|
27
|
+
extracted: Optional[bool] = None,
|
28
|
+
evidence: Optional[List[EvidenceReference]] = None,
|
29
|
+
media: Optional[List[SourceReference]] = None,
|
30
|
+
identifiers: Optional[IdentifierList] = None,
|
31
|
+
names: Optional[List[TextValue]] = None,
|
31
32
|
type: Optional[str] = None,
|
32
33
|
place: Optional[URI] = None,
|
33
34
|
jurisdiction: Optional["Resource | PlaceDescription"] = None, # PlaceDescription
|
@@ -35,6 +36,7 @@ class PlaceDescription(Subject):
|
|
35
36
|
longitude: Optional[float] = None,
|
36
37
|
temporalDescription: Optional[Date] = None,
|
37
38
|
spatialDescription: Optional[Resource] = None,) -> None:
|
39
|
+
|
38
40
|
super().__init__(id, lang, sources, analysis, notes, confidence, attribution, extracted, evidence, media, identifiers)
|
39
41
|
self.names = names
|
40
42
|
self.type = type
|
@@ -47,8 +49,8 @@ class PlaceDescription(Subject):
|
|
47
49
|
|
48
50
|
@property
|
49
51
|
def _as_dict_(self):
|
50
|
-
|
51
|
-
|
52
|
+
type_as_dict = super()._as_dict_
|
53
|
+
type_as_dict.update({
|
52
54
|
"names": [n for n in self.names] if self.names else None,
|
53
55
|
"type": self.type if self.type else None,
|
54
56
|
"place": self.place._as_dict_ if self.place else None,
|
@@ -56,6 +58,13 @@ class PlaceDescription(Subject):
|
|
56
58
|
"latitude": float(self.latitide) if self.latitide else None,
|
57
59
|
"longitude": float(self.longitute) if self.longitute else None,
|
58
60
|
"temporalDescription": self.temporalDescription if self.temporalDescription else None,
|
59
|
-
"spatialDescription": self.spacialDescription._as_dict_ if self.
|
61
|
+
"spatialDescription": self.spacialDescription._as_dict_ if self.spacialDescription else None
|
60
62
|
})
|
61
|
-
return Serialization.serialize_dict(
|
63
|
+
return Serialization.serialize_dict(type_as_dict)
|
64
|
+
|
65
|
+
@classmethod
|
66
|
+
def _from_json_(cls, data: dict):
|
67
|
+
"""
|
68
|
+
Create a PlaceDescription instance from a JSON-dict (already parsed).
|
69
|
+
"""
|
70
|
+
return Serialization.deserialize(data, PlaceDescription)
|
gedcomx/PlaceReference.py
CHANGED
@@ -7,27 +7,24 @@ class PlaceReference:
|
|
7
7
|
identifier = 'http://gedcomx.org/v1/PlaceReference'
|
8
8
|
version = 'http://gedcomx.org/conceptual-model/v1'
|
9
9
|
|
10
|
-
def __init__(self,
|
10
|
+
def __init__(self,
|
11
|
+
original: Optional[str] = None,
|
12
|
+
description: Optional[Resource] = None) -> None:
|
11
13
|
self.original = original
|
12
|
-
self.
|
14
|
+
self.description = description
|
13
15
|
|
14
16
|
@property
|
15
17
|
def _as_dict_(self):
|
16
|
-
|
18
|
+
type_as_dict = {
|
17
19
|
'original': self.original,
|
18
|
-
'
|
20
|
+
'description': self.description._as_dict_ if self.description else None
|
19
21
|
}
|
20
|
-
return Serialization.serialize_dict(
|
22
|
+
return Serialization.serialize_dict(type_as_dict)
|
23
|
+
|
24
|
+
@classmethod
|
25
|
+
def _from_json_(cls, data):
|
26
|
+
|
27
|
+
return Serialization.deserialize(data, PlaceReference)
|
21
28
|
|
22
|
-
def ensure_list(val):
|
23
|
-
if val is None:
|
24
|
-
return []
|
25
|
-
return val if isinstance(val, list) else [val]
|
26
29
|
|
27
|
-
# PlaceReference
|
28
|
-
PlaceReference._from_json_ = classmethod(lambda cls, data: PlaceReference(
|
29
|
-
original=data.get('original'),
|
30
|
-
descriptionRef=Resource._from_json_(data['description']) if data.get('description') else None
|
31
|
-
))
|
32
30
|
|
33
|
-
|
gedcomx/Relationship.py
CHANGED
@@ -35,12 +35,15 @@ class Relationship(Subject):
|
|
35
35
|
person2 (Person): Second Person in Relationship
|
36
36
|
|
37
37
|
Raises:
|
38
|
-
|
38
|
+
|
39
39
|
"""
|
40
40
|
identifier = 'http://gedcomx.org/v1/Relationship'
|
41
41
|
version = 'http://gedcomx.org/conceptual-model/v1'
|
42
42
|
|
43
|
-
def __init__(self,
|
43
|
+
def __init__(self,
|
44
|
+
person1: Optional[Person | Resource] = None,
|
45
|
+
person2: Optional[Person | Resource] = None,
|
46
|
+
facts: Optional[List[Fact]] = None,
|
44
47
|
id: Optional[str] = None,
|
45
48
|
lang: Optional[str] = None,
|
46
49
|
sources: Optional[List[SourceReference]] = None,
|
@@ -53,74 +56,40 @@ class Relationship(Subject):
|
|
53
56
|
media: Optional[List[SourceReference]] = None,
|
54
57
|
identifiers: Optional[List[Identifier]] = None,
|
55
58
|
type: Optional[RelationshipType] = None,
|
56
|
-
|
57
|
-
person2: Optional[Person | Resource] = None,
|
58
|
-
facts: Optional[List[Fact]] = None) -> None:
|
59
|
+
) -> None:
|
59
60
|
|
60
61
|
# Call superclass initializer if required
|
61
62
|
super().__init__(id, lang, sources, analysis, notes, confidence, attribution, extracted, evidence, media, identifiers)
|
62
63
|
|
63
|
-
# Initialize optional parameters with default empty lists if None
|
64
|
-
#self.sources = sources if sources is not None else []
|
65
|
-
#self.notes = notes if notes is not None else []
|
66
|
-
#self.evidence = evidence if evidence is not None else []
|
67
|
-
#self.media = media if media is not None else []
|
68
|
-
#self.identifiers = identifiers if identifiers is not None else []
|
69
|
-
#self.facts = facts if facts is not None else []
|
70
|
-
|
71
|
-
# Initialize other attributes
|
72
64
|
self.type = type
|
73
65
|
self.person1 = person1
|
74
66
|
self.person2 = person2
|
75
|
-
self.facts = facts if facts else
|
67
|
+
self.facts = facts if facts else []
|
76
68
|
|
69
|
+
def add_fact(self,fact: Fact):
|
70
|
+
if fact is not None and isinstance(fact,Fact):
|
71
|
+
for existing_fact in self.facts:
|
72
|
+
if fact == existing_fact:
|
73
|
+
return
|
74
|
+
self.facts.append(fact)
|
75
|
+
else:
|
76
|
+
raise TypeError(f"Expected type 'Fact' recieved type {type(fact)}")
|
77
|
+
|
77
78
|
@property
|
78
79
|
def _as_dict_(self):
|
79
|
-
|
80
|
+
type_as_dict = super()._as_dict_
|
81
|
+
type_as_dict.update({
|
80
82
|
"type": self.type.value if isinstance(self.type, RelationshipType) else self.type,
|
81
|
-
"person1": self.person1.
|
82
|
-
"person2": self.person2.
|
83
|
+
"person1": self.person1._as_dict_ if self.person1 else None,
|
84
|
+
"person2": self.person2._as_dict_ if self.person2 else None,
|
83
85
|
"facts": [fact for fact in self.facts] if self.facts else None
|
84
86
|
})
|
87
|
+
return Serialization.serialize_dict(type_as_dict)
|
85
88
|
|
86
89
|
@classmethod
|
87
90
|
def _from_json_(cls, data: dict):
|
88
91
|
"""
|
89
|
-
Create a
|
92
|
+
Create a Person instance from a JSON-dict (already parsed).
|
90
93
|
"""
|
91
|
-
|
92
|
-
if value is None:
|
93
|
-
return []
|
94
|
-
if isinstance(value, list):
|
95
|
-
return value
|
96
|
-
return [value] # wrap single item in list
|
97
|
-
|
98
|
-
# Basic scalar fields (adjust as needed)
|
99
|
-
id_ = data.get('id')
|
100
|
-
type_ = data.get('type')
|
101
|
-
extracted = data.get('extracted', None)
|
102
|
-
private = data.get('private', None)
|
103
|
-
|
104
|
-
# Complex singletons (adjust as needed)
|
105
|
-
person1 = Resource.from_url(data.get('person1')['resource']) if data.get('person1') else None
|
106
|
-
person2 = Resource.from_url(data.get('person2')['resource']) if data.get('person2') else None
|
107
|
-
facts = [Fact._from_json_(o) for o in ensure_list(data.get('facts'))]
|
108
|
-
sources = [SourceReference._from_json_(o) for o in ensure_list(data.get('sources'))]
|
109
|
-
notes = [Note._from_json_(o) for o in ensure_list(data.get('notes'))]
|
110
|
-
|
111
|
-
# Build the instance
|
112
|
-
inst = cls(
|
113
|
-
id = id_,
|
114
|
-
type = type_,
|
115
|
-
extracted = extracted,
|
116
|
-
#private = private, #TODO Has this been added?
|
117
|
-
person1 = person1,
|
118
|
-
person2 = person2,
|
119
|
-
facts = facts,
|
120
|
-
sources = sources,
|
121
|
-
notes = notes
|
122
|
-
)
|
123
|
-
|
124
|
-
return inst
|
125
|
-
|
94
|
+
return Serialization.deserialize(data, Relationship)
|
126
95
|
|