gedcom-x 0.5.6__py3-none-any.whl → 0.5.8__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.6.dist-info → gedcom_x-0.5.8.dist-info}/METADATA +1 -1
- gedcom_x-0.5.8.dist-info/RECORD +56 -0
- gedcomx/Extensions/__init__.py +1 -0
- gedcomx/Extensions/rs10/__init__.py +1 -0
- gedcomx/Extensions/rs10/rsLink.py +116 -0
- gedcomx/TopLevelTypeCollection.py +1 -1
- gedcomx/__init__.py +43 -41
- gedcomx/{Address.py → address.py} +13 -13
- gedcomx/{Agent.py → agent.py} +52 -24
- gedcomx/{Attribution.py → attribution.py} +36 -9
- gedcomx/{Conclusion.py → conclusion.py} +49 -21
- gedcomx/converter.py +1049 -0
- gedcomx/coverage.py +55 -0
- gedcomx/{Date.py → date.py} +11 -4
- gedcomx/{Document.py → document.py} +27 -8
- gedcomx/{Event.py → event.py} +102 -27
- gedcomx/{EvidenceReference.py → evidence_reference.py} +2 -2
- gedcomx/{Fact.py → fact.py} +45 -34
- gedcomx/{Gedcom5x.py → gedcom5x.py} +78 -61
- gedcomx/gedcom7/Exceptions.py +9 -0
- gedcomx/gedcom7/Gedcom7.py +160 -0
- gedcomx/gedcom7/GedcomStructure.py +94 -0
- gedcomx/gedcom7/Specification.py +347 -0
- gedcomx/gedcom7/__init__.py +26 -0
- gedcomx/gedcom7/g7interop.py +205 -0
- gedcomx/gedcom7/logger.py +19 -0
- gedcomx/gedcomx.py +501 -0
- gedcomx/{Gender.py → gender.py} +29 -17
- gedcomx/group.py +63 -0
- gedcomx/{Identifier.py → identifier.py} +13 -16
- gedcomx/{LoggingHub.py → logging_hub.py} +21 -0
- gedcomx/{Mutations.py → mutations.py} +50 -26
- gedcomx/name.py +396 -0
- gedcomx/{Note.py → note.py} +17 -10
- gedcomx/{OnlineAccount.py → online_account.py} +1 -1
- gedcomx/{Person.py → person.py} +52 -29
- gedcomx/place_description.py +123 -0
- gedcomx/place_reference.py +62 -0
- gedcomx/qualifier.py +54 -0
- gedcomx/{Relationship.py → relationship.py} +33 -13
- gedcomx/resource.py +85 -0
- gedcomx/serialization.py +815 -0
- gedcomx/{SourceDescription.py → source_description.py} +144 -85
- gedcomx/{SourceReference.py → source_reference.py} +15 -14
- gedcomx/{Subject.py → subject.py} +30 -28
- gedcomx/{GedcomX.py → translation.py} +283 -446
- gedcomx/{URI.py → uri.py} +42 -26
- gedcom_x-0.5.6.dist-info/RECORD +0 -45
- gedcomx/Coverage.py +0 -36
- gedcomx/Group.py +0 -37
- gedcomx/Name.py +0 -276
- gedcomx/PlaceDescription.py +0 -70
- gedcomx/PlaceReference.py +0 -30
- gedcomx/Qualifier.py +0 -27
- gedcomx/Resource.py +0 -75
- gedcomx/Serialization.py +0 -401
- gedcomx/Translation.py +0 -219
- {gedcom_x-0.5.6.dist-info → gedcom_x-0.5.8.dist-info}/WHEEL +0 -0
- {gedcom_x-0.5.6.dist-info → gedcom_x-0.5.8.dist-info}/top_level.txt +0 -0
- /gedcomx/{Exceptions.py → exceptions.py} +0 -0
- /gedcomx/{ExtensibleEnum.py → extensible_enum.py} +0 -0
- /gedcomx/{Gedcom.py → gedcom.py} +0 -0
- /gedcomx/{SourceCitation.py → source_citation.py} +0 -0
- /gedcomx/{TextValue.py → textvalue.py} +0 -0
gedcomx/gedcomx.py
ADDED
@@ -0,0 +1,501 @@
|
|
1
|
+
DEBUG = False
|
2
|
+
|
3
|
+
import json
|
4
|
+
import random
|
5
|
+
import string
|
6
|
+
|
7
|
+
from typing import Any, Dict, Optional
|
8
|
+
|
9
|
+
"""
|
10
|
+
======================================================================
|
11
|
+
Project: Gedcom-X
|
12
|
+
File: GedcomX.py
|
13
|
+
Author: David J. Cartwright
|
14
|
+
Purpose: Object for working with Gedcom-X Data
|
15
|
+
|
16
|
+
Created: 2025-07-25
|
17
|
+
Updated:
|
18
|
+
- 2025-08-31: _as_dict_ to only create entries in dict for fields that hold data,
|
19
|
+
id_index functionality, will be used for resolution of Resources
|
20
|
+
|
21
|
+
======================================================================
|
22
|
+
"""
|
23
|
+
|
24
|
+
"""
|
25
|
+
======================================================================
|
26
|
+
GEDCOM Module Types
|
27
|
+
======================================================================
|
28
|
+
"""
|
29
|
+
from .agent import Agent
|
30
|
+
from .attribution import Attribution
|
31
|
+
from .document import Document
|
32
|
+
from .event import Event
|
33
|
+
from .group import Group
|
34
|
+
from .identifier import make_uid
|
35
|
+
from .Logging import get_logger
|
36
|
+
from .person import Person
|
37
|
+
from .place_description import PlaceDescription
|
38
|
+
from .relationship import Relationship, RelationshipType
|
39
|
+
from .resource import Resource
|
40
|
+
from .source_description import ResourceType, SourceDescription
|
41
|
+
from .textvalue import TextValue
|
42
|
+
from .uri import URI
|
43
|
+
#=====================================================================
|
44
|
+
|
45
|
+
|
46
|
+
def TypeCollection(item_type):
|
47
|
+
"""
|
48
|
+
Factory that creates a typed, indexable collection for a specific model class.
|
49
|
+
|
50
|
+
The returned object behaves like:
|
51
|
+
- a container (append/remove, __len__, __getitem__)
|
52
|
+
- an iterator (for item in collection)
|
53
|
+
- a simple query helper via __call__(**field_equals)
|
54
|
+
- a small in-memory index on id, name (see note), and uri
|
55
|
+
|
56
|
+
Parameters
|
57
|
+
----------
|
58
|
+
item_type : type
|
59
|
+
The class/type that items in this collection must be instances of.
|
60
|
+
|
61
|
+
Returns
|
62
|
+
-------
|
63
|
+
Collection
|
64
|
+
A new, empty collection instance specialized for `item_type`.
|
65
|
+
|
66
|
+
Notes
|
67
|
+
-----
|
68
|
+
- Name indexing is currently disabled (see TODO in `_update_indexes`).
|
69
|
+
- The collection auto-assigns/normalizes an item's `uri.path` based on `item_type`.
|
70
|
+
"""
|
71
|
+
class Collection:
|
72
|
+
def __init__(self):
|
73
|
+
self._items = []
|
74
|
+
self._id_index = {}
|
75
|
+
self._name_index = {}
|
76
|
+
self._uri_index = {}
|
77
|
+
self.uri = URI(path=f'/{item_type.__name__}s/')
|
78
|
+
|
79
|
+
def __iter__(self):
|
80
|
+
self._index = 0
|
81
|
+
return self
|
82
|
+
|
83
|
+
def __next__(self):
|
84
|
+
if self._index < len(self._items):
|
85
|
+
result = self._items[self._index]
|
86
|
+
self._index += 1
|
87
|
+
return result
|
88
|
+
else:
|
89
|
+
raise StopIteration
|
90
|
+
|
91
|
+
@property
|
92
|
+
def item_type(self):
|
93
|
+
return item_type
|
94
|
+
|
95
|
+
def _update_indexes(self, item):
|
96
|
+
# Update the id index
|
97
|
+
if hasattr(item, 'id'):
|
98
|
+
self._id_index[item.id] = item
|
99
|
+
|
100
|
+
try:
|
101
|
+
if hasattr(item, 'uri'):
|
102
|
+
self._uri_index[item.uri.value] = item
|
103
|
+
except AttributeError as e:
|
104
|
+
print(f"type{item}")
|
105
|
+
assert False
|
106
|
+
|
107
|
+
# Update the name index
|
108
|
+
''' #TODO Fix name handling on persons
|
109
|
+
if hasattr(item, 'names'):
|
110
|
+
names = getattr(item, 'names')
|
111
|
+
for name in names:
|
112
|
+
print(name._as_dict_)
|
113
|
+
name_value = name.value if isinstance(name, TextValue) else name
|
114
|
+
if name_value in self._name_index:
|
115
|
+
self._name_index[name_value].append(item)
|
116
|
+
else:
|
117
|
+
self._name_index[name_value] = [item]
|
118
|
+
'''
|
119
|
+
@property
|
120
|
+
def id_index(self):
|
121
|
+
return self._id_index
|
122
|
+
|
123
|
+
def _remove_from_indexes(self, item):
|
124
|
+
# Remove from the id index
|
125
|
+
if hasattr(item, 'id'):
|
126
|
+
if item.id in self._id_index:
|
127
|
+
del self._id_index[item.id]
|
128
|
+
|
129
|
+
# Remove from the name index
|
130
|
+
if hasattr(item, 'names'):
|
131
|
+
names = getattr(item, 'names')
|
132
|
+
for name in names:
|
133
|
+
name_value = name.value if isinstance(name, TextValue) else name
|
134
|
+
if name_value in self._name_index:
|
135
|
+
if item in self._name_index[name_value]:
|
136
|
+
self._name_index[name_value].remove(item)
|
137
|
+
if not self._name_index[name_value]:
|
138
|
+
del self._name_index[name_value]
|
139
|
+
|
140
|
+
def byName(self, sname: str | None):
|
141
|
+
# Use the name index for fast lookup
|
142
|
+
if sname:
|
143
|
+
sname = sname.strip()
|
144
|
+
return self._name_index.get(sname, [])
|
145
|
+
return []
|
146
|
+
|
147
|
+
def byId(self, id):
|
148
|
+
# Use the id index for fast lookup
|
149
|
+
return self._id_index.get(id, None)
|
150
|
+
|
151
|
+
def byUri(self, uri):
|
152
|
+
# Use the id index for fast lookup
|
153
|
+
return self._uri_index.get(uri.value, None)
|
154
|
+
|
155
|
+
def append(self, item):
|
156
|
+
if not isinstance(item, item_type):
|
157
|
+
raise TypeError(f"Expected item of type {item_type.__name__}, got {type(item).__name__}")
|
158
|
+
if item.uri:
|
159
|
+
item.uri.path = f'{str(item_type.__name__)}s' if (item.uri.path is None or item.uri.path == "") else item.uri.path
|
160
|
+
else:
|
161
|
+
item.uri = URI(path=f'/{item_type.__name__}s/',fragment=item.id)
|
162
|
+
|
163
|
+
self._items.append(item)
|
164
|
+
self._update_indexes(item)
|
165
|
+
|
166
|
+
def remove(self, item):
|
167
|
+
if item not in self._items:
|
168
|
+
raise ValueError("Item not found in the collection.")
|
169
|
+
self._items.remove(item)
|
170
|
+
self._remove_from_indexes(item)
|
171
|
+
|
172
|
+
def __repr__(self):
|
173
|
+
return f"Collection({self._items!r})"
|
174
|
+
|
175
|
+
def list(self):
|
176
|
+
for item in self._items:
|
177
|
+
print(item)
|
178
|
+
|
179
|
+
def __call__(self, **kwargs):
|
180
|
+
results = []
|
181
|
+
for item in self._items:
|
182
|
+
match = True
|
183
|
+
for key, value in kwargs.items():
|
184
|
+
if not hasattr(item, key) or getattr(item, key) != value:
|
185
|
+
match = False
|
186
|
+
break
|
187
|
+
if match:
|
188
|
+
results.append(item)
|
189
|
+
return results
|
190
|
+
|
191
|
+
def __len__(self):
|
192
|
+
return len(self._items)
|
193
|
+
|
194
|
+
def __getitem__(self, index):
|
195
|
+
return self._items[index]
|
196
|
+
|
197
|
+
@property
|
198
|
+
def _items_as_dict(self) -> dict:
|
199
|
+
return {f'{str(item_type.__name__)}s': [item._as_dict_ for item in self._items]}
|
200
|
+
|
201
|
+
@property
|
202
|
+
def _as_dict_(self):
|
203
|
+
return {f'{str(item_type.__name__).lower()}s': [item._as_dict_ for item in self._items]}
|
204
|
+
|
205
|
+
@property
|
206
|
+
def json(self) -> str:
|
207
|
+
|
208
|
+
return json.dumps(self._as_dict_, indent=4)
|
209
|
+
|
210
|
+
return Collection()
|
211
|
+
|
212
|
+
class GedcomX:
|
213
|
+
"""
|
214
|
+
Main GedcomX Object representing a Genealogy. Stores collections of Top Level Gedcom-X Types.
|
215
|
+
complies with GEDCOM X Conceptual Model V1 (http://gedcomx.org/conceptual-model/v1)
|
216
|
+
|
217
|
+
Parameters
|
218
|
+
----------
|
219
|
+
id : str
|
220
|
+
Unique identifier for this Genealogy.
|
221
|
+
attribution : Attribution Object
|
222
|
+
Attribution information for the Genealogy
|
223
|
+
filepath : str
|
224
|
+
Not Implimented.
|
225
|
+
description : str
|
226
|
+
Description of the Genealogy: ex. 'My Family Tree'
|
227
|
+
|
228
|
+
Raises
|
229
|
+
------
|
230
|
+
ValueError
|
231
|
+
If `id` is not a valid UUID.
|
232
|
+
"""
|
233
|
+
version = 'http://gedcomx.org/conceptual-model/v1'
|
234
|
+
|
235
|
+
def __init__(self, id: Optional[str] = None,
|
236
|
+
attribution: Optional[Attribution] = None,
|
237
|
+
filepath: Optional[str] = None,
|
238
|
+
description: Optional[str] = None) -> None:
|
239
|
+
|
240
|
+
self.id = id
|
241
|
+
self.attribution = attribution
|
242
|
+
self._filepath = None
|
243
|
+
|
244
|
+
self.description = description
|
245
|
+
self.source_descriptions = TypeCollection(SourceDescription)
|
246
|
+
self.persons = TypeCollection(Person)
|
247
|
+
self.relationships = TypeCollection(Relationship)
|
248
|
+
self.agents = TypeCollection(Agent)
|
249
|
+
self.events = TypeCollection(Event)
|
250
|
+
self.documents = TypeCollection(Document)
|
251
|
+
self.places = TypeCollection(PlaceDescription)
|
252
|
+
self.groups = TypeCollection(Group)
|
253
|
+
|
254
|
+
self.relationship_table = {}
|
255
|
+
|
256
|
+
self.default_id_generator = make_uid
|
257
|
+
|
258
|
+
@property
|
259
|
+
def contents(self):
|
260
|
+
return {
|
261
|
+
"source_descriptions": len(self.source_descriptions),
|
262
|
+
"persons": len(self.persons),
|
263
|
+
"relationships": len(self.relationships),
|
264
|
+
"agents": len(self.agents),
|
265
|
+
"events": len(self.events),
|
266
|
+
"documents": len(self.documents),
|
267
|
+
"places": len(self.places),
|
268
|
+
"groups": len(self.groups),
|
269
|
+
}
|
270
|
+
|
271
|
+
def add(self,gedcomx_type_object):
|
272
|
+
if gedcomx_type_object:
|
273
|
+
if isinstance(gedcomx_type_object,Person):
|
274
|
+
self.add_person(gedcomx_type_object)
|
275
|
+
elif isinstance(gedcomx_type_object,SourceDescription):
|
276
|
+
self.add_source_description(gedcomx_type_object)
|
277
|
+
elif isinstance(gedcomx_type_object,Agent):
|
278
|
+
self.add_agent(gedcomx_type_object)
|
279
|
+
elif isinstance(gedcomx_type_object,PlaceDescription):
|
280
|
+
self.add_place_description(gedcomx_type_object)
|
281
|
+
elif isinstance(gedcomx_type_object,Event):
|
282
|
+
self.add_event(gedcomx_type_object)
|
283
|
+
elif isinstance(gedcomx_type_object,Relationship):
|
284
|
+
self.add_relationship(gedcomx_type_object)
|
285
|
+
else:
|
286
|
+
raise ValueError(f"I do not know how to add an Object of type {type(gedcomx_type_object)}")
|
287
|
+
else:
|
288
|
+
Warning("Tried to add a None type to the Geneology")
|
289
|
+
|
290
|
+
def add_source_description(self,sourceDescription: SourceDescription):
|
291
|
+
if sourceDescription and isinstance(sourceDescription,SourceDescription):
|
292
|
+
if sourceDescription.id is None:
|
293
|
+
sourceDescription.id =self.default_id_generator()
|
294
|
+
self.source_descriptions.append(item=sourceDescription)
|
295
|
+
self.lastSourceDescriptionAdded = sourceDescription
|
296
|
+
else:
|
297
|
+
raise ValueError(f"When adding a SourceDescription, value must be of type SourceDescription, type {type(sourceDescription)} was provided")
|
298
|
+
|
299
|
+
def add_person(self,person: Person):
|
300
|
+
"""Add a Person object to the Genealogy
|
301
|
+
|
302
|
+
Args:
|
303
|
+
person: Person Object
|
304
|
+
|
305
|
+
Returns:
|
306
|
+
None
|
307
|
+
|
308
|
+
Raises:
|
309
|
+
ValueError: If `person` is not of type Person.
|
310
|
+
"""
|
311
|
+
if person and isinstance(person,Person):
|
312
|
+
if person.id is None:
|
313
|
+
person.id =self.make_id()
|
314
|
+
self.persons.append(item=person)
|
315
|
+
else:
|
316
|
+
raise ValueError(f'person must be a Person Object not type: {type(person)}')
|
317
|
+
|
318
|
+
def add_relationship(self,relationship: Relationship):
|
319
|
+
if relationship and isinstance(relationship,Relationship):
|
320
|
+
if isinstance(relationship.person1,Resource) and isinstance(relationship.person2,Resource):
|
321
|
+
print("Adding unresolved Relationship")
|
322
|
+
self.relationships.append(relationship)
|
323
|
+
return
|
324
|
+
elif isinstance(relationship.person1,Person) and isinstance(relationship.person2,Person):
|
325
|
+
|
326
|
+
if relationship.person1:
|
327
|
+
if relationship.person1.id is None:
|
328
|
+
relationship.person1.id = self.make_id()
|
329
|
+
if not self.persons.byId(relationship.person1.id):
|
330
|
+
self.persons.append(relationship.person1)
|
331
|
+
if relationship.person1.id not in self.relationship_table:
|
332
|
+
self.relationship_table[relationship.person1.id] = []
|
333
|
+
self.relationship_table[relationship.person1.id].append(relationship)
|
334
|
+
relationship.person1._add_relationship(relationship)
|
335
|
+
else:
|
336
|
+
pass
|
337
|
+
|
338
|
+
if relationship.person2:
|
339
|
+
if relationship.person2.id is None:
|
340
|
+
relationship.person2.id = self.make_id() #TODO
|
341
|
+
if not self.persons.byId(relationship.person2.id):
|
342
|
+
self.persons.append(relationship.person2)
|
343
|
+
if relationship.person2.id not in self.relationship_table:
|
344
|
+
self.relationship_table[relationship.person2.id] = []
|
345
|
+
self.relationship_table[relationship.person2.id].append(relationship)
|
346
|
+
relationship.person2._add_relationship(relationship)
|
347
|
+
else:
|
348
|
+
pass
|
349
|
+
|
350
|
+
self.relationships.append(relationship)
|
351
|
+
else:
|
352
|
+
raise ValueError()
|
353
|
+
|
354
|
+
def add_place_description(self,placeDescription: PlaceDescription):
|
355
|
+
if placeDescription and isinstance(placeDescription,PlaceDescription):
|
356
|
+
if placeDescription.id is None:
|
357
|
+
Warning("PlaceDescription has no id")
|
358
|
+
self.places.append(placeDescription)
|
359
|
+
|
360
|
+
def add_agent(self,agent: Agent):
|
361
|
+
"""Add a Agent object to the Genealogy
|
362
|
+
|
363
|
+
Args:
|
364
|
+
agent: Agent Object
|
365
|
+
|
366
|
+
Returns:
|
367
|
+
None
|
368
|
+
|
369
|
+
Raises:
|
370
|
+
ValueError: If `agent` is not of type Agent.
|
371
|
+
"""
|
372
|
+
if agent and isinstance(agent,Agent):
|
373
|
+
if agent in self.agents:
|
374
|
+
return
|
375
|
+
if agent.id is None:
|
376
|
+
agent.id = make_uid()
|
377
|
+
if self.agents.byId(agent.id):
|
378
|
+
pass #TODO Deal with duplicates
|
379
|
+
#raise ValueError
|
380
|
+
self.agents.append(agent)
|
381
|
+
|
382
|
+
def add_event(self,event_to_add: Event):
|
383
|
+
if event_to_add and isinstance(event_to_add,Event):
|
384
|
+
if event_to_add.id is None: event_to_add.id = make_uid()
|
385
|
+
for current_event in self.events:
|
386
|
+
if event_to_add == current_event:
|
387
|
+
print("DUPLICATE EVENT")
|
388
|
+
print(event_to_add._as_dict_)
|
389
|
+
print(current_event._as_dict_)
|
390
|
+
return
|
391
|
+
self.events.append(event_to_add)
|
392
|
+
else:
|
393
|
+
raise ValueError
|
394
|
+
|
395
|
+
def get_person_by_id(self,id: str):
|
396
|
+
filtered = [person for person in self.persons if getattr(person, 'id') == id]
|
397
|
+
if filtered: return filtered[0]
|
398
|
+
return None
|
399
|
+
|
400
|
+
def source(self,id: str):
|
401
|
+
filtered = [source for source in self.source_descriptions if getattr(source, 'id') == id]
|
402
|
+
if filtered: return filtered[0]
|
403
|
+
return None
|
404
|
+
|
405
|
+
@property
|
406
|
+
def id_index(self):
|
407
|
+
combined = {**self.source_descriptions.id_index,
|
408
|
+
**self.persons.id_index,
|
409
|
+
**self.relationships.id_index,
|
410
|
+
**self.agents.id_index,
|
411
|
+
**self.events.id_index,
|
412
|
+
**self.documents.id_index,
|
413
|
+
**self.places.id_index,
|
414
|
+
**self.groups.id_index
|
415
|
+
}
|
416
|
+
for i in combined.keys():
|
417
|
+
combined[i] = str(type(combined[i]).__name__)
|
418
|
+
return combined
|
419
|
+
|
420
|
+
@property
|
421
|
+
def _as_dict(self) -> dict[str, Any]:
|
422
|
+
type_as_dict: Dict[str, Any] = {}
|
423
|
+
|
424
|
+
if self.persons and len(self.persons) > 0:
|
425
|
+
type_as_dict["persons"] = [person._as_dict_ for person in self.persons]
|
426
|
+
|
427
|
+
if self.source_descriptions:
|
428
|
+
type_as_dict["sourceDescriptions"] = [
|
429
|
+
sd._as_dict_ for sd in self.source_descriptions
|
430
|
+
]
|
431
|
+
|
432
|
+
if self.relationships:
|
433
|
+
type_as_dict["relationships"] = [
|
434
|
+
rel._as_dict_ for rel in self.relationships
|
435
|
+
]
|
436
|
+
|
437
|
+
if self.agents:
|
438
|
+
type_as_dict["agents"] = [agent._as_dict_ for agent in self.agents]
|
439
|
+
|
440
|
+
if self.events:
|
441
|
+
type_as_dict["events"] = [event._as_dict_ for event in self.events]
|
442
|
+
|
443
|
+
if self.places:
|
444
|
+
type_as_dict["places"] = [place._as_dict_ for place in self.places]
|
445
|
+
|
446
|
+
if self.documents:
|
447
|
+
type_as_dict["documents"] = [doc._as_dict_ for doc in self.documents]
|
448
|
+
|
449
|
+
return type_as_dict
|
450
|
+
|
451
|
+
@property
|
452
|
+
def json(self):
|
453
|
+
"""
|
454
|
+
JSON Representation of the GedcomX Genealogy.
|
455
|
+
|
456
|
+
Returns:
|
457
|
+
str: JSON Representation of the GedcomX Genealogy in the GEDCOM X JSON Serialization Format
|
458
|
+
"""
|
459
|
+
gedcomx_json = {
|
460
|
+
'persons': [person._as_dict_ for person in self.persons],
|
461
|
+
'sourceDescriptions' : [sourceDescription._as_dict_ for sourceDescription in self.source_descriptions],
|
462
|
+
'relationships': [relationship._as_dict_ for relationship in self.relationships],
|
463
|
+
'agents': [agent._as_dict_ for agent in self.agents],
|
464
|
+
'events': [event._as_dict_ for event in self.events],
|
465
|
+
'places': [place._as_dict_ for place in self.places],
|
466
|
+
'documents': [document._as_dict_ for document in self.documents],
|
467
|
+
}
|
468
|
+
return json.dumps(gedcomx_json, indent=4)
|
469
|
+
|
470
|
+
@staticmethod
|
471
|
+
def from_json(data: dict):
|
472
|
+
from .serialization import Serialization
|
473
|
+
gx = GedcomX()
|
474
|
+
|
475
|
+
source_descriptions = data.get('sourceDescriptions', [])
|
476
|
+
for source in source_descriptions:
|
477
|
+
gx.add_source_description(Serialization.deserialize(source,SourceDescription))
|
478
|
+
|
479
|
+
persons = data.get('persons', [])
|
480
|
+
for person in persons:
|
481
|
+
gx.add_person(Serialization.deserialize(person,Person))
|
482
|
+
|
483
|
+
relationships = data.get('relationships', [])
|
484
|
+
for relationship in relationships:
|
485
|
+
gx.add_relationship(Serialization.deserialize(relationship,Relationship))
|
486
|
+
|
487
|
+
agents = data.get('agents', [])
|
488
|
+
for agent in agents:
|
489
|
+
gx.add_agent(Serialization.deserialize(agent,Agent))
|
490
|
+
|
491
|
+
events = data.get('events', [])
|
492
|
+
for event in events:
|
493
|
+
gx.add_event(Serialization.deserialize(event,Event))
|
494
|
+
|
495
|
+
return gx
|
496
|
+
|
497
|
+
@staticmethod
|
498
|
+
def make_id(length: int = 12) -> str:
|
499
|
+
"""Generate a random alphanumeric ID of given length."""
|
500
|
+
alphabet = string.ascii_letters + string.digits
|
501
|
+
return ''.join(random.choices(alphabet, k=length))
|
gedcomx/{Gender.py → gender.py}
RENAMED
@@ -1,17 +1,31 @@
|
|
1
1
|
from enum import Enum
|
2
2
|
from typing import List, Optional
|
3
|
+
"""
|
4
|
+
======================================================================
|
5
|
+
Project: Gedcom-X
|
6
|
+
File: gender.py
|
7
|
+
Author: David J. Cartwright
|
8
|
+
Purpose:
|
3
9
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
10
|
+
Created: 2025-08-25
|
11
|
+
Updated:
|
12
|
+
- 2025-08-31:
|
13
|
+
|
14
|
+
======================================================================
|
15
|
+
"""
|
9
16
|
|
10
|
-
|
11
|
-
|
12
|
-
|
17
|
+
"""
|
18
|
+
======================================================================
|
19
|
+
GEDCOM Module Types
|
20
|
+
======================================================================
|
21
|
+
"""
|
22
|
+
from .attribution import Attribution
|
23
|
+
from .conclusion import ConfidenceLevel, Conclusion
|
24
|
+
from .note import Note
|
25
|
+
from .resource import Resource
|
26
|
+
from .source_reference import SourceReference
|
27
|
+
#=====================================================================
|
13
28
|
|
14
|
-
from collections.abc import Sized
|
15
29
|
|
16
30
|
class GenderType(Enum):
|
17
31
|
Male = "http://gedcomx.org/Male"
|
@@ -35,7 +49,7 @@ class Gender(Conclusion):
|
|
35
49
|
|
36
50
|
def __init__(self,
|
37
51
|
id: Optional[str] = None,
|
38
|
-
lang: Optional[str] =
|
52
|
+
lang: Optional[str] = None,
|
39
53
|
sources: Optional[List[SourceReference]] = None,
|
40
54
|
analysis: Optional[Resource] = None,
|
41
55
|
notes: Optional[List[Note]] = None,
|
@@ -48,19 +62,17 @@ class Gender(Conclusion):
|
|
48
62
|
|
49
63
|
@property
|
50
64
|
def _as_dict_(self):
|
65
|
+
from .serialization import Serialization
|
66
|
+
type_as_dict = super()._as_dict_
|
67
|
+
if self.type:
|
68
|
+
type_as_dict['type'] = self.type.value if self.type else None
|
51
69
|
|
52
|
-
|
53
|
-
type_as_dict = super()._as_dict_ # Start with base class fields
|
54
|
-
# Only add Relationship-specific fields
|
55
|
-
type_as_dict.update({
|
56
|
-
'type':self.type.value if self.type else None
|
57
|
-
|
58
|
-
})
|
59
70
|
|
60
71
|
return Serialization.serialize_dict(type_as_dict)
|
61
72
|
|
62
73
|
@classmethod
|
63
74
|
def _from_json_(cls,data):
|
75
|
+
from .serialization import Serialization
|
64
76
|
|
65
77
|
return Serialization.deserialize(data, Gender)
|
66
78
|
|
gedcomx/group.py
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
from enum import Enum
|
2
|
+
from typing import List, Optional
|
3
|
+
"""
|
4
|
+
======================================================================
|
5
|
+
Project: Gedcom-X
|
6
|
+
File: group.py
|
7
|
+
Author: David J. Cartwright
|
8
|
+
Purpose:
|
9
|
+
|
10
|
+
Created: 2025-08-25
|
11
|
+
Updated:
|
12
|
+
- 2025-09-01: Updating basic structure, identify TODO s
|
13
|
+
|
14
|
+
======================================================================
|
15
|
+
"""
|
16
|
+
|
17
|
+
"""
|
18
|
+
======================================================================
|
19
|
+
GEDCOM Module Types
|
20
|
+
======================================================================
|
21
|
+
"""
|
22
|
+
from .attribution import Attribution
|
23
|
+
from .conclusion import ConfidenceLevel
|
24
|
+
from .document import Document
|
25
|
+
from .date import Date
|
26
|
+
from .evidence_reference import EvidenceReference
|
27
|
+
from .identifier import Identifier
|
28
|
+
from .note import Note
|
29
|
+
from .place_reference import PlaceReference
|
30
|
+
from .source_reference import SourceReference
|
31
|
+
from .resource import Resource
|
32
|
+
|
33
|
+
from .textvalue import TextValue
|
34
|
+
from .subject import Subject
|
35
|
+
|
36
|
+
class GroupRoleType(Enum): #TODO Impliment
|
37
|
+
def __init__(self) -> None:
|
38
|
+
super().__init__()
|
39
|
+
|
40
|
+
class GroupRole: #TODO Impliment
|
41
|
+
identifier = 'http://gedcomx.org/v1/GroupRole'
|
42
|
+
version = 'http://gedcomx.org/conceptual-model/v1'
|
43
|
+
|
44
|
+
def __init__(self, person: Resource,type: Optional[Enum], date: Optional[Date],details: Optional[str]) -> None:
|
45
|
+
pass
|
46
|
+
|
47
|
+
class Group(Subject): #TODO Impliment
|
48
|
+
identifier = 'http://gedcomx.org/v1/Group'
|
49
|
+
version = 'http://gedcomx.org/conceptual-model/v1'
|
50
|
+
|
51
|
+
def __init__(self,
|
52
|
+
id: str | None, lang: str | None,
|
53
|
+
sources: List[SourceReference] | None,
|
54
|
+
analysis: Document | Resource | None, notes: List[Note] | None, confidence: ConfidenceLevel | None, attribution: Attribution | None, extracted: bool | None, evidence: List[EvidenceReference] | None, media: List[SourceReference] | None, identifiers: List[Identifier] | None,
|
55
|
+
names: List[TextValue],
|
56
|
+
date: Optional[Date],
|
57
|
+
place: Optional[PlaceReference],
|
58
|
+
roles: Optional[List[GroupRole]]) -> None:
|
59
|
+
super().__init__(id, lang, sources, analysis, notes, confidence, attribution, extracted, evidence, media, identifiers)
|
60
|
+
self.names = names if names else []
|
61
|
+
self.date = date
|
62
|
+
self.place = place
|
63
|
+
self.roles = roles if roles else []
|