gedcom-x 0.5.10__py3-none-any.whl → 0.5.11__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.10.dist-info → gedcom_x-0.5.11.dist-info}/METADATA +1 -1
- gedcom_x-0.5.11.dist-info/RECORD +57 -0
- gedcomx/Extensions/rs10/rsLink.py +2 -1
- gedcomx/__init__.py +5 -3
- gedcomx/address.py +3 -0
- gedcomx/agent.py +11 -6
- gedcomx/attribution.py +3 -1
- gedcomx/conclusion.py +9 -5
- gedcomx/converter.py +92 -27
- gedcomx/coverage.py +3 -1
- gedcomx/date.py +3 -0
- gedcomx/document.py +3 -1
- gedcomx/event.py +3 -0
- gedcomx/evidence_reference.py +30 -3
- gedcomx/fact.py +6 -2
- gedcomx/gedcom5x.py +21 -3
- gedcomx/gedcomx.py +3 -0
- gedcomx/gender.py +5 -1
- gedcomx/group.py +11 -2
- gedcomx/identifier.py +5 -2
- gedcomx/logging_hub.py +132 -22
- gedcomx/name.py +15 -6
- gedcomx/note.py +25 -10
- gedcomx/online_account.py +20 -0
- gedcomx/person.py +6 -5
- gedcomx/place_description.py +3 -1
- gedcomx/place_reference.py +5 -2
- gedcomx/qualifier.py +2 -0
- gedcomx/relationship.py +8 -5
- gedcomx/resource.py +20 -6
- gedcomx/schemas.py +521 -319
- gedcomx/serialization.py +36 -16
- gedcomx/source_citation.py +22 -0
- gedcomx/source_description.py +22 -18
- gedcomx/source_reference.py +25 -3
- gedcomx/subject.py +2 -3
- gedcomx/textvalue.py +19 -4
- gedcomx/uri.py +8 -6
- gedcom_x-0.5.10.dist-info/RECORD +0 -58
- gedcomx/Logging.py +0 -19
- {gedcom_x-0.5.10.dist-info → gedcom_x-0.5.11.dist-info}/WHEEL +0 -0
- {gedcom_x-0.5.10.dist-info → gedcom_x-0.5.11.dist-info}/top_level.txt +0 -0
gedcomx/fact.py
CHANGED
@@ -2,7 +2,7 @@ import difflib
|
|
2
2
|
import re
|
3
3
|
|
4
4
|
from enum import Enum
|
5
|
-
from typing import List, Optional, Dict, Any
|
5
|
+
from typing import List, Optional, Dict, Any, Union, TYPE_CHECKING
|
6
6
|
"""
|
7
7
|
======================================================================
|
8
8
|
Project: Gedcom-X
|
@@ -13,6 +13,7 @@ from typing import List, Optional, Dict, Any
|
|
13
13
|
Created: 2025-08-25
|
14
14
|
Updated:
|
15
15
|
- 2025-09-03: _from_json_ refactor
|
16
|
+
- 2025-09-09: added schema_class
|
16
17
|
|
17
18
|
======================================================================
|
18
19
|
"""
|
@@ -31,6 +32,7 @@ from .note import Note
|
|
31
32
|
from .place_reference import PlaceReference
|
32
33
|
from .qualifier import Qualifier
|
33
34
|
from .resource import Resource
|
35
|
+
from .schemas import schema_class
|
34
36
|
from .source_reference import SourceReference
|
35
37
|
from .logging_hub import hub, logging
|
36
38
|
"""
|
@@ -406,6 +408,7 @@ class FactQualifier(Enum):
|
|
406
408
|
}
|
407
409
|
return descriptions.get(self, "No description available.")
|
408
410
|
|
411
|
+
@schema_class()
|
409
412
|
class Fact(Conclusion):
|
410
413
|
identifier = 'http://gedcomx.org/v1/Fact'
|
411
414
|
version = 'http://gedcomx.org/conceptual-model/v1'
|
@@ -414,7 +417,7 @@ class Fact(Conclusion):
|
|
414
417
|
id: Optional[str] = None,
|
415
418
|
lang: Optional[str] = None,
|
416
419
|
sources: Optional[List[SourceReference]] = None,
|
417
|
-
analysis: Optional[Resource
|
420
|
+
analysis: Optional[Union[Resource,Document]] = None,
|
418
421
|
notes: Optional[List[Note]] = None,
|
419
422
|
confidence: Optional[ConfidenceLevel] = None,
|
420
423
|
attribution: Optional[Attribution] = None,
|
@@ -430,6 +433,7 @@ class Fact(Conclusion):
|
|
430
433
|
self.place = place
|
431
434
|
self.value = value
|
432
435
|
self._qualifiers = qualifiers if qualifiers else []
|
436
|
+
self.id = id if id else None # No need for id on 'Fact' unless provided
|
433
437
|
|
434
438
|
|
435
439
|
@property
|
gedcomx/gedcom5x.py
CHANGED
@@ -7,10 +7,28 @@ from typing import List, Optional, Tuple, Any
|
|
7
7
|
import re
|
8
8
|
from collections import defaultdict
|
9
9
|
from typing import Iterable, Iterator, List, Optional, Tuple, Union
|
10
|
+
"""
|
11
|
+
======================================================================
|
12
|
+
Project: Gedcom-X
|
13
|
+
File: gedcom5x.py
|
14
|
+
Author: David J. Cartwright
|
15
|
+
Purpose:
|
16
|
+
|
17
|
+
Created: 2025-08-25
|
18
|
+
Updated:
|
19
|
+
- 2025-09-03:
|
20
|
+
|
21
|
+
======================================================================
|
22
|
+
"""
|
23
|
+
|
24
|
+
"""
|
25
|
+
======================================================================
|
26
|
+
GEDCOM Module Types
|
27
|
+
======================================================================
|
28
|
+
"""
|
29
|
+
#import logging
|
30
|
+
from .logging_hub import hub, ChannelConfig, logging
|
10
31
|
|
11
|
-
import logging
|
12
|
-
from .logging_hub import hub, ChannelConfig
|
13
|
-
from .logging_hub import hub, logging
|
14
32
|
"""
|
15
33
|
======================================================================
|
16
34
|
Logging
|
gedcomx/gedcomx.py
CHANGED
@@ -20,6 +20,7 @@ from typing import Any, Dict, List, Optional
|
|
20
20
|
- 2025-08-31: _as_dict_ to only create entries in dict for fields that hold data,
|
21
21
|
id_index functionality, will be used for resolution of Resources
|
22
22
|
- 2025-09-03: _from_json_ refactor
|
23
|
+
- 2025-09-09: added schema_class
|
23
24
|
|
24
25
|
======================================================================
|
25
26
|
"""
|
@@ -40,6 +41,7 @@ from .person import Person
|
|
40
41
|
from .place_description import PlaceDescription
|
41
42
|
from .relationship import Relationship, RelationshipType
|
42
43
|
from .resource import Resource
|
44
|
+
from .schemas import schema_class
|
43
45
|
from .source_description import ResourceType, SourceDescription
|
44
46
|
from .textvalue import TextValue
|
45
47
|
from .uri import URI
|
@@ -228,6 +230,7 @@ def TypeCollection(item_type):
|
|
228
230
|
|
229
231
|
return Collection()
|
230
232
|
|
233
|
+
@schema_class()
|
231
234
|
class GedcomX:
|
232
235
|
"""
|
233
236
|
Main GedcomX Object representing a Genealogy. Stores collections of Top Level Gedcom-X Types.
|
gedcomx/gender.py
CHANGED
@@ -10,6 +10,7 @@ from typing import List, Optional
|
|
10
10
|
Created: 2025-08-25
|
11
11
|
Updated:
|
12
12
|
- 2025-09-03: _from_json_ refactor
|
13
|
+
- 2025-09-09: added schema_class
|
13
14
|
|
14
15
|
======================================================================
|
15
16
|
"""
|
@@ -24,6 +25,7 @@ from .conclusion import ConfidenceLevel, Conclusion
|
|
24
25
|
from .Extensions.rs10.rsLink import _rsLinks
|
25
26
|
from .note import Note
|
26
27
|
from .resource import Resource
|
28
|
+
from .schemas import schema_class
|
27
29
|
from .source_reference import SourceReference
|
28
30
|
from .logging_hub import hub, logging
|
29
31
|
"""
|
@@ -51,7 +53,8 @@ class GenderType(Enum):
|
|
51
53
|
GenderType.Intersex: "Intersex (assignment at birth)."
|
52
54
|
}
|
53
55
|
return descriptions.get(self, "No description available.")
|
54
|
-
|
56
|
+
|
57
|
+
@schema_class()
|
55
58
|
class Gender(Conclusion):
|
56
59
|
identifier = 'http://gedcomx.org/v1/Gender'
|
57
60
|
version = 'http://gedcomx.org/conceptual-model/v1'
|
@@ -69,6 +72,7 @@ class Gender(Conclusion):
|
|
69
72
|
) -> None:
|
70
73
|
super().__init__(id=id, lang=lang, sources=sources, analysis=analysis, notes=notes, confidence=confidence, attribution=attribution, links=links)
|
71
74
|
self.type = type
|
75
|
+
self.id = id if id else None # No need for id unless provided
|
72
76
|
|
73
77
|
@property
|
74
78
|
def _as_dict_(self):
|
gedcomx/group.py
CHANGED
@@ -10,6 +10,7 @@ from typing import List, Optional
|
|
10
10
|
Created: 2025-08-25
|
11
11
|
Updated:
|
12
12
|
- 2025-09-01: Updating basic structure, identify TODO s
|
13
|
+
- 2025-09-09: added schema_class
|
13
14
|
|
14
15
|
======================================================================
|
15
16
|
"""
|
@@ -29,8 +30,8 @@ from .note import Note
|
|
29
30
|
from .place_reference import PlaceReference
|
30
31
|
from .source_reference import SourceReference
|
31
32
|
from .resource import Resource
|
32
|
-
|
33
33
|
from .textvalue import TextValue
|
34
|
+
from .schemas import schema_class
|
34
35
|
from .subject import Subject
|
35
36
|
from .logging_hub import hub, logging
|
36
37
|
"""
|
@@ -53,6 +54,7 @@ class GroupRole: #TODO Impliment
|
|
53
54
|
def __init__(self, person: Resource,type: Optional[Enum], date: Optional[Date],details: Optional[str]) -> None:
|
54
55
|
pass
|
55
56
|
|
57
|
+
@schema_class(toplevel=True)
|
56
58
|
class Group(Subject): #TODO Impliment
|
57
59
|
identifier = 'http://gedcomx.org/v1/Group'
|
58
60
|
version = 'http://gedcomx.org/conceptual-model/v1'
|
@@ -60,7 +62,14 @@ class Group(Subject): #TODO Impliment
|
|
60
62
|
def __init__(self,
|
61
63
|
id: str | None, lang: str | None,
|
62
64
|
sources: List[SourceReference] | None,
|
63
|
-
analysis: Document | Resource | None,
|
65
|
+
analysis: Document | Resource | None,
|
66
|
+
notes: List[Note] | None,
|
67
|
+
confidence: ConfidenceLevel | None,
|
68
|
+
attribution: Attribution | None,
|
69
|
+
extracted: bool | None,
|
70
|
+
evidence: List[EvidenceReference] | None,
|
71
|
+
media: List[SourceReference] | None,
|
72
|
+
identifiers: List[Identifier] | None,
|
64
73
|
names: List[TextValue],
|
65
74
|
date: Optional[Date],
|
66
75
|
place: Optional[PlaceReference],
|
gedcomx/identifier.py
CHANGED
@@ -16,6 +16,7 @@ import json
|
|
16
16
|
Updated:
|
17
17
|
- 2025-09-03: _from_json_ refactor
|
18
18
|
- 2025-09-04: fixe identifier and identifieList json deserialization
|
19
|
+
- 2025-09-09: added schema_class
|
19
20
|
|
20
21
|
======================================================================
|
21
22
|
"""
|
@@ -27,6 +28,7 @@ GEDCOM Module Types
|
|
27
28
|
"""
|
28
29
|
from .extensible_enum import _EnumItem
|
29
30
|
from .resource import Resource
|
31
|
+
from .schemas import schema_class
|
30
32
|
from .uri import URI
|
31
33
|
from .extensible_enum import ExtensibleEnum
|
32
34
|
from .logging_hub import hub, logging
|
@@ -65,7 +67,8 @@ IdentifierType.register("Persistent", "http://gedcomx.org/Persistent")
|
|
65
67
|
IdentifierType.register("External", "https://gedcom.io/terms/v7/EXID")
|
66
68
|
IdentifierType.register("Other", "user provided")
|
67
69
|
IdentifierType.register("ChildAndParentsRelationship","http://familysearch.org/v1/ChildAndParentsRelationship")
|
68
|
-
|
70
|
+
|
71
|
+
@schema_class()
|
69
72
|
class Identifier:
|
70
73
|
identifier = 'http://gedcomx.org/v1/Identifier'
|
71
74
|
version = 'http://gedcomx.org/conceptual-model/v1'
|
@@ -231,7 +234,7 @@ class IdentifierList:
|
|
231
234
|
raise ValueError("Data must be a dict of identifiers.")
|
232
235
|
|
233
236
|
@property
|
234
|
-
def
|
237
|
+
def _serializer(self):
|
235
238
|
type_as_dict = {}
|
236
239
|
for k in self.identifiers.keys():
|
237
240
|
#print(k,self.identifiers[k],type(self.identifiers[k]))
|
gedcomx/logging_hub.py
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
|
2
|
+
"""
|
3
|
+
======================================================================
|
4
|
+
Project: Gedcom-X
|
5
|
+
File: logging_hub.py
|
6
|
+
Author: David J. Cartwright
|
7
|
+
Purpose: provide module wide logging at context/channel level
|
8
|
+
|
9
|
+
Created: 2025-08-25
|
10
|
+
Updated:
|
11
|
+
- 2025-09-09: added global kill
|
12
|
+
|
13
|
+
======================================================================
|
14
|
+
"""
|
1
15
|
# logging_hub.py
|
2
16
|
from __future__ import annotations
|
3
17
|
import logging
|
@@ -8,8 +22,12 @@ from dataclasses import dataclass
|
|
8
22
|
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
|
9
23
|
from typing import Dict, Optional
|
10
24
|
|
11
|
-
#
|
12
|
-
|
25
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
26
|
+
# Context: which "channel" (log) is current?
|
27
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
28
|
+
_current_channel: contextvars.ContextVar[str] = contextvars.ContextVar(
|
29
|
+
"current_log_channel", default="default"
|
30
|
+
)
|
13
31
|
|
14
32
|
def get_current_channel() -> str:
|
15
33
|
return _current_channel.get()
|
@@ -17,16 +35,36 @@ def get_current_channel() -> str:
|
|
17
35
|
def set_current_channel(name: str) -> None:
|
18
36
|
_current_channel.set(name)
|
19
37
|
|
38
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
39
|
+
# Filters and Handlers
|
40
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
20
41
|
class ChannelFilter(logging.Filter):
|
21
42
|
"""Injects the current channel into every LogRecord."""
|
22
43
|
def filter(self, record: logging.LogRecord) -> bool:
|
23
44
|
record.log_channel = get_current_channel()
|
24
45
|
return True
|
25
46
|
|
47
|
+
class KillSwitchFilter(logging.Filter):
|
48
|
+
"""Fast global on/off. Returning False drops the record early."""
|
49
|
+
def __init__(self) -> None:
|
50
|
+
super().__init__()
|
51
|
+
self._enabled: bool = True
|
52
|
+
|
53
|
+
@property
|
54
|
+
def enabled(self) -> bool:
|
55
|
+
return self._enabled
|
56
|
+
|
57
|
+
@enabled.setter
|
58
|
+
def enabled(self, value: bool) -> None:
|
59
|
+
self._enabled = bool(value)
|
60
|
+
|
61
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
62
|
+
return self._enabled
|
63
|
+
|
26
64
|
class DispatchingHandler(logging.Handler):
|
27
65
|
"""
|
28
|
-
Routes records to a per-channel handler (file/stream),
|
29
|
-
|
66
|
+
Routes records to a per-channel handler (file/stream),
|
67
|
+
based on LogRecord.log_channel (set by ChannelFilter).
|
30
68
|
"""
|
31
69
|
def __init__(self):
|
32
70
|
super().__init__()
|
@@ -69,6 +107,9 @@ class DispatchingHandler(logging.Handler):
|
|
69
107
|
return # channel muted
|
70
108
|
handler.emit(record)
|
71
109
|
|
110
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
111
|
+
# Configuration model
|
112
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
72
113
|
@dataclass
|
73
114
|
class ChannelConfig:
|
74
115
|
name: str
|
@@ -82,18 +123,21 @@ class ChannelConfig:
|
|
82
123
|
# "size:10MB:3" -> RotatingFileHandler(maxBytes=10MB, backupCount=3)
|
83
124
|
# "time:midnight:7" -> TimedRotatingFileHandler(when="midnight", backupCount=7)
|
84
125
|
|
126
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
127
|
+
# Hub
|
128
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
85
129
|
class LoggingHub:
|
86
130
|
"""
|
87
|
-
|
88
|
-
|
131
|
+
Centralized, context-aware logging hub.
|
132
|
+
|
133
|
+
Example:
|
89
134
|
hub = LoggingHub()
|
90
|
-
hub.init_root()
|
91
|
-
hub.start_channel(ChannelConfig(name="default", path="logs/
|
92
|
-
hub.set_current("default")
|
135
|
+
hub.init_root()
|
136
|
+
hub.start_channel(ChannelConfig(name="default", path="logs/app.log"), make_current=True)
|
93
137
|
|
94
|
-
|
95
|
-
|
96
|
-
|
138
|
+
log = hub.get_logger("gedcomx")
|
139
|
+
if hub.loggingenable: # <— cheap guard to avoid formatting
|
140
|
+
log.info("hello %s", "world")
|
97
141
|
|
98
142
|
with hub.use("import-job-42"):
|
99
143
|
log.info("within job 42")
|
@@ -102,9 +146,10 @@ class LoggingHub:
|
|
102
146
|
self.root_name = root_logger_name
|
103
147
|
self._root = logging.getLogger(self.root_name)
|
104
148
|
self._dispatch = DispatchingHandler()
|
105
|
-
self._root.setLevel(logging.DEBUG) #
|
149
|
+
self._root.setLevel(logging.DEBUG) # Handlers/filters determine final behavior
|
106
150
|
|
107
151
|
self._filter = ChannelFilter()
|
152
|
+
self._killswitch = KillSwitchFilter()
|
108
153
|
self._initialized = False
|
109
154
|
|
110
155
|
# -------- Initialization --------
|
@@ -114,10 +159,19 @@ class LoggingHub:
|
|
114
159
|
# Clean existing handlers on the root logger (optional safety)
|
115
160
|
for h in list(self._root.handlers):
|
116
161
|
self._root.removeHandler(h)
|
162
|
+
|
163
|
+
# Order matters: kill-switch first for fastest early exit.
|
164
|
+
self._root.addFilter(self._killswitch)
|
117
165
|
self._root.addFilter(self._filter)
|
118
166
|
self._root.addHandler(self._dispatch)
|
119
167
|
self._initialized = True
|
120
168
|
|
169
|
+
# Optional: env bootstrap
|
170
|
+
if os.getenv("GEDCOMX_LOG", "1").lower() in {"0", "false", "off"}:
|
171
|
+
self.disable_all()
|
172
|
+
if os.getenv("GEDCOMX_LOG_HARD", "0") == "1":
|
173
|
+
self.hard_disable()
|
174
|
+
|
121
175
|
# -------- Channel Management --------
|
122
176
|
def start_channel(self, cfg: ChannelConfig, make_current: bool = False, enabled: bool = True) -> None:
|
123
177
|
"""Create/replace a channel with a file/rotating handler."""
|
@@ -125,7 +179,6 @@ class LoggingHub:
|
|
125
179
|
formatter = logging.Formatter(cfg.fmt, datefmt=cfg.datefmt)
|
126
180
|
|
127
181
|
if cfg.path is None:
|
128
|
-
# StreamHandler to stdout if no path provided
|
129
182
|
handler = logging.StreamHandler()
|
130
183
|
else:
|
131
184
|
# Rotation options
|
@@ -133,7 +186,7 @@ class LoggingHub:
|
|
133
186
|
# "size:10MB:3"
|
134
187
|
_, size_str, backups_str = cfg.rotation.split(":")
|
135
188
|
size_str = size_str.upper().replace("MB", "*1024*1024").replace("KB", "*1024")
|
136
|
-
max_bytes = int(eval(size_str)) #
|
189
|
+
max_bytes = int(eval(size_str)) # simple controlled eval of KB/MB
|
137
190
|
backup_count = int(backups_str)
|
138
191
|
handler = RotatingFileHandler(cfg.path, maxBytes=max_bytes, backupCount=backup_count, encoding="utf-8")
|
139
192
|
elif cfg.rotation and cfg.rotation.startswith("time:"):
|
@@ -182,11 +235,66 @@ class LoggingHub:
|
|
182
235
|
finally:
|
183
236
|
_current_channel.reset(token)
|
184
237
|
|
185
|
-
# --------
|
238
|
+
# -------- Global kill switch (soft) --------
|
239
|
+
def enable_all(self) -> None:
|
240
|
+
self._killswitch.enabled = True
|
241
|
+
|
242
|
+
def disable_all(self) -> None:
|
243
|
+
self._killswitch.enabled = False
|
244
|
+
|
245
|
+
def is_enabled(self) -> bool:
|
246
|
+
return self._killswitch.enabled
|
247
|
+
|
248
|
+
@property
|
249
|
+
def logging_enabled(self) -> bool:
|
250
|
+
"""Preferred property name."""
|
251
|
+
return self._killswitch.enabled
|
252
|
+
|
253
|
+
@logging_enabled.setter
|
254
|
+
def logging_enabled(self, value: bool) -> None:
|
255
|
+
self._killswitch.enabled = bool(value)
|
256
|
+
|
257
|
+
# Alias to match your requested spelling: hub.loggingenable
|
258
|
+
@property
|
259
|
+
def logEnabled(self) -> bool:
|
260
|
+
return self.logging_enabled
|
261
|
+
|
262
|
+
@logEnabled.setter
|
263
|
+
def logEnabled(self, value: bool) -> None:
|
264
|
+
self.logging_enabled = bool(value)
|
265
|
+
|
266
|
+
@contextmanager
|
267
|
+
def muted(self):
|
268
|
+
"""Temporarily mute all logging in a with-block."""
|
269
|
+
prev = self._killswitch.enabled
|
270
|
+
try:
|
271
|
+
self._killswitch.enabled = False
|
272
|
+
yield
|
273
|
+
finally:
|
274
|
+
self._killswitch.enabled = prev
|
275
|
+
|
276
|
+
# -------- Hard nuke (affects 3rd-party libs too) --------
|
277
|
+
def hard_disable(self) -> None:
|
278
|
+
# Drop all messages of all loggers by using a level above CRITICAL
|
279
|
+
logging.disable(100)
|
280
|
+
|
281
|
+
def hard_enable(self) -> None:
|
282
|
+
logging.disable(0)
|
283
|
+
|
284
|
+
# -------- Convenience --------
|
186
285
|
def set_default_channel(self, name: str) -> None:
|
187
286
|
self._dispatch.set_default_channel(name)
|
188
287
|
|
189
|
-
|
288
|
+
def get_logger(self, name: Optional[str] = None) -> logging.Logger:
|
289
|
+
"""Get a named logger under the hub root."""
|
290
|
+
if not name:
|
291
|
+
return self._root
|
292
|
+
return logging.getLogger(f"{self.root_name}.{name}")
|
293
|
+
|
294
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
295
|
+
# Hub instance & sample bootstrap
|
296
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
297
|
+
hub = LoggingHub("gedcomx") # app logger root
|
190
298
|
hub.init_root() # do this ONCE at startup
|
191
299
|
|
192
300
|
os.makedirs("logs", exist_ok=True)
|
@@ -211,16 +319,18 @@ hub.start_channel(
|
|
211
319
|
name=serial_log,
|
212
320
|
path=f"logs/{serial_log}.log",
|
213
321
|
level=logging.DEBUG,
|
214
|
-
rotation="size:10MB:3",
|
215
|
-
)
|
322
|
+
rotation="size:10MB:3",
|
323
|
+
)
|
324
|
+
)
|
216
325
|
|
217
326
|
hub.start_channel(
|
218
327
|
ChannelConfig(
|
219
328
|
name=deserial_log,
|
220
329
|
path=f"logs/{deserial_log}.log",
|
221
330
|
level=logging.DEBUG,
|
222
|
-
rotation="size:10MB:3",
|
223
|
-
)
|
331
|
+
rotation="size:10MB:3",
|
332
|
+
)
|
333
|
+
)
|
224
334
|
|
225
335
|
# (optional) Also a console channel you can switch to
|
226
|
-
hub.start_channel(ChannelConfig(name="console", path=None, level=logging.DEBUG))
|
336
|
+
hub.start_channel(ChannelConfig(name="console", path=None, level=logging.DEBUG))
|
gedcomx/name.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
from enum import Enum
|
2
|
-
from typing import List,Optional
|
2
|
+
from typing import List,Optional, Union
|
3
3
|
|
4
4
|
"""
|
5
5
|
======================================================================
|
@@ -12,6 +12,7 @@ from typing import List,Optional
|
|
12
12
|
Updated:
|
13
13
|
- 2025-08-31: _as_dict_ to only create entries in dict for fields that hold data
|
14
14
|
- 2025-09-03: _from_json_ refactor
|
15
|
+
- 2025-09-09: added schema_class
|
15
16
|
|
16
17
|
======================================================================
|
17
18
|
"""
|
@@ -29,6 +30,7 @@ from .document import Document
|
|
29
30
|
from .Extensions.rs10.rsLink import _rsLinks
|
30
31
|
from .note import Note
|
31
32
|
from .resource import Resource
|
33
|
+
from .schemas import schema_class
|
32
34
|
from .source_reference import SourceReference
|
33
35
|
from .logging_hub import hub, logging
|
34
36
|
"""
|
@@ -119,7 +121,8 @@ class NamePartType(Enum):
|
|
119
121
|
NamePartType.Surname: "A surname."
|
120
122
|
}
|
121
123
|
return descriptions.get(self, "No description available.")
|
122
|
-
|
124
|
+
|
125
|
+
@schema_class()
|
123
126
|
class NamePart:
|
124
127
|
"""Used to model a portion of a full name
|
125
128
|
including the terms that make up that portion. Some name parts may have qualifiers
|
@@ -219,6 +222,7 @@ class NamePart:
|
|
219
222
|
f"qualifiers={self.qualifiers!r})"
|
220
223
|
)
|
221
224
|
|
225
|
+
@schema_class()
|
222
226
|
class NameForm:
|
223
227
|
"""A representation of a name (a "name form")
|
224
228
|
within a given cultural context, such as a given language and script.
|
@@ -285,6 +289,7 @@ class NameForm:
|
|
285
289
|
def _fulltext_parts(self):
|
286
290
|
pass
|
287
291
|
|
292
|
+
@schema_class()
|
288
293
|
class Name(Conclusion):
|
289
294
|
"""**Defines a name of a person.**
|
290
295
|
|
@@ -334,11 +339,13 @@ class Name(Conclusion):
|
|
334
339
|
#given = given.replace('/', '')
|
335
340
|
#surname = surname.replace('/', '')
|
336
341
|
|
337
|
-
|
338
|
-
|
342
|
+
parts =[]
|
343
|
+
if given: parts.append(NamePart(type = NamePartType.Given, value=given))
|
344
|
+
if surname: parts.append(NamePart(type = NamePartType.Surname, value=surname))
|
339
345
|
|
340
|
-
name_form = NameForm(fullText=text
|
346
|
+
name_form = NameForm(fullText=text)
|
341
347
|
name = Name(type=NameType.BirthName,nameForms=[name_form])
|
348
|
+
|
342
349
|
else:
|
343
350
|
name = Name()
|
344
351
|
return name
|
@@ -346,7 +353,7 @@ class Name(Conclusion):
|
|
346
353
|
def __init__(self, id: Optional[str] = None,
|
347
354
|
lang: Optional[str] = None,
|
348
355
|
sources: Optional[List[SourceReference]] = None,
|
349
|
-
analysis: Optional[Document
|
356
|
+
analysis: Optional[Union[Resource,Document]] = None,
|
350
357
|
notes: Optional[List[Note]] = None,
|
351
358
|
confidence: Optional[ConfidenceLevel] = None,
|
352
359
|
attribution: Optional[Attribution] = None,
|
@@ -358,6 +365,7 @@ class Name(Conclusion):
|
|
358
365
|
self.type = type
|
359
366
|
self.nameForms = nameForms if nameForms else []
|
360
367
|
self.date = date
|
368
|
+
self.id = id if id else None # no need for id
|
361
369
|
|
362
370
|
def _add_name_part(self, namepart: NamePart):
|
363
371
|
if namepart and isinstance(namepart, NamePart):
|
@@ -418,6 +426,7 @@ class Name(Conclusion):
|
|
418
426
|
f"date={self.date!r})"
|
419
427
|
)
|
420
428
|
|
429
|
+
|
421
430
|
class QuickName():
|
422
431
|
def __new__(cls,name: str) -> Name:
|
423
432
|
obj = Name(nameForms=[NameForm(fullText=name)])
|
gedcomx/note.py
CHANGED
@@ -9,6 +9,7 @@ from typing import Any, Optional
|
|
9
9
|
Created: 2025-08-25
|
10
10
|
Updated:
|
11
11
|
- 2025-09-03: _from_json_ refactor
|
12
|
+
- 2025-09-09: added schema_class
|
12
13
|
|
13
14
|
======================================================================
|
14
15
|
"""
|
@@ -19,6 +20,7 @@ GEDCOM Module Types
|
|
19
20
|
======================================================================
|
20
21
|
"""
|
21
22
|
from .attribution import Attribution
|
23
|
+
from .schemas import schema_class
|
22
24
|
from .logging_hub import hub, logging
|
23
25
|
"""
|
24
26
|
======================================================================
|
@@ -29,6 +31,7 @@ log = logging.getLogger("gedcomx")
|
|
29
31
|
serial_log = "gedcomx.serialization"
|
30
32
|
#=====================================================================
|
31
33
|
|
34
|
+
@schema_class()
|
32
35
|
class Note:
|
33
36
|
identifier = 'http://gedcomx.org/v1/Note'
|
34
37
|
version = 'http://gedcomx.org/conceptual-model/v1'
|
@@ -65,19 +68,31 @@ class Note:
|
|
65
68
|
return type_as_dict if type_as_dict != {} else None
|
66
69
|
return Serialization.serialize_dict(type_as_dict)
|
67
70
|
|
68
|
-
|
71
|
+
# ---- hashing & equality ----
|
72
|
+
@staticmethod
|
73
|
+
def _norm(s: str | None) -> str:
|
74
|
+
# normalize None -> "", strip outer whitespace
|
75
|
+
return (s or "").strip()
|
76
|
+
|
77
|
+
def _key(self) -> tuple:
|
78
|
+
# Base identity: language (case-insensitive), subject, text
|
79
|
+
base = (
|
80
|
+
self._norm(self.lang).casefold(),
|
81
|
+
self._norm(self.subject),
|
82
|
+
self._norm(self.text),
|
83
|
+
)
|
84
|
+
# If you want attribution to affect identity AND it has a stable id,
|
85
|
+
# uncomment the next 3 lines:
|
86
|
+
# a = self.attribution
|
87
|
+
# a_id = getattr(a, "id", None) if a is not None else None
|
88
|
+
# return base + (a_id,)
|
89
|
+
return base
|
90
|
+
|
91
|
+
def __eq__(self, other: object) -> bool:
|
69
92
|
if not isinstance(other, Note):
|
70
93
|
return NotImplemented
|
94
|
+
return self._key() == other._key()
|
71
95
|
|
72
|
-
def safe_str(val):
|
73
|
-
return val.strip() if isinstance(val, str) else ''
|
74
|
-
|
75
|
-
return (
|
76
|
-
#safe_str(self.lang) == safe_str(other.lang) and
|
77
|
-
#safe_str(self.subject) == safe_str(other.subject) and
|
78
|
-
safe_str(self.text) == safe_str(other.text) #and
|
79
|
-
# self.attribution == other.attribution # Assumes Attribution defines __eq__
|
80
|
-
)
|
81
96
|
|
82
97
|
@classmethod
|
83
98
|
def _from_json_(cls, data: Any, context=None) -> "Note":
|
gedcomx/online_account.py
CHANGED
@@ -1,6 +1,25 @@
|
|
1
1
|
from typing import Optional
|
2
|
+
"""
|
3
|
+
======================================================================
|
4
|
+
Project: Gedcom-X
|
5
|
+
File: online_account.py
|
6
|
+
Author: David J. Cartwright
|
7
|
+
Purpose:
|
8
|
+
|
9
|
+
Created: 2025-08-25
|
10
|
+
Updated:
|
11
|
+
- 2025-09-09: added schema_class
|
12
|
+
|
13
|
+
======================================================================
|
14
|
+
"""
|
2
15
|
|
16
|
+
"""
|
17
|
+
======================================================================
|
18
|
+
GEDCOM Module Types
|
19
|
+
======================================================================
|
20
|
+
"""
|
3
21
|
from .resource import Resource
|
22
|
+
from .schemas import schema_class
|
4
23
|
from .logging_hub import hub, logging
|
5
24
|
"""
|
6
25
|
======================================================================
|
@@ -11,6 +30,7 @@ log = logging.getLogger("gedcomx")
|
|
11
30
|
serial_log = "gedcomx.serialization"
|
12
31
|
#=====================================================================
|
13
32
|
|
33
|
+
@schema_class()
|
14
34
|
class OnlineAccount:
|
15
35
|
identifier = 'http://gedcomx.org/v1/OnlineAccount'
|
16
36
|
version = 'http://gedcomx.org/conceptual-model/v1'
|