gedcom-x 0.5.1__py3-none-any.whl → 0.5.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gedcom-x
3
- Version: 0.5.1
3
+ Version: 0.5.5
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,43 @@
1
+ gedcomx/Address.py,sha256=FDBic6oQB30eKMWM7-6_YVPHaJdPhziU5LybwE5TY5Q,4855
2
+ gedcomx/Agent.py,sha256=C-tzfqcjuU22iJ2LjoatKdCbI_i0xskpmZsuj58K6WY,8555
3
+ gedcomx/Attribution.py,sha256=2bcl2UNsMPn10kDrA080qFRzxK-KYqZBgmPGNBZ2M4Y,2608
4
+ gedcomx/Conclusion.py,sha256=t8n8FuWwzinFTPHp9D2Umu93-4A12I9L5fCx510mfqA,8414
5
+ gedcomx/Coverage.py,sha256=BVlB3kWDoOP2j86j67hNJLC4LqoLwqs3N93z0ZbXYTw,1227
6
+ gedcomx/Date.py,sha256=cv2UeyOcTHuH2CVHq9EjTnj3FR-r4_OVuPiuc8sbOPc,2130
7
+ gedcomx/Document.py,sha256=32nWfDNVWkPK2_I0-hhcNQkrjH3ssdbtw-II3QMMelw,2957
8
+ gedcomx/Event.py,sha256=BHhmO2UlxbkSzB5htdMRf2h-WNuJQsWfrSC8tNGQ6Mg,10375
9
+ gedcomx/EvidenceReference.py,sha256=WXZpmcKzwb3lYQmGVf-TY2IsbNG3fIInPY1khRjtOSg,348
10
+ gedcomx/Exceptions.py,sha256=0OdPM3euhBMgX8o61ZwPuKeN8zPuSuuDcSBFflVGFqk,587
11
+ gedcomx/Fact.py,sha256=-Zz321xeFd93riaX4C4i7gIPwpqdArAYZDlWRoXA1Uk,24444
12
+ gedcomx/Gedcom.py,sha256=l_BuLBynQacDtpLjhs2Afrabfiqt2Opoo_5_7myI7Z4,1709
13
+ gedcomx/Gedcom5x.py,sha256=qkRmOI99N3SJEtxuzhL_IiICQIm5OyJwBkLEQIkNjUA,21223
14
+ gedcomx/GedcomX.py,sha256=u95GeBQGRgyEp_wBl7y-RjPnhF8wTciH8Wlief99g_8,63258
15
+ gedcomx/Gender.py,sha256=AV125vECC5JSgvNJRmotjDEt-A3cFzth6pcSLFwJBEE,2328
16
+ gedcomx/Group.py,sha256=VNI_cI_-YnJ9cHzwMmHD8qwDNMe89kEocPuQ67rwStA,1606
17
+ gedcomx/Identifier.py,sha256=MePkMgx0o2wzzSPGv4AJ6JklIWniEgl8XZSWKTLHXhs,8515
18
+ gedcomx/Logging.py,sha256=vBDOjawVXc4tCge1laYjy6_2Ves-fnGzG0m6NnLZejE,624
19
+ gedcomx/Mutations.py,sha256=idhQsnikCTovmNEGAhDsOe97V0L9AnFTRaufzUPm64w,6298
20
+ gedcomx/Name.py,sha256=en_VkGjpZPP_Dbl9HSq2h2JCtLS2NMzmLvis5RdBPeM,12913
21
+ gedcomx/Note.py,sha256=cvV4-fCZ0oh2zXfzD-YYoIM_WTox-fk2_bfl3i4CoNc,2251
22
+ gedcomx/OnlineAccount.py,sha256=P24o98IXo_8XQoICYZPgALjdzS0q8t2l4DU3Od9KxyI,291
23
+ gedcomx/Person.py,sha256=LyGPCZh6rZU_dE3GCJE92x1lLk41c_Edr_Po9smhPLc,7321
24
+ gedcomx/PlaceDescription.py,sha256=TRHIkDiErHa_mlx_x8gYCF6UnQ-XgT08uSmYmFNa4u0,3255
25
+ gedcomx/PlaceReference.py,sha256=3NyEYq0FaMI8m3l4H6-Ov-n9NUq_m751iYVxofqtv30,870
26
+ gedcomx/Qualifier.py,sha256=mZ_GWT3gca8g9nWidlXIRTN5dEtf_rmnLHYl1jJkQYs,728
27
+ gedcomx/Relationship.py,sha256=oyT501vR5jvWxqEYy3KvW9egD4CPL5JaAYWJmlNy9Ig,3599
28
+ gedcomx/Resource.py,sha256=bZ2dV-OdpvmDRCppyfHJXiarIKL7-OcofwvYRnpOJW0,2324
29
+ gedcomx/Serialization.py,sha256=ubtmrvi-nKmVUCK1XGwuLijrPMggNJe--kkCIC-87Ds,18580
30
+ gedcomx/SourceCitation.py,sha256=aW-lEb7bT9QU49GiBjJppFMBvtisR6fhVVuXjr5y4vQ,742
31
+ gedcomx/SourceDescription.py,sha256=zOkXo-fy-uCl_EGU8B-c6pzhlMk3nYNHn13ln1AkYk0,11914
32
+ gedcomx/SourceReference.py,sha256=swVzZ5A0jt5yFYJNJvMnQZtb7d-2YEAQPxp2a4lEY1k,5071
33
+ gedcomx/Subject.py,sha256=znee3SxYKk99JLP6KmZHkXs5hIOGu_jeLwxagENNfXs,3298
34
+ gedcomx/TextValue.py,sha256=6B0wMxL0nigFNzhXZDhbTONvFGbnM2t2NcDZiZuu4Zw,1112
35
+ gedcomx/TopLevelTypeCollection.py,sha256=nvTO6GwFwEZk9jX4fVqhy75ygsshomNb20tnlExKqyY,1495
36
+ gedcomx/Translation.py,sha256=NWa1NtTOC_VgQ-EGwWG06zUpddRxdKFSLPP6066JFaA,12485
37
+ gedcomx/URI.py,sha256=S2pFsu_VyKgOsRdHIj8pjnh6_tdA3OjAYiIugoEd70k,4271
38
+ gedcomx/Zip.py,sha256=lBxcv-Vip45884EHj56wZJJ5I36Q38UuHUidDxQBoS8,14
39
+ gedcomx/__init__.py,sha256=Et3E0lBdtrMjyposrQ0MeoaRilg0ShSDqXg0y15pPf4,1616
40
+ gedcom_x-0.5.5.dist-info/METADATA,sha256=cTARXUhB_6HIix2ih1I9ThdwN1OYGn4gQA0jF9pKepM,633
41
+ gedcom_x-0.5.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
42
+ gedcom_x-0.5.5.dist-info/top_level.txt,sha256=smVBF4nxSU-mzCd6idtRYTbYjPICMMi8pTqewEmqF8Y,8
43
+ gedcom_x-0.5.5.dist-info/RECORD,,
gedcomx/Address.py CHANGED
@@ -1,7 +1,28 @@
1
1
  from typing import Optional
