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.
@@ -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]] = [],
80
+ rights: Optional[List[Resource]] = [],
53
81
  coverage: Optional[Coverage] = None, # Coverage
54
- descriptions: Optional[List[TextValue]] = [],
55
- identifiers: Optional[List[Identifier]] = [],
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,40 @@ 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)
183
+
156
184
 
157
- data = {
185
+
186
+ type_as_dict = {
158
187
  'id': self.id,
188
+ 'about': self.about._as_dict_ if self.about else None,
159
189
  'resourceType': self.resourceType.value if self.resourceType else None,
160
190
  'citations': [c._as_dict_ for c in self.citations] or None,
161
191
  '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,
192
+ 'mediator': get_resource_as_dict(self.mediator),
193
+ 'publisher': get_resource_as_dict(self.publisher),
194
+ 'authors': [get_resource_as_dict(a) for a in self.authors] or None,
166
195
  'sources': [s._as_dict_ for s in self.sources] or None,
167
- 'analysis': self.analysis._prop_dict() if self.analysis else None,
196
+ 'analysis': self.analysis._as_dict_ if self.analysis else None,
168
197
  '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,
198
+ 'titles': [t._as_dict_ for t in self.titles] or None,
199
+ 'notes': [n._as_dict_ for n in self.notes] or None,
171
200
  'attribution': self.attribution._as_dict_ if self.attribution else None,
172
- 'rights': [r._prop_dict() for r in self.rights] or None,
201
+ 'rights': [r._as_dict_ for r in self.rights] or None,
173
202
  '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
203
+ 'descriptions': [d._as_dict_ for d in self.descriptions] or None,
204
+ 'identifiers': self.identifiers._as_dict_ if self.identifiers else None,
205
+ 'created': self.created if self.created else None,
206
+ 'modified': self.modified if self.modified else None,
207
+ 'published': self.published if self.published else None,
208
+ 'repository': get_resource_as_dict(self.repository),
209
+ 'uri': self.uri.value
180
210
  }
181
- # Remove None values
182
- return {k: v for k, v in data.items() if v is not None}
183
-
211
+
212
+ return Serialization.serialize_dict(type_as_dict)
213
+
214
+
184
215
  @classmethod
185
216
  def _from_json_(cls, data: Dict[str, Any]) -> 'SourceDescription':
186
- print(data,type(data))
187
217
  # TODO Hande Resource/URI
188
218
 
189
219
  # Basic fields
@@ -193,11 +223,11 @@ class SourceDescription:
193
223
  # Sub-objects
194
224
  citations = [SourceCitation._from_json_(c) for c in data.get('citations', [])]
195
225
  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', [])]
226
+ mediator = Resource._from_json_(data['mediator']) if data.get('mediator') else None
227
+ publisher = Resource._from_json_(data['publisher']) if data.get('publisher') else None
228
+ authors = [Resource._from_json_(a) for a in data.get('authors', [])]
199
229
  sources = [SourceReference._from_json_(s) for s in data.get('sources', [])]
200
- analysis = URI._from_json_(data['analysis']) if data.get('analysis') else None
230
+ analysis = Resource._from_json_(data['analysis']) if data.get('analysis') else None
201
231
  component_of = SourceReference._from_json_(data['componentOf']) if data.get('componentOf') else None
202
232
  titles = [TextValue._from_json_(t) for t in data.get('titles', [])]
203
233
  notes = [Note._from_json_(n) for n in data.get('notes', [])]
@@ -205,9 +235,10 @@ class SourceDescription:
205
235
  rights = [URI._from_json_(r) for r in data.get('rights', [])]
206
236
  coverage = [Coverage._from_json_(cvg) for cvg in data.get('coverage',[])]
207
237
  descriptions = [TextValue._from_json_(d) for d in data.get('descriptions', [])]
208
- identifiers = [Identifier._from_json_(i) for i in data.get('identifiers', [])]
238
+ identifiers = IdentifierList._from_json_(data.get('identifiers', []))
239
+
209
240
  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
241
+ modified = data.get('modified',None)
211
242
  published = Date._from_json_(data['published']) if data.get('published') else None
212
243
  repository = Agent._from_json_(data['repository']) if data.get('repository') else None
213
244
 
@@ -224,18 +255,4 @@ class SourceDescription:
224
255
  published=published, repository=repository
225
256
  )
226
257
 
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")
258
+
@@ -3,8 +3,11 @@ from typing import List, Optional
3
3
  from .Attribution import Attribution
4
4
  from .Qualifier import Qualifier
5
5
 
6
+ from .Resource import Resource
6
7
  from .URI import URI
7
8
 
9
+ from collections.abc import Sized
10
+
8
11
  class KnownSourceReference(Qualifier):
