Skip to content

Commit 9bb31c0

Browse files
Merge branch 'master' into GETDEL
2 parents 853bc30 + 627db54 commit 9bb31c0

File tree

2 files changed

+116
-2
lines changed

2 files changed

+116
-2
lines changed

redis/client.py

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -530,15 +530,15 @@ def parse_client_info(value):
530530
"key1=value1 key2=value2 key3=value3"
531531
"""
532532
client_info = {}
533-
infos = value.split(" ")
533+
infos = str_if_bytes(value).split(" ")
534534
for info in infos:
535535
key, value = info.split("=")
536536
client_info[key] = value
537537

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

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

1247+
def client_info(self):
1248+
"""
1249+
Returns information and statistics about the current
1250+
client connection.
1251+
"""
1252+
return self.execute_command('CLIENT INFO')
1253+
12461254
def client_list(self, _type=None):
12471255
"""
12481256
Returns a list of currently connected clients.
@@ -1689,6 +1697,7 @@ def get(self, name):
16891697
"""
16901698
return self.execute_command('GET', name)
16911699

1700+
16921701
def getdel(self, name):
16931702
"""
16941703
Get the value at key ``name`` and delete the key. This command
@@ -1698,6 +1707,57 @@ def getdel(self, name):
16981707
"""
16991708
return self.execute_command('GETDEL', name)
17001709

1710+
def getex(self, name,
1711+
ex=None, px=None, exat=None, pxat=None, persist=False):
1712+
"""
1713+
Get the value of key and optionally set its expiration.
1714+
GETEX is similar to GET, but is a write command with
1715+
additional options. All time parameters can be given as
1716+
datetime.timedelta or integers.
1717+
1718+
``ex`` sets an expire flag on key ``name`` for ``ex`` seconds.
1719+
1720+
``px`` sets an expire flag on key ``name`` for ``px`` milliseconds.
1721+
1722+
``exat`` sets an expire flag on key ``name`` for ``ex`` seconds,
1723+
specified in unix time.
1724+
1725+
``pxat`` sets an expire flag on key ``name`` for ``ex`` milliseconds,
1726+
specified in unix time.
1727+
1728+
``persist`` remove the time to live associated with ``name``.
1729+
"""
1730+
1731+
pieces = []
1732+
# similar to set command
1733+
if ex is not None:
1734+
pieces.append('EX')
1735+
if isinstance(ex, datetime.timedelta):
1736+
ex = int(ex.total_seconds())
1737+
pieces.append(ex)
1738+
if px is not None:
1739+
pieces.append('PX')
1740+
if isinstance(px, datetime.timedelta):
1741+
px = int(px.total_seconds() * 1000)
1742+
pieces.append(px)
1743+
# similar to pexpireat command
1744+
if exat is not None:
1745+
pieces.append('EXAT')
1746+
if isinstance(exat, datetime.datetime):
1747+
s = int(exat.microsecond / 1000000)
1748+
exat = int(mod_time.mktime(exat.timetuple())) + s
1749+
pieces.append(exat)
1750+
if pxat is not None:
1751+
pieces.append('PXAT')
1752+
if isinstance(pxat, datetime.datetime):
1753+
ms = int(pxat.microsecond / 1000)
1754+
pxat = int(mod_time.mktime(pxat.timetuple())) * 1000 + ms
1755+
pieces.append(pxat)
1756+
if persist:
1757+
pieces.append('PERSIST')
1758+
1759+
return self.execute_command('GETEX', name, *pieces)
1760+
17011761
def __getitem__(self, name):
17021762
"""
17031763
Return the value at key ``name``, raises a KeyError if the key
@@ -1829,6 +1889,26 @@ def pttl(self, name):
18291889
"Returns the number of milliseconds until the key ``name`` will expire"
18301890
return self.execute_command('PTTL', name)
18311891

1892+
def hrandfield(self, key, count=None, withvalues=False):
1893+
"""
1894+
Return a random field from the hash value stored at key.
1895+
1896+
count: if the argument is positive, return an array of distinct fields.
1897+
If called with a negative count, the behavior changes and the command
1898+
is allowed to return the same field multiple times. In this case,
1899+
the number of returned fields is the absolute value of the
1900+
specified count.
1901+
withvalues: The optional WITHVALUES modifier changes the reply so it
1902+
includes the respective values of the randomly selected hash fields.
1903+
"""
1904+
params = []
1905+
if count is not None:
1906+
params.append(count)
1907+
if withvalues:
1908+
params.append("WITHVALUES")
1909+
1910+
return self.execute_command("HRANDFIELD", key, *params)
1911+
18321912
def randomkey(self):
18331913
"Returns the name of a random key"
18341914
return self.execute_command('RANDOMKEY')

tests/test_commands.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,12 @@ def test_client_list(self, r):
281281
assert isinstance(clients[0], dict)
282282
assert 'addr' in clients[0]
283283

284+
@skip_if_server_version_lt('6.2.0')
285+
def test_client_info(self, r):
286+
info = r.client_info()
287+
assert isinstance(info, dict)
288+
assert 'addr' in info
289+
284290
@skip_if_server_version_lt('5.0.0')
285291
def test_client_list_type(self, r):
286292
with pytest.raises(exceptions.RedisError):
@@ -733,6 +739,21 @@ def test_getdel(self, r):
733739
r.set('a', 1)
734740
assert r.getdel('a') == b'1'
735741
assert r.getdel('a') is None
742+
743+
@skip_if_server_version_lt('6.2.0')
744+
def test_getex(self, r):
745+
r.set('a', 1)
746+
assert r.getex('a') == b'1'
747+
assert r.ttl('a') == -1
748+
assert r.getex('a', ex=60) == b'1'
749+
assert r.ttl('a') == 60
750+
assert r.getex('a', px=6000) == b'1'
751+
assert r.ttl('a') == 6
752+
expire_at = redis_server_time(r) + datetime.timedelta(minutes=1)
753+
assert r.getex('a', pxat=expire_at) == b'1'
754+
assert r.ttl('a') <= 60
755+
assert r.getex('a', persist=True) == b'1'
756+
assert r.ttl('a') == -1
736757

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

905+
@skip_if_server_version_lt('6.2.0')
906+
def test_hrandfield(self, r):
907+
assert r.hrandfield('key') is None
908+
r.hset('key', mapping={'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5})
909+
assert r.hrandfield('key') is not None
910+
assert len(r.hrandfield('key', 2)) == 2
911+
# with values
912+
assert len(r.hrandfield('key', 2, True)) == 4
913+
# without duplications
914+
assert len(r.hrandfield('key', 10)) == 5
915+
# with duplications
916+
assert len(r.hrandfield('key', -10)) == 10
917+
884918
def test_randomkey(self, r):
885919
assert r.randomkey() is None
886920
for key in ('a', 'b', 'c'):

0 commit comments

Comments
 (0)