Hello community, here is the log from the commit of package python-redis for openSUSE:Factory checked in at 2012-02-14 11:26:39 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-redis (Old) and /work/SRC/openSUSE:Factory/.python-redis.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Package is "python-redis", Maintainer is "" Changes: -------- --- /work/SRC/openSUSE:Factory/python-redis/python-redis.changes 2011-11-28 18:27:14.000000000 +0100 +++ /work/SRC/openSUSE:Factory/.python-redis.new/python-redis.changes 2012-02-14 11:26:40.000000000 +0100 @@ -1,0 +2,34 @@ +Tue Feb 7 01:15:02 UTC 2012 - alexandre@exatati.com.br + +- Update to 2.4.11: + * AuthenticationError will now be correctly raised if an invalid password + is supplied. + * If Hiredis is unavailable, the HiredisParser will raise a RedisError + if selected manually. + * Made the INFO command more tolerant of Redis changes formatting. Fix + for #217. +- Aditional changes from 2.4.10: + * Buffer reads from socket in the PythonParser. Fix for a Windows-specific + bug (#205). + * Added the OBJECT and DEBUG OBJECT commands. + * Added __del__ methods for classes that hold on to resources that need to + be cleaned up. This should prevent resource leakage when these objects + leave scope due to misuse or unhandled exceptions. Thanks David Wolever + for the suggestion. + * Added the ECHO command for completeness. + * Fixed a bug where attempting to subscribe to a PubSub channel of a Redis + server that's down would blow out the stack. Fixes #179 and #195. Thanks + Ovidiu Predescu for the test case. + * StrictRedis's TTL command now returns a -1 when querying a key with no + expiration. The Redis class continues to return None. + * ZADD and SADD now return integer values indicating the number of items + added. Thanks Homer Strong. + * Renamed the base client class to StrictRedis, replacing ZADD and LREM in + favor of their official argument order. The Redis class is now a subclass + of StrictRedis, implementing the legacy redis-py implementations of ZADD + and LREM. Docs have been updated to suggesting the use of StrictRedis. + * SETEX in StrictRedis is now compliant with official Redis SETEX command. + the name, value, time implementation moved to "Redis" for backwards + compatability. + +------------------------------------------------------------------- Old: ---- redis-2.4.9.tar.gz New: ---- redis-2.4.11.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-redis.spec ++++++ --- /var/tmp/diff_new_pack.KgAlRS/_old 2012-02-14 11:26:40.000000000 +0100 +++ /var/tmp/diff_new_pack.KgAlRS/_new 2012-02-14 11:26:40.000000000 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-redis # -# Copyright (c) 2011 SUSE LINUX Products GmbH, Nuernberg, Germany. +# Copyright (c) 2012 SUSE LINUX Products GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -11,13 +11,12 @@ # case the license is the MIT License). An "Open Source License" is a # license that conforms to the Open Source Definition (Version 1.9) # published by the Open Source Initiative. -# + # Please submit bugfixes or comments via http://bugs.opensuse.org/ # - Name: python-redis -Version: 2.4.9 +Version: 2.4.11 Release: 0 Url: http://github.com/andymccurdy/redis-py Summary: Python client for Redis key-value store ++++++ redis-2.4.9.tar.gz -> redis-2.4.11.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-2.4.9/CHANGES new/redis-2.4.11/CHANGES --- old/redis-2.4.9/CHANGES 2011-07-23 01:25:23.000000000 +0200 +++ new/redis-2.4.11/CHANGES 2012-01-13 22:47:27.000000000 +0100 @@ -1,3 +1,33 @@ +* 2.4.11 + * AuthenticationError will now be correctly raised if an invalid password + is supplied. + * If Hiredis is unavailable, the HiredisParser will raise a RedisError + if selected manually. + * Made the INFO command more tolerant of Redis changes formatting. Fix + for #217. +* 2.4.10 + * Buffer reads from socket in the PythonParser. Fix for a Windows-specific + bug (#205). + * Added the OBJECT and DEBUG OBJECT commands. + * Added __del__ methods for classes that hold on to resources that need to + be cleaned up. This should prevent resource leakage when these objects + leave scope due to misuse or unhandled exceptions. Thanks David Wolever + for the suggestion. + * Added the ECHO command for completeness. + * Fixed a bug where attempting to subscribe to a PubSub channel of a Redis + server that's down would blow out the stack. Fixes #179 and #195. Thanks + Ovidiu Predescu for the test case. + * StrictRedis's TTL command now returns a -1 when querying a key with no + expiration. The Redis class continues to return None. + * ZADD and SADD now return integer values indicating the number of items + added. Thanks Homer Strong. + * Renamed the base client class to StrictRedis, replacing ZADD and LREM in + favor of their official argument order. The Redis class is now a subclass + of StrictRedis, implementing the legacy redis-py implementations of ZADD + and LREM. Docs have been updated to suggesting the use of StrictRedis. + * SETEX in StrictRedis is now compliant with official Redis SETEX command. + the name, value, time implementation moved to "Redis" for backwards + compatability. * 2.4.9 * Removed socket retry logic in Connection. This is the responsbility of the caller to determine if the command is safe and can be retried. Thanks diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-2.4.9/PKG-INFO new/redis-2.4.11/PKG-INFO --- old/redis-2.4.9/PKG-INFO 2011-07-23 01:26:05.000000000 +0200 +++ new/redis-2.4.11/PKG-INFO 2012-01-13 22:48:58.000000000 +0100 @@ -1,12 +1,12 @@ Metadata-Version: 1.0 Name: redis -Version: 2.4.9 +Version: 2.4.11 Summary: Python client for Redis key-value store Home-page: http://github.com/andymccurdy/redis-py Author: Andy McCurdy Author-email: sedrik@gmail.com License: MIT -Download-URL: http://cloud.github.com/downloads/andymccurdy/redis-py/redis-2.4.9.tar.gz +Download-URL: http://cloud.github.com/downloads/andymccurdy/redis-py/redis-2.4.11.tar.gz Description: # redis-py The Python interface to the Redis key-value store. @@ -27,12 +27,47 @@ ## Getting Started >>> import redis - >>> r = redis.Redis(host='localhost', port=6379, db=0) + >>> r = redis.StrictRedis(host='localhost', port=6379, db=0) >>> r.set('foo', 'bar') True >>> r.get('foo') 'bar' + ## API Reference + + The official Redis documentation does a great job of explaining each command in + detail (http://redis.io/commands). redis-py exposes two client classes that + implement these commands. The StrictRedis class attempts to adhere to the + official official command syntax. There are a few exceptions: + + * SELECT: Not implemented. See the explanation in the Thread Safety section + below. + * DEL: 'del' is a reserved keyword in the Python syntax. Therefore redis-py + uses 'delete' instead. + * CONFIG GET|SET: These are implemented separately as config_get or config_set. + * MULTI/EXEC: These are implemented as part of the Pipeline class. Calling + the pipeline method and specifying use_transaction=True will cause the + pipeline to be wrapped with the MULTI and EXEC statements when it is executed. + See more about Pipelines below. + * SUBSCRIBE/LISTEN: Similar to pipelines, PubSub is implemented as a separate + class as it places the underlying connection in a state where it can't + execute non-pubsub commands. Calling the pubsub method from the Redis client + will return a PubSub instance where you can subscribe to channels and listen + for messages. You can call PUBLISH from both classes. + + In addition to the changes above, the Redis class, a subclass of StrictRedis, + overrides several other commands to provide backwards compatibility with older + versions of redis-py: + + * LREM: Order of 'num' and 'value' arguments reversed such that 'num' can + provide a default value of zero. + * ZADD: Redis specifies the 'score' argument before 'value'. These were swapped + accidentally when being implemented and not discovered until after people + were already using it. The Redis class expects *args in the form of: + name1, score1, name2, score2, ... + * SETEX: Order of 'time' and 'value' arguments reversed. + + ## More Detail ### Connection Pools @@ -48,7 +83,7 @@ >>> pool = redis.ConnectionPool(host='localhost', port=6379, db=0) >>> r = redis.Redis(connection_pool=pool) - ### Connetions + ### Connections ConnectionPools manage a set of Connection instances. redis-py ships with two types of Connections. The default, Connection, is a normal TCP socket based @@ -131,7 +166,7 @@ create a separate client instance (and possibly a separate connection pool) for each database. - It is not save to pass PubSub objects between threads. + It is not safe to pass PubSub or Pipeline objects between threads. ## Pipelines @@ -145,7 +180,7 @@ >>> r = redis.Redis(...) >>> r.set('bing', 'baz') >>> # Use the pipeline() method to create a pipeline instance - >>> pipe = redis.pipeline() + >>> pipe = r.pipeline() >>> # The following SET commands are buffered >>> pipe.set('foo', 'bar') >>> pipe.get('bing') @@ -179,7 +214,7 @@ Enter the WATCH command. WATCH provides the ability to monitor one or more keys prior to starting a transaction. If any of those keys change prior the - execution of that transaction, the entre transaction will be canceled and a + execution of that transaction, the entire transaction will be canceled and a WatchError will be raised. To implement our own client-side INCR command, we could do something like this: @@ -208,10 +243,10 @@ ... continue Note that, because the Pipeline must bind to a single connection for the - duration of a WATCH, care must be taken to ensure that he connection is + duration of a WATCH, care must be taken to ensure that the connection is returned to the connection pool by calling the reset() method. If the Pipeline is used as a context manager (as in the example above) reset() - will be called automatically. Of course you can do this the manual way as by + will be called automatically. Of course you can do this the manual way by explicity calling reset(): >>> pipe = r.pipeline() @@ -241,35 +276,6 @@ >>> r.transaction(client_side_incr, 'OUR-SEQUENCE-KEY') [True] - - ## API Reference - - The official Redis documentation does a great job of explaining each command in - detail (http://redis.io/commands). In most cases, redis-py uses the same - arguments as the official spec. There are a few exceptions noted here: - - * SELECT: Not implemented. See the explanation in the Thread Safety section - above. - * ZADD: Redis specifies the 'score' argument before 'value'. These were swapped - accidentally when being implemented and not discovered until after people - were already using it. As of Redis 2.4, ZADD will start supporting variable - arguments. redis-py implements these as python keyword arguments where the - name is the 'value' and the value is the 'score'. - * DEL: 'del' is a reserved keyword in the Python syntax. Therefore redis-py - uses 'delete' instead. - * CONFIG GET|SET: These are implemented separately as config_get or config_set. - * MULTI/EXEC: These are implemented as part of the Pipeline class. Calling - the pipeline method and specifying use_transaction=True will cause the - pipeline to be wrapped with the MULTI and EXEC statements when it is executed. - See more about Pipelines above. - * SUBSCRIBE/LISTEN: Similar to pipelines, PubSub is implemented as a separate - class as it places the underlying connection in a state where it can't - execute non-pubsub commands. Calling the pubsub method from the Redis client - will return a PubSub instance where you can subscribe to channels and listen - for messages. You can call PUBLISH from both classes. - * LREM: Order of 'num' and 'value' arguments reversed such that 'num' can - provide a default value of zero. - ## Versioning scheme redis-py is versioned after Redis. For example, redis-py 2.0.0 should diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-2.4.9/README.md new/redis-2.4.11/README.md --- old/redis-2.4.9/README.md 2011-07-18 00:48:52.000000000 +0200 +++ new/redis-2.4.11/README.md 2011-11-08 01:50:42.000000000 +0100 @@ -18,12 +18,47 @@ ## Getting Started >>> import redis - >>> r = redis.Redis(host='localhost', port=6379, db=0) + >>> r = redis.StrictRedis(host='localhost', port=6379, db=0) >>> r.set('foo', 'bar') True >>> r.get('foo') 'bar' +## API Reference + +The official Redis documentation does a great job of explaining each command in +detail (http://redis.io/commands). redis-py exposes two client classes that +implement these commands. The StrictRedis class attempts to adhere to the +official official command syntax. There are a few exceptions: + +* SELECT: Not implemented. See the explanation in the Thread Safety section + below. +* DEL: 'del' is a reserved keyword in the Python syntax. Therefore redis-py + uses 'delete' instead. +* CONFIG GET|SET: These are implemented separately as config_get or config_set. +* MULTI/EXEC: These are implemented as part of the Pipeline class. Calling + the pipeline method and specifying use_transaction=True will cause the + pipeline to be wrapped with the MULTI and EXEC statements when it is executed. + See more about Pipelines below. +* SUBSCRIBE/LISTEN: Similar to pipelines, PubSub is implemented as a separate + class as it places the underlying connection in a state where it can't + execute non-pubsub commands. Calling the pubsub method from the Redis client + will return a PubSub instance where you can subscribe to channels and listen + for messages. You can call PUBLISH from both classes. + +In addition to the changes above, the Redis class, a subclass of StrictRedis, +overrides several other commands to provide backwards compatibility with older +versions of redis-py: + +* LREM: Order of 'num' and 'value' arguments reversed such that 'num' can + provide a default value of zero. +* ZADD: Redis specifies the 'score' argument before 'value'. These were swapped + accidentally when being implemented and not discovered until after people + were already using it. The Redis class expects *args in the form of: + name1, score1, name2, score2, ... +* SETEX: Order of 'time' and 'value' arguments reversed. + + ## More Detail ### Connection Pools @@ -39,7 +74,7 @@ >>> pool = redis.ConnectionPool(host='localhost', port=6379, db=0) >>> r = redis.Redis(connection_pool=pool) -### Connetions +### Connections ConnectionPools manage a set of Connection instances. redis-py ships with two types of Connections. The default, Connection, is a normal TCP socket based @@ -122,7 +157,7 @@ create a separate client instance (and possibly a separate connection pool) for each database. -It is not save to pass PubSub objects between threads. +It is not safe to pass PubSub or Pipeline objects between threads. ## Pipelines @@ -136,7 +171,7 @@ >>> r = redis.Redis(...) >>> r.set('bing', 'baz') >>> # Use the pipeline() method to create a pipeline instance - >>> pipe = redis.pipeline() + >>> pipe = r.pipeline() >>> # The following SET commands are buffered >>> pipe.set('foo', 'bar') >>> pipe.get('bing') @@ -170,7 +205,7 @@ Enter the WATCH command. WATCH provides the ability to monitor one or more keys prior to starting a transaction. If any of those keys change prior the -execution of that transaction, the entre transaction will be canceled and a +execution of that transaction, the entire transaction will be canceled and a WatchError will be raised. To implement our own client-side INCR command, we could do something like this: @@ -199,10 +234,10 @@ ... continue Note that, because the Pipeline must bind to a single connection for the -duration of a WATCH, care must be taken to ensure that he connection is +duration of a WATCH, care must be taken to ensure that the connection is returned to the connection pool by calling the reset() method. If the Pipeline is used as a context manager (as in the example above) reset() -will be called automatically. Of course you can do this the manual way as by +will be called automatically. Of course you can do this the manual way by explicity calling reset(): >>> pipe = r.pipeline() @@ -232,35 +267,6 @@ >>> r.transaction(client_side_incr, 'OUR-SEQUENCE-KEY') [True] - -## API Reference - -The official Redis documentation does a great job of explaining each command in -detail (http://redis.io/commands). In most cases, redis-py uses the same -arguments as the official spec. There are a few exceptions noted here: - -* SELECT: Not implemented. See the explanation in the Thread Safety section - above. -* ZADD: Redis specifies the 'score' argument before 'value'. These were swapped - accidentally when being implemented and not discovered until after people - were already using it. As of Redis 2.4, ZADD will start supporting variable - arguments. redis-py implements these as python keyword arguments where the - name is the 'value' and the value is the 'score'. -* DEL: 'del' is a reserved keyword in the Python syntax. Therefore redis-py - uses 'delete' instead. -* CONFIG GET|SET: These are implemented separately as config_get or config_set. -* MULTI/EXEC: These are implemented as part of the Pipeline class. Calling - the pipeline method and specifying use_transaction=True will cause the - pipeline to be wrapped with the MULTI and EXEC statements when it is executed. - See more about Pipelines above. -* SUBSCRIBE/LISTEN: Similar to pipelines, PubSub is implemented as a separate - class as it places the underlying connection in a state where it can't - execute non-pubsub commands. Calling the pubsub method from the Redis client - will return a PubSub instance where you can subscribe to channels and listen - for messages. You can call PUBLISH from both classes. -* LREM: Order of 'num' and 'value' arguments reversed such that 'num' can - provide a default value of zero. - ## Versioning scheme redis-py is versioned after Redis. For example, redis-py 2.0.0 should diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-2.4.9/redis/__init__.py new/redis-2.4.11/redis/__init__.py --- old/redis-2.4.9/redis/__init__.py 2011-07-23 01:25:34.000000000 +0200 +++ new/redis-2.4.11/redis/__init__.py 2012-01-13 22:48:06.000000000 +0100 @@ -1,4 +1,4 @@ -from redis.client import Redis +from redis.client import Redis, StrictRedis from redis.connection import ( ConnectionPool, Connection, @@ -16,11 +16,12 @@ ) -__version__ = '2.4.9' +__version__ = '2.4.11' VERSION = tuple(map(int, __version__.split('.'))) __all__ = [ - 'Redis', 'ConnectionPool', 'Connection', 'UnixDomainSocketConnection', + 'Redis', 'StrictRedis', 'ConnectionPool', + 'Connection', 'UnixDomainSocketConnection', 'RedisError', 'ConnectionError', 'ResponseError', 'AuthenticationError', 'InvalidResponse', 'DataError', 'PubSubError', 'WatchError', ] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-2.4.9/redis/client.py new/redis-2.4.11/redis/client.py --- old/redis-2.4.9/redis/client.py 2011-07-18 17:10:52.000000000 +0200 +++ new/redis-2.4.11/redis/client.py 2012-01-13 22:45:24.000000000 +0100 @@ -44,12 +44,36 @@ [merged.update(d) for d in dicts] return merged +def parse_debug_object(response): + "Parse the results of Redis's DEBUG OBJECT command into a Python dict" + # The 'type' of the object is the first item in the response, but isn't + # prefixed with a name + response = 'type:' + response + response = dict([kv.split(':') for kv in response.split()]) + + # parse some expected int values from the string response + # note: this cmd isn't spec'd so these may not appear in all redis versions + int_fields = ('refcount', 'serializedlength', 'lru', 'lru_seconds_idle') + for field in int_fields: + if field in response: + response[field] = int(response[field]) + + return response + +def parse_object(response, infotype): + "Parse the results of an OBJECT command" + if infotype in ('idletime', 'refcount'): + return int(response) + return response + def parse_info(response): "Parse the result of Redis's INFO command into a Python dict" info = {} + def get_value(value): - if ',' not in value: + if ',' not in value or '=' not in value: return value + sub_dict = {} for item in value.split(','): k, v = item.rsplit('=', 1) @@ -58,6 +82,7 @@ except ValueError: sub_dict[k] = v return sub_dict + for line in response.splitlines(): if line and not line.startswith('#'): key, value = line.split(':') @@ -102,7 +127,7 @@ return response and pairs_to_dict(response) or {} return response == 'OK' -class Redis(object): +class StrictRedis(object): """ Implementation of the Redis protocol. @@ -115,13 +140,13 @@ RESPONSE_CALLBACKS = dict_merge( string_keys_to_dict( 'AUTH DEL EXISTS EXPIRE EXPIREAT HDEL HEXISTS HMSET MOVE MSETNX ' - 'PERSIST RENAMENX SADD SISMEMBER SMOVE SETEX SETNX SREM ZADD ZREM', + 'PERSIST RENAMENX SISMEMBER SMOVE SETEX SETNX SREM ZREM', bool ), string_keys_to_dict( - 'DECRBY GETBIT HLEN INCRBY LINSERT LLEN LPUSHX RPUSHX SCARD ' - 'SDIFFSTORE SETBIT SETRANGE SINTERSTORE STRLEN SUNIONSTORE ZCARD ' - 'ZREMRANGEBYRANK ZREMRANGEBYSCORE', + 'DECRBY GETBIT HLEN INCRBY LINSERT LLEN LPUSHX RPUSHX SADD SCARD ' + 'SDIFFSTORE SETBIT SETRANGE SINTERSTORE STRLEN SUNIONSTORE ZADD ' + 'ZCARD ZREMRANGEBYRANK ZREMRANGEBYSCORE', int ), string_keys_to_dict( @@ -149,12 +174,13 @@ 'BGSAVE': lambda r: r == 'Background saving started', 'BRPOPLPUSH': lambda r: r and r or None, 'CONFIG': parse_config, + 'DEBUG': parse_debug_object, 'HGETALL': lambda r: r and pairs_to_dict(r) or {}, 'INFO': parse_info, 'LASTSAVE': timestamp_to_datetime, + 'OBJECT': parse_object, 'PING': lambda r: r == 'PONG', 'RANDOMKEY': lambda r: r and r or None, - 'TTL': lambda r: r != -1 and r or None, } ) @@ -198,7 +224,7 @@ atomic, pipelines are useful for reducing the back-and-forth overhead between the client and server. """ - return Pipeline( + return StrictPipeline( self.connection_pool, self.response_callbacks, transaction, @@ -289,11 +315,19 @@ "Returns the number of keys in the current database" return self.execute_command('DBSIZE') + def debug_object(self, key): + "Returns version specific metainformation about a give key" + return self.execute_command('DEBUG', 'OBJECT', key) + def delete(self, *names): "Delete one or more keys specified by ``names``" return self.execute_command('DEL', *names) __delitem__ = delete + def echo(self, value): + "Echo the string back from the server" + return self.execute_command('ECHO', value) + def flushall(self): "Delete all keys in all databases on the current host" return self.execute_command('FLUSHALL') @@ -313,6 +347,10 @@ """ return self.execute_command('LASTSAVE') + def object(self, infotype, key): + "Return the encoding, idletime, or refcount about the key" + return self.execute_command('OBJECT', infotype, key, infotype=infotype) + def ping(self): "Ping the Redis server" return self.execute_command('PING') @@ -474,7 +512,7 @@ value = value and 1 or 0 return self.execute_command('SETBIT', name, offset, value) - def setex(self, name, value, time): + def setex(self, name, time, value): """ Set the value of key ``name`` to ``value`` that expires in ``time`` seconds @@ -628,13 +666,17 @@ """ return self.execute_command('LRANGE', name, start, end) - def lrem(self, name, value, num=0): + def lrem(self, name, count, value): """ - Remove the first ``num`` occurrences of ``value`` from list ``name`` + Remove the first ``count`` occurrences of elements equal to ``value`` + from the list stored at ``name``. - If ``num`` is 0, then all occurrences will be removed + The count argument influences the operation in the following ways: + count > 0: Remove elements equal to value moving from head to tail. + count < 0: Remove elements equal to value moving from tail to head. + count = 0: Remove all elements equal to value. """ - return self.execute_command('LREM', name, num, value) + return self.execute_command('LREM', name, count, value) def lset(self, name, index, value): "Set ``position`` of list ``name`` to ``value``" @@ -798,27 +840,27 @@ #### SORTED SET COMMANDS #### - def zadd(self, name, value=None, score=None, **pairs): + def zadd(self, name, *args, **kwargs): """ - For each kwarg in ``pairs``, add that item and it's score to the - sorted set ``name``. + Set any number of score, element-name pairs to the key ``name``. Pairs + can be specified in two ways: - The ``value`` and ``score`` arguments are deprecated. - """ - all_pairs = [] - if value is not None or score is not None: - if value is None or score is None: - raise RedisError("Both 'value' and 'score' must be specified " \ - "to ZADD") - warnings.warn(DeprecationWarning( - "Passing 'value' and 'score' has been deprecated. " \ - "Please pass via kwargs instead.")) - all_pairs.append(score) - all_pairs.append(value) - for pair in pairs.iteritems(): - all_pairs.append(pair[1]) - all_pairs.append(pair[0]) - return self.execute_command('ZADD', name, *all_pairs) + As *args, in the form of: score1, name1, score2, name2, ... + or as **kwargs, in the form of: name1=score1, name2=score2, ... + + The following example would add four values to the 'my-key' key: + redis.zadd('my-key', 1.1, 'name1', 2.2, 'name2', name3=3.3, name4=4.4) + """ + pieces = [] + if args: + if len(args) % 2 != 0: + raise RedisError("ZADD requires an equal number of " + "values and scores") + pieces.extend(args) + for pair in kwargs.iteritems(): + pieces.append(pair[1]) + pieces.append(pair[0]) + return self.execute_command('ZADD', name, *pieces) def zcard(self, name): "Return the number of elements in the sorted set ``name``" @@ -1063,6 +1105,86 @@ return self.execute_command('PUBLISH', channel, message) +class Redis(StrictRedis): + """ + Provides backwards compatibility with older versions of redis-py that + changed arguments to some commands to be more Pythonic, sane, or by + accident. + """ + + # Overridden callbacks + RESPONSE_CALLBACKS = dict_merge( + StrictRedis.RESPONSE_CALLBACKS, + { + 'TTL': lambda r: r != -1 and r or None, + } + ) + + def pipeline(self, transaction=True, shard_hint=None): + """ + Return a new pipeline object that can queue multiple commands for + later execution. ``transaction`` indicates whether all commands + should be executed atomically. Apart from making a group of operations + atomic, pipelines are useful for reducing the back-and-forth overhead + between the client and server. + """ + return Pipeline( + self.connection_pool, + self.response_callbacks, + transaction, + shard_hint) + + def setex(self, name, value, time): + """ + Set the value of key ``name`` to ``value`` + that expires in ``time`` seconds + """ + return self.execute_command('SETEX', name, time, value) + + def lrem(self, name, value, num=0): + """ + Remove the first ``num`` occurrences of elements equal to ``value`` + from the list stored at ``name``. + + The ``num`` argument influences the operation in the following ways: + num > 0: Remove elements equal to value moving from head to tail. + num < 0: Remove elements equal to value moving from tail to head. + num = 0: Remove all elements equal to value. + """ + return self.execute_command('LREM', name, num, value) + + def zadd(self, name, *args, **kwargs): + """ + NOTE: The order of arguments differs from that of the official ZADD + command. For backwards compatability, this method accepts arguments + in the form of name1, score1, name2, score2, while the official Redis + documents expects score1, name1, score2, name2. + + If you're looking to use the standard syntax, consider using the + StrictRedis class. See the API Reference section of the docs for more + information. + + Set any number of element-name, score pairs to the key ``name``. Pairs + can be specified in two ways: + + As *args, in the form of: name1, score1, name2, score2, ... + or as **kwargs, in the form of: name1=score1, name2=score2, ... + + The following example would add four values to the 'my-key' key: + redis.zadd('my-key', 'name1', 1.1, 'name2', 2.2, name3=3.3, name4=4.4) + """ + pieces = [] + if args: + if len(args) % 2 != 0: + raise RedisError("ZADD requires an equal number of " + "values and scores") + pieces.extend(reversed(args)) + for pair in kwargs.iteritems(): + pieces.append(pair[1]) + pieces.append(pair[0]) + return self.execute_command('ZADD', name, *pieces) + + class PubSub(object): """ PubSub provides publish, subscribe and listen support to Redis channels. @@ -1082,6 +1204,22 @@ ('subscribe', 'psubscribe', 'unsubscribe', 'punsubscribe') ) + def __del__(self): + try: + # if this object went out of scope prior to shutting down + # subscriptions, close the connection manually before + # returning it to the connection pool + if self.connection and (self.channels or self.patterns): + self.connection.disconnect() + self.reset() + except: + pass + + def reset(self): + if self.connection: + self.connection_pool.release(self.connection) + self.connection = None + def execute_command(self, *args, **kwargs): "Execute a publish/subscribe command" if self.connection is None: @@ -1095,6 +1233,9 @@ return self.parse_response() except ConnectionError: connection.disconnect() + # Connect manually here. If the Redis server is down, this will + # fail and raise a ConnectionError as desired. + connection.connect() # resubscribe to all channels and patterns before # resending the current command for channel in self.channels: @@ -1112,8 +1253,7 @@ # if we've just unsubscribed from the remaining channels, # release the connection back to the pool if not self.subscription_count: - self.connection_pool.release(self.connection) - self.connection = None + self.reset() return response def psubscribe(self, patterns): @@ -1181,7 +1321,7 @@ yield msg -class Pipeline(Redis): +class BasePipeline(object): """ Pipelines provide a way to transmit multiple commands to the Redis server in one transmission. This is convenient for batch processing, such as @@ -1219,6 +1359,12 @@ def __exit__(self, exc_type, exc_value, traceback): self.reset() + def __del__(self): + try: + self.reset() + except: + pass + def reset(self): self.command_stack = [] # make sure to reset the connection state in the event that we were @@ -1341,8 +1487,8 @@ for args, options in commands] def parse_response(self, connection, command_name, **options): - result = super(Pipeline, self).parse_response( - connection, command_name, **options) + result = StrictRedis.parse_response( + self, connection, command_name, **options) if command_name in self.UNWATCH_COMMANDS: self.watching = False elif command_name == 'WATCH': @@ -1353,7 +1499,7 @@ "Execute all the commands in the current pipeline" stack = self.command_stack if self.transaction or self.explicit_transaction: - stack = [(('MULTI' ,), {})] + stack + [(('EXEC', ), {})] + stack = [(('MULTI', ), {})] + stack + [(('EXEC', ), {})] execute = self._execute_transaction else: execute = self._execute_pipeline @@ -1398,6 +1544,14 @@ return self.watching and self.execute_command('UNWATCH') or True +class StrictPipeline(BasePipeline, StrictRedis): + "Pipeline for the StrictRedis class" + pass + +class Pipeline(BasePipeline, Redis): + "Pipeline for the Redis class" + pass + class LockError(RedisError): "Errors thrown from the Lock" pass @@ -1426,8 +1580,8 @@ holding the lock. Note: If using ``timeout``, you should make sure all the hosts - that are running clients are within the same timezone and are using - a network time service like ntp. + that are running clients have their time synchronized with a network time + service like ntp. """ self.redis = redis self.name = name diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-2.4.9/redis/connection.py new/redis-2.4.11/redis/connection.py --- old/redis-2.4.9/redis/connection.py 2011-07-23 01:22:49.000000000 +0200 +++ new/redis-2.4.11/redis/connection.py 2011-11-08 01:57:26.000000000 +0100 @@ -1,12 +1,37 @@ -import errno import socket from itertools import chain, imap -from redis.exceptions import ConnectionError, ResponseError, InvalidResponse +from redis.exceptions import ( + RedisError, + ConnectionError, + ResponseError, + InvalidResponse, + AuthenticationError +) + +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO + +try: + import hiredis + hiredis_available = True +except ImportError: + hiredis_available = False class PythonParser(object): + "Plain Python parsing class" + MAX_READ_LENGTH = 1000000 + def __init__(self): self._fp = None + def __del__(self): + try: + self.on_disconnect() + except: + pass + def on_connect(self, connection): "Called when the socket connects" self._fp = connection._sock.makefile('r') @@ -24,7 +49,25 @@ """ try: if length is not None: - return self._fp.read(length+2)[:-2] + bytes_left = length + 2 # read the line ending + if length > self.MAX_READ_LENGTH: + # apparently reading more than 1MB or so from a windows + # socket can cause MemoryErrors. See: + # https://github.com/andymccurdy/redis-py/issues/205 + # read smaller chunks at a time to work around this + try: + buf = StringIO() + while bytes_left > 0: + read_len = min(bytes_left, self.MAX_READ_LENGTH) + buf.write(self._fp.read(read_len)) + bytes_left -= read_len + buf.seek(0) + return buf.read(length) + finally: + buf.close() + return self._fp.read(bytes_left)[:-2] + + # no length, read a full line return self._fp.readline()[:-2] except (socket.error, socket.timeout), e: raise ConnectionError("Error while reading from socket: %s" % \ @@ -68,6 +111,17 @@ raise InvalidResponse("Protocol Error") class HiredisParser(object): + "Parser class for connections using Hiredis" + def __init__(self): + if not hiredis_available: + raise RedisError("Hiredis is not installed") + + def __del__(self): + try: + self.on_disconnect() + except: + pass + def on_connect(self, connection): self._sock = connection._sock self._reader = hiredis.Reader( @@ -79,6 +133,8 @@ self._reader = None def read_response(self): + if not self._reader: + raise ConnectionError("Socket closed on remote end") response = self._reader.gets() while response is False: try: @@ -96,12 +152,12 @@ response = self._reader.gets() return response -try: - import hiredis +if hiredis_available: DefaultParser = HiredisParser -except ImportError: +else: DefaultParser = PythonParser + class Connection(object): "Manages TCP communication to and from a Redis server" def __init__(self, host='localhost', port=6379, db=0, password=None, @@ -117,6 +173,12 @@ self._sock = None self._parser = parser_class() + def __del__(self): + try: + self.disconnect() + except: + pass + def connect(self): "Connects to the Redis server if not already connected" if self._sock: @@ -146,7 +208,6 @@ return "Error %s connecting %s:%s. %s." % \ (exception.args[0], self.host, self.port, exception.args[1]) - def on_connect(self): "Initialize the connection, authenticate and select a database" self._parser.on_connect(self) @@ -155,7 +216,7 @@ if self.password: self.send_command('AUTH', self.password) if self.read_response() != 'OK': - raise ConnectionError('Invalid Password') + raise AuthenticationError('Invalid Password') # if a database is specified, switch to it if self.db: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-2.4.9/redis.egg-info/PKG-INFO new/redis-2.4.11/redis.egg-info/PKG-INFO --- old/redis-2.4.9/redis.egg-info/PKG-INFO 2011-07-23 01:26:05.000000000 +0200 +++ new/redis-2.4.11/redis.egg-info/PKG-INFO 2012-01-13 22:48:58.000000000 +0100 @@ -1,12 +1,12 @@ Metadata-Version: 1.0 Name: redis -Version: 2.4.9 +Version: 2.4.11 Summary: Python client for Redis key-value store Home-page: http://github.com/andymccurdy/redis-py Author: Andy McCurdy Author-email: sedrik@gmail.com License: MIT -Download-URL: http://cloud.github.com/downloads/andymccurdy/redis-py/redis-2.4.9.tar.gz +Download-URL: http://cloud.github.com/downloads/andymccurdy/redis-py/redis-2.4.11.tar.gz Description: # redis-py The Python interface to the Redis key-value store. @@ -27,12 +27,47 @@ ## Getting Started >>> import redis - >>> r = redis.Redis(host='localhost', port=6379, db=0) + >>> r = redis.StrictRedis(host='localhost', port=6379, db=0) >>> r.set('foo', 'bar') True >>> r.get('foo') 'bar' + ## API Reference + + The official Redis documentation does a great job of explaining each command in + detail (http://redis.io/commands). redis-py exposes two client classes that + implement these commands. The StrictRedis class attempts to adhere to the + official official command syntax. There are a few exceptions: + + * SELECT: Not implemented. See the explanation in the Thread Safety section + below. + * DEL: 'del' is a reserved keyword in the Python syntax. Therefore redis-py + uses 'delete' instead. + * CONFIG GET|SET: These are implemented separately as config_get or config_set. + * MULTI/EXEC: These are implemented as part of the Pipeline class. Calling + the pipeline method and specifying use_transaction=True will cause the + pipeline to be wrapped with the MULTI and EXEC statements when it is executed. + See more about Pipelines below. + * SUBSCRIBE/LISTEN: Similar to pipelines, PubSub is implemented as a separate + class as it places the underlying connection in a state where it can't + execute non-pubsub commands. Calling the pubsub method from the Redis client + will return a PubSub instance where you can subscribe to channels and listen + for messages. You can call PUBLISH from both classes. + + In addition to the changes above, the Redis class, a subclass of StrictRedis, + overrides several other commands to provide backwards compatibility with older + versions of redis-py: + + * LREM: Order of 'num' and 'value' arguments reversed such that 'num' can + provide a default value of zero. + * ZADD: Redis specifies the 'score' argument before 'value'. These were swapped + accidentally when being implemented and not discovered until after people + were already using it. The Redis class expects *args in the form of: + name1, score1, name2, score2, ... + * SETEX: Order of 'time' and 'value' arguments reversed. + + ## More Detail ### Connection Pools @@ -48,7 +83,7 @@ >>> pool = redis.ConnectionPool(host='localhost', port=6379, db=0) >>> r = redis.Redis(connection_pool=pool) - ### Connetions + ### Connections ConnectionPools manage a set of Connection instances. redis-py ships with two types of Connections. The default, Connection, is a normal TCP socket based @@ -131,7 +166,7 @@ create a separate client instance (and possibly a separate connection pool) for each database. - It is not save to pass PubSub objects between threads. + It is not safe to pass PubSub or Pipeline objects between threads. ## Pipelines @@ -145,7 +180,7 @@ >>> r = redis.Redis(...) >>> r.set('bing', 'baz') >>> # Use the pipeline() method to create a pipeline instance - >>> pipe = redis.pipeline() + >>> pipe = r.pipeline() >>> # The following SET commands are buffered >>> pipe.set('foo', 'bar') >>> pipe.get('bing') @@ -179,7 +214,7 @@ Enter the WATCH command. WATCH provides the ability to monitor one or more keys prior to starting a transaction. If any of those keys change prior the - execution of that transaction, the entre transaction will be canceled and a + execution of that transaction, the entire transaction will be canceled and a WatchError will be raised. To implement our own client-side INCR command, we could do something like this: @@ -208,10 +243,10 @@ ... continue Note that, because the Pipeline must bind to a single connection for the - duration of a WATCH, care must be taken to ensure that he connection is + duration of a WATCH, care must be taken to ensure that the connection is returned to the connection pool by calling the reset() method. If the Pipeline is used as a context manager (as in the example above) reset() - will be called automatically. Of course you can do this the manual way as by + will be called automatically. Of course you can do this the manual way by explicity calling reset(): >>> pipe = r.pipeline() @@ -241,35 +276,6 @@ >>> r.transaction(client_side_incr, 'OUR-SEQUENCE-KEY') [True] - - ## API Reference - - The official Redis documentation does a great job of explaining each command in - detail (http://redis.io/commands). In most cases, redis-py uses the same - arguments as the official spec. There are a few exceptions noted here: - - * SELECT: Not implemented. See the explanation in the Thread Safety section - above. - * ZADD: Redis specifies the 'score' argument before 'value'. These were swapped - accidentally when being implemented and not discovered until after people - were already using it. As of Redis 2.4, ZADD will start supporting variable - arguments. redis-py implements these as python keyword arguments where the - name is the 'value' and the value is the 'score'. - * DEL: 'del' is a reserved keyword in the Python syntax. Therefore redis-py - uses 'delete' instead. - * CONFIG GET|SET: These are implemented separately as config_get or config_set. - * MULTI/EXEC: These are implemented as part of the Pipeline class. Calling - the pipeline method and specifying use_transaction=True will cause the - pipeline to be wrapped with the MULTI and EXEC statements when it is executed. - See more about Pipelines above. - * SUBSCRIBE/LISTEN: Similar to pipelines, PubSub is implemented as a separate - class as it places the underlying connection in a state where it can't - execute non-pubsub commands. Calling the pubsub method from the Redis client - will return a PubSub instance where you can subscribe to channels and listen - for messages. You can call PUBLISH from both classes. - * LREM: Order of 'num' and 'value' arguments reversed such that 'num' can - provide a default value of zero. - ## Versioning scheme redis-py is versioned after Redis. For example, redis-py 2.0.0 should -- To unsubscribe, e-mail: opensuse-commit+unsubscribe@opensuse.org For additional commands, e-mail: opensuse-commit+help@opensuse.org