@@ -167,16 +167,10 @@ class Redis(object):
167
167
}
168
168
)
169
169
170
- # commands that should NOT pull data off the network buffer when executed
171
- SUBSCRIPTION_COMMANDS = set ([
172
- 'SUBSCRIBE' , 'UNSUBSCRIBE' , 'PSUBSCRIBE' , 'PUNSUBSCRIBE'
173
- ])
174
-
175
170
def __init__ (self , host = 'localhost' , port = 6379 ,
176
171
db = 0 , password = None , socket_timeout = None ,
177
172
connection_pool = None ,
178
173
charset = 'utf-8' , errors = 'strict' ):
179
- self .subscribed = False
180
174
if connection_pool :
181
175
self .connection_pool = connection_pool
182
176
else :
@@ -214,26 +208,20 @@ def lock(self, name, timeout=None, sleep=0.1):
214
208
"""
215
209
return Lock (self , name , timeout = timeout , sleep = sleep )
216
210
211
+ def pubsub (self ):
212
+ return PubSub (self .connection_pool )
213
+
217
214
#### COMMAND EXECUTION AND PROTOCOL PARSING ####
218
215
def execute_command (self , * args , ** options ):
219
216
command_name = args [0 ]
220
217
connection = self .connection_pool .get_connection (command_name )
221
218
try :
222
- subscription_command = command_name in self .SUBSCRIPTION_COMMANDS
223
- if self .subscribed and not subscription_command :
224
- raise PubSubError ("Cannot issue commands other than SUBSCRIBE "
225
- "and UNSUBSCRIBE while channels are open" )
226
- try :
227
- connection .send_command (* args )
228
- if subscription_command :
229
- return None
230
- return self .parse_response (connection , command_name , ** options )
231
- except ConnectionError :
232
- connection .disconnect ()
233
- connection .send_command (* args )
234
- if subscription_command :
235
- return None
236
- return self .parse_response (connection , command_name , ** options )
219
+ connection .send_command (* args )
220
+ return self .parse_response (connection , command_name , ** options )
221
+ except ConnectionError :
222
+ connection .disconnect ()
223
+ connection .send_command (* args )
224
+ return self .parse_response (connection , command_name , ** options )
237
225
finally :
238
226
self .connection_pool .release (connection )
239
227
@@ -244,23 +232,6 @@ def parse_response(self, connection, command_name, **options):
244
232
return self .RESPONSE_CALLBACKS [command_name ](response , ** options )
245
233
return response
246
234
247
- #### CONNECTION HANDLING ####
248
- def select (self , db ):
249
- "SELECT a differnet Redis database."
250
- return self .execute_command ('SELECT' , db )
251
-
252
- def shutdown (self ):
253
- "Shutdown the server"
254
- if self .subscribed :
255
- raise PubSubError ("Can't call 'shutdown' when 'subscribed'" )
256
- try :
257
- self .execute_command ('SHUTDOWN' )
258
- except ConnectionError :
259
- # a ConnectionError here is expected
260
- return
261
- raise RedisError ("SHUTDOWN seems to have failed." )
262
-
263
-
264
235
#### SERVER INFORMATION ####
265
236
def bgrewriteaof (self ):
266
237
"Tell the Redis server to rewrite the AOF file from data in memory."
@@ -328,6 +299,19 @@ def save(self):
328
299
"""
329
300
return self .execute_command ('SAVE' )
330
301
302
+ def select (self , db ):
303
+ "Select a differnet Redis database"
304
+ return self .execute_command ('SELECT' , db )
305
+
306
+ def shutdown (self ):
307
+ "Shutdown the server"
308
+ try :
309
+ self .execute_command ('SHUTDOWN' )
310
+ except ConnectionError :
311
+ # a ConnectionError here is expected
312
+ return
313
+ raise RedisError ("SHUTDOWN seems to have failed." )
314
+
331
315
def slaveof (self , host = None , port = None ):
332
316
"""
333
317
Set the server to be a replicated slave of the instance identified
@@ -552,18 +536,12 @@ def watch(self, *names):
552
536
"""
553
537
Watches the values at keys ``names``, or None if the key doesn't exist
554
538
"""
555
- if self .subscribed :
556
- raise PubSubError ("Can't call 'watch' when 'subscribed'" )
557
-
558
539
return self .execute_command ('WATCH' , * names )
559
540
560
541
def unwatch (self ):
561
542
"""
562
543
Unwatches the value at key ``name``, or None of the key doesn't exist
563
544
"""
564
- if self .subscribed :
565
- raise PubSubError ("Can't call 'unwatch' when 'subscribed'" )
566
-
567
545
return self .execute_command ('UNWATCH' )
568
546
569
547
#### LIST COMMANDS ####
@@ -1107,20 +1085,63 @@ def hvals(self, name):
1107
1085
"Return the list of values within hash ``name``"
1108
1086
return self .execute_command ('HVALS' , name )
1109
1087
1110
- def pubsub (self ):
1111
- return PubSub (self .connection_pool )
1088
+ def publish (self , channel , message ):
1089
+ """
1090
+ Publish ``message`` on ``channel``.
1091
+ Returns the number of subscribers the message was delivered to.
1092
+ """
1093
+ return self .execute_command ('PUBLISH' , channel , message )
1112
1094
1113
1095
1114
- # channels
1096
+ class PubSub (object ):
1097
+ def __init__ (self , connection_pool ):
1098
+ self .connection_pool = connection_pool
1099
+ self .connection = None
1100
+ self .channels = set ()
1101
+ self .patterns = set ()
1102
+ self .subscription_count = 0
1103
+ self .subscribe_commands = set (
1104
+ ('subscribe' , 'psusbscribe' , 'unsubscribe' , 'punsubscribe' )
1105
+ )
1106
+
1107
+ def execute_command (self , * args , ** kwargs ):
1108
+ "Execute a publish/subscribe command"
1109
+ if self .connection is None :
1110
+ self .connection = self .connection_pool .get_connection ('pubsub' )
1111
+ connection = self .connection
1112
+ try :
1113
+ connection .send_command (* args )
1114
+ return self .parse_response ()
1115
+ except ConnectionError :
1116
+ connection .disconnect ()
1117
+ # resubscribe to all channels and patterns before
1118
+ # resending the current command
1119
+ for channel in self .channels :
1120
+ self .subscribe (channel )
1121
+ for pattern in self .patterns :
1122
+ self .psubscribe (pattern )
1123
+ connection .send_command (* args )
1124
+ return self .parse_response ()
1125
+
1126
+ def parse_response (self ):
1127
+ "Parse the response from a publish/subscribe command"
1128
+ response = self .connection .read_response ()
1129
+ if response [0 ] in self .subscribe_commands :
1130
+ self .subscription_count = response [2 ]
1131
+ # if we've just unsubscribed from the remaining channels,
1132
+ # release the connection back to the pool
1133
+ if not self .subscription_count :
1134
+ self .connection_pool .release (self .connection )
1135
+ self .connection = None
1136
+ return response
1137
+
1115
1138
def psubscribe (self , patterns ):
1116
1139
"Subscribe to all channels matching any pattern in ``patterns``"
1117
1140
if isinstance (patterns , basestring ):
1118
1141
patterns = [patterns ]
1119
- response = self .execute_command ('PSUBSCRIBE' , * patterns )
1120
- # this is *after* the SUBSCRIBE in order to allow for lazy and broken
1121
- # connections that need to issue AUTH and SELECT commands
1122
- self .subscribed = True
1123
- return response
1142
+ for pattern in patterns :
1143
+ self .patterns .add (pattern )
1144
+ return self .execute_command ('PSUBSCRIBE' , * patterns )
1124
1145
1125
1146
def punsubscribe (self , patterns = []):
1126
1147
"""
@@ -1129,17 +1150,20 @@ def punsubscribe(self, patterns=[]):
1129
1150
"""
1130
1151
if isinstance (patterns , basestring ):
1131
1152
patterns = [patterns ]
1153
+ for pattern in patterns :
1154
+ try :
1155
+ self .patterns .remove (pattern )
1156
+ except KeyError :
1157
+ pass
1132
1158
return self .execute_command ('PUNSUBSCRIBE' , * patterns )
1133
1159
1134
1160
def subscribe (self , channels ):
1135
1161
"Subscribe to ``channels``, waiting for messages to be published"
1136
1162
if isinstance (channels , basestring ):
1137
1163
channels = [channels ]
1138
- response = self .execute_command ('SUBSCRIBE' , * channels )
1139
- # this is *after* the SUBSCRIBE in order to allow for lazy and broken
1140
- # connections that need to issue AUTH and SELECT commands
1141
- self .subscribed = True
1142
- return response
1164
+ for channel in channels :
1165
+ self .channels .add (channel )
1166
+ return self .execute_command ('SUBSCRIBE' , * channels )
1143
1167
1144
1168
def unsubscribe (self , channels = []):
1145
1169
"""
@@ -1148,6 +1172,11 @@ def unsubscribe(self, channels=[]):
1148
1172
"""
1149
1173
if isinstance (channels , basestring ):
1150
1174
channels = [channels ]
1175
+ for channel in channels :
1176
+ try :
1177
+ self .channels .remove (channel )
1178
+ except KeyError :
1179
+ pass
1151
1180
return self .execute_command ('UNSUBSCRIBE' , * channels )
1152
1181
1153
1182
def publish (self , channel , message ):
@@ -1159,24 +1188,22 @@ def publish(self, channel, message):
1159
1188
1160
1189
def listen (self ):
1161
1190
"Listen for messages on channels this client has been subscribed to"
1162
- while self .subscribed :
1163
- r = self .parse_response ('LISTEN' )
1191
+ while self .subscription_count :
1192
+ r = self .parse_response ()
1164
1193
if r [0 ] == 'pmessage' :
1165
1194
msg = {
1166
- 'type' : r [0 ],
1167
- 'pattern' : r [1 ],
1168
- 'channel' : r [2 ],
1169
- 'data' : r [3 ]
1195
+ 'type' : r [0 ],
1196
+ 'pattern' : r [1 ],
1197
+ 'channel' : r [2 ],
1198
+ 'data' : r [3 ]
1170
1199
}
1171
1200
else :
1172
1201
msg = {
1173
- 'type' : r [0 ],
1174
- 'pattern' : None ,
1175
- 'channel' : r [1 ],
1176
- 'data' : r [2 ]
1202
+ 'type' : r [0 ],
1203
+ 'pattern' : None ,
1204
+ 'channel' : r [1 ],
1205
+ 'data' : r [2 ]
1177
1206
}
1178
- if r [0 ] == 'unsubscribe' and r [2 ] == 0 :
1179
- self .subscribed = False
1180
1207
yield msg
1181
1208
1182
1209
@@ -1201,7 +1228,6 @@ class Pipeline(Redis):
1201
1228
def __init__ (self , connection_pool , transaction ):
1202
1229
self .connection_pool = connection_pool
1203
1230
self .transaction = transaction
1204
- self .subscribed = False # NOTE not in use, but necessary
1205
1231
self .reset ()
1206
1232
1207
1233
def reset (self ):
0 commit comments