Пример #1
0
    async def _georadiusgeneric(self, command, *args, **kwargs):
        pieces = list(args)
        if kwargs['unit'] and kwargs['unit'] not in ('m', 'km', 'mi', 'ft'):
            raise RedisError("GEORADIUS invalid unit")
        elif kwargs['unit']:
            pieces.append(kwargs['unit'])
        else:
            pieces.append('m', )

        for token in ('withdist', 'withcoord', 'withhash'):
            if kwargs[token]:
                pieces.append(b(token.upper()))

        if kwargs['count']:
            pieces.extend([b('COUNT'), kwargs['count']])

        if kwargs['sort'] and kwargs['sort'] not in ('ASC', 'DESC'):
            raise RedisError("GEORADIUS invalid sort")
        elif kwargs['sort']:
            pieces.append(b(kwargs['sort']))

        if kwargs['store'] and kwargs['store_dist']:
            raise RedisError("GEORADIUS store and store_dist cant be set"
                             " together")

        if kwargs['store']:
            pieces.extend([b('STORE'), kwargs['store']])

        if kwargs['store_dist']:
            pieces.extend([b('STOREDIST'), kwargs['store_dist']])

        return await self.execute_command(command, *pieces, **kwargs)
Пример #2
0
    async def zaddoption(self, name, option=None, *args, **kwargs):
        """
        Differs from zadd in that you can set either 'XX' or 'NX' option as
        described here: https://redis.io/commands/zadd. Only for Redis 3.0.2 or
        later.

        The following example would add four values to the 'my-key' key:
        redis.zaddoption('my-key', 'XX', 1.1, 'name1', 2.2, 'name2', name3=3.3, name4=4.4)
        redis.zaddoption('my-key', 'NX CH', name1=2.2)
        """
        if not option:
            raise RedisError("ZADDOPTION must take options")
        options = set(opt.upper() for opt in option.split())
        if options - VALID_ZADD_OPTIONS:
            raise RedisError("ZADD only takes XX, NX, CH, or INCR")
        if 'NX' in options and 'XX' in options:
            raise RedisError("ZADD only takes one of XX or NX")
        pieces = list(options)
        members = []
        if args:
            if len(args) % 2 != 0:
                raise RedisError("ZADD requires an equal number of "
                                 "values and scores")
            members.extend(args)
        for pair in iteritems(kwargs):
            members.append(pair[1])
            members.append(pair[0])
        if 'INCR' in options and len(members) != 2:
            raise RedisError("ZADD with INCR only takes one score-name pair")
        return await self.execute_command('ZADD', name, *pieces, *members)
Пример #3
0
 def multi(self):
     """
     Start a transactional block of the pipeline after WATCH commands
     are issued. End the transactional block with `execute`.
     """
     if self.explicit_transaction:
         raise RedisError('Cannot issue nested calls to MULTI')
     if self.command_stack:
         raise RedisError('Commands without an initial WATCH have already '
                          'been issued')
     self.explicit_transaction = True
Пример #4
0
    async def xreadgroup(self,
                         group: str,
                         consumer_id: str,
                         count=None,
                         block=None,
                         **streams):
        """
        Available since 5.0.0.

        Time complexity:
        For each stream mentioned: O(log(N)+M) with N being the number of elements
        in the stream and M the number of elements being returned.
        If M is constant (e.g. always asking for the first 10 elements with COUNT),
        you can consider it O(log(N)). On the other side,
        XADD will pay the O(N) time in order to serve
        the N clients blocked on the stream getting new data.

        Read data from one or multiple streams via the consumer group,
        only returning entries with an ID greater
        than the last received ID reported by the caller.

        :param group: the name of the consumer group
        :param consumer_id: the name of the consumer that is attempting to read
        :param count: int, if set, only return this many items, beginning with the
               earliest available.
        :param block: int, milliseconds we want to block before timing out,
                if the BLOCK option is not used, the command is synchronous
        :param streams: stream_name - stream_id mapping
        :return dict like {stream_name: [(stream_id: entry), ...]}
        """
        pieces = ['GROUP', group, consumer_id]
        if block is not None:
            if not isinstance(block, int) or block < 1:
                raise RedisError("XREAD block must be a positive integer")
            pieces.append("BLOCK")
            pieces.append(str(block))
        if count is not None:
            if not isinstance(count, int) or count < 1:
                raise RedisError("XREAD count must be a positive integer")
            pieces.append("COUNT")
            pieces.append(str(count))
        pieces.append("STREAMS")
        ids = []
        for partial_stream in streams.items():
            pieces.append(partial_stream[0])
            ids.append(partial_stream[1])
        pieces.extend(ids)
        return await self.execute_command('XREADGROUP', *pieces)
