3 """ redis.py - A client for the Redis daemon.
7 - 20090603 fix missing errno import, add sunion and sunionstore commands,
8 generalize shebang (Jochen Kupperschmidt)
12 __author__
= "Ludovico Magnocavallo <ludo\x40qix\x2eit>"
13 __copyright__
= "Copyright 2009, Ludovico Magnocavallo"
16 __revision__
= "$LastChangedRevision: 175 $"[22:-2]
17 __date__
= "$LastChangedDate: 2009-03-17 16:15:55 +0100 (Mar, 17 Mar 2009) $"[18:-2]
20 # TODO: Redis._get_multi_response
31 class RedisError(Exception): pass
32 class ConnectionError(RedisError
): pass
33 class ResponseError(RedisError
): pass
34 class InvalidResponse(RedisError
): pass
35 class InvalidData(RedisError
): pass
39 """The main Redis client.
42 def __init__(self
, host
=None, port
=None, timeout
=None, db
=None, nodelay
=None, charset
='utf8', errors
='strict'):
43 self
.host
= host
or 'localhost'
44 self
.port
= port
or 6379
46 socket
.setdefaulttimeout(timeout
)
47 self
.nodelay
= nodelay
48 self
.charset
= charset
55 if isinstance(s
, str):
57 if isinstance(s
, unicode):
59 return s
.encode(self
.charset
, self
.errors
)
60 except UnicodeEncodeError, e
:
61 raise InvalidData("Error encoding unicode value '%s': %s" % (value
.encode(self
.charset
, 'replace'), e
))
71 ... except ConnectionError, e:
73 Error 9 while writing to socket. Bad file descriptor.
79 except socket
.error
, e
:
83 raise ConnectionError("Error %s while writing to socket. %s." % tuple(e
.args
))
87 return self
._fp
.readline()
88 except socket
.error
, e
:
89 if e
.args
and e
.args
[0] == errno
.EAGAIN
:
92 raise ConnectionError("Error %s while reading from socket. %s." % tuple(e
.args
))
95 raise ConnectionError("Socket connection closed when reading.")
106 self
._write
('PING\r\n')
107 return self
.get_response()
109 def set(self
, name
, value
, preserve
=False, getset
=False):
112 >>> r.set('a', 'pippo')
114 >>> r.set('a', u'pippo \u3235')
118 >>> r.set('b', 105.2)
120 >>> r.set('b', 'xxx', preserve=True)
127 # the following will raise an error for unicode values that can't be encoded to ascii
128 # we could probably add an 'encoding' arg to init, but then what do we do with get()?
129 # convert back to unicode? and what about ints, or pickled values?
130 if getset
: command
= 'GETSET'
131 elif preserve
: command
= 'SETNX'
132 else: command
= 'SET'
133 value
= self
._encode
(value
)
134 self
._write
('%s %s %s\r\n%s\r\n' % (
135 command
, name
, len(value
), value
137 return self
.get_response()
142 >>> r.set('a', 'pippo'), r.set('b', 15), r.set('c', ' \\r\\naaa\\nbbb\\r\\ncccc\\nddd\\r\\n '), r.set('d', '\\r\\n')
143 ('OK', 'OK', 'OK', 'OK')
153 u' \\r\\naaa\\nbbb\\r\\ncccc\\nddd\\r\\n '
155 u' \\r\\naaa\\nbbb\\r\\ncccc\\nddd\\r\\n '
160 self
._write
('GET %s\r\n' % name
)
161 return self
.get_response()
163 def getset(self
, name
, value
):
166 >>> r.set('a', 'pippo')
172 return self
.set(name
, value
, getset
=True)
174 def mget(self
, *args
):
177 >>> r.set('a', 'pippo'), r.set('b', 15), r.set('c', '\\r\\naaa\\nbbb\\r\\ncccc\\nddd\\r\\n'), r.set('d', '\\r\\n')
178 ('OK', 'OK', 'OK', 'OK')
179 >>> r.mget('a', 'b', 'c', 'd')
180 [u'pippo', 15, u'\\r\\naaa\\nbbb\\r\\ncccc\\nddd\\r\\n', u'\\r\\n']
184 self
._write
('MGET %s\r\n' % ' '.join(args
))
185 return self
.get_response()
187 def incr(self
, name
, amount
=1):
202 self
._write
('INCR %s\r\n' % name
)
204 self
._write
('INCRBY %s %s\r\n' % (name
, amount
))
205 return self
.get_response()
207 def decr(self
, name
, amount
=1):
225 self
._write
('DECR %s\r\n' % name
)
227 self
._write
('DECRBY %s %s\r\n' % (name
, amount
))
228 return self
.get_response()
230 def exists(self
, name
):
233 >>> r.exists('dsjhfksjdhfkdsjfh')
242 self
._write
('EXISTS %s\r\n' % name
)
243 return self
.get_response()
245 def delete(self
, name
):
248 >>> r.delete('dsjhfksjdhfkdsjfh')
261 self
._write
('DEL %s\r\n' % name
)
262 return self
.get_response()
264 def get_type(self
, name
):
271 >>> r.get_type('zzz')
275 self
._write
('TYPE %s\r\n' % name
)
276 res
= self
.get_response()
277 return None if res
== 'none' else res
279 def keys(self
, pattern
):
294 >>> r.keys('sjdfhskjh*')
299 self
._write
('KEYS %s\r\n' % pattern
)
300 return self
.get_response().split()
307 >>> isinstance(r.randomkey(), str)
311 #raise NotImplementedError("Implemented but buggy, do not use.")
313 self
._write
('RANDOMKEY\r\n')
314 return self
.get_response()
316 def rename(self
, src
, dst
, preserve
=False):
320 ... r.rename('a', 'a')
321 ... except ResponseError, e:
323 source and destination objects are the same
324 >>> r.rename('a', 'b')
327 ... r.rename('a', 'b')
328 ... except ResponseError, e:
333 >>> r.rename('b', 'a', preserve=True)
339 self
._write
('RENAMENX %s %s\r\n' % (src
, dst
))
340 return self
.get_response()
342 self
._write
('RENAME %s %s\r\n' % (src
, dst
))
343 return self
.get_response() #.strip()
353 self
._write
('DBSIZE\r\n')
354 return self
.get_response()
361 >>> r.expire('a', 10)
370 self
._write
('TTL %s\r\n' % name
)
371 return self
.get_response()
373 def expire(self
, name
, time
):
380 >>> r.expire('zzzzz', 1)
385 self
._write
('EXPIRE %s %s\r\n' % (name
, time
))
386 return self
.get_response()
388 def push(self
, name
, value
, tail
=False):
399 ... except ResponseError, e:
401 Operation against a key holding the wrong kind of value
405 value
= self
._encode
(value
)
406 self
._write
('%s %s %s\r\n%s\r\n' % (
407 'LPUSH' if tail
else 'RPUSH', name
, len(value
), value
409 return self
.get_response()
411 def llen(self
, name
):
427 self
._write
('LLEN %s\r\n' % name
)
428 return self
.get_response()
430 def lrange(self
, name
, start
, end
):
435 >>> r.lrange('l', 0, 1)
437 >>> r.push('l', 'aaa')
439 >>> r.lrange('l', 0, 1)
441 >>> r.push('l', 'bbb')
443 >>> r.lrange('l', 0, 0)
445 >>> r.lrange('l', 0, 1)
447 >>> r.lrange('l', -1, 0)
449 >>> r.lrange('l', -1, -1)
454 self
._write
('LRANGE %s %s %s\r\n' % (name
, start
, end
))
455 return self
.get_response()
457 def ltrim(self
, name
, start
, end
):
463 ... r.ltrim('l', 0, 1)
464 ... except ResponseError, e:
467 >>> r.push('l', 'aaa')
469 >>> r.push('l', 'bbb')
471 >>> r.push('l', 'ccc')
473 >>> r.ltrim('l', 0, 1)
477 >>> r.ltrim('l', 99, 95)
484 self
._write
('LTRIM %s %s %s\r\n' % (name
, start
, end
))
485 return self
.get_response()
487 def lindex(self
, name
, index
):
490 >>> res = r.delete('l')
492 >>> r.push('l', 'aaa')
497 >>> r.push('l', 'ccc')
501 >>> r.lindex('l', -1)
506 self
._write
('LINDEX %s %s\r\n' % (name
, index
))
507 return self
.get_response()
509 def pop(self
, name
, tail
=False):
515 >>> r.push('l', 'aaa')
517 >>> r.push('l', 'bbb')
524 >>> r.push('l', 'aaa')
526 >>> r.push('l', 'bbb')
528 >>> r.pop('l', tail=True)
536 self
._write
('%s %s\r\n' % ('RPOP' if tail
else 'LPOP', name
))
537 return self
.get_response()
539 def lset(self
, name
, index
, value
):
545 ... r.lset('l', 0, 'a')
546 ... except ResponseError, e:
549 >>> r.push('l', 'aaa')
552 ... r.lset('l', 1, 'a')
553 ... except ResponseError, e:
556 >>> r.lset('l', 0, 'bbb')
558 >>> r.lrange('l', 0, 1)
563 value
= self
._encode
(value
)
564 self
._write
('LSET %s %s %s\r\n%s\r\n' % (
565 name
, index
, len(value
), value
567 return self
.get_response()
569 def lrem(self
, name
, value
, num
=0):
574 >>> r.push('l', 'aaa')
576 >>> r.push('l', 'bbb')
578 >>> r.push('l', 'aaa')
580 >>> r.lrem('l', 'aaa')
582 >>> r.lrange('l', 0, 10)
584 >>> r.push('l', 'aaa')
586 >>> r.push('l', 'aaa')
588 >>> r.lrem('l', 'aaa', 1)
590 >>> r.lrem('l', 'aaa', 1)
592 >>> r.lrem('l', 'aaa', 1)
597 value
= self
._encode
(value
)
598 self
._write
('LREM %s %s %s\r\n%s\r\n' % (
599 name
, num
, len(value
), value
601 return self
.get_response()
603 def sort(self
, name
, by
=None, get
=None, start
=None, num
=None, desc
=False, alpha
=False):
608 >>> r.push('l', 'ccc')
610 >>> r.push('l', 'aaa')
612 >>> r.push('l', 'ddd')
614 >>> r.push('l', 'bbb')
616 >>> r.sort('l', alpha=True)
617 [u'aaa', u'bbb', u'ccc', u'ddd']
620 >>> for i in range(1, 5):
621 ... res = r.push('l', 1.0 / i)
623 [Decimal("0.25"), Decimal("0.333333333333"), Decimal("0.5"), Decimal("1.0")]
624 >>> r.sort('l', desc=True)
625 [Decimal("1.0"), Decimal("0.5"), Decimal("0.333333333333"), Decimal("0.25")]
626 >>> r.sort('l', desc=True, start=2, num=1)
627 [Decimal("0.333333333333")]
628 >>> r.set('weight_0.5', 10)
630 >>> r.sort('l', desc=True, by='weight_*')
631 [Decimal("0.5"), Decimal("1.0"), Decimal("0.333333333333"), Decimal("0.25")]
632 >>> for i in r.sort('l', desc=True):
633 ... res = r.set('test_%s' % i, 100 - float(i))
634 >>> r.sort('l', desc=True, get='test_*')
635 [Decimal("99.0"), Decimal("99.5"), Decimal("99.6666666667"), Decimal("99.75")]
636 >>> r.sort('l', desc=True, by='weight_*', get='test_*')
637 [Decimal("99.5"), Decimal("99.0"), Decimal("99.6666666667"), Decimal("99.75")]
638 >>> r.sort('l', desc=True, by='weight_*', get='missing_*')
639 [None, None, None, None]
642 stmt
= ['SORT', name
]
644 stmt
.append("BY %s" % by
)
646 stmt
.append("LIMIT %s %s" % (start
, num
))
649 elif isinstance(get
, basestring
):
650 stmt
.append("GET %s" % get
)
651 elif isinstance(get
, list) or isinstance(get
, tuple):
653 stmt
.append("GET %s" % g
)
655 raise RedisError("Invalid parameter 'get' for Redis sort")
661 self
._write
(' '.join(stmt
+ ["\r\n"]))
662 return self
.get_response()
664 def sadd(self
, name
, value
):
667 >>> res = r.delete('s')
675 value
= self
._encode
(value
)
676 self
._write
('SADD %s %s\r\n%s\r\n' % (
677 name
, len(value
), value
679 return self
.get_response()
681 def srem(self
, name
, value
):
686 >>> r.srem('s', 'aaa')
692 >>> r.sismember('s', 'b')
697 value
= self
._encode
(value
)
698 self
._write
('SREM %s %s\r\n%s\r\n' % (
699 name
, len(value
), value
701 return self
.get_response()
703 def sismember(self
, name
, value
):
708 >>> r.sismember('s', 'b')
712 >>> r.sismember('s', 'b')
714 >>> r.sismember('s', 'a')
719 value
= self
._encode
(value
)
720 self
._write
('SISMEMBER %s %s\r\n%s\r\n' % (
721 name
, len(value
), value
723 return self
.get_response()
725 def sinter(self
, *args
):
728 >>> res = r.delete('s1')
729 >>> res = r.delete('s2')
730 >>> res = r.delete('s3')
731 >>> r.sadd('s1', 'a')
733 >>> r.sadd('s2', 'a')
735 >>> r.sadd('s3', 'b')
739 ... except ResponseError, e:
741 wrong number of arguments
744 ... except ResponseError, e:
746 Operation against a key holding the wrong kind of value
747 >>> r.sinter('s1', 's2', 's3')
749 >>> r.sinter('s1', 's2')
754 self
._write
('SINTER %s\r\n' % ' '.join(args
))
755 return set(self
.get_response())
757 def sinterstore(self
, dest
, *args
):
760 >>> res = r.delete('s1')
761 >>> res = r.delete('s2')
762 >>> res = r.delete('s3')
763 >>> r.sadd('s1', 'a')
765 >>> r.sadd('s2', 'a')
767 >>> r.sadd('s3', 'b')
769 >>> r.sinterstore('s_s', 's1', 's2', 's3')
771 >>> r.sinterstore('s_s', 's1', 's2')
773 >>> r.smembers('s_s')
778 self
._write
('SINTERSTORE %s %s\r\n' % (dest
, ' '.join(args
)))
779 return self
.get_response()
781 def smembers(self
, name
):
792 ... except ResponseError, e:
794 Operation against a key holding the wrong kind of value
800 self
._write
('SMEMBERS %s\r\n' % name
)
801 return set(self
.get_response())
803 def sunion(self
, *args
):
806 >>> res = r.delete('s1')
807 >>> res = r.delete('s2')
808 >>> res = r.delete('s3')
809 >>> r.sadd('s1', 'a')
811 >>> r.sadd('s2', 'a')
813 >>> r.sadd('s3', 'b')
815 >>> r.sunion('s1', 's2', 's3')
817 >>> r.sadd('s2', 'c')
819 >>> r.sunion('s1', 's2', 's3')
820 set([u'a', u'c', u'b'])
824 self
._write
('SUNION %s\r\n' % ' '.join(args
))
825 return set(self
.get_response())
827 def sunionstore(self
, dest
, *args
):
830 >>> res = r.delete('s1')
831 >>> res = r.delete('s2')
832 >>> res = r.delete('s3')
833 >>> r.sadd('s1', 'a')
835 >>> r.sadd('s2', 'a')
837 >>> r.sadd('s3', 'b')
839 >>> r.sunionstore('s4', 's1', 's2', 's3')
846 self
._write
('SUNIONSTORE %s %s\r\n' % (dest
, ' '.join(args
)))
847 return self
.get_response()
849 def select(self
, db
):
864 self
._write
('SELECT %s\r\n' % db
)
865 return self
.get_response()
867 def move(self
, name
, db
):
893 self
._write
('MOVE %s %s\r\n' % (name
, db
))
894 return self
.get_response()
896 def save(self
, background
=False):
902 ... resp = r.save(background=True)
903 ... except ResponseError, e:
904 ... assert str(e) == 'background save already in progress', str(e)
906 ... assert resp == 'OK'
911 self
._write
('BGSAVE\r\n')
913 self
._write
('SAVE\r\n')
914 return self
.get_response()
920 >>> t = int(time.time())
923 >>> r.lastsave() >= t
928 self
._write
('LASTSAVE\r\n')
929 return self
.get_response()
931 def flush(self
, all_dbs
=False):
936 >>> # r.flush(all_dbs=True)
940 self
._write
('%s\r\n' % ('FLUSHALL' if all_dbs
else 'FLUSHDB'))
941 return self
.get_response()
947 >>> info and isinstance(info, dict)
949 >>> isinstance(info.get('connected_clients'), int)
954 self
._write
('INFO\r\n')
956 for l
in self
.get_response().split('\r\n'):
959 k
, v
= l
.split(':', 1)
960 info
[k
] = int(v
) if v
.isdigit() else v
963 def auth(self
, passwd
):
965 self
._write
('AUTH %s\r\n' % passwd
)
966 return self
.get_response()
968 def get_response(self
):
969 data
= self
._read
().strip()
972 raise ConnectionError("Socket closed on remote end")
975 raise ResponseError(data
[5:] if data
[:5] == '-ERR ' else data
[1:])
981 except (TypeError, ValueError):
982 raise InvalidResponse("Cannot convert multi-response header '%s' to integer" % data
)
985 result
.append(self
._get
_value
())
987 return self
._get
_value
(data
)
989 def _get_value(self
, data
=None):
990 data
= data
or self
._read
().strip()
994 c
, i
= data
[0], (int(data
[1:]) if data
.find('.') == -1 else float(data
[1:]))
996 raise InvalidResponse("Cannot convert data '%s' to integer" % data
)
1000 raise InvalidResponse("Unkown response prefix for '%s'" % data
)
1008 data
= ''.join(buf
)[:-2]
1010 return int(data
) if data
.find('.') == -1 else decimal
.Decimal(data
)
1011 except (ValueError, decimal
.InvalidOperation
):
1012 return data
.decode(self
.charset
)
1014 def disconnect(self
):
1015 if isinstance(self
._sock
, socket
.socket
):
1018 except socket
.error
:
1027 >>> isinstance(r._sock, socket.socket)
1032 if isinstance(self
._sock
, socket
.socket
):
1035 sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
1036 sock
.connect((self
.host
, self
.port
))
1037 except socket
.error
, e
:
1038 raise ConnectionError("Error %s connecting to %s:%s. %s." % (e
.args
[0], self
.host
, self
.port
, e
.args
[1]))
1041 self
._fp
= self
._sock
.makefile('r')
1043 self
.select(self
.db
)
1044 if self
.nodelay
is not None:
1045 self
._sock
.setsockopt(socket
.SOL_TCP
, socket
.TCP_NODELAY
, self
.nodelay
)
1048 if __name__
== '__main__':