@@ -225,7 +225,7 @@ def execute_command(self, command_name, command, **options):
225
225
self .connection .disconnect ()
226
226
return self ._execute_command (command_name , command , ** options )
227
227
228
- def _parse_response (self , command_name ):
228
+ def _parse_response (self , command_name , catch_errors ):
229
229
conn = self .connection
230
230
response = conn .read ().strip ()
231
231
if not response :
@@ -261,13 +261,27 @@ def _parse_response(self, command_name):
261
261
length = int (response )
262
262
if length == - 1 :
263
263
return None
264
- return [self ._parse_response (command_name ) for i in range (length )]
264
+ if not catch_errors :
265
+ return [self ._parse_response (command_name , catch_errors )
266
+ for i in range (length )]
267
+ else :
268
+ # for pipelines, we need to read everything, including response errors.
269
+ # otherwise we'd completely mess up the receive buffer
270
+ data = []
271
+ for i in range (length ):
272
+ try :
273
+ data .append (
274
+ self ._parse_response (command_name , catch_errors )
275
+ )
276
+ except Exception , e :
277
+ data .append (e )
278
+ return data
265
279
266
280
raise InvalidResponse ("Unknown response type for: %s" % command_name )
267
281
268
- def parse_response (self , command_name , ** options ):
282
+ def parse_response (self , command_name , catch_errors = False , ** options ):
269
283
"Parses a response from the Redis server"
270
- response = self ._parse_response (command_name )
284
+ response = self ._parse_response (command_name , catch_errors )
271
285
if command_name in self .RESPONSE_CALLBACKS :
272
286
return self .RESPONSE_CALLBACKS [command_name ](response , ** options )
273
287
return response
@@ -886,16 +900,27 @@ class Pipeline(Redis):
886
900
in one transmission. This is convenient for batch processing, such as
887
901
saving all the values in a list to Redis.
888
902
889
- Note that pipelining does *not* guarantee all the commands will be executed
890
- together atomically, nor does it guarantee any transactional consistency.
891
- If the third command in the batch fails, the first two will still have been
892
- executed and "committed"
903
+ All commands executed within a pipeline are wrapped with MULTI and EXEC
904
+ calls. This guarantees all commands executed in the pipeline will be
905
+ executed atomically.
906
+
907
+ Any command raising an exception does *not* halt the execution of
908
+ subsequent commands in the pipeline. Instead, the exception is caught
909
+ and its instance is placed into the response list returned by execute().
910
+ Code iterating over the response list should be able to deal with an
911
+ instance of an exception as a potential value. In general, these will be
912
+ ResponseError exceptions, such as those raised when issuing a command
913
+ on a key of a different datatype.
893
914
"""
894
915
def __init__ (self , connection , charset , errors ):
895
916
self .connection = connection
896
- self .command_stack = []
897
917
self .encoding = charset
898
918
self .errors = errors
919
+ self .reset ()
920
+
921
+ def reset (self ):
922
+ self .command_stack = []
923
+ self .format_inline ('MULTI' )
899
924
900
925
def execute_command (self , command_name , command , ** options ):
901
926
"""
@@ -921,15 +946,34 @@ def execute_command(self, command_name, command, **options):
921
946
return self
922
947
923
948
def _execute (self , commands ):
924
- for _ , command , options in commands :
949
+ for name , command , options in commands :
925
950
self .connection .send (command , self )
926
- return [self .parse_response (name , ** options )
927
- for name , _ , options in commands ]
951
+ # we only care about the last item in the response, which should be
952
+ # the EXEC command
953
+ for i in range (len (commands )- 1 ):
954
+ _ = self .parse_response ('_' )
955
+ # tell the response parse to catch errors and return them as
956
+ # part of the response
957
+ response = self .parse_response ('_' , catch_errors = True )
958
+ # don't return the results of the MULTI or EXEC command
959
+ commands = [(c [0 ], c [2 ]) for c in commands [1 :- 1 ]]
960
+ if len (response ) != len (commands ):
961
+ raise ResponseError ("Wrong number of response items from "
962
+ "pipline execution" )
963
+ # Run any callbacks for the commands run in the pipeline
964
+ data = []
965
+ for r , cmd in zip (response , commands ):
966
+ if not isinstance (r , Exception ):
967
+ if cmd [0 ] in self .RESPONSE_CALLBACKS :
968
+ r = self .RESPONSE_CALLBACKS [cmd [0 ]](r , ** cmd [1 ])
969
+ data .append (r )
970
+ return data
928
971
929
972
def execute (self ):
930
973
"Execute all the commands in the current pipeline"
974
+ self .format_inline ('EXEC' )
931
975
stack = self .command_stack
932
- self .command_stack = []
976
+ self .reset ()
933
977
try :
934
978
return self ._execute (stack )
935
979
except ConnectionError :
0 commit comments