Skip to content

Commit a9dabf9

Browse files
committed
Fix handling of no data in hiredis-py
Use a sentinel object instance to signal lack of data in hiredis-py, instead of piggybacking of `False`, which can also be returned by parsing valid RESP payloads.
1 parent 030869e commit a9dabf9

File tree

3 files changed

+16
-29
lines changed

3 files changed

+16
-29
lines changed

redis/_parsers/hiredis.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@
1919
SERVER_CLOSED_CONNECTION_ERROR,
2020
)
2121

22+
# Used to signal that hiredis-py does not have enough data to parse.
23+
# Using `False` or `None` is not reliable, given that the parser can
24+
# return `False` or `None` for legitimate reasons from RESP payloads.
25+
NOT_ENOUGH_DATA = object()
26+
2227

2328
class _HiredisReaderArgs(TypedDict, total=False):
2429
protocolError: Callable[[str], Exception]
@@ -51,25 +56,26 @@ def on_connect(self, connection, **kwargs):
5156
"protocolError": InvalidResponse,
5257
"replyError": self.parse_error,
5358
"errors": connection.encoder.encoding_errors,
59+
"notEnoughData": NOT_ENOUGH_DATA,
5460
}
5561

5662
if connection.encoder.decode_responses:
5763
kwargs["encoding"] = connection.encoder.encoding
5864
self._reader = hiredis.Reader(**kwargs)
59-
self._next_response = False
65+
self._next_response = NOT_ENOUGH_DATA
6066

6167
def on_disconnect(self):
6268
self._sock = None
6369
self._reader = None
64-
self._next_response = False
70+
self._next_response = NOT_ENOUGH_DATA
6571

6672
def can_read(self, timeout):
6773
if not self._reader:
6874
raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR)
6975

70-
if self._next_response is False:
76+
if self._next_response is NOT_ENOUGH_DATA:
7177
self._next_response = self._reader.gets()
72-
if self._next_response is False:
78+
if self._next_response is NOT_ENOUGH_DATA:
7379
return self.read_from_socket(timeout=timeout, raise_on_timeout=False)
7480
return True
7581

@@ -108,17 +114,17 @@ def read_response(self, disable_decoding=False):
108114
raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR)
109115

110116
# _next_response might be cached from a can_read() call
111-
if self._next_response is not False:
117+
if self._next_response is not NOT_ENOUGH_DATA:
112118
response = self._next_response
113-
self._next_response = False
119+
self._next_response = NOT_ENOUGH_DATA
114120
return response
115121

116122
if disable_decoding:
117123
response = self._reader.gets(False)
118124
else:
119125
response = self._reader.gets()
120126

121-
while response is False:
127+
while response is NOT_ENOUGH_DATA:
122128
self.read_from_socket()
123129
if disable_decoding:
124130
response = self._reader.gets(False)
@@ -156,6 +162,7 @@ def on_connect(self, connection):
156162
kwargs: _HiredisReaderArgs = {
157163
"protocolError": InvalidResponse,
158164
"replyError": self.parse_error,
165+
"notEnoughData": NOT_ENOUGH_DATA,
159166
}
160167
if connection.encoder.decode_responses:
161168
kwargs["encoding"] = connection.encoder.encoding
@@ -170,7 +177,7 @@ def on_disconnect(self):
170177
async def can_read_destructive(self):
171178
if not self._connected:
172179
raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR)
173-
if self._reader.gets():
180+
if self._reader.gets() is not NOT_ENOUGH_DATA:
174181
return True
175182
try:
176183
async with async_timeout(0):
@@ -200,7 +207,7 @@ async def read_response(
200207
response = self._reader.gets(False)
201208
else:
202209
response = self._reader.gets()
203-
while response is False:
210+
while response is NOT_ENOUGH_DATA:
204211
await self.read_from_socket()
205212
if disable_decoding:
206213
response = self._reader.gets(False)

tests/test_asyncio/test_bloom.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,6 @@ async def test_tdigest_create(decoded_r: redis.Redis):
4242
assert await decoded_r.tdigest().create("tDigest", 100)
4343

4444

45-
# hiredis-py can't process well boolean responses
46-
@pytest.mark.skipif(HIREDIS_AVAILABLE, reason="PythonParser only")
4745
@pytest.mark.redismod
4846
async def test_bf_add(decoded_r: redis.Redis):
4947
assert await decoded_r.bf().create("bloom", 0.01, 1000)
@@ -57,8 +55,6 @@ async def test_bf_add(decoded_r: redis.Redis):
5755
assert [1, 0] == intlist(await decoded_r.bf().mexists("bloom", "foo", "noexist"))
5856

5957

60-
# hiredis-py can't process well boolean responses
61-
@pytest.mark.skipif(HIREDIS_AVAILABLE, reason="PythonParser only")
6258
@pytest.mark.redismod
6359
async def test_bf_insert(decoded_r: redis.Redis):
6460
assert await decoded_r.bf().create("bloom", 0.01, 1000)
@@ -90,8 +86,6 @@ async def test_bf_insert(decoded_r: redis.Redis):
9086
)
9187

