diff --git a/changelog.d/598.feature b/changelog.d/598.feature new file mode 100644 index 00000000..dfae9a2a --- /dev/null +++ b/changelog.d/598.feature @@ -0,0 +1 @@ +Support HashMaps \ No newline at end of file diff --git a/django_redis/cache.py b/django_redis/cache.py index 57cc7e43..d26c33fa 100644 --- a/django_redis/cache.py +++ b/django_redis/cache.py @@ -184,3 +184,23 @@ def close(self, **kwargs): @omit_exception def touch(self, *args, **kwargs): return self.client.touch(*args, **kwargs) + + @omit_exception + def hset(self, *args, **kwargs): + return self.client.hset(*args, **kwargs) + + @omit_exception + def hdel(self, *args, **kwargs): + return self.client.hdel(*args, **kwargs) + + @omit_exception + def hlen(self, *args, **kwargs): + return self.client.hlen(*args, **kwargs) + + @omit_exception + def hkeys(self, *args, **kwargs): + return self.client.hkeys(*args, **kwargs) + + @omit_exception + def hexists(self, *args, **kwargs): + return self.client.hexists(*args, **kwargs) diff --git a/django_redis/client/default.py b/django_redis/client/default.py index 69ff0df4..7850d3c7 100644 --- a/django_redis/client/default.py +++ b/django_redis/client/default.py @@ -813,3 +813,79 @@ def touch( # Convert to milliseconds timeout = int(timeout * 1000) return bool(client.pexpire(key, timeout)) + + def hset( + self, + name: str, + key: KeyT, + value: EncodableT, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> int: + """ + Set the value of hash name at key to value. + Returns the number of fields added to the hash. + """ + if client is None: + client = self.get_client(write=True) + nkey = self.make_key(key, version=version) + nvalue = self.encode(value) + return int(client.hset(name, nkey, nvalue)) + + def hdel( + self, + name: str, + key: KeyT, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> int: + """ + Remove keys from hash name. + Returns the number of fields deleted from the hash. + """ + if client is None: + client = self.get_client(write=True) + nkey = self.make_key(key, version=version) + return int(client.hdel(name, nkey)) + + def hlen( + self, + name: str, + client: Optional[Redis] = None, + ) -> int: + """ + Return the number of items in hash name. + """ + if client is None: + client = self.get_client(write=False) + return int(client.hlen(name)) + + def hkeys( + self, + name: str, + client: Optional[Redis] = None, + ) -> List[Any]: + """ + Return a list of keys in hash name. + """ + if client is None: + client = self.get_client(write=False) + try: + return [self.reverse_key(k.decode()) for k in client.hkeys(name)] + except _main_exceptions as e: + raise ConnectionInterrupted(connection=client) from e + + def hexists( + self, + name: str, + key: KeyT, + version: Optional[int] = None, + client: Optional[Redis] = None, + ) -> bool: + """ + Return True if key exists in hash name, else False. + """ + if client is None: + client = self.get_client(write=False) + nkey = self.make_key(key, version=version) + return bool(client.hexists(name, nkey)) diff --git a/tests/test_backend.py b/tests/test_backend.py index 58d6b92c..550ce79c 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -797,3 +797,51 @@ def test_clear(self, cache: RedisCache): cache.clear() value_from_cache_after_clear = cache.get("foo") 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_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 + deleted_count = cache.hdel("foo_hash2", "foo1") + assert deleted_count == 1 + assert cache.hlen("foo_hash2") == 1 + assert not cache.hexists("foo_hash2", "foo1") + 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") + keys = cache.hkeys("foo_hash4") + assert len(keys) == 3 + for i in range(len(keys)): + 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")