-#!/usr/bin/python
+#!/usr/bin/env python
""" redis.py - A client for the Redis daemon.
+History:
+
+ - 20090603 fix missing errno import, add sunion and sunionstore commands,
+ generalize shebang (Jochen Kupperschmidt)
+
"""
__author__ = "Ludovico Magnocavallo <ludo\x40qix\x2eit>"
import socket
+import decimal
+import errno
BUFSIZE = 4096
"""The main Redis client.
"""
- def __init__(self, host=None, port=None, timeout=None):
+ def __init__(self, host=None, port=None, timeout=None, db=None, nodelay=None, charset='utf8', errors='strict'):
self.host = host or 'localhost'
self.port = port or 6379
if timeout:
socket.setdefaulttimeout(timeout)
+ self.nodelay = nodelay
+ self.charset = charset
+ self.errors = errors
self._sock = None
self._fp = None
+ self.db = db
+ def _encode(self, s):
+ if isinstance(s, str):
+ return s
+ if isinstance(s, unicode):
+ try:
+ return s.encode(self.charset, self.errors)
+ except UnicodeEncodeError, e:
+ raise InvalidData("Error encoding unicode value '%s': %s" % (value.encode(self.charset, 'replace'), e))
+ return str(s)
+
def _write(self, s):
"""
- >>> r = Redis()
+ >>> r = Redis(db=9)
>>> r.connect()
>>> r._sock.close()
>>> try:
def ping(self):
"""
- >>> r = Redis()
+ >>> r = Redis(db=9)
>>> r.ping()
'PONG'
>>>
self._write('PING\r\n')
return self.get_response()
- def set(self, name, value, preserve=False):
+ def set(self, name, value, preserve=False, getset=False):
"""
- >>> r = Redis()
+ >>> r = Redis(db=9)
>>> r.set('a', 'pippo')
'OK'
- >>> try:
- ... r.set('a', u'pippo \u3235')
- ... except InvalidData, e:
- ... print e
- Error encoding unicode value for key 'a': 'ascii' codec can't encode character u'\u3235' in position 15: ordinal not in range(128).
+ >>> r.set('a', u'pippo \u3235')
+ 'OK'
+ >>> r.get('a')
+ u'pippo \u3235'
>>> r.set('b', 105.2)
'OK'
>>> r.set('b', 'xxx', preserve=True)
0
>>> r.get('b')
- '105.2'
+ Decimal("105.2")
>>>
"""
self.connect()
# the following will raise an error for unicode values that can't be encoded to ascii
# we could probably add an 'encoding' arg to init, but then what do we do with get()?
# convert back to unicode? and what about ints, or pickled values?
- try:
- value = value if isinstance(value, basestring) else str(value)
- self._write('%s %s %s\r\n%s\r\n' % (
- 'SETNX' if preserve else 'SET', name, len(value), value
+ if getset: command = 'GETSET'
+ elif preserve: command = 'SETNX'
+ else: command = 'SET'
+ value = self._encode(value)
+ self._write('%s %s %s\r\n%s\r\n' % (
+ command, name, len(value), value
))
- except UnicodeEncodeError, e:
- raise InvalidData("Error encoding unicode value for key '%s': %s." % (name, e))
return self.get_response()
def get(self, name):
"""
- >>> r = Redis()
- >>> r.set('a', 'pippo'), r.set('b', 15), r.set('c', '\\r\\naaa\\nbbb\\r\\ncccc\\nddd\\r\\n'), r.set('d', '\\r\\n')
+ >>> r = Redis(db=9)
+ >>> r.set('a', 'pippo'), r.set('b', 15), r.set('c', ' \\r\\naaa\\nbbb\\r\\ncccc\\nddd\\r\\n '), r.set('d', '\\r\\n')
('OK', 'OK', 'OK', 'OK')
>>> r.get('a')
- 'pippo'
+ u'pippo'
>>> r.get('b')
- '15'
+ 15
>>> r.get('d')
- '\\r\\n'
+ u'\\r\\n'
>>> r.get('b')
- '15'
+ 15
>>> r.get('c')
- '\\r\\naaa\\nbbb\\r\\ncccc\\nddd\\r\\n'
+ u' \\r\\naaa\\nbbb\\r\\ncccc\\nddd\\r\\n '
>>> r.get('c')
- '\\r\\naaa\\nbbb\\r\\ncccc\\nddd\\r\\n'
+ u' \\r\\naaa\\nbbb\\r\\ncccc\\nddd\\r\\n '
>>> r.get('ajhsd')
>>>
"""
self._write('GET %s\r\n' % name)
return self.get_response()
+ def getset(self, name, value):
+ """
+ >>> r = Redis(db=9)
+ >>> r.set('a', 'pippo')
+ 'OK'
+ >>> r.getset('a', 2)
+ u'pippo'
+ >>>
+ """
+ return self.set(name, value, getset=True)
+
def mget(self, *args):
"""
- >>> r = Redis()
+ >>> r = Redis(db=9)
>>> r.set('a', 'pippo'), r.set('b', 15), r.set('c', '\\r\\naaa\\nbbb\\r\\ncccc\\nddd\\r\\n'), r.set('d', '\\r\\n')
('OK', 'OK', 'OK', 'OK')
>>> r.mget('a', 'b', 'c', 'd')
- ['pippo', '15', '\\r\\naaa\\nbbb\\r\\ncccc\\nddd\\r\\n', '\\r\\n']
+ [u'pippo', 15, u'\\r\\naaa\\nbbb\\r\\ncccc\\nddd\\r\\n', u'\\r\\n']
>>>
"""
self.connect()
def incr(self, name, amount=1):
"""
- >>> r = Redis()
+ >>> r = Redis(db=9)
>>> r.delete('a')
1
>>> r.incr('a')
def decr(self, name, amount=1):
"""
- >>> r = Redis()
+ >>> r = Redis(db=9)
>>> if r.get('a'):
... r.delete('a')
... else:
def exists(self, name):
"""
- >>> r = Redis()
+ >>> r = Redis(db=9)
>>> r.exists('dsjhfksjdhfkdsjfh')
0
>>> r.set('a', 'a')
def delete(self, name):
"""
- >>> r = Redis()
+ >>> r = Redis(db=9)
>>> r.delete('dsjhfksjdhfkdsjfh')
0
>>> r.set('a', 'a')
self._write('DEL %s\r\n' % name)
return self.get_response()
- def key_type(self, name):
+ def get_type(self, name):
"""
- Not yet implemented.
+ >>> r = Redis(db=9)
+ >>> r.set('a', 3)
+ 'OK'
+ >>> r.get_type('a')
+ 'string'
+ >>> r.get_type('zzz')
+ >>>
"""
self.connect()
self._write('TYPE %s\r\n' % name)
- return self.get_response()
+ res = self.get_response()
+ return None if res == 'none' else res
def keys(self, pattern):
"""
- >>> r = Redis()
+ >>> r = Redis(db=9)
>>> r.flush()
'OK'
>>> r.set('a', 'a')
'OK'
>>> r.keys('a*')
- ['a']
+ [u'a']
>>> r.set('a2', 'a')
'OK'
>>> r.keys('a*')
- ['a', 'a2']
+ [u'a', u'a2']
>>> r.delete('a2')
1
>>> r.keys('sjdfhskjh*')
def randomkey(self):
"""
- >>> r = Redis()
+ >>> r = Redis(db=9)
>>> r.set('a', 'a')
'OK'
>>> isinstance(r.randomkey(), str)
def rename(self, src, dst, preserve=False):
"""
- >>> r = Redis()
+ >>> r = Redis(db=9)
>>> try:
... r.rename('a', 'a')
... except ResponseError, e:
return self.get_response()
else:
self._write('RENAME %s %s\r\n' % (src, dst))
- return self.get_response().strip()
+ return self.get_response() #.strip()
+
+ def dbsize(self):
+ """
+ >>> r = Redis(db=9)
+ >>> type(r.dbsize())
+ <type 'int'>
+ >>>
+ """
+ self.connect()
+ self._write('DBSIZE\r\n')
+ return self.get_response()
+
+ def ttl(self, name):
+ """
+ >>> r = Redis(db=9)
+ >>> r.ttl('a')
+ -1
+ >>> r.expire('a', 10)
+ 1
+ >>> r.ttl('a')
+ 10
+ >>> r.expire('a', 0)
+ 0
+ >>>
+ """
+ self.connect()
+ self._write('TTL %s\r\n' % name)
+ return self.get_response()
+
+ def expire(self, name, time):
+ """
+ >>> r = Redis(db=9)
+ >>> r.set('a', 1)
+ 'OK'
+ >>> r.expire('a', 1)
+ 1
+ >>> r.expire('zzzzz', 1)
+ 0
+ >>>
+ """
+ self.connect()
+ self._write('EXPIRE %s %s\r\n' % (name, time))
+ return self.get_response()
def push(self, name, value, tail=False):
"""
- >>> r = Redis()
+ >>> r = Redis(db=9)
>>> r.delete('l')
1
>>> r.push('l', 'a')
>>>
"""
self.connect()
- # same considerations on unicode as in set() apply here
- try:
- value = value if isinstance(value, basestring) else str(value)
- self._write('%s %s %s\r\n%s\r\n' % (
- 'LPUSH' if tail else 'RPUSH', name, len(value), value
- ))
- except UnicodeEncodeError, e:
- raise InvalidData("Error encoding unicode value for element in list '%s': %s." % (name, e))
+ value = self._encode(value)
+ self._write('%s %s %s\r\n%s\r\n' % (
+ 'LPUSH' if tail else 'RPUSH', name, len(value), value
+ ))
return self.get_response()
def llen(self, name):
"""
- >>> r = Redis()
+ >>> r = Redis(db=9)
>>> r.delete('l')
1
>>> r.push('l', 'a')
def lrange(self, name, start, end):
"""
- >>> r = Redis()
+ >>> r = Redis(db=9)
>>> r.delete('l')
1
>>> r.lrange('l', 0, 1)
>>> r.push('l', 'aaa')
'OK'
>>> r.lrange('l', 0, 1)
- ['aaa']
+ [u'aaa']
>>> r.push('l', 'bbb')
'OK'
>>> r.lrange('l', 0, 0)
- ['aaa']
+ [u'aaa']
>>> r.lrange('l', 0, 1)
- ['aaa', 'bbb']
+ [u'aaa', u'bbb']
>>> r.lrange('l', -1, 0)
[]
>>> r.lrange('l', -1, -1)
- ['bbb']
+ [u'bbb']
>>>
"""
self.connect()
def ltrim(self, name, start, end):
"""
- >>> r = Redis()
+ >>> r = Redis(db=9)
>>> r.delete('l')
1
>>> try:
def lindex(self, name, index):
"""
- >>> r = Redis()
+ >>> r = Redis(db=9)
>>> res = r.delete('l')
>>> r.lindex('l', 0)
>>> r.push('l', 'aaa')
'OK'
>>> r.lindex('l', 0)
- 'aaa'
+ u'aaa'
>>> r.lindex('l', 2)
>>> r.push('l', 'ccc')
'OK'
>>> r.lindex('l', 1)
- 'ccc'
+ u'ccc'
>>> r.lindex('l', -1)
- 'ccc'
+ u'ccc'
>>>
"""
self.connect()
def pop(self, name, tail=False):
"""
- >>> r = Redis()
+ >>> r = Redis(db=9)
>>> r.delete('l')
1
>>> r.pop('l')
>>> r.push('l', 'bbb')
'OK'
>>> r.pop('l')
- 'aaa'
+ u'aaa'
>>> r.pop('l')
- 'bbb'
+ u'bbb'
>>> r.pop('l')
>>> r.push('l', 'aaa')
'OK'
>>> r.push('l', 'bbb')
'OK'
>>> r.pop('l', tail=True)
- 'bbb'
+ u'bbb'
>>> r.pop('l')
- 'aaa'
+ u'aaa'
>>> r.pop('l')
>>>
"""
def lset(self, name, index, value):
"""
- >>> r = Redis()
+ >>> r = Redis(db=9)
>>> r.delete('l')
1
>>> try:
>>> r.lset('l', 0, 'bbb')
'OK'
>>> r.lrange('l', 0, 1)
- ['bbb']
+ [u'bbb']
>>>
"""
self.connect()
- try:
- value = value if isinstance(value, basestring) else str(value)
- self._write('LSET %s %s %s\r\n%s\r\n' % (
- name, index, len(value), value
- ))
- except UnicodeEncodeError, e:
- raise InvalidData("Error encoding unicode value for element %s in list '%s': %s." % (index, name, e))
+ value = self._encode(value)
+ self._write('LSET %s %s %s\r\n%s\r\n' % (
+ name, index, len(value), value
+ ))
return self.get_response()
def lrem(self, name, value, num=0):
"""
- >>> r = Redis()
+ >>> r = Redis(db=9)
>>> r.delete('l')
1
>>> r.push('l', 'aaa')
>>> r.lrem('l', 'aaa')
2
>>> r.lrange('l', 0, 10)
- ['bbb']
+ [u'bbb']
>>> r.push('l', 'aaa')
'OK'
>>> r.push('l', 'aaa')
>>>
"""
self.connect()
- try:
- value = value if isinstance(value, basestring) else str(value)
- self._write('LREM %s %s %s\r\n%s\r\n' % (
- name, num, len(value), value
- ))
- except UnicodeEncodeError, e:
- raise InvalidData("Error encoding unicode value for element %s in list '%s': %s." % (index, name, e))
+ value = self._encode(value)
+ self._write('LREM %s %s %s\r\n%s\r\n' % (
+ name, num, len(value), value
+ ))
return self.get_response()
def sort(self, name, by=None, get=None, start=None, num=None, desc=False, alpha=False):
"""
- >>> r = Redis()
+ >>> r = Redis(db=9)
>>> r.delete('l')
1
>>> r.push('l', 'ccc')
>>> r.push('l', 'bbb')
'OK'
>>> r.sort('l', alpha=True)
- ['aaa', 'bbb', 'ccc', 'ddd']
+ [u'aaa', u'bbb', u'ccc', u'ddd']
>>> r.delete('l')
1
>>> for i in range(1, 5):
... res = r.push('l', 1.0 / i)
>>> r.sort('l')
- ['0.25', '0.333333333333', '0.5', '1.0']
+ [Decimal("0.25"), Decimal("0.333333333333"), Decimal("0.5"), Decimal("1.0")]
>>> r.sort('l', desc=True)
- ['1.0', '0.5', '0.333333333333', '0.25']
+ [Decimal("1.0"), Decimal("0.5"), Decimal("0.333333333333"), Decimal("0.25")]
>>> r.sort('l', desc=True, start=2, num=1)
- ['0.333333333333']
+ [Decimal("0.333333333333")]
>>> r.set('weight_0.5', 10)
'OK'
>>> r.sort('l', desc=True, by='weight_*')
- ['0.5', '1.0', '0.333333333333', '0.25']
+ [Decimal("0.5"), Decimal("1.0"), Decimal("0.333333333333"), Decimal("0.25")]
>>> for i in r.sort('l', desc=True):
... res = r.set('test_%s' % i, 100 - float(i))
>>> r.sort('l', desc=True, get='test_*')
- ['99.0', '99.5', '99.6666666667', '99.75']
+ [Decimal("99.0"), Decimal("99.5"), Decimal("99.6666666667"), Decimal("99.75")]
>>> r.sort('l', desc=True, by='weight_*', get='test_*')
- ['99.5', '99.0', '99.6666666667', '99.75']
+ [Decimal("99.5"), Decimal("99.0"), Decimal("99.6666666667"), Decimal("99.75")]
>>> r.sort('l', desc=True, by='weight_*', get='missing_*')
[None, None, None, None]
>>>
def sadd(self, name, value):
"""
- >>> r = Redis()
+ >>> r = Redis(db=9)
>>> res = r.delete('s')
>>> r.sadd('s', 'a')
1
>>>
"""
self.connect()
- # same considerations on unicode as in set() apply here
- try:
- value = value if isinstance(value, basestring) else str(value)
- self._write('SADD %s %s\r\n%s\r\n' % (
- name, len(value), value
- ))
- except UnicodeEncodeError, e:
- raise InvalidData("Error encoding unicode value for element in set '%s': %s." % (name, e))
+ value = self._encode(value)
+ self._write('SADD %s %s\r\n%s\r\n' % (
+ name, len(value), value
+ ))
return self.get_response()
def srem(self, name, value):
"""
- >>> r = Redis()
+ >>> r = Redis(db=9)
>>> r.delete('s')
1
>>> r.srem('s', 'aaa')
>>>
"""
self.connect()
- # same considerations on unicode as in set() apply here
- try:
- value = value if isinstance(value, basestring) else str(value)
- self._write('SREM %s %s\r\n%s\r\n' % (
- name, len(value), value
- ))
- except UnicodeEncodeError, e:
- raise InvalidData("Error encoding unicode value for element in set '%s': %s." % (name, e))
+ value = self._encode(value)
+ self._write('SREM %s %s\r\n%s\r\n' % (
+ name, len(value), value
+ ))
return self.get_response()
def sismember(self, name, value):
"""
- >>> r = Redis()
+ >>> r = Redis(db=9)
>>> r.delete('s')
1
>>> r.sismember('s', 'b')
>>>
"""
self.connect()
- # same considerations on unicode as in set() apply here
- try:
- value = value if isinstance(value, basestring) else str(value)
- self._write('SISMEMBER %s %s\r\n%s\r\n' % (
- name, len(value), value
- ))
- except UnicodeEncodeError, e:
- raise InvalidData("Error encoding unicode value for element in set '%s': %s." % (name, e))
+ value = self._encode(value)
+ self._write('SISMEMBER %s %s\r\n%s\r\n' % (
+ name, len(value), value
+ ))
return self.get_response()
def sinter(self, *args):
"""
- >>> r = Redis()
+ >>> r = Redis(db=9)
>>> res = r.delete('s1')
>>> res = r.delete('s2')
>>> res = r.delete('s3')
>>> r.sinter('s1', 's2', 's3')
set([])
>>> r.sinter('s1', 's2')
- set(['a'])
+ set([u'a'])
>>>
"""
self.connect()
def sinterstore(self, dest, *args):
"""
- >>> r = Redis()
+ >>> r = Redis(db=9)
>>> res = r.delete('s1')
>>> res = r.delete('s2')
>>> res = r.delete('s3')
>>> r.sadd('s3', 'b')
1
>>> r.sinterstore('s_s', 's1', 's2', 's3')
- 'OK'
+ 0
>>> r.sinterstore('s_s', 's1', 's2')
- 'OK'
+ 1
>>> r.smembers('s_s')
- set(['a'])
+ set([u'a'])
>>>
"""
self.connect()
def smembers(self, name):
"""
- >>> r = Redis()
+ >>> r = Redis(db=9)
>>> r.delete('s')
1
>>> r.sadd('s', 'a')
... print e
Operation against a key holding the wrong kind of value
>>> r.smembers('s')
- set(['a', 'b'])
+ set([u'a', u'b'])
>>>
"""
self.connect()
self._write('SMEMBERS %s\r\n' % name)
return set(self.get_response())
+ def sunion(self, *args):
+ """
+ >>> r = Redis(db=9)
+ >>> res = r.delete('s1')
+ >>> res = r.delete('s2')
+ >>> res = r.delete('s3')
+ >>> r.sadd('s1', 'a')
+ 1
+ >>> r.sadd('s2', 'a')
+ 1
+ >>> r.sadd('s3', 'b')
+ 1
+ >>> r.sunion('s1', 's2', 's3')
+ set([u'a', u'b'])
+ >>> r.sadd('s2', 'c')
+ 1
+ >>> r.sunion('s1', 's2', 's3')
+ set([u'a', u'c', u'b'])
+ >>>
+ """
+ self.connect()
+ self._write('SUNION %s\r\n' % ' '.join(args))
+ return set(self.get_response())
+
+ def sunionstore(self, dest, *args):
+ """
+ >>> r = Redis(db=9)
+ >>> res = r.delete('s1')
+ >>> res = r.delete('s2')
+ >>> res = r.delete('s3')
+ >>> r.sadd('s1', 'a')
+ 1
+ >>> r.sadd('s2', 'a')
+ 1
+ >>> r.sadd('s3', 'b')
+ 1
+ >>> r.sunionstore('s4', 's1', 's2', 's3')
+ 2
+ >>> r.smembers('s4')
+ set([u'a', u'b'])
+ >>>
+ """
+ self.connect()
+ self._write('SUNIONSTORE %s %s\r\n' % (dest, ' '.join(args)))
+ return self.get_response()
+
def select(self, db):
"""
- >>> r = Redis()
+ >>> r = Redis(db=9)
>>> r.delete('a')
1
- >>> r.select(1)
+ >>> r.select(10)
'OK'
>>> r.set('a', 1)
'OK'
- >>> r.select(0)
+ >>> r.select(9)
'OK'
>>> r.get('a')
>>>
def move(self, name, db):
"""
- >>> r = Redis()
- >>> r.select(0)
- 'OK'
+ >>> r = Redis(db=9)
>>> r.set('a', 'a')
'OK'
- >>> r.select(1)
+ >>> r.select(10)
'OK'
>>> if r.get('a'):
... r.delete('a')
... else:
... print 1
1
- >>> r.select(0)
+ >>> r.select(9)
'OK'
- >>> r.move('a', 1)
+ >>> r.move('a', 10)
1
>>> r.get('a')
- >>> r.select(1)
+ >>> r.select(10)
'OK'
>>> r.get('a')
- 'a'
- >>> r.select(0)
+ u'a'
+ >>> r.select(9)
'OK'
>>>
"""
def save(self, background=False):
"""
- >>> r = Redis()
+ >>> r = Redis(db=9)
>>> r.save()
'OK'
>>> try:
def lastsave(self):
"""
>>> import time
- >>> r = Redis()
+ >>> r = Redis(db=9)
>>> t = int(time.time())
>>> r.save()
'OK'
def flush(self, all_dbs=False):
"""
- >>> r = Redis()
+ >>> r = Redis(db=9)
>>> r.flush()
'OK'
- >>> r.flush(all_dbs=True)
- 'OK'
+ >>> # r.flush(all_dbs=True)
>>>
"""
self.connect()
def info(self):
"""
- >>> r = Redis()
+ >>> r = Redis(db=9)
>>> info = r.info()
>>> info and isinstance(info, dict)
True
info[k] = int(v) if v.isdigit() else v
return info
+ def auth(self, passwd):
+ self.connect()
+ self._write('AUTH %s\r\n' % passwd)
+ return self.get_response()
+
def get_response(self):
data = self._read().strip()
+ if not data:
+ self.disconnect()
+ raise ConnectionError("Socket closed on remote end")
c = data[0]
if c == '-':
raise ResponseError(data[5:] if data[:5] == '-ERR ' else data[1:])
if c != '$':
raise InvalidResponse("Unkown response prefix for '%s'" % data)
buf = []
- while i > 0:
+ while True:
data = self._read()
i -= len(data)
- if len(data) > i:
- # we got the ending crlf
- data = data.rstrip()
buf.append(data)
- if i == 0:
- # the data has a trailing crlf embedded, let's restore it
- buf.append(self._read())
- return ''.join(buf)
+ if i < 0:
+ break
+ data = ''.join(buf)[:-2]
+ try:
+ return int(data) if data.find('.') == -1 else decimal.Decimal(data)
+ except (ValueError, decimal.InvalidOperation):
+ return data.decode(self.charset)
def disconnect(self):
if isinstance(self._sock, socket.socket):
def connect(self):
"""
- >>> r = Redis()
+ >>> r = Redis(db=9)
>>> r.connect()
>>> isinstance(r._sock, socket.socket)
True
+ >>> r.disconnect()
>>>
"""
if isinstance(self._sock, socket.socket):
else:
self._sock = sock
self._fp = self._sock.makefile('r')
-
+ if self.db:
+ self.select(self.db)
+ if self.nodelay is not None:
+ self._sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, self.nodelay)
+
if __name__ == '__main__':
import doctest
doctest.testmod()
-
\ No newline at end of file
+