Skip to content

Commit

Permalink
Merge branch 'master' into DOC-4199-tces-for-combined-query-page
Browse files Browse the repository at this point in the history
  • Loading branch information
vladvildanov authored Oct 9, 2024
2 parents ebbbfcb + 17db62e commit 79086f5
Show file tree
Hide file tree
Showing 17 changed files with 234 additions and 83 deletions.
33 changes: 33 additions & 0 deletions .github/workflows/integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,21 @@ jobs:
invoke ${{matrix.test-type}}-tests
ls -1
- name: Run tests against hiredis < 3.0.0
if: ${{ matrix.connection-type == 'hiredis' && matrix.python-version == '3.12'}}
run: |
pip uninstall hiredis -y
pip install -U setuptools wheel
pip install -r requirements.txt
pip install -r dev_requirements.txt
if [ "${{matrix.connection-type}}" == "hiredis" ]; then
pip install "hiredis<3.0.0"
fi
invoke devenv
sleep 10 # time to settle
invoke ${{matrix.test-type}}-tests
ls -1
- name: Upload test results and profiling data
uses: actions/upload-artifact@v4
with:
Expand Down Expand Up @@ -145,6 +160,24 @@ jobs:
invoke ${{matrix.test-type}}-tests --protocol=3
fi
- name: Run tests against hiredis < 3.0.0
if: ${{ matrix.connection-type == 'hiredis' && matrix.python-version == '3.12'}}
run: |
pip uninstall hiredis -y
pip install -U setuptools wheel
pip install -r requirements.txt
pip install -r dev_requirements.txt
if [ "${{matrix.connection-type}}" == "hiredis" ]; then
pip install "hiredis<3.0.0"
fi
invoke devenv
sleep 10 # time to settle
if [ "${{matrix.event-loop}}" == "uvloop" ]; then
invoke ${{matrix.test-type}}-tests --uvloop --protocol=3
else
invoke ${{matrix.test-type}}-tests --protocol=3
fi
- name: Upload test results and profiling data
uses: actions/upload-artifact@v4
with:
Expand Down
34 changes: 17 additions & 17 deletions doctests/dt_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,11 @@
r.sadd("bikes:racing:usa", "bike:1", "bike:4")
# HIDE_END
res7 = r.sinter("bikes:racing:france", "bikes:racing:usa")
print(res7) # >>> ['bike:1']
print(res7) # >>> {'bike:1'}
# STEP_END

# REMOVE_START
assert res7 == ["bike:1"]
assert res7 == {"bike:1"}
# REMOVE_END

# STEP_START scard
Expand All @@ -83,12 +83,12 @@
print(res9) # >>> 3

res10 = r.smembers("bikes:racing:france")
print(res10) # >>> ['bike:1', 'bike:2', 'bike:3']
print(res10) # >>> {'bike:1', 'bike:2', 'bike:3'}
# STEP_END

# REMOVE_START
assert res9 == 3
assert res10 == ['bike:1', 'bike:2', 'bike:3']
assert res10 == {'bike:1', 'bike:2', 'bike:3'}
# REMOVE_END

# STEP_START smismember
Expand All @@ -109,11 +109,11 @@
r.sadd("bikes:racing:usa", "bike:1", "bike:4")

res13 = r.sdiff("bikes:racing:france", "bikes:racing:usa")
print(res13) # >>> ['bike:2', 'bike:3']
print(res13) # >>> {'bike:2', 'bike:3'}
# STEP_END

# REMOVE_START
assert res13 == ['bike:2', 'bike:3']
assert res13 == {'bike:2', 'bike:3'}
r.delete("bikes:racing:france")
r.delete("bikes:racing:usa")
# REMOVE_END
Expand All @@ -124,27 +124,27 @@
r.sadd("bikes:racing:italy", "bike:1", "bike:2", "bike:3", "bike:4")

res13 = r.sinter("bikes:racing:france", "bikes:racing:usa", "bikes:racing:italy")
print(res13) # >>> ['bike:1']
print(res13) # >>> {'bike:1'}