Пример #5
0
    async def xrevrange(self,
                        name: str,
                        start='+',
                        end='-',
                        count=None) -> list:
        """
        Read stream values within an interval, in reverse order.

        Available since 5.0.0.
        Time complexity: O(log(N)+M) with N being the number of elements in the stream and M the number
        of elements being returned. If M is constant (e.g. always asking for the first 10 elements with COUNT),
        you can consider it O(log(N)).

        :param name: name of the stream
        :param start: first stream ID. defaults to '+',
               meaning the latest available.
        :param end: last stream ID. defaults to '-',
                meaning the earliest available.
        :param count: if set, only return this many items, beginning with the
               latest available.

        """
        pieces = [start, end]
        if count is not None:
            if not isinstance(count, int) or count < 1:
                raise RedisError("XREVRANGE count must be a positive integer")
            pieces.append("COUNT")
            pieces.append(str(count))
        return await self.execute_command('XREVRANGE', name, *pieces)
Пример #6
0
    async def zrevrangebyscore(self,
                               name,
                               max,
                               min,
                               start=None,
                               num=None,
                               withscores=False,
                               score_cast_func=float):
        """
        Returns a range of values from the sorted set ``name`` with scores
        between ``min`` and ``max`` in descending order.

        If ``start`` and ``num`` are specified, then return a slice
        of the range.

        ``withscores`` indicates to return the scores along with the values.
        The return type is a list of (value, score) pairs

        ``score_cast_func`` a callable used to cast the score return value
        """
        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 = ['ZREVRANGEBYSCORE', name, max, min]
        if start is not None and num is not None:
            pieces.extend([b('LIMIT'), start, num])
        if withscores:
            pieces.append(b('WITHSCORES'))
        options = {
            'withscores': withscores,
            'score_cast_func': score_cast_func
        }
        return await self.execute_command(*pieces, **options)
Пример #7
0
 async def shutdown(self):
     "Shutdown the server"
     try:
         await self.execute_command('SHUTDOWN')
     except ConnectionError:
         # a ConnectionError here is expected
         return
     raise RedisError("SHUTDOWN seems to have failed.")
Пример #8
0
    async def bitpos(self, key, bit, start=None, end=None):
        """
        Return the position of the first bit set to 1 or 0 in a string.
        ``start`` and ``end`` difines search range. The range is interpreted
        as a range of bytes and not a range of bits, so start=0 and end=2
        means to look at the first three bytes.
        """
        if bit not in (0, 1):
            raise RedisError('bit must be 0 or 1')
        params = [key, bit]

        start is not None and params.append(start)

        if start is not None and end is not None:
            params.append(end)
        elif start is None and end is not None:
            raise RedisError("start argument is not set, "
                             "when end is specified")
        return await self.execute_command('BITPOS', *params)
Пример #9
0
 async def geoadd(self, name, *values):
     """
     Add the specified geospatial items to the specified key identified
     by the ``name`` argument. The Geospatial items are given as ordered
     members of the ``values`` argument, each item or place is formed by
     the triad latitude, longitude and name.
     """
     if len(values) % 3 != 0:
         raise RedisError("GEOADD requires places with lon, lat and name"
                          " values")
     return await self.execute_command('GEOADD', name, *values)
Пример #10
0
    async def cluster_setslot(self, node_id, slot_id, state):
        """
        Bind an hash slot to a specific node

        Sends to specified node
        """
        if state.upper() in {'IMPORTING', 'MIGRATING', 'NODE'} and node_id is not None:
            return await self.execute_command('CLUSTER SETSLOT', slot_id, state, node_id)
        elif state.upper() == 'STABLE':
            return await self.execute_command('CLUSTER SETSLOT', slot_id, 'STABLE')
        else:
            raise RedisError('Invalid slot state: {0}'.format(state))
Пример #11
0
 async def mset(self, *args, **kwargs):
     """
     Sets key/values based on a mapping. Mapping can be supplied as a single
     dictionary argument or as kwargs.
     """
     if args:
         if len(args) != 1 or not isinstance(args[0], dict):
             raise RedisError('MSET requires **kwargs or a single dict arg')
         kwargs.update(args[0])
     items = []
     for pair in iteritems(kwargs):
         items.extend(pair)
     return await self.execute_command('MSET', *items)
Пример #12
0
 async def bitcount(self, key, start=None, end=None):
     """
     Returns the count of set bits in the value of ``key``.  Optional
     ``start`` and ``end`` paramaters indicate which bytes to consider
     """
     params = [key]
     if start is not None and end is not None:
         params.append(start)
         params.append(end)
     elif (start is not None and end is None) or \
             (end is not None and start is None):
         raise RedisError("Both start and end must be specified")
     return await self.execute_command('BITCOUNT', *params)
Пример #13
0
 async def geodist(self, name, place1, place2, unit=None):
     """
     Return the distance between ``place1`` and ``place2`` members of the
     ``name`` key.
     The units must be one of the following : m, km mi, ft. By async default
     meters are used.
     """
     pieces = [name, place1, place2]
     if unit and unit not in ('m', 'km', 'mi', 'ft'):
         raise RedisError("GEODIST invalid unit")
     elif unit:
         pieces.append(unit)
     return await self.execute_command('GEODIST', *pieces)
Пример #14
0
 async def _watch(self, node, conn, names):
     "Watches the values at keys ``names``"
     for name in names:
         slot = self._determine_slot('WATCH', name)
         dist_node = self.connection_pool.get_node_by_slot(slot)
         if node.get('name') != dist_node['name']:
             # raise error if commands in a transaction can not hash to same node
             if len(node) > 0:
                 raise ClusterTransactionError(
                     "Keys in request don't hash to the same node")
     if self.explicit_transaction:
         raise RedisError('Cannot issue a WATCH after a MULTI')
     await conn.send_command('WATCH', *names)
     return await conn.read_response()
Пример #15
0
 async def msetnx(self, *args, **kwargs):
     """
     Sets key/values based on a mapping if none of the keys are already set.
     Mapping can be supplied as a single dictionary argument or as kwargs.
     Returns a boolean indicating if the operation was successful.
     """
     if args:
         if len(args) != 1 or not isinstance(args[0], dict):
             raise RedisError('MSETNX requires **kwargs or a single '
                              'dict arg')
         kwargs.update(args[0])
     items = []
     for pair in iteritems(kwargs):
         items.extend(pair)
     return await self.execute_command('MSETNX', *items)
