def psubscribe(self, *context, **delivery_mechanisms): """ Subscribe to channel patterns. Patterns are specified as keyword arguments. The pattern string is the key and the value is either a callable, or a Queue.Queue. If a callable, then it will be invoked when a message arrives that pattern. The argument will be a BusMessage. If a keyword argument's value is a Queue.Queue instance, then the BusMessage that encapsulates an incoming message will be placed on the queue. :param *context: an optional data structure that will be included in the BusMessage object that is passed to the callable(s) when messages arrive. The context will be available in the BusMessage as property 'context'. Used, for instance, to pass an Event object that the callable sets when it is done. :type *context: <any> :param *delivery_mechanisms-keys: keys of keyword arguments are channel patterns to subscribe to. :type *delivery_mechanisms-keys: string :param *delivery_mechanisms-values: either a callable or a Queue.Queue instance :type *delivery_mechanisms-values: {callable | Queue.Queue} :returns: the number of channels the caller is now subscribed to. :rtype: int :raises TimeoutError: when server does not respond in time. """ if self.connection is None: self.connection = self.connection_pool.get_connection() if context: context = context[0] else: context = None new_patterns = {} for pattern, handler_or_queue in iteritems(delivery_mechanisms): # Map channel to a tuple containing the handler or queue and # the context for use in handle_message(): new_patterns[self.encode(pattern)] = (handler_or_queue, context) # Get the access lock to the dict that # holds the pattern-subscriptions: self.patterns.acquire() try: ret_val = self.execute_command('PSUBSCRIBE', *iterkeys(new_patterns)) # Now update the self.patterns data structure; # acquire the access lock. # (update the patternss dict AFTER we send the command. we don't want to # subscribe twice to these patterns, once for the command and again # for the reconnection.): self.patterns.update(new_patterns) finally: self.patterns.release() return ret_val
def on_connect(self, connection): "Re-subscribe to any channels and patterns previously subscribed to" # NOTE: for python3, we can't pass bytestrings as keyword arguments # so we need to decode channel/pattern names back to unicode strings # before passing them to [p]subscribe. if self.channels: channels = {} for k, v in iteritems(self.channels): if not self.decode_responses: k = k.decode(self.encoding, self.encoding_errors) channels[k] = v self.subscribe(**channels) if self.patterns: patterns = {} for k, v in iteritems(self.patterns): if not self.decode_responses: k = k.decode(self.encoding, self.encoding_errors) patterns[k] = v self.psubscribe(**patterns)
def _connect(self): "Create a TCP socket connection" # we want to mimic what socket.create_connection does to support # ipv4/ipv6, but we want to set options prior to calling # socket.connect() err = None for res in socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM): family, socktype, proto, canonname, socket_address = res #@UnusedVariable sock = None try: sock = socket.socket(family, socktype, proto) # TCP_NODELAY sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) # TCP_KEEPALIVE if self.socket_keepalive: sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) for k, v in iteritems(self.socket_keepalive_options): sock.setsockopt(socket.SOL_TCP, k, v) # set the socket_connect_timeout before we connect sock.settimeout(self.socket_connect_timeout) # connect sock.connect(socket_address) # set the socket_timeout now that we're connected sock.settimeout(self.socket_timeout) return sock except socket.error as _: err = _ if sock is not None: sock.close() if err is not None: raise err raise socket.error("socket.getaddrinfo returned an empty list")
def from_url(cls, url, db=None, decode_components=False, **kwargs): """ Return a connection pool configured from the given URL. For example:: redis://[:password]@localhost:6379/0 rediss://[:password]@localhost:6379/0 unix://[:password]@/path/to/socket.sock?db=0 Three URL schemes are supported: redis:// creates a normal TCP socket connection rediss:// creates a SSL wrapped TCP socket connection unix:// creates a Unix Domain Socket connection There are several ways to specify a database number. The parse function will return the first specified option: 1. A ``db`` querystring option, e.g. redis://localhost?db=0 2. If using the redis:// scheme, the path argument of the url, e.g. redis://localhost/0 3. The ``db`` argument to this function. 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. """ url_string = url url = urlparse(url) qs = '' # in python2.6, custom URL schemes don't recognize querystring values # they're left as part of the url.path. if '?' in url.path and not url.query: # chop the querystring including the ? off the end of the url # and reparse it. qs = url.path.split('?', 1)[1] url = urlparse(url_string[:-(len(qs) + 1)]) else: qs = url.query url_options = {} for name, value in iteritems(parse_qs(qs)): 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': password, 'path': path, 'connection_class': UnixDomainSocketConnection, }) else: url_options.update({ 'host': hostname, 'port': int(url.port or 6379), '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 path: try: url_options['db'] = int(path.replace('/', '')) except (AttributeError, ValueError): pass if url.scheme == 'rediss': url_options['connection_class'] = SSLConnection # last shot at the db value url_options['db'] = int(url_options.get('db', db or 0)) # update the arguments from the URL values kwargs.update(url_options) # backwards compatability if 'charset' in kwargs: warnings.warn(DeprecationWarning( '"charset" is deprecated. Use "encoding" instead')) kwargs['encoding'] = kwargs.pop('charset') if 'errors' in kwargs: warnings.warn(DeprecationWarning( '"errors" is deprecated. Use "encoding_errors" instead')) kwargs['encoding_errors'] = kwargs.pop('errors') return cls(**kwargs)