2
2
  import json
3
3
 
4
+ from .Serialization import Serialization
5
+
4
6
  class Address:
7
+ """A GedcomX Address Data Type
8
+ A GedcomX Address Data Type.
9
+
10
+ Represents a postal address according to the GedcomX conceptual model.
11
+
12
+ Args:
13
+ value (str, optional): A complete address as a single string.
14
+ city (str, optional): Name of the city or town.
15
+ country (str, optional): Name of the country.
16
+ postalCode (str, optional): Postal or ZIP code.
17
+ stateOrProvince (str, optional): Name of the state, province, or region.
18
+ street (str, optional): First street address line.
19
+ street2 (str, optional): Second street address line.
20
+ street3 (str, optional): Third street address line.
21
+ street4 (str, optional): Fourth street address line.
22
+ street5 (str, optional): Fifth street address line.
23
+ street6 (str, optional): Sixth street address line.
24
+ """
25
+
5
26
  identifier = "http://gedcomx.org/v1/Address"
6
27
  version = 'http://gedcomx.org/conceptual-model/v1'
7
28
 
@@ -30,17 +51,26 @@ class Address:
30
51
  self.street6 = street6
31
52
 
32
53
  @property
33
- def value(self) -> Optional[str]:
34
- if self._value:
35
- return self._value
54
+ def value(self) -> str:
36
55
  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
