Skip to content

Commit

Permalink
Merge branch 'master' into GETDEL
Browse files Browse the repository at this point in the history
  • Loading branch information
AvitalFineRedis authored Jul 22, 2021
2 parents 853bc30 + 627db54 commit 9bb31c0
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 2 deletions.
84 changes: 82 additions & 2 deletions redis/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -530,15 +530,15 @@ def parse_client_info(value):
"key1=value1 key2=value2 key3=value3"
"""
client_info = {}
infos = value.split(" ")
infos = str_if_bytes(value).split(" ")
for info in infos:
key, value = info.split("=")
client_info[key] = value

# Those fields are definded as int in networking.c
for int_key in {"id", "age", "idle", "db", "sub", "psub",
"multi", "qbuf", "qbuf-free", "obl",
"oll", "omem"}:
"argv-mem", "oll", "omem", "tot-mem"}:
client_info[int_key] = int(client_info[int_key])
return client_info

Expand Down Expand Up @@ -620,6 +620,7 @@ class Redis:
'CLIENT ID': int,
'CLIENT KILL': parse_client_kill,
'CLIENT LIST': parse_client_list,
'CLIENT INFO': parse_client_info,
'CLIENT SETNAME': bool_ok,
'CLIENT UNBLOCK': lambda r: r and int(r) == 1 or False,
'CLIENT PAUSE': bool_ok,
Expand Down Expand Up @@ -1243,6 +1244,13 @@ def client_kill_filter(self, _id=None, _type=None, addr=None, skipme=None):
"<value> must specify at least one filter")
return self.execute_command('CLIENT KILL', *args)

def client_info(self):
"""
Returns information and statistics about the current
client connection.
"""
return self.execute_command('CLIENT INFO')

def client_list(self, _type=None):
"""
Returns a list of currently connected clients.
Expand Down Expand Up @@ -1689,6 +1697,7 @@ def get(self, name):
"""
return self.execute_command('GET', name)


def getdel(self, name):
"""
Get the value at key ``name`` and delete the key. This command
Expand All @@ -1698,6 +1707,57 @@ def getdel(self, name):
"""
return self.execute_command('GETDEL', name)

def getex(self, name,
ex=None, px=None, exat=None, pxat=None, persist=False):
"""
Get the value of key and optionally set its expiration.
GETEX is similar to GET, but is a write command with
additional options. All time parameters can be given as
datetime.timedelta or integers.
``ex`` sets an expire flag on key ``name`` for ``ex`` seconds.
``px`` sets an expire flag on key ``name`` for ``px`` milliseconds.
``exat`` sets an expire flag on key ``name`` for ``ex`` seconds,
specified in unix time.
``pxat`` sets an expire flag on key ``name`` for ``ex`` milliseconds,
specified in unix time.
``persist`` remove the time to live associated with ``name``.
"""

pieces = []
# similar to set command
if ex is not None:
pieces.append('EX')
if isinstance(ex, datetime.timedelta):
ex = int(ex.total_seconds())
pieces.append(ex)
if px is not None:
pieces.append('PX')
if isinstance(px, datetime.timedelta):
px = int(px.total_seconds() * 1000)
pieces.append(px)
# similar to pexpireat command
if exat is not None:
pieces.append('EXAT')
if isinstance(exat, datetime.datetime):
s = int(exat.microsecond / 1000000)
exat = int(mod_time.mktime(exat.timetuple())) + s
pieces.append(exat)
if pxat is not None:
pieces.append('PXAT')
if isinstance(pxat, datetime.datetime):
ms = int(pxat.microsecond / 1000)
pxat = int(mod_time.mktime(pxat.timetuple())) * 1000 + ms
pieces.append(pxat)
if persist:
pieces.append('PERSIST')

return self.execute_command('GETEX', name, *pieces)

def __getitem__(self, name):
"""
Return the value at key ``name``, raises a KeyError if the key
Expand Down Expand Up @@ -1829,6 +1889,26 @@ def pttl(self, name):
"Returns the number of milliseconds until the key ``name`` will expire"
return self.execute_command('PTTL', name)

def hrandfield(self, key, count=None, withvalues=False):
"""
Return a random field from the hash value stored at key.
count: if the argument is positive, return an array of distinct fields.
If called with a negative count, the behavior changes and the command
is allowed to return the same field multiple times. In this case,
the number of returned fields is the absolute value of the
specified count.
withvalues: The optional WITHVALUES modifier changes the reply so it
includes the respective values of the randomly selected hash fields.
"""
params = []
if count is not None:
params.append(count)
if withvalues:
params.append("WITHVALUES")

return self.execute_command("HRANDFIELD", key, *params)

def randomkey(self):
"Returns the name of a random key"
return self.execute_command('RANDOMKEY')
Expand Down
34 changes: 34 additions & 0 deletions tests/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,12 @@ def test_client_list(self, r):
assert isinstance(clients[0], dict)
assert 'addr' in clients[0]

@skip_if_server_version_lt('6.2.0')
def test_client_info(self, r):
info = r.client_info()
assert isinstance(info, dict)
assert 'addr' in info

@skip_if_server_version_lt('5.0.0')
def test_client_list_type(self, r):
with pytest.raises(exceptions.RedisError):
Expand Down Expand Up @@ -733,6 +739,21 @@ def test_getdel(self, r):
r.set('a', 1)
assert r.getdel('a') == b'1'
assert r.getdel('a') is None

@skip_if_server_version_lt('6.2.0')
def test_getex(self, r):
r.set('a', 1)
assert r.getex('a') == b'1'
assert r.ttl('a') == -1
assert r.getex('a', ex=60) == b'1'
assert r.ttl('a') == 60
assert r.getex('a', px=6000) == b'1'
assert r.ttl('a') == 6
expire_at = redis_server_time(r) + datetime.timedelta(minutes=1)
assert r.getex('a', pxat=expire_at) == b'1'
assert r.ttl('a') <= 60
assert r.getex('a', persist=True) == b'1'
assert r.ttl('a') == -1

def test_getitem_and_setitem(self, r):
r['a'] = 'bar'
Expand Down Expand Up @@ -881,6 +902,19 @@ def test_pttl_no_key(self, r):
"PTTL on servers 2.8 and after return -2 when the key doesn't exist"
assert r.pttl('a') == -2

@skip_if_server_version_lt('6.2.0')
def test_hrandfield(self, r):
assert r.hrandfield('key') is None
r.hset('key', mapping={'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5})
assert r.hrandfield('key') is not None
assert len(r.hrandfield('key', 2)) == 2
# with values
assert len(r.hrandfield('key', 2, True)) == 4
# without duplications
assert len(r.hrandfield('key', 10)) == 5
# with duplications
assert len(r.hrandfield('key', -10)) == 10

def test_randomkey(self, r):
assert r.randomkey() is None
for key in ('a', 'b', 'c'):
Expand Down

0 comments on commit 9bb31c0

Please sign in to comment.