Skip to content

Commit 54e5b06

Browse files
committed
automatic message decoding if decode_responses=True. bugfixes, tests.
1 parent 8b7cd8d commit 54e5b06

File tree

3 files changed

+289
-59
lines changed

3 files changed

+289
-59
lines changed

redis/client.py

Lines changed: 66 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1762,6 +1762,12 @@ def __init__(self, connection_pool, shard_hint=None,
17621762
self.shard_hint = shard_hint
17631763
self.ignore_subscribe_messages = ignore_subscribe_messages
17641764
self.connection = None
1765+
# we need to know the encoding options for this connection in order
1766+
# to lookup channel and pattern names for callback handlers.
1767+
connection_kwargs = self.connection_pool.connection_kwargs
1768+
self.encoding = connection_kwargs['encoding']
1769+
self.encoding_errors = connection_kwargs['encoding_errors']
1770+
self.decode_responses = connection_kwargs['decode_responses']
17651771
self.reset()
17661772

17671773
def __del__(self):
@@ -1786,10 +1792,35 @@ def close(self):
17861792
self.reset()
17871793

17881794
def on_connect(self, connection):
1795+
"Re-subscribe to any channels and patterns previously subscribed to"
1796+
# NOTE: for python3, we can't pass bytestrings as keyword arguments
1797+
# so we need to decode channel/pattern names back to unicode strings
1798+
# before passing them to [p]subscribe.
17891799
if self.channels:
1790-
self.subscribe(**self.channels)
1800+
channels = {}
1801+
for k, v in iteritems(self.channels):
1802+
if not self.decode_responses:
1803+
k = k.decode(self.encoding, self.encoding_errors)
1804+
channels[k] = v
1805+
self.subscribe(**channels)
17911806
if self.patterns:
1792-
self.psubscribe(**self.patterns)
1807+
patterns = {}
1808+
for k, v in iteritems(self.patterns):
1809+
if not self.decode_responses:
1810+
k = k.decode(self.encoding, self.encoding_errors)
1811+
patterns[k] = v
1812+
self.psubscribe(**patterns)
1813+
1814+
def encode(self, value):
1815+
"""
1816+
Encode the value so that it's identical to what we'll
1817+
read off the connection
1818+
"""
1819+
if self.decode_responses and isinstance(value, bytes):
1820+
value = value.decode(self.encoding, self.encoding_errors)
1821+
elif not self.decode_responses and isinstance(value, unicode):
1822+
value = value.encode(self.encoding, self.encoding_errors)
1823+
return value
17931824

17941825
@property
17951826
def subscribed(self):
@@ -1808,10 +1839,8 @@ def execute_command(self, *args, **kwargs):
18081839
'pubsub',
18091840
self.shard_hint
18101841
)
1811-
# initially connect here so we don't run our callback the first
1812-
# time. If we did, it would dupe the subscriptions, once from the
1813-
# callback and a second time from the actual command invocation
1814-
self.connection.connect()
1842+
# register a callback that re-subscribes to any channels we
1843+
# were listening to when we were disconnected
18151844
self.connection.register_connect_callback(self.on_connect)
18161845
connection = self.connection
18171846
self._execute(connection, connection.send_command, *args)
@@ -1847,10 +1876,15 @@ def psubscribe(self, *args, **kwargs):
18471876
if args:
18481877
args = list_or_args(args[0], args[1:])
18491878
new_patterns = {}
1850-
new_patterns.update(dict.fromkeys(args))
1851-
new_patterns.update(kwargs)
1879+
new_patterns.update(dict.fromkeys(imap(self.encode, args)))
1880+
for pattern, handler in iteritems(kwargs):
1881+
new_patterns[self.encode(pattern)] = handler
1882+
ret_val = self.execute_command('PSUBSCRIBE', *iterkeys(new_patterns))
1883+
# update the patterns dict AFTER we send the command. we don't want to
1884+
# subscribe twice to these patterns, once for the command and again
1885+
# for the reconnection.
18521886
self.patterns.update(new_patterns)
1853-
return self.execute_command('PSUBSCRIBE', *iterkeys(new_patterns))
1887+
return ret_val
18541888

18551889
def punsubscribe(self, *args):
18561890
"""
@@ -1872,10 +1906,15 @@ def subscribe(self, *args, **kwargs):
18721906
if args:
18731907
args = list_or_args(args[0], args[1:])
18741908
new_channels = {}
1875-
new_channels.update(dict.fromkeys(args))
1876-
new_channels.update(kwargs)
1909+
new_channels.update(dict.fromkeys(imap(self.encode, args)))
1910+
for channel, handler in iteritems(kwargs):
1911+
new_channels[self.encode(channel)] = handler
1912+
ret_val = self.execute_command('SUBSCRIBE', *iterkeys(new_channels))
1913+
# update the channels dict AFTER we send the command. we don't want to
1914+
# subscribe twice to these channels, once for the command and again
1915+
# for the reconnection.
18771916
self.channels.update(new_channels)
1878-
return self.execute_command('SUBSCRIBE', *iterkeys(new_channels))
1917+
return ret_val
18791918

18801919
def unsubscribe(self, *args):
18811920
"""
@@ -1910,18 +1949,30 @@ def handle_message(self, response, ignore_subscribe_messages=False):
19101949
if message_type == 'pmessage':
19111950
message = {
19121951
'type': message_type,
1913-
'pattern': nativestr(response[1]),
1914-
'channel': nativestr(response[2]),
1952+
'pattern': response[1],
1953+
'channel': response[2],
19151954
'data': response[3]
19161955
}
19171956
else:
19181957
message = {
19191958
'type': message_type,
19201959
'pattern': None,
1921-
'channel': nativestr(response[1]),
1960+
'channel': response[1],
19221961
'data': response[2]
19231962
}
19241963

1964+
# if this is an unsubscribe message, remove it from memory
1965+
if message_type in self.UNSUBSCRIBE_MESSAGE_TYPES:
1966+
subscribed_dict = None
1967+
if message_type == 'punsubscribe':
1968+
subscribed_dict = self.patterns
1969+
else:
1970+
subscribed_dict = self.channels
1971+
try:
1972+
del subscribed_dict[message['channel']]
1973+
except KeyError:
1974+
pass
1975+
19251976
if message_type in self.PUBLISH_MESSAGE_TYPES:
19261977
# if there's a message handler, invoke it
19271978
handler = None
@@ -1938,18 +1989,6 @@ def handle_message(self, response, ignore_subscribe_messages=False):
19381989
if ignore_subscribe_messages or self.ignore_subscribe_messages:
19391990
return None
19401991

1941-
# if this is an unsubscribe message, remove it from memory
1942-
if message_type in self.UNSUBSCRIBE_MESSAGE_TYPES:
1943-
subscribed_dict = None
1944-
if message_type == 'punsubscribe':
1945-
subscribed_dict = self.patterns
1946-
else:
1947-
subscribed_dict = self.channels
1948-
try:
1949-
del subscribed_dict[message['channel']]
1950-
except KeyError:
1951-
pass
1952-
19531992
return message
19541993

19551994
def run_in_thread(self, sleep_time=0):

redis/connection.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ def on_disconnect(self):
154154
if self._buffer is not None:
155155
self._buffer.close()
156156
self._buffer = None
157+
self.encoding = None
157158

158159
def can_read(self):
159160
return self._buffer and bool(self._buffer.length)

0 commit comments

Comments
 (0)