Skip to content

Commit 0851c0d

Browse files
authored
Fix str/bytes mixup in PythonParser.read_response() (#1324)
Calling str() on a bytes object can result in a BytesWarning being emitted and usually indicates a mixup between byte and string handling. Now, in the event of an invalid RESP response, use the repr value of the raw response in the exception message. Can further simplify the bytes/str handling by comparing the first byte as a bytes object instead of converting it to str. The bytes literal is available on all supported Pythons. This removes the need for the compatibility function, byte_to_chr().
1 parent 5fa3fe5 commit 0851c0d

File tree

3 files changed

+26
-18
lines changed

3 files changed

+26
-18
lines changed

redis/_compat.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,6 @@ def nativestr(x):
143143
def next(x):
144144
return x.next()
145145

146-
def byte_to_chr(x):
147-
return x
148-
149146
unichr = unichr
150147
xrange = xrange
151148
basestring = basestring
@@ -166,9 +163,6 @@ def iterkeys(x):
166163
def itervalues(x):
167164
return iter(x.values())
168165

169-
def byte_to_chr(x):
170-
return chr(x)
171-
172166
def nativestr(x):
173167
return x if isinstance(x, str) else x.decode('utf-8', 'replace')
174168

redis/connection.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import threading
1111
import warnings
1212

13-
from redis._compat import (xrange, imap, byte_to_chr, unicode, long,
13+
from redis._compat import (xrange, imap, unicode, long,
1414
nativestr, basestring, iteritems,
1515
LifoQueue, Empty, Full, urlparse, parse_qs,
1616
recv, recv_into, unquote, BlockingIOError,
@@ -319,18 +319,17 @@ def can_read(self, timeout):
319319
return self._buffer and self._buffer.can_read(timeout)
320320

321321
def read_response(self):
322-
response = self._buffer.readline()
323-
if not response:
322+
raw = self._buffer.readline()
323+
if not raw:
324324
raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR)
325325

326-
byte, response = byte_to_chr(response[0]), response[1:]
326+
byte, response = raw[:1], raw[1:]
327327

328-
if byte not in ('-', '+', ':', '$', '*'):
329-
raise InvalidResponse("Protocol Error: %s, %s" %
330-
(str(byte), str(response)))
328+
if byte not in (b'-', b'+', b':', b'$', b'*'):
329+
raise InvalidResponse("Protocol Error: %r" % raw)
331330

332331
# server returned an error
333-
if byte == '-':
332+
if byte == b'-':
334333
response = nativestr(response)
335334
error = self.parse_error(response)
336335
# if the error is a ConnectionError, raise immediately so the user
@@ -343,19 +342,19 @@ def read_response(self):
343342
# necessary, so just return the exception instance here.
344343
return error
345344
# single value
346-
elif byte == '+':
345+
elif byte == b'+':
347346
pass
348347
# int value
349-
elif byte == ':':
348+
elif byte == b':':
350349
response = long(response)
351350
# bulk response
352-
elif byte == '$':
351+
elif byte == b'$':
353352
length = int(response)
354353
if length == -1:
355354
return None
356355
response = self._buffer.read(length)
357356
# multi-bulk response
358-
elif byte == '*':
357+
elif byte == b'*':
359358
length = int(response)
360359
if length == -1:
361360
return None

tests/test_connection.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import mock
2+
import pytest
3+
4+
from redis.exceptions import InvalidResponse
5+
from redis.utils import HIREDIS_AVAILABLE
6+
7+
8+
@pytest.mark.skipif(HIREDIS_AVAILABLE, reason='PythonParser only')
9+
def test_invalid_response(r):
10+
raw = b'x'
11+
parser = r.connection._parser
12+
with mock.patch.object(parser._buffer, 'readline', return_value=raw):
13+
with pytest.raises(InvalidResponse) as cm:
14+
parser.read_response()
15+
assert str(cm.value) == 'Protocol Error: %r' % raw

0 commit comments

Comments
 (0)