gedcom-x 0.5.8__py3-none-any.whl → 0.5.10__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.8.dist-info → gedcom_x-0.5.10.dist-info}/METADATA +1 -1
- gedcom_x-0.5.10.dist-info/RECORD +58 -0
- gedcomx/Extensions/rs10/rsLink.py +109 -59
- gedcomx/__init__.py +4 -1
- gedcomx/address.py +102 -16
- gedcomx/agent.py +81 -24
- gedcomx/attribution.py +52 -28
- gedcomx/conclusion.py +98 -46
- gedcomx/converter.py +209 -79
- gedcomx/coverage.py +10 -1
- gedcomx/date.py +42 -8
- gedcomx/document.py +37 -7
- gedcomx/event.py +77 -20
- gedcomx/evidence_reference.py +9 -0
- gedcomx/extensible.py +86 -0
- gedcomx/fact.py +53 -54
- gedcomx/gedcom.py +10 -0
- gedcomx/gedcom5x.py +30 -20
- gedcomx/gedcom7/GedcomStructure.py +1 -3
- gedcomx/gedcom7/__init__.py +2 -2
- gedcomx/gedcom7/{Gedcom7.py → gedcom7.py} +3 -3
- gedcomx/gedcom7/specification.py +4817 -0
- gedcomx/gedcomx.py +95 -93
- gedcomx/gender.py +21 -9
- gedcomx/group.py +9 -0
- gedcomx/identifier.py +47 -20
- gedcomx/logging_hub.py +19 -0
- gedcomx/mutations.py +10 -5
- gedcomx/name.py +74 -33
- gedcomx/note.py +50 -18
- gedcomx/online_account.py +9 -0
- gedcomx/person.py +46 -27
- gedcomx/place_description.py +54 -8
- gedcomx/place_reference.py +30 -8
- gedcomx/qualifier.py +19 -3
- gedcomx/relationship.py +55 -14
- gedcomx/resource.py +45 -18
- gedcomx/schemas.py +328 -0
- gedcomx/serialization.py +400 -421
- gedcomx/source_citation.py +16 -4
- gedcomx/source_description.py +181 -94
- gedcomx/source_reference.py +51 -16
- gedcomx/subject.py +59 -14
- gedcomx/textvalue.py +66 -12
- gedcomx/translation.py +3 -3
- gedcomx/uri.py +155 -3
- gedcom_x-0.5.8.dist-info/RECORD +0 -56
- gedcomx/gedcom7/Specification.py +0 -347
- {gedcom_x-0.5.8.dist-info → gedcom_x-0.5.10.dist-info}/WHEEL +0 -0
- {gedcom_x-0.5.8.dist-info → gedcom_x-0.5.10.dist-info}/top_level.txt +0 -0
gedcomx/attribution.py
CHANGED
@@ -12,6 +12,8 @@ from typing import Optional, Dict, Any
|
|
12
12
|
Created: 2025-08-25
|
13
13
|
Updated:
|
14
14
|
- 2025-08-31: fixed _as_dict_ to deal with Resources and ignore empty fields
|
15
|
+
- 2025-09-03: _from_json_ refactor
|
16
|
+
|
15
17
|
|
16
18
|
======================================================================
|
17
19
|
"""
|
@@ -21,9 +23,16 @@ from typing import Optional, Dict, Any
|
|
21
23
|
GEDCOM Module Types
|
22
24
|
======================================================================
|
23
25
|
"""
|
24
|
-
|
25
26
|
from .agent import Agent
|
26
27
|
from .resource import Resource
|
28
|
+
from .logging_hub import hub, logging
|
29
|
+
"""
|
30
|
+
======================================================================
|
31
|
+
Logging
|
32
|
+
======================================================================
|
33
|
+
"""
|
34
|
+
log = logging.getLogger("gedcomx")
|
35
|
+
serial_log = "gedcomx.serialization"
|
27
36
|
#=====================================================================
|
28
37
|
|
29
38
|
|
@@ -56,36 +65,51 @@ class Attribution:
|
|
56
65
|
self.created = created
|
57
66
|
|
58
67
|
@property
|
59
|
-
def _as_dict_(self) -> Dict[str, Any]:
|
68
|
+
def _as_dict_(self) -> Dict[str, Any] | None:
|
60
69
|
"""
|
61
70
|
Serialize Attribution to a JSON-ready dict, skipping None values.
|
62
71
|
"""
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
72
|
+
with hub.use(serial_log):
|
73
|
+
log.debug(f"Serializing 'Attribution'")
|
74
|
+
type_as_dict: Dict[str, Any] = {}
|
75
|
+
if self.contributor:
|
76
|
+
type_as_dict['contributor'] = Resource(target=self.contributor)._as_dict_
|
77
|
+
if self.modified:
|
78
|
+
type_as_dict['modified'] = self.modified if self.modified else None
|
79
|
+
if self.changeMessage:
|
80
|
+
type_as_dict['changeMessage'] = self.changeMessage if self.changeMessage else None
|
81
|
+
if self.creator:
|
82
|
+
type_as_dict['creator'] = Resource(target=self.creator)._as_dict_
|
83
|
+
if self.created:
|
84
|
+
type_as_dict['created'] = self.created if self.created else None
|
75
85
|
|
76
|
-
|
86
|
+
log.debug(f"'Attribution' serialized with fields: {type_as_dict.keys()}")
|
87
|
+
if type_as_dict == {}: log.warning("serializing and empty 'Attribution'")
|
88
|
+
return type_as_dict if type_as_dict != {} else None
|
77
89
|
|
78
90
|
@classmethod
|
79
|
-
def _from_json_(cls, data: Dict[str, Any]) -> 'Attribution':
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
""
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
91
|
+
def _from_json_(cls, data: Dict[str, Any],context) -> 'Attribution':
|
92
|
+
if not isinstance(data, dict):
|
93
|
+
raise TypeError(f"{cls.__name__}._from_json_ expected dict or str, got {type(data)}")
|
94
|
+
|
95
|
+
attribution_data: Dict[str, Any] = {}
|
96
|
+
|
97
|
+
# contributor: Agent | Resource | URI string
|
98
|
+
if (contrib := data.get("contributor")) is not None:
|
99
|
+
attribution_data["contributor"] = Resource._from_json_(contrib, context)
|
100
|
+
|
101
|
+
# creator: Agent | Resource | URI string
|
102
|
+
if (creator := data.get("creator")) is not None:
|
103
|
+
attribution_data["creator"] = Resource._from_json_(creator, context)
|
104
|
+
|
105
|
+
# changeMessage: str
|
106
|
+
if (cm := data.get("changeMessage")) is not None:
|
107
|
+
attribution_data["changeMessage"] = cm
|
108
|
+
|
109
|
+
# created/modified: datetime
|
110
|
+
if (created := data.get("created")) is not None:
|
111
|
+
attribution_data["created"] = created
|
112
|
+
if (modified := data.get("modified")) is not None:
|
113
|
+
attribution_data["modified"] = modified
|
114
|
+
|
115
|
+
return cls(**attribution_data)
|
gedcomx/conclusion.py
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
|
2
|
-
import uuid
|
1
|
+
|
3
2
|
import warnings
|
4
3
|
|
5
|
-
from typing import
|
4
|
+
from typing import Any, Optional, List
|
5
|
+
|
6
6
|
"""
|
7
7
|
======================================================================
|
8
8
|
Project: Gedcom-X
|
@@ -12,7 +12,7 @@ from typing import List, Optional
|
|
12
12
|
|
13
13
|
Created: 2025-08-25
|
14
14
|
Updated:
|
15
|
-
-
|
15
|
+
- 2025-09-03: _from_json_ refactor
|
16
16
|
|
17
17
|
======================================================================
|
18
18
|
"""
|
@@ -23,11 +23,21 @@ GEDCOM Module Type Imports
|
|
23
23
|
======================================================================
|
24
24
|
"""
|
25
25
|
from .attribution import Attribution
|
26
|
-
from .Extensions.rs10.rsLink import
|
26
|
+
from .Extensions.rs10.rsLink import _rsLinks, rsLink
|
27
|
+
from .identifier import make_uid
|
27
28
|
from .note import Note
|
28
29
|
from .qualifier import Qualifier
|
29
30
|
from .resource import Resource, URI
|
30
31
|
from .source_reference import SourceReference
|
32
|
+
from .logging_hub import hub, logging
|
33
|
+
"""
|
34
|
+
======================================================================
|
35
|
+
Logging
|
36
|
+
======================================================================
|
37
|
+
"""
|
38
|
+
log = logging.getLogger("gedcomx")
|
39
|
+
serial_log = "gedcomx.serialization"
|
40
|
+
#=====================================================================
|
31
41
|
|
32
42
|
|
33
43
|
|
@@ -103,7 +113,7 @@ class ConfidenceLevel(Qualifier):
|
|
103
113
|
return descriptions.get(key, "No description available.")
|
104
114
|
|
105
115
|
|
106
|
-
class Conclusion:
|
116
|
+
class Conclusion():
|
107
117
|
"""
|
108
118
|
Represents a conclusion in the GEDCOM X conceptual model. A conclusion is a
|
109
119
|
genealogical assertion about a person, relationship, or event, derived from
|
@@ -131,17 +141,7 @@ class Conclusion:
|
|
131
141
|
"""
|
132
142
|
identifier = 'http://gedcomx.org/v1/Conclusion'
|
133
143
|
version = 'http://gedcomx.org/conceptual-model/v1'
|
134
|
-
|
135
|
-
@staticmethod
|
136
|
-
def default_id_generator():
|
137
|
-
# Generate a standard UUID
|
138
|
-
standard_uuid = uuid.uuid4()
|
139
|
-
# Convert UUID to bytes
|
140
|
-
uuid_bytes = standard_uuid.bytes
|
141
|
-
# Encode bytes to a Base64 string
|
142
|
-
short_uuid = base64.urlsafe_b64encode(uuid_bytes).rstrip(b'=').decode('utf-8')
|
143
|
-
return short_uuid
|
144
|
-
|
144
|
+
|
145
145
|
def __init__(self,
|
146
146
|
id: Optional[str] = None,
|
147
147
|
lang: Optional[str] = None,
|
@@ -152,11 +152,10 @@ class Conclusion:
|
|
152
152
|
attribution: Optional[Attribution] = None,
|
153
153
|
uri: Optional[Resource] = None,
|
154
154
|
_max_note_count: int = 20,
|
155
|
-
links: Optional[
|
155
|
+
links: Optional[_rsLinks] = None) -> None:
|
156
156
|
|
157
|
-
|
158
|
-
|
159
|
-
self.id = id if id else None
|
157
|
+
|
158
|
+
self.id = id if id else make_uid()
|
160
159
|
self.lang = lang
|
161
160
|
self.sources = sources if sources else []
|
162
161
|
self.analysis = analysis
|
@@ -164,8 +163,7 @@ class Conclusion:
|
|
164
163
|
self.confidence = confidence
|
165
164
|
self.attribution = attribution
|
166
165
|
self.max_note_count = _max_note_count
|
167
|
-
self.
|
168
|
-
self.links = links if links else _rsLinkList() #NOTE This is not in specification, following FS format
|
166
|
+
self.links = links if links else _rsLinks() #NOTE This is not in specification, following FS format
|
169
167
|
|
170
168
|
def add_note(self,note_to_add: Note):
|
171
169
|
if self.notes and len(self.notes) >= self.max_note_count:
|
@@ -177,7 +175,7 @@ class Conclusion:
|
|
177
175
|
return False
|
178
176
|
self.notes.append(note_to_add)
|
179
177
|
|
180
|
-
def
|
178
|
+
def add_source_reference(self, source_to_add: SourceReference):
|
181
179
|
if source_to_add and isinstance(source_to_add,SourceReference):
|
182
180
|
for current_source in self.sources:
|
183
181
|
if source_to_add == current_source:
|
@@ -205,30 +203,84 @@ class Conclusion:
|
|
205
203
|
|
206
204
|
@property
|
207
205
|
def _as_dict_(self):
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
206
|
+
with hub.use(serial_log):
|
207
|
+
log.debug(f"Serializing 'Conclusion' with id: {self.id}")
|
208
|
+
type_as_dict = {}
|
209
|
+
|
210
|
+
if self.id:
|
211
|
+
type_as_dict['id'] = self.id
|
212
|
+
if self.lang:
|
213
|
+
type_as_dict['lang'] = self.lang
|
214
|
+
if self.sources and self.sources != []:
|
215
|
+
type_as_dict['sources'] = [s._as_dict_ for s in self.sources if s]
|
216
|
+
if self.analysis:
|
217
|
+
type_as_dict['analysis'] = getattr(self.analysis, '_as_dict_', self.analysis)
|
218
|
+
if self.notes and self.notes != []:
|
219
|
+
type_as_dict['notes'] = [
|
220
|
+
(n._as_dict_ if hasattr(n, '_as_dict_') else n) for n in self.notes if n
|
221
|
+
]
|
222
|
+
if self.confidence is not None:
|
223
|
+
type_as_dict['confidence'] = self.confidence
|
224
|
+
if self.attribution is not None:
|
225
|
+
type_as_dict['attribution'] = self.attribution._as_dict_
|
226
|
+
if self.links is not None:
|
227
|
+
print(self.links)
|
228
|
+
type_as_dict['links'] = self.links._as_dict_
|
229
|
+
log.debug(f"'Conclusion' serialized with fields: {type_as_dict.keys()}")
|
230
|
+
if type_as_dict == {}: log.warning("serializing and empty 'Conclusion'")
|
229
231
|
|
230
|
-
return
|
232
|
+
return type_as_dict if type_as_dict != {} else None
|
233
|
+
|
231
234
|
|
235
|
+
|
236
|
+
|
237
|
+
@classmethod
|
238
|
+
def _dict_from_json_(cls, data: dict, context=None) -> dict:
|
239
|
+
conclusion = {}
|
240
|
+
|
241
|
+
# Scalars
|
242
|
+
if (id_ := data.get("id")) is not None:
|
243
|
+
conclusion["id"] = id_
|
244
|
+
|
245
|
+
if (lang := data.get("lang")) is not None:
|
246
|
+
conclusion["lang"] = lang
|
247
|
+
|
248
|
+
# Lists
|
249
|
+
if (sources := data.get("sources")) is not None:
|
250
|
+
conclusion["sources"] = [
|
251
|
+
SourceReference._from_json_(x, context) for x in sources
|
252
|
+
]
|
253
|
+
|
254
|
+
if (notes := data.get("notes")) is not None:
|
255
|
+
conclusion["notes"] = [
|
256
|
+
Note._from_json_(x, context) for x in notes
|
257
|
+
]
|
258
|
+
|
259
|
+
# Objects
|
260
|
+
if (analysis := data.get("analysis")) is not None:
|
261
|
+
# depending on your model, analysis might be a Resource or Document
|
262
|
+
conclusion["analysis"] = Resource._from_json_(analysis, context)
|
263
|
+
|
264
|
+
if (confidence := data.get("confidence")) is not None:
|
265
|
+
conclusion["confidence"] = ConfidenceLevel._from_json_(confidence, context)
|
266
|
+
|
267
|
+
if (attribution := data.get("attribution")) is not None:
|
268
|
+
conclusion["attribution"] = Attribution._from_json_(attribution, context)
|
269
|
+
|
270
|
+
if (uri := data.get("uri")) is not None:
|
271
|
+
conclusion["uri"] = URI(uri)
|
272
|
+
|
273
|
+
if (links := data.get("links")) is not None:
|
274
|
+
conclusion["links"] = _rsLinks._from_json_(links, context)
|
275
|
+
if isinstance(conclusion["links"],dict): assert False
|
276
|
+
|
277
|
+
# Constant / defaults
|
278
|
+
#conclusion["_max_note_count"] = 20
|
279
|
+
|
280
|
+
#return cls(**conclusion)
|
281
|
+
return conclusion
|
282
|
+
|
283
|
+
|
232
284
|
def __eq__(self, other):
|
233
285
|
if not isinstance(other, self.__class__):
|
234
286
|
return False
|