gedcom-x 0.5.5__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.
@@ -0,0 +1,144 @@
1
+ Metadata-Version: 2.4
2
+ Name: gedcom-x
3
+ Version: 0.5.7
4
+ Summary: Python implimentation of gedcom-x standard
5
+ Author-email: "David J. Cartwright" <davidcartwright@hotmail.com>
6
+ License: MIT
7
+ Keywords: gedcom,gedcomx,ged
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Environment :: Console
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Topic :: Utilities
14
+ Classifier: Topic :: Software Development :: User Interfaces
15
+ Requires-Python: >=3.6
16
+ Description-Content-Type: text/markdown
17
+ Dynamic: requires-python
18
+
19
+ # GEDCOM-X Python Toolkit (gedcom-x beta 0.5.5)
20
+
21
+ A lightweight, class-based Python implementation of the [GEDCOM-X data model](https://github.com/FamilySearch/gedcomx).
22
+
23
+ ## ⚠️ Project Status
24
+
25
+ This project is currently in **beta**.
26
+ While the core GEDCOM-X classes and serialization are functional, some features may not be fully implemented or may not behave exactly as expected.
27
+
28
+ - Certain GEDCOM 7 tags are not yet mapped
29
+ - Some classes may be missing methods or fields
30
+ - Error handling and validation are still evolving
31
+ - Backward compatibility is **not guaranteed** until the first stable release
32
+
33
+ ### ✅ What You Can Do
34
+ - Create and manipulate GEDCOM-X objects in Python
35
+ - Serialize and deserialize data to/from JSON
36
+ - Experimentally convert GEDCOM 5x & 7 files into GEDCOM-X JSON
37
+ - Extend the classes to handle new GEDCOM tags or custom attributes
38
+ - Use the library as a foundation for genealogy-related tooling or RAG pipelines
39
+
40
+ ### ❌ What You Can’t Do (Yet)
41
+ - Rely on complete coverage of all GEDCOM 7 tags
42
+ - Expect perfect compliance with the GEDCOM-X specification
43
+ - Assume strong validation or error recovery on malformed input
44
+ - Use it as a drop-in replacement for production genealogy software
45
+ - Write GEDCOM-X to GEDCOM 5x / 7
46
+ - Create Graphs from Genealogies
47
+
48
+ Contributors and testers are welcome — feedback will help stabilize the library!
49
+
50
+ ---
51
+
52
+ This library aims to provide:
53
+
54
+ - Python classes for every GEDCOM-X type (Person, Fact, Source, etc.)
55
+ - Extensibility, with current GEDCOM RS etc, extension built in
56
+ - Serialization and Deserialization to/from GEDCOM-X JSON
57
+ - Utilities to convert GEDCOM 5x & 7 GEDCOM Files into GEDCOM-X and back
58
+ - Type-safe field definitions and extensibility hooks for future tags
59
+
60
+ ---
61
+
62
+ ## Features
63
+
64
+ - **Complete GEDCOM-X Class Coverage**
65
+ Each GEDCOM-X type is represented as a Python class with fields and types.
66
+
67
+ - **Serialization / Deserialization**
68
+ Every class can serialize to JSON and reconstruct from JSON via `_as_dict_()` and `_from_json()` methods.
69
+
70
+ - **Type Checking & Enum Validation**
71
+ Uses Python type hints and enums to ensure correct values (e.g. FactType, EventType, ConfidenceLevel).
72
+
73
+ - **Composable / Nestable Classes**
74
+ Nested objects (e.g. Person → Name → NameForm → TextValue) are constructed and validated recursively.
75
+
76
+ - **GEDCOM 7 → GEDCOM-X Conversion**
77
+ Experimental parser to read GEDCOM 7 files and convert them into structured GEDCOM-X JSON.
78
+
79
+ ---
80
+
81
+ ## Installation
82
+
83
+ Clone the repository and install dependencies:
84
+
85
+ ```bash
86
+ git clone https://github.com/yourusername/gedcom-x.git
87
+ cd gedcom-x
88
+ pip install -r requirements.txt
89
+ ```
90
+ or
91
+ ```
92
+ pip install gedcom-x
93
+ ```
94
+ ---
95
+
96
+ ## Examples
97
+
98
+ <details>
99
+
100
+ <summary>Create a Person Gedcom-X Type</summary>
101
+
102
+ ```python
103
+ import json
104
+ from gedcomx import Person, Name, NameForm, TextValue
105
+
106
+ person = Person(
107
+ id="P-123",
108
+ names=[Name(
109
+ nameForms=[NameForm(
110
+ fullText=TextValue(value="John Doe")
111
+ )]
112
+ )]
113
+ )
114
+
115
+ print(json.dumps(person._as_dict_,indent=4))
116
+ ```
117
+ result
118
+ ```text
119
+ {
120
+ "id": "P-123",
121
+ "lang": "en",
122
+ "private": false,
123
+ "living": false,
124
+ "gender": {
125
+ "lang": "en",
126
+ "type": "http://gedcomx.org/Unknown"
127
+ },
128
+ "names": [
129
+ {
130
+ "lang": "en",
131
+ "nameForms": [
132
+ {
133
+ "lang": "en",
134
+ "fullText": {
135
+ "lang": "en",
136
+ "value": "John Doe"
137
+ }
138
+ }
139
+ ]
140
+ }
141
+ ]
142
+ }
143
+
144
+ </details>
@@ -0,0 +1,49 @@
1
+ gedcomx/Address.py,sha256=HPh-YZEkcxhzGGUxDWrrmAvKsyL__RthMJl6vXcMA80,4865
2
+ gedcomx/Agent.py,sha256=u7zIr6SNYREDhceOkj09uS6zjl3RXbix5QlQB7mxjz4,8844
3
+ gedcomx/Attribution.py,sha256=JESWw3U_momYDkQ6vknvaAW9DdIWwRRdQJwdD0UPAEI,3381
4
+ gedcomx/Conclusion.py,sha256=9VWEu6lphB0Y8L3YH3t2QcXt5xW5NdnSKXpa9eujU30,8781
5
+ gedcomx/Converter.py,sha256=aM8i8qIhzc9pJ3k75hFQSBkeRax2brucTefPYxENQHI,50968
6
+ gedcomx/Coverage.py,sha256=VR_lQDUgJ9rI-hHzOA8GxS_I1C_Du_3SvG57dzulXNI,1361
7
+ gedcomx/Date.py,sha256=I11mm2YEqUenxt-skphCHO-GWKPQrpMomOL_Utapzy0,2344
8
+ gedcomx/Document.py,sha256=DjL3HwpyCF9cOCdvpvrLu4Mxp7ZIYT1AkQEJGB3TSjY,2967
9
+ gedcomx/Event.py,sha256=j34nM5pZcmfSthdI87be6cGKGemHJky5xVaajmeSVK4,13321
10
+ gedcomx/EvidenceReference.py,sha256=WXZpmcKzwb3lYQmGVf-TY2IsbNG3fIInPY1khRjtOSg,348
11
+ gedcomx/Exceptions.py,sha256=0OdPM3euhBMgX8o61ZwPuKeN8zPuSuuDcSBFflVGFqk,587
12
+ gedcomx/ExtensibleEnum.py,sha256=DftCZLMBNul3C9hwh-rf0GE3SVdvylvyd5mt7bX_l6o,6535
13
+ gedcomx/Fact.py,sha256=r1Ct96moCIXCAd24Ho2aLCI12PGH2AoKIyjACtJT7go,24458
14
+ gedcomx/Gedcom.py,sha256=l_BuLBynQacDtpLjhs2Afrabfiqt2Opoo_5_7myI7Z4,1709
15
+ gedcomx/Gedcom5x.py,sha256=lbIEvK3h2b-IJoO9Fgmq_qc3nItzB_qTonqLafxYfXk,23137
16
+ gedcomx/GedcomX.py,sha256=p0PeJqS4bKXGsXY487JOdGNK4vIwARX7YeTDIqPeNNw,19000
17
+ gedcomx/Gender.py,sha256=vBkYJqhf1mN58Wrw9MvhbVJDNw1pM4-ktFW6mYja_Rw,2285
18
+ gedcomx/Group.py,sha256=VNI_cI_-YnJ9cHzwMmHD8qwDNMe89kEocPuQ67rwStA,1606
19
+ gedcomx/Identifier.py,sha256=1uhNO4Ll-_xLyVM-IYdywz7wTEwHEH44xx4JQId9F9c,8491
20
+ gedcomx/Logging.py,sha256=vBDOjawVXc4tCge1laYjy6_2Ves-fnGzG0m6NnLZejE,624
21
+ gedcomx/LoggingHub.py,sha256=f4z1r6WL3TdL1kg1eaCfCk7XEn9epCH5zUCXd1OT4mc,7793
22
+ gedcomx/Mutations.py,sha256=wtmoaiVS9Vw7Dzpfq34VQETBAVFvQnIlD5rbtSTopfs,6314
23
+ gedcomx/Name.py,sha256=esVS1nqvo5F5ggKQhnW4DYvww_hjXQF91oayLGG4LuU,18661
24
+ gedcomx/Note.py,sha256=hCwLDJYEQ-kR1-3rknHTx_nLq6ytBzi23sQukD8HHlg,2547
25
+ gedcomx/OnlineAccount.py,sha256=P24o98IXo_8XQoICYZPgALjdzS0q8t2l4DU3Od9KxyI,291
26
+ gedcomx/Person.py,sha256=tygyfHWqhbrH3z7TKPAyjJK14M7qu04mCGgXMgi7J_g,8057
27
+ gedcomx/PlaceDescription.py,sha256=oUih1T8jsUVcxdevPGuFpQOc34t4HoYc9ldr87ZH0IA,5591
28
+ gedcomx/PlaceReference.py,sha256=20HmaQUAlPrTjsra38j22NBsNqDBnvHZ5NuP6YzlizA,2111
29
+ gedcomx/Qualifier.py,sha256=iivgraoayJTYZUlkbSAKPzQ7msPYpBvTlmsEsXxz3zw,1778
30
+ gedcomx/Relationship.py,sha256=Et_72kRohBmWpWra2fS_-ha0YvEDZTZqkoD8HrerQW0,3693
31
+ gedcomx/Resource.py,sha256=HIOoKlVZtEp13j7sn4xkqRwRrYadqpiXswKpdJRe2Ns,2674
32
+ gedcomx/Serialization.py,sha256=ZIECHrG-PRZuS2SHGtfv1eg_2Rt-PHPbz4ivF7eH4p8,34041
33
+ gedcomx/SourceCitation.py,sha256=aW-lEb7bT9QU49GiBjJppFMBvtisR6fhVVuXjr5y4vQ,742
34
+ gedcomx/SourceDescription.py,sha256=yUBJXsn64OL0MAWSc3QPWrLt8RKYa1omGGp0FOLo-n8,14929
35
+ gedcomx/SourceReference.py,sha256=d5kNxqalCFlbRWD2kSJAMCexOrH_s1oXu9y_RUmCGa0,5189
36
+ gedcomx/Subject.py,sha256=2xPZiNjZDYSwLkloUPKDRa7F8_ql5zYMFJLS_9ayUtk,2609
37
+ gedcomx/TextValue.py,sha256=6B0wMxL0nigFNzhXZDhbTONvFGbnM2t2NcDZiZuu4Zw,1112
38
+ gedcomx/TopLevelTypeCollection.py,sha256=nvTO6GwFwEZk9jX4fVqhy75ygsshomNb20tnlExKqyY,1495
39
+ gedcomx/Translation.py,sha256=TZZTfuzjsx2glnUJwPrs0TrrShvLjygYUQ6cxhZXnMc,61398
40
+ gedcomx/URI.py,sha256=AuIECTwHUkp-2z0Q9iuA3Me1PiawFKZKCR4sAA64FE8,4270
41
+ gedcomx/Zip.py,sha256=lBxcv-Vip45884EHj56wZJJ5I36Q38UuHUidDxQBoS8,14
42
+ gedcomx/__init__.py,sha256=DfKVj46fhYtpM45NZnK333iDpt2WQ1XJXWsLcVCARNo,1704
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=uVmdTJl_atheO-pRpP3a5uUrazudf4R_tZvWSZIQdNk,4353
46
+ gedcom_x-0.5.7.dist-info/METADATA,sha256=bLAwaXozcWmQ0blAY2igIeEa3Lh-gQjX7NiPHuGESNs,4332
47
+ gedcom_x-0.5.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
48
+ gedcom_x-0.5.7.dist-info/top_level.txt,sha256=smVBF4nxSU-mzCd6idtRYTbYjPICMMi8pTqewEmqF8Y,8
49
+ gedcom_x-0.5.7.dist-info/RECORD,,
gedcomx/Address.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from typing import Optional
2
2
  import json
3
3
 
4
- from .Serialization import Serialization
4
+
5
5
 
6
6
  class Address:
7
7
  """A GedcomX Address Data Type
@@ -113,18 +113,18 @@ class Address:
113
113
 
114
114
  @property
115
115
  def _as_dict_(self):
116
- type_as_dict = {
117
- "city": self.city if self.city else None,
118
- "country": self.country if self.country else None,
119
- "postalCode": self.postalCode if self.postalCode else None,
120
- "stateOrProvince": self.stateOrProvince if self.stateOrProvince else None,
121
- "street": self.street if self.street else None,
122
- "street2": self.street2 if self.street2 else None,
123
- "street3": self.street3 if self.street3 else None,
124
- "street4": self.street4 if self.street4 else None,
125
- "street5": self.street5 if self.street5 else None,
126
- "street6": self.street6 if self.street6 else None
127
- }
116
+ from .Serialization import Serialization
117
+ type_as_dict = {}
118
+ if self.city: type_as_dict["city"] = self.city
119
+ if self.country: type_as_dict["country"] = self.country
120
+ if self.postalCode: type_as_dict["postalCode"] = self.postalCode
121
+ if self.stateOrProvince: type_as_dict["stateOrProvince"] = self.stateOrProvince
122
+ if self.street: type_as_dict["street"] = self.street
123
+ if self.street2: type_as_dict["street2"] = self.street2
124
+ if self.street3: type_as_dict["street3"] = self.street3
125
+ if self.street4: type_as_dict["street4"] = self.street4
126
+ if self.street5: type_as_dict["street5"] = self.street5
127
+ if self.street6: type_as_dict["street6"] = self.street6
128
128
 
129
129
  return Serialization.serialize_dict(type_as_dict)
130
130
 
gedcomx/Agent.py CHANGED
@@ -11,7 +11,7 @@ from .OnlineAccount import OnlineAccount
11
11
  from .TextValue import TextValue
12
12
  from .Resource import Resource
13
13
  from .URI import URI
14
- from .Serialization import Serialization
14
+
15
15
 
16
16
 
17
17
  class Agent:
@@ -82,9 +82,10 @@ class Agent:
82
82
  self.emails = emails or []
83
83
  self.phones = phones or []
84
84
  self.addresses = addresses if addresses else []
85
- self.xnotes = []
85
+ self.person = person
86
+ self.notes = []
86
87
  self.attribution = attribution or None
87
- self.uri = URI(fragment=self.id)
88
+ self.uri = URI(fragment=self.id) if self.id else None
88
89
 
89
90
  def _append_to_name(self, text_to_append: str):
90
91
  if self.names and self.names[0] and self.names[0].value:
@@ -113,7 +114,7 @@ class Agent:
113
114
  else:
114
115
  raise ValueError(f'name must be of type str or TextValue, recived {type(name_to_add)}')
115
116
 
116
- def add_note(self, note_to_add: 'Note'):
117
+ def add_note(self, note_to_add):
117
118
  from .Note import Note
118
119
  if note_to_add and isinstance(note_to_add,Note):
119
120
  self.xnotes.append(note_to_add)
@@ -125,18 +126,29 @@ class Agent:
125
126
 
126
127
  @property
127
128
  def _as_dict_(self):
128
- type_as_dict = {
129
- "id": self.id if self.id else None,
130
- "identifiers": self.identifiers._as_dict_ if self.identifiers else None,
131
- "names": [name._as_dict_ for name in self.names],
132
- "homepage": self.homepage if self.homepage else None,
133
- "openid": self.openid if self.openid else None,
134
- "accounts": self.accounts if self.accounts else None,
135
- "emails": self.emails if self.emails else None,
136
- "phones": self.phones if self.phones else None,
137
- "addresses": [address._as_dict_ for address in self.addresses],
138
- "notes": [note._as_dict_ for note in self.xnotes]
139
- }
129
+ from .Serialization import Serialization
130
+ type_as_dict = {}
131
+
132
+ if self.id:
133
+ type_as_dict["id"] = self.id
134
+ if self.identifiers:
135
+ type_as_dict["identifiers"] = self.identifiers._as_dict_
136
+ if self.names:
137
+ type_as_dict["names"] = [name._as_dict_ for name in self.names if name]
138
+ if self.homepage:
139
+ type_as_dict["homepage"] = self.homepage
140
+ if self.openid:
141
+ type_as_dict["openid"] = self.openid
142
+ if self.accounts:
143
+ type_as_dict["accounts"] = self.accounts
144
+ if self.emails:
145
+ type_as_dict["emails"] = self.emails
146
+ if self.phones:
147
+ type_as_dict["phones"] = self.phones
148
+ if self.addresses:
149
+ type_as_dict["addresses"] = [address._as_dict_ for address in self.addresses if address]
150
+ if self.notes:
151
+ type_as_dict["notes"] = [note._as_dict_() for note in self.notes if note]
140
152
  return Serialization.serialize_dict(type_as_dict)
141
153
 
142
154
  @classmethod
gedcomx/Attribution.py CHANGED
@@ -2,9 +2,30 @@
2
2
  from datetime import datetime
3
3
  from typing import Optional, Dict, Any
4
4
 
5
+ """
6
+ ======================================================================
7
+ Project: Gedcom-X
8
+ File: Attribution.py
9
+ Author: David J. Cartwright
10
+ Purpose:
11
+
12
+ Created: 2025-08-25
13
+ Updated:
14
+ - 2025-08-31: fixed _as_dict_ to deal with Resources and ignore empty fields
15
+
16
+ ======================================================================
17
+ """
18
+
19
+ """
20
+ ======================================================================
21
+ GEDCOM Module Types
22
+ ======================================================================
23
+ """
24
+
5
25
  from .Agent import Agent
6
- from .Resource import Resource, get_resource_as_dict
7
- from .Serialization import Serialization
26
+ from .Resource import Resource
27
+ #=====================================================================
28
+
8
29
 
9
30
  class Attribution:
10
31
  """Attribution Information for a Genealogy, Conclusion, Subject and child classes
@@ -39,12 +60,18 @@ class Attribution:
39
60
  """
40
61
  Serialize Attribution to a JSON-ready dict, skipping None values.
41
62
  """
63
+ from Serialization import Serialization
42
64
  type_as_dict: Dict[str, Any] = {}
43
- type_as_dict['contributor'] = get_resource_as_dict(self.contributor)
44
- type_as_dict['modified'] = self.modified if self.modified else None
45
- type_as_dict['changeMessage'] = self.changeMessage if self.changeMessage else None
46
- type_as_dict['creator'] = get_resource_as_dict(self.creator)
47
- type_as_dict['created'] = self.created if self.created else None
65
+ if self.contributor:
66
+ type_as_dict['contributor'] = Resource(target=self.contributor)._as_dict_
67
+ if self.modified:
68
+ type_as_dict['modified'] = self.modified if self.modified else None
69
+ if self.changeMessage:
70
+ type_as_dict['changeMessage'] = self.changeMessage if self.changeMessage else None
71
+ if self.creator:
72
+ type_as_dict['creator'] = Resource(target=self.creator)._as_dict_
73
+ if self.created:
74
+ type_as_dict['created'] = self.created if self.created else None
48
75
 
49
76
  return Serialization.serialize_dict(type_as_dict)
50
77
 
gedcomx/Conclusion.py CHANGED
@@ -8,7 +8,7 @@ from .Attribution import Attribution
8
8
  #from .Document import Document
9
9
  from .Note import Note
10
10
  from .Qualifier import Qualifier
11
- from .Serialization import Serialization
11
+
12
12
  from .SourceReference import SourceReference
13
13
  from .Resource import Resource, URI
14
14
  from .Extensions.rs10.rsLink import _rsLinkList, rsLink
@@ -96,7 +96,7 @@ class Conclusion:
96
96
  Args:
97
97
  id (str, optional): A unique identifier for the conclusion. If not provided,
98
98
  a UUID-based identifier will be automatically generated.
99
- lang (str, optional): The language code of the conclusion. Defaults to 'en'.
99
+ lang (str, optional): The language code of the conclusion.
100
100
  sources (list[SourceReference], optional): A list of source references that
101
101
  support the conclusion.
102
102
  analysis (Document | Resource, optional): A reference to an analysis document
@@ -127,7 +127,7 @@ class Conclusion:
127
127
 
128
128
  def __init__(self,
129
129
  id: Optional[str] = None,
130
- lang: Optional[str] = 'en',
130
+ lang: Optional[str] = None,
131
131
  sources: Optional[List[SourceReference]] = None,
132
132
  analysis: Optional[object | Resource] = None,
133
133
  notes: Optional[List[Note]] = None,
@@ -188,16 +188,27 @@ class Conclusion:
188
188
 
189
189
  @property
190
190
  def _as_dict_(self):
191
- type_as_dict = {
192
- 'id':self.id,
193
- 'lang':self.lang,
194
- 'sources': [source._as_dict_ for source in self.sources] if self.sources else None,
195
- 'analysis': self.analysis if self.analysis else None,
196
- 'notes': [note for note in self.notes] if self.notes else None,
197
- 'confidence':self.confidence,
198
- 'attribution':self.attribution,
199
- 'links':self.links._as_dict_ if self.links else None
200
- }
191
+ from .Serialization import Serialization
192
+ type_as_dict = {}
193
+
194
+ if self.id:
195
+ type_as_dict['id'] = self.id
196
+ if self.lang:
197
+ type_as_dict['lang'] = self.lang
198
+ if self.sources:
199
+ type_as_dict['sources'] = [s._as_dict_ for s in self.sources if s]
200
+ if self.analysis:
201
+ type_as_dict['analysis'] = getattr(self.analysis, '_as_dict_', self.analysis)
202
+ if self.notes:
203
+ type_as_dict['notes'] = [
204
+ (n._as_dict_ if hasattr(n, '_as_dict_') else n) for n in self.notes if n
205
+ ]
206
+ if self.confidence is not None:
207
+ type_as_dict['confidence'] = self.confidence
208
+ if self.attribution:
209
+ type_as_dict['attribution'] = getattr(self.attribution, '_as_dict_', self.attribution)
210
+ if self.links:
211
+ type_as_dict['links'] = self.links._as_dict_
201
212
 
202
213
  return Serialization.serialize_dict(type_as_dict)
203
214