306 lines
9.8 KiB
Python
306 lines
9.8 KiB
Python
from collections import namedtuple
|
|
|
|
from aioredis.util import wait_ok, wait_convert, wait_make_dict, _NOTSET
|
|
|
|
|
|
class ServerCommandsMixin:
|
|
"""Server commands mixin.
|
|
|
|
For commands details see: http://redis.io/commands/#server
|
|
"""
|
|
|
|
SHUTDOWN_SAVE = 'SHUTDOWN_SAVE'
|
|
SHUTDOWN_NOSAVE = 'SHUTDOWN_NOSAVE'
|
|
|
|
def bgrewriteaof(self):
|
|
"""Asynchronously rewrite the append-only file."""
|
|
fut = self.execute(b'BGREWRITEAOF')
|
|
return wait_ok(fut)
|
|
|
|
def bgsave(self):
|
|
"""Asynchronously save the dataset to disk."""
|
|
fut = self.execute(b'BGSAVE')
|
|
return wait_ok(fut)
|
|
|
|
def client_kill(self):
|
|
"""Kill the connection of a client.
|
|
|
|
.. warning:: Not Implemented
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def client_list(self):
|
|
"""Get the list of client connections.
|
|
|
|
Returns list of ClientInfo named tuples.
|
|
"""
|
|
fut = self.execute(b'CLIENT', b'LIST', encoding='utf-8')
|
|
return wait_convert(fut, to_tuples)
|
|
|
|
def client_getname(self, encoding=_NOTSET):
|
|
"""Get the current connection name."""
|
|
return self.execute(b'CLIENT', b'GETNAME', encoding=encoding)
|
|
|
|
def client_pause(self, timeout):
|
|
"""Stop processing commands from clients for *timeout* milliseconds.
|
|
|
|
:raises TypeError: if timeout is not int
|
|
:raises ValueError: if timeout is less than 0
|
|
"""
|
|
if not isinstance(timeout, int):
|
|
raise TypeError("timeout argument must be int")
|
|
if timeout < 0:
|
|
raise ValueError("timeout must be greater equal 0")
|
|
fut = self.execute(b'CLIENT', b'PAUSE', timeout)
|
|
return wait_ok(fut)
|
|
|
|
def client_reply(self):
|
|
raise NotImplementedError()
|
|
|
|
def client_setname(self, name):
|
|
"""Set the current connection name."""
|
|
fut = self.execute(b'CLIENT', b'SETNAME', name)
|
|
return wait_ok(fut)
|
|
|
|
def command(self):
|
|
"""Get array of Redis commands."""
|
|
# TODO: convert result
|
|
return self.execute(b'COMMAND', encoding='utf-8')
|
|
|
|
def command_count(self):
|
|
"""Get total number of Redis commands."""
|
|
return self.execute(b'COMMAND', b'COUNT')
|
|
|
|
def command_getkeys(self, command, *args, encoding='utf-8'):
|
|
"""Extract keys given a full Redis command."""
|
|
return self.execute(b'COMMAND', b'GETKEYS', command, *args,
|
|
encoding=encoding)
|
|
|
|
def command_info(self, command, *commands):
|
|
"""Get array of specific Redis command details."""
|
|
return self.execute(b'COMMAND', b'INFO', command, *commands,
|
|
encoding='utf-8')
|
|
|
|
def config_get(self, parameter='*'):
|
|
"""Get the value of a configuration parameter(s).
|
|
|
|
If called without argument will return all parameters.
|
|
|
|
:raises TypeError: if parameter is not string
|
|
"""
|
|
if not isinstance(parameter, str):
|
|
raise TypeError("parameter must be str")
|
|
fut = self.execute(b'CONFIG', b'GET', parameter, encoding='utf-8')
|
|
return wait_make_dict(fut)
|
|
|
|
def config_rewrite(self):
|
|
"""Rewrite the configuration file with the in memory configuration."""
|
|
fut = self.execute(b'CONFIG', b'REWRITE')
|
|
return wait_ok(fut)
|
|
|
|
def config_set(self, parameter, value):
|
|
"""Set a configuration parameter to the given value."""
|
|
if not isinstance(parameter, str):
|
|
raise TypeError("parameter must be str")
|
|
fut = self.execute(b'CONFIG', b'SET', parameter, value)
|
|
return wait_ok(fut)
|
|
|
|
def config_resetstat(self):
|
|
"""Reset the stats returned by INFO."""
|
|
fut = self.execute(b'CONFIG', b'RESETSTAT')
|
|
return wait_ok(fut)
|
|
|
|
def dbsize(self):
|
|
"""Return the number of keys in the selected database."""
|
|
return self.execute(b'DBSIZE')
|
|
|
|
def debug_sleep(self, timeout):
|
|
"""Suspend connection for timeout seconds."""
|
|
fut = self.execute(b'DEBUG', b'SLEEP', timeout)
|
|
return wait_ok(fut)
|
|
|
|
def debug_object(self, key):
|
|
"""Get debugging information about a key."""
|
|
return self.execute(b'DEBUG', b'OBJECT', key)
|
|
|
|
def debug_segfault(self, key):
|
|
"""Make the server crash."""
|
|
# won't test, this probably works
|
|
return self.execute(b'DEBUG', 'SEGFAULT') # pragma: no cover
|
|
|
|
def flushall(self, async_op=False):
|
|
"""
|
|
Remove all keys from all databases.
|
|
|
|
:param async_op: lets the entire dataset to be freed asynchronously. \
|
|
Defaults to False
|
|
"""
|
|
if async_op:
|
|
fut = self.execute(b'FLUSHALL', b'ASYNC')
|
|
else:
|
|
fut = self.execute(b'FLUSHALL')
|
|
return wait_ok(fut)
|
|
|
|
def flushdb(self, async_op=False):
|
|
"""
|
|
Remove all keys from the current database.
|
|
|
|
:param async_op: lets a single database to be freed asynchronously. \
|
|
Defaults to False
|
|
"""
|
|
if async_op:
|
|
fut = self.execute(b'FLUSHDB', b'ASYNC')
|
|
else:
|
|
fut = self.execute(b'FLUSHDB')
|
|
return wait_ok(fut)
|
|
|
|
def info(self, section='default'):
|
|
"""Get information and statistics about the server.
|
|
|
|
If called without argument will return default set of sections.
|
|
For available sections, see http://redis.io/commands/INFO
|
|
|
|
:raises ValueError: if section is invalid
|
|
|
|
"""
|
|
if not section:
|
|
raise ValueError("invalid section")
|
|
fut = self.execute(b'INFO', section, encoding='utf-8')
|
|
return wait_convert(fut, parse_info)
|
|
|
|
def lastsave(self):
|
|
"""Get the UNIX time stamp of the last successful save to disk."""
|
|
return self.execute(b'LASTSAVE')
|
|
|
|
def monitor(self):
|
|
"""Listen for all requests received by the server in real time.
|
|
|
|
.. warning::
|
|
Will not be implemented for now.
|
|
"""
|
|
# NOTE: will not implement for now;
|
|
raise NotImplementedError
|
|
|
|
def role(self):
|
|
"""Return the role of the server instance.
|
|
|
|
Returns named tuples describing role of the instance.
|
|
For fields information see http://redis.io/commands/role#output-format
|
|
"""
|
|
fut = self.execute(b'ROLE', encoding='utf-8')
|
|
return wait_convert(fut, parse_role)
|
|
|
|
def save(self):
|
|
"""Synchronously save the dataset to disk."""
|
|
return self.execute(b'SAVE')
|
|
|
|
def shutdown(self, save=None):
|
|
"""Synchronously save the dataset to disk and then
|
|
shut down the server.
|
|
"""
|
|
if save is self.SHUTDOWN_SAVE:
|
|
return self.execute(b'SHUTDOWN', b'SAVE')
|
|
elif save is self.SHUTDOWN_NOSAVE:
|
|
return self.execute(b'SHUTDOWN', b'NOSAVE')
|
|
else:
|
|
return self.execute(b'SHUTDOWN')
|
|
|
|
def slaveof(self, host, port=None):
|
|
"""Make the server a slave of another instance,
|
|
or promote it as master.
|
|
|
|
Calling ``slaveof(None)`` will send ``SLAVEOF NO ONE``.
|
|
|
|
.. versionchanged:: v0.2.6
|
|
``slaveof()`` form deprecated
|
|
in favour of explicit ``slaveof(None)``.
|
|
"""
|
|
if host is None and port is None:
|
|
return self.execute(b'SLAVEOF', b'NO', b'ONE')
|
|
return self.execute(b'SLAVEOF', host, port)
|
|
|
|
def slowlog_get(self, length=None):
|
|
"""Returns the Redis slow queries log."""
|
|
if length is not None:
|
|
if not isinstance(length, int):
|
|
raise TypeError("length must be int or None")
|
|
return self.execute(b'SLOWLOG', b'GET', length)
|
|
else:
|
|
return self.execute(b'SLOWLOG', b'GET')
|
|
|
|
def slowlog_len(self):
|
|
"""Returns length of Redis slow queries log."""
|
|
return self.execute(b'SLOWLOG', b'LEN')
|
|
|
|
def slowlog_reset(self):
|
|
"""Resets Redis slow queries log."""
|
|
fut = self.execute(b'SLOWLOG', b'RESET')
|
|
return wait_ok(fut)
|
|
|
|
def sync(self):
|
|
"""Redis-server internal command used for replication."""
|
|
return self.execute(b'SYNC')
|
|
|
|
def time(self):
|
|
"""Return current server time."""
|
|
fut = self.execute(b'TIME')
|
|
return wait_convert(fut, to_time)
|
|
|
|
|
|
def _split(s):
|
|
k, v = s.split('=')
|
|
return k.replace('-', '_'), v
|
|
|
|
|
|
def to_time(obj):
|
|
return int(obj[0]) + int(obj[1]) * 1e-6
|
|
|
|
|
|
def to_tuples(value):
|
|
line, *lines = value.splitlines(False)
|
|
line = list(map(_split, line.split(' ')))
|
|
ClientInfo = namedtuple('ClientInfo', ' '.join(k for k, v in line))
|
|
# TODO: parse flags and other known fields
|
|
result = [ClientInfo(**dict(line))]
|
|
for line in lines:
|
|
result.append(ClientInfo(**dict(map(_split, line.split(' ')))))
|
|
return result
|
|
|
|
|
|
def parse_info(info):
|
|
res = {}
|
|
for block in info.split('\r\n\r\n'):
|
|
section, *block = block.strip().splitlines()
|
|
section = section[2:].lower()
|
|
res[section] = tmp = {}
|
|
for line in block:
|
|
key, value = line.split(':', 1)
|
|
if ',' in line and '=' in line:
|
|
value = dict(map(lambda i: i.split('='), value.split(',')))
|
|
tmp[key] = value
|
|
return res
|
|
|
|
|
|
# XXX: may change in future
|
|
# (may be hard to maintain for new/old redis versions)
|
|
MasterInfo = namedtuple('MasterInfo', 'role replication_offset slaves')
|
|
MasterSlaveInfo = namedtuple('MasterSlaveInfo', 'ip port ack_offset')
|
|
|
|
SlaveInfo = namedtuple('SlaveInfo',
|
|
'role master_ip master_port state received')
|
|
|
|
SentinelInfo = namedtuple('SentinelInfo', 'role masters')
|
|
|
|
|
|
def parse_role(role):
|
|
type_ = role[0]
|
|
if type_ == 'master':
|
|
slaves = [MasterSlaveInfo(s[0], int(s[1]), int(s[2]))
|
|
for s in role[2]]
|
|
return MasterInfo(role[0], int(role[1]), slaves)
|
|
elif type_ == 'slave':
|
|
return SlaveInfo(role[0], role[1], int(role[2]), role[3], int(role[4]))
|
|
elif type_ == 'sentinel':
|
|
return SentinelInfo(*role)
|
|
return role
|