gedcom-x 0.5.1__py3-none-any.whl → 0.5.5__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.
@@ -9,18 +9,23 @@ from .Agent import Agent
9
9
  from .Attribution import Attribution
10
10
  from .Coverage import Coverage
11
11
  from .Date import Date
12
- from .Identifier import Identifier
12
+ from .Identifier import Identifier, IdentifierList
13
13
  from .Note import Note
14
14
  from .SourceCitation import SourceCitation
15
15
  from .SourceReference import SourceReference
16
16
  from .TextValue import TextValue
17
+ from .Resource import Resource, get_resource_as_dict
18
+ from .Serialization import Serialization
17
19
  from .URI import URI
18
20
 
21
+ from collections.abc import Sized
22
+
19
23
  class ResourceType(Enum):
20
24
  Collection = "http://gedcomx.org/Collection"
21
25
  PhysicalArtifact = "http://gedcomx.org/PhysicalArtifact"
22
26
  DigitalArtifact = "http://gedcomx.org/DigitalArtifact"
23
27
  Record = "http://gedcomx.org/Record"
28
+ Person = "http://gedcomx.org/Person"
24
29
 
25
30
  @property
26
31
  def description(self):
@@ -33,26 +38,49 @@ class ResourceType(Enum):
33
38
  return descriptions.get(self, "No description available.")
34
39
 
35
40
  class SourceDescription:
41
+ """
42
+ The SourceDescription data type defines a description of a source of genealogical information.
43
+ http://gedcomx.org/v1/SourceDescription
44
+
45
+ Parameters
46
+ ----------
47
+ id : str
48
+ Unique identifier for this SourceDescription.
49
+ attribution : Attribution Object
50
+ Attribution information for the Genealogy
51
+ filepath : str
52
+ Not Implimented.
53
+ description : str
54
+ Description of the Genealogy: ex. 'My Family Tree'
55
+
56
+ Raises
57
+ ------
58
+ ValueError
59
+ If `id` is not a valid UUID.
60
+ """
36
61
  identifier = "http://gedcomx.org/v1/SourceDescription"
62
+ """
63
+ Gedcom-X Specification Identifier
64
+ """
37
65
 
