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.
@@ -0,0 +1,47 @@
1
+ from typing import List, Optional
2
+
3
+ from .Attribution import Attribution
4
+ from .Conclusion import ConfidenceLevel
5
+ from .Date import Date
6
+ from .EvidenceReference import EvidenceReference
7
+ from .Identifier import Identifier
8
+ from .Note import Note
9
+ from .SourceReference import SourceReference
10
+ from .TextValue import TextValue
11
+ from .URI import URI
12
+
13
+ from .Subject import Subject
14
+
15
+ class PlaceDescription(Subject):
16
+ identifier = "http://gedcomx.org/v1/PlaceDescription"
17
+ version = 'http://gedcomx.org/conceptual-model/v1'
18
+
19
+ def __init__(self, id: str =None,
20
+ lang: str = 'en',
21
+ sources: Optional[List[SourceReference]] = [],
22
+ analysis: URI = None, notes: Optional[List[Note]] =[],
23
+ confidence: ConfidenceLevel = None,
24
+ attribution: Attribution = None,
25
+ extracted: bool = None,
26
+ evidence: List[EvidenceReference] = None,
27
+ media: List[SourceReference] = [],
28
+ identifiers: List[Identifier] = [],
29
+ names: List[TextValue] = [],
30
+ type: Optional[str] = None,
31
+ place: Optional[URI] = None,
32
+ jurisdiction: Optional["PlaceDescription"] = None, # PlaceDescription
33
+ latitude: Optional[float] = None,
34
+ longitude: Optional[float] = None,
35
+ temporalDescription: Optional[Date] = None,
36
+ spatialDescription: Optional[URI] = None,) -> None:
37
+ super().__init__(id, lang, sources, analysis, notes, confidence, attribution, extracted, evidence, media, identifiers)
38
+ self.names = names
39
+ self.type = type
40
+ self.place = place
41
+ self.jurisdiction = jurisdiction
42
+ self.latitide = latitude
43
+ self.longitute = longitude
44
+ self.temporalDescription = temporalDescription
45
+ self.spacialDescription = spatialDescription
46
+
47
+
@@ -0,0 +1,31 @@
1
+ from typing import Optional
2
+
3
+ from .URI import URI
4
+
5
+ class PlaceReference:
6
+ identifier = 'http://gedcomx.org/v1/PlaceReference'
7
+ version = 'http://gedcomx.org/conceptual-model/v1'
8
+
9
+ def __init__(self, original: Optional[str], descriptionRef: Optional[URI]) -> None:
10
+ self.original = original
11
+ self.descriptionRef = descriptionRef
12
+
13
+ @property
14
+ def _as_dict_(self):
15
+ return {
16
+ 'original': self.original,
17
+ 'descriptionRef': self.descriptionRef._as_dict_ if self.descriptionRef else None
18
+ }
19
+
20
+ def ensure_list(val):
21
+ if val is None:
22
+ return []
23
+ return val if isinstance(val, list) else [val]
24
+
25
+ # PlaceReference
26
+ PlaceReference._from_json_ = classmethod(lambda cls, data: PlaceReference(
27
+ original=data.get('original'),
28
+ descriptionRef=URI._from_json_(data['description']) if data.get('description') else None
29
+ ))
30
+
31
+
gedcomx/Qualifier.py ADDED
@@ -0,0 +1,27 @@
1
+ from typing import Optional
2
+
3
+ class Qualifier:
4
+ identifier = 'http://gedcomx.org/v1/Qualifier'
5
+ version = 'http://gedcomx.org/conceptual-model/v1'
6
+
7
+ def __init__(self, name: str, value: Optional[str]) -> None:
8
+ self.name = name
9
+ self.value = value
10
+
11
+ @property
12
+ def __as_dict__(self):
13
+ from .Serialization import serialize_to_dict
14
+
15
+ data = {
16
+ "name":self.name if self.name else None,
17
+ "value":self.value if self.value else None
18
+ }
19
+
20
+ return serialize_to_dict(data,False)
21
+
22
+ # Qualifier
23
+ Qualifier._from_json_ = classmethod(lambda cls, data: Qualifier(
24
+ name=data.get('name'),
25
+ value=data.get('value')
26
+ ))
27
+
@@ -0,0 +1,116 @@
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 .Identifier import Identifier
9
+ from .Note import Note
10
+ from .Person import Person
11
+ from .Serialization import serialize_to_dict
12
+ from .SourceReference import SourceReference
13
+ from .URI import URI
14
+
15
+ from .Subject import Subject
16
+
17
+ class RelationshipType(Enum):
18
+ Couple = "http://gedcomx.org/Couple"
19
+ ParentChild = "http://gedcomx.org/ParentChild"
20
+
21
+ @property
22
+ def description(self):
23
+ descriptions = {
24
+ RelationshipType.Couple: "A relationship of a pair of persons.",
25
+ RelationshipType.ParentChild: "A relationship from a parent to a child."
26
+ }
27
+ return descriptions.get(self, "No description available.")
28
+
29
+ class Relationship(Subject):
30
+ identifier = 'http://gedcomx.org/v1/Relationship'
31
+ version = 'http://gedcomx.org/conceptual-model/v1'
32
+
33
+ def __init__(self,
34
+ id: Optional[str] = None,
35
+ lang: Optional[str] = None,
36
+ sources: Optional[List[SourceReference]] = None,
37
+ analysis: Optional[URI] = None,
38
+ notes: Optional[List[Note]] = None,
39
+ confidence: Optional[ConfidenceLevel] = None,
40
+ attribution: Optional[Attribution] = None,
41
+ extracted: Optional[bool] = None,
42
+ evidence: Optional[List[EvidenceReference]] = None,
43
+ media: Optional[List[SourceReference]] = None,
44
+ identifiers: Optional[List[Identifier]] = None,
45
+ type: Optional[RelationshipType] = None,
46
+ person1: Optional[URI] = None,
47
+ person2: Optional[URI] = None,
48
+ facts: Optional[List[Fact]] = None) -> None:
49
+
50
+ # Call superclass initializer if required
51
+ super().__init__(id, lang, sources, analysis, notes, confidence, attribution, extracted, evidence, media, identifiers)
52
+
53
+ # Initialize optional parameters with default empty lists if None
54
+ #self.sources = sources if sources is not None else []
55
+ #self.notes = notes if notes is not None else []
56
+ #self.evidence = evidence if evidence is not None else []
57
+ #self.media = media if media is not None else []
58
+ #self.identifiers = identifiers if identifiers is not None else []
59
+ #self.facts = facts if facts is not None else []
60
+
61
+ # Initialize other attributes
62
+ self.type = type
63
+ self.person1 = person1
64
+ self.person2 = person2
65
+ self.facts = facts if facts else None
66
+
67
+ @property
68
+ def _as_dict_(self):
69
+ return serialize_to_dict(self, {
70
+ "type": self.type.value if isinstance(self.type, RelationshipType) else self.type,
71
+ "person1": self.person1._uri,
72
+ "person2": self.person2._uri,
73
+ "facts": [fact for fact in self.facts] if self.facts else None
74
+ })
75
+
76
+ @classmethod
77
+ def _from_json_(cls, data: dict):
78
+ """
79
+ Create a Relationship instance from a JSON-dict (already parsed).
80
+ """
81
+ def ensure_list(value):
82
+ if value is None:
83
+ return []
84
+ if isinstance(value, list):
85
+ return value
86
+ return [value] # wrap single item in list
87
+
88
+ # Basic scalar fields (adjust as needed)
89
+ id_ = data.get('id')
90
+ type_ = data.get('type')
91
+ extracted = data.get('extracted', None)
92
+ private = data.get('private', None)
93
+
94
+ # Complex singletons (adjust as needed)
95
+ person1 = URI.from_url(data.get('person1')['resource']) if data.get('person1') else None
96
+ person2 = URI.from_url(data.get('person2')['resource']) if data.get('person2') else None
97
+ facts = [Fact._from_json_(o) for o in ensure_list(data.get('facts'))]
98
+ sources = [SourceReference._from_json_(o) for o in ensure_list(data.get('sources'))]
99
+ notes = [Note._from_json_(o) for o in ensure_list(data.get('notes'))]
100
+
101
+ # Build the instance
102
+ inst = cls(
103
+ id = id_,
104
+ type = type_,
105
+ extracted = extracted,
106
+ #private = private, #TODO Has this been added?
107
+ person1 = person1,
108
+ person2 = person2,
109
+ facts = facts,
110
+ sources = sources,
111
+ notes = notes
112
+ )
113
+
114
+ return inst
115
+
116
+
@@ -0,0 +1,37 @@
1
+ from typing import Dict
2
+
3
+
4
+ def _has_parent_class(obj) -> bool:
5
+ return hasattr(obj, '__class__') and hasattr(obj.__class__, '__bases__') and len(obj.__class__.__bases__) > 0
6
+
7
+ def serialize_to_dict(obj,class_values:Dict,ignore_null=True):
8
+ def _serialize(value):
9
+ if isinstance(value, (str, int, float, bool, type(None))):
10
+ return value
11
+ elif isinstance(value, dict):
12
+ return {k: _serialize(v) for k, v in value.items()}
13
+ elif isinstance(value, (list, tuple, set)):
14
+ return [_serialize(v) for v in value]
15
+ elif hasattr(value, "_as_dict_"):
16
+ return value._as_dict_
17
+ else:
18
+ return str(value) # fallback for unknown objects
19
+
20
+ values_dict = {}
21
+ if _has_parent_class(obj):
22
+ values_dict.update(super(obj.__class__, obj)._as_dict_)
23
+ if class_values:
24
+ values_dict.update(class_values)
25
+ # Serialize and exclude None values
26
+
27
+ empty_fields = []
28
+ for key, value in values_dict.items():
29
+ if value is not None:
30
+ values_dict[key] = _serialize(value)
31
+ else:
32
+ empty_fields.append(key)
33
+
34
+ for key in empty_fields:
35
+ del values_dict[key]
36
+
37
+ return values_dict
@@ -0,0 +1,20 @@
1
+ from typing import Optional
2
+
3
+ class SourceCitation:
4
+ identifier = 'http://gedcomx.org/v1/SourceCitation'
5
+ version = 'http://gedcomx.org/conceptual-model/v1'
6
+
7
+ def __init__(self, lang: Optional[str], value: str) -> None:
8
+ self.lang = lang if lang else 'en'
9
+ self.value = value
10
+
11
+ # ...existing code...
12
+
13
+ @classmethod
14
+ def _from_json_(cls, data: dict):
15
+ """
16
+ Create a SourceCitation instance from a JSON-dict (already parsed).
17
+ """
18
+ lang = data.get('lang', 'en')
19
+ value = data.get('value')
20
+ return cls(lang=lang, value=value)
@@ -0,0 +1,241 @@
1
+ import base64
2
+ import uuid
3
+ import warnings
4
+
5
+ from enum import Enum
6
+ from typing import List, Optional, Dict, Any
7
+
8
+ from .Agent import Agent
9
+ from .Attribution import Attribution
10
+ from .Coverage import Coverage
11
+ from .Date import Date
12
+ from .Identifier import Identifier
13
+ from .Note import Note
14
+ from .SourceCitation import SourceCitation
15
+ from .SourceReference import SourceReference
16
+ from .TextValue import TextValue
17
+ from .URI import URI
18
+
19
+ class ResourceType(Enum):
20
+ Collection = "http://gedcomx.org/Collection"
21
+ PhysicalArtifact = "http://gedcomx.org/PhysicalArtifact"
22
+ DigitalArtifact = "http://gedcomx.org/DigitalArtifact"
23
+ Record = "http://gedcomx.org/Record"
24
+
25
+ @property
26
+ def description(self):
27
+ descriptions = {
28
+ ResourceType.Collection: "A collection of genealogical resources. A collection may contain physical artifacts (such as a collection of books in a library), records (such as the 1940 U.S. Census), or digital artifacts (such as an online genealogical application).",
29
+ ResourceType.PhysicalArtifact: "A physical artifact, such as a book.",
30
+ ResourceType.DigitalArtifact: "A digital artifact, such as a digital image of a birth certificate or other record.",
31
+ ResourceType.Record: "A historical record, such as a census record or a vital record."
32
+ }
33
+ return descriptions.get(self, "No description available.")
34
+
35
+ class SourceDescription:
36
+ identifier = "http://gedcomx.org/v1/SourceDescription"
37
+
38
+ def __init__(self, id: Optional[str] = None,
39
+ resourceType: Optional[ResourceType] = None,
40
+ citations: Optional[List[SourceCitation]] = [],
41
+ mediaType: Optional[str] = None,
42
+ about: Optional[URI] = None,
43
+ mediator: Optional[URI] = None,
44
+ publisher: Optional[URI] = None,
45
+ authors: Optional[List[URI]] = [],
46
+ sources: List[SourceReference] = [], # SourceReference
47
+ analysis: Optional[URI] = None, # analysis should be of type 'Document', not specified to avoid circular import
48
+ componentOf: Optional[SourceReference] = None, # SourceReference
49
+ titles: Optional[List[TextValue]] = [],
50
+ notes: Optional[List[Note]] = [],
51
+ attribution: Optional[Attribution] = None,
52
+ rights: Optional[List[URI]] = [],
53
+ coverage: Optional[Coverage] = None, # Coverage
54
+ descriptions: Optional[List[TextValue]] = [],
55
+ identifiers: Optional[List[Identifier]] = [],
56
+ created: Optional[Date] = None,
57
+ modified: Optional[Date] = None,
58
+ published: Optional[Date] = None,
59
+ repository: Optional[Agent] = None,
60
+ max_note_count: int = 20):
61
+
62
+ self.id = id if id else SourceDescription.default_id_generator()
63
+ self.resourceType = resourceType
64
+ self.citations = citations or []
65
+ self.mediaType = mediaType
66
+ self.about = about
67
+ self.mediator = mediator
68
+ self.publisher = publisher
69
+ self.authors = authors or []
70
+ self.source_refs = sources or []
71
+ self.analysis = analysis
72
+ self.componentOf = componentOf
73
+ self.titles = titles or []
74
+ self.notes = notes or []
75
+ self.attribution = attribution
76
+ self.rights = rights or []
77
+ self.coverage = coverage or []
78
+ self.descriptions = descriptions or []
79
+ self.identifiers = identifiers or []
80
+ self.created = created
81
+ self.modified = modified
82
+ self.published = published
83
+ self.repository = repository
84
+ self.max_note_count = max_note_count
85
+
86
+ self._uri = URI(fragment=id)
87
+
88
+ def add_description(self, desccription_to_add: TextValue):
89
+ if desccription_to_add and isinstance(desccription_to_add,TextValue):
90
+ for current_description in self.descriptions:
91
+ if desccription_to_add == current_description:
92
+ return
93
+ self.descriptions.append(desccription_to_add)
94
+
95
+ def add_identifier(self, identifier_to_add: Identifier):
96
+ if identifier_to_add and isinstance(identifier_to_add,Identifier):
97
+ for current_identifier in self.identifiers:
98
+ if identifier_to_add == current_identifier:
99
+ return
100
+ self.identifiers.append(identifier_to_add)
101
+
102
+ def add_note(self,note_to_add: Note):
103
+ if len(self.notes) >= self.max_note_count:
104
+ warnings.warn(f"Max not count of {self.max_note_count} reached for id: {self.id}")
105
+ return False
106
+ if note_to_add and isinstance(note_to_add,Note):
107
+ for existing in self.notes:
108
+ if note_to_add == existing:
109
+ return False
110
+ self.notes.append(note_to_add)
111
+
112
+ def add_source(self, source_to_add: object):
113
+ #from .SourceReference import SourceReference
114
+ if source_to_add and isinstance(object,SourceReference):
115
+ for current_source in self.sources:
116
+ if current_source == source_to_add:
117
+ return
118
+ self.sources.append(source_to_add)
119
+
120
+ def add_title(self, title_to_add: TextValue):
121
+ if isinstance(title_to_add,str): title_to_add = TextValue(value=title_to_add)
122
+ if title_to_add and isinstance(title_to_add, TextValue):
123
+ for current_title in self.titles:
124
+ if title_to_add == current_title:
125
+ return False
126
+ self.titles.append(title_to_add)
127
+ else:
128
+ raise ValueError(f"Cannot add title of type {type(title_to_add)}")
129
+
130
+ @staticmethod
131
+ def default_id_generator():
132
+ # Generate a standard UUID
133
+ standard_uuid = uuid.uuid4()
134
+ # Convert UUID to bytes
135
+ uuid_bytes = standard_uuid.bytes
136
+ # Encode bytes to a Base64 string
137
+ short_uuid = base64.urlsafe_b64encode(uuid_bytes).rstrip(b'=').decode('utf-8')
138
+ return 'SD-' + str(short_uuid)
139
+
140
+ @property
141
+ def _as_dict_(self) -> Dict[str, Any]:
142
+ def _serialize(val: Any) -> Any:
143
+ if hasattr(val, '_as_dict_'):
144
+ return val._as_dict_
145
+ if isinstance(val, URI):
146
+ return val._prop_dict()
147
+ if isinstance(val, Enum):
148
+ return val.value
149
+ if isinstance(val, (str, int, float, bool)) or val is None:
150
+ return val
151
+ if isinstance(val, list):
152
+ return [_serialize(v) for v in val]
153
+ if isinstance(val, dict):
154
+ return {k: _serialize(v) for k, v in val.items()}
155
+ return str(val)
156
+
157
+ data = {
158
+ 'id': self.id,
159
+ 'resourceType': self.resourceType.value if self.resourceType else None,
160
+ 'citations': [c._as_dict_ for c in self.citations] or None,
161
+ 'mediaType': self.mediaType,
162
+ 'about': self.about._prop_dict() if self.about else None,
163
+ 'mediator': self.mediator._prop_dict() if self.mediator else None,
164
+ 'publisher': self.publisher._prop_dict() if self.publisher else None,
165
+ 'authors': [a._prop_dict() for a in self.authors] or None,
166
+ 'sources': [s._as_dict_ for s in self.sources] or None,
167
+ 'analysis': self.analysis._prop_dict() if self.analysis else None,
168
+ 'componentOf': self.componentOf._as_dict_ if self.componentOf else None,
169
+ 'titles': [t._prop_dict() for t in self.titles] or None,
170
+ 'notes': [n._prop_dict() for n in self.notes] or None,
171
+ 'attribution': self.attribution._as_dict_ if self.attribution else None,
172
+ 'rights': [r._prop_dict() for r in self.rights] or None,
173
+ 'coverage': self.coverage._as_dict_ if self.coverage else None,
174
+ 'descriptions': [d._prop_dict() for d in self.descriptions] or None,
175
+ 'identifiers': [i._as_dict_ for i in self.identifiers] or None,
176
+ 'created': self.created._prop_dict() if self.created else None,
177
+ 'modified': self.modified._prop_dict() if self.modified else None,
178
+ 'published': self.published._prop_dict() if self.published else None,
179
+ 'repository': self.repository._prop_dict() if self.repository else None
180
+ }
181
+ # Remove None values
182
+ return {k: v for k, v in data.items() if v is not None}
183
+
184
+ @classmethod
185
+ def _from_json_(cls, data: Dict[str, Any]) -> 'SourceDescription':
186
+ print(data,type(data))
187
+ # TODO Hande Resource/URI
188
+
189
+ # Basic fields
190
+ id_ = data.get('id')
191
+ rt = ResourceType(data['resourceType']) if data.get('resourceType') else None
192
+
193
+ # Sub-objects
194
+ citations = [SourceCitation._from_json_(c) for c in data.get('citations', [])]
195
+ about = URI._from_json_(data['about']) if data.get('about') else None
196
+ mediator = URI._from_json_(data['mediator']) if data.get('mediator') else None
197
+ publisher = Agent._from_json_(data['publisher']) if data.get('publisher') else None
198
+ authors = [URI._from_json_(a) for a in data.get('authors', [])]
199
+ sources = [SourceReference._from_json_(s) for s in data.get('sources', [])]
200
+ analysis = URI._from_json_(data['analysis']) if data.get('analysis') else None
201
+ component_of = SourceReference._from_json_(data['componentOf']) if data.get('componentOf') else None
202
+ titles = [TextValue._from_json_(t) for t in data.get('titles', [])]
203
+ notes = [Note._from_json_(n) for n in data.get('notes', [])]
204
+ attribution = Attribution._from_json_(data['attribution']) if data.get('attribution') else None
205
+ rights = [URI._from_json_(r) for r in data.get('rights', [])]
206
+ coverage = [Coverage._from_json_(cvg) for cvg in data.get('coverage',[])]
207
+ descriptions = [TextValue._from_json_(d) for d in data.get('descriptions', [])]
208
+ identifiers = [Identifier._from_json_(i) for i in data.get('identifiers', [])]
209
+ created = Date._from_json_(data['created']) if data.get('created') else None
210
+ modified = Date._from_json_(data['modified']) if data.get('modified') else None
211
+ published = Date._from_json_(data['published']) if data.get('published') else None
212
+ repository = Agent._from_json_(data['repository']) if data.get('repository') else None
213
+
214
+ return cls(
215
+ id=id_, resourceType=rt, citations=citations,
216
+ mediaType=data.get('mediaType'), about=about,
217
+ mediator=mediator, publisher=publisher,
218
+ authors=authors, sources=sources,
219
+ analysis=analysis, componentOf=component_of,
220
+ titles=titles, notes=notes, attribution=attribution,
221
+ rights=rights, coverage=coverage,
222
+ descriptions=descriptions, identifiers=identifiers,
223
+ created=created, modified=modified,
224
+ published=published, repository=repository
225
+ )
226
+
227
+ @property
228
+ def publisher(self) -> URI:
229
+ if not self._publisher is None or isinstance(self._publisher,URI): assert False
230
+ return self._publisher
231
+
232
+ @publisher.setter
233
+ def publisher(self, value: URI | Agent):
234
+ if value is None:
235
+ self._publisher = None
236
+ elif isinstance(value,URI):
237
+ assert False
238
+ elif isinstance(value,Agent):
239
+ self._publisher = value._uri
240
+ else:
241
+ raise ValueError(f"'publisher' must be of type 'URI' or 'Agent', type: {type(value)} was provided")