Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support redis hashes in the API #373

Open
pbysh opened this issue Mar 15, 2019 · 7 comments
Open

Support redis hashes in the API #373

pbysh opened this issue Mar 15, 2019 · 7 comments

Comments

@pbysh
Copy link

pbysh commented Mar 15, 2019

Following #371, I want to make a clear and simple case for supporting hset and hget within django-redis.

While I understand that this cache strives to be swappable with other cache backends, the truth of the matter is that I would wager over 50% of people using django-redis are doing so because they knew the platform they wanted to use - redis - and were simply looking for a django-compatible package for it. While I am not advocating (yet!) supporting every single redis feature, it seems curious to leave hashes out, as redis itself recommends using them whenever possible. Django is no stranger to platform-specific functionality, having database fields specific to PostgreSQL and the like.

From my experience in situations in which a library is supporting a specific back-end and expecting to be swappable with other libraries, the standard course of action is to be clear in the documentation that specific features are not available in the other back-ends and doing so would make it difficult to migrate. At that point it is up to the user if they want to lock themselves into redis, which I again wager many users would happily do, as it is the bomb :)

Why not use the native redis client?

I am coming from a use case in which I had rolled my own mini Cache class in which I was doing my own serialization (msgpack) and compression (zlib). I was happy to find a native solution I could use within my Django application, but was immediately distraught to see I'd have to use the native client for hash functionality. No worries, I thought, I'll use django-redis for non-hash stuff and do it by myself with hashes. What ends up happening then is that I have my serialization/compression functionality split into two places and if I wanted to switch I'd have to do it twice, and that point why even use django-redis at all? I very much like using something with the django ecosystem and appreciate not having to roll my own different serialization and compression functionality that I can easily swap between. I like having things like the key prefix and the versioning, as these are all things I did not have in my own custom class. So I definitely want to use django-redis, but then have to convert code to not use hashes if I don't want to be duplicating my concerns.

When the alternative is the seemingly simple path of simply allowing users like myself to use hash functions within the library at their own risk, it seems like a reasonable ask that we be allowed to do so.

I hope you will consider my suggestion.

@niwinz
Copy link
Collaborator

niwinz commented Apr 3, 2019

After a thinking a bit on this I'm open to see a PR with that and merge it ;)

@Mogost
Copy link
Member

Mogost commented Sep 24, 2019

I agree with @pbysh.
The fact that I need to use the native client for hset and hget is painful.
If someone has time, please provide a pool request.

@WisdomPill
Copy link
Member

@pbysh can you share your implementation and maybe submit a PR?

@masoodkamyab

This comment has been minimized.

@AlexDaniel
Copy link

#598 seems to be a duplicate of this. And yes, a very needed feature.

@some1ataplace
Copy link

some1ataplace commented Mar 29, 2023

Did not test any of this code but hopefully it helps someone make a PR.

To support Redis hashes in django-redis, you'll need to update the Django cache backend to include the new hash methods. You can do this by extending the DefaultClient class in django_redis/client/default.py and adding the new methods for hset, hdel, hlen, hkeys, and hexists. Then, update the RedisCache class in django_redis/cache.py to use the new client with hash support.

Here's an example of how you can implement this:

  1. Create a new file django_redis/client/hash.py with the following content:
from django_redis.client.default import DefaultClient


class HashClient(DefaultClient):
    def hset(self, key, field, value, version=None, client=None):
        key = self.make_key(key, version=version)
        self.validate_key(key)
        client = client or self.get_client(write=True)
        return client.hset(key, field, value)

    def hdel(self, key, field, version=None, client=None):
        key = self.make_key(key, version=version)
        self.validate_key(key)
        client = client or self.get_client(write=True)
        return client.hdel(key, field)

    def hlen(self, key, version=None, client=None):
        key = self.make_key(key, version=version)
        self.validate_key(key)
        client = client or self.get_client(write=True)
        return client.hlen(key)

    def hkeys(self, key, version=None, client=None):
        key = self.make_key(key, version=version)
        self.validate_key(key)
        client = client or self.get_client(write=True)
        return client.hkeys(key)

    def hexists(self, key, field, version=None, client=None):
        key = self.make_key(key, version=version)
        self.validate_key(key)
        client = client or self.get_client(write=True)
        return client.hexists(key, field)
  1. Update the RedisCache class in django_redis/cache.py to use the new HashClient:
from django_redis.client.hash import HashClient

class RedisCache(BaseCache):

    def init(self, server, params):
        self._client = HashClient
  1. Finally, update your Django settings to use the new cache backend:
CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
    }
}

Now you should be able to use Redis hashes with Django cache:

from django.core.cache import cache

cache.hset('my_hash', 'field1', 'value1')
cache.hdel('my_hash', 'field1')
cache.hlen('my_hash')
cache.hkeys('my_hash')
cache.hexists('my_hash', 'field1')

To support Redis hashes in the Django-Redis API, you can add the following methods to the django_redis.client.DefaultClient class:

import json

class DefaultClient:
    # ... existing code ...

    def hset(self, name, key, value):
        """
        Set the value of hash name at key to value.
        """
        self.get_client().hset(name, key, json.dumps(value))

    def hdel(self, name, keys):
        """
        Remove keys from hash name.
        """
        self.get_client().hdel(name,keys)

    def hlen(self, name):
        """
        Return the number of items in hash name.
        """
        return self.get_client().hlen(name)

    def hkeys(self, name):
        """
        Return a list of keys in hash name.
        """
        return self.get_client().hkeys(name)

    def hexists(self, name, key):
        """
        Return True if key exists in hash name, else False.
        """
        return self.get_client().hexists(name, key)

To add an interface similar to Redis-Py for hashes, you can also create a new class that extends the django_redis.client.DefaultDict class and adds methods for the various Redis hash operations. Here is an example implementation:

import json
from django_redis.client.default import DefaultClient

class RedisHash(DefaultClient):
    def hset(self, name, key, value):
        self.get_client().hset(name, key, json.dumps(value))

    def hget(self, name, key):
        value = self.get_client().hget(name, key)
        if value:
            return json.loads(value)
        return None

    def hmset(self, name, mapping):
        mapping = {k: json.dumps(v) for k, v in mapping.items()}
        self.get_client().hmset(name, mapping)

    def hmget(self, name, keys):
        values = self.get_client().hmget(name, keys)
        results = []
        for value in values:
            if value:
                results.append(json.loads(value))
            else:
                results.append(None)
        return results

    def hgetall(self, name):
        values = self.get_client().hgetall(name)
        results = {}
        for key, value in values.items():
            results[key.decode('utf-8')] = json.loads(value)
        return results

This RedisHash class extends the DefaultClient class and adds methods for the Redis hash operations such as hset, hget, hmset, hmget, and hgetall. It serializes and deserializes the values into JSON format using the json.dumps and json.loads functions.

You can use this RedisHash class wherever you use the Django-Redis API and need to interact with Redis hash data. For example:

from redis_hash import RedisHash

redis_hash = RedisHash()
redis_hash.hset('user:1', 'name', 'John')
redis_hash.hset('user:1', 'age', 25)
name = redis_hash.hget('user:1', 'name')
age = redis_hash.hget('user:1', 'age')
print(name)  # Output: John
print(age)  # Output: 25

@WisdomPill
Copy link
Member

thanks @some1ataplace but set and hash functionality have to be inside DefaultClient like #654 as it's easier for developers to have one client only to use most of the basic redis' commands

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants