]> git.saurik.com Git - redis.git/blob - client-libraries/python/redis.py
protocol fix in SORT reply with null elements
[redis.git] / client-libraries / python / redis.py
1 #!/usr/bin/python
2
3 """ redis.py - A client for the Redis daemon.
4
5 """
6
7 __author__ = "Ludovico Magnocavallo <ludo\x40qix\x2eit>"
8 __copyright__ = "Copyright 2009, Ludovico Magnocavallo"
9 __license__ = "MIT"
10 __version__ = "0.5"
11 __revision__ = "$LastChangedRevision: 175 $"[22:-2]
12 __date__ = "$LastChangedDate: 2009-03-17 16:15:55 +0100 (Mar, 17 Mar 2009) $"[18:-2]
13
14
15 # TODO: Redis._get_multi_response
16
17
18 import socket
19
20
21 BUFSIZE = 4096
22
23
24 class RedisError(Exception): pass
25 class ConnectionError(RedisError): pass
26 class ResponseError(RedisError): pass
27 class InvalidResponse(RedisError): pass
28 class InvalidData(RedisError): pass
29
30
31 class Redis(object):
32 """The main Redis client.
33 """
34
35 def __init__(self, host=None, port=None, timeout=None):
36 self.host = host or 'localhost'
37 self.port = port or 6379
38 if timeout:
39 socket.setdefaulttimeout(timeout)
40 self._sock = None
41 self._fp = None
42
43 def _write(self, s):
44 """
45 >>> r = Redis()
46 >>> r.connect()
47 >>> r._sock.close()
48 >>> try:
49 ... r._write('pippo')
50 ... except ConnectionError, e:
51 ... print e
52 Error 9 while writing to socket. Bad file descriptor.
53 >>>
54 >>>
55 """
56 try:
57 self._sock.sendall(s)
58 except socket.error, e:
59 if e.args[0] == 32:
60 # broken pipe
61 self.disconnect()
62 raise ConnectionError("Error %s while writing to socket. %s." % tuple(e.args))
63
64 def _read(self):
65 try:
66 return self._fp.readline()
67 except socket.error, e:
68 if e.args and e.args[0] == errno.EAGAIN:
69 return
70 self.disconnect()
71 raise ConnectionError("Error %s while reading from socket. %s." % tuple(e.args))
72 if not data:
73 self.disconnect()
74 raise ConnectionError("Socket connection closed when reading.")
75 return data
76
77 def ping(self):
78 """
79 >>> r = Redis()
80 >>> r.ping()
81 'PONG'
82 >>>
83 """
84 self.connect()
85 self._write('PING\r\n')
86 return self._get_simple_response()
87
88 def set(self, name, value, preserve=False):
89 """
90 >>> r = Redis()
91 >>> r.set('a', 'pippo')
92 'OK'
93 >>> try:
94 ... r.set('a', u'pippo \u3235')
95 ... except InvalidData, e:
96 ... print e
97 Error encoding unicode value for key 'a': 'ascii' codec can't encode character u'\u3235' in position 15: ordinal not in range(128).
98 >>> r.set('b', 105.2)
99 'OK'
100 >>> r.set('b', 'xxx', preserve=True)
101 0
102 >>> r.get('b')
103 '105.2'
104 >>>
105 """
106 self.connect()
107 # the following will raise an error for unicode values that can't be encoded to ascii
108 # we could probably add an 'encoding' arg to init, but then what do we do with get()?
109 # convert back to unicode? and what about ints, or pickled values?
110 try:
111 value = value if isinstance(value, basestring) else str(value)
112 self._write('%s %s %s\r\n%s\r\n' % (
113 'SETNX' if preserve else 'SET', name, len(value), value
114 ))
115 except UnicodeEncodeError, e:
116 raise InvalidData("Error encoding unicode value for key '%s': %s." % (name, e))
117 return self._get_numeric_response() if preserve else self._get_simple_response()
118
119 def get(self, name):
120 """
121 >>> r = Redis()
122 >>> r.set('a', 'pippo'), r.set('b', 15), r.set('c', '\\r\\naaa\\nbbb\\r\\ncccc\\nddd\\r\\n'), r.set('d', '\\r\\n')
123 ('OK', 'OK', 'OK', 'OK')
124 >>> r.get('a')
125 'pippo'
126 >>> r.get('b')
127 '15'
128 >>> r.get('d')
129 '\\r\\n'
130 >>> r.get('b')
131 '15'
132 >>> r.get('c')
133 '\\r\\naaa\\nbbb\\r\\ncccc\\nddd\\r\\n'
134 >>> r.get('c')
135 '\\r\\naaa\\nbbb\\r\\ncccc\\nddd\\r\\n'
136 >>> r.get('ajhsd')
137 >>>
138 """
139 self.connect()
140 self._write('GET %s\r\n' % name)
141 return self._get_value()
142
143 def mget(self, *args):
144 """
145 >>> r = Redis()
146 >>> r.set('a', 'pippo'), r.set('b', 15), r.set('c', '\\r\\naaa\\nbbb\\r\\ncccc\\nddd\\r\\n'), r.set('d', '\\r\\n')
147 ('OK', 'OK', 'OK', 'OK')
148 >>> r.mget('a', 'b', 'c', 'd')
149 ['pippo', '15', '\\r\\naaa\\nbbb\\r\\ncccc\\nddd\\r\\n', '\\r\\n']
150 >>>
151 """
152 self.connect()
153 self._write('MGET %s\r\n' % ' '.join(args))
154 return self._get_multi_response()
155
156 def incr(self, name, amount=1):
157 """
158 >>> r = Redis()
159 >>> r.delete('a')
160 1
161 >>> r.incr('a')
162 1
163 >>> r.incr('a')
164 2
165 >>> r.incr('a', 2)
166 4
167 >>>
168 """
169 self.connect()
170 if amount == 1:
171 self._write('INCR %s\r\n' % name)
172 else:
173 self._write('INCRBY %s %s\r\n' % (name, amount))
174 return self._get_numeric_response()
175
176 def decr(self, name, amount=1):
177 """
178 >>> r = Redis()
179 >>> if r.get('a'):
180 ... r.delete('a')
181 ... else:
182 ... print 1
183 1
184 >>> r.decr('a')
185 -1
186 >>> r.decr('a')
187 -2
188 >>> r.decr('a', 5)
189 -7
190 >>>
191 """
192 self.connect()
193 if amount == 1:
194 self._write('DECR %s\r\n' % name)
195 else:
196 self._write('DECRBY %s %s\r\n' % (name, amount))
197 return self._get_numeric_response()
198
199 def exists(self, name):
200 """
201 >>> r = Redis()
202 >>> r.exists('dsjhfksjdhfkdsjfh')
203 0
204 >>> r.set('a', 'a')
205 'OK'
206 >>> r.exists('a')
207 1
208 >>>
209 """
210 self.connect()
211 self._write('EXISTS %s\r\n' % name)
212 return self._get_numeric_response()
213
214 def delete(self, name):
215 """
216 >>> r = Redis()
217 >>> r.delete('dsjhfksjdhfkdsjfh')
218 0
219 >>> r.set('a', 'a')
220 'OK'
221 >>> r.delete('a')
222 1
223 >>> r.exists('a')
224 0
225 >>> r.delete('a')
226 0
227 >>>
228 """
229 self.connect()
230 self._write('DEL %s\r\n' % name)
231 return self._get_numeric_response()
232
233 def key_type(self, name):
234 """
235 Not yet implemented.
236 """
237 self.connect()
238 self._write('TYPE %s\r\n' % name)
239 return self._get_simple_response()
240
241 def keys(self, pattern):
242 """
243 >>> r = Redis()
244 >>> r.flush()
245 'OK'
246 >>> r.set('a', 'a')
247 'OK'
248 >>> r.keys('a*')
249 ['a']
250 >>> r.set('a2', 'a')
251 'OK'
252 >>> r.keys('a*')
253 ['a', 'a2']
254 >>> r.delete('a2')
255 1
256 >>> r.keys('sjdfhskjh*')
257 []
258 >>>
259 """
260 self.connect()
261 self._write('KEYS %s\r\n' % pattern)
262 return self._get_value().split()
263
264 def randomkey(self):
265 """
266 >>> r = Redis()
267 >>> r.set('a', 'a')
268 'OK'
269 >>> isinstance(r.randomkey(), str)
270 True
271 >>>
272 """
273 #raise NotImplementedError("Implemented but buggy, do not use.")
274 self.connect()
275 self._write('RANDOMKEY\r\n')
276 data = self._read().strip()
277 self._check_for_error(data)
278 return data
279
280 def rename(self, src, dst, preserve=False):
281 """
282 >>> r = Redis()
283 >>> try:
284 ... r.rename('a', 'a')
285 ... except ResponseError, e:
286 ... print e
287 src and dest key are the same
288 >>> r.rename('a', 'b')
289 'OK'
290 >>> try:
291 ... r.rename('a', 'b')
292 ... except ResponseError, e:
293 ... print e
294 no such key
295 >>> r.set('a', 1)
296 'OK'
297 >>> r.rename('b', 'a', preserve=True)
298 0
299 >>>
300 """
301 self.connect()
302 if preserve:
303 self._write('RENAMENX %s %s\r\n' % (src, dst))
304 return self._get_numeric_response()
305 else:
306 self._write('RENAME %s %s\r\n' % (src, dst))
307 return self._get_simple_response().strip()
308
309 def push(self, name, value, tail=False):
310 """
311 >>> r = Redis()
312 >>> r.delete('l')
313 1
314 >>> r.push('l', 'a')
315 'OK'
316 >>> r.set('a', 'a')
317 'OK'
318 >>> try:
319 ... r.push('a', 'a')
320 ... except ResponseError, e:
321 ... print e
322 Operation against a key holding the wrong kind of value
323 >>>
324 """
325 self.connect()
326 # same considerations on unicode as in set() apply here
327 try:
328 value = value if isinstance(value, basestring) else str(value)
329 self._write('%s %s %s\r\n%s\r\n' % (
330 'LPUSH' if tail else 'RPUSH', name, len(value), value
331 ))
332 except UnicodeEncodeError, e:
333 raise InvalidData("Error encoding unicode value for element in list '%s': %s." % (name, e))
334 return self._get_simple_response()
335
336 def llen(self, name):
337 """
338 >>> r = Redis()
339 >>> r.delete('l')
340 1
341 >>> r.push('l', 'a')
342 'OK'
343 >>> r.llen('l')
344 1
345 >>> r.push('l', 'a')
346 'OK'
347 >>> r.llen('l')
348 2
349 >>>
350 """
351 self.connect()
352 self._write('LLEN %s\r\n' % name)
353 return self._get_numeric_response()
354
355 def lrange(self, name, start, end):
356 """
357 >>> r = Redis()
358 >>> r.delete('l')
359 1
360 >>> r.lrange('l', 0, 1)
361 []
362 >>> r.push('l', 'aaa')
363 'OK'
364 >>> r.lrange('l', 0, 1)
365 ['aaa']
366 >>> r.push('l', 'bbb')
367 'OK'
368 >>> r.lrange('l', 0, 0)
369 ['aaa']
370 >>> r.lrange('l', 0, 1)
371 ['aaa', 'bbb']
372 >>> r.lrange('l', -1, 0)
373 []
374 >>> r.lrange('l', -1, -1)
375 ['bbb']
376 >>>
377 """
378 self.connect()
379 self._write('LRANGE %s %s %s\r\n' % (name, start, end))
380 return self._get_multi_response()
381
382 def ltrim(self, name, start, end):
383 """
384 >>> r = Redis()
385 >>> r.delete('l')
386 1
387 >>> try:
388 ... r.ltrim('l', 0, 1)
389 ... except ResponseError, e:
390 ... print e
391 no such key
392 >>> r.push('l', 'aaa')
393 'OK'
394 >>> r.push('l', 'bbb')
395 'OK'
396 >>> r.push('l', 'ccc')
397 'OK'
398 >>> r.ltrim('l', 0, 1)
399 'OK'
400 >>> r.llen('l')
401 2
402 >>> r.ltrim('l', 99, 95)
403 'OK'
404 >>> r.llen('l')
405 0
406 >>>
407 """
408 self.connect()
409 self._write('LTRIM %s %s %s\r\n' % (name, start, end))
410 return self._get_simple_response()
411
412 def lindex(self, name, index):
413 """
414 >>> r = Redis()
415 >>> res = r.delete('l')
416 >>> r.lindex('l', 0)
417 >>> r.push('l', 'aaa')
418 'OK'
419 >>> r.lindex('l', 0)
420 'aaa'
421 >>> r.lindex('l', 2)
422 >>> r.push('l', 'ccc')
423 'OK'
424 >>> r.lindex('l', 1)
425 'ccc'
426 >>> r.lindex('l', -1)
427 'ccc'
428 >>>
429 """
430 self.connect()
431 self._write('LINDEX %s %s\r\n' % (name, index))
432 return self._get_value()
433
434 def pop(self, name, tail=False):
435 """
436 >>> r = Redis()
437 >>> r.delete('l')
438 1
439 >>> r.pop('l')
440 >>> r.push('l', 'aaa')
441 'OK'
442 >>> r.push('l', 'bbb')
443 'OK'
444 >>> r.pop('l')
445 'aaa'
446 >>> r.pop('l')
447 'bbb'
448 >>> r.pop('l')
449 >>> r.push('l', 'aaa')
450 'OK'
451 >>> r.push('l', 'bbb')
452 'OK'
453 >>> r.pop('l', tail=True)
454 'bbb'
455 >>> r.pop('l')
456 'aaa'
457 >>> r.pop('l')
458 >>>
459 """
460 self.connect()
461 self._write('%s %s\r\n' % ('RPOP' if tail else 'LPOP', name))
462 return self._get_value()
463
464 def lset(self, name, index, value):
465 """
466 >>> r = Redis()
467 >>> r.delete('l')
468 1
469 >>> try:
470 ... r.lset('l', 0, 'a')
471 ... except ResponseError, e:
472 ... print e
473 no such key
474 >>> r.push('l', 'aaa')
475 'OK'
476 >>> try:
477 ... r.lset('l', 1, 'a')
478 ... except ResponseError, e:
479 ... print e
480 index out of range
481 >>> r.lset('l', 0, 'bbb')
482 'OK'
483 >>> r.lrange('l', 0, 1)
484 ['bbb']
485 >>>
486 """
487 self.connect()
488 try:
489 value = value if isinstance(value, basestring) else str(value)
490 self._write('LSET %s %s %s\r\n%s\r\n' % (
491 name, index, len(value), value
492 ))
493 except UnicodeEncodeError, e:
494 raise InvalidData("Error encoding unicode value for element %s in list '%s': %s." % (index, name, e))
495 return self._get_simple_response()
496
497 def lrem(self, name, value, num=0):
498 """
499 >>> r = Redis()
500 >>> r.delete('l')
501 1
502 >>> r.push('l', 'aaa')
503 'OK'
504 >>> r.push('l', 'bbb')
505 'OK'
506 >>> r.push('l', 'aaa')
507 'OK'
508 >>> r.lrem('l', 'aaa')
509 2
510 >>> r.lrange('l', 0, 10)
511 ['bbb']
512 >>> r.push('l', 'aaa')
513 'OK'
514 >>> r.push('l', 'aaa')
515 'OK'
516 >>> r.lrem('l', 'aaa', 1)
517 1
518 >>> r.lrem('l', 'aaa', 1)
519 1
520 >>> r.lrem('l', 'aaa', 1)
521 0
522 >>>
523 """
524 self.connect()
525 try:
526 value = value if isinstance(value, basestring) else str(value)
527 self._write('LREM %s %s %s\r\n%s\r\n' % (
528 name, num, len(value), value
529 ))
530 except UnicodeEncodeError, e:
531 raise InvalidData("Error encoding unicode value for element %s in list '%s': %s." % (index, name, e))
532 return self._get_numeric_response()
533
534 def sort(self, name, by=None, get=None, start=None, num=None, desc=False, alpha=False):
535 """
536 >>> r = Redis()
537 >>> r.delete('l')
538 1
539 >>> r.push('l', 'ccc')
540 'OK'
541 >>> r.push('l', 'aaa')
542 'OK'
543 >>> r.push('l', 'ddd')
544 'OK'
545 >>> r.push('l', 'bbb')
546 'OK'
547 >>> r.sort('l', alpha=True)
548 ['aaa', 'bbb', 'ccc', 'ddd']
549 >>> r.delete('l')
550 1
551 >>> for i in range(1, 5):
552 ... res = r.push('l', 1.0 / i)
553 >>> r.sort('l')
554 ['0.25', '0.333333333333', '0.5', '1.0']
555 >>> r.sort('l', desc=True)
556 ['1.0', '0.5', '0.333333333333', '0.25']
557 >>> r.sort('l', desc=True, start=2, num=1)
558 ['0.333333333333']
559 >>> r.set('weight_0.5', 10)
560 'OK'
561 >>> r.sort('l', desc=True, by='weight_*')
562 ['0.5', '1.0', '0.333333333333', '0.25']
563 >>> for i in r.sort('l', desc=True):
564 ... res = r.set('test_%s' % i, 100 - float(i))
565 >>> r.sort('l', desc=True, get='test_*')
566 ['99.0', '99.5', '99.6666666667', '99.75']
567 >>> r.sort('l', desc=True, by='weight_*', get='test_*')
568 ['99.5', '99.0', '99.6666666667', '99.75']
569 >>> r.sort('l', desc=True, by='weight_*', get='missing_*')
570 [None, None, None, None]
571 >>>
572 """
573 stmt = ['SORT', name]
574 if by:
575 stmt.append("BY %s" % by)
576 if start and num:
577 stmt.append("LIMIT %s %s" % (start, num))
578 if get is None:
579 pass
580 elif isinstance(get, basestring):
581 stmt.append("GET %s" % get)
582 elif isinstance(get, list) or isinstance(get, tuple):
583 for g in get:
584 stmt.append("GET %s" % g)
585 else:
586 raise RedisError("Invalid parameter 'get' for Redis sort")
587 if desc:
588 stmt.append("DESC")
589 if alpha:
590 stmt.append("ALPHA")
591 self.connect()
592 self._write(' '.join(stmt + ["\r\n"]))
593 return self._get_multi_response()
594
595 def sadd(self, name, value):
596 """
597 >>> r = Redis()
598 >>> res = r.delete('s')
599 >>> r.sadd('s', 'a')
600 1
601 >>> r.sadd('s', 'b')
602 1
603 >>>
604 """
605 self.connect()
606 # same considerations on unicode as in set() apply here
607 try:
608 value = value if isinstance(value, basestring) else str(value)
609 self._write('SADD %s %s\r\n%s\r\n' % (
610 name, len(value), value
611 ))
612 except UnicodeEncodeError, e:
613 raise InvalidData("Error encoding unicode value for element in set '%s': %s." % (name, e))
614 return self._get_numeric_response()
615
616 def srem(self, name, value):
617 """
618 >>> r = Redis()
619 >>> r.delete('s')
620 1
621 >>> r.srem('s', 'aaa')
622 0
623 >>> r.sadd('s', 'b')
624 1
625 >>> r.srem('s', 'b')
626 1
627 >>> r.sismember('s', 'b')
628 0
629 >>>
630 """
631 self.connect()
632 # same considerations on unicode as in set() apply here
633 try:
634 value = value if isinstance(value, basestring) else str(value)
635 self._write('SREM %s %s\r\n%s\r\n' % (
636 name, len(value), value
637 ))
638 except UnicodeEncodeError, e:
639 raise InvalidData("Error encoding unicode value for element in set '%s': %s." % (name, e))
640 return self._get_numeric_response()
641
642 def sismember(self, name, value):
643 """
644 >>> r = Redis()
645 >>> r.delete('s')
646 1
647 >>> r.sismember('s', 'b')
648 0
649 >>> r.sadd('s', 'a')
650 1
651 >>> r.sismember('s', 'b')
652 0
653 >>> r.sismember('s', 'a')
654 1
655 >>>
656 """
657 self.connect()
658 # same considerations on unicode as in set() apply here
659 try:
660 value = value if isinstance(value, basestring) else str(value)
661 self._write('SISMEMBER %s %s\r\n%s\r\n' % (
662 name, len(value), value
663 ))
664 except UnicodeEncodeError, e:
665 raise InvalidData("Error encoding unicode value for element in set '%s': %s." % (name, e))
666 return self._get_numeric_response()
667
668 def sinter(self, *args):
669 """
670 >>> r = Redis()
671 >>> res = r.delete('s1')
672 >>> res = r.delete('s2')
673 >>> res = r.delete('s3')
674 >>> r.sadd('s1', 'a')
675 1
676 >>> r.sadd('s2', 'a')
677 1
678 >>> r.sadd('s3', 'b')
679 1
680 >>> try:
681 ... r.sinter()
682 ... except ResponseError, e:
683 ... print e
684 wrong number of arguments
685 >>> try:
686 ... r.sinter('l')
687 ... except ResponseError, e:
688 ... print e
689 Operation against a key holding the wrong kind of value
690 >>> r.sinter('s1', 's2', 's3')
691 set([])
692 >>> r.sinter('s1', 's2')
693 set(['a'])
694 >>>
695 """
696 self.connect()
697 self._write('SINTER %s\r\n' % ' '.join(args))
698 return set(self._get_multi_response())
699
700 def sinterstore(self, dest, *args):
701 """
702 >>> r = Redis()
703 >>> res = r.delete('s1')
704 >>> res = r.delete('s2')
705 >>> res = r.delete('s3')
706 >>> r.sadd('s1', 'a')
707 1
708 >>> r.sadd('s2', 'a')
709 1
710 >>> r.sadd('s3', 'b')
711 1
712 >>> r.sinterstore('s_s', 's1', 's2', 's3')
713 'OK'
714 >>> r.sinterstore('s_s', 's1', 's2')
715 'OK'
716 >>> r.smembers('s_s')
717 set(['a'])
718 >>>
719 """
720 self.connect()
721 self._write('SINTERSTORE %s %s\r\n' % (dest, ' '.join(args)))
722 return self._get_simple_response()
723
724 def smembers(self, name):
725 """
726 >>> r = Redis()
727 >>> r.delete('s')
728 1
729 >>> r.sadd('s', 'a')
730 1
731 >>> r.sadd('s', 'b')
732 1
733 >>> try:
734 ... r.smembers('l')
735 ... except ResponseError, e:
736 ... print e
737 Operation against a key holding the wrong kind of value
738 >>> r.smembers('s')
739 set(['a', 'b'])
740 >>>
741 """
742 self.connect()
743 self._write('SMEMBERS %s\r\n' % name)
744 return set(self._get_multi_response())
745
746 def select(self, db):
747 """
748 >>> r = Redis()
749 >>> r.delete('a')
750 1
751 >>> r.select(1)
752 'OK'
753 >>> r.set('a', 1)
754 'OK'
755 >>> r.select(0)
756 'OK'
757 >>> r.get('a')
758 >>>
759 """
760 self.connect()
761 self._write('SELECT %s\r\n' % db)
762 return self._get_simple_response()
763
764 def move(self, name, db):
765 """
766 >>> r = Redis()
767 >>> r.select(0)
768 'OK'
769 >>> r.set('a', 'a')
770 'OK'
771 >>> r.select(1)
772 'OK'
773 >>> if r.get('a'):
774 ... r.delete('a')
775 ... else:
776 ... print 1
777 1
778 >>> r.select(0)
779 'OK'
780 >>> r.move('a', 1)
781 1
782 >>> r.get('a')
783 >>> r.select(1)
784 'OK'
785 >>> r.get('a')
786 'a'
787 >>> r.select(0)
788 'OK'
789 >>>
790 """
791 self.connect()
792 self._write('MOVE %s %s\r\n' % (name, db))
793 return self._get_numeric_response()
794
795 def save(self, background=False):
796 """
797 >>> r = Redis()
798 >>> r.save()
799 'OK'
800 >>> try:
801 ... resp = r.save(background=True)
802 ... except ResponseError, e:
803 ... assert str(e) == 'background save already in progress', str(e)
804 ... else:
805 ... assert resp == 'OK'
806 >>>
807 """
808 self.connect()
809 if background:
810 self._write('BGSAVE\r\n')
811 else:
812 self._write('SAVE\r\n')
813 return self._get_simple_response()
814
815 def lastsave(self):
816 """
817 >>> import time
818 >>> r = Redis()
819 >>> t = int(time.time())
820 >>> r.save()
821 'OK'
822 >>> r.lastsave() >= t
823 True
824 >>>
825 """
826 self.connect()
827 self._write('LASTSAVE\r\n')
828 return self._get_numeric_response()
829
830 def flush(self, all_dbs=False):
831 """
832 >>> r = Redis()
833 >>> r.flush()
834 'OK'
835 >>> r.flush(all_dbs=True)
836 'OK'
837 >>>
838 """
839 self.connect()
840 self._write('%s\r\n' % ('FLUSHALL' if all_dbs else 'FLUSHDB'))
841 return self._get_simple_response()
842
843 def info(self):
844 """
845 >>> r = Redis()
846 >>> info = r.info()
847 >>> info and isinstance(info, dict)
848 True
849 >>> isinstance(info.get('connected_clients'), int)
850 True
851 >>>
852 """
853 self.connect()
854 self._write('INFO\r\n')
855 info = dict()
856 for l in self._get_value().split('\r\n'):
857 if not l:
858 continue
859 k, v = l.split(':', 1)
860 info[k] = int(v) if v.isdigit() else v
861 return info
862
863 def _get_value(self, negative_as_nil=False):
864 data = self._read().strip()
865 if data == 'nil' or (negative_as_nil and data == '-1'):
866 return
867 elif data[0] == '-':
868 self._check_for_error(data)
869 try:
870 l = int(data)
871 except (TypeError, ValueError):
872 raise ResponseError("Cannot parse response '%s' as data length." % data)
873 buf = []
874 while l > 0:
875 data = self._read()
876 l -= len(data)
877 if len(data) > l:
878 # we got the ending crlf
879 data = data.rstrip()
880 buf.append(data)
881 if l == 0:
882 # the data has a trailing crlf embedded, let's restore it
883 buf.append(self._read())
884 return ''.join(buf)
885
886 def _get_simple_response(self):
887 data = self._read().strip()
888 if data[0] == '+':
889 return data[1:]
890 self._check_for_error(data)
891 raise InvalidResponse("Cannot parse first line '%s' for a simple response." % data, data)
892
893 def _get_numeric_response(self, allow_negative=True):
894 data = self._read().strip()
895 try:
896 value = int(data)
897 except (TypeError, ValueError), e:
898 pass
899 else:
900 if not allow_negative and value < 0:
901 self._check_for_error(data)
902 return value
903 self._check_for_error(data)
904 raise InvalidResponse("Cannot parse first line '%s' for a numeric response: %s." % (data, e), data)
905
906 def _get_multi_response(self):
907 results = list()
908 try:
909 num = self._get_numeric_response(allow_negative=False)
910 except InvalidResponse, e:
911 if e.args[1] == 'nil':
912 return results
913 raise
914 while num:
915 results.append(self._get_value(negative_as_nil=True))
916 num -= 1
917 return results
918
919 def _check_for_error(self, data):
920 if not data or data[0] != '-':
921 return
922 if data.startswith('-ERR'):
923 raise ResponseError(data[4:].strip())
924 try:
925 error_len = int(data[1:])
926 except (TypeError, ValueError):
927 raise ResponseError("Unknown error format '%s'." % data)
928 error_message = self._read().strip()[5:]
929 raise ResponseError(error_message)
930
931 def disconnect(self):
932 if isinstance(self._sock, socket.socket):
933 try:
934 self._sock.close()
935 except socket.error:
936 pass
937 self._sock = None
938 self._fp = None
939
940 def connect(self):
941 """
942 >>> r = Redis()
943 >>> r.connect()
944 >>> isinstance(r._sock, socket.socket)
945 True
946 >>>
947 """
948 if isinstance(self._sock, socket.socket):
949 return
950 try:
951 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
952 sock.connect((self.host, self.port))
953 except socket.error, e:
954 raise ConnectionError("Error %s connecting to %s:%s. %s." % (e.args[0], self.host, self.port, e.args[1]))
955 else:
956 self._sock = sock
957 self._fp = self._sock.makefile('r')
958
959
960 if __name__ == '__main__':
961 import doctest
962 doctest.testmod()
963