gedcom-x 0.5__py3-none-any.whl → 0.5.2__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.
gedcomx/Person.py CHANGED
@@ -3,38 +3,53 @@ from typing import List, Optional
3
3
 
4
4
  from .Attribution import Attribution
5
5
  from .Conclusion import ConfidenceLevel
6
+ from .Date import Date
6
7
  from .EvidenceReference import EvidenceReference
7
- from .Fact import Fact
8
+ from .Fact import Fact, FactType
8
9
  from .Gender import Gender, GenderType
9
- from .Identifier import Identifier
10
- from .Name import Name, NameForm, NamePart, NamePartType, NamePartQualifier
10
+ from .Identifier import IdentifierList
11
+ from .Name import Name
11
12
  from .Note import Note
12
13
  from .SourceReference import SourceReference
13
14
  from .Subject import Subject
14
- from .URI import URI
15
+ from .Resource import Resource
16
+ from collections.abc import Sized
17
+ from ._Links import _LinkList
15
18
 
16
19
  class Person(Subject):
20
+ """A person in the system.
21
+
22
+ Args:
23
+ id (str): Unique identifier for this person.
24
+ name (str): Full name.
25
+ birth (date): Birth date (YYYY-MM-DD).
26
+ friends (List[Person], optional): List of friends. Defaults to None.
27
+
28
+ Raises:
29
+ ValueError: If `id` is not a valid UUID.
30
+ """
17
31
  identifier = 'http://gedcomx.org/v1/Person'
18
32
  version = 'http://gedcomx.org/conceptual-model/v1'
19
33
 
20
- def __init__(self, id: str = None,
34
+ def __init__(self, id: str | None = None,
21
35
  lang: str = 'en',
22
36
  sources: Optional[List[SourceReference]] = None,
23
- analysis: Optional[URI] = None,
37
+ analysis: Optional[Resource] = None,
24
38
  notes: Optional[List[Note]] = None,
25
39
  confidence: Optional[ConfidenceLevel] = None,
26
40
  attribution: Optional[Attribution] = None,
27
41
  extracted: bool = None,
28
42
  evidence: Optional[List[EvidenceReference]] = None,
29
43
  media: Optional[List[SourceReference]] = None,
30
- identifiers: Optional[List[Identifier]] = None,
44
+ identifiers: Optional[IdentifierList] = None,
31
45
  private: Optional[bool] = False,
32
46
  gender: Optional[Gender] = Gender(type=GenderType.Unknown),
33
47
  names: Optional[List[Name]] = None,
34
48
  facts: Optional[List[Fact]] = None,
35
- living: Optional[bool] = False) -> None:
49
+ living: Optional[bool] = False,
50
+ links: Optional[_LinkList] = None) -> None:
36
51
  # Call superclass initializer if needed
37
- super().__init__(id, lang, sources, analysis, notes, confidence, attribution, extracted, evidence, media, identifiers)
52
+ super().__init__(id, lang, sources, analysis, notes, confidence, attribution, extracted, evidence, media, identifiers,links=links)
38
53
 
39
54
  # Initialize mutable attributes to empty lists if None
40
55
  self.sources = sources if sources is not None else []
@@ -51,8 +66,7 @@ class Person(Subject):
51
66
  self.living = living #TODO This is from familysearch API
52
67
 
53
68
  self._relationships = []
54
-
55
-
69
+
56
70
  def add_fact(self, fact_to_add: Fact) -> bool:
57
71
  if fact_to_add and isinstance(fact_to_add,Fact):
58
72
  for current_fact in self.facts:
@@ -80,6 +94,18 @@ class Person(Subject):
80
94
  else:
81
95
  raise ValueError()
82
96
 
97
+ def display(self):
98
+ display = {
99
+ "ascendancyNumber": "1",
100
+ "deathDate": "from 2001 to 2005",
101
+ "descendancyNumber": "1",
102
+ "gender": self.gender.type,
103
+ "lifespan": "-2005",
104
+ "name": self.names[0].nameForms[0].fullText
105
+ }
106
+
107
+ return display
108
+
83
109
  @property
84
110
  def _as_dict_(self):
85
111
  def _serialize(value):
@@ -99,9 +125,10 @@ class Person(Subject):
99
125
  subject_fields.update({
100
126
  'private': self.private,
101
127
  'living': self.living,
102
- 'gender': self.gender.type.value if self.gender.type else None,
103
- 'names': [name for name in self.names],
104
- 'facts': [fact for fact in self.facts]
128
+ 'gender': self.gender._as_dict_ if self.gender else None,
129
+ 'names': [name._as_dict_ for name in self.names],
130
+ 'facts': [fact for fact in self.facts],
131
+ 'uri': 'uri: ' + self.uri.value
105
132
 
106
133
  })
