Skip to content

Commit cb07272

Browse files
authored
Merge branch 'master' into master
2 parents 2a1ff87 + 03031a2 commit cb07272

File tree

10 files changed

+213
-64
lines changed

10 files changed

+213
-64
lines changed

.github/actions/run-tests/action.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ runs:
3535
CLIENT_LIBS_TEST_IMAGE: "redislabs/client-libs-test:${{ inputs.redis-version }}"
3636
run: |
3737
set -e
38+
39+
if [ "${{inputs.redis-version}}" == "8.0-M04-pre" ]; then
40+
export REDIS_IMAGE=redis:8.0-M03
41+
fi
3842
3943
echo "::group::Installing dependencies"
4044
pip install -U setuptools wheel
@@ -56,9 +60,9 @@ runs:
5660
5761
# Mapping of redis version to stack version
5862
declare -A redis_stack_version_mapping=(
59-
["7.4.1"]="7.4.0-v1"
60-
["7.2.6"]="7.2.0-v13"
61-
["6.2.16"]="6.2.6-v17"
63+
["7.4.2"]="7.4.0-v2"
64+
["7.2.7"]="7.2.0-v14"
65+
["6.2.17"]="6.2.6-v18"
6266
)
6367
6468
if [[ -v redis_stack_version_mapping[$REDIS_VERSION] ]]; then

.github/workflows/integration.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ env:
2929
COVERAGE_CORE: sysmon
3030
REDIS_IMAGE: redis:latest
3131
REDIS_STACK_IMAGE: redis/redis-stack-server:latest
32-
CURRENT_REDIS_VERSION: '7.4.1'
32+
CURRENT_REDIS_VERSION: '7.4.2'
3333

3434
jobs:
3535
dependency-audit:
@@ -74,7 +74,7 @@ jobs:
7474
max-parallel: 15
7575
fail-fast: false
7676
matrix:
77-
redis-version: ['8.0-M02', '${{ needs.redis_version.outputs.CURRENT }}', '7.2.6', '6.2.16']
77+
redis-version: ['8.0-M04-pre', '${{ needs.redis_version.outputs.CURRENT }}', '7.2.7', '6.2.17']
7878
python-version: ['3.8', '3.12']
7979
parser-backend: ['plain']
8080
event-loop: ['asyncio']

docker-compose.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ services:
103103
- all
104104

105105
redis-stack:
106-
image: ${REDIS_STACK_IMAGE:-redis/redis-stack-server:edge}
106+
image: ${REDIS_STACK_IMAGE:-redis/redis-stack-server:latest}
107107
container_name: redis-stack
108108
ports:
109109
- 6479:6379
@@ -112,6 +112,7 @@ services:
112112
profiles:
113113
- standalone
114114
- all-stack
115+
- all
115116

116117
redis-stack-graph:
117118
image: redis/redis-stack-server:6.2.6-v15