38
66
  def __init__(self, id: Optional[str] = None,
39
67
  resourceType: Optional[ResourceType] = None,
40
68
  citations: Optional[List[SourceCitation]] = [],
41
69
  mediaType: Optional[str] = None,
42
70
  about: Optional[URI] = None,
43
- mediator: Optional[URI] = None,
44
- publisher: Optional[URI] = None,
45
- authors: Optional[List[URI]] = [],
46
- sources: List[SourceReference] = [], # SourceReference
47
- analysis: Optional[URI] = None, # analysis should be of type 'Document', not specified to avoid circular import
71
+ mediator: Optional[Resource] = None,
72
+ publisher: Optional[Resource|Agent] = None,
73
+ authors: Optional[List[Resource]] = None,
74
+ sources: Optional[List[SourceReference]] = None, # SourceReference
75
+ analysis: Optional[Resource] = None, # analysis should be of type 'Document', not specified to avoid circular import
48
76
  componentOf: Optional[SourceReference] = None, # SourceReference
49
- titles: Optional[List[TextValue]] = [],
50
- notes: Optional[List[Note]] = [],
77
+ titles: Optional[List[TextValue]] = None,
78
+ notes: Optional[List[Note]] = None,
51
79
  attribution: Optional[Attribution] = None,
52
- rights: Optional[List[URI]] = [],
53
- coverage: Optional[Coverage] = None, # Coverage
54
- descriptions: Optional[List[TextValue]] = [],
55
- identifiers: Optional[List[Identifier]] = [],
80
+ rights: Optional[List[Resource]] = [],
81
+ coverage: Optional[List[Coverage]] = None, # Coverage
82
+ descriptions: Optional[List[TextValue]] = None,
83
+ identifiers: Optional[IdentifierList] = None,
56
84
  created: Optional[Date] = None,
57
85
  modified: Optional[Date] = None,
58
86
  published: Optional[Date] = None,
@@ -65,9 +93,9 @@ class SourceDescription:
65
93
  self.mediaType = mediaType
66
94
  self.about = about
67
95
  self.mediator = mediator
68
- self.publisher = publisher
96
+ self._publisher = publisher
69
97
  self.authors = authors or []
70
- self.source_refs = sources or []
98
+ self.sources = sources or []
71
99
  self.analysis = analysis
72
100
  self.componentOf = componentOf
73
101
  self.titles = titles or []
@@ -76,14 +104,30 @@ class SourceDescription:
76
104
  self.rights = rights or []
77
105
  self.coverage = coverage or []
78
106
  self.descriptions = descriptions or []
79
- self.identifiers = identifiers or []
107
+ self.identifiers = identifiers or IdentifierList()
80
108
  self.created = created
81
109
  self.modified = modified
82
110
  self.published = published
83
111
  self.repository = repository
84
112
  self.max_note_count = max_note_count
85
113
 
86
- self._uri = URI(fragment=id)
114
+ self.uri = URI(fragment=id)
115
+
116
+ @property
117
+ def publisher(self) -> Resource | Agent | None:
118
+ return self._publisher
119
+
120
+
121
+ @publisher.setter
122
+ def publisher(self, value: Resource | Agent):
123
+ if value is None:
124
+ self._publisher = None
125
+ elif isinstance(value,Resource):
126
+ self._publisher = value
127
+ elif isinstance(value,Agent):
128
+ self._publisher = value
129
+ else:
130
+ raise ValueError(f"'publisher' must be of type 'URI' or 'Agent', type: {type(value)} was provided")
87
131
 
88
132
  def add_description(self, desccription_to_add: TextValue):
89
133
  if desccription_to_add and isinstance(desccription_to_add,TextValue):
@@ -94,9 +138,6 @@ class SourceDescription:
94
138
 
95
139
  def add_identifier(self, identifier_to_add: Identifier):
96
140
  if identifier_to_add and isinstance(identifier_to_add,Identifier):
97
- for current_identifier in self.identifiers:
98
- if identifier_to_add == current_identifier:
99
- return
100
141
  self.identifiers.append(identifier_to_add)
101
142
 
102
143
  def add_note(self,note_to_add: Note):
@@ -139,51 +180,37 @@ class SourceDescription:
139
180
 
140
181
  @property
141
182
  def _as_dict_(self) -> Dict[str, Any]:
142
- def _serialize(val: Any) -> Any:
143
- if hasattr(val, '_as_dict_'):
144
- return val._as_dict_
145
- if isinstance(val, URI):
146
- return val._prop_dict()
147
- if isinstance(val, Enum):
148
- return val.value
149
- if isinstance(val, (str, int, float, bool)) or val is None:
150
- return val
151
- if isinstance(val, list):
152
- return [_serialize(v) for v in val]
153
- if isinstance(val, dict):
154
- return {k: _serialize(v) for k, v in val.items()}
155
- return str(val)
156
-
157
- data = {
183
+ type_as_dict = {
158
184
  'id': self.id,
185
+ 'about': self.about._as_dict_ if self.about else None,
159
186
  'resourceType': self.resourceType.value if self.resourceType else None,
160
187
  'citations': [c._as_dict_ for c in self.citations] or None,
161
188
  'mediaType': self.mediaType,
162
- 'about': self.about._prop_dict() if self.about else None,
163
- 'mediator': self.mediator._prop_dict() if self.mediator else None,
164
- 'publisher': self.publisher._prop_dict() if self.publisher else None,
165
- 'authors': [a._prop_dict() for a in self.authors] or None,
189
+ 'mediator': self.mediator._as_dict_ if self.mediator else None,
190
+ 'publisher': self.publisher._as_dict_ if self.publisher else None,
191
+ 'authors': self.authors and [a._as_dict_ for a in self.authors] or None,
166
192
  'sources': [s._as_dict_ for s in self.sources] or None,
167
- 'analysis': self.analysis._prop_dict() if self.analysis else None,
193
+ 'analysis': self.analysis._as_dict_ if self.analysis else None,
168
194
  'componentOf': self.componentOf._as_dict_ if self.componentOf else None,
169
- 'titles': [t._prop_dict() for t in self.titles] or None,
170
- 'notes': [n._prop_dict() for n in self.notes] or None,
195
+ 'titles': [t._as_dict_ for t in self.titles] or None,
196
+ 'notes': [n._as_dict_ for n in self.notes] or None,
171
197
  'attribution': self.attribution._as_dict_ if self.attribution else None,
172
- 'rights': [r._prop_dict() for r in self.rights] or None,
173
- 'coverage': self.coverage._as_dict_ if self.coverage else None,
174
- 'descriptions': [d._prop_dict() for d in self.descriptions] or None,
175
- 'identifiers': [i._as_dict_ for i in self.identifiers] or None,
176
- 'created': self.created._prop_dict() if self.created else None,
177
- 'modified': self.modified._prop_dict() if self.modified else None,
178
- 'published': self.published._prop_dict() if self.published else None,
179
- 'repository': self.repository._prop_dict() if self.repository else None
198
+ 'rights': [r._as_dict_ for r in self.rights] or None,
199
+ 'coverage': [c._as_dict_ for c in self.coverage] or None,
200
+ 'descriptions': [d._as_dict_ for d in self.descriptions] or None,
201
+ 'identifiers': self.identifiers._as_dict_ if self.identifiers else None,
202
+ 'created': self.created if self.created else None,
203
+ 'modified': self.modified if self.modified else None,
204
+ 'published': self.published if self.published else None,
205
+ 'repository': self.repository._as_dict_ if self.repository else None,
206
+ 'uri': self.uri.value
180
207
  }
181
- # Remove None values
182
- return {k: v for k, v in data.items() if v is not None}
183
-
208
+
209
+ return Serialization.serialize_dict(type_as_dict)
210
+
211
+
184
212
  @classmethod
185
213
  def _from_json_(cls, data: Dict[str, Any]) -> 'SourceDescription':
186
- print(data,type(data))
187
214
  # TODO Hande Resource/URI
188
215
 
189
216
  # Basic fields
@@ -193,11 +220,11 @@ class SourceDescription:
193
220
  # Sub-objects
194
221
  citations = [SourceCitation._from_json_(c) for c in data.get('citations', [])]
195
222
  about = URI._from_json_(data['about']) if data.get('about') else None
196
- mediator = URI._from_json_(data['mediator']) if data.get('mediator') else None
197
- publisher = Agent._from_json_(data['publisher']) if data.get('publisher') else None
198
- authors = [URI._from_json_(a) for a in data.get('authors', [])]
223
+ mediator = Resource._from_json_(data['mediator']) if data.get('mediator') else None
224
+ publisher = Resource._from_json_(data['publisher']) if data.get('publisher') else None
225
+ authors = [Resource._from_json_(a) for a in data.get('authors', [])]
199
226
  sources = [SourceReference._from_json_(s) for s in data.get('sources', [])]
200
- analysis = URI._from_json_(data['analysis']) if data.get('analysis') else None
227
+ analysis = Resource._from_json_(data['analysis']) if data.get('analysis') else None
201
228
  component_of = SourceReference._from_json_(data['componentOf']) if data.get('componentOf') else None
202
229
  titles = [TextValue._from_json_(t) for t in data.get('titles', [])]
203
230
  notes = [Note._from_json_(n) for n in data.get('notes', [])]
@@ -205,9 +232,10 @@ class SourceDescription:
205
232
  rights = [URI._from_json_(r) for r in data.get('rights', [])]
206
233
  coverage = [Coverage._from_json_(cvg) for cvg in data.get('coverage',[])]
207
234
  descriptions = [TextValue._from_json_(d) for d in data.get('descriptions', [])]
208
- identifiers = [Identifier._from_json_(i) for i in data.get('identifiers', [])]
235
+ identifiers = IdentifierList._from_json_(data.get('identifiers', []))
236
+
209
237
  created = Date._from_json_(data['created']) if data.get('created') else None
210
- modified = Date._from_json_(data['modified']) if data.get('modified') else None
238
+ modified = data.get('modified',None)
211
239
  published = Date._from_json_(data['published']) if data.get('published') else None
212
240
  repository = Agent._from_json_(data['repository']) if data.get('repository') else None
213
241
 
@@ -224,18 +252,4 @@ class SourceDescription:
224
252
  published=published, repository=repository
225
253
  )
226
254
 
227
- @property
228
- def publisher(self) -> URI:
229
- if not self._publisher is None or isinstance(self._publisher,URI): assert False
230
- return self._publisher
231
-
232
- @publisher.setter
233
- def publisher(self, value: URI | Agent):
234
- if value is None:
235
- self._publisher = None
236
- elif isinstance(value,URI):
237
- assert False
238
- elif isinstance(value,Agent):
239
- self._publisher = value._uri
240
- else:
241
- raise ValueError(f"'publisher' must be of type 'URI' or 'Agent', type: {type(value)} was provided")
255
+
@@ -3,8 +3,12 @@ from typing import List, Optional
3
3
  from .Attribution import Attribution
4
4
  from .Qualifier import Qualifier
5
5
 
6
+ from .Resource import Resource
7
+ from .Serialization import Serialization
6
8
  from .URI import URI
7
9
 
10
+ from collections.abc import Sized
11
+
8
12
  class KnownSourceReference(Qualifier):
9
13
  CharacterRegion = "http://gedcomx.org/CharacterRegion"
10
14
  RectangleRegion = "http://gedcomx.org/RectangleRegion"
@@ -42,38 +46,29 @@ class KnownSourceReference(Qualifier):
42
46
  class SourceReference:
43
47
  identifier = 'http://gedcomx.org/v1/SourceReference'
44
48
  version = 'http://gedcomx.org/conceptual-model/v1'
45
-
49
+
46
50
  def __init__(self,
47
- description: URI, descriptionId: Optional[str] = None, attribution: Optional[Attribution] = None, qualifiers: Optional[List[Qualifier]] = []) -> None:
51
+ description: URI | object | None = None,
52
+ descriptionId: Optional[str] = None,
53
+ attribution: Optional[Attribution] = None,
54
+ qualifiers: Optional[List[Qualifier]] = None
55
+ ) -> None:
48
56
 
