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

Hash field expiration commands #3218

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
368 changes: 368 additions & 0 deletions redis/commands/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -5094,6 +5094,374 @@ def hstrlen(self, name: str, key: str) -> Union[Awaitable[int], int]:
"""
return self.execute_command("HSTRLEN", name, key, keys=[name])

def hexpire(
self,
name: KeyT,
seconds: ExpiryT,
*fields: str,
nx: bool = False,
xx: bool = False,
gt: bool = False,
lt: bool = False,
) -> ResponseT:
"""
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW on the topic of docstrings : as mentioned by this discussion in Stackoverflow we can use the Pyment tool to generate new reST documentation or convert existing one from other formats. This is probably going to help in the general direction of consistency.

IMHO it is a good idea to continue with the reST format you are using here chiefly because of two things - we can generate documentation using Sphinx and it is generally recommended by PEP 287

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After trying out some more things, I think that the Google style documentation would be better suited. The main reason is that it supports bullet lists in a straightforward way. With reST I could not get a bullet list properly formatted in the PyCharm documentation popup.

Also it seems to me that a great deal of the existing documentation is a flavor of the Google style. It would be the least effort to convert it to the proper Google format.

And, after enabling the napoleon preprocessor for Sphinx, this Google style documentation also gets converted well (after some additional fine tuning related to typing).

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense.

Sets or updates the expiration time for fields within a hash key, using relative
time in seconds.

If a field already has an expiration time, the behavior of the update can be
controlled using the `nx`, `xx`, `gt`, and `lt` parameters.

The return value provides detailed information about the outcome for each field.

For more information, see https://redis.io/commands/hexpire

Args:
name: The name of the hash key.
seconds: Expiration time in seconds, relative. Can be an integer, or a
Python `timedelta` object.
fields: List of fields within the hash to apply the expiration time to.
nx: Set expiry only when the field has no expiry.
xx: Set expiry only when the field has an existing expiry.
gt: Set expiry only when the new expiry is greater than the current one.
lt: Set expiry only when the new expiry is less than the current one.

Returns:
If the key does not exist, returns an empty list. If the key exists, returns
a list which contains for each field in the request:
- `-2` if the field does not exist.
- `0` if the specified NX | XX | GT | LT condition was not met.
- `1` if the expiration time was set or updated.
- `2` if the field was deleted because the specified expiration time is
in the past.
"""
conditions = [nx, xx, gt, lt]
if sum(conditions) > 1:
raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.")

if isinstance(seconds, datetime.timedelta):
seconds = int(seconds.total_seconds())

options = []
if nx:
options.append("NX")
if xx:
options.append("XX")
if gt:
options.append("GT")
if lt:
options.append("LT")

return self.execute_command(
"HEXPIRE", name, seconds, *options, "FIELDS", len(fields), *fields
)

def hpexpire(
self,
name: KeyT,
milliseconds: ExpiryT,
*fields: str,
nx: bool = False,
xx: bool = False,
gt: bool = False,
lt: bool = False,
) -> ResponseT:
"""
Sets or updates the expiration time for fields within a hash key, using relative
time in milliseconds.

If a field already has an expiration time, the behavior of the update can be
controlled using the `nx`, `xx`, `gt`, and `lt` parameters.

The return value provides detailed information about the outcome for each field.

For more information, see https://redis.io/commands/hpexpire

Args:
name: The name of the hash key.
milliseconds: Expiration time in milliseconds, relative. Can be an integer,
or a Python `timedelta` object.
fields: List of fields within the hash to apply the expiration time to.
nx: Set expiry only when the field has no expiry.
xx: Set expiry only when the field has an existing expiry.
gt: Set expiry only when the new expiry is greater than the current one.
lt: Set expiry only when the new expiry is less than the current one.

