X-Git-Url: https://git.saurik.com/redis.git/blobdiff_plain/11cb1537835786275c57afa5461dbd71b5a710b2..be42428336aeb2059c6312cfeaac8d2bd23b53f1:/client-libraries/python/redis.py diff --git a/client-libraries/python/redis.py b/client-libraries/python/redis.py index 6187cf9d..f0e4cd45 100644 --- a/client-libraries/python/redis.py +++ b/client-libraries/python/redis.py @@ -16,6 +16,7 @@ __date__ = "$LastChangedDate: 2009-03-17 16:15:55 +0100 (Mar, 17 Mar 2009) $"[18 import socket +import decimal BUFSIZE = 4096 @@ -32,17 +33,31 @@ class Redis(object): """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: @@ -76,7 +91,7 @@ class Redis(object): def ping(self): """ - >>> r = Redis() + >>> r = Redis(db=9) >>> r.ping() 'PONG' >>> @@ -85,54 +100,53 @@ class Redis(object): 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') >>> """ @@ -140,13 +154,24 @@ class Redis(object): 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() @@ -155,7 +180,7 @@ class Redis(object): def incr(self, name, amount=1): """ - >>> r = Redis() + >>> r = Redis(db=9) >>> r.delete('a') 1 >>> r.incr('a') @@ -175,7 +200,7 @@ class Redis(object): def decr(self, name, amount=1): """ - >>> r = Redis() + >>> r = Redis(db=9) >>> if r.get('a'): ... r.delete('a') ... else: @@ -198,7 +223,7 @@ class Redis(object): def exists(self, name): """ - >>> r = Redis() + >>> r = Redis(db=9) >>> r.exists('dsjhfksjdhfkdsjfh') 0 >>> r.set('a', 'a') @@ -213,7 +238,7 @@ class Redis(object): def delete(self, name): """ - >>> r = Redis() + >>> r = Redis(db=9) >>> r.delete('dsjhfksjdhfkdsjfh') 0 >>> r.set('a', 'a') @@ -230,27 +255,34 @@ class Redis(object): 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*') @@ -263,7 +295,7 @@ class Redis(object): def randomkey(self): """ - >>> r = Redis() + >>> r = Redis(db=9) >>> r.set('a', 'a') 'OK' >>> isinstance(r.randomkey(), str) @@ -277,7 +309,7 @@ class Redis(object): def rename(self, src, dst, preserve=False): """ - >>> r = Redis() + >>> r = Redis(db=9) >>> try: ... r.rename('a', 'a') ... except ResponseError, e: @@ -302,11 +334,54 @@ class Redis(object): 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()) + + >>> + """ + 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') @@ -321,19 +396,15 @@ class Redis(object): >>> """ 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') @@ -352,7 +423,7 @@ class Redis(object): def lrange(self, name, start, end): """ - >>> r = Redis() + >>> r = Redis(db=9) >>> r.delete('l') 1 >>> r.lrange('l', 0, 1) @@ -360,17 +431,17 @@ class Redis(object): >>> 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() @@ -379,7 +450,7 @@ class Redis(object): def ltrim(self, name, start, end): """ - >>> r = Redis() + >>> r = Redis(db=9) >>> r.delete('l') 1 >>> try: @@ -409,20 +480,20 @@ class Redis(object): 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() @@ -431,7 +502,7 @@ class Redis(object): def pop(self, name, tail=False): """ - >>> r = Redis() + >>> r = Redis(db=9) >>> r.delete('l') 1 >>> r.pop('l') @@ -440,18 +511,18 @@ class Redis(object): >>> 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') >>> """ @@ -461,7 +532,7 @@ class Redis(object): def lset(self, name, index, value): """ - >>> r = Redis() + >>> r = Redis(db=9) >>> r.delete('l') 1 >>> try: @@ -479,22 +550,19 @@ class Redis(object): >>> 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') @@ -506,7 +574,7 @@ class Redis(object): >>> r.lrem('l', 'aaa') 2 >>> r.lrange('l', 0, 10) - ['bbb'] + [u'bbb'] >>> r.push('l', 'aaa') 'OK' >>> r.push('l', 'aaa') @@ -520,18 +588,15 @@ class Redis(object): >>> """ 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') @@ -543,27 +608,27 @@ class Redis(object): >>> 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] >>> @@ -592,7 +657,7 @@ class Redis(object): def sadd(self, name, value): """ - >>> r = Redis() + >>> r = Redis(db=9) >>> res = r.delete('s') >>> r.sadd('s', 'a') 1 @@ -601,19 +666,15 @@ class Redis(object): >>> """ 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') @@ -627,19 +688,15 @@ class Redis(object): >>> """ 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') @@ -653,19 +710,15 @@ class Redis(object): >>> """ 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') @@ -688,7 +741,7 @@ class Redis(object): >>> r.sinter('s1', 's2', 's3') set([]) >>> r.sinter('s1', 's2') - set(['a']) + set([u'a']) >>> """ self.connect() @@ -697,7 +750,7 @@ class Redis(object): def sinterstore(self, dest, *args): """ - >>> r = Redis() + >>> r = Redis(db=9) >>> res = r.delete('s1') >>> res = r.delete('s2') >>> res = r.delete('s3') @@ -708,11 +761,11 @@ class Redis(object): >>> 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() @@ -721,7 +774,7 @@ class Redis(object): def smembers(self, name): """ - >>> r = Redis() + >>> r = Redis(db=9) >>> r.delete('s') 1 >>> r.sadd('s', 'a') @@ -734,7 +787,7 @@ class Redis(object): ... 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() @@ -743,14 +796,14 @@ class Redis(object): 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') >>> @@ -761,28 +814,26 @@ class Redis(object): 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' >>> """ @@ -792,7 +843,7 @@ class Redis(object): def save(self, background=False): """ - >>> r = Redis() + >>> r = Redis(db=9) >>> r.save() 'OK' >>> try: @@ -813,7 +864,7 @@ class Redis(object): def lastsave(self): """ >>> import time - >>> r = Redis() + >>> r = Redis(db=9) >>> t = int(time.time()) >>> r.save() 'OK' @@ -827,11 +878,10 @@ class Redis(object): 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() @@ -840,7 +890,7 @@ class Redis(object): def info(self): """ - >>> r = Redis() + >>> r = Redis(db=9) >>> info = r.info() >>> info and isinstance(info, dict) True @@ -858,8 +908,16 @@ class Redis(object): 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:]) @@ -889,17 +947,17 @@ class Redis(object): 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): @@ -912,10 +970,11 @@ class Redis(object): 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): @@ -928,9 +987,13 @@ class Redis(object): 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 +