Skip to content

Commit 027b452

Browse files
authored
Add support for new TDIGEST features and changes (#2392)
* Add support to TDIGEST * linters * linters * linters * disable View Test Results in CI
1 parent 66c4e60 commit 027b452

File tree

7 files changed

+229
-88
lines changed

7 files changed

+229
-88
lines changed

.github/workflows/integration.yaml

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -71,21 +71,21 @@ jobs:
7171
with:
7272
name: pytest-results-${{matrix.test-type}}
7373
path: '${{matrix.test-type}}*results.xml'
74-
- name: View Test Results
75-
uses: dorny/test-reporter@v1
76-
if: success() || failure()
77-
with:
78-
name: Test Results ${{matrix.python-version}} ${{matrix.test-type}}-${{matrix.connection-type}}
79-
path: '${{matrix.test-type}}*results.xml'
80-
reporter: java-junit
81-
list-suites: failed
82-
list-tests: failed
83-
max-annotations: 10
84-
- name: Upload codecov coverage
85-
uses: codecov/codecov-action@v2
86-
with:
87-
fail_ci_if_error: false
88-
token: ${{ secrets.CODECOV_TOKEN }}
74+
# - name: View Test Results
75+
# uses: dorny/test-reporter@v1
76+
# if: success() || failure()
77+
# with:
78+
# name: Test Results ${{matrix.python-version}} ${{matrix.test-type}}-${{matrix.connection-type}}
79+
# path: '${{matrix.test-type}}*results.xml'
80+
# reporter: java-junit
81+
# list-suites: failed
82+
# list-tests: failed
83+
# max-annotations: 10
84+
# - name: Upload codecov coverage
85+
# uses: codecov/codecov-action@v2
86+
# with:
87+
# fail_ci_if_error: false
88+
# token: ${{ secrets.CODECOV_TOKEN }}
8989

9090
build_and_test_package:
9191
name: Validate building and installing the package

redis/commands/bf/__init__.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from ..helpers import parse_to_list
44
from .commands import * # noqa
55
from .info import BFInfo, CFInfo, CMSInfo, TDigestInfo, TopKInfo
6-
from .utils import parse_tdigest_quantile
76

87

98
class AbstractBloom(object):
@@ -166,12 +165,16 @@ def __init__(self, client, **kwargs):
166165
# TDIGEST_RESET: bool_ok,
167166
# TDIGEST_ADD: spaceHolder,
168167
# TDIGEST_MERGE: spaceHolder,
169-
TDIGEST_CDF: float,
170-
TDIGEST_QUANTILE: parse_tdigest_quantile,
168+
TDIGEST_CDF: parse_to_list,
169+
TDIGEST_QUANTILE: parse_to_list,
171170
TDIGEST_MIN: float,
172171
TDIGEST_MAX: float,
173172
TDIGEST_TRIMMED_MEAN: float,
174173
TDIGEST_INFO: TDigestInfo,
174+
TDIGEST_RANK: parse_to_list,
175+
TDIGEST_REVRANK: parse_to_list,
176+
TDIGEST_BYRANK: parse_to_list,
177+
TDIGEST_BYREVRANK: parse_to_list,
175178
}
176179

177180
self.client = client

redis/commands/bf/commands.py

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,10 @@
5252
TDIGEST_MAX = "TDIGEST.MAX"
5353
TDIGEST_INFO = "TDIGEST.INFO"
5454
TDIGEST_TRIMMED_MEAN = "TDIGEST.TRIMMED_MEAN"
55-
TDIGEST_MERGESTORE = "TDIGEST.MERGESTORE"
55+
TDIGEST_RANK = "TDIGEST.RANK"
56+
TDIGEST_REVRANK = "TDIGEST.REVRANK"
57+
TDIGEST_BYRANK = "TDIGEST.BYRANK"
58+
TDIGEST_BYREVRANK = "TDIGEST.BYREVRANK"
5659

5760

