Skip to content

Commit

Permalink
Implement HRANDFIELD
Browse files Browse the repository at this point in the history
Fix #156
  • Loading branch information
cunla committed May 26, 2023
1 parent 091f82d commit b9faa48
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 8 deletions.
11 changes: 4 additions & 7 deletions docs/redis-commands/Redis.md
Original file line number Diff line number Diff line change
Expand Up @@ -1276,6 +1276,10 @@ Returns the values of all fields in a hash.

Sets the values of multiple fields.

### [HRANDFIELD](https://redis.io/commands/hrandfield/)

Returns one or more random fields from a hash.

### [HSCAN](https://redis.io/commands/hscan/)

Iterates over fields and values of a hash.
Expand All @@ -1297,13 +1301,6 @@ Returns the length of the value of a field.
Returns all values in a hash.


### Unsupported hash commands
> To implement support for a command, see [here](/guides/implement-command/)
#### [HRANDFIELD](https://redis.io/commands/hrandfield/) <small>(not implemented)</small>

Returns one or more random fields from a hash.


## hyperloglog commands

Expand Down
25 changes: 24 additions & 1 deletion fakeredis/commands_mixins/hash_mixin.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import itertools
import math
import random

from fakeredis import _msgs as msgs
from fakeredis._commands import (command, Key, Hash, Int, Float)
from fakeredis._helpers import (SimpleError, OK)
from fakeredis._helpers import (SimpleError, OK, casematch)


class HashCommandsMixin:
Expand Down Expand Up @@ -98,3 +99,25 @@ def hstrlen(self, key, field):
@command((Key(Hash),))
def hvals(self, key):
return list(key.value.values())

@command(name='HRANDFIELD', fixed=(Key(Hash),), repeat=(bytes,))
def hrandfield(self, key, *args):
if len(args) > 2:
raise SimpleError(msgs.SYNTAX_ERROR_MSG)
if key.value is None or len(key.value) == 0:
return None
count = min(Int.decode(args[0]) if len(args) >= 1 else 1, len(key.value))
withvalues = casematch(args[1], b"withvalues") if len(args) >= 2 else False
if count == 0:
return list()

if count < 0: # Allow repetitions
res = random.choices(sorted(key.value.items()), k=-count)
else: # Unique values from hash
res = random.sample(sorted(key.value.items()), count)

if withvalues:
res = [item for t in res for item in t]
else:
res = [t[0] for t in res]
return res
28 changes: 28 additions & 0 deletions test/test_mixins/test_hash_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import redis
import redis.client

from test import testtools


# Tests for the hash type.

Expand Down Expand Up @@ -289,3 +291,29 @@ def test_hscan(r: redis.Redis):
assert b'key:7' in results
assert b'key:17' in results
assert len(results) == 2


def test_hrandfield(r: redis.Redis):
assert r.hrandfield("key") is None
hash = {b"a": 1, b"b": 2, b"c": 3, b"d": 4, b"e": 5}
r.hset("key", mapping=hash)
assert r.hrandfield("key") is not None
assert len(r.hrandfield("key", 0)) == 0
res = r.hrandfield("key", 2)
assert len(res) == 2
assert res[0] in set(hash.keys())
assert res[1] in set(hash.keys())
# with values
res = r.hrandfield("key", 2, True)
assert len(res) == 4
assert res[0] in set(hash.keys())
assert res[1] in {str(x).encode() for x in hash.values()}
assert res[2] in set(hash.keys())
assert res[3] in {str(x).encode() for x in hash.values()}
# without duplications
assert len(r.hrandfield("key", 10)) == 5
# with duplications
assert len(r.hrandfield("key", -10)) == 10

with pytest.raises(redis.ResponseError):
testtools.raw_command(r, 'HRANDFIELD', 'key', 3, 'WITHVALUES', 3)

0 comments on commit b9faa48

Please sign in to comment.