gedcom-x 0.5.7__py3-none-any.whl → 0.5.9__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.7.dist-info → gedcom_x-0.5.9.dist-info}/METADATA +1 -1
- gedcom_x-0.5.9.dist-info/RECORD +56 -0
- gedcomx/Extensions/rs10/rsLink.py +110 -60
- gedcomx/TopLevelTypeCollection.py +1 -1
- gedcomx/__init__.py +43 -42
- gedcomx/address.py +217 -0
- gedcomx/{Agent.py → agent.py} +107 -34
- gedcomx/attribution.py +115 -0
- gedcomx/{Conclusion.py → conclusion.py} +120 -51
- gedcomx/{Converter.py → converter.py} +261 -116
- gedcomx/coverage.py +64 -0
- gedcomx/{Date.py → date.py} +43 -9
- gedcomx/{Document.py → document.py} +60 -12
- gedcomx/{Event.py → event.py} +88 -31
- gedcomx/evidence_reference.py +20 -0
- gedcomx/{Fact.py → fact.py} +81 -74
- gedcomx/{Gedcom.py → gedcom.py} +10 -0
- gedcomx/{Gedcom5x.py → gedcom5x.py} +31 -21
- gedcomx/gedcom7/Exceptions.py +9 -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/gedcom7.py +160 -0
- gedcomx/gedcom7/logger.py +19 -0
- gedcomx/{GedcomX.py → gedcomx.py} +109 -106
- gedcomx/gender.py +91 -0
- gedcomx/group.py +72 -0
- gedcomx/{Identifier.py → identifier.py} +48 -21
- gedcomx/{LoggingHub.py → logging_hub.py} +19 -0
- gedcomx/{Mutations.py → mutations.py} +59 -30
- gedcomx/{Name.py → name.py} +88 -47
- gedcomx/note.py +105 -0
- gedcomx/online_account.py +19 -0
- gedcomx/{Person.py → person.py} +61 -41
- gedcomx/{PlaceDescription.py → place_description.py} +71 -23
- gedcomx/{PlaceReference.py → place_reference.py} +32 -10
- gedcomx/{Qualifier.py → qualifier.py} +20 -4
- gedcomx/relationship.py +156 -0
- gedcomx/resource.py +112 -0
- gedcomx/serialization.py +794 -0
- gedcomx/source_citation.py +37 -0
- gedcomx/source_description.py +401 -0
- gedcomx/{SourceReference.py → source_reference.py} +56 -21
- gedcomx/subject.py +122 -0
- gedcomx/textvalue.py +89 -0
- gedcomx/{Translation.py → translation.py} +4 -4
- gedcomx/uri.py +273 -0
- gedcom_x-0.5.7.dist-info/RECORD +0 -49
- gedcomx/Address.py +0 -131
- gedcomx/Attribution.py +0 -91
- gedcomx/Coverage.py +0 -37
- gedcomx/EvidenceReference.py +0 -11
- gedcomx/Gender.py +0 -65
- gedcomx/Group.py +0 -37
- gedcomx/Note.py +0 -73
- gedcomx/OnlineAccount.py +0 -10
- gedcomx/Relationship.py +0 -97
- gedcomx/Resource.py +0 -85
- gedcomx/Serialization.py +0 -816
- gedcomx/SourceCitation.py +0 -25
- gedcomx/SourceDescription.py +0 -314
- gedcomx/Subject.py +0 -59
- gedcomx/TextValue.py +0 -35
- gedcomx/URI.py +0 -105
- {gedcom_x-0.5.7.dist-info → gedcom_x-0.5.9.dist-info}/WHEEL +0 -0
- {gedcom_x-0.5.7.dist-info → gedcom_x-0.5.9.dist-info}/top_level.txt +0 -0
- /gedcomx/{Exceptions.py → exceptions.py} +0 -0
- /gedcomx/{ExtensibleEnum.py → extensible_enum.py} +0 -0
@@ -3,8 +3,10 @@ DEBUG = False
|
|
3
3
|
import json
|
4
4
|
import random
|
5
5
|
import string
|
6
|
+
import orjson
|
6
7
|
|
7
|
-
from
|
8
|
+
from functools import lru_cache
|
9
|
+
from typing import Any, Dict, List, Optional
|
8
10
|
|
9
11
|
"""
|
10
12
|
======================================================================
|
@@ -15,8 +17,9 @@ from typing import Any, Dict, Optional
|
|
15
17
|
|
16
18
|
Created: 2025-07-25
|
17
19
|
Updated:
|
18
|
-
|
20
|
+
- 2025-08-31: _as_dict_ to only create entries in dict for fields that hold data,
|
19
21
|
id_index functionality, will be used for resolution of Resources
|
22
|
+
- 2025-09-03: _from_json_ refactor
|
20
23
|
|
21
24
|
======================================================================
|
22
25
|
"""
|
@@ -26,21 +29,27 @@ from typing import Any, Dict, Optional
|
|
26
29
|
GEDCOM Module Types
|
27
30
|
======================================================================
|
28
31
|
"""
|
29
|
-
from .
|
30
|
-
from .
|
31
|
-
from .
|
32
|
-
from .
|
33
|
-
from .
|
34
|
-
from .
|
35
|
-
from .
|
36
|
-
from .
|
37
|
-
from .
|
38
|
-
from .
|
39
|
-
from .
|
40
|
-
from .
|
41
|
-
from .
|
32
|
+
from .agent import Agent
|
33
|
+
from .attribution import Attribution
|
34
|
+
from .document import Document
|
35
|
+
from .event import Event
|
36
|
+
from .group import Group
|
37
|
+
from .identifier import make_uid
|
38
|
+
from .logging_hub import logging, hub, ChannelConfig
|
39
|
+
from .person import Person
|
40
|
+
from .place_description import PlaceDescription
|
41
|
+
from .relationship import Relationship, RelationshipType
|
42
|
+
from .resource import Resource
|
43
|
+
from .source_description import ResourceType, SourceDescription
|
44
|
+
from .textvalue import TextValue
|
45
|
+
from .uri import URI
|
42
46
|
#=====================================================================
|
43
47
|
|
48
|
+
log = logging.getLogger("gedcomx")
|
49
|
+
serial_log = "gedcomx.serialization"
|
50
|
+
deserial_log = "gedcomx.serialization"
|
51
|
+
|
52
|
+
|
44
53
|
|
45
54
|
def TypeCollection(item_type):
|
46
55
|
"""
|
@@ -104,17 +113,20 @@ def TypeCollection(item_type):
|
|
104
113
|
assert False
|
105
114
|
|
106
115
|
# Update the name index
|
107
|
-
|
116
|
+
#TODO Fix name handling on persons
|
108
117
|
if hasattr(item, 'names'):
|
109
118
|
names = getattr(item, 'names')
|
110
119
|
for name in names:
|
111
|
-
print(name._as_dict_)
|
112
|
-
|
113
|
-
|
114
|
-
self._name_index
|
120
|
+
#print(name._as_dict_)
|
121
|
+
if isinstance(name, TextValue):
|
122
|
+
name_value = name.value
|
123
|
+
if name_value in self._name_index:
|
124
|
+
self._name_index[name_value].append(item)
|
125
|
+
else:
|
126
|
+
self._name_index[name_value] = [item]
|
115
127
|
else:
|
116
|
-
|
117
|
-
|
128
|
+
pass
|
129
|
+
|
118
130
|
@property
|
119
131
|
def id_index(self):
|
120
132
|
return self._id_index
|
@@ -154,6 +166,7 @@ def TypeCollection(item_type):
|
|
154
166
|
def append(self, item):
|
155
167
|
if not isinstance(item, item_type):
|
156
168
|
raise TypeError(f"Expected item of type {item_type.__name__}, got {type(item).__name__}")
|
169
|
+
#if item.id in self.id_index: assert False
|
157
170
|
if item.uri:
|
158
171
|
item.uri.path = f'{str(item_type.__name__)}s' if (item.uri.path is None or item.uri.path == "") else item.uri.path
|
159
172
|
else:
|
@@ -162,6 +175,13 @@ def TypeCollection(item_type):
|
|
162
175
|
self._items.append(item)
|
163
176
|
self._update_indexes(item)
|
164
177
|
|
178
|
+
def extend(self, items: list):
|
179
|
+
"""Add multiple items to the collection at once."""
|
180
|
+
if not isinstance(items, (list, tuple)):
|
181
|
+
raise TypeError("extend() expects a list or tuple of items")
|
182
|
+
for item in items:
|
183
|
+
self.append(item)
|
184
|
+
|
165
185
|
def remove(self, item):
|
166
186
|
if item not in self._items:
|
167
187
|
raise ValueError("Item not found in the collection.")
|
@@ -234,30 +254,40 @@ class GedcomX:
|
|
234
254
|
def __init__(self, id: Optional[str] = None,
|
235
255
|
attribution: Optional[Attribution] = None,
|
236
256
|
filepath: Optional[str] = None,
|
237
|
-
description: Optional[str] = None
|
257
|
+
description: Optional[str] = None,
|
258
|
+
persons: Optional[List[Person]] = None,
|
259
|
+
relationships: Optional[List[Relationship]] = None,
|
260
|
+
sourceDescriptions: Optional[List[SourceDescription]] = None,
|
261
|
+
agents: Optional[List[Agent]] = None,
|
262
|
+
places: Optional[List[PlaceDescription]] = None) -> None:
|
238
263
|
|
239
264
|
self.id = id
|
240
265
|
self.attribution = attribution
|
241
266
|
self._filepath = None
|
242
267
|
|
243
268
|
self.description = description
|
244
|
-
self.
|
269
|
+
self.sourceDescriptions = TypeCollection(SourceDescription)
|
270
|
+
if sourceDescriptions: self.sourceDescriptions.extend(sourceDescriptions)
|
245
271
|
self.persons = TypeCollection(Person)
|
246
|
-
self.
|
272
|
+
if persons: self.persons.extend(persons)
|
273
|
+
self.relationships = TypeCollection(Relationship)
|
274
|
+
if relationships: self.relationships.extend(relationships)
|
247
275
|
self.agents = TypeCollection(Agent)
|
276
|
+
if agents: self.agents.extend(agents)
|
248
277
|
self.events = TypeCollection(Event)
|
249
278
|
self.documents = TypeCollection(Document)
|
250
279
|
self.places = TypeCollection(PlaceDescription)
|
280
|
+
if places: self.places.extend(places)
|
251
281
|
self.groups = TypeCollection(Group)
|
252
282
|
|
253
283
|
self.relationship_table = {}
|
254
284
|
|
255
|
-
self.default_id_generator = make_uid
|
285
|
+
#self.default_id_generator = make_uid
|
256
286
|
|
257
287
|
@property
|
258
288
|
def contents(self):
|
259
289
|
return {
|
260
|
-
"source_descriptions": len(self.
|
290
|
+
"source_descriptions": len(self.sourceDescriptions),
|
261
291
|
"persons": len(self.persons),
|
262
292
|
"relationships": len(self.relationships),
|
263
293
|
"agents": len(self.agents),
|
@@ -289,8 +319,8 @@ class GedcomX:
|
|
289
319
|
def add_source_description(self,sourceDescription: SourceDescription):
|
290
320
|
if sourceDescription and isinstance(sourceDescription,SourceDescription):
|
291
321
|
if sourceDescription.id is None:
|
292
|
-
|
293
|
-
self.
|
322
|
+
assert False
|
323
|
+
self.sourceDescriptions.append(item=sourceDescription)
|
294
324
|
self.lastSourceDescriptionAdded = sourceDescription
|
295
325
|
else:
|
296
326
|
raise ValueError(f"When adding a SourceDescription, value must be of type SourceDescription, type {type(sourceDescription)} was provided")
|
@@ -317,7 +347,6 @@ class GedcomX:
|
|
317
347
|
def add_relationship(self,relationship: Relationship):
|
318
348
|
if relationship and isinstance(relationship,Relationship):
|
319
349
|
if isinstance(relationship.person1,Resource) and isinstance(relationship.person2,Resource):
|
320
|
-
print("Adding unresolved Relationship")
|
321
350
|
self.relationships.append(relationship)
|
322
351
|
return
|
323
352
|
elif isinstance(relationship.person1,Person) and isinstance(relationship.person2,Person):
|
@@ -357,26 +386,13 @@ class GedcomX:
|
|
357
386
|
self.places.append(placeDescription)
|
358
387
|
|
359
388
|
def add_agent(self,agent: Agent):
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
Returns:
|
366
|
-
None
|
367
|
-
|
368
|
-
Raises:
|
369
|
-
ValueError: If `agent` is not of type Agent.
|
370
|
-
"""
|
371
|
-
if agent and isinstance(agent,Agent):
|
372
|
-
if agent in self.agents:
|
373
|
-
return
|
374
|
-
if agent.id is None:
|
375
|
-
agent.id = make_uid()
|
376
|
-
if self.agents.byId(agent.id):
|
377
|
-
pass #TODO Deal with duplicates
|
378
|
-
#raise ValueError
|
389
|
+
if isinstance(agent,Agent) and agent is not None:
|
390
|
+
if self.agents.byId(agent.id) is not None:
|
391
|
+
print(f"Did not add agent with Duplicate ID")
|
392
|
+
return False
|
379
393
|
self.agents.append(agent)
|
394
|
+
else:
|
395
|
+
raise ValueError()
|
380
396
|
|
381
397
|
def add_event(self,event_to_add: Event):
|
382
398
|
if event_to_add and isinstance(event_to_add,Event):
|
@@ -386,24 +402,27 @@ class GedcomX:
|
|
386
402
|
print("DUPLICATE EVENT")
|
387
403
|
print(event_to_add._as_dict_)
|
388
404
|
print(current_event._as_dict_)
|
405
|
+
|
389
406
|
return
|
390
407
|
self.events.append(event_to_add)
|
391
408
|
else:
|
392
409
|
raise ValueError
|
393
410
|
|
411
|
+
@lru_cache(maxsize=65536)
|
394
412
|
def get_person_by_id(self,id: str):
|
395
413
|
filtered = [person for person in self.persons if getattr(person, 'id') == id]
|
396
414
|
if filtered: return filtered[0]
|
397
415
|
return None
|
398
|
-
|
399
|
-
|
400
|
-
|
416
|
+
|
417
|
+
@lru_cache(maxsize=65536)
|
418
|
+
def source_by_id(self,id: str):
|
419
|
+
filtered = [source for source in self.sourceDescriptions if getattr(source, 'id') == id]
|
401
420
|
if filtered: return filtered[0]
|
402
421
|
return None
|
403
422
|
|
404
423
|
@property
|
405
424
|
def id_index(self):
|
406
|
-
combined = {**self.
|
425
|
+
combined = {**self.sourceDescriptions.id_index,
|
407
426
|
**self.persons.id_index,
|
408
427
|
**self.relationships.id_index,
|
409
428
|
**self.agents.id_index,
|
@@ -418,78 +437,62 @@ class GedcomX:
|
|
418
437
|
|
419
438
|
@property
|
420
439
|
def _as_dict(self) -> dict[str, Any]:
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
type_as_dict["persons"] = [person._as_dict_ for person in self.persons]
|
425
|
-
|
426
|
-
if self.source_descriptions:
|
427
|
-
type_as_dict["sourceDescriptions"] = [
|
428
|
-
sd._as_dict_ for sd in self.source_descriptions
|
429
|
-
]
|
430
|
-
|
431
|
-
if self.relationships:
|
432
|
-
type_as_dict["relationships"] = [
|
433
|
-
rel._as_dict_ for rel in self.relationships
|
434
|
-
]
|
435
|
-
|
436
|
-
if self.agents:
|
437
|
-
type_as_dict["agents"] = [agent._as_dict_ for agent in self.agents]
|
438
|
-
|
439
|
-
if self.events:
|
440
|
-
type_as_dict["events"] = [event._as_dict_ for event in self.events]
|
441
|
-
|
442
|
-
if self.places:
|
443
|
-
type_as_dict["places"] = [place._as_dict_ for place in self.places]
|
444
|
-
|
445
|
-
if self.documents:
|
446
|
-
type_as_dict["documents"] = [doc._as_dict_ for doc in self.documents]
|
447
|
-
|
448
|
-
return type_as_dict
|
440
|
+
from .serialization import Serialization
|
441
|
+
return Serialization.serialize(self)
|
442
|
+
|
449
443
|
|
450
444
|
@property
|
451
|
-
def json(self):
|
445
|
+
def json(self) -> bytes:
|
452
446
|
"""
|
453
447
|
JSON Representation of the GedcomX Genealogy.
|
454
448
|
|
455
449
|
Returns:
|
456
450
|
str: JSON Representation of the GedcomX Genealogy in the GEDCOM X JSON Serialization Format
|
457
451
|
"""
|
458
|
-
|
459
|
-
|
460
|
-
'sourceDescriptions' : [sourceDescription._as_dict_ for sourceDescription in self.source_descriptions],
|
461
|
-
'relationships': [relationship._as_dict_ for relationship in self.relationships],
|
462
|
-
'agents': [agent._as_dict_ for agent in self.agents],
|
463
|
-
'events': [event._as_dict_ for event in self.events],
|
464
|
-
'places': [place._as_dict_ for place in self.places],
|
465
|
-
'documents': [document._as_dict_ for document in self.documents],
|
466
|
-
}
|
467
|
-
return json.dumps(gedcomx_json, indent=4)
|
452
|
+
return orjson.dumps(self._as_dict,option= orjson.OPT_INDENT_2 | orjson.OPT_APPEND_NEWLINE)
|
453
|
+
|
468
454
|
|
469
455
|
@staticmethod
|
470
456
|
def from_json(data: dict):
|
471
|
-
from .
|
457
|
+
from .serialization import Serialization
|
472
458
|
gx = GedcomX()
|
459
|
+
|
460
|
+
persons = data.get('persons', [])
|
461
|
+
for person in persons:
|
462
|
+
if (person := Person._from_json_(person)) is not None:
|
463
|
+
gx.add_person(person)
|
473
464
|
|
474
465
|
source_descriptions = data.get('sourceDescriptions', [])
|
475
466
|
for source in source_descriptions:
|
476
|
-
|
477
|
-
|
478
|
-
persons = data.get('persons', [])
|
479
|
-
for person in persons:
|
480
|
-
gx.add_person(Serialization.deserialize(person,Person))
|
467
|
+
if (source_description := SourceDescription._from_json_(source)) is not None:
|
468
|
+
gx.add_source_description(source_description)
|
481
469
|
|
482
470
|
relationships = data.get('relationships', [])
|
483
|
-
for
|
484
|
-
|
485
|
-
|
486
|
-
agents = data.get('agents', [])
|
487
|
-
for agent in agents:
|
488
|
-
gx.add_agent(Serialization.deserialize(agent,Agent))
|
471
|
+
for rel in relationships:
|
472
|
+
if (relationship := Relationship._from_json_(rel)) is not None:
|
473
|
+
gx.add_relationship(relationship)
|
489
474
|
|
475
|
+
agents = data.get('agents', [])
|
476
|
+
for agent_data in agents:
|
477
|
+
if (agent := Agent._from_json_(agent_data)) is not None:
|
478
|
+
gx.add_agent(agent)
|
479
|
+
else:
|
480
|
+
raise ValueError()
|
481
|
+
|
490
482
|
events = data.get('events', [])
|
491
|
-
for
|
492
|
-
|
483
|
+
for event_data in events:
|
484
|
+
if (event := Event._from_json_(event_data)) is not None:
|
485
|
+
gx.add_event(event)
|
486
|
+
|
487
|
+
places = data.get('places', [])
|
488
|
+
for place_data in places:
|
489
|
+
if (place := PlaceDescription._from_json_(place_data)) is not None:
|
490
|
+
gx.add_place_description(place)
|
491
|
+
|
492
|
+
documents = data.get('documents', [])
|
493
|
+
for doc_data in documents:
|
494
|
+
if (event := Document._from_json_(event_data)) is not None:
|
495
|
+
pass
|
493
496
|
|
494
497
|
return gx
|
495
498
|
|
gedcomx/gender.py
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
from enum import Enum
|
2
|
+
from typing import List, Optional
|
3
|
+
"""
|
4
|
+
======================================================================
|
5
|
+
Project: Gedcom-X
|
6
|
+
File: gender.py
|
7
|
+
Author: David J. Cartwright
|
8
|
+
Purpose:
|
9
|
+
|
10
|
+
Created: 2025-08-25
|
11
|
+
Updated:
|
12
|
+
- 2025-09-03: _from_json_ refactor
|
13
|
+
|
14
|
+
======================================================================
|
15
|
+
"""
|
16
|
+
|
17
|
+
"""
|
18
|
+
======================================================================
|
19
|
+
GEDCOM Module Types
|
20
|
+
======================================================================
|
21
|
+
"""
|
22
|
+
from .attribution import Attribution
|
23
|
+
from .conclusion import ConfidenceLevel, Conclusion
|
24
|
+
from .Extensions.rs10.rsLink import _rsLinks
|
25
|
+
from .note import Note
|
26
|
+
from .resource import Resource
|
27
|
+
from .source_reference import SourceReference
|
28
|
+
from .logging_hub import hub, logging
|
29
|
+
"""
|
30
|
+
======================================================================
|
31
|
+
Logging
|
32
|
+
======================================================================
|
33
|
+
"""
|
34
|
+
log = logging.getLogger("gedcomx")
|
35
|
+
serial_log = "gedcomx.serialization"
|
36
|
+
#=====================================================================
|
37
|
+
|
38
|
+
|
39
|
+
class GenderType(Enum):
|
40
|
+
Male = "http://gedcomx.org/Male"
|
41
|
+
Female = "http://gedcomx.org/Female"
|
42
|
+
Unknown = "http://gedcomx.org/Unknown"
|
43
|
+
Intersex = "http://gedcomx.org/Intersex"
|
44
|
+
|
45
|
+
@property
|
46
|
+
def description(self):
|
47
|
+
descriptions = {
|
48
|
+
GenderType.Male: "Male gender.",
|
49
|
+
GenderType.Female: "Female gender.",
|
50
|
+
GenderType.Unknown: "Unknown gender.",
|
51
|
+
GenderType.Intersex: "Intersex (assignment at birth)."
|
52
|
+
}
|
53
|
+
return descriptions.get(self, "No description available.")
|
54
|
+
|
55
|
+
class Gender(Conclusion):
|
56
|
+
identifier = 'http://gedcomx.org/v1/Gender'
|
57
|
+
version = 'http://gedcomx.org/conceptual-model/v1'
|
58
|
+
|
59
|
+
def __init__(self,
|
60
|
+
id: Optional[str] = None,
|
61
|
+
lang: Optional[str] = None,
|
62
|
+
sources: Optional[List[SourceReference]] = None,
|
63
|
+
analysis: Optional[Resource] = None,
|
64
|
+
notes: Optional[List[Note]] = None,
|
65
|
+
confidence: Optional[ConfidenceLevel] = None,
|
66
|
+
attribution: Optional[Attribution] = None,
|
67
|
+
type: Optional[GenderType] = None,
|
68
|
+
links: Optional[_rsLinks] = None
|
69
|
+
) -> None:
|
70
|
+
super().__init__(id=id, lang=lang, sources=sources, analysis=analysis, notes=notes, confidence=confidence, attribution=attribution, links=links)
|
71
|
+
self.type = type
|
72
|
+
|
73
|
+
@property
|
74
|
+
def _as_dict_(self):
|
75
|
+
|
76
|
+
type_as_dict = super()._as_dict_ or {}
|
77
|
+
if self.type:
|
78
|
+
type_as_dict['type'] = self.type.value if self.type else None
|
79
|
+
|
80
|
+
return type_as_dict if type_as_dict != {} else None
|
81
|
+
|
82
|
+
|
83
|
+
@classmethod
|
84
|
+
def _from_json_(cls,data,context):
|
85
|
+
conclusion = Conclusion._dict_from_json_(data,context)
|
86
|
+
if (type_ := data.get("type")) is not None:
|
87
|
+
conclusion["type"] = GenderType(type_)
|
88
|
+
|
89
|
+
return cls(**conclusion)
|
90
|
+
|
91
|
+
|
gedcomx/group.py
ADDED
@@ -0,0 +1,72 @@
|
|
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
|
+
from .logging_hub import hub, logging
|
36
|
+
"""
|
37
|
+
======================================================================
|
38
|
+
Logging
|
39
|
+
======================================================================
|
40
|
+
"""
|
41
|
+
log = logging.getLogger("gedcomx")
|
42
|
+
serial_log = "gedcomx.serialization"
|
43
|
+
#=====================================================================
|
44
|
+
|
45
|
+
class GroupRoleType(Enum): #TODO Impliment
|
46
|
+
def __init__(self) -> None:
|
47
|
+
super().__init__()
|
48
|
+
|
49
|
+
class GroupRole: #TODO Impliment
|
50
|
+
identifier = 'http://gedcomx.org/v1/GroupRole'
|
51
|
+
version = 'http://gedcomx.org/conceptual-model/v1'
|
52
|
+
|
53
|
+
def __init__(self, person: Resource,type: Optional[Enum], date: Optional[Date],details: Optional[str]) -> None:
|
54
|
+
pass
|
55
|
+
|
56
|
+
class Group(Subject): #TODO Impliment
|
57
|
+
identifier = 'http://gedcomx.org/v1/Group'
|
58
|
+
version = 'http://gedcomx.org/conceptual-model/v1'
|
59
|
+
|
60
|
+
def __init__(self,
|
61
|
+
id: str | None, lang: str | None,
|
62
|
+
sources: List[SourceReference] | None,
|
63
|
+
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,
|
64
|
+
names: List[TextValue],
|
65
|
+
date: Optional[Date],
|
66
|
+
place: Optional[PlaceReference],
|
67
|
+
roles: Optional[List[GroupRole]]) -> None:
|
68
|
+
super().__init__(id, lang, sources, analysis, notes, confidence, attribution, extracted, evidence, media, identifiers)
|
69
|
+
self.names = names if names else []
|
70
|
+
self.date = date
|
71
|
+
self.place = place
|
72
|
+
self.roles = roles if roles else []
|
@@ -1,18 +1,44 @@
|
|
1
|
-
|
2
|
-
from enum import Enum
|
3
|
-
|
4
1
|
from typing import List, Optional, Dict, Any
|
5
|
-
|
6
2
|
from collections.abc import Iterator
|
7
3
|
import json
|
8
|
-
from .Resource import Resource
|
9
|
-
from .URI import URI
|
10
|
-
from .ExtensibleEnum import ExtensibleEnum
|
11
|
-
|
12
4
|
import secrets
|
13
5
|
import string
|
14
6
|
import json
|
15
7
|
|
8
|
+
"""
|
9
|
+
======================================================================
|
10
|
+
Project: Gedcom-X
|
11
|
+
File: identifier.py
|
12
|
+
Author: David J. Cartwright
|
13
|
+
Purpose:
|
14
|
+
|
15
|
+
Created: 2025-08-25
|
16
|
+
Updated:
|
17
|
+
- 2025-09-03: _from_json_ refactor
|
18
|
+
- 2025-09-04: fixe identifier and identifieList json deserialization
|
19
|
+
|
20
|
+
======================================================================
|
21
|
+
"""
|
22
|
+
|
23
|
+
"""
|
24
|
+
======================================================================
|
25
|
+
GEDCOM Module Types
|
26
|
+
======================================================================
|
27
|
+
"""
|
28
|
+
from .extensible_enum import _EnumItem
|
29
|
+
from .resource import Resource
|
30
|
+
from .uri import URI
|
31
|
+
from .extensible_enum import ExtensibleEnum
|
32
|
+
from .logging_hub import hub, logging
|
33
|
+
"""
|
34
|
+
======================================================================
|
35
|
+
Logging
|
36
|
+
======================================================================
|
37
|
+
"""
|
38
|
+
log = logging.getLogger("gedcomx")
|
39
|
+
serial_log = "gedcomx.serialization"
|
40
|
+
#=====================================================================
|
41
|
+
|
16
42
|
def make_uid(length: int = 10, alphabet: str = string.ascii_letters + string.digits) -> str:
|
17
43
|
"""
|
18
44
|
Generate a cryptographically secure alphanumeric UID.
|
@@ -38,6 +64,7 @@ IdentifierType.register("Deprecated", "http://gedcomx.org/Deprecated")
|
|
38
64
|
IdentifierType.register("Persistent", "http://gedcomx.org/Persistent")
|
39
65
|
IdentifierType.register("External", "https://gedcom.io/terms/v7/EXID")
|
40
66
|
IdentifierType.register("Other", "user provided")
|
67
|
+
IdentifierType.register("ChildAndParentsRelationship","http://familysearch.org/v1/ChildAndParentsRelationship")
|
41
68
|
|
42
69
|
class Identifier:
|
43
70
|
identifier = 'http://gedcomx.org/v1/Identifier'
|
@@ -51,12 +78,12 @@ class Identifier:
|
|
51
78
|
|
52
79
|
@property
|
53
80
|
def _as_dict_(self):
|
54
|
-
from .
|
81
|
+
from .serialization import Serialization
|
55
82
|
type_as_dict = {}
|
56
83
|
if self.values:
|
57
|
-
type_as_dict["value"] =
|
84
|
+
type_as_dict["value"] = None # [v._as_dict_ for v in self.values]
|
58
85
|
if self.type:
|
59
|
-
type_as_dict["type"] = getattr(self.type, "value", self.type) # type: ignore[attr-defined]
|
86
|
+
type_as_dict["type"] = None #getattr(self.type, "value", self.type) # type: ignore[attr-defined]
|
60
87
|
|
61
88
|
return Serialization.serialize_dict(type_as_dict)
|
62
89
|
|
@@ -116,16 +143,13 @@ class IdentifierList:
|
|
116
143
|
self.add_identifier(identifier)
|
117
144
|
else:
|
118
145
|
raise ValueError("append expects an Identifier instance")
|
119
|
-
|
120
|
-
# keep the old name working; point it at the corrected spelling
|
121
|
-
def add_identifer(self, identifier: "Identifier"): # backward-compat alias
|
122
|
-
return self.add_identifier(identifier)
|
123
|
-
|
146
|
+
|
124
147
|
def add_identifier(self, identifier: "Identifier"):
|
125
148
|
"""Add/merge an Identifier (which may contain multiple values)."""
|
126
149
|
if not (identifier and isinstance(identifier, Identifier) and identifier.type):
|
127
150
|
raise ValueError("The 'identifier' must be a valid Identifier instance with a type.")
|
128
|
-
|
151
|
+
if not isinstance(identifier.type,_EnumItem):
|
152
|
+
raise ValueError
|
129
153
|
key = identifier.type.value if hasattr(identifier.type, "value") else str(identifier.type)
|
130
154
|
existing = self.identifiers.get(key, [])
|
131
155
|
merged = self.unique_list(list(existing) + list(identifier.values))
|
@@ -193,12 +217,12 @@ class IdentifierList:
|
|
193
217
|
yield (k, v)
|
194
218
|
|
195
219
|
@classmethod
|
196
|
-
def _from_json_(cls, data):
|
220
|
+
def _from_json_(cls, data,context=None):
|
197
221
|
if isinstance(data, dict):
|
198
222
|
identifier_list = IdentifierList()
|
199
223
|
for key, vals in data.items():
|
200
224
|
# Accept both single value and list in JSON
|
201
|
-
vals =
|
225
|
+
vals = [URI._from_json_(v) for v in vals]
|
202
226
|
identifier_list.add_identifier(
|
203
227
|
Identifier(value=vals, type=IdentifierType(key))
|
204
228
|
)
|
@@ -208,8 +232,11 @@ class IdentifierList:
|
|
208
232
|
|
209
233
|
@property
|
210
234
|
def _as_dict_(self):
|
211
|
-
|
212
|
-
|
235
|
+
type_as_dict = {}
|
236
|
+
for k in self.identifiers.keys():
|
237
|
+
#print(k,self.identifiers[k],type(self.identifiers[k]))
|
238
|
+
type_as_dict[k] = [i._as_dict_ for i in self.identifiers[k]]
|
239
|
+
return type_as_dict if type_as_dict != {} else None
|
213
240
|
|
214
241
|
def __repr__(self) -> str:
|
215
242
|
return json.dumps(self._as_dict_, indent=4)
|
@@ -203,5 +203,24 @@ hub.start_channel(
|
|
203
203
|
make_current=True
|
204
204
|
)
|
205
205
|
|
206
|
+
serial_log = "gedcomx.serialization"
|
207
|
+
deserial_log = "gedcomx.deserialization"
|
208
|
+
|
209
|
+
hub.start_channel(
|
210
|
+
ChannelConfig(
|
211
|
+
name=serial_log,
|
212
|
+
path=f"logs/{serial_log}.log",
|
213
|
+
level=logging.DEBUG,
|
214
|
+
rotation="size:10MB:3", # rotate by size, keep 3 backups
|
215
|
+
))
|
216
|
+
|
217
|
+
hub.start_channel(
|
218
|
+
ChannelConfig(
|
219
|
+
name=deserial_log,
|
220
|
+
path=f"logs/{deserial_log}.log",
|
221
|
+
level=logging.DEBUG,
|
222
|
+
rotation="size:10MB:3", # rotate by size, keep 3 backups
|
223
|
+
))
|
224
|
+
|
206
225
|
# (optional) Also a console channel you can switch to
|
207
226
|
hub.start_channel(ChannelConfig(name="console", path=None, level=logging.DEBUG))
|