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.
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
- from .Serialization import Serialization
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] = 'en',
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
- 'value': [v for v in self.values] if self.values else None,
56
- 'type': self.type.value if self.type else None
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 GedcomRecord
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: GedcomRecord | None = None) -> None:
182
- self.created_with_tag: str = record.tag if record and isinstance(record, GedcomRecord) else None
183
- self.created_at_level: int = record.level if record and isinstance(record, GedcomRecord) else None
184
- self.created_at_line_number: int = record.line_number if record and isinstance(record, GedcomRecord) else None
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: GedcomRecord | None = None) -> None:
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: GedcomRecord | None = None, object_stack: dict | None = None) -> object:
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: GedcomRecord | None = None, object_stack: dict | None = None) -> object:
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]] = []) -> None:
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
- return Serialization.serialize_dict(
106
- {'type': self.type.value if self.type else None,
107
- 'value': self.value if self.value else None,
108
- 'qualifiers': [qualifier.value for qualifier in self.qualifiers] if self.qualifiers else None})
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] = 'en', fullText: Optional[str] = None,parts: Optional[List[NamePart]] = []) -> None:
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
- return Serialization.serialize_dict(
129
- {'lang': self.lang,
130
- 'fullText': self.fullText,
131
- 'parts': [part._as_dict_() for part in self.parts] if self.parts else None})
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 = 'en',
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
- def _serialize(value):
191
- if isinstance(value, (str, int, float, bool, type(None))):
192
- return value
193
- elif isinstance(value, dict):
194
- return {k: _serialize(v) for k, v in value.items()}
195
- elif isinstance(value, (list, tuple, set)):
196
- return [_serialize(v) for v in value]
197
- elif hasattr(value, "_as_dict_"):
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
- #del name_as_dict['id']
210
- # Serialize and exclude None values
211
- for key, value in name_as_dict.items():
212
- if value is not None:
213
- name_as_dict[key] = _serialize(value)
214
-
215
- # 3) merge and filter out None *at the top level*
216
- return {
217
- k: v
218
- for k, v in name_as_dict.items()
219
- if v is not None
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
- return name_as_dict
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
- NamePart._from_json_ = classmethod(lambda cls, data: NamePart(
235
- type=NamePartType(data['type']) if 'type' in data else None,
236
- value=data.get('value'),
237
- qualifiers=[NamePartQualifier(q) for q in ensure_list(data.get('qualifiers'))]
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
- self.text = self.text + text_to_add
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
- note_dict = {
26
- "lang":self.lang if self.lang else None,
27
- "subject":self.subject if self.subject else None,
28
- "text":self.text if self.text else None,
29
- "attribution": self.attribution if self.attribution else None
30
- }
31
- return Serialization.serialize_dict(note_dict)
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):