gedcom-x 0.5.8__tar.gz → 0.5.10__tar.gz
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.8 → gedcom_x-0.5.10}/PKG-INFO +1 -1
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcom_x.egg-info/PKG-INFO +1 -1
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcom_x.egg-info/SOURCES.txt +5 -1
- gedcom_x-0.5.10/gedcomx/Extensions/rs10/rsLink.py +166 -0
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/__init__.py +4 -1
- gedcom_x-0.5.10/gedcomx/address.py +217 -0
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/agent.py +81 -24
- gedcom_x-0.5.10/gedcomx/attribution.py +115 -0
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/conclusion.py +98 -46
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/converter.py +209 -79
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/coverage.py +10 -1
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/date.py +42 -8
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/document.py +37 -7
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/event.py +77 -20
- gedcom_x-0.5.10/gedcomx/evidence_reference.py +20 -0
- gedcom_x-0.5.10/gedcomx/extensible.py +86 -0
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/fact.py +53 -54
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/gedcom.py +10 -0
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/gedcom5x.py +30 -20
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/gedcom7/GedcomStructure.py +1 -3
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/gedcom7/__init__.py +2 -2
- gedcom_x-0.5.8/gedcomx/gedcom7/Gedcom7.py → gedcom_x-0.5.10/gedcomx/gedcom7/gedcom7.py +3 -3
- gedcom_x-0.5.10/gedcomx/gedcom7/specification.py +4817 -0
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/gedcomx.py +95 -93
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/gender.py +21 -9
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/group.py +9 -0
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/identifier.py +47 -20
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/logging_hub.py +19 -0
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/mutations.py +10 -5
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/name.py +74 -33
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/note.py +50 -18
- gedcom_x-0.5.10/gedcomx/online_account.py +19 -0
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/person.py +46 -27
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/place_description.py +54 -8
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/place_reference.py +30 -8
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/qualifier.py +19 -3
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/relationship.py +55 -14
- gedcom_x-0.5.10/gedcomx/resource.py +112 -0
- gedcom_x-0.5.10/gedcomx/schemas.py +328 -0
- gedcom_x-0.5.10/gedcomx/serialization.py +794 -0
- gedcom_x-0.5.10/gedcomx/source_citation.py +37 -0
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/source_description.py +181 -94
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/source_reference.py +51 -16
- gedcom_x-0.5.10/gedcomx/subject.py +122 -0
- gedcom_x-0.5.10/gedcomx/textvalue.py +89 -0
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/translation.py +3 -3
- gedcom_x-0.5.10/gedcomx/uri.py +273 -0
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/pyproject.toml +1 -1
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/setup.py +1 -1
- gedcom_x-0.5.8/gedcomx/Extensions/rs10/rsLink.py +0 -116
- gedcom_x-0.5.8/gedcomx/address.py +0 -131
- gedcom_x-0.5.8/gedcomx/attribution.py +0 -91
- gedcom_x-0.5.8/gedcomx/evidence_reference.py +0 -11
- gedcom_x-0.5.8/gedcomx/gedcom7/Specification.py +0 -347
- gedcom_x-0.5.8/gedcomx/online_account.py +0 -10
- gedcom_x-0.5.8/gedcomx/resource.py +0 -85
- gedcom_x-0.5.8/gedcomx/serialization.py +0 -815
- gedcom_x-0.5.8/gedcomx/source_citation.py +0 -25
- gedcom_x-0.5.8/gedcomx/subject.py +0 -77
- gedcom_x-0.5.8/gedcomx/textvalue.py +0 -35
- gedcom_x-0.5.8/gedcomx/uri.py +0 -121
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/README.md +0 -0
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcom_x.egg-info/dependency_links.txt +0 -0
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcom_x.egg-info/top_level.txt +0 -0
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/Extensions/__init__.py +0 -0
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/Extensions/rs10/__init__.py +0 -0
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/Logging.py +0 -0
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/TopLevelTypeCollection.py +0 -0
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/Zip.py +0 -0
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/exceptions.py +0 -0
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/extensible_enum.py +0 -0
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/gedcom7/Exceptions.py +0 -0
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/gedcom7/g7interop.py +0 -0
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/gedcomx/gedcom7/logger.py +0 -0
- {gedcom_x-0.5.8 → gedcom_x-0.5.10}/setup.cfg +0 -0
@@ -49,6 +49,7 @@ gedcomx/document.py
|
|
49
49
|
gedcomx/event.py
|
50
50
|
gedcomx/evidence_reference.py
|
51
51
|
gedcomx/exceptions.py
|
52
|
+
gedcomx/extensible.py
|
52
53
|
gedcomx/extensible_enum.py
|
53
54
|
gedcomx/fact.py
|
54
55
|
gedcomx/gedcom.py
|
@@ -68,6 +69,7 @@ gedcomx/place_reference.py
|
|
68
69
|
gedcomx/qualifier.py
|
69
70
|
gedcomx/relationship.py
|
70
71
|
gedcomx/resource.py
|
72
|
+
gedcomx/schemas.py
|
71
73
|
gedcomx/serialization.py
|
72
74
|
gedcomx/source_citation.py
|
73
75
|
gedcomx/source_description.py
|
@@ -85,4 +87,6 @@ gedcomx/gedcom7/GedcomStructure.py
|
|
85
87
|
gedcomx/gedcom7/Specification.py
|
86
88
|
gedcomx/gedcom7/__init__.py
|
87
89
|
gedcomx/gedcom7/g7interop.py
|
88
|
-
gedcomx/gedcom7/
|
90
|
+
gedcomx/gedcom7/gedcom7.py
|
91
|
+
gedcomx/gedcom7/logger.py
|
92
|
+
gedcomx/gedcom7/specification.py
|
@@ -0,0 +1,166 @@
|
|
1
|
+
from typing import Any, Dict, Iterable, Iterator, List, Optional, Tuple, Union
|
2
|
+
"""
|
3
|
+
======================================================================
|
4
|
+
Project: Gedcom-X
|
5
|
+
File: rsLink.py
|
6
|
+
Author: David J. Cartwright
|
7
|
+
Purpose: Link type of GedcomX RS (Extension)
|
8
|
+
|
9
|
+
Created: 2025-08-25
|
10
|
+
Updated:
|
11
|
+
-
|
12
|
+
|
13
|
+
======================================================================
|
14
|
+
"""
|
15
|
+
|
16
|
+
"""
|
17
|
+
======================================================================
|
18
|
+
GEDCOM Module Types
|
19
|
+
======================================================================
|
20
|
+
"""
|
21
|
+
from ...exceptions import GedcomClassAttributeError
|
22
|
+
from ...logging_hub import hub, logging
|
23
|
+
from ...uri import URI
|
24
|
+
"""
|
25
|
+
======================================================================
|
26
|
+
Logging
|
27
|
+
======================================================================
|
28
|
+
"""
|
29
|
+
log = logging.getLogger("gedcomx")
|
30
|
+
serial_log = "gedcomx.serialization"
|
31
|
+
deserial_log = "gedcomx.deserialization"
|
32
|
+
#=====================================================================
|
33
|
+
|
34
|
+
|
35
|
+
class rsLink():
|
36
|
+
"""A link description object. RS Extension to GedcomX by FamilySearch.
|
37
|
+
|
38
|
+
Args:
|
39
|
+
rel (str): Link relation identifier. Required.
|
40
|
+
href (str, optional): Link target URI. If omitted, provide `template`.
|
41
|
+
template (str, optional): URI Template (see RFC 6570). If omitted, provide `href`.
|
42
|
+
type (str, optional): Media type(s) of the linked resource (RFC 2616 §3.7).
|
43
|
+
accept (str, optional): Acceptable media type(s) for updating the linked resource (RFC 2616 §3.7).
|
44
|
+
allow (str, optional): Allowable HTTP methods to transition to the linked resource (RFC 2616 §14.7).
|
45
|
+
hreflang (str, optional): Language of the linked resource (e.g., BCP-47 tag).
|
46
|
+
title (str, optional): Human-readable label for the link.
|
47
|
+
|
48
|
+
Raises:
|
49
|
+
ValueError: If neither `href` nor `template` is provided.
|
50
|
+
"""
|
51
|
+
|
52
|
+
"""Attribution Information for a Genealogy, Conclusion, Subject and child classes
|
53
|
+
|
54
|
+
Args:
|
55
|
+
contributor (Agent, optional): Contributor to object being attributed.
|
56
|
+
modified (timestamp, optional): timestamp for when this record was modified.
|
57
|
+
changeMessage (str, optional): Birth date (YYYY-MM-DD).
|
58
|
+
creator (Agent, optional): Creator of object being attributed.
|
59
|
+
created (timestamp, optional): timestamp for when this record was created
|
60
|
+
|
61
|
+
Raises:
|
62
|
+
|
63
|
+
"""
|
64
|
+
identifier = "http://gedcomx.org/v1/Link"
|
65
|
+
|
66
|
+
def __init__(self,
|
67
|
+
href: Optional[URI] = None,
|
68
|
+
template: Optional[str] = None,
|
69
|
+
type: Optional[str] = None,
|
70
|
+
accept: Optional[str] = None,
|
71
|
+
allow: Optional[str] = None,
|
72
|
+
hreflang: Optional[str] = None,
|
73
|
+
title: Optional[str] = None) -> None:
|
74
|
+
|
75
|
+
|
76
|
+
self.href = href if isinstance(href,URI) else URI.from_url(href) if isinstance(href,str) else None
|
77
|
+
self.template = template
|
78
|
+
self.type = type
|
79
|
+
self.accept = accept
|
80
|
+
self.allow = allow
|
81
|
+
self.hreflang = hreflang
|
82
|
+
self.title = title
|
83
|
+
|
84
|
+
if self.href is None: # and self.template is None:
|
85
|
+
raise GedcomClassAttributeError("href or template are required")
|
86
|
+
|
87
|
+
def __str__(self) -> str:
|
88
|
+
def to_text(v):
|
89
|
+
if v is None:
|
90
|
+
return None
|
91
|
+
# unwrap URI-like objects
|
92
|
+
if isinstance(v, URI):
|
93
|
+
return getattr(v, "value", None) or str(v)
|
94
|
+
# normalize strings (skip empty/whitespace-only)
|
95
|
+
if isinstance(v, str):
|
96
|
+
s = v.strip()
|
97
|
+
return s or None
|
98
|
+
return str(v)
|
99
|
+
|
100
|
+
parts = []
|
101
|
+
|
102
|
+
# show href as the primary bit if present
|
103
|
+
href_s = to_text(self.href)
|
104
|
+
if href_s:
|
105
|
+
parts.append(href_s)
|
106
|
+
|
107
|
+
# show other fields as key=value
|
108
|
+
for name in ("template", "type", "accept", "allow", "hreflang", "title"):
|
109
|
+
val = to_text(getattr(self, name, None))
|
110
|
+
if val:
|
111
|
+
parts.append(f"{name}={val}")
|
112
|
+
|
113
|
+
return " | ".join(parts) if parts else self.__class__.__name__
|
114
|
+
|
115
|
+
|
116
|
+
|
117
|
+
@classmethod
|
118
|
+
def _from_json_(cls, data: Any, context: Any = None) -> "rsLink":
|
119
|
+
"""
|
120
|
+
Build an rsLink from JSON.
|
121
|
+
|
122
|
+
Accepted shapes:
|
123
|
+
- {"rel": "self", "href": "https://..."}
|
124
|
+
- {"rel": {...}, "href": {...}} # URI objects as dicts
|
125
|
+
- {"href": "https://...", "type": "...", ...} # rel optional
|
126
|
+
- {"uri": "https://..."} or {"url": "..."} # href aliases
|
127
|
+
- "https://example.com" # shorthand -> href only
|
128
|
+
|
129
|
+
Note:
|
130
|
+
- `rel` is coerced to a URI if possible.
|
131
|
+
- `href` is coerced to a URI (string/dict supported).
|
132
|
+
- If both `href` and `template` are missing, __init__ will raise.
|
133
|
+
"""
|
134
|
+
# Shorthand: bare string is an href
|
135
|
+
|
136
|
+
if not isinstance(data, dict):
|
137
|
+
raise TypeError(f"{cls.__name__}._from_json_ expected dict or str, got {type(data)}")
|
138
|
+
|
139
|
+
print("LINK DATA:",data)
|
140
|
+
# Extract with common aliases
|
141
|
+
rel = data.get("rel")
|
142
|
+
href = data.get("href")
|
143
|
+
|
144
|
+
|
145
|
+
|
146
|
+
return cls(
|
147
|
+
rel=rel,
|
148
|
+
href=href,
|
149
|
+
template=data.get("template"),
|
150
|
+
type=data.get("type"),
|
151
|
+
accept=data.get("accept"),
|
152
|
+
allow=data.get("allow"),
|
153
|
+
hreflang=data.get("hreflang"),
|
154
|
+
title=data.get("title"),
|
155
|
+
)
|
156
|
+
|
157
|
+
|
158
|
+
class _rsLinks():
|
159
|
+
|
160
|
+
def __init__(self,
|
161
|
+
person: rsLink | None = None,
|
162
|
+
portrait: rsLink | None= None
|
163
|
+
) -> None:
|
164
|
+
|
165
|
+
self.person = person
|
166
|
+
self.portrait = portrait
|
@@ -1,3 +1,4 @@
|
|
1
|
+
from .schemas import SCHEMA
|
1
2
|
from .agent import Agent
|
2
3
|
from .address import Address
|
3
4
|
from .attribution import Attribution
|
@@ -12,6 +13,8 @@ from .extensible_enum import ExtensibleEnum
|
|
12
13
|
from .event import Event
|
13
14
|
from .event import EventType
|
14
15
|
from .event import EventRole
|
16
|
+
from .extensible import Extensible
|
17
|
+
from. extensible import _ExtraField
|
15
18
|
from .fact import Fact
|
16
19
|
from .fact import FactQualifier
|
17
20
|
from .fact import FactType
|
@@ -43,7 +46,7 @@ from .uri import URI
|
|
43
46
|
|
44
47
|
from .Extensions.rs10.rsLink import rsLink
|
45
48
|
|
46
|
-
from .gedcom7.
|
49
|
+
from .gedcom7.gedcom7 import Gedcom7, GedcomStructure
|
47
50
|
from .translation import g7toXtable
|
48
51
|
|
49
52
|
|
@@ -0,0 +1,217 @@
|
|
1
|
+
from typing import Any, Dict, Optional, List
|
2
|
+
|
3
|
+
"""
|
4
|
+
======================================================================
|
5
|
+
Project: Gedcom-X
|
6
|
+
File: address.py
|
7
|
+
Author: David J. Cartwright
|
8
|
+
Purpose:
|
9
|
+
|
10
|
+
Created: 2025-08-25
|
11
|
+
Updated:
|
12
|
+
- 2025-09-03: _from_json_ refactoring
|
13
|
+
|
14
|
+
======================================================================
|
15
|
+
"""
|
16
|
+
|
17
|
+
"""
|
18
|
+
======================================================================
|
19
|
+
GEDCOM Module Types
|
20
|
+
======================================================================
|
21
|
+
"""
|
22
|
+
from .logging_hub import hub, logging
|
23
|
+
"""
|
24
|
+
======================================================================
|
25
|
+
Logging
|
26
|
+
======================================================================
|
27
|
+
"""
|
28
|
+
log = logging.getLogger("gedcomx")
|
29
|
+
serial_log = "gedcomx.serialization"
|
30
|
+
#=====================================================================
|
31
|
+
|
32
|
+
class Address:
|
33
|
+
"""A GedcomX Address Data Type
|
34
|
+
A GedcomX Address Data Type.
|
35
|
+
|
36
|
+
Represents a postal address according to the GedcomX conceptual model.
|
37
|
+
|
38
|
+
Args:
|
39
|
+
value (str, optional): A complete address as a single string.
|
40
|
+
city (str, optional): Name of the city or town.
|
41
|
+
country (str, optional): Name of the country.
|
42
|
+
postalCode (str, optional): Postal or ZIP code.
|
43
|
+
stateOrProvince (str, optional): Name of the state, province, or region.
|
44
|
+
street (str, optional): First street address line.
|
45
|
+
street2 (str, optional): Second street address line.
|
46
|
+
street3 (str, optional): Third street address line.
|
47
|
+
street4 (str, optional): Fourth street address line.
|
48
|
+
street5 (str, optional): Fifth street address line.
|
49
|
+
street6 (str, optional): Sixth street address line.
|
50
|
+
"""
|
51
|
+
|
52
|
+
identifier = "http://gedcomx.org/v1/Address"
|
53
|
+
version = 'http://gedcomx.org/conceptual-model/v1'
|
54
|
+
|
55
|
+
def __init__(self, value: Optional[str] = None,
|
56
|
+
city: Optional[str] = None,
|
57
|
+
country: Optional[str] = None,
|
58
|
+
postalCode: Optional[str] = None,
|
59
|
+
stateOrProvince: Optional[str] = None,
|
60
|
+
street: Optional[str] = None,
|
61
|
+
street2: Optional[str] = None,
|
62
|
+
street3: Optional[str] = None,
|
63
|
+
street4: Optional[str] = None,
|
64
|
+
street5: Optional[str] = None,
|
65
|
+
street6: Optional[str] = None):
|
66
|
+
|
67
|
+
self._value = value #TODO impliment a parser for date strings.
|
68
|
+
self.city = city
|
69
|
+
self.country = country
|
70
|
+
self.postalCode = postalCode
|
71
|
+
self.stateOrProvince = stateOrProvince
|
72
|
+
self.street = street
|
73
|
+
self.street2 = street2
|
74
|
+
self.street3 = street3
|
75
|
+
self.street4 = street4
|
76
|
+
self.street5 = street5
|
77
|
+
self.street6 = street6
|
78
|
+
|
79
|
+
@property
|
80
|
+
def value(self) -> str:
|
81
|
+
return ', '.join(filter(None, [
|
82
|
+
self.street, self.street2, self.street3,
|
83
|
+
self.street4, self.street5, self.street6,
|
84
|
+
self.city, self.stateOrProvince,
|
85
|
+
self.postalCode, self.country
|
86
|
+
]))
|
87
|
+
|
88
|
+
@value.setter
|
89
|
+
def value(self,value: str):
|
90
|
+
self._value = value
|
91
|
+
return
|
92
|
+
raise NotImplementedError("Parsing of a full address is not implimented.")
|
93
|
+
|
94
|
+
def _append(self,value):
|
95
|
+
if self._value:
|
96
|
+
self._value = self._value + ' ' + value
|
97
|
+
else:
|
98
|
+
self._value = value
|
99
|
+
|
100
|
+
def __eq__(self, other):
|
101
|
+
if not isinstance(other, self.__class__):
|
102
|
+
return False
|
103
|
+
|
104
|
+
return (
|
105
|
+
self.value == other.value and
|
106
|
+
self.city == other.city and
|
107
|
+
self.country == other.country and
|
108
|
+
self.postalCode == other.postalCode and
|
109
|
+
self.stateOrProvince == other.stateOrProvince and
|
110
|
+
self.street == other.street and
|
111
|
+
self.street2 == other.street2 and
|
112
|
+
self.street3 == other.street3 and
|
113
|
+
self.street4 == other.street4 and
|
114
|
+
self.street5 == other.street5 and
|
115
|
+
self.street6 == other.street6
|
116
|
+
)
|
117
|
+
|
118
|
+
def __str__(self) -> str:
|
119
|
+
# Combine non-empty address components into a formatted string
|
120
|
+
parts = [
|
121
|
+
self._value,
|
122
|
+
self.street,
|
123
|
+
self.street2,
|
124
|
+
self.street3,
|
125
|
+
self.street4,
|
126
|
+
self.street5,
|
127
|
+
self.street6,
|
128
|
+
self.city,
|
129
|
+
self.stateOrProvince,
|
130
|
+
self.postalCode,
|
131
|
+
self.country
|
132
|
+
]
|
133
|
+
|
134
|
+
# Filter out any parts that are None or empty strings
|
135
|
+
filtered_parts = [str(part) for part in parts if part]
|
136
|
+
|
137
|
+
# Join the remaining parts with a comma and space
|
138
|
+
return ', '.join(filtered_parts)
|
139
|
+
|
140
|
+
@property
|
141
|
+
def _as_dict_(self):
|
142
|
+
with hub.use(serial_log):
|
143
|
+
log.debug(f"Serializing 'Address' with value: '{self.value}'")
|
144
|
+
type_as_dict = {}
|
145
|
+
if self.city: type_as_dict["city"] = self.city
|
146
|
+
if self.country: type_as_dict["country"] = self.country
|
147
|
+
if self.postalCode: type_as_dict["postalCode"] = self.postalCode
|
148
|
+
if self.stateOrProvince: type_as_dict["stateOrProvince"] = self.stateOrProvince
|
149
|
+
if self.street: type_as_dict["street"] = self.street
|
150
|
+
if self.street2: type_as_dict["street2"] = self.street2
|
151
|
+
if self.street3: type_as_dict["street3"] = self.street3
|
152
|
+
if self.street4: type_as_dict["street4"] = self.street4
|
153
|
+
if self.street5: type_as_dict["street5"] = self.street5
|
154
|
+
if self.street6: type_as_dict["street6"] = self.street6
|
155
|
+
log.debug(f"'Address' serialized with fields: '{type_as_dict.keys()}'")
|
156
|
+
if type_as_dict == {} or len(type_as_dict.keys()) == 0: log.warning("serializing and empty 'Address' Object")
|
157
|
+
|
158
|
+
return type_as_dict if type_as_dict != {} else None
|
159
|
+
|
160
|
+
|
161
|
+
@classmethod
|
162
|
+
def _from_json_(cls, data: Any, context: Any = None) -> "Address":
|
163
|
+
"""
|
164
|
+
Build an Address from JSON.
|
165
|
+
Supports:
|
166
|
+
- Shorthand string -> value
|
167
|
+
- Aliases: postal_code/postal -> postalCode; state/province -> stateOrProvince
|
168
|
+
- Line aliases: line1..line6 / address1..address6 / addr1..addr6 -> street..street6
|
169
|
+
- 'lines': [..] list -> street..street6
|
170
|
+
"""
|
171
|
+
if data is None: return None
|
172
|
+
|
173
|
+
if not isinstance(data, dict):
|
174
|
+
raise TypeError(f"{cls.__name__}._from_json_ expected dict or str, got {type(data)}")
|
175
|
+
|
176
|
+
address_data: Dict[str, Any] = {}
|
177
|
+
|
178
|
+
# Freeform value (accept a few aliases)
|
179
|
+
if (v := data.get("value")) is None:
|
180
|
+
address_data["value"] = str(v)
|
181
|
+
|
182
|
+
# Simple scalars
|
183
|
+
if (city := data.get("city")) is not None:
|
184
|
+
address_data["city"] = city
|
185
|
+
if (country := data.get("country")) is not None:
|
186
|
+
address_data["country"] = country
|
187
|
+
|
188
|
+
# Postal code (aliases)
|
189
|
+
if (postal := data.get("postalCode")) is not None:
|
190
|
+
address_data["postalCode"] = postal
|
191
|
+
|
192
|
+
# State / Province (aliases)
|
193
|
+
if (stateprov := data.get("stateOrProvince")) is not None:
|
194
|
+
address_data["stateOrProvince"] = stateprov
|
195
|
+
|
196
|
+
if data.get("street") is not None:
|
197
|
+
address_data["street"] = data["street"]
|
198
|
+
|
199
|
+
if data.get("street2") is not None:
|
200
|
+
address_data["street2"] = data["street2"]
|
201
|
+
|
202
|
+
if data.get("street3") is not None:
|
203
|
+
address_data["street3"] = data["street3"]
|
204
|
+
|
205
|
+
if data.get("street4") is not None:
|
206
|
+
address_data["street4"] = data["street4"]
|
207
|
+
|
208
|
+
if data.get("street5") is not None:
|
209
|
+
address_data["street5"] = data["street5"]
|
210
|
+
|
211
|
+
if data.get("street6") is not None:
|
212
|
+
address_data["street6"] = data["street6"]
|
213
|
+
|
214
|
+
|
215
|
+
return cls(**address_data)
|
216
|
+
|
217
|
+
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import base64
|
2
2
|
import uuid
|
3
3
|
|
4
|
-
from typing import List, Optional
|
4
|
+
from typing import Any, Dict, List, Optional
|
5
5
|
"""
|
6
6
|
======================================================================
|
7
7
|
Project: Gedcom-X
|
@@ -11,7 +11,7 @@ from typing import List, Optional
|
|
11
11
|
|
12
12
|
Created: 2025-08-25
|
13
13
|
Updated:
|
14
|
-
-
|
14
|
+
- 2025-09-03: _from_json_ refactor
|
15
15
|
|
16
16
|
======================================================================
|
17
17
|
"""
|
@@ -22,11 +22,20 @@ GEDCOM Module Type Imports
|
|
22
22
|
======================================================================
|
23
23
|
"""
|
24
24
|
from .address import Address
|
25
|
-
from .identifier import Identifier, IdentifierList
|
25
|
+
from .identifier import Identifier, IdentifierList, make_uid
|
26
26
|
from .online_account import OnlineAccount
|
27
27
|
from .resource import Resource
|
28
28
|
from .textvalue import TextValue
|
29
29
|
from .uri import URI
|
30
|
+
from .logging_hub import hub, logging
|
31
|
+
"""
|
32
|
+
======================================================================
|
33
|
+
Logging
|
34
|
+
======================================================================
|
35
|
+
"""
|
36
|
+
log = logging.getLogger("gedcomx")
|
37
|
+
serial_log = "gedcomx.serialization"
|
38
|
+
#=====================================================================
|
30
39
|
|
31
40
|
|
32
41
|
class Agent:
|
@@ -61,17 +70,7 @@ class Agent:
|
|
61
70
|
attribution (Attribution, optional): Attribution information related to the agent.
|
62
71
|
uri (Resource, optional): A URI reference for this agent.
|
63
72
|
"""
|
64
|
-
|
65
|
-
@staticmethod
|
66
|
-
def default_id_generator():
|
67
|
-
# Generate a standard UUID
|
68
|
-
standard_uuid = uuid.uuid4()
|
69
|
-
# Convert UUID to bytes
|
70
|
-
uuid_bytes = standard_uuid.bytes
|
71
|
-
# Encode bytes to a Base64 string
|
72
|
-
short_uuid = base64.urlsafe_b64encode(uuid_bytes).rstrip(b'=').decode('utf-8')
|
73
|
-
return short_uuid
|
74
|
-
|
73
|
+
|
75
74
|
def __init__(self, id: Optional[str] = None,
|
76
75
|
identifiers: Optional[IdentifierList] = None,
|
77
76
|
names: Optional[List[TextValue]] = [],
|
@@ -86,9 +85,8 @@ class Agent:
|
|
86
85
|
attribution: Optional[object] = None, # Added for compatibility with GEDCOM5/7 Imports
|
87
86
|
uri: Optional[URI | Resource] = None):
|
88
87
|
|
89
|
-
|
90
|
-
|
91
|
-
self.id = id if id else None #TODO self._id_generator()
|
88
|
+
|
89
|
+
self.id = id if id else make_uid()
|
92
90
|
self.identifiers = identifiers or IdentifierList()
|
93
91
|
self.names = names if names else []
|
94
92
|
self.homepage = homepage or None
|
@@ -164,16 +162,75 @@ class Agent:
|
|
164
162
|
type_as_dict["addresses"] = [address._as_dict_ for address in self.addresses if address]
|
165
163
|
if self.xnotes:
|
166
164
|
type_as_dict["notes"] = [note._as_dict_() for note in self.xnotes if note]
|
165
|
+
return type_as_dict if type_as_dict != {} else None
|
167
166
|
return Serialization.serialize_dict(type_as_dict)
|
168
167
|
|
169
168
|
@classmethod
|
170
|
-
def _from_json_(cls, data:
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
169
|
+
def _from_json_(cls, data: Any, context: Any = None) -> "Agent":
|
170
|
+
|
171
|
+
if not isinstance(data, dict):
|
172
|
+
raise TypeError(f"{cls.__name__}._from_json_ expected dict or str, got {type(data)}")
|
173
|
+
|
174
|
+
agent_data: Dict[str, Any] = {}
|
175
|
+
|
176
|
+
# ── Scalars ──────────────────────────────────────────────────────────────
|
177
|
+
if (id_ := data.get("id")) is not None:
|
178
|
+
agent_data["id"] = id_
|
179
|
+
|
180
|
+
# ── Objects ─────────────────────────────────────────────────────────────
|
181
|
+
if (identifiers := data.get("identifiers")) is not None:
|
182
|
+
agent_data["identifiers"] = IdentifierList._from_json_(identifiers, context)
|
183
|
+
|
184
|
+
# homepage / openid / uri: accept string or dict
|
185
|
+
if (homepage := data.get("homepage")) is not None:
|
186
|
+
agent_data["homepage"] = URI.from_url_(homepage) if isinstance(homepage, str) else URI._from_json_(homepage, context)
|
187
|
+
|
188
|
+
if (openid := data.get("openid")) is not None:
|
189
|
+
agent_data["openid"] = URI.from_url_(openid) if isinstance(openid, str) else URI._from_json_(openid, context)
|
190
|
+
|
191
|
+
if (uri := data.get("uri")) is not None:
|
192
|
+
if isinstance(uri, str):
|
193
|
+
agent_data["uri"] = URI.from_url_(uri)
|
194
|
+
else: raise ValueError()
|
195
|
+
|
196
|
+
# person can be a full Person object or a Resource/URI reference
|
197
|
+
if (person := data.get("person")) is not None:
|
198
|
+
if isinstance(person, dict):
|
199
|
+
agent_data["person"] = Resource._from_json_(person, context)
|
200
|
+
else:
|
201
|
+
raise ValueError()
|
202
|
+
|
203
|
+
# attribution (GEDCOM5/7 compatibility): try Attribution if shape matches; otherwise store as-is
|
204
|
+
if (attr := data.get("attribution")) is not None:
|
205
|
+
"""
|
206
|
+
======================================================================
|
207
|
+
GEDCOM Module Type Imports
|
208
|
+
======================================================================
|
209
|
+
"""
|
210
|
+
from .attribution import Attribution
|
211
|
+
#======================================================================
|
212
|
+
if isinstance(attr, dict) and any(k in attr for k in ("contributor", "created", "modified")):
|
213
|
+
agent_data["attribution"] = Attribution._from_json_(attr, context)
|
214
|
+
else:
|
215
|
+
raise ValueError()
|
216
|
+
|
217
|
+
# ── Lists ───────────────────────────────────────────────────────────────
|
218
|
+
if (names := data.get("names")) is not None:
|
219
|
+
agent_data["names"] = [TextValue._from_json_(n, context) if isinstance(n, (dict,)) else TextValue(n) for n in names]
|
220
|
+
|
221
|
+
if (accounts := data.get("accounts")) is not None:
|
222
|
+
agent_data["accounts"] = [OnlineAccount._from_json_(a, context) for a in accounts]
|
223
|
+
|
224
|
+
if (emails := data.get("emails")) is not None:
|
225
|
+
agent_data["emails"] = [URI.from_url_(e) if isinstance(e, str) else URI._from_json_(e, context) for e in emails]
|
226
|
+
|
227
|
+
if (phones := data.get("phones")) is not None:
|
228
|
+
agent_data["phones"] = [URI.from_url_(p) if isinstance(p, str) else URI._from_json_(p, context) for p in phones]
|
229
|
+
|
230
|
+
if (addresses := data.get("addresses")) is not None:
|
231
|
+
agent_data["addresses"] = [Address._from_json_(a, context) for a in addresses]
|
232
|
+
|
233
|
+
return cls(**agent_data)
|
177
234
|
|
178
235
|
def __str__(self):
|
179
236
|
"""
|