3
3
import socket
4
4
import threading
5
5
import warnings
6
+ from itertools import chain
6
7
from redis .exceptions import ConnectionError , ResponseError , InvalidResponse
7
8
from redis .exceptions import RedisError , AuthenticationError
8
9
@@ -246,8 +247,20 @@ def _get_db(self):
246
247
return self .connection .db
247
248
db = property (_get_db )
248
249
249
- def pipeline (self ):
250
- return Pipeline (self .connection , self .encoding , self .errors )
250
+ def pipeline (self , transaction = True ):
251
+ """
252
+ Return a new pipeline object that can queue multiple commands for
253
+ later execution. ``transaction`` indicates whether all commands
254
+ should be executed atomically. Apart from multiple atomic operations,
255
+ pipelines are useful for batch loading of data as they reduce the
256
+ number of back and forth network operations between client and server.
257
+ """
258
+ return Pipeline (
259
+ self .connection ,
260
+ transaction ,
261
+ self .encoding ,
262
+ self .errors
263
+ )
251
264
252
265
253
266
#### COMMAND EXECUTION AND PROTOCOL PARSING ####
@@ -1032,16 +1045,16 @@ class Pipeline(Redis):
1032
1045
ResponseError exceptions, such as those raised when issuing a command
1033
1046
on a key of a different datatype.
1034
1047
"""
1035
- def __init__ (self , connection , charset , errors ):
1048
+ def __init__ (self , connection , transaction , charset , errors ):
1036
1049
self .connection = connection
1050
+ self .transaction = transaction
1037
1051
self .encoding = charset
1038
1052
self .errors = errors
1039
1053
self .subscribed = False # NOTE not in use, but necessary
1040
1054
self .reset ()
1041
1055
1042
1056
def reset (self ):
1043
1057
self .command_stack = []
1044
- self .execute_command ('MULTI' )
1045
1058
1046
1059
def _execute_command (self , command_name , command , ** options ):
1047
1060
"""
@@ -1066,19 +1079,20 @@ def _execute_command(self, command_name, command, **options):
1066
1079
self .command_stack .append ((command_name , command , options ))
1067
1080
return self
1068
1081
1069
- def _execute (self , commands ):
1070
- # build up all commands into a single request to increase network perf
1071
- all_cmds = '' .join ([c for _1 , c , _2 in commands ])
1082
+ def _execute_transaction (self , commands ):
1083
+ # wrap the commands in MULTI ... EXEC statements to indicate an
1084
+ # atomic operation
1085
+ all_cmds = '' .join ([c for _1 , c , _2 in chain (
1086
+ (('' , 'MULTI\r \n ' , '' ),),
1087
+ commands ,
1088
+ (('' , 'EXEC\r \n ' , '' ),)
1089
+ )])
1072
1090
self .connection .send (all_cmds , self )
1073
- # we only care about the last item in the response, which should be
1074
- # the EXEC command
1075
- for i in range (len (commands )- 1 ):
1091
+ # parse off the response for MULTI and all commands prior to EXEC
1092
+ for i in range (len (commands )+ 1 ):
1076
1093
_ = self .parse_response ('_' )
1077
- # tell the response parse to catch errors and return them as
1078
- # part of the response
1094
+ # parse the EXEC. we want errors returned as items in the response
1079
1095
response = self .parse_response ('_' , catch_errors = True )
1080
- # don't return the results of the MULTI or EXEC command
1081
- commands = [(c [0 ], c [2 ]) for c in commands [1 :- 1 ]]
1082
1096
if len (response ) != len (commands ):
1083
1097
raise ResponseError ("Wrong number of response items from "
1084
1098
"pipline execution" )
@@ -1087,20 +1101,34 @@ def _execute(self, commands):
1087
1101
for r , cmd in zip (response , commands ):
1088
1102
if not isinstance (r , Exception ):
1089
1103
if cmd [0 ] in self .RESPONSE_CALLBACKS :
1090
- r = self .RESPONSE_CALLBACKS [cmd [0 ]](r , ** cmd [1 ])
1104
+ r = self .RESPONSE_CALLBACKS [cmd [0 ]](r , ** cmd [2 ])
1091
1105
data .append (r )
1092
1106
return data
1093
1107
1108
+ def _execute_pipeline (self , commands ):
1109
+ # build up all commands into a single request to increase network perf
1110
+ all_cmds = '' .join ([c for _1 , c , _2 in commands ])
1111
+ self .connection .send (all_cmds , self )
1112
+ data = []
1113
+ for command_name , _ , options in commands :
1114
+ data .append (
1115
+ self .parse_response (command_name , catch_errors = True , ** options )
1116
+ )
1117
+ return data
1118
+
1094
1119
def execute (self ):
1095
1120
"Execute all the commands in the current pipeline"
1096
- self .execute_command ('EXEC' )
1097
1121
stack = self .command_stack
1098
1122
self .reset ()
1123
+ if self .transaction :
1124
+ execute = self ._execute_transaction
1125
+ else :
1126
+ execute = self ._execute_pipeline
1099
1127
try :
1100
- return self . _execute (stack )
1128
+ return execute (stack )
1101
1129
except ConnectionError :
1102
1130
self .connection .disconnect ()
1103
- return self . _execute (stack )
1131
+ return execute (stack )
1104
1132
1105
1133
def select (self , * args , ** kwargs ):
1106
1134
raise RedisError ("Cannot select a different database from a pipeline" )
0 commit comments