107
134
 
@@ -110,20 +137,20 @@ class Person(Subject):
110
137
  if value is not None:
111
138
  subject_fields[key] = _serialize(value)
112
139
 
113
- return subject_fields
114
-
115
-
140
+ # 3) merge and filter out None *at the top level*
141
+ return {
142
+ k: v
143
+ for k, v in subject_fields.items()
144
+ if v is not None and not (isinstance(v, Sized) and len(v) == 0)
145
+ }
146
+
147
+
116
148
  @classmethod
117
149
  def _from_json_(cls, data: dict):
118
150
  """
119
151
  Create a Person instance from a JSON-dict (already parsed).
120
152
  """
121
- def ensure_list(value):
122
- if value is None:
123
- return []
124
- if isinstance(value, list):
125
- return value
126
- return [value] # wrap single item in list
153
+
127
154
 
128
155
  # Basic scalar fields
129
156
  id_ = data.get('id')
@@ -134,9 +161,9 @@ class Person(Subject):
134
161
  living = data.get('extracted', False)
135
162
 
136
163
  # Complex singletons
137
- analysis = URI._from_json_(data['analysis']) if data.get('analysis') else None
164
+ analysis = Resource._from_json_(data['analysis']) if data.get('analysis') else None
138
165
  attribution = Attribution._from_json_(data['attribution']) if data.get('attribution') else None
139
- confidence = ConfidenceLevel._from_json_(data['confidence']) if data.get('confidence') else None
166
+ confidence = ConfidenceLevel_from_json_(data['confidence']) if data.get('confidence') else None
140
167
 
141
168
  # Gender (string or dict depending on your JSON)
142
169
  gender_json = data.get('gender')
@@ -147,13 +174,14 @@ class Person(Subject):
147
174
  gender = Gender(type=GenderType(gender_json)) if gender_json else Gender(type=GenderType.Unknown)
148
175
 
149
176
 
150
- sources = [SourceReference._from_json_(o) for o in ensure_list(data.get('sources'))]
151
- notes = [Note._from_json_(o) for o in ensure_list(data.get('notes'))]
152
- evidence = [EvidenceReference._from_json_(o) for o in ensure_list(data.get('evidence'))]
153
- media = [SourceReference._from_json_(o) for o in ensure_list(data.get('media'))]
154
- identifiers = [Identifier._from_json_(o) for o in ensure_list(data.get('identifiers'))]
155
- names = [Name._from_json_(o) for o in ensure_list(data.get('names'))]
156
- facts = [Fact._from_json_(o) for o in ensure_list(data.get('facts'))]
177
+ sources = [SourceReference._from_json_(o) for o in data.get('sources')] if data.get('sources') else None
178
+ notes = [Note._from_json_(o) for o in data.get('notes')] if data.get('notes') else None
179
+ evidence = [EvidenceReference._from_json_(o) for o in data.get('evidence')] if data.get('evidence') else None
180
+ media = [SourceReference._from_json_(o) for o in data.get('media')] if data.get('media') else None
181
+ identifiers = IdentifierList._from_json_(data.get('identifiers')) if data.get('identifiers') else None
182
+ names = [Name._from_json_(o) for o in data.get('names')] if data.get('names') else None
183
+ facts = [Fact._from_json_(o) for o in data.get('facts')] if data.get('facts') else None
184
+ links = _LinkList._from_json_(data.get('links')) if data.get('links') else None
157
185
 
158
186
  # Build the instance
159
187
  inst = cls(
@@ -172,7 +200,34 @@ class Person(Subject):
172
200
  gender = gender,
173
201
  names = names,
174
202
  facts = facts,
175
- living = living
203
+ living = living,
204
+ links = links
176
205
  )
177
206
 
178
207
  return inst
208
+
209
+ class QuickPerson:
210
+ """A GedcomX Person Data Type created with basic information.
211
+
212
+ Underlying GedcomX Types are created for you.
213
+
214
+ Args:
215
+ name (str): Full name.
216
+ birth (date,Optional): Birth date (YYYY-MM-DD).
217
+ death (date, Optional)
218
+
219
+
220
+
221
+ Raises:
222
+ ValueError: If `id` is not a valid UUID.
223
+ """
224
+ def __new__(cls, name: Optional[str], dob: Optional[str], dod: Optional[str]):
225
+ # Build facts from args
226
+ facts = []
227
+ if dob:
228
+ facts.append(Fact(type=FactType.Birth, date=Date(original=dob)))
229
+ if dod:
230
+ facts.append(Fact(type=FactType.Death, date=Date(original=dod)))
231
+
232
+ # Return the different class instance
233
+ return Person(facts=facts, names=[name] if name else None)
@@ -4,13 +4,14 @@ from .Attribution import Attribution
4
4
  from .Conclusion import ConfidenceLevel
