gedcom-x 0.5.7__py3-none-any.whl → 0.5.9__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.
Files changed (69) hide show
  1. {gedcom_x-0.5.7.dist-info → gedcom_x-0.5.9.dist-info}/METADATA +1 -1
  2. gedcom_x-0.5.9.dist-info/RECORD +56 -0
  3. gedcomx/Extensions/rs10/rsLink.py +110 -60
  4. gedcomx/TopLevelTypeCollection.py +1 -1
  5. gedcomx/__init__.py +43 -42
  6. gedcomx/address.py +217 -0
  7. gedcomx/{Agent.py → agent.py} +107 -34
  8. gedcomx/attribution.py +115 -0
  9. gedcomx/{Conclusion.py → conclusion.py} +120 -51
  10. gedcomx/{Converter.py → converter.py} +261 -116
  11. gedcomx/coverage.py +64 -0
  12. gedcomx/{Date.py → date.py} +43 -9
  13. gedcomx/{Document.py → document.py} +60 -12
  14. gedcomx/{Event.py → event.py} +88 -31
  15. gedcomx/evidence_reference.py +20 -0
  16. gedcomx/{Fact.py → fact.py} +81 -74
  17. gedcomx/{Gedcom.py → gedcom.py} +10 -0
  18. gedcomx/{Gedcom5x.py → gedcom5x.py} +31 -21
  19. gedcomx/gedcom7/Exceptions.py +9 -0
  20. gedcomx/gedcom7/GedcomStructure.py +94 -0
  21. gedcomx/gedcom7/Specification.py +347 -0
  22. gedcomx/gedcom7/__init__.py +26 -0
  23. gedcomx/gedcom7/g7interop.py +205 -0
  24. gedcomx/gedcom7/gedcom7.py +160 -0
  25. gedcomx/gedcom7/logger.py +19 -0
  26. gedcomx/{GedcomX.py → gedcomx.py} +109 -106
  27. gedcomx/gender.py +91 -0
  28. gedcomx/group.py +72 -0
  29. gedcomx/{Identifier.py → identifier.py} +48 -21
  30. gedcomx/{LoggingHub.py → logging_hub.py} +19 -0
  31. gedcomx/{Mutations.py → mutations.py} +59 -30
  32. gedcomx/{Name.py → name.py} +88 -47
  33. gedcomx/note.py +105 -0
  34. gedcomx/online_account.py +19 -0
  35. gedcomx/{Person.py → person.py} +61 -41
  36. gedcomx/{PlaceDescription.py → place_description.py} +71 -23
  37. gedcomx/{PlaceReference.py → place_reference.py} +32 -10
  38. gedcomx/{Qualifier.py → qualifier.py} +20 -4
  39. gedcomx/relationship.py +156 -0
  40. gedcomx/resource.py +112 -0
  41. gedcomx/serialization.py +794 -0
  42. gedcomx/source_citation.py +37 -0
  43. gedcomx/source_description.py +401 -0
  44. gedcomx/{SourceReference.py → source_reference.py} +56 -21
  45. gedcomx/subject.py +122 -0
  46. gedcomx/textvalue.py +89 -0
  47. gedcomx/{Translation.py → translation.py} +4 -4
  48. gedcomx/uri.py +273 -0
  49. gedcom_x-0.5.7.dist-info/RECORD +0 -49
  50. gedcomx/Address.py +0 -131
  51. gedcomx/Attribution.py +0 -91
  52. gedcomx/Coverage.py +0 -37
  53. gedcomx/EvidenceReference.py +0 -11
  54. gedcomx/Gender.py +0 -65
  55. gedcomx/Group.py +0 -37
  56. gedcomx/Note.py +0 -73
  57. gedcomx/OnlineAccount.py +0 -10
  58. gedcomx/Relationship.py +0 -97
  59. gedcomx/Resource.py +0 -85
  60. gedcomx/Serialization.py +0 -816
  61. gedcomx/SourceCitation.py +0 -25
  62. gedcomx/SourceDescription.py +0 -314
  63. gedcomx/Subject.py +0 -59
  64. gedcomx/TextValue.py +0 -35
  65. gedcomx/URI.py +0 -105
  66. {gedcom_x-0.5.7.dist-info → gedcom_x-0.5.9.dist-info}/WHEEL +0 -0
  67. {gedcom_x-0.5.7.dist-info → gedcom_x-0.5.9.dist-info}/top_level.txt +0 -0
  68. /gedcomx/{Exceptions.py → exceptions.py} +0 -0
  69. /gedcomx/{ExtensibleEnum.py → extensible_enum.py} +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gedcom-x