56
+ self.street, self.street2, self.street3,
57
+ self.street4, self.street5, self.street6,
58
+ self.city, self.stateOrProvince,
59
+ self.postalCode, self.country
41
60
  ]))
42
61
 
43
-
62
+ @value.setter
63
+ def value(self,value: str):
64
+ self._value = value
65
+ return
66
+ raise NotImplementedError("Parsing of a full address is not implimented.")
67
+
68
+ def _append(self,value):
69
+ if self._value:
70
+ self._value = self._value + ' ' + value
71
+ else:
72
+ self._value = value
73
+
44
74
  def __eq__(self, other):
45
75
  if not isinstance(other, self.__class__):
46
76
  return False
@@ -82,9 +112,8 @@ class Address:
82
112
  return ', '.join(filtered_parts)
83
113
 
84
114
  @property
85
- def __as_dict__(self):
86
- return {
87
- #"value": self._value if self._value else None,
115
+ def _as_dict_(self):
116
+ type_as_dict = {
88
117
  "city": self.city if self.city else None,
89
118
  "country": self.country if self.country else None,
90
119
  "postalCode": self.postalCode if self.postalCode else None,
@@ -97,4 +126,6 @@ class Address:
97
126
  "street6": self.street6 if self.street6 else None
98
127
  }
99
128
 
129
+ return Serialization.serialize_dict(type_as_dict)
130
+
100
131
 
gedcomx/Agent.py CHANGED
@@ -4,12 +4,49 @@ import uuid
4
4
  from typing import List, Optional
5
5
 
6
6
  from .Address import Address
7
- from .Identifier import Identifier
7
+ #from .Attribution import Attribution
8
+ from .Identifier import Identifier, IdentifierList
9
+
8
10
  from .OnlineAccount import OnlineAccount
9
11
  from .TextValue import TextValue
12
+ from .Resource import Resource
10
13
  from .URI import URI
14
+ from .Serialization import Serialization
15
+
11
16
 
12
17
  class Agent:
18
+ """A GedcomX Agent Data Type.
19
+
20
+ Represents an agent entity such as a person, organization, or software
21
+ responsible for creating or modifying genealogical data, as defined in
22
+ the GedcomX conceptual model.
23
+
24
+ Static Methods:
25
+ default_id_generator(): Generates a short, URL-safe Base64-encoded UUID
26
+ for use as a default agent identifier.
27
+
28
+ Args:
29
+ id (str, optional): A unique identifier for this agent. If not provided,
30
+ one may be generated automatically using `default_id_generator()`.
31
+ identifiers (IdentifierList, optional): A list of alternate identifiers for this agent.
32
+ names (List[TextValue], optional): Names associated with the agent. Defaults to an empty list.
33
+ homepage (URI, optional): A link to the agent's homepage or primary website.
34
+ openid (URI, optional): The OpenID identifier for the agent.
35
+ accounts (List[OnlineAccount], optional): Online accounts associated with the agent.
36
+ Defaults to an empty list.
37
+ emails (List[URI], optional): Email addresses associated with the agent.
38
+ Defaults to an empty list.
39
+ phones (List[Resource], optional): Phone numbers associated with the agent.
40
+ Defaults to an empty list.
41
+ addresses (List[Address], optional): Postal addresses associated with the agent.
42
+ Defaults to an empty list.
43
+ person (Person, optional): A reference to the person represented
44
+ by the agent. Accepts a `Person` object or a `Resource` reference.
45
+ Declared as `object` to avoid circular imports.
46
+ attribution (Attribution, optional): Attribution information related to the agent.
47
+ uri (Resource, optional): A URI reference for this agent.
48
+ """
49
+
13
50
  @staticmethod
14
51
  def default_id_generator():
15
52
  # Generate a standard UUID
@@ -21,7 +58,7 @@ class Agent:
21
58
  return short_uuid
22
59
 
23
60
  def __init__(self, id: Optional[str] = None,
24
- identifiers: Optional[List[Identifier]] = [],
61
+ identifiers: Optional[IdentifierList] = None,
25
62
  names: Optional[List[TextValue]] = [],
26
63
  homepage: Optional[URI] = None,
27
64
  openid: Optional[URI] = None,
@@ -29,25 +66,33 @@ class Agent:
29
66
  emails: Optional[List[URI]] = [],
30
67
  phones: Optional[List[URI]] = [],
31
68
  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):
69
+ person: Optional[object] | Optional[Resource] = None, # should be of Type 'Person', 'object' to avoid circular imports
70
+ #xnotes: Optional[List[Note]] = None,
71
+ attribution: Optional[object] = None, # Added for compatibility with GEDCOM5/7 Imports
72
+ uri: Optional[URI | Resource] = None):
34
73
 
