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.
Files changed (69) hide show
  1. {gedcom_x-0.5.7.dist-info → gedcom_x-0.5.9.dist-info}/METADATA +1 -1
  2. gedcom_x-0.5.9.dist-info/RECORD +56 -0
  3. gedcomx/Extensions/rs10/rsLink.py +110 -60
  4. gedcomx/TopLevelTypeCollection.py +1 -1
  5. gedcomx/__init__.py +43 -42
  6. gedcomx/address.py +217 -0
  7. gedcomx/{Agent.py → agent.py} +107 -34
  8. gedcomx/attribution.py +115 -0
  9. gedcomx/{Conclusion.py → conclusion.py} +120 -51
  10. gedcomx/{Converter.py → converter.py} +261 -116
  11. gedcomx/coverage.py +64 -0
  12. gedcomx/{Date.py → date.py} +43 -9
  13. gedcomx/{Document.py → document.py} +60 -12
  14. gedcomx/{Event.py → event.py} +88 -31
  15. gedcomx/evidence_reference.py +20 -0
  16. gedcomx/{Fact.py → fact.py} +81 -74
  17. gedcomx/{Gedcom.py → gedcom.py} +10 -0
  18. gedcomx/{Gedcom5x.py → gedcom5x.py} +31 -21
  19. gedcomx/gedcom7/Exceptions.py +9 -0
  20. gedcomx/gedcom7/GedcomStructure.py +94 -0
  21. gedcomx/gedcom7/Specification.py +347 -0
  22. gedcomx/gedcom7/__init__.py +26 -0
  23. gedcomx/gedcom7/g7interop.py +205 -0
  24. gedcomx/gedcom7/gedcom7.py +160 -0
  25. gedcomx/gedcom7/logger.py +19 -0
  26. gedcomx/{GedcomX.py → gedcomx.py} +109 -106
  27. gedcomx/gender.py +91 -0
  28. gedcomx/group.py +72 -0
  29. gedcomx/{Identifier.py → identifier.py} +48 -21
  30. gedcomx/{LoggingHub.py → logging_hub.py} +19 -0
  31. gedcomx/{Mutations.py → mutations.py} +59 -30
  32. gedcomx/{Name.py → name.py} +88 -47
  33. gedcomx/note.py +105 -0
  34. gedcomx/online_account.py +19 -0
  35. gedcomx/{Person.py → person.py} +61 -41
  36. gedcomx/{PlaceDescription.py → place_description.py} +71 -23
  37. gedcomx/{PlaceReference.py → place_reference.py} +32 -10
  38. gedcomx/{Qualifier.py → qualifier.py} +20 -4
  39. gedcomx/relationship.py +156 -0
  40. gedcomx/resource.py +112 -0
  41. gedcomx/serialization.py +794 -0
  42. gedcomx/source_citation.py +37 -0
  43. gedcomx/source_description.py +401 -0
  44. gedcomx/{SourceReference.py → source_reference.py} +56 -21
  45. gedcomx/subject.py +122 -0
  46. gedcomx/textvalue.py +89 -0
  47. gedcomx/{Translation.py → translation.py} +4 -4
  48. gedcomx/uri.py +273 -0
  49. gedcom_x-0.5.7.dist-info/RECORD +0 -49
  50. gedcomx/Address.py +0 -131
  51. gedcomx/Attribution.py +0 -91
  52. gedcomx/Coverage.py +0 -37
  53. gedcomx/EvidenceReference.py +0 -11
  54. gedcomx/Gender.py +0 -65
  55. gedcomx/Group.py +0 -37
  56. gedcomx/Note.py +0 -73
  57. gedcomx/OnlineAccount.py +0 -10
  58. gedcomx/Relationship.py +0 -97
  59. gedcomx/Resource.py +0 -85
  60. gedcomx/Serialization.py +0 -816
  61. gedcomx/SourceCitation.py +0 -25
  62. gedcomx/SourceDescription.py +0 -314
  63. gedcomx/Subject.py +0 -59
  64. gedcomx/TextValue.py +0 -35
  65. gedcomx/URI.py +0 -105
  66. {gedcom_x-0.5.7.dist-info → gedcom_x-0.5.9.dist-info}/WHEEL +0 -0
  67. {gedcom_x-0.5.7.dist-info → gedcom_x-0.5.9.dist-info}/top_level.txt +0 -0
  68. /gedcomx/{Exceptions.py → exceptions.py} +0 -0
  69. /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 typing import Any, Dict, Optional
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
- - 2025-08-31: _as_dict_ to only create entries in dict for fields that hold data,
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 .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 .PlaceDescription import PlaceDescription
38
- from .Relationship import Relationship, RelationshipType
39
- from .Resource import Resource, URI
40
- from .SourceDescription import ResourceType, SourceDescription
41
- from .TextValue import TextValue
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
- ''' #TODO Fix name handling on persons
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
- name_value = name.value if isinstance(name, TextValue) else name
113
- if name_value in self._name_index:
114
- self._name_index[name_value].append(item)
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
- self._name_index[name_value] = [item]
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) -> 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.source_descriptions = TypeCollection(SourceDescription)
269
+ self.sourceDescriptions = TypeCollection(SourceDescription)
270
+ if sourceDescriptions: self.sourceDescriptions.extend(sourceDescriptions)
245
271
  self.persons = TypeCollection(Person)
246
- self.relationships = TypeCollection(Relationship)
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.source_descriptions),
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
- sourceDescription.id =self.default_id_generator()
293
- self.source_descriptions.append(item=sourceDescription)
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
- """Add a Agent object to the Genealogy
361
-
362
- Args:
363
- agent: Agent Object
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
- def source(self,id: str):
400
- filtered = [source for source in self.source_descriptions if getattr(source, 'id') == id]
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.source_descriptions.id_index,
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
- type_as_dict: Dict[str, Any] = {}
422
-
423
- if self.persons and len(self.persons) > 0:
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
- gedcomx_json = {
459
- 'persons': [person._as_dict_ for person in self.persons],
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 .Serialization import Serialization
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
- gx.add_source_description(Serialization.deserialize(source,SourceDescription))
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 relationship in relationships:
484
- gx.add_relationship(Serialization.deserialize(relationship,Relationship))
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 event in events:
492
- gx.add_event(Serialization.deserialize(event,Event))
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 .Serialization import Serialization
81
+ from .serialization import Serialization
55
82
  type_as_dict = {}
56
83
  if self.values:
57
- type_as_dict["value"] = list(self.values) # or [v for v in self.values]
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 = vals if isinstance(vals, list) else [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
- # If you want a *copy*, return `dict(self.identifiers)`
212
- return self.identifiers
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))