res14 = r.sunion("bikes:racing:france", "bikes:racing:usa", "bikes:racing:italy")
print(res14) # >>> ['bike:1', 'bike:2', 'bike:3', 'bike:4']
print(res14) # >>> {'bike:1', 'bike:2', 'bike:3', 'bike:4'}

res15 = r.sdiff("bikes:racing:france", "bikes:racing:usa", "bikes:racing:italy")
print(res15) # >>> []
print(res15) # >>> {}

res16 = r.sdiff("bikes:racing:usa", "bikes:racing:france")
print(res16) # >>> ['bike:4']
print(res16) # >>> {'bike:4'}

res17 = r.sdiff("bikes:racing:france", "bikes:racing:usa")
print(res17) # >>> ['bike:2', 'bike:3']
print(res17) # >>> {'bike:2', 'bike:3'}
# STEP_END

# REMOVE_START
assert res13 == ['bike:1']
assert res14 == ['bike:1', 'bike:2', 'bike:3', 'bike:4']
assert res15 == []
assert res16 == ['bike:4']
assert res17 == ['bike:2', 'bike:3']
assert res13 == {'bike:1'}
assert res14 == {'bike:1', 'bike:2', 'bike:3', 'bike:4'}
assert res15 == {}
assert res16 == {'bike:4'}
assert res17 == {'bike:2', 'bike:3'}
r.delete("bikes:racing:france")
r.delete("bikes:racing:usa")
r.delete("bikes:racing:italy")
Expand All @@ -160,7 +160,7 @@
print(res19) # >>> bike:3

res20 = r.smembers("bikes:racing:france")
print(res20) # >>> ['bike:2', 'bike:4', 'bike:5']
print(res20) # >>> {'bike:2', 'bike:4', 'bike:5'}

res21 = r.srandmember("bikes:racing:france")
print(res21) # >>> bike:4
Expand Down
103 changes: 103 additions & 0 deletions doctests/query_agg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# EXAMPLE: query_agg
# HIDE_START
import json
import redis
from redis.commands.json.path import Path
from redis.commands.search import Search
from redis.commands.search.aggregation import AggregateRequest
from redis.commands.search.field import NumericField, TagField
from redis.commands.search.indexDefinition import IndexDefinition, IndexType
import redis.commands.search.reducers as reducers

r = redis.Redis(decode_responses=True)

# create index
schema = (
TagField("$.condition", as_name="condition"),
NumericField("$.price", as_name="price"),
)

index = r.ft("idx:bicycle")
index.create_index(
schema,
definition=IndexDefinition(prefix=["bicycle:"], index_type=IndexType.JSON),
)

# load data
with open("data/query_em.json") as f:
bicycles = json.load(f)

pipeline = r.pipeline(transaction=False)
for bid, bicycle in enumerate(bicycles):
pipeline.json().set(f'bicycle:{bid}', Path.root_path(), bicycle)
pipeline.execute()
# HIDE_END

# STEP_START agg1
search = Search(r, index_name="idx:bicycle")
aggregate_request = AggregateRequest(query='@condition:{new}') \
.load('__key', 'price') \
.apply(discounted='@price - (@price * 0.1)')
res = search.aggregate(aggregate_request)
print(len(res.rows)) # >>> 5
print(res.rows) # >>> [['__key', 'bicycle:0', ...
#[['__key', 'bicycle:0', 'price', '270', 'discounted', '243'],
# ['__key', 'bicycle:5', 'price', '810', 'discounted', '729'],
# ['__key', 'bicycle:6', 'price', '2300', 'discounted', '2070'],
# ['__key', 'bicycle:7', 'price', '430', 'discounted', '387'],
# ['__key', 'bicycle:8', 'price', '1200', 'discounted', '1080']]
# REMOVE_START
assert len(res.rows) == 5
# REMOVE_END
# STEP_END

# STEP_START agg2
search = Search(r, index_name="idx:bicycle")
aggregate_request = AggregateRequest(query='*') \
.load('price') \
.apply(price_category='@price<1000') \
.group_by('@condition', reducers.sum('@price_category').alias('num_affordable'))
res = search.aggregate(aggregate_request)
print(len(res.rows)) # >>> 3
print(res.rows) # >>>
#[['condition', 'refurbished', 'num_affordable', '1'],
# ['condition', 'used', 'num_affordable', '1'],
# ['condition', 'new', 'num_affordable', '3']]
# REMOVE_START
assert len(res.rows) == 3
# REMOVE_END
# STEP_END