5
5
  from .Date import Date
6
6
  from .EvidenceReference import EvidenceReference
7
- from .Identifier import Identifier
7
+ from .Identifier import IdentifierList
8
8
  from .Note import Note
9
9
  from .SourceReference import SourceReference
10
10
  from .TextValue import TextValue
11
- from .URI import URI
12
-
11
+ from .Resource import Resource
12
+ from .Serialization import Serialization
13
13
  from .Subject import Subject
14
+ from .URI import URI
14
15
 
15
16
  class PlaceDescription(Subject):
16
17
  identifier = "http://gedcomx.org/v1/PlaceDescription"
@@ -19,21 +20,21 @@ class PlaceDescription(Subject):
19
20
  def __init__(self, id: str =None,
20
21
  lang: str = 'en',
21
22
  sources: Optional[List[SourceReference]] = [],
22
- analysis: URI = None, notes: Optional[List[Note]] =[],
23
+ analysis: Resource = None, notes: Optional[List[Note]] =[],
23
24
  confidence: ConfidenceLevel = None,
24
25
  attribution: Attribution = None,
25
26
  extracted: bool = None,
26
27
  evidence: List[EvidenceReference] = None,
27
28
  media: List[SourceReference] = [],
28
- identifiers: List[Identifier] = [],
29
+ identifiers: List[IdentifierList] = [],
29
30
  names: List[TextValue] = [],
30
31
  type: Optional[str] = None,
31
32
  place: Optional[URI] = None,
32
- jurisdiction: Optional["PlaceDescription"] = None, # PlaceDescription
33
+ jurisdiction: Optional["Resource | PlaceDescription"] = None, # PlaceDescription
33
34
  latitude: Optional[float] = None,
34
35
  longitude: Optional[float] = None,
35
36
  temporalDescription: Optional[Date] = None,
36
- spatialDescription: Optional[URI] = None,) -> None:
37
+ spatialDescription: Optional[Resource] = None,) -> None:
37
38
  super().__init__(id, lang, sources, analysis, notes, confidence, attribution, extracted, evidence, media, identifiers)
38
39
  self.names = names
39
40
  self.type = type
@@ -44,4 +45,17 @@ class PlaceDescription(Subject):
44
45
  self.temporalDescription = temporalDescription
45
46
  self.spacialDescription = spatialDescription
46
47
 
47
-
48
+ @property
49
+ def _as_dict_(self):
50
+ place_description_dict = super()._as_dict_
51
+ place_description_dict.update({
52
+ "names": [n for n in self.names] if self.names else None,
53
+ "type": self.type if self.type else None,
54
+ "place": self.place._as_dict_ if self.place else None,
55
+ "jurisdiction": self.jurisdiction._as_dict_ if self.jurisdiction else None,
56
+ "latitude": float(self.latitide) if self.latitide else None,
57
+ "longitude": float(self.longitute) if self.longitute else None,
58
+ "temporalDescription": self.temporalDescription if self.temporalDescription else None,
59
+ "spatialDescription": self.spacialDescription._as_dict_ if self.temporalDescription else None
60
+ })
61
+ return Serialization.serialize_dict(place_description_dict)
gedcomx/PlaceReference.py CHANGED
@@ -1,21 +1,23 @@
1
1
  from typing import Optional
2
2
 
3
- from .URI import URI
3
+ from .Resource import Resource
4
+ from .Serialization import Serialization
4
5
 
5
6
  class PlaceReference:
6
7
  identifier = 'http://gedcomx.org/v1/PlaceReference'
7
8
  version = 'http://gedcomx.org/conceptual-model/v1'
8
9
 
9
- def __init__(self, original: Optional[str], descriptionRef: Optional[URI]) -> None:
10
+ def __init__(self, original: Optional[str], descriptionRef: Optional[Resource]) -> None:
10
11
  self.original = original
11
12
  self.descriptionRef = descriptionRef
12
13
 
13
14
  @property
14
15
  def _as_dict_(self):