redis/commands/search/query.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@ def scorer(self, scorer: str) -> "Query":
179179
Use a different scoring function to evaluate document relevance.
180180
Default is `TFIDF`.
181181
182+
Since Redis 8.0 default was changed to BM25STD.
183+
182184
:param scorer: The scoring function to use
183185
(e.g. `TFIDF.DOCNORM` or `BM25`)
184186
"""

tests/test_asyncio/test_connection.py

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import asyncio
22
import socket
33
import types
4+
from errno import ECONNREFUSED
45
from unittest.mock import patch
56

67
import pytest
@@ -36,15 +37,16 @@ async def test_invalid_response(create_redis):
3637
fake_stream = MockStream(raw + b"\r\n")
3738

3839
parser: _AsyncRESPBase = r.connection._parser
39-
with mock.patch.object(parser, "_stream", fake_stream):
40-
with pytest.raises(InvalidResponse) as cm:
41-
await parser.read_response()
40+
4241
if isinstance(parser, _AsyncRESPBase):
43-
assert str(cm.value) == f"Protocol Error: {raw!r}"
42+
exp_err = f"Protocol Error: {raw!r}"
4443
else:
45-
assert (
46-
str(cm.value) == f'Protocol error, got "{raw.decode()}" as reply type byte'
47-
)
44+
exp_err = f'Protocol error, got "{raw.decode()}" as reply type byte'
45+
46+
with mock.patch.object(parser, "_stream", fake_stream):
47+
with pytest.raises(InvalidResponse, match=exp_err):
48+
await parser.read_response()
49+
4850
await r.connection.disconnect()
4951

5052

@@ -170,10 +172,9 @@ async def test_connect_timeout_error_without_retry():
170172
conn._connect = mock.AsyncMock()
171173
conn._connect.side_effect = socket.timeout
172174

173-
with pytest.raises(TimeoutError) as e:
175+
with pytest.raises(TimeoutError, match="Timeout connecting to server"):
174176
await conn.connect()
175177
assert conn._connect.call_count == 1
176-
assert str(e.value) == "Timeout connecting to server"
177178

178179

179180
@pytest.mark.onlynoncluster
@@ -531,17 +532,14 @@ async def test_format_error_message(conn, error, expected_message):
531532

532533

533534
async def test_network_connection_failure():
534-
with pytest.raises(ConnectionError) as e:
535+
exp_err = rf"^Error {ECONNREFUSED} connecting to 127.0.0.1:9999.(.+)$"
536+
with pytest.raises(ConnectionError, match=exp_err):
535537
redis = Redis(host="127.0.0.1", port=9999)
536538
await redis.set("a", "b")
537-
assert str(e.value).startswith("Error 111 connecting to 127.0.0.1:9999. Connect")
538539

539540

540541
async def test_unix_socket_connection_failure():
541-
with pytest.raises(ConnectionError) as e:
542+
exp_err = "Error 2 connecting to unix:///tmp/a.sock. No such file or directory."
543+
with pytest.raises(ConnectionError, match=exp_err):
542544
redis = Redis(unix_socket_path="unix:///tmp/a.sock")
543545
await redis.set("a", "b")
544-
assert (
545-
str(e.value)
546-
== "Error 2 connecting to unix:///tmp/a.sock. No such file or directory."
547-
)

tests/test_asyncio/test_search.py

Lines changed: 91 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,7 @@ async def test_client(decoded_r: redis.Redis):
341341

342342
@pytest.mark.redismod
343343
@pytest.mark.onlynoncluster
344+
@skip_if_server_version_gte("7.9.0")
344345
async def test_scores(decoded_r: redis.Redis):
345346
await decoded_r.ft().create_index((TextField("txt"),))
346347

@@ -361,6 +362,29 @@ async def test_scores(decoded_r: redis.Redis):
361362
assert "doc1" == res["results"][1]["id"]
362363

363364

365+
@pytest.mark.redismod
366+
@pytest.mark.onlynoncluster
367+
@skip_if_server_version_lt("7.9.0")
368+
async def test_scores_with_new_default_scorer(decoded_r: redis.Redis):
369+
await decoded_r.ft().create_index((TextField("txt"),))
370+
371+
await decoded_r.hset("doc1", mapping={"txt": "foo baz"})
372+
await decoded_r.hset("doc2", mapping={"txt": "foo bar"})
373+
374+
q = Query("foo ~bar").with_scores()
375+
res = await decoded_r.ft().search(q)
376+
if is_resp2_connection(decoded_r):
377+
assert 2 == res.total
378+
assert "doc2" == res.docs[0].id
379+
assert 0.87 == pytest.approx(res.docs[0].score, 0.01)
380+
assert "doc1" == res.docs[1].id
381+
else:
382+
assert 2 == res["total_results"]
383+
assert "doc2" == res["results"][0]["id"]
384+
assert 0.87 == pytest.approx(res["results"][0]["score"], 0.01)
385+
assert "doc1" == res["results"][1]["id"]
386+
387+
364388
@pytest.mark.redismod
365389
async def test_stopwords(decoded_r: redis.Redis):
366390
stopwords = ["foo", "bar", "baz"]
@@ -663,7 +687,7 @@ async def test_summarize(decoded_r: redis.Redis):
663687
await createIndex(decoded_r.ft())
664688
await waitForIndex(decoded_r, "idx")
665689

666-
q = Query("king henry").paging(0, 1)
690+
q = Query('"king henry"').paging(0, 1)
667691
q.highlight(fields=("play", "txt"), tags=("<b>", "</b>"))
668692
q.summarize("txt")
669693

@@ -675,7 +699,7 @@ async def test_summarize(decoded_r: redis.Redis):
675699
== doc.txt
676700
)
677701

678-
q = Query("king henry").paging(0, 1).summarize().highlight()
702+
q = Query('"king henry"').paging(0, 1).summarize().highlight()
679703

680704
doc = sorted((await decoded_r.ft().search(q)).docs)[0]
681705
assert "<b>Henry</b> ... " == doc.play
@@ -691,7 +715,7 @@ async def test_summarize(decoded_r: redis.Redis):
691715
== doc["extra_attributes"]["txt"]
692716
)
693717

694-
q = Query("king henry").paging(0, 1).summarize().highlight()
718+
q = Query('"king henry"').paging(0, 1).summarize().highlight()
695719

696720
doc = sorted((await decoded_r.ft().search(q))["results"])[0]
697721
assert "<b>Henry</b> ... " == doc["extra_attributes"]["play"]
@@ -1029,6 +1053,7 @@ async def test_phonetic_matcher(decoded_r: redis.Redis):
10291053
@pytest.mark.onlynoncluster
10301054
# NOTE(imalinovskyi): This test contains hardcoded scores valid only for RediSearch 2.8+
10311055
@skip_ifmodversion_lt("2.8.0", "search")
1056+
@skip_if_server_version_gte("7.9.0")
10321057
async def test_scorer(decoded_r: redis.Redis):
10331058
await decoded_r.ft().create_index((TextField("description"),))
10341059

@@ -1087,6 +1112,69 @@ async def test_scorer(decoded_r: redis.Redis):
10871112
assert 0.0 == res["results"][0]["score"]
10881113

10891114

1115+
@pytest.mark.redismod
1116+
@pytest.mark.onlynoncluster
1117+
# NOTE(imalinovskyi): This test contains hardcoded scores valid only for RediSearch 2.8+
1118+
@skip_ifmodversion_lt("2.8.0", "search")
1119+
@skip_if_server_version_lt("7.9.0")
1120+
async def test_scorer_with_new_default_scorer(decoded_r: redis.Redis):
1121+
await decoded_r.ft().create_index((TextField("description"),))
1122+
1123+
await decoded_r.hset(
1124+
"doc1", mapping={"description": "The quick brown fox jumps over the lazy dog"}
1125+
)
1126+
await decoded_r.hset(
1127+
"doc2",
1128+
mapping={
1129+
"description": "Quick alice was beginning to get very tired of sitting by her quick sister on the bank, and of having nothing to do." # noqa
1130+
},
1131+
)
1132+
1133+
if is_resp2_connection(decoded_r):
1134+
# default scorer is BM25STD
1135+
res = await decoded_r.ft().search(Query("quick").with_scores())
1136+
assert 0.23 == pytest.approx(res.docs[0].score, 0.05)
1137+
res = await decoded_r.ft().search(Query("quick").scorer("TFIDF").with_scores())
1138+
assert 1.0 == res.docs[0].score
1139+
res = await decoded_r.ft().search(
1140+
Query("quick").scorer("TFIDF.DOCNORM").with_scores()
1141+
)
1142+
assert 0.14285714285714285 == res.docs[0].score
1143+
res = await decoded_r.ft().search(Query("quick").scorer("BM25").with_scores())
1144+
assert 0.22471909420069797 == res.docs[0].score
1145+
res = await decoded_r.ft().search(Query("quick").scorer("DISMAX").with_scores())
1146+
assert 2.0 == res.docs[0].score
1147+
res = await decoded_r.ft().search(
1148+
Query("quick").scorer("DOCSCORE").with_scores()
1149+
)
1150+
assert 1.0 == res.docs[0].score
1151+
res = await decoded_r.ft().search(
1152+
Query("quick").scorer("HAMMING").with_scores()
1153+
)
1154+
assert 0.0 == res.docs[0].score
1155+
else:
1156+
res = await decoded_r.ft().search(Query("quick").with_scores())
1157+
assert 0.23 == pytest.approx(res["results"][0]["score"], 0.05)
1158+
res = await decoded_r.ft().search(Query("quick").scorer("TFIDF").with_scores())
1159+
assert 1.0 == res["results"][0]["score"]
1160+
res = await decoded_r.ft().search(
1161+
Query("quick").scorer("TFIDF.DOCNORM").with_scores()
1162+
)
1163+
assert 0.14285714285714285 == res["results"][0]["score"]
1164+
res = await decoded_r.ft().search(Query("quick").scorer("BM25").with_scores())
1165+
assert 0.22471909420069797 == res["results"][0]["score"]
1166+
res = await decoded_r.ft().search(Query("quick").scorer("DISMAX").with_scores())
1167+
assert 2.0 == res["results"][0]["score"]
1168+
res = await decoded_r.ft().search(
1169+
Query("quick").scorer("DOCSCORE").with_scores()
1170+
)
1171+
assert 1.0 == res["results"][0]["score"]
1172+
res = await decoded_r.ft().search(
1173+
Query("quick").scorer("HAMMING").with_scores()
1174+
)
1175+
assert 0.0 == res["results"][0]["score"]
1176+
1177+
10901178
@pytest.mark.redismod
10911179
async def test_get(decoded_r: redis.Redis):
10921180
await decoded_r.ft().create_index((TextField("f1"), TextField("f2")))

tests/test_commands.py

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4332,7 +4332,6 @@ def test_xgroup_create_mkstream(self, r):
43324332
assert r.xinfo_groups(stream) == expected
43334333

43344334
@skip_if_server_version_lt("7.0.0")
4335-
@skip_if_server_version_gte("7.9.0")
43364335
def test_xgroup_create_entriesread(self, r: redis.Redis):
43374336
stream = "stream"
43384337
group = "group"
@@ -4341,28 +4340,6 @@ def test_xgroup_create_entriesread(self, r: redis.Redis):
43414340
# no group is setup yet, no info to obtain
43424341
assert r.xinfo_groups(stream) == []
43434342

4344-
assert r.xgroup_create(stream, group, 0, entries_read=7)
4345-
expected = [
4346-
{
4347-
"name": group.encode(),
4348-
"consumers": 0,
4349-
"pending": 0,
4350-
"last-delivered-id": b"0-0",
4351-
"entries-read": 7,
4352-
"lag": -6,
4353-
}
4354-
]
4355-
assert r.xinfo_groups(stream) == expected
4356-
4357-
@skip_if_server_version_lt("7.9.0")
4358-
def test_xgroup_create_entriesread_with_fixed_lag_field(self, r: redis.Redis):
4359-
stream = "stream"
4360-
group = "group"
4361-
r.xadd(stream, {"foo": "bar"})
4362-
4363-
# no group is setup yet, no info to obtain
4364-
assert r.xinfo_groups(stream) == []
4365-
43664343
assert r.xgroup_create(stream, group, 0, entries_read=7)
43674344
expected = [
43684345
{

tests/test_connection.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import sys
55
import threading
66
import types
7+
from errno import ECONNREFUSED
78
from typing import Any
89
from unittest import mock
910
from unittest.mock import call, patch
@@ -44,9 +45,8 @@ def test_invalid_response(r):
4445
raw = b"x"
4546
parser = r.connection._parser
4647
with mock.patch.object(parser._buffer, "readline", return_value=raw):
47-
with pytest.raises(InvalidResponse) as cm:
48+
with pytest.raises(InvalidResponse, match=f"Protocol Error: {raw!r}"):
4849
parser.read_response()
49-
assert str(cm.value) == f"Protocol Error: {raw!r}"
5050

5151

5252
@skip_if_server_version_lt("4.0.0")
@@ -141,10 +141,9 @@ def test_connect_timeout_error_without_retry(self):
141141
conn._connect = mock.Mock()
142142
conn._connect.side_effect = socket.timeout
143143

144-
with pytest.raises(TimeoutError) as e:
144+
with pytest.raises(TimeoutError, match="Timeout connecting to server"):
145145
conn.connect()
146146
assert conn._connect.call_count == 1
147-
assert str(e.value) == "Timeout connecting to server"
148147
self.clear(conn)
149148

150149

@@ -349,20 +348,17 @@ def test_format_error_message(conn, error, expected_message):
349348

350349

351350
def test_network_connection_failure():
352-
with pytest.raises(ConnectionError) as e:
351+
exp_err = f"Error {ECONNREFUSED} connecting to localhost:9999. Connection refused."
352+
with pytest.raises(ConnectionError, match=exp_err):
353353
redis = Redis(port=9999)
354354
redis.set("a", "b")
355-
assert str(e.value) == "Error 111 connecting to localhost:9999. Connection refused."
356355

357356

358357
def test_unix_socket_connection_failure():
359-
with pytest.raises(ConnectionError) as e:
358+
exp_err = "Error 2 connecting to unix:///tmp/a.sock. No such file or directory."
359+
with pytest.raises(ConnectionError, match=exp_err):
360360
redis = Redis(unix_socket_path="unix:///tmp/a.sock")
361361
redis.set("a", "b")
362-
assert (
363-
str(e.value)
364-
== "Error 2 connecting to unix:///tmp/a.sock. No such file or directory."
365-
)
366362

367363

368364
class TestUnitConnectionPool:

tests/test_multiprocessing.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import contextlib
22
import multiprocessing
3+
import sys
34

45
import pytest
56
import redis
@@ -8,6 +9,9 @@
89

910
from .conftest import _get_client
1011

12+
if sys.platform == "darwin":
13+
multiprocessing.set_start_method("fork", force=True)
14+
1115

1216
@contextlib.contextmanager
1317
def exit_callback(callback, *args):

0 commit comments

Comments
 (0)