9
12
  CharacterRegion = "http://gedcomx.org/CharacterRegion"
10
13
  RectangleRegion = "http://gedcomx.org/RectangleRegion"
@@ -42,13 +45,16 @@ class KnownSourceReference(Qualifier):
42
45
  class SourceReference:
43
46
  identifier = 'http://gedcomx.org/v1/SourceReference'
44
47
  version = 'http://gedcomx.org/conceptual-model/v1'
45
-
48
+
46
49
  def __init__(self,
47
- description: URI, descriptionId: Optional[str] = None, attribution: Optional[Attribution] = None, qualifiers: Optional[List[Qualifier]] = []) -> None:
50
+ description: URI | object | None = None,
51
+ descriptionId: Optional[str] = None,
52
+ attribution: Optional[Attribution] = None,
53
+ qualifiers: Optional[List[Qualifier]] = None
54
+ ) -> None:
48
55
 
49
56
  #if not isinstance(description,URI): raise ValueError(f"description is of type {type(description)}")
50
-
51
- from .SourceDescription import SourceDescription
57
+ '''from .SourceDescription import SourceDescription
52
58
  self._description_object = None
53
59
  if isinstance(description,URI):
54
60
  #TODO See if Local, If not try to resolve,
@@ -60,13 +66,14 @@ class SourceReference:
60
66
  self.description = description._uri
61
67
  else:
62
68
  assert False
63
- self.description = URI(object=description)
69
+ self.description = Resource(object=description)
64
70
  description._uri = self.description
65
71
  description._object = description
66
72
  else:
67
- raise ValueError(f"'description' must be of type 'SourceDescription' or 'URI', type: {type(description)} was provided")
73
+ raise ValueError(f"'description' must be of type 'SourceDescription' or 'URI', type: {type(description)} was provided")'''
68
74
 
69
- #self.description = description
75
+
76
+ self.description = description
70
77
  self.descriptionId = descriptionId
71
78
  self.attribution = attribution
72
79
  self.qualifiers = qualifiers
@@ -101,9 +108,9 @@ class SourceReference:
101
108
 
102
109
  # Only add Relationship-specific fields
103
110
  sourcereference_fields = {
104
- 'description':self._description_object._uri if self._description_object else None,
111
+ 'description':self.description.uri if self.description else None,
105
112
  'descriptionId': self.descriptionId.replace("\n"," ").replace("\r"," ") if self.descriptionId else None,
106
- 'attribution': self.attribution if self.attribution else None,
113
+ 'attribution': self.attribution._as_dict_ if self.attribution else None,
107
114
  'qualifiers':[qualifier.value for qualifier in self.qualifiers ] if self.qualifiers else None
108
115
  }
109
116
 
@@ -112,12 +119,16 @@ class SourceReference:
112
119
  if value is not None:
113
120
  sourcereference_fields[key] = _serialize(value)
114
121
 
115
- return sourcereference_fields
122
+ return {
123
+ k: v
124
+ for k, v in sourcereference_fields.items()
125
+ if v is not None and not (isinstance(v, Sized) and len(v) == 0)
126
+ }
127
+
116
128
 
117
129
  @classmethod
118
130
  def _from_json_(cls, data: dict):
119
- print(data)
120
-
131
+
121
132
  """
122
133
  Rehydrate a SourceReference from the dict form produced by _as_dict_.
123
134
  """
@@ -128,7 +139,7 @@ class SourceReference:
128
139
  #if not desc_json:
129
140
  # raise ValueError("SourceReference JSON missing 'description'")
130
141
  #desc_obj = SourceDescription._from_json_(desc_json)
131
- desc_obj = URI.from_url(data.get('description')) #TODO <--- URI Reference
142
+ desc_obj = URI.from_url_(data.get('description')) #TODO <--- URI Reference
132
143
 
133
144
 
134
145
  # 2) Simple fields
@@ -164,5 +175,5 @@ class SourceReference:
164
175
  return False
165
176
 
166
177
  return (
167
- self.description._uri == other.description._uri
178
+ self.description.uri == other.description.uri
168
179
  )
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 ._Links import _LinkList
12
13
 
13
14
  class Subject(Conclusion):
14
15
  identifier = 'http://gedcomx.org/v1/Subject'
@@ -18,16 +19,17 @@ 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[_LinkList] = 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
@@ -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
gedcomx/URI.py CHANGED
@@ -1,70 +1,104 @@
1
- from typing import Optional
2
- from urllib.parse import urlparse
1
+ from __future__ import annotations
2
+ from dataclasses import dataclass, field
3
+ from typing import Mapping, Optional, Union, Iterable, Tuple
4
+ from urllib.parse import urlsplit, urlunsplit, urlencode, parse_qsl, SplitResult
3
5
 
