Repository: redis/redis-py

Issue:
Support for new LPOS command
Redis 6.0.6 will support a new command called [LPOS](https://redis.io/commands/lpos)

> The command returns the index of matching elements inside a Redis list. By default, when no options are given, it will scan the list from head to tail, looking for the first match of "element". If the element is found, its index (the zero-based position in the list) is returned. Otherwise, if no match is found, NULL is returned.

**Version**: redis 6.0.6

**Platform**: all

**Description**: This is no bug report but a notification of the new command.


Discussion:

Resolving patch:
diff --git a/redis/client.py b/redis/client.py
--- a/redis/client.py
+++ b/redis/client.py
@@ -2030,6 +2030,42 @@ def rpushx(self, name, value):
         "Push ``value`` onto the tail of the list ``name`` if ``name`` exists"
         return self.execute_command('RPUSHX', name, value)
 
+    def lpos(self, name, value, rank=None, count=None, maxlen=None):
+        """
+        Get position of ``value`` within the list ``name``
+
+         If specified, ``rank`` indicates the "rank" of the first element to
+         return in case there are multiple copies of ``value`` in the list.
+         By default, LPOS returns the position of the first occurrence of
+         ``value`` in the list. When ``rank`` 2, LPOS returns the position of
+         the second ``value`` in the list. If ``rank`` is negative, LPOS
+         searches the list in reverse. For example, -1 would return the
+         position of the last occurrence of ``value`` and -2 would return the
+         position of the next to last occurrence of ``value``.
+
+         If specified, ``count`` indicates that LPOS should return a list of
+         up to ``count`` positions. A ``count`` of 2 would return a list of
+         up to 2 positions. A ``count`` of 0 returns a list of all positions
+         matching ``value``. When ``count`` is specified and but ``value``
+         does not exist in the list, an empty list is returned.
+
+         If specified, ``maxlen`` indicates the maximum number of list
+         elements to scan. A ``maxlen`` of 1000 will only return the
+         position(s) of items within the first 1000 entries in the list.
+         A ``maxlen`` of 0 (the default) will scan the entire list.
+        """
+        pieces = [name, value]
+        if rank is not None:
+            pieces.extend(['RANK', rank])
+
+        if count is not None:
+            pieces.extend(['COUNT', count])
+
+        if maxlen is not None:
+            pieces.extend(['MAXLEN', maxlen])
+
+        return self.execute_command('LPOS', *pieces)
+
     def sort(self, name, start=None, num=None, by=None, get=None,
              desc=False, alpha=False, store=None, groups=False):
         """


Test patch:
diff --git a/tests/test_commands.py b/tests/test_commands.py
--- a/tests/test_commands.py
+++ b/tests/test_commands.py
@@ -1052,6 +1052,38 @@ def test_rpush(self, r):
         assert r.rpush('a', '3', '4') == 4
         assert r.lrange('a', 0, -1) == [b'1', b'2', b'3', b'4']
 
+    @skip_if_server_version_lt('6.0.6')
+    def test_lpos(self, r):
+        assert r.rpush('a', 'a', 'b', 'c', '1', '2', '3', 'c', 'c') == 8
+        assert r.lpos('a', 'a') == 0
+        assert r.lpos('a', 'c') == 2
+
+        assert r.lpos('a', 'c', rank=1) == 2
+        assert r.lpos('a', 'c', rank=2) == 6
+        assert r.lpos('a', 'c', rank=4) is None
+        assert r.lpos('a', 'c', rank=-1) == 7
+        assert r.lpos('a', 'c', rank=-2) == 6
+
+        assert r.lpos('a', 'c', count=0) == [2, 6, 7]
+        assert r.lpos('a', 'c', count=1) == [2]
+        assert r.lpos('a', 'c', count=2) == [2, 6]
+        assert r.lpos('a', 'c', count=100) == [2, 6, 7]
+
+        assert r.lpos('a', 'c', count=0, rank=2) == [6, 7]
+        assert r.lpos('a', 'c', count=2, rank=-1) == [7, 6]
+
+        assert r.lpos('axxx', 'c', count=0, rank=2) == []
+        assert r.lpos('axxx', 'c') is None
+
+        assert r.lpos('a', 'x', count=2) == []
+        assert r.lpos('a', 'x') is None
+
+        assert r.lpos('a', 'a', count=0, maxlen=1) == [0]
+        assert r.lpos('a', 'c', count=0, maxlen=1) == []
+        assert r.lpos('a', 'c', count=0, maxlen=3) == [2]
+        assert r.lpos('a', 'c', count=0, maxlen=3, rank=-1) == [7, 6]
+        assert r.lpos('a', 'c', count=0, maxlen=7, rank=2) == [6]
+
     def test_rpushx(self, r):
         assert r.rpushx('a', 'b') == 0
         assert r.lrange('a', 0, -1) == []