35
74
  self._id_generator = Agent.default_id_generator
36
75
 
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
-
76
+ self.id = id if id else None #TODO self._id_generator()
77
+ self.identifiers = identifiers or IdentifierList()
78
+ self.names = names if names else []
79
+ self.homepage = homepage or None
80
+ self.openid = openid or None
81
+ self.accounts = accounts or []
82
+ self.emails = emails or []
83
+ self.phones = phones or []
84
+ self.addresses = addresses if addresses else []
85
+ self.xnotes = []
86
+ self.attribution = attribution or None
87
+ self.uri = URI(fragment=self.id)
88
+
49
89
  def _append_to_name(self, text_to_append: str):
50
- self.names[0].value = self.names[0].value + text_to_append
90
+ if self.names and self.names[0] and self.names[0].value:
91
+ self.names[0].value = self.names[0].value + text_to_append
92
+ elif self.names and self.names[0]:
93
+ self.names[0].value = text_to_append
94
+ else:
95
+ raise ValueError() #TODO
51
96
 
52
97
  def add_address(self, address_to_add: Address):
53
98
  if address_to_add and isinstance(address_to_add, Address):
@@ -68,16 +113,84 @@ class Agent:
68
113
  else:
69
114
  raise ValueError(f'name must be of type str or TextValue, recived {type(name_to_add)}')
70
115
 
116
+ def add_note(self, note_to_add: 'Note'):
117
+ from .Note import Note
118
+ if note_to_add and isinstance(note_to_add,Note):
119
+ self.xnotes.append(note_to_add)
120
+ else:
121
+ raise ValueError(f'note must be of type Note, recived {type(note_to_add)}')
122
+
123
+ def add_identifier(self, identifier_to_add: Identifier):
124
+ self.identifiers.append(identifier_to_add)
125
+
71
126
  @property
72
- def __as_dict__(self):
73
- return {
127
+ def _as_dict_(self):
128
+ type_as_dict = {
74
129
  "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],
130
+ "identifiers": self.identifiers._as_dict_ if self.identifiers else None,
131
+ "names": [name._as_dict_ for name in self.names],
77
132
  "homepage": self.homepage if self.homepage else None,
78
133
  "openid": self.openid if self.openid else None,
79
134
  "accounts": self.accounts if self.accounts else None,
80
135
  "emails": self.emails if self.emails else None,
81
136
  "phones": self.phones if self.phones else None,
82
- "addresses": [address.__as_dict__ for address in self.addresses]
83
- }
137
+ "addresses": [address._as_dict_ for address in self.addresses],
138
+ "notes": [note._as_dict_ for note in self.xnotes]
139
+ }
140
+ return Serialization.serialize_dict(type_as_dict)
141
+
142
+ @classmethod
143
+ def _from_json_(cls, data: dict):
144
+ """
145
+ Create a Person instance from a JSON-dict (already parsed).
146
+ """
147
+ type_as_dict = Serialization.get_class_fields('Agent')
148
+ return Serialization.deserialize(data, type_as_dict)
149
+
150
+ def __str__(self):
151
+ """
152
+ Return a human-readable string representation of the Agent.
153
+
154
+ Returns:
155
+ str: A concise description including ID, primary name (if any), and type of agent.
156
+ """
157
+ primary_name = self.names[0].value if self.names else "Unnamed Agent"
158
+ homepage_str = f", homepage={self.homepage}" if self.homepage else ""
159
+ return f"Agent(id={self.id}, name='{primary_name}'{homepage_str})"
160
+
161
+ def __eq__(self, other):
162
+ """
163
+ Determine equality between two Agent instances.
164
+
165
+ Args:
166
+ other (Agent): The other object to compare against.
167
+
168
+ Returns:
169
+ bool: True if both objects represent the same agent, False otherwise.
170
+ """
171
+ '''
172
+ if not isinstance(other, Agent):
173
+ return NotImplemented
174
+
175
+ return (
176
+ self.id == other.id and
177
+ self.identifiers == other.identifiers and
178
+ self.names == other.names and
179
+ self.homepage == other.homepage and
180
+ self.openid == other.openid and
181
+ self.accounts == other.accounts and
182
+ self.emails == other.emails and
183
+ self.phones == other.phones and
184
+ self.addresses == other.addresses and
185
+ self.person == other.person and
186
+ self.attribution == other.attribution and
187
+ self.uri == other.uri
188
+ )
189
+ '''
190
+
191
+ self_names = {n.value for n in self.names if hasattr(n, "value")}
192
+ other_names = {n.value for n in other.names if hasattr(n, "value")}
193
+ if self_names & other_names: # intersection not empty
194
+ return True
195
+
196
+ return False
gedcomx/Attribution.py CHANGED
@@ -1,67 +1,52 @@
1
+
1
2
  from datetime import datetime