6
+ _DEFAULT_SCHEME = "gedcomx"
7
+
8
+ @dataclass(frozen=False, slots=True)
4
9
  class URI:
5
- @classmethod
6
- def from_url(cls, url: str) -> 'URI':
7
- parsed_url = urlparse(url)
8
- return cls(
9
- value=url,
10
- scheme=parsed_url.scheme if parsed_url.scheme else 'gedcomx',
11
- authority=parsed_url.netloc,
12
- path=parsed_url.path,
13
- query=parsed_url.query,
14
- fragment=parsed_url.fragment
15
- )
16
-
17
- def parse(value: str) -> None:
18
- """Parse the URI string and populate attributes."""
19
- parsed = urlparse(value)
20
- return URI(scheme=parsed.scheme,authority = parsed.netloc, path = parsed.path, query = parsed.query, fragment = parsed.fragment)
21
-
10
+ scheme: str = field(default=_DEFAULT_SCHEME)
11
+ authority: str = field(default="")
12
+ path: str = field(default="")
13
+ query: str = field(default="")
14
+ fragment: str = field(default="")
22
15
 
23
- def __init__(self, value: Optional[str] = None,
24
- scheme: Optional[str] = None,
25
- authority: Optional[str] = None,
26
- path: Optional[str] = None,
27
- query: Optional[str] = None,
28
- fragment: Optional[str] = None,
29
- object = None) -> None:
30
-
31
- self._scheme = scheme if scheme else 'gedcomx'
32
- self._authority = authority
33
- self._path = path
34
- self._query = query
35
- self._fragment = fragment
36
- self._object = None
16
+ # ---------- constructors ----------
17
+ @classmethod
18
+ def from_url(cls, url: str, *, default_scheme: str = _DEFAULT_SCHEME) -> "URI":
19
+ s = urlsplit(url)
20
+ scheme = s.scheme or default_scheme
21
+ return cls(scheme=scheme, authority=s.netloc, path=s.path, query=s.query, fragment=s.fragment)
37
22
 
38
- if object is not None:
39
- self._object = object
40
- if hasattr(object,'_uri'):
41
- self._object._uri = self
23
+ @classmethod
24
+ def parse(cls, value: str) -> "URI":
25
+ return cls.from_url(value)
42
26
 
27
+ @classmethod
28
+ def from_parts(
29
+ cls,
30
+ *,
31
+ scheme: Optional[str] = None,
32
+ authority: str = "",
33
+ path: str = "",
34
+ query: Union[str, Mapping[str, str], Iterable[Tuple[str, str]]] = "",
35
+ fragment: str = ""
36
+ ) -> "URI":
37
+ q = query if isinstance(query, str) else urlencode(query, doseq=True)
38
+ return cls(scheme=scheme or _DEFAULT_SCHEME, authority=authority, path=path, query=q, fragment=fragment)
43
39
 
40
+ # ---------- views ----------
44
41
  @property
45
- def _uri(self) -> str:
46
- uri = ""
47
- if self._scheme:
48
- uri += f"{self._scheme}://"
49
- if self._authority:
50
- uri += f"{self._authority}/"
51
- if self._path:
52
- uri += f"{self._path}/"
53
- if self._query:
54
- uri += f"?{self._query}"
55
- if self._fragment:
56
- uri += f"#{self._fragment}"
57
- return uri
58
-
59
- @property
60
- def value(self):
61
- return self._uri
62
-
42
+ def value(self) -> str:
43
+ return str(self)
44
+
45
+ def split(self) -> SplitResult:
46
+ return SplitResult(self.scheme, self.authority, self.path, self.query, self.fragment)
47
+
48
+ def __str__(self) -> str:
49
+ return urlunsplit(self.split())
50
+
63
51
  @property
64
- def _as_dict_(self):
65
- return {"Resource":self._uri}
66
-
67
-
52
+ def _as_dict_(self) -> dict:
53
+ # Keeps a simple, explicit structure
54
+ return {
55
+ "scheme": self.scheme,
56
+ "authority": self.authority,
57
+ "path": self.path,
58
+ "query": self.query,
59
+ "fragment": self.fragment,
60
+ "value": str(self),
61
+ }
62
+
63
+ # Accepts {'resource': '...'} or a plain string, mirroring your original
68
64
  @classmethod
