gedcom-x 0.5__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.dist-info/METADATA +17 -0
- gedcom_x-0.5.dist-info/RECORD +37 -0
- gedcom_x-0.5.dist-info/WHEEL +5 -0
- gedcom_x-0.5.dist-info/top_level.txt +1 -0
- gedcomx/Address.py +100 -0
- gedcomx/Agent.py +83 -0
- gedcomx/Attribution.py +116 -0
- gedcomx/Conclusion.py +137 -0
- gedcomx/Coverage.py +26 -0
- gedcomx/Date.py +29 -0
- gedcomx/Document.py +42 -0
- gedcomx/Event.py +195 -0
- gedcomx/EvidenceReference.py +11 -0
- gedcomx/Fact.py +462 -0
- gedcomx/Gedcom.py +345 -0
- gedcomx/GedcomX.py +1105 -0
- gedcomx/Gender.py +48 -0
- gedcomx/Group.py +37 -0
- gedcomx/Identifier.py +89 -0
- gedcomx/Name.py +241 -0
- gedcomx/Note.py +65 -0
- gedcomx/OnlineAccount.py +10 -0
- gedcomx/Person.py +178 -0
- gedcomx/PlaceDescription.py +47 -0
- gedcomx/PlaceReference.py +31 -0
- gedcomx/Qualifier.py +27 -0
- gedcomx/Relationship.py +116 -0
- gedcomx/Serialization.py +37 -0
- gedcomx/SourceCitation.py +20 -0
- gedcomx/SourceDescription.py +241 -0
- gedcomx/SourceReference.py +168 -0
- gedcomx/Subject.py +73 -0
- gedcomx/TextValue.py +34 -0
- gedcomx/TopLevelTypeCollection.py +47 -0
- gedcomx/URI.py +70 -0
- gedcomx/_Resource.py +11 -0
- gedcomx/__init__.py +39 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: gedcom-x
|
3
|
+
Version: 0.5
|
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
|
@@ -0,0 +1,37 @@
|
|
1
|
+
gedcomx/Address.py,sha256=gAi9vfSLdTBAsNpCNIV9mdOaB9ewoPpW-7Xrt6JYsPc,3672
|
2
|
+
gedcomx/Agent.py,sha256=lp_--92eEo1df0nBqV2pJJafv_Zfzfz_bNj-UlJIjJk,3478
|
3
|
+
gedcomx/Attribution.py,sha256=yVctLjdgDWp4d-uOOxHXsCyJvvGLyzkY-x2ysiYv4yc,4367
|
4
|
+
gedcomx/Conclusion.py,sha256=hymaHcHk2JayGAGpxOlJvxzxnDjPh2q67eXI9TQk50c,5240
|
5
|
+
gedcomx/Coverage.py,sha256=FuVsLDAhzS4HjNHKOHCjwQcvnxd4Uf_EeF3QcDEbems,912
|
6
|
+
gedcomx/Date.py,sha256=A21KaTl44dLnMuB2hAcZ7QbqmgaQC27LoNPl4uI3i48,741
|
7
|
+
gedcomx/Document.py,sha256=F04hRAddU5OmwAeULbXJRRAPh5CJIzeaaxFK6TI48d0,1850
|
8
|
+
gedcomx/Event.py,sha256=qZNGEYdS3jmKI0fnw1DM5zETTw7Du07A_1tr3sc02-w,9538
|
9
|
+
gedcomx/EvidenceReference.py,sha256=8LNyfnjLmXt4RbOHpRyw-VHyD2CCm33pPYuYqWIaxtg,333
|
10
|
+
gedcomx/Fact.py,sha256=7UVDiimuF8LJG7ASqVdCLKed1YWqb4YhAANI0KJLwdA,23682
|
11
|
+
gedcomx/Gedcom.py,sha256=Dn0I8SInN1K_Q_K2CAzZYX1Zer3TwGaNlw5J4rJj688,12692
|
12
|
+
gedcomx/GedcomX.py,sha256=bWNeBPxFGaVeTIbHgyEzrefk6rwq-XzI3VOkxsrp5aw,50225
|
13
|
+
gedcomx/Gender.py,sha256=98__1lMIdGLfMmUSh5jrqQ0SwQzN1xedgMiW9Gpabko,1720
|
14
|
+
gedcomx/Group.py,sha256=ayp3wZEnR8vSuPwe-KfeH8MM0uQ3HJH8NGRnHDPV58M,1586
|
15
|
+
gedcomx/Identifier.py,sha256=aFXJQNyWvfnIzUt7ALrRTtwQjSjuToCY7qLBFFVmRC8,3398
|
16
|
+
gedcomx/Name.py,sha256=cQkWDjhuF62aoRdGUBXZy6kfvXAmUz7AxWDbnZ-bWIA,11650
|
17
|
+
gedcomx/Note.py,sha256=PLElHAQ3XFwETL71UgGkmP4ir5s9nBo3X83jwrRBnTc,2132
|
18
|
+
gedcomx/OnlineAccount.py,sha256=dm9HbWWf1Xekn8grDJtWChDOR-hxrIxmIRFiPRx2SQQ,276
|
19
|
+
gedcomx/Person.py,sha256=B2zqHN8kYq4K8uRIayRxYOi6Dp7fiiv9EKd-pNDxG6U,7357
|
20
|
+
gedcomx/PlaceDescription.py,sha256=EEBWAuxHQr1Kx4PoUOOsu26g4I5ICV1emx1mQXEU1vM,1971
|
21
|
+
gedcomx/PlaceReference.py,sha256=N7kU8pjtmsru5tzD3cdyXkYlaRb7ncueyCqj9fihi6U,929
|
22
|
+
gedcomx/Qualifier.py,sha256=mZ_GWT3gca8g9nWidlXIRTN5dEtf_rmnLHYl1jJkQYs,728
|
23
|
+
gedcomx/Relationship.py,sha256=MVuIzHxw6jTFfOx-HK4VhnMxystZ_tWA3dv1cHJJQjo,4599
|
24
|
+
gedcomx/Serialization.py,sha256=LYH3HuSwTLEmM8DQyU0MTrIUBIThGy7uG45AfzDaCrA,1277
|
25
|
+
gedcomx/SourceCitation.py,sha256=WyeXHS2HbqxHCmVvcffr5xNTK8xxy68yaY_wX8seJuY,622
|
26
|
+
gedcomx/SourceDescription.py,sha256=dOg7-omwHVEI2-NaRNPh8ciXKwJ1ooZKDd3iwBzkiP0,11884
|
27
|
+
gedcomx/SourceReference.py,sha256=OpD1hQH_xA-BGw7Lt17wU9u1L98V2FBtxlNQgJrMXrc,7252
|
28
|
+
gedcomx/Subject.py,sha256=jGDYgTDIaQrECC9DtQBS2k76ik-yr39ZVDyjhQKCem8,3116
|
29
|
+
gedcomx/TextValue.py,sha256=znGDzzb-pNCaatwS7kxcUT1g_csswhnD5zebkLKmdV8,1098
|
30
|
+
gedcomx/TopLevelTypeCollection.py,sha256=nvTO6GwFwEZk9jX4fVqhy75ygsshomNb20tnlExKqyY,1495
|
31
|
+
gedcomx/URI.py,sha256=OUSCFA7oOtJ7Ya4mTJFoJawq0bZt8BDg8masb9XX-cY,2143
|
32
|
+
gedcomx/_Resource.py,sha256=JqtbinRXpt6dLUXUtqGCZ2WmcYmGRzqXmjaw8YPpnYE,242
|
33
|
+
gedcomx/__init__.py,sha256=brX-pkFLY5owEk1CKnArX5qXlpTu9PTu0mG_VG12ox4,1362
|
34
|
+
gedcom_x-0.5.dist-info/METADATA,sha256=m_ewdjTzhhU2JSxpZvKT2LI_Dopam9Kbdt2X_GVPtiA,631
|
35
|
+
gedcom_x-0.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
36
|
+
gedcom_x-0.5.dist-info/top_level.txt,sha256=smVBF4nxSU-mzCd6idtRYTbYjPICMMi8pTqewEmqF8Y,8
|
37
|
+
gedcom_x-0.5.dist-info/RECORD,,
|
@@ -0,0 +1 @@
|
|
1
|
+
gedcomx
|
gedcomx/Address.py
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
from typing import Optional
|
2
|
+
import json
|
3
|
+
|
4
|
+
class Address:
|
5
|
+
identifier = "http://gedcomx.org/v1/Address"
|
6
|
+
version = 'http://gedcomx.org/conceptual-model/v1'
|
7
|
+
|
8
|
+
def __init__(self, value: Optional[str] = None,
|
9
|
+
city: Optional[str] = None,
|
10
|
+
country: Optional[str] = None,
|
11
|
+
postalCode: Optional[str] = None,
|
12
|
+
stateOrProvince: Optional[str] = None,
|
13
|
+
street: Optional[str] = None,
|
14
|
+
street2: Optional[str] = None,
|
15
|
+
street3: Optional[str] = None,
|
16
|
+
street4: Optional[str] = None,
|
17
|
+
street5: Optional[str] = None,
|
18
|
+
street6: Optional[str] = None):
|
19
|
+
|
20
|
+
self._value = value #TODO impliment a parser for date strings.
|
21
|
+
self.city = city
|
22
|
+
self.country = country
|
23
|
+
self.postalCode = postalCode
|
24
|
+
self.stateOrProvince = stateOrProvince
|
25
|
+
self.street = street
|
26
|
+
self.street2 = street2
|
27
|
+
self.street3 = street3
|
28
|
+
self.street4 = street4
|
29
|
+
self.street5 = street5
|
30
|
+
self.street6 = street6
|
31
|
+
|
32
|
+
@property
|
33
|
+
def value(self) -> Optional[str]:
|
34
|
+
if self._value:
|
35
|
+
return self._value
|
36
|
+
return ', '.join(filter(None, [
|
37
|
+
self._street, self._street2, self._street3,
|
38
|
+
self._street4, self._street5, self._street6,
|
39
|
+
self._city, self._stateOrProvince,
|
40
|
+
self._postalCode, self._country
|
41
|
+
]))
|
42
|
+
|
43
|
+
|
44
|
+
def __eq__(self, other):
|
45
|
+
if not isinstance(other, self.__class__):
|
46
|
+
return False
|
47
|
+
|
48
|
+
return (
|
49
|
+
self.value == other.value and
|
50
|
+
self.city == other.city and
|
51
|
+
self.country == other.country and
|
52
|
+
self.postalCode == other.postalCode and
|
53
|
+
self.stateOrProvince == other.stateOrProvince and
|
54
|
+
self.street == other.street and
|
55
|
+
self.street2 == other.street2 and
|
56
|
+
self.street3 == other.street3 and
|
57
|
+
self.street4 == other.street4 and
|
58
|
+
self.street5 == other.street5 and
|
59
|
+
self.street6 == other.street6
|
60
|
+
)
|
61
|
+
|
62
|
+
def __str__(self) -> str:
|
63
|
+
# Combine non-empty address components into a formatted string
|
64
|
+
parts = [
|
65
|
+
self._value,
|
66
|
+
self.street,
|
67
|
+
self.street2,
|
68
|
+
self.street3,
|
69
|
+
self.street4,
|
70
|
+
self.street5,
|
71
|
+
self.street6,
|
72
|
+
self.city,
|
73
|
+
self.stateOrProvince,
|
74
|
+
self.postalCode,
|
75
|
+
self.country
|
76
|
+
]
|
77
|
+
|
78
|
+
# Filter out any parts that are None or empty strings
|
79
|
+
filtered_parts = [str(part) for part in parts if part]
|
80
|
+
|
81
|
+
# Join the remaining parts with a comma and space
|
82
|
+
return ', '.join(filtered_parts)
|
83
|
+
|
84
|
+
@property
|
85
|
+
def __as_dict__(self):
|
86
|
+
return {
|
87
|
+
#"value": self._value if self._value else None,
|
88
|
+
"city": self.city if self.city else None,
|
89
|
+
"country": self.country if self.country else None,
|
90
|
+
"postalCode": self.postalCode if self.postalCode else None,
|
91
|
+
"stateOrProvince": self.stateOrProvince if self.stateOrProvince else None,
|
92
|
+
"street": self.street if self.street else None,
|
93
|
+
"street2": self.street2 if self.street2 else None,
|
94
|
+
"street3": self.street3 if self.street3 else None,
|
95
|
+
"street4": self.street4 if self.street4 else None,
|
96
|
+
"street5": self.street5 if self.street5 else None,
|
97
|
+
"street6": self.street6 if self.street6 else None
|
98
|
+
}
|
99
|
+
|
100
|
+
|
gedcomx/Agent.py
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
import base64
|
2
|
+
import uuid
|
3
|
+
|
4
|
+
from typing import List, Optional
|
5
|
+
|
6
|
+
from .Address import Address
|
7
|
+
from .Identifier import Identifier
|
8
|
+
from .OnlineAccount import OnlineAccount
|
9
|
+
from .TextValue import TextValue
|
10
|
+
from .URI import URI
|
11
|
+
|
12
|
+
class Agent:
|
13
|
+
@staticmethod
|
14
|
+
def default_id_generator():
|
15
|
+
# Generate a standard UUID
|
16
|
+
standard_uuid = uuid.uuid4()
|
17
|
+
# Convert UUID to bytes
|
18
|
+
uuid_bytes = standard_uuid.bytes
|
19
|
+
# Encode bytes to a Base64 string
|
20
|
+
short_uuid = base64.urlsafe_b64encode(uuid_bytes).rstrip(b'=').decode('utf-8')
|
21
|
+
return short_uuid
|
22
|
+
|
23
|
+
def __init__(self, id: Optional[str] = None,
|
24
|
+
identifiers: Optional[List[Identifier]] = [],
|
25
|
+
names: Optional[List[TextValue]] = [],
|
26
|
+
homepage: Optional[URI] = None,
|
27
|
+
openid: Optional[URI] = None,
|
28
|
+
accounts: Optional[List[OnlineAccount]] = [],
|
29
|
+
emails: Optional[List[URI]] = [],
|
30
|
+
phones: Optional[List[URI]] = [],
|
31
|
+
addresses: Optional[List[Address]] = [],
|
32
|
+
person: Optional[object] | Optional[URI] = None, # should be of Type 'Person', 'object' to avoid circular imports
|
33
|
+
uri: URI = None):
|
34
|
+
|
35
|
+
self._id_generator = Agent.default_id_generator
|
36
|
+
|
37
|
+
self.id = id if id else self._id_generator()
|
38
|
+
self.identifiers = identifiers
|
39
|
+
self.names = names
|
40
|
+
self.homepage = homepage
|
41
|
+
self.openid = openid
|
42
|
+
self.accounts = accounts
|
43
|
+
self.emails = emails
|
44
|
+
self.phones = phones
|
45
|
+
self.addresses = addresses
|
46
|
+
|
47
|
+
self._uri = URI(fragment="agent:"+ self.id)
|
48
|
+
|
49
|
+
def _append_to_name(self, text_to_append: str):
|
50
|
+
self.names[0].value = self.names[0].value + text_to_append
|
51
|
+
|
52
|
+
def add_address(self, address_to_add: Address):
|
53
|
+
if address_to_add and isinstance(address_to_add, Address):
|
54
|
+
for current_address in self.addresses:
|
55
|
+
if address_to_add == current_address:
|
56
|
+
return False
|
57
|
+
self.addresses.append(address_to_add)
|
58
|
+
else:
|
59
|
+
raise ValueError(f"address must be of type Address, not {type(address_to_add)}")
|
60
|
+
|
61
|
+
def add_name(self, name_to_add: TextValue):
|
62
|
+
if isinstance(name_to_add,str): name_to_add = TextValue(value=name_to_add)
|
63
|
+
if name_to_add and isinstance(name_to_add,TextValue):
|
64
|
+
for current_name in self.names:
|
65
|
+
if name_to_add == current_name:
|
66
|
+
return
|
67
|
+
self.names.append(name_to_add)
|
68
|
+
else:
|
69
|
+
raise ValueError(f'name must be of type str or TextValue, recived {type(name_to_add)}')
|
70
|
+
|
71
|
+
@property
|
72
|
+
def __as_dict__(self):
|
73
|
+
return {
|
74
|
+
"id": self.id if self.id else None,
|
75
|
+
"identifiers": [identifier.__as_dict__() for identifier in self.identifiers],
|
76
|
+
"names": [name._prop_dict() for name in self.names],
|
77
|
+
"homepage": self.homepage if self.homepage else None,
|
78
|
+
"openid": self.openid if self.openid else None,
|
79
|
+
"accounts": self.accounts if self.accounts else None,
|
80
|
+
"emails": self.emails if self.emails else None,
|
81
|
+
"phones": self.phones if self.phones else None,
|
82
|
+
"addresses": [address.__as_dict__ for address in self.addresses]
|
83
|
+
}
|
gedcomx/Attribution.py
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
from datetime import datetime
|
2
|
+
from typing import Optional, Dict, Any
|
3
|
+
|
4
|
+
from .URI import URI
|
5
|
+
|
6
|
+
class Attribution:
|
7
|
+
identifier = 'http://gedcomx.org/v1/Attribution'
|
8
|
+
version = 'http://gedcomx.org/conceptual-model/v1'
|
9
|
+
|
10
|
+
def __init__(self,contributor: Optional[URI],
|
11
|
+
modified: Optional[datetime],
|
12
|
+
changeMessage: Optional[str],
|
13
|
+
creator: Optional[URI],
|
14
|
+
created: Optional[datetime]) -> None:
|
15
|
+
|
16
|
+
|
17
|
+
from .Agent import Agent
|
18
|
+
self._contributor_object = None
|
19
|
+
self.modified = modified
|
20
|
+
self.changeMessage = changeMessage
|
21
|
+
self.creator = creator
|
22
|
+
self.created = created
|
23
|
+
|
24
|
+
|
25
|
+
if isinstance(contributor,URI):
|
26
|
+
# TODO DEAL WITH URI <------------------------------------------------------------------------------------------------------------------
|
27
|
+
self._contributor_object = contributor
|
28
|
+
elif isinstance(contributor,Agent):
|
29
|
+
self._contributor_object = contributor
|
30
|
+
if hasattr(contributor,'_uri'):
|
31
|
+
self.contributor = contributor._uri
|
32
|
+
else:
|
33
|
+
assert False
|
34
|
+
self.description = URI(object=description)
|
35
|
+
description._uri = self.description
|
36
|
+
description._object = description
|
37
|
+
else:
|
38
|
+
raise ValueError(f"'description' must be of type 'SourceDescription' or 'URI', type: {type(contributor)} was provided, with value: {contributor}")
|
39
|
+
|
40
|
+
@property
|
41
|
+
def _as_dict_(self) -> Dict[str, Any]:
|
42
|
+
"""
|
43
|
+
Serialize Attribution to a JSON-ready dict, skipping None values.
|
44
|
+
"""
|
45
|
+
def _fmt_dt(dt: datetime) -> str:
|
46
|
+
# ISO 8601 format
|
47
|
+
return dt.isoformat()
|
48
|
+
|
49
|
+
data: Dict[str, Any] = {}
|
50
|
+
if self._contributor_object:
|
51
|
+
data['contributor'] = (self._contributor_object._as_dict_
|
52
|
+
if hasattr(self._contributor_object, '_as_dict_') else
|
53
|
+
None)
|
54
|
+
if self.created:
|
55
|
+
data['created'] = _fmt_dt(self.created)
|
56
|
+
if self.creator:
|
57
|
+
data['creator'] = (self.creator._prop_dict()
|
58
|
+
if hasattr(self.creator, '_prop_dict') else
|
59
|
+
self.creator._prop_dict())
|
60
|
+
if self.modified:
|
61
|
+
data['modified'] = _fmt_dt(self.modified)
|
62
|
+
if self.changeMessage is not None:
|
63
|
+
data['changeMessage'] = self.changeMessage
|
64
|
+
return data
|
65
|
+
|
66
|
+
@classmethod
|
67
|
+
def _from_json_(cls, data: Dict[str, Any]) -> 'Attribution':
|
68
|
+
"""
|
69
|
+
Construct Attribution from a dict (as parsed from JSON).
|
70
|
+
Handles 'created' and 'modified' as ISO strings or epoch ms ints.
|
71
|
+
"""
|
72
|
+
# contributor
|
73
|
+
contrib = None
|
74
|
+
if 'contributor' in data:
|
75
|
+
raw = data['contributor']
|
76
|
+
if isinstance(raw, dict):
|
77
|
+
contrib = URI._from_json_(raw)
|
78
|
+
elif isinstance(raw, str):
|
79
|
+
contrib = URI(uri=raw)
|
80
|
+
|
81
|
+
# creator
|
82
|
+
creat = None
|
83
|
+
if 'creator' in data:
|
84
|
+
raw = data['creator']
|
85
|
+
if isinstance(raw, dict):
|
86
|
+
creat = URI._from_json_(raw)
|
87
|
+
elif isinstance(raw, str):
|
88
|
+
creat = URI(uri=raw)
|
89
|
+
|
90
|
+
# parse created date
|
91
|
+
raw_created = data.get('created')
|
92
|
+
if isinstance(raw_created, (int, float)):
|
93
|
+
created_dt = datetime.fromtimestamp(raw_created / 1000.0)
|
94
|
+
elif isinstance(raw_created, str):
|
95
|
+
created_dt = datetime.fromisoformat(raw_created)
|
96
|
+
else:
|
97
|
+
created_dt = None
|
98
|
+
|
99
|
+
# parse modified date
|
100
|
+
raw_modified = data.get('modified')
|
101
|
+
if isinstance(raw_modified, (int, float)):
|
102
|
+
modified_dt = datetime.fromtimestamp(raw_modified / 1000.0)
|
103
|
+
elif isinstance(raw_modified, str):
|
104
|
+
modified_dt = datetime.fromisoformat(raw_modified)
|
105
|
+
else:
|
106
|
+
modified_dt = None
|
107
|
+
|
108
|
+
change_msg = data.get('changeMessage')
|
109
|
+
|
110
|
+
return cls(
|
111
|
+
contributor=contrib,
|
112
|
+
created=created_dt,
|
113
|
+
creator=creat,
|
114
|
+
modified=modified_dt,
|
115
|
+
changeMessage=change_msg
|
116
|
+
)
|
gedcomx/Conclusion.py
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
import base64
|
2
|
+
import uuid
|
3
|
+
import warnings
|
4
|
+
|
5
|
+
from typing import List, Optional
|
6
|
+
|
7
|
+
from .Attribution import Attribution
|
8
|
+
from .Note import Note
|
9
|
+
from .Qualifier import Qualifier
|
10
|
+
from .SourceReference import SourceReference
|
11
|
+
from .URI import URI
|
12
|
+
|
13
|
+
class ConfidenceLevel(Qualifier):
|
14
|
+
High = "http://gedcomx.org/High"
|
15
|
+
Medium = "http://gedcomx.org/Medium"
|
16
|
+
Low = "http://gedcomx.org/Low"
|
17
|
+
|
18
|
+
@property
|
19
|
+
def description(self):
|
20
|
+
descriptions = {
|
21
|
+
ConfidenceLevel.High: "The contributor has a high degree of confidence that the assertion is true.",
|
22
|
+
ConfidenceLevel.Medium: "The contributor has a medium degree of confidence that the assertion is true.",
|
23
|
+
ConfidenceLevel.Low: "The contributor has a low degree of confidence that the assertion is true."
|
24
|
+
}
|
25
|
+
return descriptions.get(self, "No description available.")
|
26
|
+
|
27
|
+
class Conclusion:
|
28
|
+
identifier = 'http://gedcomx.org/v1/Conclusion'
|
29
|
+
version = 'http://gedcomx.org/conceptual-model/v1'
|
30
|
+
|
31
|
+
@staticmethod
|
32
|
+
def default_id_generator():
|
33
|
+
# Generate a standard UUID
|
34
|
+
standard_uuid = uuid.uuid4()
|
35
|
+
# Convert UUID to bytes
|
36
|
+
uuid_bytes = standard_uuid.bytes
|
37
|
+
# Encode bytes to a Base64 string
|
38
|
+
short_uuid = base64.urlsafe_b64encode(uuid_bytes).rstrip(b'=').decode('utf-8')
|
39
|
+
return short_uuid
|
40
|
+
|
41
|
+
def __init__(self,
|
42
|
+
id: Optional[str],
|
43
|
+
lang: Optional[str] = 'en',
|
44
|
+
sources: Optional[List[SourceReference]] = [],
|
45
|
+
analysis: Optional[URI] = None,
|
46
|
+
notes: Optional[List[Note]] = [],
|
47
|
+
confidence: Optional[ConfidenceLevel] = None,
|
48
|
+
attribution: Optional[Attribution] = None,
|
49
|
+
uri: Optional[URI] = None,
|
50
|
+
max_note_count: int = 20) -> None:
|
51
|
+
|
52
|
+
self._id_generator = Conclusion.default_id_generator
|
53
|
+
|
54
|
+
self.id = id if id else self._id_generator()
|
55
|
+
self.lang = lang
|
56
|
+
self.sources = sources
|
57
|
+
self.analysis = analysis
|
58
|
+
self.notes = notes
|
59
|
+
self.confidence = confidence
|
60
|
+
self.attribution = attribution
|
61
|
+
self.max_note_count = max_note_count
|
62
|
+
self._uri = uri if uri else URI(fragment=id)
|
63
|
+
|
64
|
+
def add_note(self,note_to_add: Note):
|
65
|
+
if len(self.notes) >= self.max_note_count:
|
66
|
+
warnings.warn(f"Max not count of {self.max_note_count} reached for id: {self.id}")
|
67
|
+
return False
|
68
|
+
if note_to_add and isinstance(note_to_add,Note):
|
69
|
+
for existing in self.notes:
|
70
|
+
if note_to_add == existing:
|
71
|
+
return False
|
72
|
+
self.notes.append(note_to_add)
|
73
|
+
|
74
|
+
def add_source(self, source_to_add: SourceReference):
|
75
|
+
if source_to_add and isinstance(source_to_add,SourceReference):
|
76
|
+
for current_source in self.sources:
|
77
|
+
if source_to_add == current_source:
|
78
|
+
return
|
79
|
+
self.sources.append(source_to_add)
|
80
|
+
else:
|
81
|
+
raise ValueError()
|
82
|
+
|
83
|
+
'''
|
84
|
+
def _as_dict_(self):
|
85
|
+
return {
|
86
|
+
'id':self.id,
|
87
|
+
'lang':self.lang,
|
88
|
+
'sources': [source._prop_dict() for source in self.sources] if self.sources else None,
|
89
|
+
'analysis': self.analysis._uri if self.analysis else None,
|
90
|
+
'notes':"Add notes here",
|
91
|
+
'confidence':self.confidence
|
92
|
+
}
|
93
|
+
'''
|
94
|
+
|
95
|
+
@property
|
96
|
+
def _as_dict_(self):
|
97
|
+
def _serialize(value):
|
98
|
+
if isinstance(value, (str, int, float, bool, type(None))):
|
99
|
+
return value
|
100
|
+
elif isinstance(value, dict):
|
101
|
+
return {k: _serialize(v) for k, v in value.items()}
|
102
|
+
elif isinstance(value, (list, tuple, set)):
|
103
|
+
return [_serialize(v) for v in value]
|
104
|
+
elif hasattr(value, "_as_dict_"):
|
105
|
+
return value._as_dict_
|
106
|
+
else:
|
107
|
+
return str(value) # fallback for unknown objects
|
108
|
+
|
109
|
+
# Only add Relationship-specific fields
|
110
|
+
conclusion_fields = {
|
111
|
+
'id':self.id,
|
112
|
+
'lang':self.lang,
|
113
|
+
'sources': [source for source in self.sources] if self.sources else None,
|
114
|
+
'analysis': self.analysis._uri if self.analysis else None,
|
115
|
+
'notes': [note for note in self.notes] if self.notes else None,
|
116
|
+
'confidence':self.confidence
|
117
|
+
}
|
118
|
+
|
119
|
+
# Serialize and exclude None values
|
120
|
+
for key, value in conclusion_fields.items():
|
121
|
+
if value is not None:
|
122
|
+
conclusion_fields[key] = _serialize(value)
|
123
|
+
return conclusion_fields
|
124
|
+
|
125
|
+
def __eq__(self, other):
|
126
|
+
if not isinstance(other, self.__class__):
|
127
|
+
return False
|
128
|
+
|
129
|
+
return (
|
130
|
+
self.id == other.id and
|
131
|
+
self.lang == other.lang and
|
132
|
+
self.sources == other.sources and
|
133
|
+
self.analysis == other.analysis and
|
134
|
+
self.notes == other.notes and
|
135
|
+
self.confidence == other.confidence and
|
136
|
+
self.attribution == other.attribution
|
137
|
+
)
|
gedcomx/Coverage.py
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
from typing import Optional
|
2
|
+
|
3
|
+
from .Date import Date
|
4
|
+
from .PlaceReference import PlaceReference
|
5
|
+
|
6
|
+
class Coverage:
|
7
|
+
identifier = 'http://gedcomx.org/v1/Coverage'
|
8
|
+
version = 'http://gedcomx.org/conceptual-model/v1'
|
9
|
+
|
10
|
+
def __init__(self,spatial: Optional[PlaceReference], temporal: Optional[Date]) -> None:
|
11
|
+
self.spatial = spatial
|
12
|
+
self.temporal = temporal
|
13
|
+
|
14
|
+
# ...existing code...
|
15
|
+
|
16
|
+
@classmethod
|
17
|
+
def _from_json_(cls, data: dict):
|
18
|
+
"""
|
19
|
+
Create a Coverage instance from a JSON-dict (already parsed).
|
20
|
+
"""
|
21
|
+
from .PlaceReference import PlaceReference
|
22
|
+
from .Date import Date
|
23
|
+
|
24
|
+
spatial = PlaceReference._from_json_(data.get('spatial')) if data.get('spatial') else None
|
25
|
+
temporal = Date._from_json_(data.get('temporal')) if data.get('temporal') else None
|
26
|
+
return cls(spatial=spatial, temporal=temporal)
|
gedcomx/Date.py
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
from typing import Optional
|
2
|
+
|
3
|
+
|
4
|
+
class DateFormat:
|
5
|
+
def __init__(self) -> None:
|
6
|
+
pass
|
7
|
+
|
8
|
+
|
9
|
+
class Date:
|
10
|
+
identifier = 'http://gedcomx.org/v1/Date'
|
11
|
+
version = 'http://gedcomx.org/conceptual-model/v1'
|
12
|
+
|
13
|
+
def __init__(self, original: Optional[str],formal: Optional[str | DateFormat] = None) -> None:
|
14
|
+
self.orginal = original
|
15
|
+
self.formal = formal
|
16
|
+
|
17
|
+
def _prop_dict(self):
|
18
|
+
return {'original': self.orginal,
|
19
|
+
'formal': self.formal}
|
20
|
+
|
21
|
+
# Date
|
22
|
+
Date._from_json_ = classmethod(lambda cls, data: Date(
|
23
|
+
original=data.get('original'),
|
24
|
+
formal=data.get('formal')
|
25
|
+
))
|
26
|
+
|
27
|
+
Date._to_dict_ = lambda self: {
|
28
|
+
'original': self.orginal,
|
29
|
+
'formal': self.formal}
|
gedcomx/Document.py
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
from enum import Enum
|
2
|
+
from typing import Optional
|
3
|
+
|
4
|
+
from gedcomx.Attribution import Attribution
|
5
|
+
from gedcomx.Conclusion import ConfidenceLevel
|
6
|
+
from gedcomx.Note import Note
|
7
|
+
from gedcomx.SourceReference import SourceReference
|
8
|
+
from gedcomx.URI import URI
|
9
|
+
|
10
|
+
from .Conclusion import Conclusion
|
11
|
+
|
12
|
+
class DocumentType(Enum):
|
13
|
+
Abstract = "http://gedcomx.org/Abstract"
|
14
|
+
Transcription = "http://gedcomx.org/Transcription"
|
15
|
+
Translation = "http://gedcomx.org/Translation"
|
16
|
+
Analysis = "http://gedcomx.org/Analysis"
|
17
|
+
|
18
|
+
@property
|
19
|
+
def description(self):
|
20
|
+
descriptions = {
|
21
|
+
DocumentType.Abstract: "The document is an abstract of a record or document.",
|
22
|
+
DocumentType.Transcription: "The document is a transcription of a record or document.",
|
23
|
+
DocumentType.Translation: "The document is a translation of a record or document.",
|
24
|
+
DocumentType.Analysis: "The document is an analysis done by a researcher; a genealogical proof statement is an example of one kind of analysis document."
|
25
|
+
}
|
26
|
+
return descriptions.get(self, "No description available.")
|
27
|
+
|
28
|
+
class TextType(Enum):
|
29
|
+
plain = 'plain'
|
30
|
+
xhtml = 'xhtml'
|
31
|
+
|
32
|
+
class Document(Conclusion):
|
33
|
+
identifier = 'http://gedcomx.org/v1/Document'
|
34
|
+
version = 'http://gedcomx.org/conceptual-model/v1'
|
35
|
+
|
36
|
+
def __init__(self, id: str | None, lang: str | None, sources: SourceReference | None, analysis: URI | None, notes: Note | None, confidence: ConfidenceLevel | None, attribution: Attribution | None,
|
37
|
+
type: Optional[DocumentType],
|
38
|
+
extracted: Optional[bool], # Default to False
|
39
|
+
textType: Optional[TextType],
|
40
|
+
text: str,
|
41
|
+
) -> None:
|
42
|
+
super().__init__(id, lang, sources, analysis, notes, confidence, attribution)
|