49
- #if not isinstance(description,URI): raise ValueError(f"description is of type {type(description)}")
50
-
51
- from .SourceDescription import SourceDescription
52
- self._description_object = None
53
- if isinstance(description,URI):
54
- #TODO See if Local, If not try to resolve,
55
- self._description_object = description
56
-
57
- elif isinstance(description,SourceDescription):
58
- self._description_object = description
59
- if hasattr(description,'_uri'):
60
- self.description = description._uri
61
- else:
62
- assert False
63
- self.description = URI(object=description)
64
- description._uri = self.description
65
- description._object = description
66
- else:
67
- raise ValueError(f"'description' must be of type 'SourceDescription' or 'URI', type: {type(description)} was provided")
68
-
69
- #self.description = description
57
+ self.description = description
70
58
  self.descriptionId = descriptionId
71
59
  self.attribution = attribution
72
- self.qualifiers = qualifiers
60
+ self.qualifiers = qualifiers if qualifiers and isinstance(qualifiers, list) else []
73
61
 
74
62
  def add_qualifier(self, qualifier: Qualifier):
75
- if isinstance(qualifier, Qualifier):
63
+ if isinstance(qualifier, (Qualifier,KnownSourceReference)):
64
+ if self.qualifiers:
65
+ #TODO Prevent Duplicates
66
+ for current_qualifier in self.qualifiers:
67
+ if qualifier == current_qualifier:
68
+ return
76
69
  self.qualifiers.append(qualifier)