# STEP_START agg3
search = Search(r, index_name="idx:bicycle")
aggregate_request = AggregateRequest(query='*') \
.apply(type="'bicycle'") \
.group_by('@type', reducers.count().alias('num_total'))
res = search.aggregate(aggregate_request)
print(len(res.rows)) # >>> 1
print(res.rows) # >>> [['type', 'bicycle', 'num_total', '10']]
# REMOVE_START
assert len(res.rows) == 1
# REMOVE_END
# STEP_END

# STEP_START agg4
search = Search(r, index_name="idx:bicycle")
aggregate_request = AggregateRequest(query='*') \
.load('__key') \
.group_by('@condition', reducers.tolist('__key').alias('bicycles'))
res = search.aggregate(aggregate_request)
print(len(res.rows)) # >>> 3
print(res.rows) # >>>
#[['condition', 'refurbished', 'bicycles', ['bicycle:9']],
# ['condition', 'used', 'bicycles', ['bicycle:1', 'bicycle:2', 'bicycle:3', 'bicycle:4']],
# ['condition', 'new', 'bicycles', ['bicycle:5', 'bicycle:6', 'bicycle:7', 'bicycle:0', 'bicycle:8']]]
# REMOVE_START
assert len(res.rows) == 3
# REMOVE_END
# STEP_END

# REMOVE_START
# destroy index and data
r.ft("idx:bicycle").dropindex(delete_documents=True)
# REMOVE_END
6 changes: 6 additions & 0 deletions redis/_parsers/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,9 @@ def string_keys_to_dict(key_string, callback):


