From 9e5a79701f62d01b8298d98a30e5e3fe1b7cbfbc Mon Sep 17 00:00:00 2001 From: Gabriel Erzse Date: Fri, 26 Apr 2024 19:32:21 +0300 Subject: [PATCH 01/12] Hash field expiration commands --- pytest.ini | 2 +- redis/commands/core.py | 479 ++++++++++++++++++++++++++++++++ tests/test_asyncio/test_hash.py | 289 +++++++++++++++++++ tests/test_hash.py | 289 +++++++++++++++++++ 4 files changed, 1058 insertions(+), 1 deletion(-) create mode 100644 tests/test_asyncio/test_hash.py create mode 100644 tests/test_hash.py diff --git a/pytest.ini b/pytest.ini index f1b716ae96..c574a2eddc 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,5 +1,5 @@ [pytest] -addopts = -s +addopts = -s --doctest-modules markers = redismod: run only the redis module tests pipeline: pipeline tests diff --git a/redis/commands/core.py b/redis/commands/core.py index 566846a20e..35a796b8fd 100644 --- a/redis/commands/core.py +++ b/redis/commands/core.py @@ -5081,6 +5081,485 @@ 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: + """ + Set or update 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. + + :param name: The name of the hash key. + :param seconds: Expiration time in seconds, relative. Can be an integer, or a + Python `timedelta` object. + :param fields: List of fields within the hash to apply the expiration time to. + :param nx: Set expiry only when the field has no expiry. + :param xx: Set expiry only when the field has an existing expiry. + :param gt: Set expiry only when the new expiry is greater than current one. + :param lt: Set expiry only when the new expiry is less than current one. + :return: If the key does not exist, returns `None`. 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. + + Example: + >>> import redis + >>> import time + >>> + >>> r = redis.Redis(host='localhost', port=6379) + >>> _ = r.delete('vehicle:10') + >>> r.hset('vehicle:10', 'id', 'f3a64766-5dec-4e07-b5f8-3c0f8e1a5e4b') + 1 + >>> r.hset('vehicle:10', 'longitude', '-122.335167') + 1 + >>> r.hset('vehicle:10', 'latitude', '37.779284') + 1 + >>> r.hexpire('vehicle:10', 1, 'longitude', 'latitude', 'foobar') + [1, 1, -2] + >>> time.sleep(2) + >>> r.hexists('vehicle:10', 'longitude') + False + >>> r.hgetall('vehicle:10') + {b'id': b'f3a64766-5dec-4e07-b5f8-3c0f8e1a5e4b'} + + For more information see https://redis.io/commands/hexpire + """ + 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, 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: + """ + Set or update 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. + + :param name: The name of the hash key. + :param milliseconds: Expiration time in milliseconds, relative. Can be an + integer, or a Python `timedelta` object. + :param fields: List of fields within the hash to apply the expiration time to. + :param nx: Set expiry only when the field has no expiry. + :param xx: Set expiry only when the field has an existing expiry. + :param gt: Set expiry only when the new expiry is greater than current one. + :param lt: Set expiry only when the new expiry is less than current one. + :return: If the key does not exist, returns `None`. 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. + + Example: + >>> import redis + >>> import time + >>> + >>> r = redis.Redis(host='localhost', port=6379) + >>> _ = r.delete('sensor:456') + >>> r.hset('sensor:456', 'id', 'sensor_456') + 1 + >>> r.hset('sensor:456', 'temperature', '36.6') + 1 + >>> r.hset('sensor:456', 'alert', 'overheat') + 1 + >>> r.hpexpire('sensor:456', 800, 'temperature', 'alert') + [1, 1] + >>> time.sleep(0.9) + >>> r.hexists('sensor:456', 'alert') + False + >>> r.hgetall('sensor:456') + {b'id': b'sensor_456'} + + For more information see https://redis.io/commands/hpexpire + """ + 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, 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: + """ + Set or update 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. + + :param name: The name of the hash key. + :param unix_time_seconds: Expiration time as Unix timestamp in milliseconds. Can + be an integer or a Python `datetime` object. + :param fields: List of fields within the hash to apply the expiration time to. + :param nx: Set expiry only when the field has no expiry. + :param xx: Set expiry only when the field has an existing expiration time. + :param gt: Set expiry only when the new expiry is greater than current one. + :param lt: Set expiry only when the new expiry is less than current one. + :return: If the key does not exist, returns `None`. 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. + + Example: + >>> import redis + >>> from datetime import datetime, timedelta + >>> + >>> r = redis.Redis(host='localhost', port=6379) + >>> _ = r.delete('vehicle:10') + >>> r.hset('vehicle:10', 'id', 'f3a64766-5dec-4e07-b5f8-3c0f8e1a5e4b') + 1 + >>> r.hset('vehicle:10', 'longitude', '-122.335167') + 1 + >>> r.hset('vehicle:10', 'latitude', '37.779284') + 1 + >>> exp_time = datetime.now() + timedelta(seconds=1) + >>> r.hexpireat('vehicle:10', exp_time, 'longitude', 'latitude', 'foobar') + [1, 1, -2] + >>> time.sleep(2) + >>> r.hexists('vehicle:10', 'longitude') + False + >>> r.hgetall('vehicle:10') + {b'id': b'f3a64766-5dec-4e07-b5f8-3c0f8e1a5e4b'} + + For more information see https://redis.io/commands/hexpireat + """ + 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, 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: + """ + Set or update 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. + + :param name: The name of the hash key. + :param unix_time_milliseconds: Expiration time as Unix timestamp in + milliseconds. Can be an integer or a Python + `datetime` object. + :param fields: List of fields within the hash to apply the expiry. + :param nx: Set expiry only when the field has no expiry. + :param xx: Set expiry only when the field has an existing expiry. + :param gt: Set expiry only when the new expiry is greater than current one. + :param lt: Set expiry only when the new expiry is less than current one. + :return: If the key does not exist, returns `None`. 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. + + Examples: + >>> import redis + >>> from datetime import datetime, timedelta + >>> + >>> r = redis.Redis(host='localhost', port=6379) + >>> _ = r.delete('sensor:456') + >>> r.hset('sensor:456', 'id', 'sensor_456') + 1 + >>> r.hset('sensor:456', 'temperature', '36.6') + 1 + >>> r.hset('sensor:456', 'alert', 'overheat') + 1 + >>> exp_time = datetime.now() + timedelta(milliseconds=800) + >>> r.hpexpireat('sensor:456', exp_time, 'temperature', 'alert') + [1, 1] + >>> time.sleep(0.9) + >>> r.hexists('sensor:456', 'alert') + False + >>> r.hgetall('sensor:456') + {b'id': b'sensor_456'} + + For more information see https://redis.io/commands/hpexpireat + """ + 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, len(fields), *fields + ) + + def hpersist(self, name: KeyT, *fields: str) -> ResponseT: + """ + Removes the expiration time for each specified field in a hash. + + :param name: The name of the hash key. + :param fields: A list of fields within the hash from which to remove the + expiration time from. + :return: If the key does not exist, returns `None`. 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. + + Example: + >>> import redis + >>> + >>> r = redis.Redis(host='localhost', port=6379) + >>> _ = r.delete('vehicle:10') + >>> r.hset('vehicle:10', 'id', 'f3a64766-5dec-4e07-b5f8-3c0f8e1a5e4b') + 1 + >>> r.hset('vehicle:10', 'longitude', '-122.335167') + 1 + >>> r.hset('vehicle:10', 'latitude', '37.779284') + 1 + >>> r.hexpire('vehicle:10', 5, 'longitude', 'latitude') + [1, 1] + >>> r.hpersist('vehicle:10', 'id', 'longitude', 'foobar') + [-1, 1, -2] + + For more information see https://redis.io/commands/hpersist + """ + return self.execute_command("HPERSIST", name, len(fields), *fields) + + def hexpiretime(self, key: str, *fields: str) -> ResponseT: + """ + Returns the expiration times of hash fields as Unix timestamps in seconds. + + :param key: The hash key. + :param fields: A list of fields within the hash for which to get the expiration + time. + :return: `None` if the key does not exist. Otherwise, a list containing the + result for each field, in the same order as requested. The result for + each field can be: + - `-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. + + Examples: + >>> import redis + >>> from datetime import datetime, timedelta + >>> + >>> r = redis.Redis(host='localhost', port=6379) + >>> _ = r.delete('vehicle:10') + >>> r.hset('vehicle:10', 'id', 'f3a64766-5dec-4e07-b5f8-3c0f8e1a5e4b') + 1 + >>> r.hset('vehicle:10', 'longitude', '-122.335167') + 1 + >>> r.hset('vehicle:10', 'latitude', '37.779284') + 1 + >>> r.hexpireat('vehicle:10', 32503672800, 'longitude', 'latitude') + [1, 1] + >>> r.hexpiretime('vehicle:10', 'id', 'longitude', 'foobar') + [-1, 32503672800, -2] + + For more information see https://redis.io/commands/hexpiretime + """ + return self.execute_command("HEXPIRETIME", key, len(fields), *fields) + + def hpexpiretime(self, key: str, *fields: str) -> ResponseT: + """ + Returns the expiration times of hash fields as Unix timestamps in milliseconds. + + :param key: The hash key. + :param fields: A list of fields within the hash for which to get the expiration + time. + :return: `None` if the key does not exist. Otherwise, a list containing the + result for each field, in the same order as requested. The result for + each field can be: + - `-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. + + Examples: + >>> import redis + >>> from datetime import datetime, timedelta + >>> + >>> r = redis.Redis(host='localhost', port=6379) + >>> _ = r.delete('vehicle:10') + >>> r.hset('vehicle:10', 'id', 'f3a64766-5dec-4e07-b5f8-3c0f8e1a5e4b') + 1 + >>> r.hset('vehicle:10', 'longitude', '-122.335167') + 1 + >>> r.hset('vehicle:10', 'latitude', '37.779284') + 1 + >>> r.hexpireat('vehicle:10', 32503672800, 'longitude', 'latitude') + [1, 1] + >>> r.hpexpiretime('vehicle:10', 'id', 'longitude', 'foobar') + [-1, 32503672800000, -2] + + For more information see https://redis.io/commands/hpexpiretime + """ + return self.execute_command("HPEXPIRETIME", key, len(fields), *fields) + + def httl(self, key: str, *fields: str) -> ResponseT: + """ + Returns the TTL (Time To Live) in seconds for each specified field within a hash + key. + + :param key: The hash key. + :param fields: A list of fields within the hash for which to get the TTL. + :return: `None` if the key does not exist. Otherwise, a list containing the + result for each field, in the same order as requested. The result for + each field can be: + - `-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. + + Examples: + >>> import redis + >>> + >>> r = redis.Redis(host='localhost', port=6379) + >>> _ = r.delete('vehicle:10') + >>> r.hset('vehicle:10', 'id', 'f3a64766-5dec-4e07-b5f8-3c0f8e1a5e4b') + 1 + >>> r.hset('vehicle:10', 'longitude', '-122.335167') + 1 + >>> r.hset('vehicle:10', 'latitude', '37.779284') + 1 + >>> r.hexpire('vehicle:10', 5, 'longitude', 'latitude') + [1, 1] + >>> # Note: we can't know precisely the returned value for 'longitude', + >>> # because time passes between the two commands. It can be 5, or 4. + >>> r.httl('vehicle:10', 'id', 'longitude', 'foobar') # doctest: +ELLIPSIS + [-1, ..., -2] + + For more information see https://redis.io/commands/httl + """ + return self.execute_command("HTTL", key, len(fields), *fields) + + def hpttl(self, key: str, *fields: str) -> ResponseT: + """ + Returns the TTL (Time To Live) in milliseconds for each specified field within a + hash key. + + :param key: The hash key. + :param fields: A list of fields within the hash for which to get the TTL. + :return: `None` if the key does not exist. Otherwise, a list containing the + result for each field, in the same order as requested. The result for + each field can be: + - `-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. + + Examples: + >>> import redis + >>> + >>> r = redis.Redis(host='localhost', port=6379) + >>> _ = r.delete('vehicle:10') + >>> r.hset('vehicle:10', 'id', 'f3a64766-5dec-4e07-b5f8-3c0f8e1a5e4b') + 1 + >>> r.hset('vehicle:10', 'longitude', '-122.335167') + 1 + >>> r.hset('vehicle:10', 'latitude', '37.779284') + 1 + >>> r.hexpire('vehicle:10', 5, 'longitude', 'latitude') + [1, 1] + >>> # Note: we can't know precisely the returned value for 'longitude' + >>> # because time passes between the two commands. + >>> # It can be 5000, or a bit less. + >>> r.hpttl('vehicle:10', 'id', 'longitude', 'foobar') # doctest: +ELLIPSIS + [-1, ..., -2] + + For more information see https://redis.io/commands/hpttl + """ + return self.execute_command("HPTTL", key, len(fields), *fields) + AsyncHashCommands = HashCommands diff --git a/tests/test_asyncio/test_hash.py b/tests/test_asyncio/test_hash.py new file mode 100644 index 0000000000..986cf513dc --- /dev/null +++ b/tests/test_asyncio/test_hash.py @@ -0,0 +1,289 @@ +import asyncio +from datetime import timedelta, datetime + +from tests.conftest import skip_if_server_version_lt + + +@skip_if_server_version_lt("7.4.0") +async def test_hexpire_basic(r): + await r.delete('test:hash') + await r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + assert await r.hexpire('test:hash', 1, 'field1') == [1] + await asyncio.sleep(1.1) + assert await r.hexists('test:hash', 'field1') is False + assert await r.hexists('test:hash', 'field2') is True + + +@skip_if_server_version_lt("7.4.0") +async def test_hexpire_with_timedelta(r): + await r.delete('test:hash') + await r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + assert await r.hexpire('test:hash', timedelta(seconds=1), 'field1') == [1] + await asyncio.sleep(1.1) + assert await r.hexists('test:hash', 'field1') is False + assert await r.hexists('test:hash', 'field2') is True + + +@skip_if_server_version_lt("7.4.0") +async def test_hexpire_conditions(r): + await r.delete('test:hash') + await r.hset('test:hash', mapping={'field1': 'value1'}) + assert await r.hexpire('test:hash', 2, 'field1', xx=True) == [0] + assert await r.hexpire('test:hash', 2, 'field1', nx=True) == [1] + assert await r.hexpire('test:hash', 1, 'field1', xx=True) == [1] + assert await r.hexpire('test:hash', 2, 'field1', nx=True) == [0] + await asyncio.sleep(1.1) + assert await r.hexists('test:hash', 'field1') is False + await r.hset('test:hash', 'field1', 'value1') + await r.hexpire('test:hash', 2, 'field1') + assert await r.hexpire('test:hash', 1, 'field1', gt=True) == [0] + assert await r.hexpire('test:hash', 1, 'field1', lt=True) == [1] + await asyncio.sleep(1.1) + assert await r.hexists('test:hash', 'field1') is False + + +@skip_if_server_version_lt("7.4.0") +async def test_hexpire_nonexistent_key_or_field(r): + await r.delete('test:hash') + assert await r.hexpire('test:hash', 1, 'field1') is None + await r.hset('test:hash', 'field1', 'value1') + assert await r.hexpire('test:hash', 1, 'nonexistent_field') == [-2] + + +@skip_if_server_version_lt("7.4.0") +async def test_hexpire_multiple_fields(r): + await r.delete('test:hash') + await r.hset('test:hash', + mapping={'field1': 'value1', 'field2': 'value2', 'field3': 'value3'}) + assert await r.hexpire('test:hash', 1, 'field1', 'field2') == [1, 1] + await asyncio.sleep(1.1) + assert await r.hexists('test:hash', 'field1') is False + assert await r.hexists('test:hash', 'field2') is False + assert await r.hexists('test:hash', 'field3') is True + + +@skip_if_server_version_lt("7.4.0") +async def test_hpexpire_basic(r): + await r.delete('test:hash') + await r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + assert await r.hpexpire('test:hash', 500, 'field1') == [1] + await asyncio.sleep(0.6) + assert await r.hexists('test:hash', 'field1') is False + assert await r.hexists('test:hash', 'field2') is True + + +@skip_if_server_version_lt("7.4.0") +async def test_hpexpire_with_timedelta(r): + await r.delete('test:hash') + await r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + assert await r.hpexpire('test:hash', timedelta(milliseconds=500), 'field1') == [1] + await asyncio.sleep(0.6) + assert await r.hexists('test:hash', 'field1') is False + assert await r.hexists('test:hash', 'field2') is True + + +@skip_if_server_version_lt("7.4.0") +async def test_hpexpire_conditions(r): + await r.delete('test:hash') + await r.hset('test:hash', mapping={'field1': 'value1'}) + assert await r.hpexpire('test:hash', 1500, 'field1', xx=True) == [0] + assert await r.hpexpire('test:hash', 1500, 'field1', nx=True) == [1] + assert await r.hpexpire('test:hash', 500, 'field1', xx=True) == [1] + assert await r.hpexpire('test:hash', 1500, 'field1', nx=True) == [0] + await asyncio.sleep(0.6) + assert await r.hexists('test:hash', 'field1') is False + await r.hset('test:hash', 'field1', 'value1') + await r.hpexpire('test:hash', 1000, 'field1') + assert await r.hpexpire('test:hash', 500, 'field1', gt=True) == [0] + assert await r.hpexpire('test:hash', 500, 'field1', lt=True) == [1] + await asyncio.sleep(0.6) + assert await r.hexists('test:hash', 'field1') is False + + +@skip_if_server_version_lt("7.4.0") +async def test_hpexpire_nonexistent_key_or_field(r): + await r.delete('test:hash') + assert await r.hpexpire('test:hash', 500, 'field1') is None + await r.hset('test:hash', 'field1', 'value1') + assert await r.hpexpire('test:hash', 500, 'nonexistent_field') == [-2] + + +@skip_if_server_version_lt("7.4.0") +async def test_hpexpire_multiple_fields(r): + await r.delete('test:hash') + await r.hset('test:hash', + mapping={'field1': 'value1', 'field2': 'value2', 'field3': 'value3'}) + assert await r.hpexpire('test:hash', 500, 'field1', 'field2') == [1, 1] + await asyncio.sleep(0.6) + assert await r.hexists('test:hash', 'field1') is False + assert await r.hexists('test:hash', 'field2') is False + assert await r.hexists('test:hash', 'field3') is True + + +@skip_if_server_version_lt("7.4.0") +async def test_hexpireat_basic(r): + await r.delete('test:hash') + await r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + exp_time = int((datetime.now() + timedelta(seconds=1)).timestamp()) + assert await r.hexpireat('test:hash', exp_time, 'field1') == [1] + await asyncio.sleep(1.1) + assert await r.hexists('test:hash', 'field1') is False + assert await r.hexists('test:hash', 'field2') is True + + +@skip_if_server_version_lt("7.4.0") +async def test_hexpireat_with_datetime(r): + await r.delete('test:hash') + await r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + exp_time = datetime.now() + timedelta(seconds=1) + assert await r.hexpireat('test:hash', exp_time, 'field1') == [1] + await asyncio.sleep(1.1) + assert await r.hexists('test:hash', 'field1') is False + assert await r.hexists('test:hash', 'field2') is True + + +@skip_if_server_version_lt("7.4.0") +async def test_hexpireat_conditions(r): + await r.delete('test:hash') + await r.hset('test:hash', mapping={'field1': 'value1'}) + future_exp_time = int((datetime.now() + timedelta(seconds=2)).timestamp()) + past_exp_time = int((datetime.now() - timedelta(seconds=1)).timestamp()) + assert await r.hexpireat('test:hash', future_exp_time, 'field1', xx=True) == [0] + assert await r.hexpireat('test:hash', future_exp_time, 'field1', nx=True) == [1] + assert await r.hexpireat('test:hash', past_exp_time, 'field1', gt=True) == [0] + assert await r.hexpireat('test:hash', past_exp_time, 'field1', lt=True) == [2] + assert await r.hexists('test:hash', 'field1') is False + + +@skip_if_server_version_lt("7.4.0") +async def test_hexpireat_nonexistent_key_or_field(r): + await r.delete('test:hash') + future_exp_time = int((datetime.now() + timedelta(seconds=1)).timestamp()) + assert await r.hexpireat('test:hash', future_exp_time, 'field1') is None + await r.hset('test:hash', 'field1', 'value1') + assert await r.hexpireat('test:hash', future_exp_time, 'nonexistent_field') == [-2] + + +@skip_if_server_version_lt("7.4.0") +async def test_hexpireat_multiple_fields(r): + await r.delete('test:hash') + await r.hset('test:hash', + mapping={'field1': 'value1', 'field2': 'value2', 'field3': 'value3'}) + exp_time = int((datetime.now() + timedelta(seconds=1)).timestamp()) + assert await r.hexpireat('test:hash', exp_time, 'field1', 'field2') == [1, 1] + await asyncio.sleep(1.1) + assert await r.hexists('test:hash', 'field1') is False + assert await r.hexists('test:hash', 'field2') is False + assert await r.hexists('test:hash', 'field3') is True + + +@skip_if_server_version_lt("7.4.0") +async def test_hpexpireat_basic(r): + await r.delete('test:hash') + await r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + exp_time = int((datetime.now() + timedelta(milliseconds=400)).timestamp() * 1000) + assert await r.hpexpireat('test:hash', exp_time, 'field1') == [1] + await asyncio.sleep(0.5) + assert await r.hexists('test:hash', 'field1') is False + assert await r.hexists('test:hash', 'field2') is True + + +@skip_if_server_version_lt("7.4.0") +async def test_hpexpireat_with_datetime(r): + await r.delete('test:hash') + await r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + exp_time = datetime.now() + timedelta(milliseconds=400) + assert await r.hpexpireat('test:hash', exp_time, 'field1') == [1] + await asyncio.sleep(0.5) + assert await r.hexists('test:hash', 'field1') is False + assert await r.hexists('test:hash', 'field2') is True + + +@skip_if_server_version_lt("7.4.0") +async def test_hpexpireat_conditions(r): + await r.delete('test:hash') + await r.hset('test:hash', mapping={'field1': 'value1'}) + future_exp_time = int( + (datetime.now() + timedelta(milliseconds=500)).timestamp() * 1000) + past_exp_time = int( + (datetime.now() - timedelta(milliseconds=500)).timestamp() * 1000) + assert await r.hpexpireat('test:hash', future_exp_time, 'field1', xx=True) == [0] + assert await r.hpexpireat('test:hash', future_exp_time, 'field1', nx=True) == [1] + assert await r.hpexpireat('test:hash', past_exp_time, 'field1', gt=True) == [0] + assert await r.hpexpireat('test:hash', past_exp_time, 'field1', lt=True) == [2] + assert await r.hexists('test:hash', 'field1') is False + + +@skip_if_server_version_lt("7.4.0") +async def test_hpexpireat_nonexistent_key_or_field(r): + await r.delete('test:hash') + future_exp_time = int( + (datetime.now() + timedelta(milliseconds=500)).timestamp() * 1000) + assert await r.hpexpireat('test:hash', future_exp_time, 'field1') is None + await r.hset('test:hash', 'field1', 'value1') + assert await r.hpexpireat('test:hash', future_exp_time, 'nonexistent_field') == [-2] + + +@skip_if_server_version_lt("7.4.0") +async def test_hpexpireat_multiple_fields(r): + await r.delete('test:hash') + await r.hset('test:hash', + mapping={'field1': 'value1', 'field2': 'value2', 'field3': 'value3'}) + exp_time = int((datetime.now() + timedelta(milliseconds=400)).timestamp() * 1000) + assert await r.hpexpireat('test:hash', exp_time, 'field1', 'field2') == [1, 1] + await asyncio.sleep(0.5) + assert await r.hexists('test:hash', 'field1') is False + assert await r.hexists('test:hash', 'field2') is False + assert await r.hexists('test:hash', 'field3') is True + + +@skip_if_server_version_lt("7.4.0") +async def test_hpersist_multiple_fields_mixed_conditions(r): + await r.delete('test:hash') + await r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + await r.hexpire('test:hash', 5000, 'field1') + assert await r.hpersist('test:hash', 'field1', 'field2', 'field3') == [1, -1, -2] + + +@skip_if_server_version_lt("7.4.0") +async def test_hexpiretime_multiple_fields_mixed_conditions(r): + await r.delete('test:hash') + await r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + future_time = int((datetime.now() + timedelta(minutes=30)).timestamp()) + await r.hexpireat('test:hash', future_time, 'field1') + result = await r.hexpiretime('test:hash', 'field1', 'field2', 'field3') + assert future_time - 10 < result[0] <= future_time + assert result[1:] == [-1, -2] + + +@skip_if_server_version_lt("7.4.0") +async def test_hpexpiretime_multiple_fields_mixed_conditions(r): + await r.delete('test:hash') + await r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + future_time = int((datetime.now() + timedelta(minutes=30)).timestamp()) + await r.hexpireat('test:hash', future_time, 'field1') + result = await r.hpexpiretime('test:hash', 'field1', 'field2', 'field3') + assert future_time * 1000 - 10000 < result[0] <= future_time * 1000 + assert result[1:] == [-1, -2] + + +@skip_if_server_version_lt("7.4.0") +async def test_ttl_multiple_fields_mixed_conditions(r): + await r.delete('test:hash') + await r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + future_time = int((datetime.now() + timedelta(minutes=30)).timestamp()) + await r.hexpireat('test:hash', future_time, 'field1') + result = await r.httl('test:hash', 'field1', 'field2', 'field3') + assert 30 * 60 - 10 < result[0] <= 30 * 60 + assert result[1:] == [-1, -2] + + +@skip_if_server_version_lt("7.4.0") +async def test_pttl_multiple_fields_mixed_conditions(r): + await r.delete('test:hash') + await r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + future_time = int((datetime.now() + timedelta(minutes=30)).timestamp()) + await r.hexpireat('test:hash', future_time, 'field1') + result = await r.hpttl('test:hash', 'field1', 'field2', 'field3') + assert 30 * 60000 - 10000 < result[0] <= 30 * 60000 + assert result[1:] == [-1, -2] diff --git a/tests/test_hash.py b/tests/test_hash.py new file mode 100644 index 0000000000..2927946c13 --- /dev/null +++ b/tests/test_hash.py @@ -0,0 +1,289 @@ +import time +from datetime import timedelta, datetime + +from tests.conftest import skip_if_server_version_lt + + +@skip_if_server_version_lt("7.4.0") +def test_hexpire_basic(r): + r.delete('test:hash') + r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + assert r.hexpire('test:hash', 1, 'field1') == [1] + time.sleep(1.1) + assert r.hexists('test:hash', 'field1') is False + assert r.hexists('test:hash', 'field2') is True + + +@skip_if_server_version_lt("7.4.0") +def test_hexpire_with_timedelta(r): + r.delete('test:hash') + r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + assert r.hexpire('test:hash', timedelta(seconds=1), 'field1') == [1] + time.sleep(1.1) + assert r.hexists('test:hash', 'field1') is False + assert r.hexists('test:hash', 'field2') is True + + +@skip_if_server_version_lt("7.4.0") +def test_hexpire_conditions(r): + r.delete('test:hash') + r.hset('test:hash', mapping={'field1': 'value1'}) + assert r.hexpire('test:hash', 2, 'field1', xx=True) == [0] + assert r.hexpire('test:hash', 2, 'field1', nx=True) == [1] + assert r.hexpire('test:hash', 1, 'field1', xx=True) == [1] + assert r.hexpire('test:hash', 2, 'field1', nx=True) == [0] + time.sleep(1.1) + assert r.hexists('test:hash', 'field1') is False + r.hset('test:hash', 'field1', 'value1') + r.hexpire('test:hash', 2, 'field1') + assert r.hexpire('test:hash', 1, 'field1', gt=True) == [0] + assert r.hexpire('test:hash', 1, 'field1', lt=True) == [1] + time.sleep(1.1) + assert r.hexists('test:hash', 'field1') is False + + +@skip_if_server_version_lt("7.4.0") +def test_hexpire_nonexistent_key_or_field(r): + r.delete('test:hash') + assert r.hexpire('test:hash', 1, 'field1') is None + r.hset('test:hash', 'field1', 'value1') + assert r.hexpire('test:hash', 1, 'nonexistent_field') == [-2] + + +@skip_if_server_version_lt("7.4.0") +def test_hexpire_multiple_fields(r): + r.delete('test:hash') + r.hset('test:hash', + mapping={'field1': 'value1', 'field2': 'value2', 'field3': 'value3'}) + assert r.hexpire('test:hash', 1, 'field1', 'field2') == [1, 1] + time.sleep(1.1) + assert r.hexists('test:hash', 'field1') is False + assert r.hexists('test:hash', 'field2') is False + assert r.hexists('test:hash', 'field3') is True + + +@skip_if_server_version_lt("7.4.0") +def test_hpexpire_basic(r): + r.delete('test:hash') + r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + assert r.hpexpire('test:hash', 500, 'field1') == [1] + time.sleep(0.6) + assert r.hexists('test:hash', 'field1') is False + assert r.hexists('test:hash', 'field2') is True + + +@skip_if_server_version_lt("7.4.0") +def test_hpexpire_with_timedelta(r): + r.delete('test:hash') + r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + assert r.hpexpire('test:hash', timedelta(milliseconds=500), 'field1') == [1] + time.sleep(0.6) + assert r.hexists('test:hash', 'field1') is False + assert r.hexists('test:hash', 'field2') is True + + +@skip_if_server_version_lt("7.4.0") +def test_hpexpire_conditions(r): + r.delete('test:hash') + r.hset('test:hash', mapping={'field1': 'value1'}) + assert r.hpexpire('test:hash', 1500, 'field1', xx=True) == [0] + assert r.hpexpire('test:hash', 1500, 'field1', nx=True) == [1] + assert r.hpexpire('test:hash', 500, 'field1', xx=True) == [1] + assert r.hpexpire('test:hash', 1500, 'field1', nx=True) == [0] + time.sleep(0.6) + assert r.hexists('test:hash', 'field1') is False + r.hset('test:hash', 'field1', 'value1') + r.hpexpire('test:hash', 1000, 'field1') + assert r.hpexpire('test:hash', 500, 'field1', gt=True) == [0] + assert r.hpexpire('test:hash', 500, 'field1', lt=True) == [1] + time.sleep(0.6) + assert r.hexists('test:hash', 'field1') is False + + +@skip_if_server_version_lt("7.4.0") +def test_hpexpire_nonexistent_key_or_field(r): + r.delete('test:hash') + assert r.hpexpire('test:hash', 500, 'field1') is None + r.hset('test:hash', 'field1', 'value1') + assert r.hpexpire('test:hash', 500, 'nonexistent_field') == [-2] + + +@skip_if_server_version_lt("7.4.0") +def test_hpexpire_multiple_fields(r): + r.delete('test:hash') + r.hset('test:hash', + mapping={'field1': 'value1', 'field2': 'value2', 'field3': 'value3'}) + assert r.hpexpire('test:hash', 500, 'field1', 'field2') == [1, 1] + time.sleep(0.6) + assert r.hexists('test:hash', 'field1') is False + assert r.hexists('test:hash', 'field2') is False + assert r.hexists('test:hash', 'field3') is True + + +@skip_if_server_version_lt("7.4.0") +def test_hexpireat_basic(r): + r.delete('test:hash') + r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + exp_time = int((datetime.now() + timedelta(seconds=1)).timestamp()) + assert r.hexpireat('test:hash', exp_time, 'field1') == [1] + time.sleep(1.1) + assert r.hexists('test:hash', 'field1') is False + assert r.hexists('test:hash', 'field2') is True + + +@skip_if_server_version_lt("7.4.0") +def test_hexpireat_with_datetime(r): + r.delete('test:hash') + r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + exp_time = datetime.now() + timedelta(seconds=1) + assert r.hexpireat('test:hash', exp_time, 'field1') == [1] + time.sleep(1.1) + assert r.hexists('test:hash', 'field1') is False + assert r.hexists('test:hash', 'field2') is True + + +@skip_if_server_version_lt("7.4.0") +def test_hexpireat_conditions(r): + r.delete('test:hash') + r.hset('test:hash', mapping={'field1': 'value1'}) + future_exp_time = int((datetime.now() + timedelta(seconds=2)).timestamp()) + past_exp_time = int((datetime.now() - timedelta(seconds=1)).timestamp()) + assert r.hexpireat('test:hash', future_exp_time, 'field1', xx=True) == [0] + assert r.hexpireat('test:hash', future_exp_time, 'field1', nx=True) == [1] + assert r.hexpireat('test:hash', past_exp_time, 'field1', gt=True) == [0] + assert r.hexpireat('test:hash', past_exp_time, 'field1', lt=True) == [2] + assert r.hexists('test:hash', 'field1') is False + + +@skip_if_server_version_lt("7.4.0") +def test_hexpireat_nonexistent_key_or_field(r): + r.delete('test:hash') + future_exp_time = int((datetime.now() + timedelta(seconds=1)).timestamp()) + assert r.hexpireat('test:hash', future_exp_time, 'field1') is None + r.hset('test:hash', 'field1', 'value1') + assert r.hexpireat('test:hash', future_exp_time, 'nonexistent_field') == [-2] + + +@skip_if_server_version_lt("7.4.0") +def test_hexpireat_multiple_fields(r): + r.delete('test:hash') + r.hset('test:hash', + mapping={'field1': 'value1', 'field2': 'value2', 'field3': 'value3'}) + exp_time = int((datetime.now() + timedelta(seconds=1)).timestamp()) + assert r.hexpireat('test:hash', exp_time, 'field1', 'field2') == [1, 1] + time.sleep(1.1) + assert r.hexists('test:hash', 'field1') is False + assert r.hexists('test:hash', 'field2') is False + assert r.hexists('test:hash', 'field3') is True + + +@skip_if_server_version_lt("7.4.0") +def test_hpexpireat_basic(r): + r.delete('test:hash') + r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + exp_time = int((datetime.now() + timedelta(milliseconds=400)).timestamp() * 1000) + assert r.hpexpireat('test:hash', exp_time, 'field1') == [1] + time.sleep(0.5) + assert r.hexists('test:hash', 'field1') is False + assert r.hexists('test:hash', 'field2') is True + + +@skip_if_server_version_lt("7.4.0") +def test_hpexpireat_with_datetime(r): + r.delete('test:hash') + r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + exp_time = datetime.now() + timedelta(milliseconds=400) + assert r.hpexpireat('test:hash', exp_time, 'field1') == [1] + time.sleep(0.5) + assert r.hexists('test:hash', 'field1') is False + assert r.hexists('test:hash', 'field2') is True + + +@skip_if_server_version_lt("7.4.0") +def test_hpexpireat_conditions(r): + r.delete('test:hash') + r.hset('test:hash', mapping={'field1': 'value1'}) + future_exp_time = int( + (datetime.now() + timedelta(milliseconds=500)).timestamp() * 1000) + past_exp_time = int( + (datetime.now() - timedelta(milliseconds=500)).timestamp() * 1000) + assert r.hpexpireat('test:hash', future_exp_time, 'field1', xx=True) == [0] + assert r.hpexpireat('test:hash', future_exp_time, 'field1', nx=True) == [1] + assert r.hpexpireat('test:hash', past_exp_time, 'field1', gt=True) == [0] + assert r.hpexpireat('test:hash', past_exp_time, 'field1', lt=True) == [2] + assert r.hexists('test:hash', 'field1') is False + + +@skip_if_server_version_lt("7.4.0") +def test_hpexpireat_nonexistent_key_or_field(r): + r.delete('test:hash') + future_exp_time = int( + (datetime.now() + timedelta(milliseconds=500)).timestamp() * 1000) + assert r.hpexpireat('test:hash', future_exp_time, 'field1') is None + r.hset('test:hash', 'field1', 'value1') + assert r.hpexpireat('test:hash', future_exp_time, 'nonexistent_field') == [-2] + + +@skip_if_server_version_lt("7.4.0") +def test_hpexpireat_multiple_fields(r): + r.delete('test:hash') + r.hset('test:hash', + mapping={'field1': 'value1', 'field2': 'value2', 'field3': 'value3'}) + exp_time = int((datetime.now() + timedelta(milliseconds=400)).timestamp() * 1000) + assert r.hpexpireat('test:hash', exp_time, 'field1', 'field2') == [1, 1] + time.sleep(0.5) + assert r.hexists('test:hash', 'field1') is False + assert r.hexists('test:hash', 'field2') is False + assert r.hexists('test:hash', 'field3') is True + + +@skip_if_server_version_lt("7.4.0") +def test_hpersist_multiple_fields_mixed_conditions(r): + r.delete('test:hash') + r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + r.hexpire('test:hash', 5000, 'field1') + assert r.hpersist('test:hash', 'field1', 'field2', 'field3') == [1, -1, -2] + + +@skip_if_server_version_lt("7.4.0") +def test_hexpiretime_multiple_fields_mixed_conditions(r): + r.delete('test:hash') + r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + future_time = int((datetime.now() + timedelta(minutes=30)).timestamp()) + r.hexpireat('test:hash', future_time, 'field1') + result = r.hexpiretime('test:hash', 'field1', 'field2', 'field3') + assert future_time - 10 < result[0] <= future_time + assert result[1:] == [-1, -2] + + +@skip_if_server_version_lt("7.4.0") +def test_hpexpiretime_multiple_fields_mixed_conditions(r): + r.delete('test:hash') + r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + future_time = int((datetime.now() + timedelta(minutes=30)).timestamp()) + r.hexpireat('test:hash', future_time, 'field1') + result = r.hpexpiretime('test:hash', 'field1', 'field2', 'field3') + assert future_time * 1000 - 10000 < result[0] <= future_time * 1000 + assert result[1:] == [-1, -2] + + +@skip_if_server_version_lt("7.4.0") +def test_ttl_multiple_fields_mixed_conditions(r): + r.delete('test:hash') + r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + future_time = int((datetime.now() + timedelta(minutes=30)).timestamp()) + r.hexpireat('test:hash', future_time, 'field1') + result = r.httl('test:hash', 'field1', 'field2', 'field3') + assert 30 * 60 - 10 < result[0] <= 30 * 60 + assert result[1:] == [-1, -2] + + +@skip_if_server_version_lt("7.4.0") +def test_pttl_multiple_fields_mixed_conditions(r): + r.delete('test:hash') + r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + future_time = int((datetime.now() + timedelta(minutes=30)).timestamp()) + r.hexpireat('test:hash', future_time, 'field1') + result = r.hpttl('test:hash', 'field1', 'field2', 'field3') + assert 30 * 60000 - 10000 < result[0] <= 30 * 60000 + assert result[1:] == [-1, -2] From 4ce03fb3e4f217d8239fb34f5b165e258a319c53 Mon Sep 17 00:00:00 2001 From: Gabriel Erzse Date: Tue, 14 May 2024 19:42:31 +0300 Subject: [PATCH 02/12] Remove doctests and switch to Google style docstrings --- pytest.ini | 2 +- redis/commands/core.py | 461 ++++++++++++++--------------------------- 2 files changed, 160 insertions(+), 303 deletions(-) diff --git a/pytest.ini b/pytest.ini index c574a2eddc..f1b716ae96 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,5 +1,5 @@ [pytest] -addopts = -s --doctest-modules +addopts = -s markers = redismod: run only the redis module tests pipeline: pipeline tests diff --git a/redis/commands/core.py b/redis/commands/core.py index c6647de5e7..20fedc9ea6 100644 --- a/redis/commands/core.py +++ b/redis/commands/core.py @@ -5116,51 +5116,34 @@ def hexpire( lt: bool = False, ) -> ResponseT: """ - Set or update the expiration time for fields within a hash key, using relative + 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. + controlled using the `nx`, `xx`, `gt`, and `lt` parameters. The return value provides detailed information about the outcome for each field. - :param name: The name of the hash key. - :param seconds: Expiration time in seconds, relative. Can be an integer, or a - Python `timedelta` object. - :param fields: List of fields within the hash to apply the expiration time to. - :param nx: Set expiry only when the field has no expiry. - :param xx: Set expiry only when the field has an existing expiry. - :param gt: Set expiry only when the new expiry is greater than current one. - :param lt: Set expiry only when the new expiry is less than current one. - :return: If the key does not exist, returns `None`. 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. - - Example: - >>> import redis - >>> import time - >>> - >>> r = redis.Redis(host='localhost', port=6379) - >>> _ = r.delete('vehicle:10') - >>> r.hset('vehicle:10', 'id', 'f3a64766-5dec-4e07-b5f8-3c0f8e1a5e4b') - 1 - >>> r.hset('vehicle:10', 'longitude', '-122.335167') - 1 - >>> r.hset('vehicle:10', 'latitude', '37.779284') - 1 - >>> r.hexpire('vehicle:10', 1, 'longitude', 'latitude', 'foobar') - [1, 1, -2] - >>> time.sleep(2) - >>> r.hexists('vehicle:10', 'longitude') - False - >>> r.hgetall('vehicle:10') - {b'id': b'f3a64766-5dec-4e07-b5f8-3c0f8e1a5e4b'} - - For more information see https://redis.io/commands/hexpire + 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 `None`. 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. """ if isinstance(seconds, datetime.timedelta): seconds = int(seconds.total_seconds()) @@ -5190,51 +5173,34 @@ def hpexpire( lt: bool = False, ) -> ResponseT: """ - Set or update the expiration time for fields within a hash key, using relative + 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. + controlled using the `nx`, `xx`, `gt`, and `lt` parameters. The return value provides detailed information about the outcome for each field. - :param name: The name of the hash key. - :param milliseconds: Expiration time in milliseconds, relative. Can be an - integer, or a Python `timedelta` object. - :param fields: List of fields within the hash to apply the expiration time to. - :param nx: Set expiry only when the field has no expiry. - :param xx: Set expiry only when the field has an existing expiry. - :param gt: Set expiry only when the new expiry is greater than current one. - :param lt: Set expiry only when the new expiry is less than current one. - :return: If the key does not exist, returns `None`. 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. - - Example: - >>> import redis - >>> import time - >>> - >>> r = redis.Redis(host='localhost', port=6379) - >>> _ = r.delete('sensor:456') - >>> r.hset('sensor:456', 'id', 'sensor_456') - 1 - >>> r.hset('sensor:456', 'temperature', '36.6') - 1 - >>> r.hset('sensor:456', 'alert', 'overheat') - 1 - >>> r.hpexpire('sensor:456', 800, 'temperature', 'alert') - [1, 1] - >>> time.sleep(0.9) - >>> r.hexists('sensor:456', 'alert') - False - >>> r.hgetall('sensor:456') - {b'id': b'sensor_456'} - - For more information see https://redis.io/commands/hpexpire + 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 `None`. 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. """ if isinstance(milliseconds, datetime.timedelta): milliseconds = int(milliseconds.total_seconds() * 1000) @@ -5264,52 +5230,34 @@ def hexpireat( lt: bool = False, ) -> ResponseT: """ - Set or update the expiration time for fields within a hash key, using an + 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. + controlled using the `nx`, `xx`, `gt`, and `lt` parameters. The return value provides detailed information about the outcome for each field. - :param name: The name of the hash key. - :param unix_time_seconds: Expiration time as Unix timestamp in milliseconds. Can - be an integer or a Python `datetime` object. - :param fields: List of fields within the hash to apply the expiration time to. - :param nx: Set expiry only when the field has no expiry. - :param xx: Set expiry only when the field has an existing expiration time. - :param gt: Set expiry only when the new expiry is greater than current one. - :param lt: Set expiry only when the new expiry is less than current one. - :return: If the key does not exist, returns `None`. 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. - - Example: - >>> import redis - >>> from datetime import datetime, timedelta - >>> - >>> r = redis.Redis(host='localhost', port=6379) - >>> _ = r.delete('vehicle:10') - >>> r.hset('vehicle:10', 'id', 'f3a64766-5dec-4e07-b5f8-3c0f8e1a5e4b') - 1 - >>> r.hset('vehicle:10', 'longitude', '-122.335167') - 1 - >>> r.hset('vehicle:10', 'latitude', '37.779284') - 1 - >>> exp_time = datetime.now() + timedelta(seconds=1) - >>> r.hexpireat('vehicle:10', exp_time, 'longitude', 'latitude', 'foobar') - [1, 1, -2] - >>> time.sleep(2) - >>> r.hexists('vehicle:10', 'longitude') - False - >>> r.hgetall('vehicle:10') - {b'id': b'f3a64766-5dec-4e07-b5f8-3c0f8e1a5e4b'} - - For more information see https://redis.io/commands/hexpireat + 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 `None`. 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. """ if isinstance(unix_time_seconds, datetime.datetime): unix_time_seconds = int(unix_time_seconds.timestamp()) @@ -5339,53 +5287,34 @@ def hpexpireat( lt: bool = False, ) -> ResponseT: """ - Set or update the expiration time for fields within a hash key, using an + 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. + controlled using the `nx`, `xx`, `gt`, and `lt` parameters. The return value provides detailed information about the outcome for each field. - :param name: The name of the hash key. - :param unix_time_milliseconds: Expiration time as Unix timestamp in - milliseconds. Can be an integer or a Python - `datetime` object. - :param fields: List of fields within the hash to apply the expiry. - :param nx: Set expiry only when the field has no expiry. - :param xx: Set expiry only when the field has an existing expiry. - :param gt: Set expiry only when the new expiry is greater than current one. - :param lt: Set expiry only when the new expiry is less than current one. - :return: If the key does not exist, returns `None`. 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. - - Examples: - >>> import redis - >>> from datetime import datetime, timedelta - >>> - >>> r = redis.Redis(host='localhost', port=6379) - >>> _ = r.delete('sensor:456') - >>> r.hset('sensor:456', 'id', 'sensor_456') - 1 - >>> r.hset('sensor:456', 'temperature', '36.6') - 1 - >>> r.hset('sensor:456', 'alert', 'overheat') - 1 - >>> exp_time = datetime.now() + timedelta(milliseconds=800) - >>> r.hpexpireat('sensor:456', exp_time, 'temperature', 'alert') - [1, 1] - >>> time.sleep(0.9) - >>> r.hexists('sensor:456', 'alert') - False - >>> r.hgetall('sensor:456') - {b'id': b'sensor_456'} - - For more information see https://redis.io/commands/hpexpireat + 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 `None`. 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. """ if isinstance(unix_time_milliseconds, datetime.datetime): unix_time_milliseconds = int(unix_time_milliseconds.timestamp() * 1000) @@ -5408,32 +5337,19 @@ def hpersist(self, name: KeyT, *fields: str) -> ResponseT: """ Removes the expiration time for each specified field in a hash. - :param name: The name of the hash key. - :param fields: A list of fields within the hash from which to remove the - expiration time from. - :return: If the key does not exist, returns `None`. 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. - - Example: - >>> import redis - >>> - >>> r = redis.Redis(host='localhost', port=6379) - >>> _ = r.delete('vehicle:10') - >>> r.hset('vehicle:10', 'id', 'f3a64766-5dec-4e07-b5f8-3c0f8e1a5e4b') - 1 - >>> r.hset('vehicle:10', 'longitude', '-122.335167') - 1 - >>> r.hset('vehicle:10', 'latitude', '37.779284') - 1 - >>> r.hexpire('vehicle:10', 5, 'longitude', 'latitude') - [1, 1] - >>> r.hpersist('vehicle:10', 'id', 'longitude', 'foobar') - [-1, 1, -2] - - For more information see https://redis.io/commands/hpersist + 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 `None`. 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, len(fields), *fields) @@ -5441,35 +5357,21 @@ def hexpiretime(self, key: str, *fields: str) -> ResponseT: """ Returns the expiration times of hash fields as Unix timestamps in seconds. - :param key: The hash key. - :param fields: A list of fields within the hash for which to get the expiration - time. - :return: `None` if the key does not exist. Otherwise, a list containing the - result for each field, in the same order as requested. The result for - each field can be: - - `-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. - - Examples: - >>> import redis - >>> from datetime import datetime, timedelta - >>> - >>> r = redis.Redis(host='localhost', port=6379) - >>> _ = r.delete('vehicle:10') - >>> r.hset('vehicle:10', 'id', 'f3a64766-5dec-4e07-b5f8-3c0f8e1a5e4b') - 1 - >>> r.hset('vehicle:10', 'longitude', '-122.335167') - 1 - >>> r.hset('vehicle:10', 'latitude', '37.779284') - 1 - >>> r.hexpireat('vehicle:10', 32503672800, 'longitude', 'latitude') - [1, 1] - >>> r.hexpiretime('vehicle:10', 'id', 'longitude', 'foobar') - [-1, 32503672800, -2] - - For more information see https://redis.io/commands/hexpiretime + 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: + `None` if the key does not exist. Otherwise, a list containing the result + for each field, in the same order as requested. The result for each field + can be: + - `-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, len(fields), *fields) @@ -5477,35 +5379,21 @@ def hpexpiretime(self, key: str, *fields: str) -> ResponseT: """ Returns the expiration times of hash fields as Unix timestamps in milliseconds. - :param key: The hash key. - :param fields: A list of fields within the hash for which to get the expiration - time. - :return: `None` if the key does not exist. Otherwise, a list containing the - result for each field, in the same order as requested. The result for - each field can be: - - `-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. - - Examples: - >>> import redis - >>> from datetime import datetime, timedelta - >>> - >>> r = redis.Redis(host='localhost', port=6379) - >>> _ = r.delete('vehicle:10') - >>> r.hset('vehicle:10', 'id', 'f3a64766-5dec-4e07-b5f8-3c0f8e1a5e4b') - 1 - >>> r.hset('vehicle:10', 'longitude', '-122.335167') - 1 - >>> r.hset('vehicle:10', 'latitude', '37.779284') - 1 - >>> r.hexpireat('vehicle:10', 32503672800, 'longitude', 'latitude') - [1, 1] - >>> r.hpexpiretime('vehicle:10', 'id', 'longitude', 'foobar') - [-1, 32503672800000, -2] - - For more information see https://redis.io/commands/hpexpiretime + 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: + `None` if the key does not exist. Otherwise, a list containing the result + for each field, in the same order as requested. The result for each field + can be: + - `-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, len(fields), *fields) @@ -5514,35 +5402,20 @@ def httl(self, key: str, *fields: str) -> ResponseT: Returns the TTL (Time To Live) in seconds for each specified field within a hash key. - :param key: The hash key. - :param fields: A list of fields within the hash for which to get the TTL. - :return: `None` if the key does not exist. Otherwise, a list containing the - result for each field, in the same order as requested. The result for - each field can be: - - `-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. - - Examples: - >>> import redis - >>> - >>> r = redis.Redis(host='localhost', port=6379) - >>> _ = r.delete('vehicle:10') - >>> r.hset('vehicle:10', 'id', 'f3a64766-5dec-4e07-b5f8-3c0f8e1a5e4b') - 1 - >>> r.hset('vehicle:10', 'longitude', '-122.335167') - 1 - >>> r.hset('vehicle:10', 'latitude', '37.779284') - 1 - >>> r.hexpire('vehicle:10', 5, 'longitude', 'latitude') - [1, 1] - >>> # Note: we can't know precisely the returned value for 'longitude', - >>> # because time passes between the two commands. It can be 5, or 4. - >>> r.httl('vehicle:10', 'id', 'longitude', 'foobar') # doctest: +ELLIPSIS - [-1, ..., -2] - - For more information see https://redis.io/commands/httl + 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: + `None` if the key does not exist. Otherwise, a list containing the result + for each field, in the same order as requested. The result for each field + can be: + - `-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, len(fields), *fields) @@ -5551,36 +5424,20 @@ def hpttl(self, key: str, *fields: str) -> ResponseT: Returns the TTL (Time To Live) in milliseconds for each specified field within a hash key. - :param key: The hash key. - :param fields: A list of fields within the hash for which to get the TTL. - :return: `None` if the key does not exist. Otherwise, a list containing the - result for each field, in the same order as requested. The result for - each field can be: - - `-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. - - Examples: - >>> import redis - >>> - >>> r = redis.Redis(host='localhost', port=6379) - >>> _ = r.delete('vehicle:10') - >>> r.hset('vehicle:10', 'id', 'f3a64766-5dec-4e07-b5f8-3c0f8e1a5e4b') - 1 - >>> r.hset('vehicle:10', 'longitude', '-122.335167') - 1 - >>> r.hset('vehicle:10', 'latitude', '37.779284') - 1 - >>> r.hexpire('vehicle:10', 5, 'longitude', 'latitude') - [1, 1] - >>> # Note: we can't know precisely the returned value for 'longitude' - >>> # because time passes between the two commands. - >>> # It can be 5000, or a bit less. - >>> r.hpttl('vehicle:10', 'id', 'longitude', 'foobar') # doctest: +ELLIPSIS - [-1, ..., -2] - - For more information see https://redis.io/commands/hpttl + 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: + None or list: `None` if the key does not exist. Otherwise, a list containing + the result for each field, in the same order as requested. The result for + each field can be: + - `-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, len(fields), *fields) From b4cd85acd7c6b481634ceb9d0191bdd79e5bb703 Mon Sep 17 00:00:00 2001 From: Gabriel Erzse Date: Tue, 14 May 2024 19:46:04 +0300 Subject: [PATCH 03/12] Fix linter errors --- tests/test_asyncio/test_hash.py | 303 +++++++++++++++++--------------- tests/test_hash.py | 303 +++++++++++++++++--------------- 2 files changed, 314 insertions(+), 292 deletions(-) diff --git a/tests/test_asyncio/test_hash.py b/tests/test_asyncio/test_hash.py index 986cf513dc..c6dae2651b 100644 --- a/tests/test_asyncio/test_hash.py +++ b/tests/test_asyncio/test_hash.py @@ -1,289 +1,300 @@ import asyncio -from datetime import timedelta, datetime +from datetime import datetime, timedelta from tests.conftest import skip_if_server_version_lt @skip_if_server_version_lt("7.4.0") async def test_hexpire_basic(r): - await r.delete('test:hash') - await r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) - assert await r.hexpire('test:hash', 1, 'field1') == [1] + await r.delete("test:hash") + await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"}) + assert await r.hexpire("test:hash", 1, "field1") == [1] await asyncio.sleep(1.1) - assert await r.hexists('test:hash', 'field1') is False - assert await r.hexists('test:hash', 'field2') is True + assert await r.hexists("test:hash", "field1") is False + assert await r.hexists("test:hash", "field2") is True @skip_if_server_version_lt("7.4.0") async def test_hexpire_with_timedelta(r): - await r.delete('test:hash') - await r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) - assert await r.hexpire('test:hash', timedelta(seconds=1), 'field1') == [1] + await r.delete("test:hash") + await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"}) + assert await r.hexpire("test:hash", timedelta(seconds=1), "field1") == [1] await asyncio.sleep(1.1) - assert await r.hexists('test:hash', 'field1') is False - assert await r.hexists('test:hash', 'field2') is True + assert await r.hexists("test:hash", "field1") is False + assert await r.hexists("test:hash", "field2") is True @skip_if_server_version_lt("7.4.0") async def test_hexpire_conditions(r): - await r.delete('test:hash') - await r.hset('test:hash', mapping={'field1': 'value1'}) - assert await r.hexpire('test:hash', 2, 'field1', xx=True) == [0] - assert await r.hexpire('test:hash', 2, 'field1', nx=True) == [1] - assert await r.hexpire('test:hash', 1, 'field1', xx=True) == [1] - assert await r.hexpire('test:hash', 2, 'field1', nx=True) == [0] + await r.delete("test:hash") + await r.hset("test:hash", mapping={"field1": "value1"}) + assert await r.hexpire("test:hash", 2, "field1", xx=True) == [0] + assert await r.hexpire("test:hash", 2, "field1", nx=True) == [1] + assert await r.hexpire("test:hash", 1, "field1", xx=True) == [1] + assert await r.hexpire("test:hash", 2, "field1", nx=True) == [0] await asyncio.sleep(1.1) - assert await r.hexists('test:hash', 'field1') is False - await r.hset('test:hash', 'field1', 'value1') - await r.hexpire('test:hash', 2, 'field1') - assert await r.hexpire('test:hash', 1, 'field1', gt=True) == [0] - assert await r.hexpire('test:hash', 1, 'field1', lt=True) == [1] + assert await r.hexists("test:hash", "field1") is False + await r.hset("test:hash", "field1", "value1") + await r.hexpire("test:hash", 2, "field1") + assert await r.hexpire("test:hash", 1, "field1", gt=True) == [0] + assert await r.hexpire("test:hash", 1, "field1", lt=True) == [1] await asyncio.sleep(1.1) - assert await r.hexists('test:hash', 'field1') is False + assert await r.hexists("test:hash", "field1") is False @skip_if_server_version_lt("7.4.0") async def test_hexpire_nonexistent_key_or_field(r): - await r.delete('test:hash') - assert await r.hexpire('test:hash', 1, 'field1') is None - await r.hset('test:hash', 'field1', 'value1') - assert await r.hexpire('test:hash', 1, 'nonexistent_field') == [-2] + await r.delete("test:hash") + assert await r.hexpire("test:hash", 1, "field1") is None + await r.hset("test:hash", "field1", "value1") + assert await r.hexpire("test:hash", 1, "nonexistent_field") == [-2] @skip_if_server_version_lt("7.4.0") async def test_hexpire_multiple_fields(r): - await r.delete('test:hash') - await r.hset('test:hash', - mapping={'field1': 'value1', 'field2': 'value2', 'field3': 'value3'}) - assert await r.hexpire('test:hash', 1, 'field1', 'field2') == [1, 1] + await r.delete("test:hash") + await r.hset( + "test:hash", + mapping={"field1": "value1", "field2": "value2", "field3": "value3"}, + ) + assert await r.hexpire("test:hash", 1, "field1", "field2") == [1, 1] await asyncio.sleep(1.1) - assert await r.hexists('test:hash', 'field1') is False - assert await r.hexists('test:hash', 'field2') is False - assert await r.hexists('test:hash', 'field3') is True + assert await r.hexists("test:hash", "field1") is False + assert await r.hexists("test:hash", "field2") is False + assert await r.hexists("test:hash", "field3") is True @skip_if_server_version_lt("7.4.0") async def test_hpexpire_basic(r): - await r.delete('test:hash') - await r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) - assert await r.hpexpire('test:hash', 500, 'field1') == [1] + await r.delete("test:hash") + await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"}) + assert await r.hpexpire("test:hash", 500, "field1") == [1] await asyncio.sleep(0.6) - assert await r.hexists('test:hash', 'field1') is False - assert await r.hexists('test:hash', 'field2') is True + assert await r.hexists("test:hash", "field1") is False + assert await r.hexists("test:hash", "field2") is True @skip_if_server_version_lt("7.4.0") async def test_hpexpire_with_timedelta(r): - await r.delete('test:hash') - await r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) - assert await r.hpexpire('test:hash', timedelta(milliseconds=500), 'field1') == [1] + await r.delete("test:hash") + await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"}) + assert await r.hpexpire("test:hash", timedelta(milliseconds=500), "field1") == [1] await asyncio.sleep(0.6) - assert await r.hexists('test:hash', 'field1') is False - assert await r.hexists('test:hash', 'field2') is True + assert await r.hexists("test:hash", "field1") is False + assert await r.hexists("test:hash", "field2") is True @skip_if_server_version_lt("7.4.0") async def test_hpexpire_conditions(r): - await r.delete('test:hash') - await r.hset('test:hash', mapping={'field1': 'value1'}) - assert await r.hpexpire('test:hash', 1500, 'field1', xx=True) == [0] - assert await r.hpexpire('test:hash', 1500, 'field1', nx=True) == [1] - assert await r.hpexpire('test:hash', 500, 'field1', xx=True) == [1] - assert await r.hpexpire('test:hash', 1500, 'field1', nx=True) == [0] + await r.delete("test:hash") + await r.hset("test:hash", mapping={"field1": "value1"}) + assert await r.hpexpire("test:hash", 1500, "field1", xx=True) == [0] + assert await r.hpexpire("test:hash", 1500, "field1", nx=True) == [1] + assert await r.hpexpire("test:hash", 500, "field1", xx=True) == [1] + assert await r.hpexpire("test:hash", 1500, "field1", nx=True) == [0] await asyncio.sleep(0.6) - assert await r.hexists('test:hash', 'field1') is False - await r.hset('test:hash', 'field1', 'value1') - await r.hpexpire('test:hash', 1000, 'field1') - assert await r.hpexpire('test:hash', 500, 'field1', gt=True) == [0] - assert await r.hpexpire('test:hash', 500, 'field1', lt=True) == [1] + assert await r.hexists("test:hash", "field1") is False + await r.hset("test:hash", "field1", "value1") + await r.hpexpire("test:hash", 1000, "field1") + assert await r.hpexpire("test:hash", 500, "field1", gt=True) == [0] + assert await r.hpexpire("test:hash", 500, "field1", lt=True) == [1] await asyncio.sleep(0.6) - assert await r.hexists('test:hash', 'field1') is False + assert await r.hexists("test:hash", "field1") is False @skip_if_server_version_lt("7.4.0") async def test_hpexpire_nonexistent_key_or_field(r): - await r.delete('test:hash') - assert await r.hpexpire('test:hash', 500, 'field1') is None - await r.hset('test:hash', 'field1', 'value1') - assert await r.hpexpire('test:hash', 500, 'nonexistent_field') == [-2] + await r.delete("test:hash") + assert await r.hpexpire("test:hash", 500, "field1") is None + await r.hset("test:hash", "field1", "value1") + assert await r.hpexpire("test:hash", 500, "nonexistent_field") == [-2] @skip_if_server_version_lt("7.4.0") async def test_hpexpire_multiple_fields(r): - await r.delete('test:hash') - await r.hset('test:hash', - mapping={'field1': 'value1', 'field2': 'value2', 'field3': 'value3'}) - assert await r.hpexpire('test:hash', 500, 'field1', 'field2') == [1, 1] + await r.delete("test:hash") + await r.hset( + "test:hash", + mapping={"field1": "value1", "field2": "value2", "field3": "value3"}, + ) + assert await r.hpexpire("test:hash", 500, "field1", "field2") == [1, 1] await asyncio.sleep(0.6) - assert await r.hexists('test:hash', 'field1') is False - assert await r.hexists('test:hash', 'field2') is False - assert await r.hexists('test:hash', 'field3') is True + assert await r.hexists("test:hash", "field1") is False + assert await r.hexists("test:hash", "field2") is False + assert await r.hexists("test:hash", "field3") is True @skip_if_server_version_lt("7.4.0") async def test_hexpireat_basic(r): - await r.delete('test:hash') - await r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + await r.delete("test:hash") + await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"}) exp_time = int((datetime.now() + timedelta(seconds=1)).timestamp()) - assert await r.hexpireat('test:hash', exp_time, 'field1') == [1] + assert await r.hexpireat("test:hash", exp_time, "field1") == [1] await asyncio.sleep(1.1) - assert await r.hexists('test:hash', 'field1') is False - assert await r.hexists('test:hash', 'field2') is True + assert await r.hexists("test:hash", "field1") is False + assert await r.hexists("test:hash", "field2") is True @skip_if_server_version_lt("7.4.0") async def test_hexpireat_with_datetime(r): - await r.delete('test:hash') - await r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + await r.delete("test:hash") + await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"}) exp_time = datetime.now() + timedelta(seconds=1) - assert await r.hexpireat('test:hash', exp_time, 'field1') == [1] + assert await r.hexpireat("test:hash", exp_time, "field1") == [1] await asyncio.sleep(1.1) - assert await r.hexists('test:hash', 'field1') is False - assert await r.hexists('test:hash', 'field2') is True + assert await r.hexists("test:hash", "field1") is False + assert await r.hexists("test:hash", "field2") is True @skip_if_server_version_lt("7.4.0") async def test_hexpireat_conditions(r): - await r.delete('test:hash') - await r.hset('test:hash', mapping={'field1': 'value1'}) + await r.delete("test:hash") + await r.hset("test:hash", mapping={"field1": "value1"}) future_exp_time = int((datetime.now() + timedelta(seconds=2)).timestamp()) past_exp_time = int((datetime.now() - timedelta(seconds=1)).timestamp()) - assert await r.hexpireat('test:hash', future_exp_time, 'field1', xx=True) == [0] - assert await r.hexpireat('test:hash', future_exp_time, 'field1', nx=True) == [1] - assert await r.hexpireat('test:hash', past_exp_time, 'field1', gt=True) == [0] - assert await r.hexpireat('test:hash', past_exp_time, 'field1', lt=True) == [2] - assert await r.hexists('test:hash', 'field1') is False + assert await r.hexpireat("test:hash", future_exp_time, "field1", xx=True) == [0] + assert await r.hexpireat("test:hash", future_exp_time, "field1", nx=True) == [1] + assert await r.hexpireat("test:hash", past_exp_time, "field1", gt=True) == [0] + assert await r.hexpireat("test:hash", past_exp_time, "field1", lt=True) == [2] + assert await r.hexists("test:hash", "field1") is False @skip_if_server_version_lt("7.4.0") async def test_hexpireat_nonexistent_key_or_field(r): - await r.delete('test:hash') + await r.delete("test:hash") future_exp_time = int((datetime.now() + timedelta(seconds=1)).timestamp()) - assert await r.hexpireat('test:hash', future_exp_time, 'field1') is None - await r.hset('test:hash', 'field1', 'value1') - assert await r.hexpireat('test:hash', future_exp_time, 'nonexistent_field') == [-2] + assert await r.hexpireat("test:hash", future_exp_time, "field1") is None + await r.hset("test:hash", "field1", "value1") + assert await r.hexpireat("test:hash", future_exp_time, "nonexistent_field") == [-2] @skip_if_server_version_lt("7.4.0") async def test_hexpireat_multiple_fields(r): - await r.delete('test:hash') - await r.hset('test:hash', - mapping={'field1': 'value1', 'field2': 'value2', 'field3': 'value3'}) + await r.delete("test:hash") + await r.hset( + "test:hash", + mapping={"field1": "value1", "field2": "value2", "field3": "value3"}, + ) exp_time = int((datetime.now() + timedelta(seconds=1)).timestamp()) - assert await r.hexpireat('test:hash', exp_time, 'field1', 'field2') == [1, 1] + assert await r.hexpireat("test:hash", exp_time, "field1", "field2") == [1, 1] await asyncio.sleep(1.1) - assert await r.hexists('test:hash', 'field1') is False - assert await r.hexists('test:hash', 'field2') is False - assert await r.hexists('test:hash', 'field3') is True + assert await r.hexists("test:hash", "field1") is False + assert await r.hexists("test:hash", "field2") is False + assert await r.hexists("test:hash", "field3") is True @skip_if_server_version_lt("7.4.0") async def test_hpexpireat_basic(r): - await r.delete('test:hash') - await r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + await r.delete("test:hash") + await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"}) exp_time = int((datetime.now() + timedelta(milliseconds=400)).timestamp() * 1000) - assert await r.hpexpireat('test:hash', exp_time, 'field1') == [1] + assert await r.hpexpireat("test:hash", exp_time, "field1") == [1] await asyncio.sleep(0.5) - assert await r.hexists('test:hash', 'field1') is False - assert await r.hexists('test:hash', 'field2') is True + assert await r.hexists("test:hash", "field1") is False + assert await r.hexists("test:hash", "field2") is True @skip_if_server_version_lt("7.4.0") async def test_hpexpireat_with_datetime(r): - await r.delete('test:hash') - await r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + await r.delete("test:hash") + await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"}) exp_time = datetime.now() + timedelta(milliseconds=400) - assert await r.hpexpireat('test:hash', exp_time, 'field1') == [1] + assert await r.hpexpireat("test:hash", exp_time, "field1") == [1] await asyncio.sleep(0.5) - assert await r.hexists('test:hash', 'field1') is False - assert await r.hexists('test:hash', 'field2') is True + assert await r.hexists("test:hash", "field1") is False + assert await r.hexists("test:hash", "field2") is True @skip_if_server_version_lt("7.4.0") async def test_hpexpireat_conditions(r): - await r.delete('test:hash') - await r.hset('test:hash', mapping={'field1': 'value1'}) + await r.delete("test:hash") + await r.hset("test:hash", mapping={"field1": "value1"}) future_exp_time = int( - (datetime.now() + timedelta(milliseconds=500)).timestamp() * 1000) + (datetime.now() + timedelta(milliseconds=500)).timestamp() * 1000 + ) past_exp_time = int( - (datetime.now() - timedelta(milliseconds=500)).timestamp() * 1000) - assert await r.hpexpireat('test:hash', future_exp_time, 'field1', xx=True) == [0] - assert await r.hpexpireat('test:hash', future_exp_time, 'field1', nx=True) == [1] - assert await r.hpexpireat('test:hash', past_exp_time, 'field1', gt=True) == [0] - assert await r.hpexpireat('test:hash', past_exp_time, 'field1', lt=True) == [2] - assert await r.hexists('test:hash', 'field1') is False + (datetime.now() - timedelta(milliseconds=500)).timestamp() * 1000 + ) + assert await r.hpexpireat("test:hash", future_exp_time, "field1", xx=True) == [0] + assert await r.hpexpireat("test:hash", future_exp_time, "field1", nx=True) == [1] + assert await r.hpexpireat("test:hash", past_exp_time, "field1", gt=True) == [0] + assert await r.hpexpireat("test:hash", past_exp_time, "field1", lt=True) == [2] + assert await r.hexists("test:hash", "field1") is False @skip_if_server_version_lt("7.4.0") async def test_hpexpireat_nonexistent_key_or_field(r): - await r.delete('test:hash') + await r.delete("test:hash") future_exp_time = int( - (datetime.now() + timedelta(milliseconds=500)).timestamp() * 1000) - assert await r.hpexpireat('test:hash', future_exp_time, 'field1') is None - await r.hset('test:hash', 'field1', 'value1') - assert await r.hpexpireat('test:hash', future_exp_time, 'nonexistent_field') == [-2] + (datetime.now() + timedelta(milliseconds=500)).timestamp() * 1000 + ) + assert await r.hpexpireat("test:hash", future_exp_time, "field1") is None + await r.hset("test:hash", "field1", "value1") + assert await r.hpexpireat("test:hash", future_exp_time, "nonexistent_field") == [-2] @skip_if_server_version_lt("7.4.0") async def test_hpexpireat_multiple_fields(r): - await r.delete('test:hash') - await r.hset('test:hash', - mapping={'field1': 'value1', 'field2': 'value2', 'field3': 'value3'}) + await r.delete("test:hash") + await r.hset( + "test:hash", + mapping={"field1": "value1", "field2": "value2", "field3": "value3"}, + ) exp_time = int((datetime.now() + timedelta(milliseconds=400)).timestamp() * 1000) - assert await r.hpexpireat('test:hash', exp_time, 'field1', 'field2') == [1, 1] + assert await r.hpexpireat("test:hash", exp_time, "field1", "field2") == [1, 1] await asyncio.sleep(0.5) - assert await r.hexists('test:hash', 'field1') is False - assert await r.hexists('test:hash', 'field2') is False - assert await r.hexists('test:hash', 'field3') is True + assert await r.hexists("test:hash", "field1") is False + assert await r.hexists("test:hash", "field2") is False + assert await r.hexists("test:hash", "field3") is True @skip_if_server_version_lt("7.4.0") async def test_hpersist_multiple_fields_mixed_conditions(r): - await r.delete('test:hash') - await r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) - await r.hexpire('test:hash', 5000, 'field1') - assert await r.hpersist('test:hash', 'field1', 'field2', 'field3') == [1, -1, -2] + await r.delete("test:hash") + await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"}) + await r.hexpire("test:hash", 5000, "field1") + assert await r.hpersist("test:hash", "field1", "field2", "field3") == [1, -1, -2] @skip_if_server_version_lt("7.4.0") async def test_hexpiretime_multiple_fields_mixed_conditions(r): - await r.delete('test:hash') - await r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + await r.delete("test:hash") + await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"}) future_time = int((datetime.now() + timedelta(minutes=30)).timestamp()) - await r.hexpireat('test:hash', future_time, 'field1') - result = await r.hexpiretime('test:hash', 'field1', 'field2', 'field3') + await r.hexpireat("test:hash", future_time, "field1") + result = await r.hexpiretime("test:hash", "field1", "field2", "field3") assert future_time - 10 < result[0] <= future_time assert result[1:] == [-1, -2] @skip_if_server_version_lt("7.4.0") async def test_hpexpiretime_multiple_fields_mixed_conditions(r): - await r.delete('test:hash') - await r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + await r.delete("test:hash") + await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"}) future_time = int((datetime.now() + timedelta(minutes=30)).timestamp()) - await r.hexpireat('test:hash', future_time, 'field1') - result = await r.hpexpiretime('test:hash', 'field1', 'field2', 'field3') + await r.hexpireat("test:hash", future_time, "field1") + result = await r.hpexpiretime("test:hash", "field1", "field2", "field3") assert future_time * 1000 - 10000 < result[0] <= future_time * 1000 assert result[1:] == [-1, -2] @skip_if_server_version_lt("7.4.0") async def test_ttl_multiple_fields_mixed_conditions(r): - await r.delete('test:hash') - await r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + await r.delete("test:hash") + await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"}) future_time = int((datetime.now() + timedelta(minutes=30)).timestamp()) - await r.hexpireat('test:hash', future_time, 'field1') - result = await r.httl('test:hash', 'field1', 'field2', 'field3') + await r.hexpireat("test:hash", future_time, "field1") + result = await r.httl("test:hash", "field1", "field2", "field3") assert 30 * 60 - 10 < result[0] <= 30 * 60 assert result[1:] == [-1, -2] @skip_if_server_version_lt("7.4.0") async def test_pttl_multiple_fields_mixed_conditions(r): - await r.delete('test:hash') - await r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + await r.delete("test:hash") + await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"}) future_time = int((datetime.now() + timedelta(minutes=30)).timestamp()) - await r.hexpireat('test:hash', future_time, 'field1') - result = await r.hpttl('test:hash', 'field1', 'field2', 'field3') + await r.hexpireat("test:hash", future_time, "field1") + result = await r.hpttl("test:hash", "field1", "field2", "field3") assert 30 * 60000 - 10000 < result[0] <= 30 * 60000 assert result[1:] == [-1, -2] diff --git a/tests/test_hash.py b/tests/test_hash.py index 2927946c13..6c95d3be35 100644 --- a/tests/test_hash.py +++ b/tests/test_hash.py @@ -1,289 +1,300 @@ import time -from datetime import timedelta, datetime +from datetime import datetime, timedelta from tests.conftest import skip_if_server_version_lt @skip_if_server_version_lt("7.4.0") def test_hexpire_basic(r): - r.delete('test:hash') - r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) - assert r.hexpire('test:hash', 1, 'field1') == [1] + r.delete("test:hash") + r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"}) + assert r.hexpire("test:hash", 1, "field1") == [1] time.sleep(1.1) - assert r.hexists('test:hash', 'field1') is False - assert r.hexists('test:hash', 'field2') is True + assert r.hexists("test:hash", "field1") is False + assert r.hexists("test:hash", "field2") is True @skip_if_server_version_lt("7.4.0") def test_hexpire_with_timedelta(r): - r.delete('test:hash') - r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) - assert r.hexpire('test:hash', timedelta(seconds=1), 'field1') == [1] + r.delete("test:hash") + r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"}) + assert r.hexpire("test:hash", timedelta(seconds=1), "field1") == [1] time.sleep(1.1) - assert r.hexists('test:hash', 'field1') is False - assert r.hexists('test:hash', 'field2') is True + assert r.hexists("test:hash", "field1") is False + assert r.hexists("test:hash", "field2") is True @skip_if_server_version_lt("7.4.0") def test_hexpire_conditions(r): - r.delete('test:hash') - r.hset('test:hash', mapping={'field1': 'value1'}) - assert r.hexpire('test:hash', 2, 'field1', xx=True) == [0] - assert r.hexpire('test:hash', 2, 'field1', nx=True) == [1] - assert r.hexpire('test:hash', 1, 'field1', xx=True) == [1] - assert r.hexpire('test:hash', 2, 'field1', nx=True) == [0] + r.delete("test:hash") + r.hset("test:hash", mapping={"field1": "value1"}) + assert r.hexpire("test:hash", 2, "field1", xx=True) == [0] + assert r.hexpire("test:hash", 2, "field1", nx=True) == [1] + assert r.hexpire("test:hash", 1, "field1", xx=True) == [1] + assert r.hexpire("test:hash", 2, "field1", nx=True) == [0] time.sleep(1.1) - assert r.hexists('test:hash', 'field1') is False - r.hset('test:hash', 'field1', 'value1') - r.hexpire('test:hash', 2, 'field1') - assert r.hexpire('test:hash', 1, 'field1', gt=True) == [0] - assert r.hexpire('test:hash', 1, 'field1', lt=True) == [1] + assert r.hexists("test:hash", "field1") is False + r.hset("test:hash", "field1", "value1") + r.hexpire("test:hash", 2, "field1") + assert r.hexpire("test:hash", 1, "field1", gt=True) == [0] + assert r.hexpire("test:hash", 1, "field1", lt=True) == [1] time.sleep(1.1) - assert r.hexists('test:hash', 'field1') is False + assert r.hexists("test:hash", "field1") is False @skip_if_server_version_lt("7.4.0") def test_hexpire_nonexistent_key_or_field(r): - r.delete('test:hash') - assert r.hexpire('test:hash', 1, 'field1') is None - r.hset('test:hash', 'field1', 'value1') - assert r.hexpire('test:hash', 1, 'nonexistent_field') == [-2] + r.delete("test:hash") + assert r.hexpire("test:hash", 1, "field1") is None + r.hset("test:hash", "field1", "value1") + assert r.hexpire("test:hash", 1, "nonexistent_field") == [-2] @skip_if_server_version_lt("7.4.0") def test_hexpire_multiple_fields(r): - r.delete('test:hash') - r.hset('test:hash', - mapping={'field1': 'value1', 'field2': 'value2', 'field3': 'value3'}) - assert r.hexpire('test:hash', 1, 'field1', 'field2') == [1, 1] + r.delete("test:hash") + r.hset( + "test:hash", + mapping={"field1": "value1", "field2": "value2", "field3": "value3"}, + ) + assert r.hexpire("test:hash", 1, "field1", "field2") == [1, 1] time.sleep(1.1) - assert r.hexists('test:hash', 'field1') is False - assert r.hexists('test:hash', 'field2') is False - assert r.hexists('test:hash', 'field3') is True + assert r.hexists("test:hash", "field1") is False + assert r.hexists("test:hash", "field2") is False + assert r.hexists("test:hash", "field3") is True @skip_if_server_version_lt("7.4.0") def test_hpexpire_basic(r): - r.delete('test:hash') - r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) - assert r.hpexpire('test:hash', 500, 'field1') == [1] + r.delete("test:hash") + r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"}) + assert r.hpexpire("test:hash", 500, "field1") == [1] time.sleep(0.6) - assert r.hexists('test:hash', 'field1') is False - assert r.hexists('test:hash', 'field2') is True + assert r.hexists("test:hash", "field1") is False + assert r.hexists("test:hash", "field2") is True @skip_if_server_version_lt("7.4.0") def test_hpexpire_with_timedelta(r): - r.delete('test:hash') - r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) - assert r.hpexpire('test:hash', timedelta(milliseconds=500), 'field1') == [1] + r.delete("test:hash") + r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"}) + assert r.hpexpire("test:hash", timedelta(milliseconds=500), "field1") == [1] time.sleep(0.6) - assert r.hexists('test:hash', 'field1') is False - assert r.hexists('test:hash', 'field2') is True + assert r.hexists("test:hash", "field1") is False + assert r.hexists("test:hash", "field2") is True @skip_if_server_version_lt("7.4.0") def test_hpexpire_conditions(r): - r.delete('test:hash') - r.hset('test:hash', mapping={'field1': 'value1'}) - assert r.hpexpire('test:hash', 1500, 'field1', xx=True) == [0] - assert r.hpexpire('test:hash', 1500, 'field1', nx=True) == [1] - assert r.hpexpire('test:hash', 500, 'field1', xx=True) == [1] - assert r.hpexpire('test:hash', 1500, 'field1', nx=True) == [0] + r.delete("test:hash") + r.hset("test:hash", mapping={"field1": "value1"}) + assert r.hpexpire("test:hash", 1500, "field1", xx=True) == [0] + assert r.hpexpire("test:hash", 1500, "field1", nx=True) == [1] + assert r.hpexpire("test:hash", 500, "field1", xx=True) == [1] + assert r.hpexpire("test:hash", 1500, "field1", nx=True) == [0] time.sleep(0.6) - assert r.hexists('test:hash', 'field1') is False - r.hset('test:hash', 'field1', 'value1') - r.hpexpire('test:hash', 1000, 'field1') - assert r.hpexpire('test:hash', 500, 'field1', gt=True) == [0] - assert r.hpexpire('test:hash', 500, 'field1', lt=True) == [1] + assert r.hexists("test:hash", "field1") is False + r.hset("test:hash", "field1", "value1") + r.hpexpire("test:hash", 1000, "field1") + assert r.hpexpire("test:hash", 500, "field1", gt=True) == [0] + assert r.hpexpire("test:hash", 500, "field1", lt=True) == [1] time.sleep(0.6) - assert r.hexists('test:hash', 'field1') is False + assert r.hexists("test:hash", "field1") is False @skip_if_server_version_lt("7.4.0") def test_hpexpire_nonexistent_key_or_field(r): - r.delete('test:hash') - assert r.hpexpire('test:hash', 500, 'field1') is None - r.hset('test:hash', 'field1', 'value1') - assert r.hpexpire('test:hash', 500, 'nonexistent_field') == [-2] + r.delete("test:hash") + assert r.hpexpire("test:hash", 500, "field1") is None + r.hset("test:hash", "field1", "value1") + assert r.hpexpire("test:hash", 500, "nonexistent_field") == [-2] @skip_if_server_version_lt("7.4.0") def test_hpexpire_multiple_fields(r): - r.delete('test:hash') - r.hset('test:hash', - mapping={'field1': 'value1', 'field2': 'value2', 'field3': 'value3'}) - assert r.hpexpire('test:hash', 500, 'field1', 'field2') == [1, 1] + r.delete("test:hash") + r.hset( + "test:hash", + mapping={"field1": "value1", "field2": "value2", "field3": "value3"}, + ) + assert r.hpexpire("test:hash", 500, "field1", "field2") == [1, 1] time.sleep(0.6) - assert r.hexists('test:hash', 'field1') is False - assert r.hexists('test:hash', 'field2') is False - assert r.hexists('test:hash', 'field3') is True + assert r.hexists("test:hash", "field1") is False + assert r.hexists("test:hash", "field2") is False + assert r.hexists("test:hash", "field3") is True @skip_if_server_version_lt("7.4.0") def test_hexpireat_basic(r): - r.delete('test:hash') - r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + r.delete("test:hash") + r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"}) exp_time = int((datetime.now() + timedelta(seconds=1)).timestamp()) - assert r.hexpireat('test:hash', exp_time, 'field1') == [1] + assert r.hexpireat("test:hash", exp_time, "field1") == [1] time.sleep(1.1) - assert r.hexists('test:hash', 'field1') is False - assert r.hexists('test:hash', 'field2') is True + assert r.hexists("test:hash", "field1") is False + assert r.hexists("test:hash", "field2") is True @skip_if_server_version_lt("7.4.0") def test_hexpireat_with_datetime(r): - r.delete('test:hash') - r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + r.delete("test:hash") + r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"}) exp_time = datetime.now() + timedelta(seconds=1) - assert r.hexpireat('test:hash', exp_time, 'field1') == [1] + assert r.hexpireat("test:hash", exp_time, "field1") == [1] time.sleep(1.1) - assert r.hexists('test:hash', 'field1') is False - assert r.hexists('test:hash', 'field2') is True + assert r.hexists("test:hash", "field1") is False + assert r.hexists("test:hash", "field2") is True @skip_if_server_version_lt("7.4.0") def test_hexpireat_conditions(r): - r.delete('test:hash') - r.hset('test:hash', mapping={'field1': 'value1'}) + r.delete("test:hash") + r.hset("test:hash", mapping={"field1": "value1"}) future_exp_time = int((datetime.now() + timedelta(seconds=2)).timestamp()) past_exp_time = int((datetime.now() - timedelta(seconds=1)).timestamp()) - assert r.hexpireat('test:hash', future_exp_time, 'field1', xx=True) == [0] - assert r.hexpireat('test:hash', future_exp_time, 'field1', nx=True) == [1] - assert r.hexpireat('test:hash', past_exp_time, 'field1', gt=True) == [0] - assert r.hexpireat('test:hash', past_exp_time, 'field1', lt=True) == [2] - assert r.hexists('test:hash', 'field1') is False + assert r.hexpireat("test:hash", future_exp_time, "field1", xx=True) == [0] + assert r.hexpireat("test:hash", future_exp_time, "field1", nx=True) == [1] + assert r.hexpireat("test:hash", past_exp_time, "field1", gt=True) == [0] + assert r.hexpireat("test:hash", past_exp_time, "field1", lt=True) == [2] + assert r.hexists("test:hash", "field1") is False @skip_if_server_version_lt("7.4.0") def test_hexpireat_nonexistent_key_or_field(r): - r.delete('test:hash') + r.delete("test:hash") future_exp_time = int((datetime.now() + timedelta(seconds=1)).timestamp()) - assert r.hexpireat('test:hash', future_exp_time, 'field1') is None - r.hset('test:hash', 'field1', 'value1') - assert r.hexpireat('test:hash', future_exp_time, 'nonexistent_field') == [-2] + assert r.hexpireat("test:hash", future_exp_time, "field1") is None + r.hset("test:hash", "field1", "value1") + assert r.hexpireat("test:hash", future_exp_time, "nonexistent_field") == [-2] @skip_if_server_version_lt("7.4.0") def test_hexpireat_multiple_fields(r): - r.delete('test:hash') - r.hset('test:hash', - mapping={'field1': 'value1', 'field2': 'value2', 'field3': 'value3'}) + r.delete("test:hash") + r.hset( + "test:hash", + mapping={"field1": "value1", "field2": "value2", "field3": "value3"}, + ) exp_time = int((datetime.now() + timedelta(seconds=1)).timestamp()) - assert r.hexpireat('test:hash', exp_time, 'field1', 'field2') == [1, 1] + assert r.hexpireat("test:hash", exp_time, "field1", "field2") == [1, 1] time.sleep(1.1) - assert r.hexists('test:hash', 'field1') is False - assert r.hexists('test:hash', 'field2') is False - assert r.hexists('test:hash', 'field3') is True + assert r.hexists("test:hash", "field1") is False + assert r.hexists("test:hash", "field2") is False + assert r.hexists("test:hash", "field3") is True @skip_if_server_version_lt("7.4.0") def test_hpexpireat_basic(r): - r.delete('test:hash') - r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + r.delete("test:hash") + r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"}) exp_time = int((datetime.now() + timedelta(milliseconds=400)).timestamp() * 1000) - assert r.hpexpireat('test:hash', exp_time, 'field1') == [1] + assert r.hpexpireat("test:hash", exp_time, "field1") == [1] time.sleep(0.5) - assert r.hexists('test:hash', 'field1') is False - assert r.hexists('test:hash', 'field2') is True + assert r.hexists("test:hash", "field1") is False + assert r.hexists("test:hash", "field2") is True @skip_if_server_version_lt("7.4.0") def test_hpexpireat_with_datetime(r): - r.delete('test:hash') - r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + r.delete("test:hash") + r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"}) exp_time = datetime.now() + timedelta(milliseconds=400) - assert r.hpexpireat('test:hash', exp_time, 'field1') == [1] + assert r.hpexpireat("test:hash", exp_time, "field1") == [1] time.sleep(0.5) - assert r.hexists('test:hash', 'field1') is False - assert r.hexists('test:hash', 'field2') is True + assert r.hexists("test:hash", "field1") is False + assert r.hexists("test:hash", "field2") is True @skip_if_server_version_lt("7.4.0") def test_hpexpireat_conditions(r): - r.delete('test:hash') - r.hset('test:hash', mapping={'field1': 'value1'}) + r.delete("test:hash") + r.hset("test:hash", mapping={"field1": "value1"}) future_exp_time = int( - (datetime.now() + timedelta(milliseconds=500)).timestamp() * 1000) + (datetime.now() + timedelta(milliseconds=500)).timestamp() * 1000 + ) past_exp_time = int( - (datetime.now() - timedelta(milliseconds=500)).timestamp() * 1000) - assert r.hpexpireat('test:hash', future_exp_time, 'field1', xx=True) == [0] - assert r.hpexpireat('test:hash', future_exp_time, 'field1', nx=True) == [1] - assert r.hpexpireat('test:hash', past_exp_time, 'field1', gt=True) == [0] - assert r.hpexpireat('test:hash', past_exp_time, 'field1', lt=True) == [2] - assert r.hexists('test:hash', 'field1') is False + (datetime.now() - timedelta(milliseconds=500)).timestamp() * 1000 + ) + assert r.hpexpireat("test:hash", future_exp_time, "field1", xx=True) == [0] + assert r.hpexpireat("test:hash", future_exp_time, "field1", nx=True) == [1] + assert r.hpexpireat("test:hash", past_exp_time, "field1", gt=True) == [0] + assert r.hpexpireat("test:hash", past_exp_time, "field1", lt=True) == [2] + assert r.hexists("test:hash", "field1") is False @skip_if_server_version_lt("7.4.0") def test_hpexpireat_nonexistent_key_or_field(r): - r.delete('test:hash') + r.delete("test:hash") future_exp_time = int( - (datetime.now() + timedelta(milliseconds=500)).timestamp() * 1000) - assert r.hpexpireat('test:hash', future_exp_time, 'field1') is None - r.hset('test:hash', 'field1', 'value1') - assert r.hpexpireat('test:hash', future_exp_time, 'nonexistent_field') == [-2] + (datetime.now() + timedelta(milliseconds=500)).timestamp() * 1000 + ) + assert r.hpexpireat("test:hash", future_exp_time, "field1") is None + r.hset("test:hash", "field1", "value1") + assert r.hpexpireat("test:hash", future_exp_time, "nonexistent_field") == [-2] @skip_if_server_version_lt("7.4.0") def test_hpexpireat_multiple_fields(r): - r.delete('test:hash') - r.hset('test:hash', - mapping={'field1': 'value1', 'field2': 'value2', 'field3': 'value3'}) + r.delete("test:hash") + r.hset( + "test:hash", + mapping={"field1": "value1", "field2": "value2", "field3": "value3"}, + ) exp_time = int((datetime.now() + timedelta(milliseconds=400)).timestamp() * 1000) - assert r.hpexpireat('test:hash', exp_time, 'field1', 'field2') == [1, 1] + assert r.hpexpireat("test:hash", exp_time, "field1", "field2") == [1, 1] time.sleep(0.5) - assert r.hexists('test:hash', 'field1') is False - assert r.hexists('test:hash', 'field2') is False - assert r.hexists('test:hash', 'field3') is True + assert r.hexists("test:hash", "field1") is False + assert r.hexists("test:hash", "field2") is False + assert r.hexists("test:hash", "field3") is True @skip_if_server_version_lt("7.4.0") def test_hpersist_multiple_fields_mixed_conditions(r): - r.delete('test:hash') - r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) - r.hexpire('test:hash', 5000, 'field1') - assert r.hpersist('test:hash', 'field1', 'field2', 'field3') == [1, -1, -2] + r.delete("test:hash") + r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"}) + r.hexpire("test:hash", 5000, "field1") + assert r.hpersist("test:hash", "field1", "field2", "field3") == [1, -1, -2] @skip_if_server_version_lt("7.4.0") def test_hexpiretime_multiple_fields_mixed_conditions(r): - r.delete('test:hash') - r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + r.delete("test:hash") + r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"}) future_time = int((datetime.now() + timedelta(minutes=30)).timestamp()) - r.hexpireat('test:hash', future_time, 'field1') - result = r.hexpiretime('test:hash', 'field1', 'field2', 'field3') + r.hexpireat("test:hash", future_time, "field1") + result = r.hexpiretime("test:hash", "field1", "field2", "field3") assert future_time - 10 < result[0] <= future_time assert result[1:] == [-1, -2] @skip_if_server_version_lt("7.4.0") def test_hpexpiretime_multiple_fields_mixed_conditions(r): - r.delete('test:hash') - r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + r.delete("test:hash") + r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"}) future_time = int((datetime.now() + timedelta(minutes=30)).timestamp()) - r.hexpireat('test:hash', future_time, 'field1') - result = r.hpexpiretime('test:hash', 'field1', 'field2', 'field3') + r.hexpireat("test:hash", future_time, "field1") + result = r.hpexpiretime("test:hash", "field1", "field2", "field3") assert future_time * 1000 - 10000 < result[0] <= future_time * 1000 assert result[1:] == [-1, -2] @skip_if_server_version_lt("7.4.0") def test_ttl_multiple_fields_mixed_conditions(r): - r.delete('test:hash') - r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + r.delete("test:hash") + r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"}) future_time = int((datetime.now() + timedelta(minutes=30)).timestamp()) - r.hexpireat('test:hash', future_time, 'field1') - result = r.httl('test:hash', 'field1', 'field2', 'field3') + r.hexpireat("test:hash", future_time, "field1") + result = r.httl("test:hash", "field1", "field2", "field3") assert 30 * 60 - 10 < result[0] <= 30 * 60 assert result[1:] == [-1, -2] @skip_if_server_version_lt("7.4.0") def test_pttl_multiple_fields_mixed_conditions(r): - r.delete('test:hash') - r.hset('test:hash', mapping={'field1': 'value1', 'field2': 'value2'}) + r.delete("test:hash") + r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"}) future_time = int((datetime.now() + timedelta(minutes=30)).timestamp()) - r.hexpireat('test:hash', future_time, 'field1') - result = r.hpttl('test:hash', 'field1', 'field2', 'field3') + r.hexpireat("test:hash", future_time, "field1") + result = r.hpttl("test:hash", "field1", "field2", "field3") assert 30 * 60000 - 10000 < result[0] <= 30 * 60000 assert result[1:] == [-1, -2] From 8316fd3925a514ceaa1e3ddc232cca047cabf1ed Mon Sep 17 00:00:00 2001 From: Gabriel Erzse Date: Wed, 15 May 2024 10:05:25 +0300 Subject: [PATCH 04/12] Sphinx config --- docs/conf.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index a201da2fc0..33a8589654 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -10,6 +10,7 @@ # All configuration values have a default; values that are commented out # serve to show the default. +import datetime import os import sys @@ -30,12 +31,15 @@ "nbsphinx", "sphinx_gallery.load_style", "sphinx.ext.autodoc", - "sphinx_autodoc_typehints", - "sphinx.ext.doctest", "sphinx.ext.viewcode", "sphinx.ext.autosectionlabel", + "sphinx.ext.napoleon", ] +# Napoleon settings. We only accept Google-style docstrings. +napoleon_google_docstring = True +napoleon_numpy_docstring = False + # AutosectionLabel settings. # Uses a :