commit python-redis for openSUSE:Factory
Hello community, here is the log from the commit of package python-redis for openSUSE:Factory checked in at 2016-12-08 00:30:19 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-redis (Old) and /work/SRC/openSUSE:Factory/.python-redis.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Package is "python-redis" Changes: -------- --- /work/SRC/openSUSE:Factory/python-redis/python-redis.changes 2014-09-03 08:30:35.000000000 +0200 +++ /work/SRC/openSUSE:Factory/.python-redis.new/python-redis.changes 2016-12-08 00:30:20.000000000 +0100 @@ -1,0 +2,29 @@ +Tue Nov 15 12:35:46 UTC 2016 - dmueller@suse.com + +- update to 2.10.5: + * Allow URL encoded parameters in Redis URLs. Characters like a "/" can + now be URL encoded and redis-py will correctly decode them. Thanks + * Added support for the WAIT command. Thanks https://github.com/eshizhan + * Better shutdown support for the PubSub Worker Thread. It now properly + cleans up the connection, unsubscribes from any channels and patterns + previously subscribed to and consumes any waiting messages on the socket. + * Added the ability to sleep for a brief period in the event of a + WatchError occuring. Thanks Joshua Harlow. + * Fixed a bug with pipeline error reporting when dealing with characters + in error messages that could not be encoded to the connection's + character set. Thanks Hendrik Muhs. + * Fixed a bug in Sentinel connections that would inadvertantly connect + to the master when the connection pool resets. Thanks + https://github.com/df3n5 + * Better timeout support in Pubsub get_message. Thanks Andy Isaacson. + * Fixed a bug with the HiredisParser that would cause the parser to + get stuck in an endless loop if a specific number of bytes were + delivered from the socket. This fix also increases performance of + parsing large responses from the Redis server. + * Added support for ZREVRANGEBYLEX. + * ConnectionErrors are now raised if Redis refuses a connection due to + the maxclients limit being exceeded. Thanks Roman Karpovich. + * max_connections can now be set when instantiating client instances. + Thanks Ohad Perry. + +------------------------------------------------------------------- Old: ---- redis-2.10.3.tar.gz New: ---- redis-2.10.5.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-redis.spec ++++++ --- /var/tmp/diff_new_pack.eBRrUU/_old 2016-12-08 00:30:21.000000000 +0100 +++ /var/tmp/diff_new_pack.eBRrUU/_new 2016-12-08 00:30:21.000000000 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-redis # -# Copyright (c) 2014 SUSE LINUX Products GmbH, Nuernberg, Germany. +# Copyright (c) 2016 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -17,13 +17,13 @@ Name: python-redis -Version: 2.10.3 +Version: 2.10.5 Release: 0 Url: http://github.com/andymccurdy/redis-py Summary: Python client for Redis key-value store License: MIT Group: Development/Languages/Python -Source: https://pypi.python.org/packages/source/r/redis/redis-%{version}.tar.gz +Source: https://pypi.io/packages/source/r/redis/redis-%{version}.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-build BuildRequires: python-devel BuildRequires: python-py ++++++ redis-2.10.3.tar.gz -> redis-2.10.5.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-2.10.3/CHANGES new/redis-2.10.5/CHANGES --- old/redis-2.10.3/CHANGES 2014-08-14 19:18:42.000000000 +0200 +++ new/redis-2.10.5/CHANGES 2015-11-03 01:19:33.000000000 +0100 @@ -1,3 +1,31 @@ +* 2.10.5 + * Allow URL encoded parameters in Redis URLs. Characters like a "/" can + now be URL encoded and redis-py will correctly decode them. Thanks + Paul Keene. + * Added support for the WAIT command. Thanks https://github.com/eshizhan + * Better shutdown support for the PubSub Worker Thread. It now properly + cleans up the connection, unsubscribes from any channels and patterns + previously subscribed to and consumes any waiting messages on the socket. + * Added the ability to sleep for a brief period in the event of a + WatchError occuring. Thanks Joshua Harlow. + * Fixed a bug with pipeline error reporting when dealing with characters + in error messages that could not be encoded to the connection's + character set. Thanks Hendrik Muhs. + * Fixed a bug in Sentinel connections that would inadvertantly connect + to the master when the connection pool resets. Thanks + https://github.com/df3n5 + * Better timeout support in Pubsub get_message. Thanks Andy Isaacson. + * Fixed a bug with the HiredisParser that would cause the parser to + get stuck in an endless loop if a specific number of bytes were + delivered from the socket. This fix also increases performance of + parsing large responses from the Redis server. + * Added support for ZREVRANGEBYLEX. + * ConnectionErrors are now raised if Redis refuses a connection due to + the maxclients limit being exceeded. Thanks Roman Karpovich. + * max_connections can now be set when instantiating client instances. + Thanks Ohad Perry. +* 2.10.4 + (skipped due to a PyPI snafu) * 2.10.3 * Fixed a bug with the bytearray support introduced in 2.10.2. Thanks Josh Owen. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-2.10.3/PKG-INFO new/redis-2.10.5/PKG-INFO --- old/redis-2.10.3/PKG-INFO 2014-08-14 19:19:16.000000000 +0200 +++ new/redis-2.10.5/PKG-INFO 2015-11-03 01:21:05.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: redis -Version: 2.10.3 +Version: 2.10.5 Summary: Python client for Redis key-value store Home-page: http://github.com/andymccurdy/redis-py Author: Andy McCurdy @@ -76,7 +76,7 @@ `this comment on issue #151 <https://github.com/andymccurdy/redis-py/issues/151#issuecomment-1545015>`_ for details). - * **SCAN/SSCAN/HSCAN/ZSCAN**: The *SCAN commands are implemented as they + * **SCAN/SSCAN/HSCAN/ZSCAN**: The \*SCAN commands are implemented as they exist in the Redis documentation. In addition, each command has an equivilant iterator method. These are purely for convenience so the user doesn't have to keep track of the cursor while iterating. Use the @@ -134,7 +134,7 @@ you want to control the socket behavior within an async framework. To instantiate a client class using your own connection, you need to create a connection pool, passing your class to the connection_class argument. - Other keyword parameters your pass to the pool will be passed to the class + Other keyword parameters you pass to the pool will be passed to the class specified during initialization. .. code-block:: pycon @@ -621,7 +621,7 @@ >>> sentinel.discover_slaves('mymaster') [('127.0.0.1', 6380)] - You can also create Redis client connections from a Sentinel instnace. You can + You can also create Redis client connections from a Sentinel instance. You can connect to either the master (for write operations) or a slave (for read-only operations). @@ -651,7 +651,7 @@ Scan Iterators ^^^^^^^^^^^^^^ - The *SCAN commands introduced in Redis 2.8 can be cumbersome to use. While + The \*SCAN commands introduced in Redis 2.8 can be cumbersome to use. While these commands are fully supported, redis-py also exposes the following methods that return Python iterators for convenience: `scan_iter`, `hscan_iter`, `sscan_iter` and `zscan_iter`. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-2.10.3/README.rst new/redis-2.10.5/README.rst --- old/redis-2.10.3/README.rst 2014-06-16 22:42:56.000000000 +0200 +++ new/redis-2.10.5/README.rst 2015-01-04 01:21:38.000000000 +0100 @@ -68,7 +68,7 @@ `this comment on issue #151 <https://github.com/andymccurdy/redis-py/issues/151#issuecomment-1545015>`_ for details). -* **SCAN/SSCAN/HSCAN/ZSCAN**: The *SCAN commands are implemented as they +* **SCAN/SSCAN/HSCAN/ZSCAN**: The \*SCAN commands are implemented as they exist in the Redis documentation. In addition, each command has an equivilant iterator method. These are purely for convenience so the user doesn't have to keep track of the cursor while iterating. Use the @@ -126,7 +126,7 @@ you want to control the socket behavior within an async framework. To instantiate a client class using your own connection, you need to create a connection pool, passing your class to the connection_class argument. -Other keyword parameters your pass to the pool will be passed to the class +Other keyword parameters you pass to the pool will be passed to the class specified during initialization. .. code-block:: pycon @@ -613,7 +613,7 @@ >>> sentinel.discover_slaves('mymaster') [('127.0.0.1', 6380)] -You can also create Redis client connections from a Sentinel instnace. You can +You can also create Redis client connections from a Sentinel instance. You can connect to either the master (for write operations) or a slave (for read-only operations). @@ -643,7 +643,7 @@ Scan Iterators ^^^^^^^^^^^^^^ -The *SCAN commands introduced in Redis 2.8 can be cumbersome to use. While +The \*SCAN commands introduced in Redis 2.8 can be cumbersome to use. While these commands are fully supported, redis-py also exposes the following methods that return Python iterators for convenience: `scan_iter`, `hscan_iter`, `sscan_iter` and `zscan_iter`. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-2.10.3/redis/__init__.py new/redis-2.10.5/redis/__init__.py --- old/redis-2.10.3/redis/__init__.py 2014-08-14 19:17:30.000000000 +0200 +++ new/redis-2.10.5/redis/__init__.py 2015-11-03 01:19:40.000000000 +0100 @@ -22,7 +22,7 @@ ) -__version__ = '2.10.3' +__version__ = '2.10.5' VERSION = tuple(map(int, __version__.split('.'))) __all__ = [ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-2.10.3/redis/_compat.py new/redis-2.10.5/redis/_compat.py --- old/redis-2.10.3/redis/_compat.py 2014-06-17 00:34:58.000000000 +0200 +++ new/redis-2.10.5/redis/_compat.py 2015-09-29 00:28:42.000000000 +0200 @@ -3,6 +3,7 @@ if sys.version_info[0] < 3: + from urllib import unquote from urlparse import parse_qs, urlparse from itertools import imap, izip from string import letters as ascii_letters @@ -12,15 +13,40 @@ except ImportError: from StringIO import StringIO as BytesIO - iteritems = lambda x: x.iteritems() - iterkeys = lambda x: x.iterkeys() - itervalues = lambda x: x.itervalues() - nativestr = lambda x: \ - x if isinstance(x, str) else x.encode('utf-8', 'replace') - u = lambda x: x.decode() - b = lambda x: x - next = lambda x: x.next() - byte_to_chr = lambda x: x + # special unicode handling for python2 to avoid UnicodeDecodeError + def safe_unicode(obj, *args): + """ return the unicode representation of obj """ + try: + return unicode(obj, *args) + except UnicodeDecodeError: + # obj is byte string + ascii_text = str(obj).encode('string_escape') + return unicode(ascii_text) + + def iteritems(x): + return x.iteritems() + + def iterkeys(x): + return x.iterkeys() + + def itervalues(x): + return x.itervalues() + + def nativestr(x): + return x if isinstance(x, str) else x.encode('utf-8', 'replace') + + def u(x): + return x.decode() + + def b(x): + return x + + def next(x): + return x.next() + + def byte_to_chr(x): + return x + unichr = unichr xrange = xrange basestring = basestring @@ -28,19 +54,32 @@ bytes = str long = long else: - from urllib.parse import parse_qs, urlparse + from urllib.parse import parse_qs, unquote, urlparse from io import BytesIO from string import ascii_letters from queue import Queue - iteritems = lambda x: iter(x.items()) - iterkeys = lambda x: iter(x.keys()) - itervalues = lambda x: iter(x.values()) - byte_to_chr = lambda x: chr(x) - nativestr = lambda x: \ - x if isinstance(x, str) else x.decode('utf-8', 'replace') - u = lambda x: x - b = lambda x: x.encode('latin-1') if not isinstance(x, bytes) else x + def iteritems(x): + return iter(x.items()) + + def iterkeys(x): + return iter(x.keys()) + + def itervalues(x): + return iter(x.values()) + + def byte_to_chr(x): + return chr(x) + + def nativestr(x): + return x if isinstance(x, str) else x.decode('utf-8', 'replace') + + def u(x): + return x + + def b(x): + return x.encode('latin-1') if not isinstance(x, bytes) else x + next = next unichr = chr imap = map @@ -48,6 +87,7 @@ xrange = range basestring = str unicode = str + safe_unicode = str bytes = bytes long = int diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-2.10.3/redis/client.py new/redis-2.10.5/redis/client.py --- old/redis-2.10.3/redis/client.py 2014-07-21 19:53:42.000000000 +0200 +++ new/redis-2.10.5/redis/client.py 2015-11-03 00:02:54.000000000 +0100 @@ -3,10 +3,12 @@ import datetime import sys import warnings +import time import threading import time as mod_time from redis._compat import (b, basestring, bytes, imap, iteritems, iterkeys, - itervalues, izip, long, nativestr, unicode) + itervalues, izip, long, nativestr, unicode, + safe_unicode) from redis.connection import (ConnectionPool, UnixDomainSocketConnection, SSLConnection, Token) from redis.lock import Lock, LuaLock @@ -57,7 +59,8 @@ def dict_merge(*dicts): merged = {} - [merged.update(d) for d in dicts] + for d in dicts: + merged.update(d) return merged @@ -397,7 +400,8 @@ charset=None, errors=None, decode_responses=False, retry_on_timeout=False, ssl=False, ssl_keyfile=None, ssl_certfile=None, - ssl_cert_reqs=None, ssl_ca_certs=None): + ssl_cert_reqs=None, ssl_ca_certs=None, + max_connections=None): if not connection_pool: if charset is not None: warnings.warn(DeprecationWarning( @@ -415,7 +419,8 @@ 'encoding': encoding, 'encoding_errors': encoding_errors, 'decode_responses': decode_responses, - 'retry_on_timeout': retry_on_timeout + 'retry_on_timeout': retry_on_timeout, + 'max_connections': max_connections } # based on input, setup appropriate connection args if unix_socket_path is not None: @@ -476,6 +481,7 @@ """ shard_hint = kwargs.pop('shard_hint', None) value_from_callable = kwargs.pop('value_from_callable', False) + watch_delay = kwargs.pop('watch_delay', None) with self.pipeline(True, shard_hint) as pipe: while 1: try: @@ -485,6 +491,8 @@ exec_value = pipe.execute() return func_value if value_from_callable else exec_value except WatchError: + if watch_delay is not None and watch_delay > 0: + time.sleep(watch_delay) continue def lock(self, name, timeout=None, sleep=0.1, blocking_timeout=None, @@ -762,6 +770,15 @@ """ return self.execute_command('TIME') + def wait(self, num_replicas, timeout): + """ + Redis synchronous replication + That returns the number of replicas that processed the query when + we finally have at least ``num_replicas``, or when the ``timeout`` was + reached. + """ + return self.execute_command('WAIT', num_replicas, timeout) + # BASIC KEY COMMANDS def append(self, key, value): """ @@ -1646,6 +1663,22 @@ pieces.extend([Token('LIMIT'), start, num]) return self.execute_command(*pieces) + def zrevrangebylex(self, name, max, min, start=None, num=None): + """ + Return the reversed lexicographical range of values from sorted set + ``name`` between ``max`` and ``min``. + + If ``start`` and ``num`` are specified, then return a slice of the + range. + """ + if (start is not None and num is None) or \ + (num is not None and start is None): + raise RedisError("``start`` and ``num`` must both be specified") + pieces = ['ZREVRANGEBYLEX', name, max, min] + if start is not None and num is not None: + pieces.extend([Token('LIMIT'), start, num]) + return self.execute_command(*pieces) + def zrangebyscore(self, name, min, max, start=None, num=None, withscores=False, score_cast_func=float): """ @@ -1799,12 +1832,12 @@ "Adds the specified elements to the specified HyperLogLog." return self.execute_command('PFADD', name, *values) - def pfcount(self, name): + def pfcount(self, *sources): """ Return the approximated cardinality of - the set observed by the HyperLogLog at key. + the set observed by the HyperLogLog at key(s). """ - return self.execute_command('PFCOUNT', name) + return self.execute_command('PFCOUNT', *sources) def pfmerge(self, dest, *sources): "Merge N different HyperLogLogs into a single one." @@ -2142,10 +2175,10 @@ # previously listening to return command(*args) - def parse_response(self, block=True): + def parse_response(self, block=True, timeout=0): "Parse the response from a publish/subscribe command" connection = self.connection - if not block and not connection.can_read(): + if not block and not connection.can_read(timeout=timeout): return None return self._execute(connection, connection.read_response) @@ -2216,9 +2249,15 @@ if response is not None: yield response - def get_message(self, ignore_subscribe_messages=False): - "Get the next message if one is available, otherwise None" - response = self.parse_response(block=False) + def get_message(self, ignore_subscribe_messages=False, timeout=0): + """ + Get the next message if one is available, otherwise None. + + If timeout is specified, the system will wait for `timeout` seconds + before returning. Timeout should be specified as a floating point + number. + """ + response = self.parse_response(block=False, timeout=timeout) if response: return self.handle_message(response, ignore_subscribe_messages) return None @@ -2282,30 +2321,39 @@ for pattern, handler in iteritems(self.patterns): if handler is None: raise PubSubError("Pattern: '%s' has no handler registered") - pubsub = self - class WorkerThread(threading.Thread): - def __init__(self, *args, **kwargs): - super(WorkerThread, self).__init__(*args, **kwargs) - self._running = False - - def run(self): - if self._running: - return - self._running = True - while self._running and pubsub.subscribed: - pubsub.get_message(ignore_subscribe_messages=True) - mod_time.sleep(sleep_time) - - def stop(self): - self._running = False - self.join() - - thread = WorkerThread() + thread = PubSubWorkerThread(self, sleep_time) thread.start() return thread +class PubSubWorkerThread(threading.Thread): + def __init__(self, pubsub, sleep_time): + super(PubSubWorkerThread, self).__init__() + self.pubsub = pubsub + self.sleep_time = sleep_time + self._running = False + + def run(self): + if self._running: + return + self._running = True + pubsub = self.pubsub + sleep_time = self.sleep_time + while pubsub.subscribed: + pubsub.get_message(ignore_subscribe_messages=True, + timeout=sleep_time) + pubsub.close() + self._running = False + + def stop(self): + # stopping simply unsubscribes from all channels and patterns. + # the unsubscribe responses that are generated will short circuit + # the loop in run(), calling pubsub.close() to clean up the connection + self.pubsub.unsubscribe() + self.pubsub.punsubscribe() + + class BasePipeline(object): """ Pipelines provide a way to transmit multiple commands to the Redis server @@ -2526,9 +2574,9 @@ raise r def annotate_exception(self, exception, number, command): - cmd = unicode(' ').join(imap(unicode, command)) + cmd = safe_unicode(' ').join(imap(safe_unicode, command)) msg = unicode('Command # %d (%s) of pipeline caused error: %s') % ( - number, cmd, unicode(exception.args[0])) + number, cmd, safe_unicode(exception.args[0])) exception.args = (msg,) + exception.args[1:] def parse_response(self, connection, command_name, **options): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-2.10.3/redis/connection.py new/redis-2.10.5/redis/connection.py --- old/redis-2.10.3/redis/connection.py 2014-08-14 19:15:19.000000000 +0200 +++ new/redis-2.10.5/redis/connection.py 2015-11-02 20:25:13.000000000 +0100 @@ -16,7 +16,8 @@ from redis._compat import (b, xrange, imap, byte_to_chr, unicode, bytes, long, BytesIO, nativestr, basestring, iteritems, - LifoQueue, Empty, Full, urlparse, parse_qs) + LifoQueue, Empty, Full, urlparse, parse_qs, + unquote) from redis.exceptions import ( RedisError, ConnectionError, @@ -79,7 +80,9 @@ class BaseParser(object): EXCEPTION_CLASSES = { - 'ERR': ResponseError, + 'ERR': { + 'max number of clients reached': ConnectionError + }, 'EXECABORT': ExecAbortError, 'LOADING': BusyLoadingError, 'NOSCRIPT': NoScriptError, @@ -91,7 +94,10 @@ error_code = response.split(' ')[0] if error_code in self.EXCEPTION_CLASSES: response = response[len(error_code) + 1:] - return self.EXCEPTION_CLASSES[error_code](response) + exception_class = self.EXCEPTION_CLASSES[error_code] + if isinstance(exception_class, dict): + exception_class = exception_class.get(response, ResponseError) + return exception_class(response) return ResponseError(response) @@ -179,8 +185,16 @@ self.bytes_read = 0 def close(self): - self.purge() - self._buffer.close() + try: + self.purge() + self._buffer.close() + except: + # issue #633 suggests the purge/close somehow raised a + # BadFileDescriptor error. Perhaps the client ran out of + # memory or something else? It's probably OK to ignore + # any error being raised from purge/close since we're + # removing the reference to the instance below. + pass self._buffer = None self._sock = None @@ -345,14 +359,6 @@ self._reader.feed(self._buffer, 0, bufflen) else: self._reader.feed(buffer) - # proactively, but not conclusively, check if more data is in the - # buffer. if the data received doesn't end with \r\n, there's more. - if HIREDIS_USE_BYTE_BUFFER: - if bufflen > 2 and self._buffer[bufflen - 2:bufflen] != SYM_CRLF: - continue - else: - if not buffer.endswith(SYM_CRLF): - continue response = self._reader.gets() # if an older version of hiredis is installed, we need to attempt # to convert ResponseErrors to their appropriate types. @@ -542,11 +548,12 @@ e = sys.exc_info()[1] self.disconnect() if len(e.args) == 1: - _errno, errmsg = 'UNKNOWN', e.args[0] + errno, errmsg = 'UNKNOWN', e.args[0] else: - _errno, errmsg = e.args + errno = e.args[0] + errmsg = e.args[1] raise ConnectionError("Error %s while writing to socket. %s." % - (_errno, errmsg)) + (errno, errmsg)) except: self.disconnect() raise @@ -555,13 +562,14 @@ "Pack and send a command to the Redis server" self.send_packed_command(self.pack_command(*args)) - def can_read(self): + def can_read(self, timeout=0): "Poll the socket to see if there's data that can be read." sock = self._sock if not sock: self.connect() sock = self._sock - return bool(select([sock], [], [], 0)[0]) or self._parser.can_read() + return self._parser.can_read() or \ + bool(select([sock], [], [], timeout)[0]) def read_response(self): "Read the response from a previously sent command" @@ -585,7 +593,7 @@ elif isinstance(value, float): value = b(repr(value)) elif not isinstance(value, basestring): - value = str(value) + value = unicode(value) if isinstance(value, unicode): value = value.encode(self.encoding, self.encoding_errors) return value @@ -728,7 +736,7 @@ class ConnectionPool(object): "Generic connection pool" @classmethod - def from_url(cls, url, db=None, **kwargs): + def from_url(cls, url, db=None, decode_components=False, **kwargs): """ Return a connection pool configured from the given URL. @@ -752,6 +760,12 @@ If none of these options are specified, db=0 is used. + The ``decode_components`` argument allows this function to work with + percent-encoded URLs. If this argument is set to ``True`` all ``%xx`` + escapes will be replaced by their single-character equivalents after + the URL has been parsed. This only applies to the ``hostname``, + ``path``, and ``password`` components. + Any additional querystring arguments and keyword arguments will be passed along to the ConnectionPool class's initializer. In the case of conflicting arguments, querystring arguments always win. @@ -776,26 +790,35 @@ if value and len(value) > 0: url_options[name] = value[0] + if decode_components: + password = unquote(url.password) if url.password else None + path = unquote(url.path) if url.path else None + hostname = unquote(url.hostname) if url.hostname else None + else: + password = url.password + path = url.path + hostname = url.hostname + # We only support redis:// and unix:// schemes. if url.scheme == 'unix': url_options.update({ - 'password': url.password, - 'path': url.path, + 'password': password, + 'path': path, 'connection_class': UnixDomainSocketConnection, }) else: url_options.update({ - 'host': url.hostname, + 'host': hostname, 'port': int(url.port or 6379), - 'password': url.password, + 'password': password, }) # If there's a path argument, use it as the db argument if a # querystring value wasn't specified - if 'db' not in url_options and url.path: + if 'db' not in url_options and path: try: - url_options['db'] = int(url.path.replace('/', '')) + url_options['db'] = int(path.replace('/', '')) except (AttributeError, ValueError): pass diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-2.10.3/redis/sentinel.py new/redis-2.10.5/redis/sentinel.py --- old/redis-2.10.3/redis/sentinel.py 2014-06-17 00:34:58.000000000 +0200 +++ new/redis-2.10.5/redis/sentinel.py 2014-09-18 18:01:10.000000000 +0200 @@ -129,6 +129,8 @@ self.disconnect() self.reset() self.__init__(self.service_name, self.sentinel_manager, + is_master=self.is_master, + check_connection=self.check_connection, connection_class=self.connection_class, max_connections=self.max_connections, **self.connection_kwargs) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-2.10.3/redis.egg-info/PKG-INFO new/redis-2.10.5/redis.egg-info/PKG-INFO --- old/redis-2.10.3/redis.egg-info/PKG-INFO 2014-08-14 19:19:15.000000000 +0200 +++ new/redis-2.10.5/redis.egg-info/PKG-INFO 2015-11-03 01:21:05.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: redis -Version: 2.10.3 +Version: 2.10.5 Summary: Python client for Redis key-value store Home-page: http://github.com/andymccurdy/redis-py Author: Andy McCurdy @@ -76,7 +76,7 @@ `this comment on issue #151 <https://github.com/andymccurdy/redis-py/issues/151#issuecomment-1545015>`_ for details). - * **SCAN/SSCAN/HSCAN/ZSCAN**: The *SCAN commands are implemented as they + * **SCAN/SSCAN/HSCAN/ZSCAN**: The \*SCAN commands are implemented as they exist in the Redis documentation. In addition, each command has an equivilant iterator method. These are purely for convenience so the user doesn't have to keep track of the cursor while iterating. Use the @@ -134,7 +134,7 @@ you want to control the socket behavior within an async framework. To instantiate a client class using your own connection, you need to create a connection pool, passing your class to the connection_class argument. - Other keyword parameters your pass to the pool will be passed to the class + Other keyword parameters you pass to the pool will be passed to the class specified during initialization. .. code-block:: pycon @@ -621,7 +621,7 @@ >>> sentinel.discover_slaves('mymaster') [('127.0.0.1', 6380)] - You can also create Redis client connections from a Sentinel instnace. You can + You can also create Redis client connections from a Sentinel instance. You can connect to either the master (for write operations) or a slave (for read-only operations). @@ -651,7 +651,7 @@ Scan Iterators ^^^^^^^^^^^^^^ - The *SCAN commands introduced in Redis 2.8 can be cumbersome to use. While + The \*SCAN commands introduced in Redis 2.8 can be cumbersome to use. While these commands are fully supported, redis-py also exposes the following methods that return Python iterators for convenience: `scan_iter`, `hscan_iter`, `sscan_iter` and `zscan_iter`. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-2.10.3/redis.egg-info/SOURCES.txt new/redis-2.10.5/redis.egg-info/SOURCES.txt --- old/redis-2.10.3/redis.egg-info/SOURCES.txt 2014-08-14 19:19:16.000000000 +0200 +++ new/redis-2.10.5/redis.egg-info/SOURCES.txt 2015-11-03 01:21:05.000000000 +0100 @@ -3,6 +3,7 @@ LICENSE MANIFEST.in README.rst +setup.cfg setup.py redis/__init__.py redis/_compat.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-2.10.3/setup.cfg new/redis-2.10.5/setup.cfg --- old/redis-2.10.3/setup.cfg 2014-08-14 19:19:16.000000000 +0200 +++ new/redis-2.10.5/setup.cfg 2015-11-03 01:21:05.000000000 +0100 @@ -1,3 +1,6 @@ +[bdist_wheel] +universal = 1 + [egg_info] tag_build = tag_date = 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-2.10.3/setup.py new/redis-2.10.5/setup.py --- old/redis-2.10.3/setup.py 2014-06-17 00:34:58.000000000 +0200 +++ new/redis-2.10.5/setup.py 2015-09-29 00:29:14.000000000 +0200 @@ -23,7 +23,9 @@ except ImportError: from distutils.core import setup - PyTest = lambda x: x + + def PyTest(x): + x f = open(os.path.join(os.path.dirname(__file__), 'README.rst')) long_description = f.read() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-2.10.3/tests/test_commands.py new/redis-2.10.5/tests/test_commands.py --- old/redis-2.10.3/tests/test_commands.py 2014-07-21 19:53:42.000000000 +0200 +++ new/redis-2.10.5/tests/test_commands.py 2015-11-02 19:08:25.000000000 +0100 @@ -112,7 +112,7 @@ r['a'] = 'foo' assert isinstance(r.object('refcount', 'a'), int) assert isinstance(r.object('idletime', 'a'), int) - assert r.object('encoding', 'a') == b('raw') + assert r.object('encoding', 'a') in (b('raw'), b('embstr')) assert r.object('idletime', 'invalid-key') is None def test_ping(self, r): @@ -959,6 +959,17 @@ assert r.zrangebylex('a', '[f', '+') == [b('f'), b('g')] assert r.zrangebylex('a', '-', '+', start=3, num=2) == [b('d'), b('e')] + @skip_if_server_version_lt('2.9.9') + def test_zrevrangebylex(self, r): + r.zadd('a', a=0, b=0, c=0, d=0, e=0, f=0, g=0) + assert r.zrevrangebylex('a', '[c', '-') == [b('c'), b('b'), b('a')] + assert r.zrevrangebylex('a', '(c', '-') == [b('b'), b('a')] + assert r.zrevrangebylex('a', '(g', '[aaa') == \ + [b('f'), b('e'), b('d'), b('c'), b('b')] + assert r.zrevrangebylex('a', '+', '[f') == [b('g'), b('f')] + assert r.zrevrangebylex('a', '+', '-', start=3, num=2) == \ + [b('d'), b('c')] + def test_zrangebyscore(self, r): r.zadd('a', a1=1, a2=2, a3=3, a4=4, a5=5) assert r.zrangebyscore('a', 2, 4) == [b('a2'), b('a3'), b('a4')] @@ -1106,6 +1117,10 @@ members = set([b('1'), b('2'), b('3')]) r.pfadd('a', *members) assert r.pfcount('a') == len(members) + members_b = set([b('2'), b('3'), b('4')]) + r.pfadd('b', *members_b) + assert r.pfcount('b') == len(members_b) + assert r.pfcount('a', 'b') == len(members_b.union(members)) @skip_if_server_version_lt('2.8.9') def test_pfmerge(self, r): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-2.10.3/tests/test_connection_pool.py new/redis-2.10.5/tests/test_connection_pool.py --- old/redis-2.10.3/tests/test_connection_pool.py 2014-06-17 00:34:58.000000000 +0200 +++ new/redis-2.10.5/tests/test_connection_pool.py 2015-02-10 02:18:07.000000000 +0100 @@ -163,6 +163,17 @@ 'password': None, } + def test_quoted_hostname(self): + pool = redis.ConnectionPool.from_url('redis://my %2F host %2B%3D+', + decode_components=True) + assert pool.connection_class == redis.Connection + assert pool.connection_kwargs == { + 'host': 'my / host +=+', + 'port': 6379, + 'db': 0, + 'password': None, + } + def test_port(self): pool = redis.ConnectionPool.from_url('redis://localhost:6380') assert pool.connection_class == redis.Connection @@ -183,6 +194,18 @@ 'password': 'mypassword', } + def test_quoted_password(self): + pool = redis.ConnectionPool.from_url( + 'redis://:%2Fmypass%2F%2B word%3D%24+@localhost', + decode_components=True) + assert pool.connection_class == redis.Connection + assert pool.connection_kwargs == { + 'host': 'localhost', + 'port': 6379, + 'db': 0, + 'password': '/mypass/+ word=$+', + } + def test_db_as_argument(self): pool = redis.ConnectionPool.from_url('redis://localhost', db='1') assert pool.connection_class == redis.Connection @@ -259,6 +282,28 @@ 'db': 0, 'password': 'mypassword', } + + def test_quoted_password(self): + pool = redis.ConnectionPool.from_url( + 'unix://:%2Fmypass%2F%2B word%3D%24+@/socket', + decode_components=True) + assert pool.connection_class == redis.UnixDomainSocketConnection + assert pool.connection_kwargs == { + 'path': '/socket', + 'db': 0, + 'password': '/mypass/+ word=$+', + } + + def test_quoted_path(self): + pool = redis.ConnectionPool.from_url( + 'unix://:mypassword@/my%2Fpath%2Fto%2F..%2F+_%2B%3D%24ocket', + decode_components=True) + assert pool.connection_class == redis.UnixDomainSocketConnection + assert pool.connection_kwargs == { + 'path': '/my/path/to/../+_+=$ocket', + 'db': 0, + 'password': 'mypassword', + } def test_db_as_argument(self): pool = redis.ConnectionPool.from_url('unix:///socket', db=1) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-2.10.3/tests/test_encoding.py new/redis-2.10.5/tests/test_encoding.py --- old/redis-2.10.3/tests/test_encoding.py 2014-06-17 00:34:58.000000000 +0200 +++ new/redis-2.10.5/tests/test_encoding.py 2015-06-06 21:44:46.000000000 +0200 @@ -23,6 +23,13 @@ r.rpush('a', *result) assert r.lrange('a', 0, -1) == result + def test_object_value(self, r): + unicode_string = unichr(3456) + u('abcd') + unichr(3421) + r['unicode-string'] = Exception(unicode_string) + cached_val = r['unicode-string'] + assert isinstance(cached_val, unicode) + assert unicode_string == cached_val + class TestCommandsAndTokensArentEncoded(object): @pytest.fixture() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-2.10.3/tests/test_scripting.py new/redis-2.10.5/tests/test_scripting.py --- old/redis-2.10.3/tests/test_scripting.py 2014-06-16 22:42:56.000000000 +0200 +++ new/redis-2.10.5/tests/test_scripting.py 2015-01-02 20:04:16.000000000 +0100 @@ -10,6 +10,17 @@ value = tonumber(value) return value * ARGV[1]""" +msgpack_hello_script = """ +local message = cmsgpack.unpack(ARGV[1]) +local name = message['name'] +return "hello " .. name +""" +msgpack_hello_script_broken = """ +local message = cmsgpack.unpack(ARGV[1]) +local names = message['name'] +return "hello " .. name +""" + class TestScripting(object): @pytest.fixture(autouse=True) @@ -80,3 +91,25 @@ assert r.script_exists(multiply.sha) == [False] # [SET worked, GET 'a', result of multiple script] assert pipe.execute() == [True, b('2'), 6] + + def test_eval_msgpack_pipeline_error_in_lua(self, r): + msgpack_hello = r.register_script(msgpack_hello_script) + assert not msgpack_hello.sha + + pipe = r.pipeline() + + # avoiding a dependency to msgpack, this is the output of + # msgpack.dumps({"name": "joe"}) + msgpack_message_1 = b'\x81\xa4name\xa3Joe' + + msgpack_hello(args=[msgpack_message_1], client=pipe) + + assert r.script_exists(msgpack_hello.sha) == [True] + assert pipe.execute()[0] == b'hello Joe' + + msgpack_hello_broken = r.register_script(msgpack_hello_script_broken) + + msgpack_hello_broken(args=[msgpack_message_1], client=pipe) + with pytest.raises(exceptions.ResponseError) as excinfo: + pipe.execute() + assert excinfo.type == exceptions.ResponseError
participants (1)
-
root@hilbert.suse.de