_RedisCallbacksRESP2 = {
**string_keys_to_dict(
"SDIFF SINTER SMEMBERS SUNION", lambda r: r and set(r) or set()
),
**string_keys_to_dict(
"ZDIFF ZINTER ZPOPMAX ZPOPMIN ZRANGE ZRANGEBYSCORE ZRANK ZREVRANGE "
"ZREVRANGEBYSCORE ZREVRANK ZUNION",
Expand Down Expand Up @@ -829,6 +832,9 @@ def string_keys_to_dict(key_string, callback):


_RedisCallbacksRESP3 = {
**string_keys_to_dict(
"SDIFF SINTER SMEMBERS SUNION", lambda r: r and set(r) or set()
),
**string_keys_to_dict(
"ZRANGE ZINTER ZPOPMAX ZPOPMIN ZRANGEBYSCORE ZREVRANGE ZREVRANGEBYSCORE "
"ZUNION HGETALL XREADGROUP",
Expand Down
4 changes: 4 additions & 0 deletions redis/asyncio/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1423,6 +1423,10 @@ async def _execute_transaction( # noqa: C901
if not isinstance(r, Exception):
args, options = cmd
command_name = args[0]

# Remove keys entry, it needs only for cache.
options.pop("keys", None)

if command_name in self.response_callbacks:
r = self.response_callbacks[command_name](r, **options)
if inspect.isawaitable(r):
Expand Down
4 changes: 4 additions & 0 deletions redis/cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -1163,6 +1163,10 @@ def _execute_command(self, target_node, *args, **kwargs):
asking = False
connection.send_command(*args, **kwargs)
response = redis_node.parse_response(connection, command, **kwargs)

# Remove keys entry, it needs only for cache.
kwargs.pop("keys", None)

if command in self.cluster_response_callbacks:
response = self.cluster_response_callbacks[command](
response, **kwargs
Expand Down
3 changes: 1 addition & 2 deletions redis/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
from .utils import (
CRYPTOGRAPHY_AVAILABLE,
HIREDIS_AVAILABLE,
HIREDIS_PACK_AVAILABLE,
SSL_AVAILABLE,
compare_versions,
ensure_string,
Expand Down Expand Up @@ -314,7 +313,7 @@ def __del__(self):
def _construct_command_packer(self, packer):
if packer is not None:
return packer
elif HIREDIS_PACK_AVAILABLE:
elif HIREDIS_AVAILABLE:
return HiredisRespSerializer()
else:
return PythonRespSerializer(self._buffer_cutoff, self.encoder.encode)
Expand Down
4 changes: 2 additions & 2 deletions redis/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@

# Only support Hiredis >= 3.0:
HIREDIS_AVAILABLE = int(hiredis.__version__.split(".")[0]) >= 3
HIREDIS_PACK_AVAILABLE = hasattr(hiredis, "pack_command")
if not HIREDIS_AVAILABLE:
raise ImportError("hiredis package should be >= 3.0.0")
except ImportError:
HIREDIS_AVAILABLE = False
HIREDIS_PACK_AVAILABLE = False

try:
import ssl # noqa
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
long_description_content_type="text/markdown",
keywords=["Redis", "key-value store", "database"],
license="MIT",
version="5.1.0b7",
version="5.1.1",
packages=find_packages(
include=[
"redis",
Expand Down
20 changes: 10 additions & 10 deletions tests/test_asyncio/test_cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -1752,38 +1752,38 @@ async def test_cluster_rpoplpush(self, r: RedisCluster) -> None:

async def test_cluster_sdiff(self, r: RedisCluster) -> None:
await r.sadd("{foo}a", "1", "2", "3")
assert set(await r.sdiff("{foo}a", "{foo}b")) == {b"1", b"2", b"3"}
assert await r.sdiff("{foo}a", "{foo}b") == {b"1", b"2", b"3"}
await r.sadd("{foo}b", "2", "3")
assert await r.sdiff("{foo}a", "{foo}b") == [b"1"]
assert await r.sdiff("{foo}a", "{foo}b") == {b"1"}

async def test_cluster_sdiffstore(self, r: RedisCluster) -> None:
await r.sadd("{foo}a", "1", "2", "3")
assert await r.sdiffstore("{foo}c", "{foo}a", "{foo}b") == 3
assert set(await r.smembers("{foo}c")) == {b"1", b"2", b"3"}
assert await r.smembers("{foo}c") == {b"1", b"2", b"3"}
await r.sadd("{foo}b", "2", "3")
assert await r.sdiffstore("{foo}c", "{foo}a", "{foo}b") == 1
assert await r.smembers("{foo}c") == [b"1"]
assert await r.smembers("{foo}c") == {b"1"}

async def test_cluster_sinter(self, r: RedisCluster) -> None:
await r.sadd("{foo}a", "1", "2", "3")
assert await r.sinter("{foo}a", "{foo}b") == []
assert await r.sinter("{foo}a", "{foo}b") == set()
await r.sadd("{foo}b", "2", "3")
assert set(await r.sinter("{foo}a", "{foo}b")) == {b"2", b"3"}
assert await r.sinter("{foo}a", "{foo}b") == {b"2", b"3"}

async def test_cluster_sinterstore(self, r: RedisCluster) -> None:
await r.sadd("{foo}a", "1", "2", "3")
assert await r.sinterstore("{foo}c", "{foo}a", "{foo}b") == 0
assert await r.smembers("{foo}c") == []
assert await r.smembers("{foo}c") == set()
await r.sadd("{foo}b", "2", "3")
assert await r.sinterstore("{foo}c", "{foo}a", "{foo}b") == 2
assert set(await r.smembers("{foo}c")) == {b"2", b"3"}
assert await r.smembers("{foo}c") == {b"2", b"3"}

async def test_cluster_smove(self, r: RedisCluster) -> None:
await r.sadd("{foo}a", "a1", "a2")
await r.sadd("{foo}b", "b1", "b2")
assert await r.smove("{foo}a", "{foo}b", "a1")
assert await r.smembers("{foo}a") == [b"a2"]
assert set(await r.smembers("{foo}b")) == {b"b1", b"b2", b"a1"}
assert await r.smembers("{foo}a") == {b"a2"}
assert await r.smembers("{foo}b") == {b"b1", b"b2", b"a1"}

async def test_cluster_sunion(self, r: RedisCluster) -> None:
await r.sadd("{foo}a", "1", "2")
Expand Down
Loading

0 comments on commit 79086f5

Please sign in to comment.