Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,9 @@ You can use the ``SETNX`` command through the backend ``set()`` method with the
Also, the ``incr`` and ``decr`` methods use Redis atomic operations when the
value that a key contains is suitable for it.

For Redis list, set, and sorted set operations, django-redis provides methods like
``lpush``, ``rpush``, ``lpop``, ``sadd``, ``smembers``, ``zadd``, ``zrange``, etc.

Raw client access
~~~~~~~~~~~~~~~~~

Expand Down
1 change: 1 addition & 0 deletions changelog.d/802.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add list operations (lpush, rpush, lpop, lrange, etc.) with ListMixin. List methods use 'key' parameter for consistency with Redis terminology.
41 changes: 41 additions & 0 deletions django_redis/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,3 +335,44 @@ def zrevrangebyscore(self, *args, **kwargs):
@omit_exception
def zscore(self, *args, **kwargs):
return self.client.zscore(*args, **kwargs)

# List Operations
@omit_exception
def lpush(self, *args, **kwargs):
return self.client.lpush(*args, **kwargs)

@omit_exception
def rpush(self, *args, **kwargs):
return self.client.rpush(*args, **kwargs)

@omit_exception
def lpop(self, *args, **kwargs):
return self.client.lpop(*args, **kwargs)

@omit_exception
def rpop(self, *args, **kwargs):
return self.client.rpop(*args, **kwargs)

@omit_exception
def lrange(self, *args, **kwargs):
return self.client.lrange(*args, **kwargs)

@omit_exception
def lindex(self, *args, **kwargs):
return self.client.lindex(*args, **kwargs)

@omit_exception
def llen(self, *args, **kwargs):
return self.client.llen(*args, **kwargs)

@omit_exception
def lrem(self, *args, **kwargs):
return self.client.lrem(*args, **kwargs)

@omit_exception
def ltrim(self, *args, **kwargs):
return self.client.ltrim(*args, **kwargs)

@omit_exception
def lset(self, *args, **kwargs):
return self.client.lset(*args, **kwargs)
4 changes: 2 additions & 2 deletions django_redis/client/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from redis.typing import AbsExpiryT, EncodableT, ExpiryT, KeyT, PatternT

from django_redis import pool
from django_redis.client.mixins import SortedSetMixin
from django_redis.client.mixins import ListMixin, SortedSetMixin
from django_redis.exceptions import CompressorError, ConnectionInterrupted
from django_redis.util import CacheKey

Expand All @@ -41,7 +41,7 @@ def glob_escape(s: str) -> str:
return special_re.sub(r"[\1]", s)


class DefaultClient(SortedSetMixin):
class DefaultClient(ListMixin, SortedSetMixin):
def __init__(self, server, params: dict[str, Any], backend: BaseCache) -> None:
self._backend = backend
self._server = server
Expand Down
3 changes: 2 additions & 1 deletion django_redis/client/mixins/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django_redis.client.mixins.lists import ListMixin
from django_redis.client.mixins.protocols import ClientProtocol
from django_redis.client.mixins.sorted_sets import SortedSetMixin

__all__ = ["ClientProtocol", "SortedSetMixin"]
__all__ = ["ClientProtocol", "ListMixin", "SortedSetMixin"]
173 changes: 173 additions & 0 deletions django_redis/client/mixins/lists.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
from typing import Any, Optional, Union

from redis import Redis
from redis.typing import KeyT

from django_redis.client.mixins.protocols import ClientProtocol


class ListMixin(ClientProtocol):
"""Mixin providing Redis list operations."""

def lpush(
self,
key: KeyT,
*values: Any,
version: Optional[int] = None,
client: Optional[Redis] = None,
) -> int:
"""Insert values at head of list."""
if client is None:
client = self.get_client(write=True)

key = self.make_key(key, version=version)
encoded_values = [self.encode(value) for value in values]
return int(client.lpush(key, *encoded_values))

def rpush(
self,
key: KeyT,
*values: Any,
version: Optional[int] = None,
client: Optional[Redis] = None,
) -> int:
"""Insert values at tail of list."""
if client is None:
client = self.get_client(write=True)

key = self.make_key(key, version=version)
encoded_values = [self.encode(value) for value in values]
return int(client.rpush(key, *encoded_values))

def lpop(
self,
key: KeyT,
count: Optional[int] = None,
version: Optional[int] = None,
client: Optional[Redis] = None,
) -> Union[Any, list[Any], None]:
"""Remove and return element(s) from head of list."""
if client is None:
client = self.get_client(write=True)

key = self.make_key(key, version=version)
result = client.lpop(key, count=count)

if result is None:
return None
if isinstance(result, list):
return [self.decode(item) for item in result]
return self.decode(result)

def rpop(
self,
key: KeyT,
count: Optional[int] = None,
version: Optional[int] = None,
client: Optional[Redis] = None,
) -> Union[Any, list[Any], None]:
"""Remove and return element(s) from tail of list."""
if client is None:
client = self.get_client(write=True)

key = self.make_key(key, version=version)
result = client.rpop(key, count=count)

if result is None:
return None
if isinstance(result, list):
return [self.decode(item) for item in result]
return self.decode(result)

def lrange(
self,
key: KeyT,
start: int,
end: int,
version: Optional[int] = None,
client: Optional[Redis] = None,
) -> list[Any]:
"""Return range of elements from list."""
if client is None:
client = self.get_client(write=False)

key = self.make_key(key, version=version)
result = client.lrange(key, start, end)
return [self.decode(item) for item in result]

def lindex(
self,
key: KeyT,
index: int,
version: Optional[int] = None,
client: Optional[Redis] = None,
) -> Optional[Any]:
"""Return element at index in list."""
if client is None:
client = self.get_client(write=False)

key = self.make_key(key, version=version)
result = client.lindex(key, index)
if result is None:
return None
return self.decode(result)

def llen(
self,
key: KeyT,
version: Optional[int] = None,
client: Optional[Redis] = None,
) -> int:
"""Return length of list."""
if client is None:
client = self.get_client(write=False)

key = self.make_key(key, version=version)
return int(client.llen(key))

def lrem(
self,
key: KeyT,
count: int,
value: Any,
version: Optional[int] = None,
client: Optional[Redis] = None,
) -> int:
"""Remove elements from list equal to value."""
if client is None:
client = self.get_client(write=True)

key = self.make_key(key, version=version)
encoded_value = self.encode(value)
return int(client.lrem(key, count, encoded_value))

def ltrim(
self,
key: KeyT,
start: int,
end: int,
version: Optional[int] = None,
client: Optional[Redis] = None,
) -> bool:
"""Trim list to specified range."""
if client is None:
client = self.get_client(write=True)

key = self.make_key(key, version=version)
return bool(client.ltrim(key, start, end))

def lset(
self,
key: KeyT,
index: int,
value: Any,
version: Optional[int] = None,
client: Optional[Redis] = None,
) -> bool:
"""Set element at index in list."""
if client is None:
client = self.get_client(write=True)

key = self.make_key(key, version=version)
encoded_value = self.encode(value)
return bool(client.lset(key, index, encoded_value))
Loading
Loading