gedcom-x 0.5.6__py3-none-any.whl → 0.5.7__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.6.dist-info → gedcom_x-0.5.7.dist-info}/METADATA +1 -1
- gedcom_x-0.5.7.dist-info/RECORD +49 -0
- gedcomx/Address.py +13 -13
- gedcomx/Agent.py +28 -16
- gedcomx/Attribution.py +34 -7
- gedcomx/Conclusion.py +24 -13
- gedcomx/Converter.py +1034 -0
- gedcomx/Coverage.py +7 -6
- gedcomx/Date.py +11 -4
- gedcomx/Document.py +2 -1
- gedcomx/Event.py +95 -20
- gedcomx/Extensions/__init__.py +1 -0
- gedcomx/Extensions/rs10/__init__.py +1 -0
- gedcomx/Extensions/rs10/rsLink.py +116 -0
- gedcomx/Fact.py +16 -13
- gedcomx/Gedcom5x.py +78 -61
- gedcomx/GedcomX.py +182 -1039
- gedcomx/Gender.py +7 -9
- gedcomx/Identifier.py +9 -12
- gedcomx/LoggingHub.py +21 -0
- gedcomx/Mutations.py +8 -8
- gedcomx/Name.py +207 -87
- gedcomx/Note.py +16 -9
- gedcomx/Person.py +39 -18
- gedcomx/PlaceDescription.py +70 -19
- gedcomx/PlaceReference.py +40 -8
- gedcomx/Qualifier.py +39 -12
- gedcomx/Relationship.py +5 -3
- gedcomx/Resource.py +38 -28
- gedcomx/Serialization.py +773 -358
- gedcomx/SourceDescription.py +133 -74
- gedcomx/SourceReference.py +10 -9
- gedcomx/Subject.py +5 -21
- gedcomx/Translation.py +976 -1
- gedcomx/URI.py +1 -1
- gedcomx/__init__.py +3 -2
- gedcom_x-0.5.6.dist-info/RECORD +0 -45
- {gedcom_x-0.5.6.dist-info → gedcom_x-0.5.7.dist-info}/WHEEL +0 -0
- {gedcom_x-0.5.6.dist-info → gedcom_x-0.5.7.dist-info}/top_level.txt +0 -0
gedcomx/Gender.py
CHANGED
@@ -9,7 +9,7 @@ from gedcomx.Resource import Resource
|
|
9
9
|
|
10
10
|
from .Conclusion import Conclusion
|
11
11
|
from .Qualifier import Qualifier
|
12
|
-
|
12
|
+
|
13
13
|
|
14
14
|
from collections.abc import Sized
|
15
15
|
|
@@ -35,7 +35,7 @@ class Gender(Conclusion):
|
|
35
35
|
|
36
36
|
def __init__(self,
|
37
37
|
id: Optional[str] = None,
|
38
|
-
lang: Optional[str] =
|
38
|
+
lang: Optional[str] = None,
|
39
39
|
sources: Optional[List[SourceReference]] = None,
|
40
40
|
analysis: Optional[Resource] = None,
|
41
41
|
notes: Optional[List[Note]] = None,
|
@@ -48,19 +48,17 @@ class Gender(Conclusion):
|
|
48
48
|
|
49
49
|
@property
|
50
50
|
def _as_dict_(self):
|
51
|
+
from .Serialization import Serialization
|
52
|
+
type_as_dict = super()._as_dict_
|
53
|
+
if self.type:
|
54
|
+
type_as_dict['type'] = self.type.value if self.type else None
|
51
55
|
|
52
|
-
|
53
|
-
type_as_dict = super()._as_dict_ # Start with base class fields
|
54
|
-
# Only add Relationship-specific fields
|
55
|
-
type_as_dict.update({
|
56
|
-
'type':self.type.value if self.type else None
|
57
|
-
|
58
|
-
})
|
59
56
|
|
60
57
|
return Serialization.serialize_dict(type_as_dict)
|
61
58
|
|
62
59
|
@classmethod
|
63
60
|
def _from_json_(cls,data):
|
61
|
+
from .Serialization import Serialization
|
64
62
|
|
65
63
|
return Serialization.deserialize(data, Gender)
|
66
64
|
|
gedcomx/Identifier.py
CHANGED
@@ -26,11 +26,12 @@ def make_uid(length: int = 10, alphabet: str = string.ascii_letters + string.dig
|
|
26
26
|
"""
|
27
27
|
if length <= 0:
|
28
28
|
raise ValueError("length must be > 0")
|
29
|
-
return ''.join(secrets.choice(alphabet) for _ in range(length))
|
29
|
+
return ''.join(secrets.choice(alphabet) for _ in range(length)).upper()
|
30
30
|
|
31
31
|
class IdentifierType(ExtensibleEnum):
|
32
|
-
"""Enumeration of identifier types."""
|
33
32
|
pass
|
33
|
+
|
34
|
+
"""Enumeration of identifier types."""
|
34
35
|
IdentifierType.register("Primary", "http://gedcomx.org/Primary")
|
35
36
|
IdentifierType.register("Authority", "http://gedcomx.org/Authority")
|
36
37
|
IdentifierType.register("Deprecated", "http://gedcomx.org/Deprecated")
|
@@ -42,7 +43,7 @@ class Identifier:
|
|
42
43
|
identifier = 'http://gedcomx.org/v1/Identifier'
|
43
44
|
version = 'http://gedcomx.org/conceptual-model/v1'
|
44
45
|
|
45
|
-
def __init__(self, value: Optional[List[URI]], type: Optional[IdentifierType] = IdentifierType.Primary) -> None:
|
46
|
+
def __init__(self, value: Optional[List[URI]], type: Optional[IdentifierType] = IdentifierType.Primary) -> None: # type: ignore
|
46
47
|
if not isinstance(value,list):
|
47
48
|
value = [value] if value else []
|
48
49
|
self.type = type
|
@@ -51,11 +52,11 @@ class Identifier:
|
|
51
52
|
@property
|
52
53
|
def _as_dict_(self):
|
53
54
|
from .Serialization import Serialization
|
54
|
-
type_as_dict = {
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
55
|
+
type_as_dict = {}
|
56
|
+
if self.values:
|
57
|
+
type_as_dict["value"] = list(self.values) # or [v for v in self.values]
|
58
|
+
if self.type:
|
59
|
+
type_as_dict["type"] = getattr(self.type, "value", self.type) # type: ignore[attr-defined]
|
59
60
|
|
60
61
|
return Serialization.serialize_dict(type_as_dict)
|
61
62
|
|
@@ -64,10 +65,6 @@ class Identifier:
|
|
64
65
|
"""
|
65
66
|
Construct an Identifier from a dict parsed from JSON.
|
66
67
|
"""
|
67
|
-
#for name, member in IdentifierType.__members__.items():
|
68
|
-
# print(name)
|
69
|
-
|
70
|
-
|
71
68
|
|
72
69
|
for key in data.keys():
|
73
70
|
type = key
|
gedcomx/LoggingHub.py
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
from __future__ import annotations
|
3
3
|
import logging
|
4
4
|
import contextvars
|
5
|
+
import os
|
5
6
|
from contextlib import contextmanager
|
6
7
|
from dataclasses import dataclass
|
7
8
|
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
|
@@ -184,3 +185,23 @@ class LoggingHub:
|
|
184
185
|
# -------- Utilities --------
|
185
186
|
def set_default_channel(self, name: str) -> None:
|
186
187
|
self._dispatch.set_default_channel(name)
|
188
|
+
|
189
|
+
hub = LoggingHub("gedcomx") # this becomes your app logger root
|
190
|
+
hub.init_root() # do this ONCE at startup
|
191
|
+
|
192
|
+
os.makedirs("logs", exist_ok=True)
|
193
|
+
|
194
|
+
# 1) Start a default channel (file w/ daily rotation)
|
195
|
+
hub.start_channel(
|
196
|
+
ChannelConfig(
|
197
|
+
name="default",
|
198
|
+
path="logs/app.log",
|
199
|
+
level=logging.INFO,
|
200
|
+
fmt="[%(asctime)s] %(levelname)s %(log_channel)s %(name)s: %(message)s",
|
201
|
+
rotation="time:midnight:7",
|
202
|
+
),
|
203
|
+
make_current=True
|
204
|
+
)
|
205
|
+
|
206
|
+
# (optional) Also a console channel you can switch to
|
207
|
+
hub.start_channel(ChannelConfig(name="console", path=None, level=logging.DEBUG))
|
gedcomx/Mutations.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
from .Gedcom5x import
|
1
|
+
from .Gedcom5x import Gedcom5xRecord
|
2
2
|
from .Fact import Fact, FactType
|
3
3
|
from .Event import Event, EventType
|
4
4
|
|
@@ -178,13 +178,13 @@ fact_event_table = {
|
|
178
178
|
}
|
179
179
|
|
180
180
|
class GedcomXObject:
|
181
|
-
def __init__(self,record:
|
182
|
-
self.created_with_tag: str = record.tag if record and isinstance(record,
|
183
|
-
self.created_at_level: int = record.level if record and isinstance(record,
|
184
|
-
self.created_at_line_number: int = record.line_number if record and isinstance(record,
|
181
|
+
def __init__(self,record: Gedcom5xRecord | None = None) -> None:
|
182
|
+
self.created_with_tag: str = record.tag if record and isinstance(record, Gedcom5xRecord) else None
|
183
|
+
self.created_at_level: int = record.level if record and isinstance(record, Gedcom5xRecord) else None
|
184
|
+
self.created_at_line_number: int = record.line_number if record and isinstance(record, Gedcom5xRecord) else None
|
185
185
|
|
186
186
|
class GedcomXSourceOrDocument(GedcomXObject):
|
187
|
-
def __init__(self,record:
|
187
|
+
def __init__(self,record: Gedcom5xRecord | None = None) -> None:
|
188
188
|
super().__init__(record)
|
189
189
|
self.title: str = None
|
190
190
|
self.citation: str = None
|
@@ -204,7 +204,7 @@ class GedcomXSourceOrDocument(GedcomXObject):
|
|
204
204
|
self.description: str = None
|
205
205
|
|
206
206
|
class GedcomXEventOrFact(GedcomXObject):
|
207
|
-
def __new__(cls,record:
|
207
|
+
def __new__(cls,record: Gedcom5xRecord | None = None, object_stack: dict | None = None) -> object:
|
208
208
|
super().__init__(record)
|
209
209
|
if record.tag in fact_event_table.keys():
|
210
210
|
|
@@ -219,7 +219,7 @@ class GedcomXEventOrFact(GedcomXObject):
|
|
219
219
|
raise ValueError(f"{record.tag} not found in map")
|
220
220
|
|
221
221
|
class GedcomXRelationshipBuilder(GedcomXObject):
|
222
|
-
def __new__(cls,record:
|
222
|
+
def __new__(cls,record: Gedcom5xRecord | None = None, object_stack: dict | None = None) -> object:
|
223
223
|
last_relationship = object_stack.get('lastrelationship',None)
|
224
224
|
last_relationship_data = object_stack.get('lastrelationshipdata',None)
|
225
225
|
if not isinstance(last_relationship_data,dict):
|
gedcomx/Name.py
CHANGED
@@ -2,13 +2,33 @@ from enum import Enum
|
|
2
2
|
from typing import List,Optional
|
3
3
|
from typing_extensions import Self
|
4
4
|
|
5
|
+
"""
|
6
|
+
======================================================================
|
7
|
+
Project: Gedcom-X
|
8
|
+
File: Name.py
|
9
|
+
Author: David J. Cartwright
|
10
|
+
Purpose: Python Object representation of GedcomX Name, NameType, NameForm, NamePart Types
|
11
|
+
|
12
|
+
Created: 2025-08-25
|
13
|
+
Updated:
|
14
|
+
- 2025-08-31: _as_dict_ to only create entries in dict for fields that hold data
|
15
|
+
|
16
|
+
======================================================================
|
17
|
+
"""
|
18
|
+
|
19
|
+
"""
|
20
|
+
======================================================================
|
21
|
+
GEDCOM Module Types
|
22
|
+
======================================================================
|
23
|
+
"""
|
24
|
+
#======================================================================
|
5
25
|
from .Attribution import Attribution
|
6
26
|
from .Conclusion import Conclusion, ConfidenceLevel
|
7
27
|
from .Date import Date
|
8
28
|
from .Note import Note
|
9
|
-
from .Serialization import Serialization
|
10
|
-
from .SourceReference import SourceReference
|
11
29
|
from .Resource import Resource
|
30
|
+
from .SourceReference import SourceReference
|
31
|
+
#======================================================================
|
12
32
|
|
13
33
|
|
14
34
|
class NameType(Enum):
|
@@ -90,22 +110,57 @@ class NamePartType(Enum):
|
|
90
110
|
return descriptions.get(self, "No description available.")
|
91
111
|
|
92
112
|
class NamePart:
|
113
|
+
"""Used to model a portion of a full name
|
114
|
+
including the terms that make up that portion. Some name parts may have qualifiers
|
115
|
+
to provide additional semantic meaning to the name part (e.g., "given name" or "surname").
|
116
|
+
|
117
|
+
Args:
|
118
|
+
type (NamePartType | None): Classification of this component of the name
|
119
|
+
(e.g., ``Given``, ``Surname``, ``Prefix``, ``Suffix``, ``Title``, ``Particle``).
|
120
|
+
value (str): The textual value for this part, without surrounding
|
121
|
+
punctuation (e.g., ``"John"``, ``"van"``, ``"III"``).
|
122
|
+
qualifiers (list[NamePartQualifier] | None): Optional qualifiers that refine
|
123
|
+
the meaning or usage of this part (e.g., language/script variants, initials).
|
124
|
+
|
125
|
+
Examples:
|
126
|
+
>>> from gedcomx.Name import *
|
127
|
+
>>> typ = NamePartType.Given
|
128
|
+
>>> given = "Moses"
|
129
|
+
>>> q = NamePartQualifier.Primary
|
130
|
+
>>> name = NamePart(type=typ,value=given,qualifiers=[q])
|
131
|
+
>>> print(name)
|
132
|
+
NamePart(type=Given, value='Moses', qualifiers=1)
|
133
|
+
|
134
|
+
"""
|
93
135
|
identifier = 'http://gedcomx.org/v1/NamePart'
|
94
136
|
version = 'http://gedcomx.org/conceptual-model/v1'
|
95
137
|
|
96
138
|
def __init__(self,
|
97
139
|
type: Optional[NamePartType] = None,
|
98
140
|
value: Optional[str] = None,
|
99
|
-
qualifiers: Optional[List[NamePartQualifier]] =
|
141
|
+
qualifiers: Optional[List[NamePartQualifier]] = None) -> None:
|
100
142
|
self.type = type
|
101
143
|
self.value = value
|
102
|
-
self.qualifiers = qualifiers
|
144
|
+
self.qualifiers = qualifiers if qualifiers else []
|
103
145
|
|
146
|
+
@property
|
104
147
|
def _as_dict_(self):
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
148
|
+
from .Serialization import Serialization
|
149
|
+
type_as_dict = {}
|
150
|
+
if self.type:
|
151
|
+
type_as_dict['type'] = self.type.value
|
152
|
+
if self.value:
|
153
|
+
type_as_dict['value'] = self.value
|
154
|
+
if self.qualifiers:
|
155
|
+
type_as_dict['qualifiers'] = [q.value for q in self.qualifiers]
|
156
|
+
Serialization.serialize_dict(type_as_dict)
|
157
|
+
|
158
|
+
@classmethod
|
159
|
+
def _from_json_(cls,data):
|
160
|
+
name_part =NamePart(type=NamePartType(data['type']) if 'type' in data else None,
|
161
|
+
value=data.get('value'),
|
162
|
+
qualifiers=[NamePartQualifier(q) for q in data.get('qualifiers')])
|
163
|
+
return name_part
|
109
164
|
|
110
165
|
def __eq__(self, other):
|
111
166
|
if not isinstance(other, NamePart):
|
@@ -114,26 +169,114 @@ class NamePart:
|
|
114
169
|
self.value == other.value and
|
115
170
|
self.qualifiers == other.qualifiers)
|
116
171
|
|
172
|
+
def __str__(self) -> str:
|
173
|
+
parts = []
|
174
|
+
if self.type is not None:
|
175
|
+
parts.append(f"type={getattr(self.type, 'name', str(self.type))}")
|
176
|
+
if self.value is not None:
|
177
|
+
parts.append(f"value={self.value!r}")
|
178
|
+
if self.qualifiers:
|
179
|
+
parts.append(f"qualifiers={len(self.qualifiers)}")
|
180
|
+
return f"NamePart({', '.join(parts)})" if parts else "NamePart()"
|
181
|
+
|
182
|
+
def __repr__(self) -> str:
|
183
|
+
if self.type is not None:
|
184
|
+
tcls = self.type.__class__.__name__
|
185
|
+
tname = getattr(self.type, "name", str(self.type))
|
186
|
+
tval = getattr(self.type, "value", self.type)
|
187
|
+
type_repr = f"<{tcls}.{tname}: {tval!r}>"
|
188
|
+
else:
|
189
|
+
type_repr = "None"
|
190
|
+
return (
|
191
|
+
f"{self.__class__.__name__}("
|
192
|
+
f"type={type_repr}, "
|
193
|
+
f"value={self.value!r}, "
|
194
|
+
f"qualifiers={self.qualifiers!r})"
|
195
|
+
)
|
196
|
+
|
117
197
|
class NameForm:
|
198
|
+
"""A representation of a name (a "name form")
|
199
|
+
within a given cultural context, such as a given language and script.
|
200
|
+
As names are captured (both in records or in applications), the terms
|
201
|
+
in the name are sometimes classified by type. For example, a certificate
|
202
|
+
of death might prompt for **"given name(s)"** and **"surname"**. The parts
|
203
|
+
list can be used to represent the terms in the name that have been classified.
|
204
|
+
|
205
|
+
Args:
|
206
|
+
lang (str | None): BCP-47 language tag for this name form (e.g., "en").
|
207
|
+
fullText (str | None): The full, unparsed name string as written in the source.
|
208
|
+
If provided, the name SHOULD be rendered as it would normally be spoken in
|
209
|
+
the applicable cultural context.
|
210
|
+
parts (list[NamePart] | None): Ordered structured components of the name
|
211
|
+
(e.g., Given, Surname). If provided, ``fullText`` may be omitted. If
|
212
|
+
provided, the list SHOULD be ordered such that the parts are in the order
|
213
|
+
they would normally be spoken in the applicable cultural context.
|
214
|
+
|
215
|
+
"""
|
118
216
|
identifier = 'http://gedcomx.org/v1/NameForm'
|
119
217
|
version = 'http://gedcomx.org/conceptual-model/v1'
|
120
218
|
|
121
|
-
def __init__(self, lang: Optional[str] =
|
219
|
+
def __init__(self, lang: Optional[str] = None,
|
220
|
+
fullText: Optional[str] = None,
|
221
|
+
parts: Optional[List[NamePart]] = None) -> None:
|
222
|
+
|
122
223
|
self.lang = lang
|
123
224
|
self.fullText = fullText
|
124
|
-
self.parts = parts
|
225
|
+
self.parts = parts if parts else []
|
125
226
|
|
126
227
|
@property
|
127
228
|
def _as_dict_(self):
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
229
|
+
from .Serialization import Serialization
|
230
|
+
type_as_dict = {}
|
231
|
+
if self.lang:
|
232
|
+
type_as_dict['lang'] = self.lang
|
233
|
+
if self.fullText:
|
234
|
+
type_as_dict['fullText'] = self.fullText
|
235
|
+
if self.parts:
|
236
|
+
type_as_dict['parts'] = [part._as_dict_ for part in self.parts if part]
|
237
|
+
return Serialization.serialize_dict(type_as_dict)
|
238
|
+
|
239
|
+
@classmethod
|
240
|
+
def _from_json_(cls, data: dict) -> "NameForm":
|
241
|
+
"""Build a NameForm from JSON-like dict."""
|
242
|
+
return cls(
|
243
|
+
lang=data.get("lang", "en"),
|
244
|
+
fullText=data.get("fullText"),
|
245
|
+
parts=[NamePart._from_json_(p) for p in ensure_list(data.get("parts"))],
|
246
|
+
)
|
132
247
|
|
133
248
|
def _fulltext_parts(self):
|
134
249
|
pass
|
135
250
|
|
136
251
|
class Name(Conclusion):
|
252
|
+
"""**Defines a name of a person.**
|
253
|
+
|
254
|
+
A Name is intended to represent a single variant of a person's name.
|
255
|
+
This means that nicknames, spelling variations, or other names
|
256
|
+
(often distinguishable by a name type) should be modeled with
|
257
|
+
separate instances of Name.
|
258
|
+
|
259
|
+
.. admonition:: Advanced details
|
260
|
+
:class: toggle
|
261
|
+
|
262
|
+
The name forms of a name contain alternate representations of the name.
|
263
|
+
A Name MUST contain at least one name form, presumably a representation
|
264
|
+
of the name that is considered proper and well formed in the person's native,
|
265
|
+
historical cultural context. Other name forms MAY be included, which can be
|
266
|
+
used to represent this name in contexts where the native name form is not easily
|
267
|
+
recognized and interpreted. Alternate forms are more likely in situations where
|
268
|
+
conclusions are being analyzed across cultural context boundaries that have both
|
269
|
+
language and writing script differences.
|
270
|
+
|
271
|
+
|
272
|
+
Attributes:
|
273
|
+
type (Optional[:class:`~gedcomx.NameType`]): Classification of the name
|
274
|
+
(e.g., BirthName, AlsoKnownAs).
|
275
|
+
nameForms (List[:class:`~gedcomx.NameForm`]): One or more structured
|
276
|
+
representations of the name (full text and parts).
|
277
|
+
date (Optional[:class:`~gedcomx.Date`]): Date context for this name
|
278
|
+
(e.g., when the name was used or recorded).
|
279
|
+
"""
|
137
280
|
identifier = 'http://gedcomx.org/v1/Name'
|
138
281
|
version = 'http://gedcomx.org/conceptual-model/v1'
|
139
282
|
|
@@ -163,8 +306,8 @@ class Name(Conclusion):
|
|
163
306
|
name = Name()
|
164
307
|
return name
|
165
308
|
|
166
|
-
def __init__(self, id: str = None,
|
167
|
-
lang: str =
|
309
|
+
def __init__(self, id: Optional[str] = None,
|
310
|
+
lang: Optional[str] = None,
|
168
311
|
sources: Optional[List[SourceReference]] = None,
|
169
312
|
analysis: Resource = None,
|
170
313
|
notes: Optional[List[Note]] = None,
|
@@ -187,39 +330,52 @@ class Name(Conclusion):
|
|
187
330
|
|
188
331
|
@property
|
189
332
|
def _as_dict_(self):
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
return value._as_dict_
|
199
|
-
else:
|
200
|
-
return str(value) # fallback for unknown objects
|
201
|
-
|
202
|
-
name_as_dict = super()._as_dict_
|
203
|
-
|
204
|
-
name_as_dict.update( {
|
205
|
-
'type':self.type.value if self.type else None,
|
206
|
-
'nameForms': [nameForm._as_dict_ for nameForm in self.nameForms],
|
207
|
-
'date': self.date._as_dict_ if self.date else None})
|
333
|
+
from .Serialization import Serialization
|
334
|
+
type_as_dict = super()._as_dict_
|
335
|
+
if self.type:
|
336
|
+
type_as_dict['type'] = getattr(self.type, 'value', self.type)
|
337
|
+
if self.nameForms:
|
338
|
+
type_as_dict['nameForms'] = [nf._as_dict_ for nf in self.nameForms if nf]
|
339
|
+
if self.date:
|
340
|
+
type_as_dict['date'] = self.date._as_dict_
|
208
341
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
342
|
+
return Serialization.serialize_dict(type_as_dict)
|
343
|
+
|
344
|
+
@classmethod
|
345
|
+
def _from_json_(cls, data: dict) -> "Name":
|
346
|
+
"""Build a Name from JSON-like dict."""
|
347
|
+
return cls(
|
348
|
+
id=data.get("id"),
|
349
|
+
lang=data.get("lang", "en"),
|
350
|
+
sources=[SourceReference._from_json_(s) for s in ensure_list(data.get("sources"))],
|
351
|
+
analysis=Resource._from_json_(data["analysis"]) if data.get("analysis") else None,
|
352
|
+
notes=[Note._from_json_(n) for n in ensure_list(data.get("notes"))],
|
353
|
+
confidence=ConfidenceLevel._from_json_(data["confidence"]) if data.get("confidence") else None,
|
354
|
+
attribution=Attribution._from_json_(data["attribution"]) if data.get("attribution") else None,
|
355
|
+
type=NameType(data["type"]) if data.get("type") else None,
|
356
|
+
nameForms=[NameForm._from_json_(nf) for nf in ensure_list(data.get("nameForms"))],
|
357
|
+
date=Date._from_json_(data["date"]) if data.get("date") else None,
|
358
|
+
)
|
221
359
|
|
222
|
-
|
360
|
+
def __str__(self) -> str:
|
361
|
+
"""Return a human-readable string for the Name-like object."""
|
362
|
+
return f"Name(id={self.id}, type={self.type}, forms={len(self.nameForms)}, date={self.date})"
|
363
|
+
|
364
|
+
def __repr__(self) -> str:
|
365
|
+
"""Return an unambiguous string representation of the Name-like object."""
|
366
|
+
return (
|
367
|
+
f"{self.__class__.__name__}("
|
368
|
+
f"id={self.id!r}, "
|
369
|
+
f"lang={self.lang!r}, "
|
370
|
+
f"sources={self.sources!r}, "
|
371
|
+
f"analysis={self.analysis!r}, "
|
372
|
+
f"notes={self.notes!r}, "
|
373
|
+
f"confidence={self.confidence!r}, "
|
374
|
+
f"attribution={self.attribution!r}, "
|
375
|
+
f"type={self.type!r}, "
|
376
|
+
f"nameForms={self.nameForms!r}, "
|
377
|
+
f"date={self.date!r})"
|
378
|
+
)
|
223
379
|
|
224
380
|
class QuickName():
|
225
381
|
def __new__(cls,name: str) -> Name:
|
@@ -231,46 +387,10 @@ def ensure_list(val):
|
|
231
387
|
return []
|
232
388
|
return val if isinstance(val, list) else [val]
|
233
389
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
))
|
239
|
-
|
240
|
-
NamePart._to_dict_ = lambda self: {
|
241
|
-
'type': self.type.value if self.type else None,
|
242
|
-
'value': self.value,
|
243
|
-
'qualifiers': [q.value for q in self.qualifiers] if self.qualifiers else []
|
244
|
-
}
|
245
|
-
|
246
|
-
|
247
|
-
# NameForm
|
248
|
-
NameForm._from_json_ = classmethod(lambda cls, data: NameForm(
|
249
|
-
lang=data.get('lang', 'en'),
|
250
|
-
fullText=data.get('fullText'),
|
251
|
-
parts=[NamePart._from_json_(p) for p in ensure_list(data.get('parts'))]
|
252
|
-
))
|
253
|
-
|
254
|
-
NameForm._to_dict_ = lambda self: {
|
255
|
-
'lang': self.lang,
|
256
|
-
'fullText': self.fullText,
|
257
|
-
'parts': [p._to_dict_() for p in self.parts] if self.parts else []
|
258
|
-
}
|
259
|
-
|
260
|
-
|
261
|
-
# Name
|
262
|
-
Name._from_json_ = classmethod(lambda cls, data: cls(
|
263
|
-
id=data.get('id'),
|
264
|
-
lang=data.get('lang', 'en'),
|
265
|
-
sources=[SourceReference._from_json_(s) for s in ensure_list(data.get('sources'))],
|
266
|
-
analysis=Resource._from_json_(data['analysis']) if data.get('analysis') else None,
|
267
|
-
notes=[Note._from_json_(n) for n in ensure_list(data.get('notes'))],
|
268
|
-
confidence=ConfidenceLevel._from_json_(data['confidence']) if data.get('confidence') else None,
|
269
|
-
attribution=Attribution._from_json_(data['attribution']) if data.get('attribution') else None,
|
270
|
-
type=NameType(data['type']) if data.get('type') else None,
|
271
|
-
nameForms=[NameForm._from_json_(nf) for nf in ensure_list(data.get('nameForms'))],
|
272
|
-
date=Date._from_json_(data['date']) if data.get('date') else None
|
273
|
-
))
|
390
|
+
|
391
|
+
|
392
|
+
|
393
|
+
|
274
394
|
|
275
395
|
|
276
396
|
|
gedcomx/Note.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
from typing import Optional
|
2
2
|
|
3
3
|
from .Attribution import Attribution
|
4
|
-
from .Serialization import Serialization
|
5
4
|
|
6
5
|
class Note:
|
7
6
|
identifier = 'http://gedcomx.org/v1/Note'
|
@@ -15,20 +14,28 @@ class Note:
|
|
15
14
|
|
16
15
|
def append(self, text_to_add: str):
|
17
16
|
if text_to_add and isinstance(text_to_add, str):
|
18
|
-
|
17
|
+
if self.text:
|
18
|
+
self.text = self.text + text_to_add
|
19
|
+
else:
|
20
|
+
self.text = text_to_add
|
19
21
|
else:
|
20
22
|
return #TODO
|
21
23
|
raise ValueError("The text to add must be a non-empty string.")
|
22
24
|
|
23
25
|
@property
|
24
26
|
def _as_dict_(self):
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
"
|
29
|
-
|
30
|
-
|
31
|
-
|
27
|
+
from .Serialization import Serialization
|
28
|
+
type_as_dict = {}
|
29
|
+
if self.lang:
|
30
|
+
type_as_dict["lang"] = self.lang
|
31
|
+
if self.subject:
|
32
|
+
type_as_dict["subject"] = self.subject
|
33
|
+
if self.text:
|
34
|
+
type_as_dict["text"] = self.text
|
35
|
+
if self.attribution:
|
36
|
+
# If attribution exposes `_as_dict_` as a property, use it; otherwise include as-is
|
37
|
+
type_as_dict["attribution"] = getattr(self.attribution, "_as_dict_", self.attribution)
|
38
|
+
return Serialization.serialize_dict(type_as_dict)
|
32
39
|
|
33
40
|
def __eq__(self, other):
|
34
41
|
if not isinstance(other, Note):
|