5861
class BFCommands:
@@ -381,12 +384,22 @@ def add(self, key, values, weights):
381384
self.append_values_and_weights(params, values, weights)
382385
return self.execute_command(TDIGEST_ADD, *params)
383386

384-
def merge(self, toKey, fromKey):
387+
def merge(self, destination_key, num_keys, *keys, compression=None, override=False):
385388
"""
386-
Merge all of the values from 'fromKey' to 'toKey' sketch.
389+
Merges all of the values from `keys` to 'destination-key' sketch.
390+
It is mandatory to provide the `num_keys` before passing the input keys and
391+
the other (optional) arguments.
392+
If `destination_key` already exists its values are merged with the input keys.
393+
If you wish to override the destination key contents use the `OVERRIDE` parameter.
394+
387395
For more information see `TDIGEST.MERGE <https://redis.io/commands/tdigest.merge>`_.
388396
""" # noqa
389-
return self.execute_command(TDIGEST_MERGE, toKey, fromKey)
397+
params = [destination_key, num_keys, *keys]
398+
if compression is not None:
399+
params.extend(["COMPRESSION", compression])
400+
if override:
401+
params.append("OVERRIDE")
402+
return self.execute_command(TDIGEST_MERGE, *params)
390403

391404
def min(self, key):
392405
"""
@@ -411,12 +424,12 @@ def quantile(self, key, quantile, *quantiles):
411424
""" # noqa
412425
return self.execute_command(TDIGEST_QUANTILE, key, quantile, *quantiles)
413426

414-
def cdf(self, key, value):
427+
def cdf(self, key, value, *values):
415428
"""
416429
Return double fraction of all points added which are <= value.
417430
For more information see `TDIGEST.CDF <https://redis.io/commands/tdigest.cdf>`_.
418431
""" # noqa
419-
return self.execute_command(TDIGEST_CDF, key, value)
432+
return self.execute_command(TDIGEST_CDF, key, value, *values)
420433