69
- def _from_json_(obj,text):
70
- return URI(scheme='NEED TO DEAL WITH URI')
65
+ def from_jsonish(cls, data: Union[str, Mapping[str, str]]) -> "URI":
66
+ if isinstance(data, str):
67
+ return cls.from_url(data)
68
+ if isinstance(data, Mapping):
69
+ raw = data.get("resource") or data.get("value") or ""
70
+ if raw:
71
+ return cls.from_url(raw)
72
+ raise ValueError(f"Cannot build URI from: {data!r}")
73
+
74
+ # ---------- functional updaters ----------
75
+ def with_scheme(self, scheme: str) -> "URI": return self.replace(scheme=scheme)
76
+ def with_authority(self, authority: str) -> "URI": return self.replace(authority=authority)
77
+ def with_path(self, path: str, *, join: bool = False) -> "URI":
78
+ new_path = (self.path.rstrip("/") + "/" + path.lstrip("/")) if join else path
79
+ return self.replace(path=new_path)
80
+ def with_fragment(self, fragment: str | None) -> "URI":
81
+ return self.replace(fragment=(fragment or ""))
82
+ def without_fragment(self) -> "URI": return self.replace(fragment="")
83
+ def with_query(self, query: Union[str, Mapping[str, str], Iterable[Tuple[str, str]]]) -> "URI":
84
+ q = query if isinstance(query, str) else urlencode(query, doseq=True)
85
+ return self.replace(query=q)
86
+ def add_query_params(self, params: Mapping[str, Union[str, Iterable[str]]]) -> "URI":
87
+ existing = parse_qsl(self.query, keep_blank_values=True)
88
+ for k, v in params.items():
89
+ if isinstance(v, str):
90
+ existing.append((k, v))
91
+ else:
92
+ for vv in v:
93
+ existing.append((k, vv))
94
+ return self.replace(query=urlencode(existing, doseq=True))
95
+
96
+ # ---------- helpers ----------
97
+ def replace(self, **kwargs) -> "URI":
98
+ return URI(
99
+ scheme=kwargs.get("scheme", self.scheme or _DEFAULT_SCHEME),
100
+ authority=kwargs.get("authority", self.authority),
101
+ path=kwargs.get("path", self.path),
102
+ query=kwargs.get("query", self.query),
103
+ fragment=kwargs.get("fragment", self.fragment),
104
+ )
gedcomx/Zip.py ADDED
@@ -0,0 +1 @@
1
+ import zipfile
gedcomx/_Links.py ADDED
@@ -0,0 +1,37 @@
1
+ from typing import List, Optional
2
+
3
+ class _Link():
4
+ def __init__(self,category: str, key_val_pairs: dict) -> None:
5
+ self._category = category
6
+ self._kvps: dict = key_val_pairs if key_val_pairs else {}
7
+
8
+ def _add_kvp(self, key, value):
9
+ pass
10
+
11
+ class _LinkList():
12
+ def __init__(self) -> None:
13
+ self.links = {}
14
+
15
+ def add(self,link: _Link):
16
+ if link and isinstance(link,_Link):
17
+ if link._category in self.links.keys():
18
+ self.links[link._category].append(link._kvps)
19
+ else:
20
+ self.links[link._category] = [link._kvps]
21
+
22
+
23
+ @classmethod
24
+ def _from_json_(cls,data: dict):
25
+
26
+ link_list = _LinkList()
27
+ for category in data.keys():
28
+ link_list.add(_Link(category,data[category]))
29
+
30
+ return link_list
31
+
32
+ @property
33
+ def _as_dict_(self) -> dict:
34
+ return self.links
35
+
36
+
37
+
gedcomx/__init__.py CHANGED
@@ -17,7 +17,8 @@ from .Gedcom import Gedcom
17
17
  from .GedcomX import GedcomX, Translater
18
18
  from .Gender import Gender, GenderType
19
19
  from .Group import Group, GroupRole
20
- from .Identifier import Identifier, IdentifierType
20
+ from .Identifier import Identifier, IdentifierType, IdentifierList
21
+ from .Logging import get_logger
21
22
  from .Name import Name, NameForm, NamePart, NamePartType, NameType, NamePartQualifier
22
23
  from .Note import Note
23
24
  from .OnlineAccount import OnlineAccount
@@ -26,13 +27,14 @@ from .PlaceDescription import PlaceDescription
26
27
  from .PlaceReference import PlaceReference
27
28
  from .Qualifier import Qualifier
28
29
  from .Relationship import Relationship, RelationshipType
29
- from .Serialization import serialize_to_dict
30
+ from .Serialization import Serialization
30
31
  from .SourceCitation import SourceCitation
31
32
  from .SourceDescription import SourceDescription
32
33
  from .SourceDescription import ResourceType
33
34
  from .SourceReference import SourceReference
34
35
  from .Subject import Subject
35
36
  from .TextValue import TextValue
37
+ from .Resource import Resource
36
38
  from .URI import URI
37
39
 
38
40