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.
- gedcom_x-0.5.dist-info/METADATA +17 -0
- gedcom_x-0.5.dist-info/RECORD +37 -0
- gedcom_x-0.5.dist-info/WHEEL +5 -0
- gedcom_x-0.5.dist-info/top_level.txt +1 -0
- gedcomx/Address.py +100 -0
- gedcomx/Agent.py +83 -0
- gedcomx/Attribution.py +116 -0
- gedcomx/Conclusion.py +137 -0
- gedcomx/Coverage.py +26 -0
- gedcomx/Date.py +29 -0
- gedcomx/Document.py +42 -0
- gedcomx/Event.py +195 -0
- gedcomx/EvidenceReference.py +11 -0
- gedcomx/Fact.py +462 -0
- gedcomx/Gedcom.py +345 -0
- gedcomx/GedcomX.py +1105 -0
- gedcomx/Gender.py +48 -0
- gedcomx/Group.py +37 -0
- gedcomx/Identifier.py +89 -0
- gedcomx/Name.py +241 -0
- gedcomx/Note.py +65 -0
- gedcomx/OnlineAccount.py +10 -0
- gedcomx/Person.py +178 -0
- gedcomx/PlaceDescription.py +47 -0
- gedcomx/PlaceReference.py +31 -0
- gedcomx/Qualifier.py +27 -0
- gedcomx/Relationship.py +116 -0
- gedcomx/Serialization.py +37 -0
- gedcomx/SourceCitation.py +20 -0
- gedcomx/SourceDescription.py +241 -0
- gedcomx/SourceReference.py +168 -0
- gedcomx/Subject.py +73 -0
- gedcomx/TextValue.py +34 -0
- gedcomx/TopLevelTypeCollection.py +47 -0
- gedcomx/URI.py +70 -0
- gedcomx/_Resource.py +11 -0
- gedcomx/__init__.py +39 -0
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
|
gedcomx/OnlineAccount.py
ADDED
@@ -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
|