Пример #16
0
    async def zrevrangebylex(self, name, max, min, start=None, num=None):
        """
        Returns 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([b('LIMIT'), start, num])
        return await self.execute_command(*pieces)
Пример #17
0
    async def xadd(self,
                   name: str,
                   entry: dict,
                   max_len=None,
                   stream_id='*',
                   approximate=True) -> str:
        """
        Appends the specified stream entry to the stream at the specified key.
        If the key does not exist, as a side effect of running
        this command the key is created with a stream value.
        Available since 5.0.0.
        Time complexity: O(log(N)) with N being the number of items already into the stream.

        :param name: name of the stream
        :param entry: key-values to be appended to the stream
        :param max_len: max length of the stream
        length will not be limited max_len is set to None
        notice: max_len should be int greater than 0,
        if set to 0 or negative, the stream length will not be limited

        :param stream_id: id of the options appended to the stream.
        The XADD command will auto-generate a unique id for you
        if the id argument specified is the * character.
        ID are specified by two numbers separated by a "-" character

        :param approximate: whether redis will limit
        the stream with given max length exactly, if set to True,
        there will be a few tens of entries more,
        but never less than 1000 items

        :return: id auto generated or the specified id given.
        notice: specified id without "-" character will be completed like "id-0"
        """
        pieces = []
        if max_len is not None:
            if not isinstance(max_len, int) or max_len < 1:
                raise RedisError("XADD maxlen must be a positive integer")
            pieces.append('MAXLEN')
            if approximate:
                pieces.append('~')
            pieces.append(str(max_len))
        pieces.append(stream_id)
        for kv in entry.items():
            pieces.extend(list(kv))
        return await self.execute_command('XADD', name, *pieces)
Пример #18
0
    async def mset(self, *args, **kwargs):
        """
        Sets key/values based on a mapping. Mapping can be supplied as a single
        dictionary argument or as kwargs.

        Cluster impl:
            Itterate over all items and do SET on each (k,v) pair

            Operation is no longer atomic.
        """
        if args:
            if len(args) != 1 or not isinstance(args[0], dict):
                raise RedisError('MSET requires **kwargs or a single dict arg')
            kwargs.update(args[0])

        for pair in iteritems(kwargs):
            await self.set(pair[0], pair[1])

        return True
Пример #19
0
 def __init__(self, keyfile=None, certfile=None,
              cert_reqs=None, ca_certs=None):
     self.keyfile = keyfile
     self.certfile = certfile
     if cert_reqs is None:
         self.cert_reqs = ssl.CERT_NONE
     elif isinstance(cert_reqs, str):
         CERT_REQS = {
             'none': ssl.CERT_NONE,
             'optional': ssl.CERT_OPTIONAL,
             'required': ssl.CERT_REQUIRED
         }
         if cert_reqs not in CERT_REQS:
             raise RedisError(
                 "Invalid SSL Certificate Requirements Flag: %s" %
                 cert_reqs)
         self.cert_reqs = CERT_REQS[cert_reqs]
     self.ca_certs = ca_certs
     self.context = None
Пример #20
0
    async def zadd(self, name, *args, **kwargs):
        """
        Set any number of score, element-name pairs to the key ``name``. Pairs
        can be specified in two ways:

        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 iteritems(kwargs):
            pieces.append(pair[1])
            pieces.append(pair[0])
        return await self.execute_command('ZADD', name, *pieces)
Пример #21
0
    async def msetnx(self, *args, **kwargs):
        """
        Sets key/values based on a mapping if none of the keys are already set.
        Mapping can be supplied as a single dictionary argument or as kwargs.
        Returns a boolean indicating if the operation was successful.

        Clutser impl:
            Itterate over all items and do GET to determine if all keys do not exists.
            If true then call mset() on all keys.
        """
        if args:
            if len(args) != 1 or not isinstance(args[0], dict):
                raise RedisError('MSETNX requires **kwargs or a single dict arg')
            kwargs.update(args[0])

        # Itterate over all items and fail fast if one value is True.
        for k, _ in kwargs.items():
            if await self.get(k):
                return False

        return await self.mset(**kwargs)
Пример #22
0
 async def watch(self, *names):
     "Watches the values at keys ``names``"
     if self.explicit_transaction:
         raise RedisError('Cannot issue a WATCH after a MULTI')
     return await self.execute_command('WATCH', *names)
Пример #23
0
 def __init__(self, read_size):
     if not HIREDIS_AVAILABLE:
         raise RedisError("Hiredis is not installed")
     self._stream = None
     self._reader = None
     self._read_size = read_size
