gedcom-x 0.5.1__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.
- {gedcom_x-0.5.1.dist-info → gedcom_x-0.5.2.dist-info}/METADATA +1 -1
- gedcom_x-0.5.2.dist-info/RECORD +42 -0
- gedcomx/Address.py +40 -11
- gedcomx/Agent.py +129 -23
- gedcomx/Attribution.py +38 -54
- gedcomx/Conclusion.py +60 -45
- gedcomx/Date.py +49 -8
- gedcomx/Document.py +19 -9
- gedcomx/Event.py +4 -4
- gedcomx/EvidenceReference.py +2 -2
- gedcomx/Exceptions.py +10 -0
- gedcomx/Fact.py +70 -46
- gedcomx/Gedcom.py +110 -37
- gedcomx/GedcomX.py +405 -175
- gedcomx/Gender.py +61 -8
- gedcomx/Group.py +3 -3
- gedcomx/Identifier.py +93 -10
- gedcomx/Logging.py +19 -0
- gedcomx/Name.py +67 -38
- gedcomx/Note.py +5 -4
- gedcomx/OnlineAccount.py +2 -2
- gedcomx/Person.py +88 -33
- gedcomx/PlaceDescription.py +22 -8
- gedcomx/PlaceReference.py +7 -5
- gedcomx/Relationship.py +19 -9
- gedcomx/Resource.py +61 -0
- gedcomx/Serialization.py +44 -1
- gedcomx/SourceCitation.py +6 -1
- gedcomx/SourceDescription.py +89 -72
- gedcomx/SourceReference.py +25 -14
- gedcomx/Subject.py +10 -8
- gedcomx/TextValue.py +2 -1
- gedcomx/URI.py +95 -61
- gedcomx/Zip.py +1 -0
- gedcomx/_Links.py +37 -0
- gedcomx/__init__.py +4 -2
- gedcomx/g7interop.py +205 -0
- gedcom_x-0.5.1.dist-info/RECORD +0 -37
- gedcomx/_Resource.py +0 -11
- {gedcom_x-0.5.1.dist-info → gedcom_x-0.5.2.dist-info}/WHEEL +0 -0
- {gedcom_x-0.5.1.dist-info → gedcom_x-0.5.2.dist-info}/top_level.txt +0 -0
gedcomx/SourceDescription.py
CHANGED
@@ -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[
|
44
|
-
publisher: Optional[
|
45
|
-
authors: Optional[List[
|
46
|
-
sources: List[SourceReference] =
|
47
|
-
analysis: Optional[
|
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[
|
80
|
+
rights: Optional[List[Resource]] = [],
|
53
81
|
coverage: Optional[Coverage] = None, # Coverage
|
54
|
-
descriptions: Optional[List[TextValue]] =
|
55
|
-
identifiers: Optional[
|
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.
|
96
|
+
self._publisher = publisher
|
69
97
|
self.authors = authors or []
|
70
|
-
self.
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
'
|
163
|
-
'
|
164
|
-
'
|
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.
|
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.
|
170
|
-
'notes': [n.
|
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.
|
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.
|
175
|
-
'identifiers':
|
176
|
-
'created': self.created
|
177
|
-
'modified': self.modified
|
178
|
-
'published': self.published
|
179
|
-
'repository': self.repository
|
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
|
-
|
182
|
-
return
|
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 =
|
197
|
-
publisher =
|
198
|
-
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 =
|
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 =
|
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 =
|
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
|
-
|
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
|
+
|
gedcomx/SourceReference.py
CHANGED
@@ -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
|
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 =
|
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
|
-
|
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.
|
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
|
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
|
-
|
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.
|
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.
|
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
|
9
|
+
from .Serialization import Serialization
|
10
10
|
from .SourceReference import SourceReference
|
11
|
-
from .
|
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[
|
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[
|
29
|
-
uri: Optional[
|
30
|
-
|
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":
|
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
|
-
|
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
|
2
|
-
from
|
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
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
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
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
-
|
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
|
70
|
-
|
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
|
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
|
|