15
- return {
16
+ place_reference_dict = {
16
17
  'original': self.original,
17
- 'descriptionRef': self.descriptionRef._as_dict_ if self.descriptionRef else None
18
+ 'descriptionRef': self.descriptionRef._as_dict_ if isinstance(self.descriptionRef,Resource) else Resource(target=self.descriptionRef)._as_dict_
18
19
  }
20
+ return Serialization.serialize_dict(place_reference_dict)
19
21
 
20
22
  def ensure_list(val):
21
23
  if val is None:
@@ -25,7 +27,7 @@ def ensure_list(val):
25
27
  # PlaceReference
26
28
  PlaceReference._from_json_ = classmethod(lambda cls, data: PlaceReference(
27
29
  original=data.get('original'),
28
- descriptionRef=URI._from_json_(data['description']) if data.get('description') else None
30
+ descriptionRef=Resource._from_json_(data['description']) if data.get('description') else None
29
31
  ))
30
32
 
31
33
 
gedcomx/Relationship.py CHANGED
@@ -8,9 +8,9 @@ from .Fact import Fact
8
8
  from .Identifier import Identifier
9
9
  from .Note import Note
10
10
  from .Person import Person
11
- from .Serialization import serialize_to_dict
11
+ from .Serialization import Serialization
12
12
  from .SourceReference import SourceReference
13
- from .URI import URI
13
+ from .Resource import Resource
14
14
 
15
15
  from .Subject import Subject
16
16
 
@@ -27,6 +27,16 @@ class RelationshipType(Enum):
27
27
  return descriptions.get(self, "No description available.")
28
28
 
29
29
  class Relationship(Subject):
30
+ """Represents a relationship between two Person(s)
31
+
32
+ Args:
33
+ type (RelationshipType): Type of relationship
34
+ person1 (Person) = First Person in Relationship
35
+ person2 (Person): Second Person in Relationship
36
+
37
+ Raises:
38
+ ValueError: If `id` is not a valid UUID.
39
+ """
30
40
  identifier = 'http://gedcomx.org/v1/Relationship'
31
41
  version = 'http://gedcomx.org/conceptual-model/v1'
32
42
 
@@ -34,7 +44,7 @@ class Relationship(Subject):
34
44
  id: Optional[str] = None,
35
45
  lang: Optional[str] = None,
36
46
  sources: Optional[List[SourceReference]] = None,
37
- analysis: Optional[URI] = None,
47
+ analysis: Optional[Resource] = None,
38
48
  notes: Optional[List[Note]] = None,
39
49
  confidence: Optional[ConfidenceLevel] = None,
40
50
  attribution: Optional[Attribution] = None,
@@ -43,8 +53,8 @@ class Relationship(Subject):
43
53
  media: Optional[List[SourceReference]] = None,
44
54
  identifiers: Optional[List[Identifier]] = None,
45
55
  type: Optional[RelationshipType] = None,
46
- person1: Optional[URI] = None,
47
- person2: Optional[URI] = None,
56
+ person1: Optional[Person | Resource] = None,
57
+ person2: Optional[Person | Resource] = None,
48
58
  facts: Optional[List[Fact]] = None) -> None:
49
59
 
50
60
  # Call superclass initializer if required
@@ -68,8 +78,8 @@ class Relationship(Subject):
68
78
  def _as_dict_(self):
69
79
  return serialize_to_dict(self, {
70
80
  "type": self.type.value if isinstance(self.type, RelationshipType) else self.type,
71
- "person1": self.person1._uri,
72
- "person2": self.person2._uri,
81
+ "person1": self.person1.uri,
82
+ "person2": self.person2.uri,
73
83
  "facts": [fact for fact in self.facts] if self.facts else None
74
84
  })
75
85
 
@@ -92,8 +102,8 @@ class Relationship(Subject):
92
102
  private = data.get('private', None)
93
103
 
94
104
  # Complex singletons (adjust as needed)
95
- person1 = URI.from_url(data.get('person1')['resource']) if data.get('person1') else None
96
- person2 = URI.from_url(data.get('person2')['resource']) if data.get('person2') else None
105
+ person1 = Resource.from_url(data.get('person1')['resource']) if data.get('person1') else None
106
+ person2 = Resource.from_url(data.get('person2')['resource']) if data.get('person2') else None
97
107
  facts = [Fact._from_json_(o) for o in ensure_list(data.get('facts'))]
98
108
  sources = [SourceReference._from_json_(o) for o in ensure_list(data.get('sources'))]
99
109
  notes = [Note._from_json_(o) for o in ensure_list(data.get('notes'))]