3
- Version: 0.5.7
3
+ Version: 0.5.9
4
4
  Summary: Python implimentation of gedcom-x standard
5
5
  Author-email: "David J. Cartwright" <davidcartwright@hotmail.com>
6
6
  License: MIT
@@ -0,0 +1,56 @@
1
+ gedcomx/Logging.py,sha256=vBDOjawVXc4tCge1laYjy6_2Ves-fnGzG0m6NnLZejE,624
2
+ gedcomx/TopLevelTypeCollection.py,sha256=p99i-O5LXiXe3GlC6jWuz4nH1TAcKxOLbe0VRxWbSFY,1495
3
+ gedcomx/Zip.py,sha256=lBxcv-Vip45884EHj56wZJJ5I36Q38UuHUidDxQBoS8,14
4
+ gedcomx/__init__.py,sha256=8qEa_oYCC1nMyeMt0zNmsqXnocfA2q6VNUlp0cCnMLI,1723
5
+ gedcomx/address.py,sha256=7tGUPRItlSiUZ6-WaDNj4sbjvU8W_f2kw-I8s_nntTU,8049
6
+ gedcomx/agent.py,sha256=HxDRB8axhReUmTDtn0XFNemG6znjJQezi-qxO2JRfCg,12803
7
+ gedcomx/attribution.py,sha256=S648H7it_780WLA0c4dkBSrjeXIMz_YVMk1eJ6wZk6Q,4586
8
+ gedcomx/conclusion.py,sha256=BpLFZY0p02yGa3H9Fis2eZz8NYBi7YsiLbaX6N7ZICM,11167
9
+ gedcomx/converter.py,sha256=_VzdkjS52AVXrtKUAzgI6VGDaw7Wysa76B1Vt1XnNEs,57637
10
+ gedcomx/coverage.py,sha256=XmBiTGxuH9WoeXumrgu03EMWtccHe982hFJu2Coxceo,2206
11
+ gedcomx/date.py,sha256=xiAatY25DXTW8usmhbyx_6rmEae3FJnmR5FriGkCN80,3560
12
+ gedcomx/document.py,sha256=quaa1N1XcqhMdpTwCzpvnZfRgygK1mRhFPWyX09ZrWw,4555
13
+ gedcomx/event.py,sha256=7Iogi3eVmR4_-qg6seIo7orAmVS1ENvUQcJO3_tZGlc,15417
14
+ gedcomx/evidence_reference.py,sha256=vtP0rYpehfuu4Jugf1V9u5V_wgPu-2EPb7mqPLS_9cI,696
15
+ gedcomx/exceptions.py,sha256=0OdPM3euhBMgX8o61ZwPuKeN8zPuSuuDcSBFflVGFqk,587
16
+ gedcomx/extensible_enum.py,sha256=DftCZLMBNul3C9hwh-rf0GE3SVdvylvyd5mt7bX_l6o,6535
17
+ gedcomx/fact.py,sha256=bEAUKvLpn7ZAJjyOE57dcvOnvkdzZ63GqF3yb2nGhX0,24972
18
+ gedcomx/gedcom.py,sha256=A8GQ670E6_knfP37kOGHdVl_cC9RXQoc4Dk6D_qlnuQ,2059
19
+ gedcomx/gedcom5x.py,sha256=ajjLeTlncrSgw3JSBRo8Ip9aZik2NHnUt1iDhjc9-mE,23645
20
+ gedcomx/gedcomx.py,sha256=PunMwjHIE4fp7O7sjuNrI8p44_4Y3pI43FQSCh7pyqE,19329
21
+ gedcomx/gender.py,sha256=d2AEvTSa28sfQ1910X-Ysfi7MNDRMQ35EA7jcBYsLck,3178
22
+ gedcomx/group.py,sha256=I4vdBBsdMdqAMigkzGg4mRxrCAjvsJJ-bIa_aR6CDhA,2879
23
+ gedcomx/identifier.py,sha256=hCLfBuHVTVwa2VGD_2NFsiftFkqS3-7W8XMnyjmPZbM,9603
24
+ gedcomx/logging_hub.py,sha256=DbYGwMdVD_nEj0xpVzHjsMVTi_WjttLLRexiebezfzM,8307
25
+ gedcomx/mutations.py,sha256=ugQgfQh0o9r8FIB_dz5s6KqPyIDMHn2YSP7DvAHsDv0,7236
26
+ gedcomx/name.py,sha256=5UpNgFPBfTfzC5yLQDqmeSJ-Fg1T6Nio9vEa6f5kA2Y,19501
27
+ gedcomx/note.py,sha256=joDgKrDS9pbOEEve9LGopP2Gz48kFKqMeHYR72Z7bA0,3813
28
+ gedcomx/online_account.py,sha256=EYMW-CepfwppaJu5RiPQOK63YKxnjE8bVOPUNQqbrMM,639
29
+ gedcomx/person.py,sha256=QALVxY2NlZ93BU0QR9Q4Ykm4kZ-7j4j4QTVXo4hpO60,8993
30
+ gedcomx/place_description.py,sha256=vFCVW37tKBosYCGttkXppR7yDXA-L8YVLpixSN-8b4E,7564
31
+ gedcomx/place_reference.py,sha256=XE-tyRsvfXVK--vdiA_Pvj0ZrUecl72JldbV-c-k_PU,2939
32
+ gedcomx/qualifier.py,sha256=Lg5D_CRLKxaUHcCGLzXhKmpMkmdOeQso0V-XN3yH2n4,2352
33
+ gedcomx/relationship.py,sha256=_wA1NujEqQO0HzSfjlfLcl-yCFWBcOsRyxs3klr0HfE,5852
34
+ gedcomx/resource.py,sha256=ShK3sQOaNI4ZlrIUcepL7_uLd_QX4zMx-A1SU2In_m4,3608
35
+ gedcomx/serialization.py,sha256=Pg_tDO5wUXDgyMqVGPwAlm2WdBBNUA0L-TBaRSWhaNo,30308
36
+ gedcomx/source_citation.py,sha256=QSh1zHK9DsgjQu6oHPDTCReLVs2HP8YDR-NlLAG-pek,1237
37
+ gedcomx/source_description.py,sha256=A2R1T26HYqpk8Q4W8BYp7zlvHYqycHkUMX94uQRpwV8,19258
38
+ gedcomx/source_reference.py,sha256=iakI-vLMXbSMuZLDU2c4J5s-XDanOCUJvV2Mz8uRP_8,6895
39
+ gedcomx/subject.py,sha256=dgStcWiI3a3VX2QEgUIQNbo8HELD2sFUhYT-VI46ND8,5117
40
+ gedcomx/textvalue.py,sha256=Y-SDRpHR2gGpWIyaOvTYrM5EbfOUeX7Tnh_7iOITAbk,3135
41
+ gedcomx/translation.py,sha256=4REL68WaWqcC5f3XcBCZ1dn0gtaZftQJTCkvl1Sf-NA,61395
42
+ gedcomx/uri.py,sha256=ceCcbbrx99QekivhQ_xfMvkmaUKiW_IF21WKTOFBtJg,9693
43
+ gedcomx/Extensions/__init__.py,sha256=MQzi_whzlxiLiknUNh10hG8OVrNqJE38l6n-AwCssx8,24
44
+ gedcomx/Extensions/rs10/__init__.py,sha256=nSHoZiD8hsCAyE-KyRTuWSLqSJSFh12kSz7hqilAMps,26
45
+ gedcomx/Extensions/rs10/rsLink.py,sha256=IdmcG1pVicxX91ZMu9fUPwKYoXCl0Q60inGwiThDAVE,6004
46
+ gedcomx/gedcom7/Exceptions.py,sha256=xeKr4x8b7r8pOqJ9yMpsCVTyxPeOlREDGgKoM5rX4U0,149
47
+ gedcomx/gedcom7/GedcomStructure.py,sha256=ZKNoEcXc41KdrCLPx-A8ohOU9VYmXAkFc4xuZExsBPw,3435
48
+ gedcomx/gedcom7/Specification.py,sha256=qIBe9wzL1GB0l0NyetS1ncbhz5C44b9nMyjAxHuqMt8,9245
49
+ gedcomx/gedcom7/__init__.py,sha256=tPHWW9kchPFEZ5sfURWPPzK8ZBimsQn7SRDULAW1Yc4,679
50
+ gedcomx/gedcom7/g7interop.py,sha256=hSzwqeok2n7xziEvN2QiJY7bVCWrOnZIZWXubnkrv7w,9945
51
+ gedcomx/gedcom7/gedcom7.py,sha256=i_g9W0qsZQYLMoD2sBCA13ibKRYFnSf4uj9-ix_tE4Q,5614
52
+ gedcomx/gedcom7/logger.py,sha256=QM1SySyh91UEhs90d2DMhH-s9qGF8XS8I8gr1eOcmfw,617
53
+ gedcom_x-0.5.9.dist-info/METADATA,sha256=Wn8DcPMkZvmTZRrnNlo4GzUlwRYGSRbKJABN7oVYTaw,4332
54
+ gedcom_x-0.5.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
55
+ gedcom_x-0.5.9.dist-info/top_level.txt,sha256=smVBF4nxSU-mzCd6idtRYTbYjPICMMi8pTqewEmqF8Y,8
56
+ gedcom_x-0.5.9.dist-info/RECORD,,
@@ -1,19 +1,35 @@
1
- '''
2
- The "Link" Data Type
3
- The Link data type defines a representation of an available transition from one application state to another.
4
- The base definition of a link is provided by RFC 5988.
5
-
6
- Instances of Link can be reasonably expected as extension elements to any GEDCOM X data type,
7
- except data types that are defined by the GEDCOM X Conceptual Model to explicitly restrict extension properties.
8
- '''
9
-
10
- '''
11
- 8/25/25, 0.5.5, built serialization, type checking in init for href
12
- '''
13
-
14
- from typing import List, Optional
15
- from ...URI import URI
16
- from ...Exceptions import GedcomClassAttributeError
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
+ #=====================================================================
17
33
 
