13
13
from dateutil .tz import tzutc
14
14
from inspect import getfullargspec
15
15
from inspect import getmembers
16
- from typing import Any , Callable , Dict , Generic , List , Mapping , Optional , Text , TypeVar , Type , Union , Set , overload
16
+ from typing import Any , Callable , Dict , Generic , List , Mapping , Optional , TypeVar , Type , Union , Set , overload
17
17
from typing import TYPE_CHECKING
18
18
19
19
from pynamodb ._compat import GenericMeta
20
20
from pynamodb .constants import (
21
21
BINARY , BINARY_SET , BOOLEAN , DATETIME_FORMAT , DEFAULT_ENCODING ,
22
22
LIST , MAP , NULL , NUMBER , NUMBER_SET , STRING , STRING_SET
23
23
)
24
+ from pynamodb .exceptions import AttributeDeserializationError
24
25
from pynamodb .expressions .operand import Path
25
26
26
27
@@ -71,12 +72,11 @@ def __init__(
71
72
self .is_hash_key = hash_key
72
73
self .is_range_key = range_key
73
74
74
- # AttributeContainerMeta._initialize_attributes will ensure this is a
75
- # string
75
+ # AttributeContainerMeta._initialize_attributes will ensure this is a string
76
76
self .attr_path : List [str ] = [attr_name ] # type: ignore
77
77
78
78
@property
79
- def attr_name (self ) -> Optional [ str ] :
79
+ def attr_name (self ) -> str :
80
80
return self .attr_path [- 1 ]
81
81
82
82
@attr_name .setter
@@ -120,8 +120,10 @@ def deserialize(self, value: Any) -> Any:
120
120
"""
121
121
return value
122
122
123
- def get_value (self , value : Any ) -> Any :
124
- return value .get (self .attr_type )
123
+ def get_value (self , value : Dict [str , Any ]) -> Any :
124
+ if self .attr_type not in value :
125
+ raise AttributeDeserializationError (self .attr_name , self .attr_type )
126
+ return value [self .attr_type ]
125
127
126
128
def __iter__ (self ):
127
129
# Because we define __getitem__ below for condition expression support
@@ -278,7 +280,7 @@ def get_attributes(cls) -> Dict[str, Attribute]:
278
280
return cls ._attributes # type: ignore
279
281
280
282
@classmethod
281
- def _dynamo_to_python_attr (cls , dynamo_key : str ) -> Optional [ str ] :
283
+ def _dynamo_to_python_attr (cls , dynamo_key : str ) -> str :
282
284
"""
283
285
Convert a DynamoDB attribute name to the internal Python name.
284
286
@@ -311,6 +313,18 @@ def _set_attributes(self, **attributes: Attribute) -> None:
311
313
raise ValueError ("Attribute {} specified does not exist" .format (attr_name ))
312
314
setattr (self , attr_name , attr_value )
313
315
316
+ def _deserialize (self , attribute_values : Dict [str , Dict [str , Any ]]) -> None :
317
+ """
318
+ Sets attributes sent back from DynamoDB on this object
319
+ """
320
+ self .attribute_values = {}
321
+ self ._set_defaults (_user_instantiated = False )
322
+ for name , attr in self .get_attributes ().items ():
323
+ attribute_value = attribute_values .get (attr .attr_name )
324
+ if attribute_value and NULL not in attribute_value :
325
+ value = attr .deserialize (attr .get_value (attribute_value ))
326
+ setattr (self , name , value )
327
+
314
328
def __eq__ (self , other : Any ) -> bool :
315
329
# This is required so that MapAttribute can call this method.
316
330
return self is other
@@ -803,8 +817,7 @@ def serialize(self, values):
803
817
continue
804
818
805
819
# If this is a subclassed MapAttribute, there may be an alternate attr name
806
- attr = self .get_attributes ().get (k )
807
- attr_name = attr .attr_name if attr else k
820
+ attr_name = attr_class .attr_name if not self .is_raw () else k
808
821
809
822
serialized = attr_class .serialize (v )
810
823
if self ._should_skip (serialized ):
@@ -819,23 +832,17 @@ def deserialize(self, values):
819
832
"""
820
833
Decode as a dict.
821
834
"""
822
- deserialized_dict : Dict [str , Any ] = dict ()
823
- for k in values :
824
- v = values [k ]
825
- attr_value = _get_value_for_deserialize (v )
826
- key = self ._dynamo_to_python_attr (k )
827
- attr_class = self ._get_deserialize_class (key , v )
828
- if key is None or attr_class is None :
829
- continue
830
- deserialized_value = None
831
- if attr_value is not None :
832
- deserialized_value = attr_class .deserialize (attr_value )
833
-
834
- deserialized_dict [key ] = deserialized_value
835
-
836
- # If this is a subclass of a MapAttribute (i.e typed), instantiate an instance
837
835
if not self .is_raw ():
838
- return type (self )(** deserialized_dict )
836
+ # If this is a subclass of a MapAttribute (i.e typed), instantiate an instance
837
+ instance = type (self )()
838
+ instance ._deserialize (values )
839
+ return instance
840
+
841
+ deserialized_dict : Dict [str , Any ] = dict ()
842
+ for k , v in values .items ():
843
+ attr_type , attr_value = next (iter (v .items ()))
844
+ attr_class = DESERIALIZE_CLASS_MAP [attr_type ]
845
+ deserialized_dict [k ] = attr_class .deserialize (attr_value )
839
846
return deserialized_dict
840
847
841
848
@classmethod
@@ -850,7 +857,7 @@ def as_dict(self):
850
857
851
858
def _should_skip (self , value ):
852
859
# Continue to serialize NULL values in "raw" map attributes for backwards compatibility.
853
- # This special case behavior for "raw" attribtues should be removed in the future.
860
+ # This special case behavior for "raw" attributes should be removed in the future.
854
861
return not self .is_raw () and value is None
855
862
856
863
@classmethod
@@ -859,32 +866,12 @@ def _get_serialize_class(cls, key, value):
859
866
return cls .get_attributes ().get (key )
860
867
return _get_class_for_serialize (value )
861
868
862
- @classmethod
863
- def _get_deserialize_class (cls , key , value ):
864
- if not cls .is_raw ():
865
- return cls .get_attributes ().get (key )
866
- return _get_class_for_deserialize (value )
867
-
868
-
869
- def _get_value_for_deserialize (value ):
870
- key = next (iter (value .keys ()))
871
- if key == NULL :
872
- return None
873
- return value [key ]
874
-
875
-
876
- def _get_class_for_deserialize (value ):
877
- value_type = next (iter (value .keys ()))
878
- if value_type not in DESERIALIZE_CLASS_MAP :
879
- raise ValueError ('Unknown value: ' + str (value ))
880
- return DESERIALIZE_CLASS_MAP [value_type ]
881
-
882
869
883
870
def _get_class_for_serialize (value ):
884
871
if value is None :
885
872
return NullAttribute ()
886
873
if isinstance (value , MapAttribute ):
887
- return type ( value )()
874
+ return value
888
875
value_type = type (value )
889
876
if value_type not in SERIALIZE_CLASS_MAP :
890
877
raise ValueError ('Unknown value: {}' .format (value_type ))
0 commit comments