9288

93-
# hiredis-py can't process well boolean responses
94-
@pytest.mark.skipif(HIREDIS_AVAILABLE, reason="PythonParser only")
9589
@pytest.mark.redismod
9690
async def test_bf_scandump_and_loadchunk(decoded_r: redis.Redis):
9791
# Store a filter
@@ -191,8 +185,6 @@ async def test_bf_card(decoded_r: redis.Redis):
191185
await decoded_r.bf().card("setKey")
192186

193187

194-
# hiredis-py can't process well boolean responses
195-
@pytest.mark.skipif(HIREDIS_AVAILABLE, reason="PythonParser only")
196188
@pytest.mark.redismod
197189
async def test_cf_add_and_insert(decoded_r: redis.Redis):
198190
assert await decoded_r.cf().create("cuckoo", 1000)
@@ -219,8 +211,6 @@ async def test_cf_add_and_insert(decoded_r: redis.Redis):
219211
)
220212

221213

222-
# hiredis-py can't process well boolean responses
223-
@pytest.mark.skipif(HIREDIS_AVAILABLE, reason="PythonParser only")
224214
@pytest.mark.redismod
225215
async def test_cf_exists_and_del(decoded_r: redis.Redis):
226216
assert await decoded_r.cf().create("cuckoo", 1000)

tests/test_bloom.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,6 @@ def test_tdigest_create(client):
7373
assert client.tdigest().create("tDigest", 100)
7474

7575

76-
# hiredis-py can't process well boolean responses
77-
@pytest.mark.skipif(HIREDIS_AVAILABLE, reason="PythonParser only")
7876
@pytest.mark.redismod
7977
def test_bf_add(client):
8078
assert client.bf().create("bloom", 0.01, 1000)
@@ -88,8 +86,6 @@ def test_bf_add(client):
8886
assert [1, 0] == intlist(client.bf().mexists("bloom", "foo", "noexist"))
8987

9088

91-
# hiredis-py can't process well boolean responses
92-
@pytest.mark.skipif(HIREDIS_AVAILABLE, reason="PythonParser only")
9389
@pytest.mark.redismod
9490
def test_bf_insert(client):
9591
assert client.bf().create("bloom", 0.01, 1000)
@@ -121,8 +117,6 @@ def test_bf_insert(client):
121117
)
122118

123119

124-
# hiredis-py can't process well boolean responses
125-
@pytest.mark.skipif(HIREDIS_AVAILABLE, reason="PythonParser only")
126120
@pytest.mark.redismod
127121
def test_bf_scandump_and_loadchunk(client):
128122
# Store a filter
@@ -222,8 +216,6 @@ def test_bf_card(client):
222216
client.bf().card("setKey")
223217

224218

225-
# hiredis-py can't process well boolean responses
226-
@pytest.mark.skipif(HIREDIS_AVAILABLE, reason="PythonParser only")
227219
@pytest.mark.redismod
228220
def test_cf_add_and_insert(client):
229221
assert client.cf().create("cuckoo", 1000)
@@ -250,8 +242,6 @@ def test_cf_add_and_insert(client):
250242
)
251243

252244

253-
# hiredis-py can't process well boolean responses
254-
@pytest.mark.skipif(HIREDIS_AVAILABLE, reason="PythonParser only")
255245
@pytest.mark.redismod
256246
def test_cf_exists_and_del(client):
257247
assert client.cf().create("cuckoo", 1000)

0 commit comments

Comments
 (0)