6
6
import logging
7
7
import math
8
8
import random
9
- import re
10
9
import time
11
10
import uuid
12
11
from base64 import b64decode
25
24
RETURN_CONSUMED_CAPACITY_VALUES , RETURN_ITEM_COLL_METRICS_VALUES , COMPARISON_OPERATOR_VALUES ,
26
25
RETURN_ITEM_COLL_METRICS , RETURN_CONSUMED_CAPACITY , RETURN_VALUES_VALUES , ATTR_UPDATE_ACTIONS ,
27
26
COMPARISON_OPERATOR , EXCLUSIVE_START_KEY , SCAN_INDEX_FORWARD , SCAN_FILTER_VALUES , ATTR_DEFINITIONS ,
28
- BATCH_WRITE_ITEM , CONSISTENT_READ , ATTR_VALUE_LIST , DESCRIBE_TABLE , KEY_CONDITIONS ,
27
+ BATCH_WRITE_ITEM , CONSISTENT_READ , ATTR_VALUE_LIST , DESCRIBE_TABLE , KEY_CONDITION_EXPRESSION ,
29
28
BATCH_GET_ITEM , DELETE_REQUEST , SELECT_VALUES , RETURN_VALUES , REQUEST_ITEMS , ATTR_UPDATES ,
30
29
PROJECTION_EXPRESSION , SERVICE_NAME , DELETE_ITEM , PUT_REQUEST , UPDATE_ITEM , SCAN_FILTER , TABLE_NAME ,
31
30
INDEX_NAME , KEY_SCHEMA , ATTR_NAME , ATTR_TYPE , TABLE_KEY , EXPECTED , KEY_TYPE , GET_ITEM , UPDATE ,
36
35
CONSUMED_CAPACITY , CAPACITY_UNITS , QUERY_FILTER , QUERY_FILTER_VALUES , CONDITIONAL_OPERATOR ,
37
36
CONDITIONAL_OPERATORS , NULL , NOT_NULL , SHORT_ATTR_TYPES , DELETE ,
38
37
ITEMS , DEFAULT_ENCODING , BINARY_SHORT , BINARY_SET_SHORT , LAST_EVALUATED_KEY , RESPONSES , UNPROCESSED_KEYS ,
39
- UNPROCESSED_ITEMS , STREAM_SPECIFICATION , STREAM_VIEW_TYPE , STREAM_ENABLED , EXPRESSION_ATTRIBUTE_NAMES )
38
+ UNPROCESSED_ITEMS , STREAM_SPECIFICATION , STREAM_VIEW_TYPE , STREAM_ENABLED ,
39
+ EXPRESSION_ATTRIBUTE_NAMES , EXPRESSION_ATTRIBUTE_VALUES , KEY_CONDITION_OPERATOR_MAP )
40
40
from pynamodb .exceptions import (
41
41
TableError , QueryError , PutError , DeleteError , UpdateError , GetError , ScanError , TableDoesNotExist ,
42
42
VerboseClientError
43
43
)
44
+ from pynamodb .expressions .condition import Path
45
+ from pynamodb .expressions .projection import create_projection_expression
44
46
from pynamodb .settings import get_settings_value
45
47
from pynamodb .signals import pre_dynamodb_send , post_dynamodb_send
46
48
from pynamodb .types import HASH , RANGE
47
49
48
50
BOTOCORE_EXCEPTIONS = (BotoCoreError , ClientError )
49
- PATH_SEGMENT_REGEX = re .compile (r'([^\[\]]+)((?:\[\d+\])*)$' )
50
51
51
52
log = logging .getLogger (__name__ )
52
53
log .addHandler (NullHandler ())
@@ -937,7 +938,7 @@ def batch_get_item(self,
937
938
if return_consumed_capacity :
938
939
operation_kwargs .update (self .get_consumed_capacity_map (return_consumed_capacity ))
939
940
if attributes_to_get is not None :
940
- projection_expression = self . _get_projection_expression (attributes_to_get , name_placeholders )
941
+ projection_expression = create_projection_expression (attributes_to_get , name_placeholders )
941
942
args_map [PROJECTION_EXPRESSION ] = projection_expression
942
943
if name_placeholders :
943
944
args_map [EXPRESSION_ATTRIBUTE_NAMES ] = self ._reverse_dict (name_placeholders )
@@ -966,7 +967,7 @@ def get_item(self,
966
967
operation_kwargs = {}
967
968
name_placeholders = {}
968
969
if attributes_to_get is not None :
969
- projection_expression = self . _get_projection_expression (attributes_to_get , name_placeholders )
970
+ projection_expression = create_projection_expression (attributes_to_get , name_placeholders )
970
971
operation_kwargs [PROJECTION_EXPRESSION ] = projection_expression
971
972
if name_placeholders :
972
973
operation_kwargs [EXPRESSION_ATTRIBUTE_NAMES ] = self ._reverse_dict (name_placeholders )
@@ -1141,7 +1142,7 @@ def scan(self,
1141
1142
operation_kwargs = {TABLE_NAME : table_name }
1142
1143
name_placeholders = {}
1143
1144
if attributes_to_get is not None :
1144
- projection_expression = self . _get_projection_expression (attributes_to_get , name_placeholders )
1145
+ projection_expression = create_projection_expression (attributes_to_get , name_placeholders )
1145
1146
operation_kwargs [PROJECTION_EXPRESSION ] = projection_expression
1146
1147
if name_placeholders :
1147
1148
operation_kwargs [EXPRESSION_ATTRIBUTE_NAMES ] = self ._reverse_dict (name_placeholders )
@@ -1198,11 +1199,39 @@ def query(self,
1198
1199
"""
1199
1200
operation_kwargs = {TABLE_NAME : table_name }
1200
1201
name_placeholders = {}
1202
+ expression_attribute_values = {}
1203
+
1204
+ tbl = self .get_meta_table (table_name )
1205
+ if tbl is None :
1206
+ raise TableError ("No such table: {0}" .format (table_name ))
1207
+ if index_name :
1208
+ hash_keyname = tbl .get_index_hash_keyname (index_name )
1209
+ if not hash_keyname :
1210
+ raise ValueError ("No hash key attribute for index: {0}" .format (index_name ))
1211
+ else :
1212
+ hash_keyname = tbl .hash_keyname
1213
+
1214
+ key_condition_expression = self ._get_condition_expression (table_name , hash_keyname , '__eq__' , hash_key )
1215
+ if key_conditions is None or len (key_conditions ) == 0 :
1216
+ pass # No comparisons on sort key
1217
+ elif len (key_conditions ) > 1 :
1218
+ raise ValueError ("Multiple attributes are not supported in key_conditions: {0}" .format (key_conditions ))
1219
+ else :
1220
+ (key , condition ), = key_conditions .items ()
1221
+ operator = condition .get (COMPARISON_OPERATOR )
1222
+ if operator not in COMPARISON_OPERATOR_VALUES :
1223
+ raise ValueError ("{0} must be one of {1}" .format (COMPARISON_OPERATOR , COMPARISON_OPERATOR_VALUES ))
1224
+ operator = KEY_CONDITION_OPERATOR_MAP [operator ]
1225
+ values = condition .get (ATTR_VALUE_LIST )
1226
+ sort_key_expression = self ._get_condition_expression (table_name , key , operator , * values )
1227
+ key_condition_expression = key_condition_expression & sort_key_expression
1228
+
1229
+ operation_kwargs [KEY_CONDITION_EXPRESSION ] = key_condition_expression .serialize (
1230
+ name_placeholders , expression_attribute_values )
1231
+
1201
1232
if attributes_to_get :
1202
- projection_expression = self . _get_projection_expression (attributes_to_get , name_placeholders )
1233
+ projection_expression = create_projection_expression (attributes_to_get , name_placeholders )
1203
1234
operation_kwargs [PROJECTION_EXPRESSION ] = projection_expression
1204
- if name_placeholders :
1205
- operation_kwargs [EXPRESSION_ATTRIBUTE_NAMES ] = self ._reverse_dict (name_placeholders )
1206
1235
if consistent_read :
1207
1236
operation_kwargs [CONSISTENT_READ ] = True
1208
1237
if exclusive_start_key :
@@ -1223,49 +1252,20 @@ def query(self,
1223
1252
operation_kwargs [SELECT ] = str (select ).upper ()
1224
1253
if scan_index_forward is not None :
1225
1254
operation_kwargs [SCAN_INDEX_FORWARD ] = scan_index_forward
1226
- tbl = self .get_meta_table (table_name )
1227
- if tbl is None :
1228
- raise TableError ("No such table: {0}" .format (table_name ))
1229
- if index_name :
1230
- hash_keyname = tbl .get_index_hash_keyname (index_name )
1231
- if not hash_keyname :
1232
- raise ValueError ("No hash key attribute for index: {0}" .format (index_name ))
1233
- else :
1234
- hash_keyname = tbl .hash_keyname
1235
- operation_kwargs [KEY_CONDITIONS ] = {
1236
- hash_keyname : {
1237
- ATTR_VALUE_LIST : [
1238
- {
1239
- self .get_attribute_type (table_name , hash_keyname ): hash_key ,
1240
- }
1241
- ],
1242
- COMPARISON_OPERATOR : EQ
1243
- },
1244
- }
1245
- if key_conditions is not None :
1246
- for key , condition in key_conditions .items ():
1247
- attr_type = self .get_attribute_type (table_name , key )
1248
- operator = condition .get (COMPARISON_OPERATOR )
1249
- if operator not in COMPARISON_OPERATOR_VALUES :
1250
- raise ValueError ("{0} must be one of {1}" .format (COMPARISON_OPERATOR , COMPARISON_OPERATOR_VALUES ))
1251
- operation_kwargs [KEY_CONDITIONS ][key ] = {
1252
- ATTR_VALUE_LIST : [
1253
- {
1254
- attr_type : self .parse_attribute (value )
1255
- } for value in condition .get (ATTR_VALUE_LIST )
1256
- ],
1257
- COMPARISON_OPERATOR : operator
1258
- }
1255
+ if name_placeholders :
1256
+ operation_kwargs [EXPRESSION_ATTRIBUTE_NAMES ] = self ._reverse_dict (name_placeholders )
1257
+ if expression_attribute_values :
1258
+ operation_kwargs [EXPRESSION_ATTRIBUTE_VALUES ] = expression_attribute_values
1259
1259
1260
1260
try :
1261
1261
return self .dispatch (QUERY , operation_kwargs )
1262
1262
except BOTOCORE_EXCEPTIONS as e :
1263
1263
raise QueryError ("Failed to query items: {0}" .format (e ), e )
1264
1264
1265
- @ staticmethod
1266
- def _get_projection_expression ( attributes_to_get , placeholders ):
1267
- expressions = [_substitute_names ( attribute , placeholders ) for attribute in attributes_to_get ]
1268
- return ', ' . join ( expressions )
1265
+ def _get_condition_expression ( self , table_name , attribute_name , operator , * values ):
1266
+ attr_type = self . get_attribute_type ( table_name , attribute_name )
1267
+ values = [{ attr_type : self . parse_attribute ( value )} for value in values ]
1268
+ return getattr ( Path ( attribute_name ), operator )( * values )
1269
1269
1270
1270
@staticmethod
1271
1271
def _reverse_dict (d ):
@@ -1279,23 +1279,3 @@ def _convert_binary(attr):
1279
1279
value = attr [BINARY_SET_SHORT ]
1280
1280
if value and len (value ):
1281
1281
attr [BINARY_SET_SHORT ] = set (b64decode (v .encode (DEFAULT_ENCODING )) for v in value )
1282
-
1283
-
1284
- def _substitute_names (expression , placeholders ):
1285
- """
1286
- Replaces names in the given expression with placeholders.
1287
- Stores the placeholders in the given dictionary.
1288
- """
1289
- path_segments = expression .split ('.' )
1290
- for idx , segment in enumerate (path_segments ):
1291
- match = PATH_SEGMENT_REGEX .match (segment )
1292
- if not match :
1293
- raise ValueError ('{0} is not a valid document path' .format (expression ))
1294
- name , indexes = match .groups ()
1295
- if name in placeholders :
1296
- placeholder = placeholders [name ]
1297
- else :
1298
- placeholder = '#' + str (len (placeholders ))
1299
- placeholders [name ] = placeholder
1300
- path_segments [idx ] = placeholder + indexes
1301
- return '.' .join (path_segments )
0 commit comments