Returns:
If the key does not exist, returns an empty list. If the key exists, returns
a list which contains for each field in the request:
- `-2` if the field does not exist.
- `0` if the specified NX | XX | GT | LT condition was not met.
- `1` if the expiration time was set or updated.
- `2` if the field was deleted because the specified expiration time is
in the past.
"""
conditions = [nx, xx, gt, lt]
if sum(conditions) > 1:
raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.")

if isinstance(milliseconds, datetime.timedelta):
milliseconds = int(milliseconds.total_seconds() * 1000)

options = []
if nx:
options.append("NX")
if xx:
options.append("XX")
if gt:
options.append("GT")
if lt:
options.append("LT")

return self.execute_command(
"HPEXPIRE", name, milliseconds, *options, "FIELDS", len(fields), *fields
)

def hexpireat(
self,
name: KeyT,
unix_time_seconds: AbsExpiryT,
*fields: str,
nx: bool = False,
xx: bool = False,
gt: bool = False,
lt: bool = False,
) -> ResponseT:
"""
Sets or updates the expiration time for fields within a hash key, using an
absolute Unix timestamp in seconds.

If a field already has an expiration time, the behavior of the update can be
controlled using the `nx`, `xx`, `gt`, and `lt` parameters.

The return value provides detailed information about the outcome for each field.

For more information, see https://redis.io/commands/hexpireat

Args:
name: The name of the hash key.
unix_time_seconds: Expiration time as Unix timestamp in seconds. Can be an
integer or a Python `datetime` object.
fields: List of fields within the hash to apply the expiration time to.
nx: Set expiry only when the field has no expiry.
xx: Set expiry only when the field has an existing expiration time.
gt: Set expiry only when the new expiry is greater than the current one.
lt: Set expiry only when the new expiry is less than the current one.

Returns:
If the key does not exist, returns an empty list. If the key exists, returns
a list which contains for each field in the request:
- `-2` if the field does not exist.
- `0` if the specified NX | XX | GT | LT condition was not met.
- `1` if the expiration time was set or updated.
- `2` if the field was deleted because the specified expiration time is
in the past.
"""
conditions = [nx, xx, gt, lt]
if sum(conditions) > 1:
raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.")

if isinstance(unix_time_seconds, datetime.datetime):
unix_time_seconds = int(unix_time_seconds.timestamp())

options = []
if nx:
options.append("NX")
if xx:
options.append("XX")
if gt:
options.append("GT")
if lt:
options.append("LT")

return self.execute_command(
"HEXPIREAT",
name,
unix_time_seconds,
*options,
"FIELDS",
len(fields),
*fields,
)

def hpexpireat(
self,
name: KeyT,
unix_time_milliseconds: AbsExpiryT,
*fields: str,
nx: bool = False,
xx: bool = False,
gt: bool = False,
lt: bool = False,
) -> ResponseT:
"""
Sets or updates the expiration time for fields within a hash key, using an
absolute Unix timestamp in milliseconds.

If a field already has an expiration time, the behavior of the update can be
controlled using the `nx`, `xx`, `gt`, and `lt` parameters.

The return value provides detailed information about the outcome for each field.

For more information, see https://redis.io/commands/hpexpireat

Args:
name: The name of the hash key.
unix_time_milliseconds: Expiration time as Unix timestamp in milliseconds.
Can be an integer or a Python `datetime` object.
fields: List of fields within the hash to apply the expiry.
nx: Set expiry only when the field has no expiry.
xx: Set expiry only when the field has an existing expiry.
gt: Set expiry only when the new expiry is greater than the current one.
lt: Set expiry only when the new expiry is less than the current one.

