Skip to content

Commit

Permalink
Issue #598 extend support for the hashmap functions
Browse files Browse the repository at this point in the history
  • Loading branch information
rootart committed Jun 22, 2024
1 parent e11150a commit f669b9d
Show file tree
Hide file tree
Showing 4 changed files with 281 additions and 12 deletions.
20 changes: 20 additions & 0 deletions django_redis/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,22 @@ def sunionstore(self, *args, **kwargs):
def hset(self, *args, **kwargs):
return self.client.hset(*args, **kwargs)

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

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

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

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

@omit_exception
def hdel(self, *args, **kwargs):
return self.client.hdel(*args, **kwargs)
Expand All @@ -272,3 +288,7 @@ def hkeys(self, *args, **kwargs):
@omit_exception
def hexists(self, *args, **kwargs):
return self.client.hexists(*args, **kwargs)

@omit_exception
def hincrby(self, *args, **kwargs):
return self.client.hincrby(*args, **kwargs)
93 changes: 91 additions & 2 deletions django_redis/client/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -621,7 +621,7 @@ def _incr(
# if cached value or total value is greater than 64 bit signed
# integer.
# elif int is encoded. so redis sees the data as string.
# In this situations redis will throw ResponseError
# In these situations redis will throw ResponseError

# try to keep TTL of key
timeout = self.ttl(key, version=version, client=client)
Expand Down Expand Up @@ -1115,14 +1115,77 @@ def hset(
"""
if client is None:
client = self.get_client(write=True)
name = self.make_key(name, version=version)
nkey = self.make_key(key, version=version)
nvalue = self.encode(value)
return int(client.hset(name, nkey, nvalue))

def hdel(
def hsetnx(
self,
name: str,
key: KeyT,
value: EncodableT,
version: Optional[int] = None,
client: Optional[Redis] = None,
) -> int:
if client is None:
client = self.get_client(write=True)
nkey = self.make_key(key, version=version)
name = self.make_key(name, version=version)
nvalue = self.encode(value)
return int(client.hsetnx(name, nkey, nvalue))

def hget(
self,
name: KeyT,
key: KeyT,
version: Optional[int] = None,
client: Optional[Redis] = None,
) -> Any:
"""
Return the value of hash name at key.
"""
if client is None:
client = self.get_client(write=False)
name = self.make_key(name, version=version)
nkey = self.make_key(key, version=version)
value = client.hget(name, nkey)
if value is None:
return None

Check warning on line 1154 in django_redis/client/default.py

View check run for this annotation

Codecov / codecov/patch

django_redis/client/default.py#L1154

Added line #L1154 was not covered by tests
return self.decode(value)

def hgetall(
self,
name: KeyT,
version: Optional[int] = None,
client: Optional[Redis] = None,
) -> Dict[str, Any]:

if client is None:
client = self.get_client(write=False)
name = self.make_key(name, version=version)
data = client.hgetall(name)
return {self.reverse_key(k.decode()): self.decode(v) for k, v in data.items()}

def hmget(
self,
name: KeyT,
*keys: KeyT,
version: Optional[int] = None,
client: Optional[Redis] = None,
) -> List[Any]:
if client is None:
client = self.get_client(write=False)
name = self.make_key(name, version=version)
nkeys = [self.make_key(k, version=version) for k in keys]
return [
self.decode(v) if v is not None else None for v in client.hmget(name, nkeys)
]

def hdel(
self,
name: KeyT,
key: KeyT,
version: Optional[int] = None,
client: Optional[Redis] = None,
) -> int:
Expand All @@ -1132,29 +1195,34 @@ def hdel(
"""
if client is None:
client = self.get_client(write=True)
name = self.make_key(name, version=version)
nkey = self.make_key(key, version=version)
return int(client.hdel(name, nkey))

def hlen(
self,
name: str,
version: Optional[int] = None,
client: Optional[Redis] = None,
) -> int:
"""
Return the number of items in hash name.
"""
if client is None:
client = self.get_client(write=False)
name = self.make_key(name, version=version)
return int(client.hlen(name))

def hkeys(
self,
name: str,
version: Optional[int] = None,
client: Optional[Redis] = None,
) -> List[Any]:
"""
Return a list of keys in hash name.
"""
name = self.make_key(name, version=version)
if client is None:
client = self.get_client(write=False)
try:
Expand All @@ -1174,5 +1242,26 @@ def hexists(
"""
if client is None:
client = self.get_client(write=False)
name = self.make_key(name, version=version)
nkey = self.make_key(key, version=version)
return bool(client.hexists(name, nkey))

def hincrby(
self,
name: KeyT,
key: KeyT,
increment: int = 1,
version: Optional[int] = None,
client: Optional[Redis] = None,
) -> int:
if client is None:
client = self.get_client(write=True)
name = self.make_key(name, version=version)
nkey = self.make_key(key, version=version)
try:
value = client.hincrby(name, nkey, increment)
except ResponseError as exc:
value = self.hget(name, nkey)
msg = f"Value: {value} is not an integer or out of range."
raise ValueError(msg) from exc
return int(value)
122 changes: 122 additions & 0 deletions django_redis/client/sharded.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,3 +483,125 @@ def smismember(
key = self.make_key(key, version=version)
client = self.get_server(key)
return super().smismember(key, *members, version=version, client=client)

def hset(
self,
name: str,
key: KeyT,
value: Any,
version: Optional[int] = None,
client: Optional[Redis] = None,
) -> int:
if client is None:
name = self.make_key(name, version=version)
client = self.get_server(name)
return super().hset(name, key, value, version=version, client=client)

def hsetnx(
self,
name: str,
key: KeyT,
value: Any,
version: Optional[int] = None,
client: Optional[Redis] = None,
) -> int:
if client is None:
name = self.make_key(name, version=version)
client = self.get_server(name)
return super().hsetnx(name, key, value, version=version, client=client)

def hlen(
self,
name: str,
version: Optional[int] = None,
client: Optional[Redis] = None,
) -> int:
if client is None:
name = self.make_key(name, version=version)
client = self.get_server(name)
return super().hlen(name, version=version, client=client)

def hget(
self,
name: str,
key: KeyT,
version: Optional[int] = None,
client: Optional[Redis] = None,
) -> Any:
if client is None:
name = self.make_key(name, version=version)
client = self.get_server(name)
return super().hget(name, key, version=version, client=client)

def hexists(
self,
name: str,
key: KeyT,
version: Optional[int] = None,
client: Optional[Redis] = None,
) -> bool:
if client is None:
name = self.make_key(name, version=version)
client = self.get_server(name)
return super().hexists(name, key, version=version, client=client)

def hkeys(
self,
name: str,
version: Optional[int] = None,
client: Optional[Redis] = None,
) -> List[Any]:
if client is None:
name = self.make_key(name, version=version)
client = self.get_server(name)
return super().hkeys(name, client=client)

def hgetall(
self,
name: str,
version: Optional[int] = None,
client: Optional[Redis] = None,
) -> Any:
if client is None:
name = self.make_key(name, version=version)
client = self.get_server(name)
return super().hgetall(name, version=version, client=client)

def hincrby(
self,
name: KeyT,
key: KeyT,
increment: int = 1,
version: Optional[int] = None,
client: Optional[Redis] = None,
) -> int:
if client is None:
name = self.make_key(name, version=version)
client = self.get_server(name)
return super().hincrby(
name, key, increment=increment, version=version, client=client
)

def hmget(
self,
name: KeyT,
*keys: KeyT,
version: Optional[int] = None,
client: Optional[Redis] = None,
) -> List[Any]:
if client is None:
name = self.make_key(name, version=version)
client = self.get_server(name)
return super().hmget(name, *keys, version=version, client=client)

def hdel(
self,
name: KeyT,
key: KeyT,
version: Optional[int] = None,
client: Optional[Redis] = None,
) -> int:
if client is None:
name = self.make_key(name, version=version)
client = self.get_server(name)
return super().hdel(name, key, version=version, client=client)
58 changes: 48 additions & 10 deletions tests/test_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -810,17 +810,61 @@ def test_clear(self, cache: RedisCache):
assert value_from_cache_after_clear is None

def test_hset(self, cache: RedisCache):
if isinstance(cache.client, ShardClient):
pytest.skip("ShardClient doesn't support get_client")
cache.hset("foo_hash1", "foo1", "bar1")
cache.hset("foo_hash1", "foo2", "bar2")
assert cache.hlen("foo_hash1") == 2
assert cache.hexists("foo_hash1", "foo1")
assert cache.hexists("foo_hash1", "foo2")

def test_hsetnx(self, cache: RedisCache):
result_foo1 = cache.hsetnx("foo_hash1", "foo1", "bar1")
result_foo2 = cache.hsetnx("foo_hash1", "foo2", "bar2")
result_foo2_1 = cache.hsetnx("foo_hash1", "foo2", "bar2")
assert result_foo1 == 1
assert result_foo2 == 1
assert result_foo2_1 == 0
assert cache.hlen("foo_hash1") == 2
assert cache.hexists("foo_hash1", "foo1")
assert cache.hexists("foo_hash1", "foo2")

def test_hget(self, cache: RedisCache):
cache.hset("foo_hash1", "foo1", "bar1")
cache.hset("foo_hash1", "foo2", "bar2")
assert cache.hget("foo_hash1", "foo1") == "bar1"
assert cache.hget("foo_hash1", "foo2") == "bar2"

def test_hgetall(self, cache: RedisCache):
cache.hset("foo_hash1", "foo1", "bar1")
cache.hset("foo_hash1", "foo2", "bar2")
assert cache.hgetall("foo_hash1") == {
"foo1": "bar1",
"foo2": "bar2",
}

def test_hmget(self, cache: RedisCache):
cache.hset("foo_hash1", "foo1", "bar1")
cache.hset("foo_hash1", "foo2", "bar2")
cache.hset("foo_hash1", "foo3", "bar3")
assert cache.hmget("foo_hash1", "foo3", "foo1") == ["bar3", "bar1"]
assert cache.hmget("foo_hash1", "foo1", "foo2", "not-a-key") == [
"bar1",
"bar2",
None,
]

def test_hincrby(self, cache: RedisCache):
cache.hset("foo_hash1", "foo1", 1)
assert cache.hincrby("foo_hash1", "foo1") == 2

assert cache.hincrby("foo_hash1", "foo2", 3) == 3
assert cache.hincrby("foo_hash1", "foo2", -3) == 0

cache.hset("foo_hash1", "foo3", "not_an_int")

with pytest.raises(ValueError):
cache.hincrby("foo_hash1", "foo3", -3)

def test_hdel(self, cache: RedisCache):
if isinstance(cache.client, ShardClient):
pytest.skip("ShardClient doesn't support get_client")
cache.hset("foo_hash2", "foo1", "bar1")
cache.hset("foo_hash2", "foo2", "bar2")
assert cache.hlen("foo_hash2") == 2
Expand All @@ -831,17 +875,13 @@ def test_hdel(self, cache: RedisCache):
assert cache.hexists("foo_hash2", "foo2")

def test_hlen(self, cache: RedisCache):
if isinstance(cache.client, ShardClient):
pytest.skip("ShardClient doesn't support get_client")
assert cache.hlen("foo_hash3") == 0
cache.hset("foo_hash3", "foo1", "bar1")
assert cache.hlen("foo_hash3") == 1
cache.hset("foo_hash3", "foo2", "bar2")
assert cache.hlen("foo_hash3") == 2

def test_hkeys(self, cache: RedisCache):
if isinstance(cache.client, ShardClient):
pytest.skip("ShardClient doesn't support get_client")
cache.hset("foo_hash4", "foo1", "bar1")
cache.hset("foo_hash4", "foo2", "bar2")
cache.hset("foo_hash4", "foo3", "bar3")
Expand All @@ -851,8 +891,6 @@ def test_hkeys(self, cache: RedisCache):
assert keys[i] == f"foo{i + 1}"

def test_hexists(self, cache: RedisCache):
if isinstance(cache.client, ShardClient):
pytest.skip("ShardClient doesn't support get_client")
cache.hset("foo_hash5", "foo1", "bar1")
assert cache.hexists("foo_hash5", "foo1")
assert not cache.hexists("foo_hash5", "foo")
Expand Down

0 comments on commit f669b9d

Please sign in to comment.