cunla / fakeredis-py

Implementation of Redis in python without having a Redis server running. Fully compatible with using redis-py.
https://fakeredis.moransoftware.ca/
BSD 3-Clause "New" or "Revised" License
281 stars 48 forks source link

Support hash key expiration functions #321

Open j00bar opened 2 hours ago

j00bar commented 2 hours ago

Is your feature request related to a problem? Please describe. I'm trying to use fakeredis to mock interactions that use hexpireat and httl which are not presently implemented.

Describe the solution you'd like It would be great if those commands were implemented.

Describe alternatives you've considered I took a quick look to see if it was obvious how to submit a PR for it and read the docs but given it looks like the Hash type just wraps dict and doesn't have an additional structure to manage key lifetimes, it wasn't immediately obvious.

Additional context If you can give me a basic map for how to implement such a feature, I'd be happy to try my hand at it, but having only glanced at the codebase, I would need a sherpa.

Upvote & Fund

Fund with Polar

cunla commented 2 hours ago

Hi, thanks for reporting and for offering to implement support for these.

Here is some info to help you implement:

Otherwise, please also

Let me know if you need more help. I am happy to support any contribution.

j00bar commented 2 hours ago

Thanks! That all makes perfect sense.

The one thing I'd appreciate knowing your preference on: key.value in the context of one the @command decorated functions is a Hash - which is a wrapper around dict it looks like. I would need to store additional data for the timestamps - would your preference be to define a nested dictionary on Hash for that purpose, so I'd reference key.value.expirations or something like that?

cunla commented 2 hours ago

Oh, interesting, I hadn't realized that HEXPIREAT supports expiration of specific fields within the hash. In that case, like you suggested, the Hash type needs to be changed to support this.

To make it simple, I would add the expirations field to the Hash type, and work out something like this:

class Hash(dict):  # type:ignore
    DECODE_ERROR = msgs.INVALID_HASH_MSG
    redis_type = b"hash"

    def __init__(self):
        super().__init__()
        self.expirations: Dict[bytes, int] = {}

    def _check_expire(self, key: bytes) -> None:
        if key in self.expirations and self.expirations[key] < int(time.time()):
            del self[key]
            del self.expirations[key]

    def __get__(self, key: bytes) -> Any:
        self._check_expire(key)
        if key in self:
            return self[key]
        return self.__get__(key)

    def __contains__(self, item):
        self._check_expire(item)
        return super().__contains__(item)

    # more dict methods???

This should automatically make HGET/HEXISTS/... support the expiration.

j00bar commented 2 hours ago

Word. That makes total sense. I'll see what I can throw together. Thank you!