Пример #24
0
    async def sort(self, name, start=None, num=None, by=None, get=None, desc=False, alpha=False, store=None, groups=None):
        """Sort and return the list, set or sorted set at ``name``.

        :start: and :num:
            allow for paging through the sorted data

        :by:
            allows using an external key to weight and sort the items.
            Use an "*" to indicate where in the key the item value is located

        :get:
            allows for returning items from external keys rather than the
            sorted data itself.  Use an "*" to indicate where int he key
            the item value is located

        :desc:
            allows for reversing the sort

        :alpha:
            allows for sorting lexicographically rather than numerically

        :store:
            allows for storing the result of the sort into the key `store`

        ClusterImpl:
            A full implementation of the server side sort mechanics because many of the
            options work on multiple keys that can exist on multiple servers.
        """
        if (start is None and num is not None) or \
                (start is not None and num is None):
            raise RedisError("RedisError: ``start`` and ``num`` must both be specified")
        try:
            data_type = b(await self.type(name))

            if data_type == b("none"):
                return []
            elif data_type == b("set"):
                data = list(await self.smembers(name))[:]
            elif data_type == b("list"):
                data = await self.lrange(name, 0, -1)
            else:
                raise RedisClusterException("Unable to sort data type : {0}".format(data_type))
            if by is not None:
                # _sort_using_by_arg mutates data so we don't
                # need need a return value.
                data = await self._sort_using_by_arg(data, by, alpha)
            elif not alpha:
                data.sort(key=self._strtod_key_func)
            else:
                data.sort()
            if desc:
                data = data[::-1]
            if not (start is None and num is None):
                data = data[start:start + num]

            if get:
                data = await self._retrive_data_from_sort(data, get)

            if store is not None:
                if data_type == b("set"):
                    await self.delete(store)
                    await self.rpush(store, *data)
                elif data_type == b("list"):
                    await self.delete(store)
                    await self.rpush(store, *data)
                else:
                    raise RedisClusterException("Unable to store sorted data for data type : {0}".format(data_type))

                return len(data)

            if groups:
                if not get or isinstance(get, str) or len(get) < 2:
                    raise DataError('when using "groups" the "get" argument '
                                    'must be specified and contain at least '
                                    'two keys')
                n = len(get)
                return list(zip(*[data[i::n] for i in range(n)]))
            else:
                return data
        except KeyError:
            return []
Пример #25
0
    async def sort(self, name, start=None, num=None, by=None, get=None,
             desc=False, alpha=False, store=None, groups=False):
        """
        Sorts and returns a list, set or sorted set at ``name``.

        ``start`` and ``num`` are for paginating sorted data

        ``by`` allows using an external key to weight and sort the items.
            Use an "*" to indicate where in the key the item value is located

        ``get`` is for returning items from external keys rather than the
            sorted data itself.  Use an "*" to indicate where int he key
            the item value is located

        ``desc`` is for reversing the sort

        ``alpha`` is for sorting lexicographically rather than numerically

        ``store`` is for storing the result of the sort into
            the key ``store``

        ``groups`` if set to True and if ``get`` contains at least two
            elements, sort will return a list of tuples, each containing the
            values fetched from the arguments to ``get``.

        """
        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 = [name]
        if by is not None:
            pieces.append(b('BY'))
            pieces.append(by)
        if start is not None and num is not None:
            pieces.append(b('LIMIT'))
            pieces.append(start)
            pieces.append(num)
        if get is not None:
            # If get is a string assume we want to get a single value.
            # Otherwise assume it's an interable and we want to get multiple
            # values. We can't just iterate blindly because strings are
            # iterable.
            if isinstance(get, str):
                pieces.append(b('GET'))
                pieces.append(get)
            else:
                for g in get:
                    pieces.append(b('GET'))
                    pieces.append(g)
        if desc:
            pieces.append(b('DESC'))
        if alpha:
            pieces.append(b('ALPHA'))
        if store is not None:
            pieces.append(b('STORE'))
            pieces.append(store)

        if groups:
            if not get or isinstance(get, str) or len(get) < 2:
                raise DataError('when using "groups" the "get" argument '
                                'must be specified and contain at least '
                                'two keys')

        options = {'groups': len(get) if groups else None}
        return await self.execute_command('SORT', *pieces, **options)