1
1
"""
2
2
DynamoDB Models for PynamoDB
3
3
"""
4
- import json
5
4
import random
6
5
import time
7
6
import logging
13
12
from pynamodb .expressions .update import Action
14
13
from pynamodb .exceptions import DoesNotExist , TableDoesNotExist , TableError , InvalidStateError , PutError
15
14
from pynamodb .attributes import (
16
- Attribute , AttributeContainer , AttributeContainerMeta , MapAttribute , TTLAttribute , VersionAttribute
15
+ Attribute , AttributeContainer , AttributeContainerMeta , TTLAttribute , VersionAttribute
17
16
)
18
17
from pynamodb .connection .table import TableConnection
19
18
from pynamodb .expressions .condition import Condition
@@ -119,10 +118,9 @@ def commit(self) -> None:
119
118
log .debug ("%s committing batch operation" , self .model )
120
119
put_items = []
121
120
delete_items = []
122
- attrs_name = snake_to_camel_case (ATTRIBUTES )
123
121
for item in self .pending_operations :
124
122
if item ['action' ] == PUT :
125
- put_items .append (item ['item' ]._serialize (attr_map = True )[ attrs_name ] )
123
+ put_items .append (item ['item' ]._serialize () )
126
124
elif item ['action' ] == DELETE :
127
125
delete_items .append (item ['item' ]._get_keys ())
128
126
self .pending_operations = []
@@ -380,12 +378,11 @@ def batch_write(cls: Type[_T], auto_commit: bool = True) -> BatchWrite[_T]:
380
378
return BatchWrite (cls , auto_commit = auto_commit )
381
379
382
380
def __repr__ (self ) -> str :
383
- table_name = self .Meta .table_name if self .Meta .table_name else 'unknown'
384
- serialized = self ._serialize (null_check = False )
381
+ hash_key , range_key = self ._get_serialized_keys ()
385
382
if self ._range_keyname :
386
- msg = "{}<{}, {}>" .format (self .Meta .table_name , serialized . get ( HASH ), serialized . get ( RANGE ) )
383
+ msg = "{}<{}, {}>" .format (self .Meta .table_name , hash_key , range_key )
387
384
else :
388
- msg = "{}<{}>" .format (self .Meta .table_name , serialized . get ( HASH ) )
385
+ msg = "{}<{}>" .format (self .Meta .table_name , hash_key )
389
386
return msg
390
387
391
388
def delete (self , condition : Optional [Condition ] = None ) -> Any :
@@ -824,59 +821,7 @@ def update_ttl(cls, ignore_update_ttl_errors: bool) -> None:
824
821
else :
825
822
raise
826
823
827
- @classmethod
828
- def dumps (cls ) -> Any :
829
- """
830
- Returns a JSON representation of this model's table
831
- """
832
- return json .dumps ([item ._get_json () for item in cls .scan ()])
833
-
834
- @classmethod
835
- def dump (cls , filename : str ) -> None :
836
- """
837
- Writes the contents of this model's table as JSON to the given filename
838
- """
839
- with open (filename , 'w' ) as out :
840
- out .write (cls .dumps ())
841
-
842
- @classmethod
843
- def loads (cls , data : str ) -> None :
844
- content = json .loads (data )
845
- with cls .batch_write () as batch :
846
- for item_data in content :
847
- item = cls ._from_data (item_data )
848
- batch .save (item )
849
-
850
- @classmethod
851
- def load (cls , filename : str ) -> None :
852
- with open (filename , 'r' ) as inf :
853
- cls .loads (inf .read ())
854
-
855
824
# Private API below
856
- @classmethod
857
- def _from_data (cls , data ):
858
- """
859
- Reconstructs a model object from JSON.
860
- """
861
- hash_key , attrs = data
862
- range_key = attrs .pop ('range_key' , None )
863
- attributes = attrs .pop (snake_to_camel_case (ATTRIBUTES ))
864
- hash_key_attribute = cls ._hash_key_attribute ()
865
- hash_keyname = hash_key_attribute .attr_name
866
- hash_keytype = hash_key_attribute .attr_type
867
- attributes [hash_keyname ] = {
868
- hash_keytype : hash_key
869
- }
870
- if range_key is not None :
871
- range_key_attribute = cls ._range_key_attribute ()
872
- range_keyname = range_key_attribute .attr_name
873
- range_keytype = range_key_attribute .attr_type
874
- attributes [range_keyname ] = {
875
- range_keytype : range_key
876
- }
877
- item = cls (_user_instantiated = False )
878
- item ._deserialize (attributes )
879
- return item
880
825
881
826
@classmethod
882
827
def _get_schema (cls ):
@@ -943,19 +888,6 @@ def _get_indexes(cls):
943
888
cls ._indexes [snake_to_camel_case (LOCAL_SECONDARY_INDEXES )].append (idx )
944
889
return cls ._indexes
945
890
946
- def _get_json (self ):
947
- """
948
- Returns a Python object suitable for serialization
949
- """
950
- kwargs = {}
951
- serialized = self ._serialize (null_check = False )
952
- hash_key = serialized .get (HASH )
953
- range_key = serialized .get (RANGE , None )
954
- if range_key is not None :
955
- kwargs [snake_to_camel_case (RANGE_KEY )] = range_key
956
- kwargs [snake_to_camel_case (ATTRIBUTES )] = serialized [snake_to_camel_case (ATTRIBUTES )]
957
- return hash_key , kwargs
958
-
959
891
def _get_save_args (self , attributes = True , null_check = True ):
960
892
"""
961
893
Gets the proper *args, **kwargs for saving and retrieving this object
@@ -966,14 +898,18 @@ def _get_save_args(self, attributes=True, null_check=True):
966
898
:param null_check: If True, then attributes are checked for null.
967
899
"""
968
900
kwargs = {}
969
- serialized = self ._serialize (null_check = null_check )
970
- hash_key = serialized .get (HASH )
971
- range_key = serialized .get (RANGE , None )
901
+ attribute_values = self ._serialize (null_check )
902
+ hash_key_attribute = self ._hash_key_attribute ()
903
+ hash_key = attribute_values .pop (hash_key_attribute .attr_name , {}).get (hash_key_attribute .attr_type )
904
+ range_key = None
905
+ range_key_attribute = self ._range_key_attribute ()
906
+ if range_key_attribute :
907
+ range_key = attribute_values .pop (range_key_attribute .attr_name , {}).get (range_key_attribute .attr_type )
972
908
args = (hash_key , )
973
909
if range_key is not None :
974
910
kwargs [snake_to_camel_case (RANGE_KEY )] = range_key
975
911
if attributes :
976
- kwargs [snake_to_camel_case (ATTRIBUTES )] = serialized [ snake_to_camel_case ( ATTRIBUTES )]
912
+ kwargs [snake_to_camel_case (ATTRIBUTES )] = attribute_values
977
913
return args , kwargs
978
914
979
915
def _handle_version_attribute (self , serialized_attributes , actions = None ):
@@ -1042,17 +978,21 @@ def _get_keys(self):
1042
978
"""
1043
979
Returns the proper arguments for deleting
1044
980
"""
1045
- serialized = self ._serialize (null_check = False )
1046
- hash_key = serialized .get (HASH )
1047
- range_key = serialized .get (RANGE , None )
1048
- attrs = {
1049
- self ._hash_key_attribute ().attr_name : hash_key ,
1050
- }
1051
- if self ._range_keyname is not None :
1052
- range_keyname = self ._range_key_attribute ().attr_name
1053
- attrs [range_keyname ] = range_key
981
+ hash_key , range_key = self ._get_serialized_keys ()
982
+ hash_key_attribute = self ._hash_key_attribute ()
983
+ range_key_attribute = self ._range_key_attribute ()
984
+ attrs = {}
985
+ if hash_key_attribute :
986
+ attrs [hash_key_attribute .attr_name ] = hash_key
987
+ if range_key_attribute :
988
+ attrs [range_key_attribute .attr_name ] = range_key
1054
989
return attrs
1055
990
991
+ def _get_serialized_keys (self ) -> Tuple [_KeyType , _KeyType ]:
992
+ hash_key = getattr (self , self ._hash_keyname ) if self ._hash_keyname else None
993
+ range_key = getattr (self , self ._range_keyname ) if self ._range_keyname else None
994
+ return self ._serialize_keys (hash_key , range_key )
995
+
1056
996
@classmethod
1057
997
def _batch_get_page (cls , keys_to_get , consistent_read , attributes_to_get ):
1058
998
"""
@@ -1107,27 +1047,6 @@ def _get_connection(cls) -> TableConnection:
1107
1047
aws_session_token = cls .Meta .aws_session_token )
1108
1048
return cls ._connection
1109
1049
1110
- def _serialize (self , null_check = True , attr_map = False ) -> Dict [str , Dict [str , Any ]]:
1111
- """
1112
- Serializes all model attributes for use with DynamoDB
1113
-
1114
- :param null_check: If True, then attributes are checked for null
1115
- :param attr_map: If True, then attributes are returned
1116
- """
1117
- attributes = snake_to_camel_case (ATTRIBUTES )
1118
- attrs : Dict [str , Dict ] = {attributes : super ()._serialize (null_check )}
1119
- if not attr_map :
1120
- hash_key_attribute = self ._hash_key_attribute ()
1121
- hash_key_attribute_value = attrs [attributes ].pop (hash_key_attribute .attr_name , None )
1122
- if hash_key_attribute_value is not None :
1123
- attrs [HASH ] = hash_key_attribute_value [hash_key_attribute .attr_type ]
1124
- range_key_attribute = self ._range_key_attribute ()
1125
- if range_key_attribute :
1126
- range_key_attribute_value = attrs [attributes ].pop (range_key_attribute .attr_name , None )
1127
- if range_key_attribute_value is not None :
1128
- attrs [RANGE ] = range_key_attribute_value [range_key_attribute .attr_type ]
1129
- return attrs
1130
-
1131
1050
@classmethod
1132
1051
def _serialize_value (cls , attr , value ):
1133
1052
"""
@@ -1153,7 +1072,8 @@ def _serialize_keys(cls, hash_key, range_key=None) -> Tuple[_KeyType, _KeyType]:
1153
1072
:param hash_key: The hash key value
1154
1073
:param range_key: The range key value
1155
1074
"""
1156
- hash_key = cls ._hash_key_attribute ().serialize (hash_key )
1075
+ if hash_key is not None :
1076
+ hash_key = cls ._hash_key_attribute ().serialize (hash_key )
1157
1077
if range_key is not None :
1158
1078
range_key = cls ._range_key_attribute ().serialize (range_key )
1159
1079
return hash_key , range_key
0 commit comments