Skip to content

Commit c668538

Browse files
authored
Merge branch 'master' into ckpy312
2 parents 8fe9064 + 53de308 commit c668538

39 files changed

+1158
-234
lines changed

.github/workflows/spellcheck.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88
- name: Checkout
99
uses: actions/checkout@v4
1010
- name: Check Spelling
11-
uses: rojopolis/spellcheck-github-actions@0.34.0
11+
uses: rojopolis/spellcheck-github-actions@0.35.0
1212
with:
1313
config_path: .github/spellcheck-settings.yml
1414
task_name: Markdown

CHANGES

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
* Fix return types for `get`, `set_path` and `strappend` in JSONCommands
2+
* Connection.register_connect_callback() is made public.
3+
* Fix async `read_response` to use `disable_decoding`.
14
* Add 'aclose()' methods to async classes, deprecate async close().
25
* Fix #2831, add auto_close_connection_pool=True arg to asyncio.Redis.from_url()
36
* Fix incorrect redis.asyncio.Cluster type hint for `retry_on_error`
@@ -55,6 +58,8 @@
5558
* Fix for Unhandled exception related to self.host with unix socket (#2496)
5659
* Improve error output for master discovery
5760
* Make `ClusterCommandsProtocol` an actual Protocol
61+
* Add `sum` to DUPLICATE_POLICY documentation of `TS.CREATE`, `TS.ADD` and `TS.ALTER`
62+
* Prevent async ClusterPipeline instances from becoming "false-y" in case of empty command stack (#3061)
5863

5964
* 4.1.3 (Feb 8, 2022)
6065
* Fix flushdb and flushall (#1926)

docs/advanced_features.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ running.
346346
347347
The third option runs an event loop in a separate thread.
348348
pubsub.run_in_thread() creates a new thread and starts the event loop.
349-
The thread object is returned to the caller of [un_in_thread(). The
349+
The thread object is returned to the caller of run_in_thread(). The
350350
caller can use the thread.stop() method to shut down the event loop and
351351
thread. Behind the scenes, this is simply a wrapper around get_message()
352352
that runs in a separate thread, essentially creating a tiny non-blocking

docs/clustering.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ The ‘target_nodes’ parameter is explained in the following section,
9292
>>> # target-node: default-node
9393
>>> rc.ping()
9494
95-
Specfiying Target Nodes
95+
Specifying Target Nodes
9696
-----------------------
9797

9898
As mentioned above, all non key-based RedisCluster commands accept the

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686

8787
# List of patterns, relative to source directory, that match files and
8888
# directories to ignore when looking for source files.
89-
exclude_patterns = ["_build", "**.ipynb_checkponts"]
89+
exclude_patterns = ["_build", "**.ipynb_checkpoints"]
9090

9191
# The reST default role (used for this markup: `text`) to use for all
9292
# documents.

docs/examples/pipeline_examples.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@
123123
"cell_type": "markdown",
124124
"metadata": {},
125125
"source": [
126-
"The responses of the three commands are stored in a list. In the above example, the two first boolean indicates that the `set` commands were successfull and the last element of the list is the result of the `get(\"a\")` comand."
126+
"The responses of the three commands are stored in a list. In the above example, the two first boolean indicates that the `set` commands were successful and the last element of the list is the result of the `get(\"a\")` comand."
127127
]
128128
},
129129
{

redis/_parsers/helpers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ def float_or_none(response):
322322
return float(response)
323323

324324

325-
def bool_ok(response):
325+
def bool_ok(response, **options):
326326
return str_if_bytes(response) == "OK"
327327

328328

redis/_parsers/hiredis.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,10 +198,16 @@ async def read_response(
198198
if not self._connected:
199199
raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR) from None
200200

201-
response = self._reader.gets()
201+
if disable_decoding:
202+
response = self._reader.gets(False)
203+
else:
204+
response = self._reader.gets()
202205
while response is False:
203206
await self.read_from_socket()
204-
response = self._reader.gets()
207+
if disable_decoding:
208+
response = self._reader.gets(False)
209+
else:
210+
response = self._reader.gets()
205211

206212
# if the response is a ConnectionError or the response is a list and
207213
# the first item is a ConnectionError, raise it as something bad

redis/_parsers/resp3.py

Lines changed: 56 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,18 @@
66
from .base import _AsyncRESPBase, _RESPBase
77
from .socket import SERVER_CLOSED_CONNECTION_ERROR
88

9+
_INVALIDATION_MESSAGE = [b"invalidate", "invalidate"]
10+
911

1012
class _RESP3Parser(_RESPBase):
1113
"""RESP3 protocol implementation"""
1214

1315
def __init__(self, socket_read_size):
1416
super().__init__(socket_read_size)
15-
self.push_handler_func = self.handle_push_response
17+
self.pubsub_push_handler_func = self.handle_pubsub_push_response
18+
self.invalidations_push_handler_func = None
1619

17-
def handle_push_response(self, response):
20+
def handle_pubsub_push_response(self, response):
1821
logger = getLogger("push_response")
1922
logger.info("Push response: " + str(response))
2023
return response
@@ -96,8 +99,9 @@ def _read_response(self, disable_decoding=False, push_request=False):
9699
pass
97100
# map response
98101
elif byte == b"%":
99-
# we use this approach and not dict comprehension here
100-
# because this dict comprehension fails in python 3.7
102+
# We cannot use a dict-comprehension to parse stream.
103+
# Evaluation order of key:val expression in dict comprehension only
104+
# became defined to be left-right in version 3.8
101105
resp_dict = {}
102106
for _ in range(int(response)):
103107
key = self._read_response(disable_decoding=disable_decoding)
@@ -113,30 +117,40 @@ def _read_response(self, disable_decoding=False, push_request=False):
113117
)
114118
for _ in range(int(response))
115119
]
116-
res = self.push_handler_func(response)
117-
if not push_request:
118-
return self._read_response(
119-
disable_decoding=disable_decoding, push_request=push_request
120-
)
121-
else:
122-
return res
120+
self.handle_push_response(response, disable_decoding, push_request)
123121
else:
124122
raise InvalidResponse(f"Protocol Error: {raw!r}")
125123

126124
if isinstance(response, bytes) and disable_decoding is False:
127125
response = self.encoder.decode(response)
128126
return response
129127

130-
def set_push_handler(self, push_handler_func):
131-
self.push_handler_func = push_handler_func
128+
def handle_push_response(self, response, disable_decoding, push_request):
129+
if response[0] in _INVALIDATION_MESSAGE:
130+
res = self.invalidation_push_handler_func(response)
131+
else:
132+
res = self.pubsub_push_handler_func(response)
133+
if not push_request:
134+
return self._read_response(
135+
disable_decoding=disable_decoding, push_request=push_request
136+
)
137+
else:
138+
return res
139+
140+
def set_pubsub_push_handler(self, pubsub_push_handler_func):
141+
self.pubsub_push_handler_func = pubsub_push_handler_func
142+
143+
def set_invalidation_push_handler(self, invalidations_push_handler_func):
144+
self.invalidation_push_handler_func = invalidations_push_handler_func
132145

133146

134147
class _AsyncRESP3Parser(_AsyncRESPBase):
135148
def __init__(self, socket_read_size):
136149
super().__init__(socket_read_size)
137-
self.push_handler_func = self.handle_push_response
150+
self.pubsub_push_handler_func = self.handle_pubsub_push_response
151+
self.invalidations_push_handler_func = None
138152

139-
def handle_push_response(self, response):
153+
def handle_pubsub_push_response(self, response):
140154
logger = getLogger("push_response")
141155
logger.info("Push response: " + str(response))
142156
return response
@@ -225,12 +239,16 @@ async def _read_response(
225239
pass
226240
# map response
227241
elif byte == b"%":
228-
response = {
229-
(await self._read_response(disable_decoding=disable_decoding)): (
230-
await self._read_response(disable_decoding=disable_decoding)
242+
# We cannot use a dict-comprehension to parse stream.
243+
# Evaluation order of key:val expression in dict comprehension only
244+
# became defined to be left-right in version 3.8
245+
resp_dict = {}
246+
for _ in range(int(response)):
247+
key = await self._read_response(disable_decoding=disable_decoding)
248+
resp_dict[key] = await self._read_response(
249+
disable_decoding=disable_decoding, push_request=push_request
231250
)
232-
for _ in range(int(response))
233-
}
251+
response = resp_dict
234252
# push response
235253
elif byte == b">":
236254
response = [
@@ -241,19 +259,28 @@ async def _read_response(
241259
)
242260
for _ in range(int(response))
243261
]
244-
res = self.push_handler_func(response)
245-
if not push_request:
246-
return await self._read_response(
247-
disable_decoding=disable_decoding, push_request=push_request
248-
)
249-
else:
250-
return res
262+
await self.handle_push_response(response, disable_decoding, push_request)
251263
else:
252264
raise InvalidResponse(f"Protocol Error: {raw!r}")
253265

254266
if isinstance(response, bytes) and disable_decoding is False:
255267
response = self.encoder.decode(response)
256268
return response
257269

258-
def set_push_handler(self, push_handler_func):
259-
self.push_handler_func = push_handler_func
270+
async def handle_push_response(self, response, disable_decoding, push_request):
271+
if response[0] in _INVALIDATION_MESSAGE:
272+
res = self.invalidation_push_handler_func(response)
273+
else:
274+
res = self.pubsub_push_handler_func(response)
275+
if not push_request:
276+
return await self._read_response(
277+
disable_decoding=disable_decoding, push_request=push_request
278+
)
279+
else:
280+
return res
281+
282+
def set_pubsub_push_handler(self, pubsub_push_handler_func):
283+
self.pubsub_push_handler_func = pubsub_push_handler_func
284+
285+
def set_invalidation_push_handler(self, invalidations_push_handler_func):
286+
self.invalidation_push_handler_func = invalidations_push_handler_func

0 commit comments

Comments
 (0)