Returns:
If the key does not exist, returns an empty list. If the key exists, returns
a list which contains for each field in the request:
- `-2` if the field does not exist.
- `0` if the specified NX | XX | GT | LT condition was not met.
- `1` if the expiration time was set or updated.
- `2` if the field was deleted because the specified expiration time is
in the past.
"""
conditions = [nx, xx, gt, lt]
if sum(conditions) > 1:
raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.")

if isinstance(unix_time_milliseconds, datetime.datetime):
unix_time_milliseconds = int(unix_time_milliseconds.timestamp() * 1000)

options = []
if nx:
options.append("NX")
if xx:
options.append("XX")
if gt:
options.append("GT")
if lt:
options.append("LT")

return self.execute_command(
"HPEXPIREAT",
name,
unix_time_milliseconds,
*options,
"FIELDS",
len(fields),
*fields,
)

def hpersist(self, name: KeyT, *fields: str) -> ResponseT:
"""
Removes the expiration time for each specified field in a hash.

For more information, see https://redis.io/commands/hpersist

Args:
name: The name of the hash key.
fields: A list of fields within the hash from which to remove the
expiration time.

Returns:
If the key does not exist, returns an empty list. If the key exists, returns
a list which contains for each field in the request:
- `-2` if the field does not exist.
- `-1` if the field exists but has no associated expiration time.
- `1` if the expiration time was successfully removed from the field.
"""
return self.execute_command("HPERSIST", name, "FIELDS", len(fields), *fields)

def hexpiretime(self, key: KeyT, *fields: str) -> ResponseT:
"""
Returns the expiration times of hash fields as Unix timestamps in seconds.

For more information, see https://redis.io/commands/hexpiretime

Args:
key: The hash key.
fields: A list of fields within the hash for which to get the expiration
time.

Returns:
If the key does not exist, returns an empty list. If the key exists, returns
a list which contains for each field in the request:
- `-2` if the field does not exist.
- `-1` if the field exists but has no associated expire time.
- A positive integer representing the expiration Unix timestamp in
seconds, if the field has an associated expiration time.
"""
return self.execute_command(
"HEXPIRETIME", key, "FIELDS", len(fields), *fields, keys=[key]
)

def hpexpiretime(self, key: KeyT, *fields: str) -> ResponseT:
"""
Returns the expiration times of hash fields as Unix timestamps in milliseconds.

For more information, see https://redis.io/commands/hpexpiretime

Args:
key: The hash key.
fields: A list of fields within the hash for which to get the expiration
time.

Returns:
If the key does not exist, returns an empty list. If the key exists, returns
a list which contains for each field in the request:
- `-2` if the field does not exist.
- `-1` if the field exists but has no associated expire time.
- A positive integer representing the expiration Unix timestamp in
milliseconds, if the field has an associated expiration time.
"""
return self.execute_command(
"HPEXPIRETIME", key, "FIELDS", len(fields), *fields, keys=[key]
)

def httl(self, key: KeyT, *fields: str) -> ResponseT:
"""
Returns the TTL (Time To Live) in seconds for each specified field within a hash
key.

For more information, see https://redis.io/commands/httl

Args:
key: The hash key.
fields: A list of fields within the hash for which to get the TTL.

Returns:
If the key does not exist, returns an empty list. If the key exists, returns
a list which contains for each field in the request:
- `-2` if the field does not exist.
- `-1` if the field exists but has no associated expire time.
- A positive integer representing the TTL in seconds if the field has
an associated expiration time.
"""
return self.execute_command(
"HTTL", key, "FIELDS", len(fields), *fields, keys=[key]
)

def hpttl(self, key: KeyT, *fields: str) -> ResponseT:
"""
Returns the TTL (Time To Live) in milliseconds for each specified field within a
hash key.

For more information, see https://redis.io/commands/hpttl

Args:
key: The hash key.
fields: A list of fields within the hash for which to get the TTL.

Returns:
If the key does not exist, returns an empty list. If the key exists, returns
a list which contains for each field in the request:
- `-2` if the field does not exist.
- `-1` if the field exists but has no associated expire time.
- A positive integer representing the TTL in milliseconds if the field
has an associated expiration time.
"""
return self.execute_command(
"HPTTL", key, "FIELDS", len(fields), *fields, keys=[key]
)


AsyncHashCommands = HashCommands

Expand Down