2
3
  from typing import Optional, Dict, Any
3
4
 
4
- from .URI import URI
5
+ from .Agent import Agent
6
+ from .Resource import Resource, get_resource_as_dict
7
+ from .Serialization import Serialization
5
8
 
6
9
  class Attribution:
7
- identifier = 'http://gedcomx.org/v1/Attribution'
8
- version = 'http://gedcomx.org/conceptual-model/v1'
10
+ """Attribution Information for a Genealogy, Conclusion, Subject and child classes
9
11
 
10
- def __init__(self,contributor: Optional[URI],
11
- modified: Optional[datetime],
12
- changeMessage: Optional[str],
13
- creator: Optional[URI],
14
- created: Optional[datetime]) -> None:
12
+ Args:
13
+ contributor (Agent, optional): Contributor to object being attributed.
14
+ modified (timestamp, optional): timestamp for when this record was modified.
15
+ changeMessage (str, optional): Birth date (YYYY-MM-DD).
16
+ creator (Agent, optional): Creator of object being attributed.
17
+ created (timestamp, optional): timestamp for when this record was created
18
+
19
+ Raises:
15
20
 
21
+ """
22
+ identifier = 'http://gedcomx.org/v1/Attribution'
23
+ version = 'http://gedcomx.org/conceptual-model/v1'
16
24
 
17
- from .Agent import Agent
18
- self._contributor_object = None
25
+ def __init__(self,contributor: Optional[Agent | Resource] = None,
26
+ modified: Optional[datetime] = None,
27
+ changeMessage: Optional[str] = None,
28
+ creator: Optional[Agent | Resource] = None,
29
+ created: Optional[datetime] = None) -> None:
30
+
31
+ self.contributor = contributor
19
32
  self.modified = modified
20
33
  self.changeMessage = changeMessage
21
34
  self.creator = creator
22
35
  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
-
36
+
40
37
  @property
41
38
  def _as_dict_(self) -> Dict[str, Any]:
42
39
  """
43
40
  Serialize Attribution to a JSON-ready dict, skipping None values.
44
41
  """
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
42
+ 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
48
+
49
+ return Serialization.serialize_dict(type_as_dict)
65
50
 
66
51
  @classmethod
67
52
  def _from_json_(cls, data: Dict[str, Any]) -> 'Attribution':
@@ -70,47 +55,10 @@ class Attribution:
70
55
  Handles 'created' and 'modified' as ISO strings or epoch ms ints.
71
56
  """
72
57
  # 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
- )
58
+
59
+ """
60
+ Create a Person instance from a JSON-dict (already parsed).
61
+ """
62
+ from .Serialization import Serialization
63
+
64
+ return Serialization.deserialize(data, Attribution)