gedcom-x 0.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.
gedcomx/Gender.py ADDED
@@ -0,0 +1,48 @@
1
+ from enum import Enum
2
+ from typing import List, Optional
3
+
4
+ from gedcomx.Attribution import Attribution
5
+ from gedcomx.Conclusion import ConfidenceLevel
6
+ from gedcomx.Note import Note
7
+ from gedcomx.SourceReference import SourceReference
8
+ from gedcomx.URI import URI
9
+
10
+ from .Conclusion import Conclusion
11
+ from .Qualifier import Qualifier
12
+
13
+ class GenderType(Enum):
14
+ Male = "http://gedcomx.org/Male"
15
+ Female = "http://gedcomx.org/Female"
16
+ Unknown = "http://gedcomx.org/Unknown"
17
+ Intersex = "http://gedcomx.org/Intersex"
18
+
19
+ @property
20
+ def description(self):
21
+ descriptions = {
22
+ GenderType.Male: "Male gender.",
23
+ GenderType.Female: "Female gender.",
24
+ GenderType.Unknown: "Unknown gender.",
25
+ GenderType.Intersex: "Intersex (assignment at birth)."
26
+ }
27
+ return descriptions.get(self, "No description available.")
28
+
29
+ class Gender(Conclusion):
30
+ identifier = 'http://gedcomx.org/v1/Gender'
31
+ version = 'http://gedcomx.org/conceptual-model/v1'
32
+
33
+ def __init__(self,
34
+ id: Optional[str] = None,
35
+ lang: Optional[str] = 'en',
36
+ sources: Optional[List[SourceReference]] = [],
37
+ analysis: Optional[URI] = None,
38
+ notes: Optional[List[Note]] = [],
39
+ confidence: Optional[ConfidenceLevel] = None,
40
+ attribution: Optional[Attribution] = None,
41
+ type: GenderType = None
42
+ ) -> None:
43
+ super().__init__(id, lang, sources, analysis, notes, confidence, attribution)
44
+ self.type = type
45
+
46
+ @classmethod
47
+ def _from_json_(cls,json_text):
48
+ return Gender()
gedcomx/Group.py ADDED
@@ -0,0 +1,37 @@
1
+ from enum import Enum
2
+ from typing import List, Optional
3
+
4
+ from .Attribution import Attribution
5
+ from .Conclusion import ConfidenceLevel
6
+ from .Date import Date
7
+ from .EvidenceReference import EvidenceReference
8
+ from .Identifier import Identifier
9
+ from .Note import Note
10
+ from .PlaceReference import PlaceReference
11
+ from .SourceReference import SourceReference
12
+ from .URI import URI
13
+
14
+ from .TextValue import TextValue
15
+ from .Subject import Subject
16
+
17
+ class GroupRoleType(Enum):
18
+ def __init__(self) -> None:
19
+ super().__init__()
20
+
21
+ class GroupRole:
22
+ identifier = 'http://gedcomx.org/v1/GroupRole'
23
+ version = 'http://gedcomx.org/conceptual-model/v1'
24
+
25
+ def __init__(self, person: URI,type: Optional[Enum], date: Optional[Date],details: Optional[str]) -> None:
26
+ pass
27
+
28
+ class Group(Subject):
29
+ identifier = 'http://gedcomx.org/v1/Group'
30
+ version = 'http://gedcomx.org/conceptual-model/v1'
31
+
32
+ def __init__(self, id: str | None, lang: str | None, sources: SourceReference | None, analysis: URI | None, notes: Note | None, confidence: ConfidenceLevel | None, attribution: Attribution | None, extracted: bool | None, evidence: List[EvidenceReference] | None, media: List[SourceReference] | None, identifiers: List[Identifier] | None,
33
+ names: TextValue,
34
+ date: Optional[Date],
35
+ place: Optional[PlaceReference],
36
+ roles: Optional[List[GroupRole]]) -> None:
37
+ super().__init__(id, lang, sources, analysis, notes, confidence, attribution, extracted, evidence, media, identifiers)
gedcomx/Identifier.py ADDED
@@ -0,0 +1,89 @@
1
+
2
+ from enum import Enum
3
+
4
+ from typing import List, Optional, Dict, Any
5
+
6
+ from .Qualifier import Qualifier
7
+ from .URI import URI
8
+
9
+ class IdentifierType(Enum):
10
+ Primary = "http://gedcomx.org/Primary"
11
+ Authority = "http://gedcomx.org/Authority"
12
+ Deprecated = "http://gedcomx.org/Deprecated"
13
+ Persistant = "http://gedcomx.org/Persistent"
14
+
15
+ @property
16
+ def description(self):
17
+ descriptions = {
18
+ IdentifierType.Primary: (
19
+ "The primary identifier for the resource. The value of the identifier MUST resolve to the instance of "
20
+ "Subject to which the identifier applies."
21
+ ),
22
+ IdentifierType.Authority: (
23
+ "An identifier for the resource in an external authority or other expert system. The value of the identifier "
24
+ "MUST resolve to a public, authoritative source for information about the Subject to which the identifier applies."
25
+ ),
26
+ IdentifierType.Deprecated: (
27
+ "An identifier that has been relegated, deprecated, or otherwise downgraded. This identifier is commonly used "
28
+ "as the result of a merge when what was once a primary identifier for a resource is no longer the primary identifier. "
29
+ "The value of the identifier MUST resolve to the instance of Subject to which the identifier applies."
30
+ )
31
+ }
32
+ return descriptions.get(self, "No description available.")
33
+
34
+ class Identifier:
35
+ identifier = 'http://gedcomx.org/v1/Identifier'
36
+ version = 'http://gedcomx.org/conceptual-model/v1'
37
+
38
+ def __init__(self, value: Optional[URI], type: Optional[IdentifierType] = IdentifierType.Primary) -> None:
39
+ self.value = value
40
+ self.type = type
41
+
42
+
43
+ @property
44
+ def _as_dict_(self):
45
+ def _serialize(value):
46
+ if isinstance(value, (str, int, float, bool, type(None))):
47
+ return value
48
+ elif isinstance(value, dict):
49
+ return {k: _serialize(v) for k, v in value.items()}
50
+ elif isinstance(value, (list, tuple, set)):
51
+ return [_serialize(v) for v in value]
52
+ elif hasattr(value, "_as_dict_"):
53
+ return value._as_dict_
54
+ else:
55
+ return str(value) # fallback for unknown objects
56
+
57
+
58
+ identifier_fields = {
59
+ 'value': self.value if self.value else None,
60
+ 'type': self.type.value if self.type else None
61
+
62
+ }
63
+
64
+ # Serialize and exclude None values
65
+ for key, value in identifier_fields.items():
66
+ if value is not None:
67
+ identifier_fields[key] = _serialize(value)
68
+
69
+ return identifier_fields
70
+
71
+ @classmethod
72
+ def _from_json_(cls, data: Dict[str, Any]) -> 'Identifier':
73
+ """
74
+ Construct an Identifier from a dict parsed from JSON.
75
+ """
76
+ # Parse value (URI dict or string)
77
+
78
+ for key in data.keys():
79
+ type = key
80
+ value = data[key]
81
+ uri_obj: Optional[URI] = None
82
+ # TODO DO THIS BETTER
83
+
84
+ # Parse type
85
+ raw_type = data.get('type')
86
+ id_type: Optional[IdentifierType] = IdentifierType(raw_type)
87
+
88
+
89
+ return cls(value=value, type=id_type)
gedcomx/Name.py ADDED
@@ -0,0 +1,241 @@
1
+ from enum import Enum
2
+ from typing import List,Optional
3
+
4
+ from .Attribution import Attribution
5
+ from .Conclusion import Conclusion, ConfidenceLevel
6
+ from .Date import Date
7
+ from .Note import Note
8
+ from .SourceReference import SourceReference
9
+ from .URI import URI
10
+
11
+
12
+ class NameType(Enum):
13
+ BirthName = "http://gedcomx.org/BirthName"
14
+ MarriedName = "http://gedcomx.org/MarriedName"
15
+ AlsoKnownAs = "http://gedcomx.org/AlsoKnownAs"
16
+ Nickname = "http://gedcomx.org/Nickname"
17
+ AdoptiveName = "http://gedcomx.org/AdoptiveName"
18
+ FormalName = "http://gedcomx.org/FormalName"
19
+ ReligiousName = "http://gedcomx.org/ReligiousName"
20
+
21
+ @property
22
+ def description(self):
23
+ descriptions = {
24
+ NameType.BirthName: "Name given at birth.",
25
+ NameType.MarriedName: "Name accepted at marriage.",
26
+ NameType.AlsoKnownAs: "\"Also known as\" name.",
27
+ NameType.Nickname: "Nickname.",
28
+ NameType.AdoptiveName: "Name given at adoption.",
29
+ NameType.FormalName: "A formal name, usually given to distinguish it from a name more commonly used.",
30
+ NameType.ReligiousName: "A name given at a religious rite or ceremony."
31
+ }
32
+ return descriptions.get(self, "No description available.")
33
+
34
+ class NamePartQualifier(Enum):
35
+ Title = "http://gedcomx.org/Title"
36
+ Primary = "http://gedcomx.org/Primary"
37
+ Secondary = "http://gedcomx.org/Secondary"
38
+ Middle = "http://gedcomx.org/Middle"
39
+ Familiar = "http://gedcomx.org/Familiar"
40
+ Religious = "http://gedcomx.org/Religious"
41
+ Family = "http://gedcomx.org/Family"
42
+ Maiden = "http://gedcomx.org/Maiden"
43
+ Patronymic = "http://gedcomx.org/Patronymic"
44
+ Matronymic = "http://gedcomx.org/Matronymic"
45
+ Geographic = "http://gedcomx.org/Geographic"
46
+ Occupational = "http://gedcomx.org/Occupational"
47
+ Characteristic = "http://gedcomx.org/Characteristic"
48
+ Postnom = "http://gedcomx.org/Postnom"
49
+ Particle = "http://gedcomx.org/Particle"
50
+ RootName = "http://gedcomx.org/RootName"
51
+
52
+ @property
53
+ def description(self):
54
+ descriptions = {
55
+ NamePartQualifier.Title: "A designation for honorifics (e.g., Dr., Rev., His Majesty, Haji), ranks (e.g., Colonel, General), positions (e.g., Count, Chief), or other titles (e.g., PhD, MD). Name part qualifiers of type Title SHOULD NOT provide a value.",
56
+ NamePartQualifier.Primary: "A designation for the most prominent name among names of that type (e.g., the primary given name). Name part qualifiers of type Primary SHOULD NOT provide a value.",
57
+ NamePartQualifier.Secondary: "A designation for a name that is not primary in its importance among names of that type. Name part qualifiers of type Secondary SHOULD NOT provide a value.",
58
+ NamePartQualifier.Middle: "Useful for cultures designating a middle name distinct from a given name and surname. Name part qualifiers of type Middle SHOULD NOT provide a value.",
59
+ NamePartQualifier.Familiar: "A designation for one's familiar name. Name part qualifiers of type Familiar SHOULD NOT provide a value.",
60
+ NamePartQualifier.Religious: "A name given for religious purposes. Name part qualifiers of type Religious SHOULD NOT provide a value.",
61
+ NamePartQualifier.Family: "A name that associates a person with a group, such as a clan, tribe, or patriarchal hierarchy. Name part qualifiers of type Family SHOULD NOT provide a value.",
62
+ NamePartQualifier.Maiden: "Original surname retained by women after adopting a new surname upon marriage. Name part qualifiers of type Maiden SHOULD NOT provide a value.",
63
+ NamePartQualifier.Patronymic: "A name derived from a father or paternal ancestor. Name part qualifiers of type Patronymic SHOULD NOT provide a value.",
64
+ NamePartQualifier.Matronymic: "A name derived from a mother or maternal ancestor. Name part qualifiers of type Matronymic SHOULD NOT provide a value.",
65
+ NamePartQualifier.Geographic: "A name derived from associated geography. Name part qualifiers of type Geographic SHOULD NOT provide a value.",
66
+ NamePartQualifier.Occupational: "A name derived from one's occupation. Name part qualifiers of type Occupational SHOULD NOT provide a value.",
67
+ NamePartQualifier.Characteristic: "A name derived from a characteristic. Name part qualifiers of type Characteristic SHOULD NOT provide a value.",
68
+ NamePartQualifier.Postnom: "A name mandated by law for populations in specific regions. Name part qualifiers of type Postnom SHOULD NOT provide a value.",
69
+ NamePartQualifier.Particle: "A grammatical designation for articles, prepositions, conjunctions, and other words used as name parts. Name part qualifiers of type Particle SHOULD NOT provide a value.",
70
+ NamePartQualifier.RootName: "The 'root' of a name part, as distinguished from prefixes or suffixes (e.g., the root of 'Wilkówna' is 'Wilk'). A RootName qualifier MUST provide a value property."
71
+ }
72
+ return descriptions.get(self, "No description available.")
73
+
74
+ class NamePartType(Enum):
75
+ Prefix = "http://gedcomx.org/Prefix"
76
+ Suffix = "http://gedcomx.org/Suffix"
77
+ Given = "http://gedcomx.org/Given"
78
+ Surname = "http://gedcomx.org/Surname"
79
+
80
+ @property
81
+ def description(self):
82
+ descriptions = {
83
+ NamePartType.Prefix: "A name prefix.",
84
+ NamePartType.Suffix: "A name suffix.",
85
+ NamePartType.Given: "A given name.",
86
+ NamePartType.Surname: "A surname."
87
+ }
88
+ return descriptions.get(self, "No description available.")
89
+
90
+ class NamePart:
91
+ identifier = 'http://gedcomx.org/v1/NamePart'
92
+ version = 'http://gedcomx.org/conceptual-model/v1'
93
+
94
+ def __init__(self,
95
+ type: Optional[NamePartType] = None,
96
+ value: Optional[str] = None,
97
+ qualifiers: Optional[List[NamePartQualifier]] = []) -> None:
98
+ self.type = type
99
+ self.value = value
100
+ self.qualifiers = qualifiers
101
+
102
+ def _prop_dict(self):
103
+ return {'type': self.type.value if self.type else None,
104
+ 'value': self.value if self.value else None,
105
+ 'qualifiers': [qualifier.value for qualifier in self.qualifiers]}
106
+
107
+ def __eq__(self, other):
108
+ if not isinstance(other, NamePart):
109
+ return NotImplemented
110
+ return (self.type == other.type and
111
+ self.value == other.value and
112
+ self.qualifiers == other.qualifiers)
113
+
114
+ class NameForm:
115
+ identifier = 'http://gedcomx.org/v1/NameForm'
116
+ version = 'http://gedcomx.org/conceptual-model/v1'
117
+
118
+ def __init__(self, lang: Optional[str] = 'en', fullText: Optional[str] = None,parts: Optional[List[NamePart]] = []) -> None:
119
+ self.lang = lang
120
+ self.fullText = fullText
121
+ self.parts = parts
122
+
123
+ def _prop_dict(self):
124
+ return {'lang': self.lang,
125
+ 'fullText': self.fullText,
126
+ 'parts': [part._prop_dict() for part in self.parts]}
127
+
128
+ def _fulltext_parts(self):
129
+ pass
130
+
131
+ class Name(Conclusion):
132
+ identifier = 'http://gedcomx.org/v1/Name'
133
+ version = 'http://gedcomx.org/conceptual-model/v1'
134
+
135
+ @staticmethod
136
+ def simple(text: str):
137
+ """
138
+ Takes a string and returns a GedcomX Name Object
139
+ """
140
+ text = text.replace("/","")
141
+ parts = text.rsplit(' ', 1)
142
+
143
+ # Assign val1 and val2 based on the split
144
+ given = parts[0] if len(parts) > 1 else ""
145
+ surname = parts[1] if len(parts) > 1 else parts[0]
146
+
147
+ # Remove any '/' characters from both val1 and val2
148
+ #given = given.replace('/', '')
149
+ #surname = surname.replace('/', '')
150
+
151
+ given_name_part = NamePart(type = NamePartType.Given, value=given)
152
+ surname_part = NamePart(type = NamePartType.Surname, value=surname)
153
+
154
+ name_form = NameForm(fullText=text,parts=[given_name_part,surname_part])
155
+ name = Name(type=NameType.BirthName,nameForms=[name_form])
156
+ return name
157
+
158
+ def __init__(self, id: str = None,
159
+ lang: str = 'en',
160
+ sources: Optional[List[SourceReference]] = [],
161
+ analysis: URI = None,
162
+ notes: Optional[List[Note]] = [],
163
+ confidence: ConfidenceLevel = None,
164
+ attribution: Attribution = None,
165
+ type: Optional[NameType] = None,
166
+ nameForms: NameForm = [],
167
+ date: Optional[Date] = None) -> None:
168
+ super().__init__(id, lang, sources, analysis, notes, confidence, attribution)
169
+ self.type = type
170
+ self.nameForms = nameForms
171
+ self.date = date
172
+
173
+ def _add_name_part(self, namepart_to_add: NamePart):
174
+ if namepart_to_add and isinstance(namepart_to_add, NamePart):
175
+ for current_namepart in self.nameForms[0].parts:
176
+ if namepart_to_add == current_namepart:
177
+ return False
178
+ self.nameForms[0].parts.append(namepart_to_add)
179
+
180
+ @property
181
+ def _as_dict_(self):
182
+ name_as_dict = super()._as_dict_
183
+ name_as_dict.update( {
184
+ 'type':self.type.value if self.type else None,
185
+ 'nameForms': [nameForm._prop_dict() for nameForm in self.nameForms],
186
+ 'date': self.date._prop_date() if self.date else None})
187
+ return name_as_dict
188
+
189
+ def ensure_list(val):
190
+ if val is None:
191
+ return []
192
+ return val if isinstance(val, list) else [val]
193
+
194
+ NamePart._from_json_ = classmethod(lambda cls, data: NamePart(
195
+ type=NamePartType(data['type']) if 'type' in data else None,
196
+ value=data.get('value'),
197
+ qualifiers=[NamePartQualifier(q) for q in ensure_list(data.get('qualifiers'))]
198
+ ))
199
+
200
+ NamePart._to_dict_ = lambda self: {
201
+ 'type': self.type.value if self.type else None,
202
+ 'value': self.value,
203
+ 'qualifiers': [q.value for q in self.qualifiers] if self.qualifiers else []
204
+ }
205
+
206
+
207
+ # NameForm
208
+ NameForm._from_json_ = classmethod(lambda cls, data: NameForm(
209
+ lang=data.get('lang', 'en'),
210
+ fullText=data.get('fullText'),
211
+ parts=[NamePart._from_json_(p) for p in ensure_list(data.get('parts'))]
212
+ ))
213
+
214
+ NameForm._to_dict_ = lambda self: {
215
+ 'lang': self.lang,
216
+ 'fullText': self.fullText,
217
+ 'parts': [p._to_dict_() for p in self.parts] if self.parts else []
218
+ }
219
+
220
+
221
+ # Name
222
+ Name._from_json_ = classmethod(lambda cls, data: cls(
223
+ id=data.get('id'),
224
+ lang=data.get('lang', 'en'),
225
+ sources=[SourceReference._from_json_(s) for s in ensure_list(data.get('sources'))],
226
+ analysis=URI._from_json_(data['analysis']) if data.get('analysis') else None,
227
+ notes=[Note._from_json_(n) for n in ensure_list(data.get('notes'))],
228
+ confidence=ConfidenceLevel._from_json_(data['confidence']) if data.get('confidence') else None,
229
+ attribution=Attribution._from_json_(data['attribution']) if data.get('attribution') else None,
230
+ type=NameType(data['type']) if data.get('type') else None,
231
+ nameForms=[NameForm._from_json_(nf) for nf in ensure_list(data.get('nameForms'))],
232
+ date=Date._from_json_(data['date']) if data.get('date') else None
233
+ ))
234
+
235
+ Name._to_dict_ = lambda self: {
236
+ **self._as_dict_,
237
+ 'type': self.type.value if self.type else None,
238
+ 'nameForms': [nf._to_dict_() for nf in self.nameForms] if self.nameForms else [],
239
+ 'date': self.date._prop_date() if self.date else None
240
+ }
241
+
gedcomx/Note.py ADDED
@@ -0,0 +1,65 @@
1
+ from typing import Optional
2
+
3
+ from .Attribution import Attribution
4
+
5
+ class Note:
6
+ identifier = 'http://gedcomx.org/v1/Note'
7
+ version = 'http://gedcomx.org/conceptual-model/v1'
8
+
9
+ def __init__(self,lang: Optional[str] = 'en', subject: Optional[str] = None, text: Optional[str] = None, attribution: Optional[Attribution] = None) -> None:
10
+ self.lang = lang
11
+ self.subject = subject
12
+ self.text = text
13
+ self.attribution = attribution
14
+
15
+
16
+
17
+ def append(self, text_to_add: str):
18
+ if text_to_add and isinstance(text_to_add, str):
19
+ self.text = self.text + text_to_add
20
+ else:
21
+ raise ValueError("The text to add must be a non-empty string.")
22
+
23
+ @property
24
+ def _as_dict_(self):
25
+ return {
26
+ "lang":self.lang if self.lang else None,
27
+ "subject":self.subject if self.subject else None,
28
+ "text":self.text if self.text else None,
29
+ "attribution": self.attribution if self.attribution else None
30
+ }
31
+
32
+ def __eq__(self, other):
33
+ if not isinstance(other, Note):
34
+ return NotImplemented
35
+
36
+ def safe_str(val):
37
+ return val.strip() if isinstance(val, str) else ''
38
+
39
+ return (
40
+ #safe_str(self.lang) == safe_str(other.lang) and
41
+ #safe_str(self.subject) == safe_str(other.subject) and
42
+ safe_str(self.text) == safe_str(other.text) #and
43
+ # self.attribution == other.attribution # Assumes Attribution defines __eq__
44
+ )
45
+
46
+ @classmethod
47
+ def _from_json_(cls, data: dict):
48
+ """
49
+ Create a Note instance from a JSON-dict (already parsed).
50
+ """
51
+ # Basic scalar fields
52
+ lang = data.get('lang', 'en')
53
+ text = data.get('text')
54
+ subject = data.get('subject')
55
+ # Add other fields as needed
56
+
57
+ # Build the instance
58
+ inst = cls(
59
+ lang = lang,
60
+ text = text,
61
+ subject = subject,
62
+ # Add other fields as needed
63
+ )
64
+
65
+ return inst
@@ -0,0 +1,10 @@
1
+ from typing import Optional
2
+
3
+ from .URI import URI
4
+
5
+ class OnlineAccount:
6
+ identifier = 'http://gedcomx.org/v1/OnlineAccount'
7
+ version = 'http://gedcomx.org/conceptual-model/v1'
8
+
9
+ def __init__(self, serviceHomepage: URI, accountName: str) -> None:
10
+ pass
gedcomx/Person.py ADDED
@@ -0,0 +1,178 @@
1
+ from enum import Enum
2
+ from typing import List, Optional
3
+
4
+ from .Attribution import Attribution
5
+ from .Conclusion import ConfidenceLevel
6
+ from .EvidenceReference import EvidenceReference
7
+ from .Fact import Fact
8
+ from .Gender import Gender, GenderType
9
+ from .Identifier import Identifier
10
+ from .Name import Name, NameForm, NamePart, NamePartType, NamePartQualifier
11
+ from .Note import Note
12
+ from .SourceReference import SourceReference
13
+ from .Subject import Subject
14
+ from .URI import URI
15
+
16
+ class Person(Subject):
17
+ identifier = 'http://gedcomx.org/v1/Person'
18
+ version = 'http://gedcomx.org/conceptual-model/v1'
19
+
20
+ def __init__(self, id: str = None,
21
+ lang: str = 'en',
22
+ sources: Optional[List[SourceReference]] = None,
23
+ analysis: Optional[URI] = None,
24
+ notes: Optional[List[Note]] = None,
25
+ confidence: Optional[ConfidenceLevel] = None,
26
+ attribution: Optional[Attribution] = None,
27
+ extracted: bool = None,
28
+ evidence: Optional[List[EvidenceReference]] = None,
29
+ media: Optional[List[SourceReference]] = None,
30
+ identifiers: Optional[List[Identifier]] = None,
31
+ private: Optional[bool] = False,
32
+ gender: Optional[Gender] = Gender(type=GenderType.Unknown),
33
+ names: Optional[List[Name]] = None,
34
+ facts: Optional[List[Fact]] = None,
35
+ living: Optional[bool] = False) -> None:
36
+ # Call superclass initializer if needed
37
+ super().__init__(id, lang, sources, analysis, notes, confidence, attribution, extracted, evidence, media, identifiers)
38
+
39
+ # Initialize mutable attributes to empty lists if None
40
+ self.sources = sources if sources is not None else []
41
+ self.notes = notes if notes is not None else []
42
+ self.evidence = evidence if evidence is not None else []
43
+ self.media = media if media is not None else []
44
+ self.identifiers = identifiers if identifiers is not None else []
45
+ self.names = names if names is not None else []
46
+ self.facts = facts if facts is not None else []
47
+
48
+ self.private = private
49
+ self.gender = gender
50
+
51
+ self.living = living #TODO This is from familysearch API
52
+
53
+ self._relationships = []
54
+
55
+
56
+ def add_fact(self, fact_to_add: Fact) -> bool:
57
+ if fact_to_add and isinstance(fact_to_add,Fact):
58
+ for current_fact in self.facts:
59
+ if fact_to_add == current_fact:
60
+ return False
61
+ self.facts.append(fact_to_add)
62
+ return True
63
+
64
+ def add_name(self, name_to_add: Name) -> bool:
65
+ if len(self.names) > 5:
66
+ for name in self.names:
67
+ print(name)
68
+ raise
69
+ if name_to_add and isinstance(name_to_add, Name):
70
+ for current_name in self.names:
71
+ if name_to_add == current_name:
72
+ return False
73
+ self.names.append(name_to_add)
74
+ return True
75
+
76
+ def _add_relationship(self, relationship_to_add: object):
77
+ from .Relationship import Relationship
78
+ if isinstance(relationship_to_add,Relationship):
79
+ self._relationships.append(relationship_to_add)
80
+ else:
81
+ raise ValueError()
82
+
83
+ @property
84
+ def _as_dict_(self):
85
+ def _serialize(value):
86
+ if isinstance(value, (str, int, float, bool, type(None))):
87
+ return value
88
+ elif isinstance(value, dict):
89
+ return {k: _serialize(v) for k, v in value.items()}
90
+ elif isinstance(value, (list, tuple, set)):
91
+ return [_serialize(v) for v in value]
92
+ elif hasattr(value, "_as_dict_"):
93
+ return value._as_dict_
94
+ else:
95
+ return str(value) # fallback for unknown objects
96
+
97
+ subject_fields = super()._as_dict_ # Start with base class fields
98
+ # Only add Relationship-specific fields
99
+ subject_fields.update({
100
+ 'private': self.private,
101
+ 'living': self.living,
102
+ 'gender': self.gender.type.value if self.gender.type else None,
103
+ 'names': [name for name in self.names],
104
+ 'facts': [fact for fact in self.facts]
105
+
106
+ })
107
+
108
+ # Serialize and exclude None values
109
+ for key, value in subject_fields.items():
110
+ if value is not None:
111
+ subject_fields[key] = _serialize(value)
112
+
113
+ return subject_fields
114
+
115
+
116
+ @classmethod
117
+ def _from_json_(cls, data: dict):
118
+ """
119
+ Create a Person instance from a JSON-dict (already parsed).
120
+ """
121
+ def ensure_list(value):
122
+ if value is None:
123
+ return []
124
+ if isinstance(value, list):
125
+ return value
126
+ return [value] # wrap single item in list
127
+
128
+ # Basic scalar fields
129
+ id_ = data.get('id')
130
+ lang = data.get('lang', 'en')
131
+ private = data.get('private', False)
132
+ extracted = data.get('extracted', False)
133
+
134
+ living = data.get('extracted', False)
135
+
136
+ # Complex singletons
137
+ analysis = URI._from_json_(data['analysis']) if data.get('analysis') else None
138
+ attribution = Attribution._from_json_(data['attribution']) if data.get('attribution') else None
139
+ confidence = ConfidenceLevel._from_json_(data['confidence']) if data.get('confidence') else None
140
+
141
+ # Gender (string or dict depending on your JSON)
142
+ gender_json = data.get('gender')
143
+ if isinstance(gender_json, dict):
144
+ gender = Gender._from_json_(gender_json)
145
+ else:
146
+ # if it's just the enum value
147
+ gender = Gender(type=GenderType(gender_json)) if gender_json else Gender(type=GenderType.Unknown)
148
+
149
+
150
+ sources = [SourceReference._from_json_(o) for o in ensure_list(data.get('sources'))]
151
+ notes = [Note._from_json_(o) for o in ensure_list(data.get('notes'))]
152
+ evidence = [EvidenceReference._from_json_(o) for o in ensure_list(data.get('evidence'))]
153
+ media = [SourceReference._from_json_(o) for o in ensure_list(data.get('media'))]
154
+ identifiers = [Identifier._from_json_(o) for o in ensure_list(data.get('identifiers'))]
155
+ names = [Name._from_json_(o) for o in ensure_list(data.get('names'))]
156
+ facts = [Fact._from_json_(o) for o in ensure_list(data.get('facts'))]
157
+
158
+ # Build the instance
159
+ inst = cls(
160
+ id = id_,
161
+ lang = lang,
162
+ sources = sources,
163
+ analysis = analysis,
164
+ notes = notes,
165
+ confidence = confidence,
166
+ attribution = attribution,
167
+ extracted = extracted,
168
+ evidence = evidence,
169
+ media = media,
170
+ identifiers = identifiers,
171
+ private = private,
172
+ gender = gender,
173
+ names = names,
174
+ facts = facts,
175
+ living = living
176
+ )
177
+
178
+ return inst