70
+ return
71
+ raise ValueError("The 'qualifier' must be type 'Qualifier' or 'KnownSourceReference', not " + str(type(qualifier)))
77
72
 
78
73
  def append(self, text_to_add: str):
79
74
  if text_to_add and isinstance(text_to_add, str):
@@ -86,83 +81,33 @@ class SourceReference:
86
81
 
87
82
  @property
88
83
  def _as_dict_(self):
89
-
90
- def _serialize(value):
91
- if isinstance(value, (str, int, float, bool, type(None))):
92
- return value
93
- elif isinstance(value, dict):
94
- return {k: _serialize(v) for k, v in value.items()}
95
- elif isinstance(value, (list, tuple, set)):
96
- return [_serialize(v) for v in value]
97
- elif hasattr(value, "_as_dict_"):
98
- return value._as_dict_
99
- else:
100
- return str(value) # fallback for unknown objects
101
-
102
84
  # Only add Relationship-specific fields
103
- sourcereference_fields = {
104
- 'description':self._description_object._uri if self._description_object else None,
85
+ type_as_dict = {
86
+ 'description':self.description._as_dict_ if self.description else None,
105
87
  'descriptionId': self.descriptionId.replace("\n"," ").replace("\r"," ") if self.descriptionId else None,
106
- 'attribution': self.attribution if self.attribution else None,
88
+ 'attribution': self.attribution._as_dict_ if self.attribution else None,
107
89
  'qualifiers':[qualifier.value for qualifier in self.qualifiers ] if self.qualifiers else None
108
90
  }
91
+ return Serialization.serialize_dict(type_as_dict)
109
92
 
110
- # Serialize and exclude None values
111
- for key, value in sourcereference_fields.items():
112
- if value is not None:
113
- sourcereference_fields[key] = _serialize(value)
114
-
115
- return sourcereference_fields
93
+
94
+
116
95
 
117
96
  @classmethod
118
- def _from_json_(cls, data: dict):
119
- print(data)
120
-
97
+ def _from_json_(cls, data: dict):
121
98
  """
122
- Rehydrate a SourceReference from the dict form produced by _as_dict_.
99
+ Rehydrate a SourceReference from the dict passed down from JSON deserialization.
100
+ NOTE: This does not resolve references to SourceDescription objects.
123
101
  """
124
- from .SourceDescription import SourceDescription
125
-
126
- # 1) Reconstruct the SourceDescription object
127
- desc_json = data.get('description')
128
- #if not desc_json:
129
- # raise ValueError("SourceReference JSON missing 'description'")
130
- #desc_obj = SourceDescription._from_json_(desc_json)
131
- desc_obj = URI.from_url(data.get('description')) #TODO <--- URI Reference
132
-
133
-
134
- # 2) Simple fields
135
- description_id = data.get('descriptionId')
136
-
137
- # 3) Attribution (if present)
138
- attrib = None
139
- if data.get('attribution') is not None:
140
- attrib = Attribution._from_json_(data['attribution'])
102
+ from .Serialization import Serialization
103
+ return Serialization.deserialize(data, SourceReference)
104
+
141
105
 
142
- # 4) Qualifiers list
143
- raw_quals = data.get('qualifiers', [])
144
- qualifiers: List[Qualifier] = []
145
- for q in raw_quals:
146
- try:
147
- # Try the known‐source enum first
148
- qualifiers.append(KnownSourceReference(q))
149
- except ValueError:
150
- # Fallback to generic Qualifier
151
- qualifiers.append(Qualifier(q))
152
-
153
- # 5) Instantiate via your existing __init__
154
- inst = cls(
155
- description=desc_obj,
156
- descriptionId=description_id,
157
- attribution=attrib,
158
- qualifiers=qualifiers
159
- )
160
- return inst
161
106
 
162
107
  def __eq__(self, other):
163
108
  if not isinstance(other, self.__class__):
164
109
  return False
165
110
 
166
111
  return (
167
- self.description._uri == other.description._uri
112
+ self.description.uri == other.description.uri
168
113
  )
gedcomx/Subject.py CHANGED
@@ -4,11 +4,12 @@ from typing import List, Optional
4
4
  from .Attribution import Attribution
5
5
  from .Conclusion import Conclusion, ConfidenceLevel
6
6
  from .EvidenceReference import EvidenceReference
7
- from .Identifier import Identifier
7
+ from .Identifier import Identifier, IdentifierList
8
8
  from .Note import Note
9
- from .Serialization import serialize_to_dict
9
+ from .Serialization import Serialization
10
10
  from .SourceReference import SourceReference
11
- from .URI import URI
11
+ from .Resource import Resource
12
+ from .Extensions.rs10.rsLink import _rsLinkList
12
13
 
13
14
  class Subject(Conclusion):
14
15
  identifier = 'http://gedcomx.org/v1/Subject'
@@ -18,22 +19,23 @@ class Subject(Conclusion):
18
19
  id: Optional[str],
19
20
  lang: Optional[str] = 'en',
20
21
  sources: Optional[List[SourceReference]] = [],
21
- analysis: Optional[URI] = None,
22
+ analysis: Optional[Resource] = None,
22
23
  notes: Optional[List[Note]] = [],
23
24
  confidence: Optional[ConfidenceLevel] = None,
24
25
  attribution: Optional[Attribution] = None,
25
26
  extracted: Optional[bool] = None,
26
27
  evidence: Optional[List[EvidenceReference]] = [],
27
28
  media: Optional[List[SourceReference]] = [],
28
- identifiers: Optional[List[Identifier]] = [],
29
- uri: Optional[URI] = None) -> None:
30
- super().__init__(id, lang, sources, analysis, notes, confidence, attribution)
29
+ identifiers: Optional[IdentifierList] = None,
30
+ uri: Optional[Resource] = None,
31
+ links: Optional[_rsLinkList] = None) -> None:
32
+ super().__init__(id, lang, sources, analysis, notes, confidence, attribution,links=links)
31
33
  self.extracted = extracted
