Skip to content

Commit 0c2f74f

Browse files
authored
Merge branch 'master' into cluster-fixes
2 parents 23abdb2 + bbc91af commit 0c2f74f

File tree

6 files changed

+62
-48
lines changed

6 files changed

+62
-48
lines changed

.github/workflows/integration.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ jobs:
4949
strategy:
5050
max-parallel: 15
5151
matrix:
52-
python-version: ['3.7', '3.8', '3.9', '3.10', 'pypy-3.7', 'pypy-3.8']
52+
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', 'pypy-3.7', 'pypy-3.8']
5353
test-type: ['standalone', 'cluster']
5454
connection-type: ['hiredis', 'plain']
5555
env:
@@ -106,7 +106,7 @@ jobs:
106106
runs-on: ubuntu-latest
107107
strategy:
108108
matrix:
109-
python-version: ['3.7', '3.8', '3.9', '3.10', 'pypy-3.7']
109+
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', 'pypy-3.7']
110110
steps:
111111
- uses: actions/checkout@v3
112112
- uses: actions/setup-python@v4

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,22 +49,22 @@ True
4949
b'bar'
5050
```
5151

52-
The above code connects to localhost on port 6379, sets a value in Redis, and retrieves it. All responses are returned as bytes in Python, to receive decoded strings, set *decode_responses=True*. For this, and more connection options, see [these examples](https://redis.readthedocs.io/en/stable/examples.html)
52+
The above code connects to localhost on port 6379, sets a value in Redis, and retrieves it. All responses are returned as bytes in Python, to receive decoded strings, set *decode_responses=True*. For this, and more connection options, see [these examples](https://redis.readthedocs.io/en/stable/examples.html).
5353

5454
### Connection Pools
5555

56-
By default, redis-py uses a connection pool to manage connections. Each instance of a Redis class receives its own connection pool. You can however define your own [redis.ConnectionPool](https://redis.readthedocs.io/en/stable/connections.html#connection-pools)
56+
By default, redis-py uses a connection pool to manage connections. Each instance of a Redis class receives its own connection pool. You can however define your own [redis.ConnectionPool](https://redis.readthedocs.io/en/stable/connections.html#connection-pools).
5757

5858
``` python
5959
>>> pool = redis.ConnectionPool(host='localhost', port=6379, db=0)
6060
>>> r = redis.Redis(connection_pool=pool)
6161
```
6262

63-
Alternatively, you might want to look at [Async connections](https://redis.readthedocs.io/en/stable/examples/asyncio_examples.html), or [Cluster connections](https://redis.readthedocs.io/en/stable/connections.html#cluster-client), or even [Async Cluster connections](https://redis.readthedocs.io/en/stable/connections.html#async-cluster-client)
63+
Alternatively, you might want to look at [Async connections](https://redis.readthedocs.io/en/stable/examples/asyncio_examples.html), or [Cluster connections](https://redis.readthedocs.io/en/stable/connections.html#cluster-client), or even [Async Cluster connections](https://redis.readthedocs.io/en/stable/connections.html#async-cluster-client).
6464

6565
### Redis Commands
6666

67-
There is built-in support for all of the [out-of-the-box Redis commands](https://redis.io/commands). They are exposed using the raw Redis command names (`HSET`, `HGETALL`, etc.) except where a word (i.e del) is reserved by the language. The complete set of commands can be found [here](https://github.com/redis/redis-py/tree/master/redis/commands), or [the documentation](https://redis.readthedocs.io/en/stable/commands.html).
67+
There is built-in support for all of the [out-of-the-box Redis commands](https://redis.io/commands). They are exposed using the raw Redis command names (`HSET`, `HGETALL`, etc.) except where a word (i.e. del) is reserved by the language. The complete set of commands can be found [here](https://github.com/redis/redis-py/tree/master/redis/commands), or [the documentation](https://redis.readthedocs.io/en/stable/commands.html).
6868

6969
## Advanced Topics
7070

docs/examples/asyncio_examples.ipynb

Lines changed: 27 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -140,38 +140,31 @@
140140
"source": [
141141
"import asyncio\n",
142142
"\n",
143-
"import async_timeout\n",
144-
"\n",
145143
"import redis.asyncio as redis\n",
146144
"\n",
147145
"STOPWORD = \"STOP\"\n",
148146
"\n",
149147
"\n",
150148
"async def reader(channel: redis.client.PubSub):\n",
151149
" while True:\n",
152-
" try:\n",
153-
" async with async_timeout.timeout(1):\n",
154-
" message = await channel.get_message(ignore_subscribe_messages=True)\n",
155-
" if message is not None:\n",
156-
" print(f\"(Reader) Message Received: {message}\")\n",
157-
" if message[\"data\"].decode() == STOPWORD:\n",
158-
" print(\"(Reader) STOP\")\n",
159-
" break\n",
160-
" await asyncio.sleep(0.01)\n",
161-
" except asyncio.TimeoutError:\n",
162-
" pass\n",
150+
" message = await channel.get_message(ignore_subscribe_messages=True)\n",
151+
" if message is not None:\n",
152+
" print(f\"(Reader) Message Received: {message}\")\n",
153+
" if message[\"data\"].decode() == STOPWORD:\n",
154+
" print(\"(Reader) STOP\")\n",
155+
" break\n",
163156
"\n",
164157
"r = redis.from_url(\"redis://localhost\")\n",
165-
"pubsub = r.pubsub()\n",
166-
"await pubsub.subscribe(\"channel:1\", \"channel:2\")\n",
158+
"async with r.pubsub() as pubsub:\n",
159+
" await pubsub.subscribe(\"channel:1\", \"channel:2\")\n",
167160
"\n",
168-
"future = asyncio.create_task(reader(pubsub))\n",
161+
" future = asyncio.create_task(reader(pubsub))\n",
169162
"\n",
170-
"await r.publish(\"channel:1\", \"Hello\")\n",
171-
"await r.publish(\"channel:2\", \"World\")\n",
172-
"await r.publish(\"channel:1\", STOPWORD)\n",
163+
" await r.publish(\"channel:1\", \"Hello\")\n",
164+
" await r.publish(\"channel:2\", \"World\")\n",
165+
" await r.publish(\"channel:1\", STOPWORD)\n",
173166
"\n",
174-
"await future"
167+
" await future"
175168
]
176169
},
177170
{
@@ -204,39 +197,32 @@
204197
"source": [
205198
"import asyncio\n",
206199
"\n",
207-
"import async_timeout\n",
208-
"\n",
209200
"import redis.asyncio as redis\n",
210201
"\n",
211202
"STOPWORD = \"STOP\"\n",
212203
"\n",
213204
"\n",
214205
"async def reader(channel: redis.client.PubSub):\n",
215206
" while True:\n",
216-
" try:\n",
217-
" async with async_timeout.timeout(1):\n",
218-
" message = await channel.get_message(ignore_subscribe_messages=True)\n",
219-
" if message is not None:\n",
220-
" print(f\"(Reader) Message Received: {message}\")\n",
221-
" if message[\"data\"].decode() == STOPWORD:\n",
222-
" print(\"(Reader) STOP\")\n",
223-
" break\n",
224-
" await asyncio.sleep(0.01)\n",
225-
" except asyncio.TimeoutError:\n",
226-
" pass\n",
207+
" message = await channel.get_message(ignore_subscribe_messages=True)\n",
208+
" if message is not None:\n",
209+
" print(f\"(Reader) Message Received: {message}\")\n",
210+
" if message[\"data\"].decode() == STOPWORD:\n",
211+
" print(\"(Reader) STOP\")\n",
212+
" break\n",
227213
"\n",
228214
"\n",
229215
"r = await redis.from_url(\"redis://localhost\")\n",
230-
"pubsub = r.pubsub()\n",
231-
"await pubsub.psubscribe(\"channel:*\")\n",
216+
"async with r.pubsub() as pubsub:\n",
217+
" await pubsub.psubscribe(\"channel:*\")\n",
232218
"\n",
233-
"future = asyncio.create_task(reader(pubsub))\n",
219+
" future = asyncio.create_task(reader(pubsub))\n",
234220
"\n",
235-
"await r.publish(\"channel:1\", \"Hello\")\n",
236-
"await r.publish(\"channel:2\", \"World\")\n",
237-
"await r.publish(\"channel:1\", STOPWORD)\n",
221+
" await r.publish(\"channel:1\", \"Hello\")\n",
222+
" await r.publish(\"channel:2\", \"World\")\n",
223+
" await r.publish(\"channel:1\", STOPWORD)\n",
238224
"\n",
239-
"await future"
225+
" await future"
240226
]
241227
},
242228
{
@@ -298,4 +284,4 @@
298284
},
299285
"nbformat": 4,
300286
"nbformat_minor": 1
301-
}
287+
}

redis/commands/cluster.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,25 @@ async def _split_command_across_slots(self, command: str, *keys: KeyT) -> int:
316316
# Sum up the reply from each command
317317
return sum(await self._execute_pipeline_by_slot(command, slots_to_keys))
318318

319+
async def _execute_pipeline_by_slot(
320+
self, command: str, slots_to_args: Mapping[int, Iterable[EncodableT]]
321+
) -> List[Any]:
322+
if self._initialize:
323+
await self.initialize()
324+
read_from_replicas = self.read_from_replicas and command in READ_COMMANDS
325+
pipe = self.pipeline()
326+
[
327+
pipe.execute_command(
328+
command,
329+
*slot_args,
330+
target_nodes=[
331+
self.nodes_manager.get_node_from_slot(slot, read_from_replicas)
332+
],
333+
)
334+
for slot, slot_args in slots_to_args.items()
335+
]
336+
return await pipe.execute()
337+
319338

320339
class ClusterManagementCommands(ManagementCommands):
321340
"""

redis/commands/core.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2553,7 +2553,7 @@ def lmpop(
25532553
self,
25542554
num_keys: int,
25552555
*args: List[str],
2556-
direction: str = None,
2556+
direction: str,
25572557
count: Optional[int] = 1,
25582558
) -> Union[Awaitable[list], list]:
25592559
"""

tests/test_asyncio/test_cluster.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,15 @@ async def test_unlink(self, r: RedisCluster) -> None:
874874
await asyncio.sleep(0.1)
875875
assert await r.unlink(*d.keys()) == 0
876876

877+
async def test_initialize_before_execute_multi_key_command(
878+
self, request: FixtureRequest
879+
) -> None:
880+
# Test for issue https://github.com/redis/redis-py/issues/2437
881+
url = request.config.getoption("--redis-url")
882+
r = RedisCluster.from_url(url)
883+
assert 0 == await r.exists("a", "b", "c")
884+
await r.close()
885+
877886
@skip_if_redis_enterprise()
878887
async def test_cluster_myid(self, r: RedisCluster) -> None:
879888
node = r.get_random_node()

0 commit comments

Comments
 (0)