gedcomx/Resource.py ADDED
@@ -0,0 +1,61 @@
1
+ from typing import List, Optional
2
+
3
+ from .URI import URI
4
+
5
+ class Resource:
6
+ """
7
+ Class used to track and resolve URIs and references between datastores.
8
+
9
+ Parameters
10
+ ----------
11
+
12
+ Raises
13
+ ------
14
+ ValueError
15
+ If `id` is not a valid UUID.
16
+ """
17
+ def __init__(self,uri: Optional[URI|str] = None, id:Optional[str] = None,top_lvl_object: Optional[object] = None,target= None) -> None:
18
+
19
+ self.resource = URI.from_url(uri.value)
20
+ self.Id = id
21
+
22
+ self.type = None
23
+ self.resolved = False
24
+ self.target: object = target
25
+ self.remote: bool | None = None # is the resource pointed to persitent on a remote datastore?
26
+
27
+ if target:
28
+ self.resource = target._uri
29
+ self.Id = target.id
30
+ self.type = type(target)
31
+
32
+ @property
33
+ def _as_dict_(self):
34
+ return {'resource':self.resource.value,
35
+ 'resourceId':self.Id}
36
+
37
+ def get_resource_as_dict(value):
38
+ """
39
+ If value is truthy:
40
+ - If it's already a Resource, return it.
41
+ - Otherwise, wrap it in Resource using (value._uri, value.id).
42
+ Returns None if value is falsy.
43
+ """
44
+
45
+ if not value:
46
+ return None
47
+
48
+ if isinstance(value, Resource):
49
+ return value._as_dict_
50
+
51
+ try:
52
+ return Resource(
53
+ getattr(value, "uri", None),
54
+ getattr(value, "id", None)
55
+ )._as_dict_
56
+ except AttributeError:
57
+ print('get_resource_as_dict',type(value),value)
58
+ print((f"value: {value} as inproper attributes"))
59
+ exit()
60
+
61
+
gedcomx/Serialization.py CHANGED
@@ -1,5 +1,7 @@
1
1
  from typing import Dict
2
2
 
3
+ from collections.abc import Sized
4
+
3
5
 
4
6
  def _has_parent_class(obj) -> bool:
5
7
  return hasattr(obj, '__class__') and hasattr(obj.__class__, '__bases__') and len(obj.__class__.__bases__) > 0
@@ -34,4 +36,45 @@ def serialize_to_dict(obj,class_values:Dict,ignore_null=True):
34
36
  for key in empty_fields:
35
37
  del values_dict[key]
36
38
 
37
- return values_dict
39
+ return values_dict
40
+
41
+ class Serialization:
42
+
43
+ @staticmethod
44
+ def serialize_dict(dict_to_serialize: dict) -> dict:
45
+ """
46
+ Iterates through the dict, serilaizing all Gedcom Types into a json compatible value
47
+
48
+ Parameters
49
+ ----------
50
+ dict_to_serialize: dict
51
+ dict that has been created from any Gedcom Type Object's _as_dict_ property
52
+
53
+ Raises
54
+ ------
55
+ ValueError
56
+ If `id` is not a valid UUID.
57
+ """
58
+ def _serialize(value):
59
+ if isinstance(value, (str, int, float, bool, type(None))):
60
+ return value
61
+ elif isinstance(value, dict):
62
+ return {k: _serialize(v) for k, v in value.items()}
63
+ elif isinstance(value, (list, tuple, set)):
64
+ return [_serialize(v) for v in value]
65
+ elif hasattr(value, "_as_dict_"):
66
+ return value._as_dict_
67
+ else:
68
+ return str(value) # fallback for unknown objects
69
+
70
+ if dict_to_serialize and isinstance(dict_to_serialize,dict):
71
+ for key, value in dict_to_serialize.items():
72
+ if value is not None:
73
+ dict_to_serialize[key] = _serialize(value)
74
+
75
+ return {
76
+ k: v
77
+ for k, v in dict_to_serialize.items()
78
+ if v is not None and not (isinstance(v, Sized) and len(v) == 0)
79
+ }
80
+ return {}
gedcomx/SourceCitation.py CHANGED
@@ -17,4 +17,9 @@ class SourceCitation:
17
17
  """
18
18
  lang = data.get('lang', 'en')
19
19
  value = data.get('value')
20
- return cls(lang=lang, value=value)
20
+ return cls(lang=lang, value=value)
21
+
22
+ @property
23
+ def _as_dict_(self):
24
+ return {'lang':self.lang,
25
+ 'value': self.value}