32
34
  self.evidence = evidence
33
35
  self.media = media
34
- self.identifiers = identifiers
36
+ self.identifiers = identifiers if identifiers else IdentifierList()
37
+ self.uri = uri
35
38
 
36
-
37
39
  def add_identifier(self, identifier_to_add: Identifier):
38
40
  if identifier_to_add and isinstance(identifier_to_add,Identifier):
39
41
  for current_identifier in self.identifiers:
@@ -61,7 +63,7 @@ class Subject(Conclusion):
61
63
  "extracted": self.extracted,
62
64
  "evidence": [evidence_ref for evidence_ref in self.evidence] if self.evidence else None,
63
65
  "media": [media for media in self.media] if self.media else None,
64
- "identifiers": [identifier for identifier in self.identifiers] if self.identifiers else None
66
+ "identifiers": self.identifiers._as_dict_ if self.identifiers else None
65
67
 
66
68
  })
67
69
 
gedcomx/TextValue.py CHANGED
@@ -13,7 +13,8 @@ class TextValue:
13
13
  raise ValueError(f"Cannot append object of type {type(value_to_append)}.")
14
14
  self.value += ' ' + value_to_append
15
15
 
16
- def _prop_dict(self):
16
+ @property
17
+ def _as_dict_(self):
17
18
  return {
18
19
  "lang":self.lang if self.lang else None,
19
20
  "value":self.value if self.value else None