421434
def info(self, key):
422435
"""
@@ -436,18 +449,39 @@ def trimmed_mean(self, key, low_cut_quantile, high_cut_quantile):
436449
TDIGEST_TRIMMED_MEAN, key, low_cut_quantile, high_cut_quantile
437450
)
438451

439-
def mergestore(self, dest_key, numkeys, *sourcekeys, compression=False):
452+
def rank(self, key, value, *values):
440453
"""
441-
Merges all of the values from `sourcekeys` keys to `dest_key` sketch.
442-
If destination already exists, it is overwritten.
454+
Retrieve the estimated rank of value (the number of observations in the sketch
455+
that are smaller than value + half the number of observations that are equal to value).
443456
457+
For more information see `TDIGEST.RANK <https://redis.io/commands/tdigest.rank>`_.
458+
""" # noqa
459+
return self.execute_command(TDIGEST_RANK, key, value, *values)
444460

445-
For more information see `TDIGEST.MERGESTORE <https://redis.io/commands/tdigest.mergestore>`_.
461+
def revrank(self, key, value, *values):
462+
"""
463+
Retrieve the estimated rank of value (the number of observations in the sketch
464+
that are larger than value + half the number of observations that are equal to value).
465+
466+
For more information see `TDIGEST.REVRANK <https://redis.io/commands/tdigest.revrank>`_.
446467
""" # noqa
447-
params = [dest_key, numkeys, *sourcekeys]
448-
if compression:
449-
params.extend(["COMPRESSION", compression])
450-
return self.execute_command(TDIGEST_MERGESTORE, *params)
468+
return self.execute_command(TDIGEST_REVRANK, key, value, *values)
469+
470+
def byrank(self, key, rank, *ranks):
471+
"""
472+
Retrieve an estimation of the value with the given rank.
473+
474+
For more information see `TDIGEST.BY_RANK <https://redis.io/commands/tdigest.by_rank>`_.
475+
""" # noqa
476+
return self.execute_command(TDIGEST_BYRANK, key, rank, *ranks)
477+
478+
def byrevrank(self, key, rank, *ranks):
479+
"""
480+
Retrieve an estimation of the value with the given reverse rank.
481+
482+
For more information see `TDIGEST.BY_REVRANK <https://redis.io/commands/tdigest.by_revrank>`_.
483+
""" # noqa
484+
return self.execute_command(TDIGEST_BYREVRANK, key, rank, *ranks)
451485

452486

453487
class CMSCommands:

redis/commands/bf/info.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -68,18 +68,22 @@ def __init__(self, args):
6868
class TDigestInfo(object):
6969
compression = None
7070
capacity = None
71-
mergedNodes = None
72-
unmergedNodes = None
73-
mergedWeight = None
74-
unmergedWeight = None
75-
totalCompressions = None
71+
merged_nodes = None
72+
unmerged_nodes = None
73+
merged_weight = None
74+
unmerged_weight = None
75+
total_compressions = None
76+
sum_weights = None
77+
memory_usage = None
7678

7779
def __init__(self, args):
7880
response = dict(zip(map(nativestr, args[::2]), args[1::2]))
7981
self.compression = response["Compression"]
8082
self.capacity = response["Capacity"]
81-
self.mergedNodes = response["Merged nodes"]
82-
self.unmergedNodes = response["Unmerged nodes"]
83-
self.mergedWeight = response["Merged weight"]
84-
self.unmergedWeight = response["Unmerged weight"]
85-
self.totalCompressions = response["Total compressions"]
83+
self.merged_nodes = response["Merged nodes"]
84+
self.unmerged_nodes = response["Unmerged nodes"]
85+
self.merged_weight = response["Merged weight"]
86+
self.unmerged_weight = response["Unmerged weight"]
87+
self.total_compressions = response["Total compressions"]
88+
self.sum_weights = response["Sum weights"]
89+
self.memory_usage = response["Memory usage"]

redis/commands/bf/utils.py

Lines changed: 0 additions & 3 deletions
This file was deleted.

tests/test_asyncio/test_bloom.py

Lines changed: 78 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from math import inf
2+
13
import pytest
24

35
import redis.asyncio as redis
@@ -322,11 +324,11 @@ async def test_tdigest_reset(modclient: redis.Redis):
322324
# reset on empty histogram
323325
assert await modclient.tdigest().reset("tDigest")
324326
# insert data-points into sketch
325-
assert await modclient.tdigest().add("tDigest", list(range(10)), [1.0] * 10)
327+
assert await modclient.tdigest().add("tDigest", list(range(10)), [1] * 10)
326328

327329
assert await modclient.tdigest().reset("tDigest")
328330
# assert we have 0 unmerged nodes
329-
assert 0 == (await modclient.tdigest().info("tDigest")).unmergedNodes
331+
assert 0 == (await modclient.tdigest().info("tDigest")).unmerged_nodes
330332

331333

332334
@pytest.mark.redismod
@@ -335,22 +337,32 @@ async def test_tdigest_merge(modclient: redis.Redis):
335337
assert await modclient.tdigest().create("to-tDigest", 10)
336338
assert await modclient.tdigest().create("from-tDigest", 10)
337339
# insert data-points into sketch
338-
assert await modclient.tdigest().add("from-tDigest", [1.0] * 10, [1.0] * 10)
339-
assert await modclient.tdigest().add("to-tDigest", [2.0] * 10, [10.0] * 10)
340+
assert await modclient.tdigest().add("from-tDigest", [1.0] * 10, [1] * 10)
341+
assert await modclient.tdigest().add("to-tDigest", [2.0] * 10, [10] * 10)
340342
# merge from-tdigest into to-tdigest
341-
assert await modclient.tdigest().merge("to-tDigest", "from-tDigest")
343+
assert await modclient.tdigest().merge("to-tDigest", 1, "from-tDigest")
342344
# we should now have 110 weight on to-histogram
343345
info = await modclient.tdigest().info("to-tDigest")
344-
total_weight_to = float(info.mergedWeight) + float(info.unmergedWeight)
346+
total_weight_to = float(info.merged_weight) + float(info.unmerged_weight)
345347
assert 110 == total_weight_to
348+
# test override
349+
assert await modclient.tdigest().create("from-override", 10)
350+
assert await modclient.tdigest().create("from-override-2", 10)
351+
assert await modclient.tdigest().add("from-override", [3.0] * 10, [10] * 10)
352+
assert await modclient.tdigest().add("from-override-2", [4.0] * 10, [10] * 10)
353+
assert await modclient.tdigest().merge(
354+
"to-tDigest", 2, "from-override", "from-override-2", override=True
355+
)
356+
assert 3.0 == await modclient.tdigest().min("to-tDigest")
357+
assert 4.0 == await modclient.tdigest().max("to-tDigest")
346358

347359

348360
@pytest.mark.redismod
349361
@pytest.mark.experimental
350362
async def test_tdigest_min_and_max(modclient: redis.Redis):
351363
assert await modclient.tdigest().create("tDigest", 100)
352364
# insert data-points into sketch
353-
assert await modclient.tdigest().add("tDigest", [1, 2, 3], [1.0] * 3)
365+
assert await modclient.tdigest().add("tDigest", [1, 2, 3], [1] * 3)
354366
# min/max
355367
assert 3 == await modclient.tdigest().max("tDigest")
356368
assert 1 == await modclient.tdigest().min("tDigest")
@@ -363,12 +375,12 @@ async def test_tdigest_quantile(modclient: redis.Redis):
363375
assert await modclient.tdigest().create("tDigest", 500)
364376
# insert data-points into sketch
365377
assert await modclient.tdigest().add(
366-
"tDigest", list([x * 0.01 for x in range(1, 10000)]), [1.0] * 10000
378+
"tDigest", list([x * 0.01 for x in range(1, 10000)]), [1] * 10000
367379
)
368380
# assert min min/max have same result as quantile 0 and 1
369381
assert (
370382
await modclient.tdigest().max("tDigest")
371-
== (await modclient.tdigest().quantile("tDigest", 1.0))[0]
383+
== (await modclient.tdigest().quantile("tDigest", 1))[0]
372384
)
373385
assert (
374386
await modclient.tdigest().min("tDigest")
@@ -380,7 +392,7 @@ async def test_tdigest_quantile(modclient: redis.Redis):
380392

381393
# test multiple quantiles
382394
assert await modclient.tdigest().create("t-digest", 100)
383-
assert await modclient.tdigest().add("t-digest", [1, 2, 3, 4, 5], [1.0] * 5)
395+
assert await modclient.tdigest().add("t-digest", [1, 2, 3, 4, 5], [1] * 5)
384396
res = await modclient.tdigest().quantile("t-digest", 0.5, 0.8)
385397
assert [3.0, 5.0] == res
386398

@@ -390,22 +402,67 @@ async def test_tdigest_quantile(modclient: redis.Redis):
390402
async def test_tdigest_cdf(modclient: redis.Redis):
391403
assert await modclient.tdigest().create("tDigest", 100)
392404
# insert data-points into sketch
393-
assert await modclient.tdigest().add("tDigest", list(range(1, 10)), [1.0] * 10)
394-
assert 0.1 == round(await modclient.tdigest().cdf("tDigest", 1.0), 1)
395-
assert 0.9 == round(await modclient.tdigest().cdf("tDigest", 9.0), 1)
405+
assert await modclient.tdigest().add("tDigest", list(range(1, 10)), [1] * 10)
406+
assert 0.1 == round((await modclient.tdigest().cdf("tDigest", 1.0))[0], 1)
407+
assert 0.9 == round((await modclient.tdigest().cdf("tDigest", 9.0))[0], 1)
408+
res = await modclient.tdigest().cdf("tDigest", 1.0, 9.0)
409+
assert [0.1, 0.9] == [round(x, 1) for x in res]
396410

397411

398412
@pytest.mark.redismod
399413
@pytest.mark.experimental
400414
@skip_ifmodversion_lt("2.4.0", "bf")
401-
async def test_tdigest_mergestore(modclient: redis.Redis):
402-
assert await modclient.tdigest().create("sourcekey1", 100)
403-
assert await modclient.tdigest().create("sourcekey2", 100)
404-
assert await modclient.tdigest().add("sourcekey1", [10], [1.0])
405-
assert await modclient.tdigest().add("sourcekey2", [50], [1.0])
406-
assert await modclient.tdigest().mergestore("dest", 2, "sourcekey1", "sourcekey2")
407-
assert await modclient.tdigest().max("dest") == 50
408-
assert await modclient.tdigest().min("dest") == 10
415+
async def test_tdigest_trimmed_mean(modclient: redis.Redis):
416+
assert await modclient.tdigest().create("tDigest", 100)
417+
# insert data-points into sketch
418+
assert await modclient.tdigest().add("tDigest", list(range(1, 10)), [1] * 10)
419+
assert 5 == await modclient.tdigest().trimmed_mean("tDigest", 0.1, 0.9)
420+
assert 4.5 == await modclient.tdigest().trimmed_mean("tDigest", 0.4, 0.5)
421+
422+
423+
@pytest.mark.redismod
424+
@pytest.mark.experimental
425+
async def test_tdigest_rank(modclient: redis.Redis):
426+
assert await modclient.tdigest().create("t-digest", 500)
427+
assert await modclient.tdigest().add("t-digest", list(range(0, 20)), [1] * 20)
428+
assert -1 == (await modclient.tdigest().rank("t-digest", -1))[0]
429+
assert 1 == (await modclient.tdigest().rank("t-digest", 0))[0]
430+
assert 11 == (await modclient.tdigest().rank("t-digest", 10))[0]
431+
assert [-1, 20, 10] == await modclient.tdigest().rank("t-digest", -20, 20, 9)
432+
433+
434+
@pytest.mark.redismod
435+
@pytest.mark.experimental
436+
async def test_tdigest_revrank(modclient: redis.Redis):
437+
assert await modclient.tdigest().create("t-digest", 500)
438+
assert await modclient.tdigest().add("t-digest", list(range(0, 20)), [1] * 20)
439+
assert -1 == (await modclient.tdigest().revrank("t-digest", 20))[0]
440+
assert 20 == (await modclient.tdigest().revrank("t-digest", 0))[0]
441+
assert [-1, 20, 10] == await modclient.tdigest().revrank("t-digest", 21, 0, 10)
442+
443+
444+
@pytest.mark.redismod
445+
@pytest.mark.experimental
446+
async def test_tdigest_byrank(modclient: redis.Redis):
447+
assert await modclient.tdigest().create("t-digest", 500)
448+
assert await modclient.tdigest().add("t-digest", list(range(1, 11)), [1] * 20)
449+
assert 1 == (await modclient.tdigest().byrank("t-digest", 0))[0]
450+
assert 10 == (await modclient.tdigest().byrank("t-digest", 9))[0]
451+
assert (await modclient.tdigest().byrank("t-digest", 100))[0] == inf
452+
with pytest.raises(redis.ResponseError):
453+
(await modclient.tdigest().byrank("t-digest", -1))[0]
454+
455+
456+
@pytest.mark.redismod
457+
@pytest.mark.experimental
458+
async def test_tdigest_byrevrank(modclient: redis.Redis):
459+
assert await modclient.tdigest().create("t-digest", 500)
460+
assert await modclient.tdigest().add("t-digest", list(range(1, 11)), [1] * 20)
461+
assert 10 == (await modclient.tdigest().byrevrank("t-digest", 0))[0]
462+
assert 2 == (await modclient.tdigest().byrevrank("t-digest", 9))[0]
463+
assert (await modclient.tdigest().byrevrank("t-digest", 100))[0] == -inf
464+
with pytest.raises(redis.ResponseError):
465+
(await modclient.tdigest().byrevrank("t-digest", -1))[0]
409466

410467

411468
# @pytest.mark.redismod

0 commit comments

Comments
 (0)