18
34
 
19
35
  class rsLink():
@@ -47,7 +63,7 @@ class rsLink():
47
63
  """
48
64
  identifier = "http://gedcomx.org/v1/Link"
49
65
 
50
- def __init__(self,rel: Optional[URI] = None,
66
+ def __init__(self,
51
67
  href: Optional[URI] = None,
52
68
  template: Optional[str] = None,
53
69
  type: Optional[str] = None,
@@ -56,7 +72,7 @@ class rsLink():
56
72
  hreflang: Optional[str] = None,
57
73
  title: Optional[str] = None) -> None:
58
74
 
59
- self.rel = rel
75
+
60
76
  self.href = href if isinstance(href,URI) else URI.from_url(href) if isinstance(href,str) else None
61
77
  self.template = template
62
78
  self.type = type
@@ -65,52 +81,86 @@ class rsLink():
65
81
  self.hreflang = hreflang
66
82
  self.title = title
67
83
 
68
- if self.href is None and self.template is None:
84
+ if self.href is None: # and self.template is None:
69
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
+
70
115
 
71
- @property
72
- def _as_dict_(self):
73
- from ...Serialization import Serialization
74
- type_as_dict = {
75
- "rel": self.rel._as_dict_ if self.rel else None,
76
- "href": self.href._as_dict_ if self.href else None,
77
- "template": self.template, # RFC 6570 template if used
78
- "type": self.type, # media type (note: shadows built-in 'type')
79
- "accept": self.accept,
80
- "allow": self.allow,
81
- "hreflang": self.hreflang,
82
- "title": self.title,
83
- }
84
- return Serialization.serialize_dict(type_as_dict)
85
116
 
86
- @property
87
- def json(self):
88
- import json
89
- return json.dumps(self._as_dict_)
90
-
91
- class _rsLinkList():
92
- def __init__(self) -> None:
93
- self.links = {}
94
-
95
- def add(self,link: rsLink):
96
- if link and isinstance(link,rsLink):
97
- if link.rel in self.links.keys():
98
- self.links[link.rel].append(link.href)
99
- else:
100
- self.links[link.rel] = [link.href]
101
-
102
-
103
117
  @classmethod
104
- def _from_json_(cls,data: dict):
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
105
135
 
106
- link_list = _rsLinkList()
107
- for rel in data.keys():
108
- link_list.add(rsLink(rel,data[rel]))
109
- return link_list
110
-
111
- @property
112
- def _as_dict_(self) -> dict:
113
- return self.links
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
+
114
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
+ )
115
156
 
116
-
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,4 +1,4 @@
1
- from .TextValue import TextValue
1
+ from .textvalue import TextValue
2
2
 
3
3
  class TopLevelTypeCollection:
4
4
  def __init__(self):
gedcomx/__init__.py CHANGED
@@ -1,49 +1,50 @@
1
- from .Agent import Agent
2
- from .Address import Address
3
- from .Attribution import Attribution
4
- from .Conclusion import Conclusion
5
- from .Converter import GedcomConverter
6
- from .Coverage import Coverage
7
- from .Date import Date
8
- from .Document import Document
9
- from .Document import DocumentType
10
- from .EvidenceReference import EvidenceReference
11
- from .ExtensibleEnum import ExtensibleEnum
12
- from .Event import Event
13
- from .Event import EventType
14
- from .Event import EventRole
15
- from .Fact import Fact
16
- from .Fact import FactQualifier
17
- from .Fact import FactType
18
- from .Gedcom import Gedcom
19
- from .Gedcom5x import Gedcom5x, Gedcom5xRecord
20
- from .GedcomX import GedcomX
21
- from .Gender import Gender, GenderType
22
- from .Group import Group, GroupRole
23
- from .Identifier import Identifier, IdentifierType, IdentifierList
1
+ from .agent import Agent
2
+ from .address import Address
3
+ from .attribution import Attribution
4
+ from .conclusion import Conclusion
5
+ from .converter import GedcomConverter
6
+ from .coverage import Coverage
7
+ from .date import Date
8
+ from .document import Document
9
+ from .document import DocumentType
10
+ from .evidence_reference import EvidenceReference
11
+ from .extensible_enum import ExtensibleEnum
12
+ from .event import Event
13
+ from .event import EventType
14
+ from .event import EventRole
15
+ from .fact import Fact
16
+ from .fact import FactQualifier
17
+ from .fact import FactType
18
+ from .gedcom import Gedcom
19
+ from .gedcom5x import Gedcom5x, Gedcom5xRecord
20
+ from .gedcomx import GedcomX
21
+ from .gender import Gender, GenderType
22
+ from .group import Group, GroupRole
23
+ from .identifier import Identifier, IdentifierType, IdentifierList
24
24
  from .Logging import get_logger
25
- from .Name import Name, NameForm, NamePart, NamePartType, NameType, NamePartQualifier
26
- from .Note import Note
27
- from .OnlineAccount import OnlineAccount
28
- from .Person import Person, QuickPerson
29
- from .PlaceDescription import PlaceDescription
30
- from .PlaceReference import PlaceReference
31
- from .Qualifier import Qualifier
32
- from .Relationship import Relationship, RelationshipType
33
- from .Serialization import Serialization
34
- from .SourceCitation import SourceCitation
35
- from .SourceDescription import SourceDescription
36
- from .SourceDescription import ResourceType
37
- from .SourceReference import SourceReference
38
- from .Subject import Subject
39
- from .TextValue import TextValue
40
- from .Resource import Resource
41
- from .URI import URI
25
+ from .name import Name, NameForm, NamePart, NamePartType, NameType, NamePartQualifier
26
+ from .note import Note
27
+ from .online_account import OnlineAccount
28
+ from .person import Person, QuickPerson
29
+ from .place_description import PlaceDescription
30
+ from .place_reference import PlaceReference
31
+ from .qualifier import Qualifier
32
+ from .relationship import Relationship, RelationshipType
33
+ from .serialization import Serialization
34
+ from .source_citation import SourceCitation
35
+ from .source_description import SourceDescription
36
+ from .source_description import ResourceType
37
+ from .source_reference import SourceReference
38
+ from .subject import Subject
39
+ from .textvalue import TextValue
40
+ from .resource import Resource
41
+ from .uri import URI
42
+
42
43
 
43
44
  from .Extensions.rs10.rsLink import rsLink
44
45
 
45
- from .gedcom7 import Gedcom7, GedcomStructure
46
- from .Translation import g7toXtable
46
+ from .gedcom7.gedcom7 import Gedcom7, GedcomStructure
47
+ from .translation import g7toXtable
47
48
 
48
49
 
49
50
 
gedcomx/address.py ADDED
@@ -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
+