diff --git a/CHANGELOG.md b/CHANGELOG.md index d5d97301c3..562a8cde9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * Node: Renamed `ReturnType` to `GlideReturnType` ([#2241](https://github.com/valkey-io/valkey-glide/pull/2241)) * Node, Python: Rename `stop` to `end` in sorted set queries ([#2214](https://github.com/valkey-io/valkey-glide/pull/2214)) * Node: Added binary variant to sorted set commands - part 1 ([#2190](https://github.com/valkey-io/valkey-glide/pull/2190)) +* Python: Fix BITPOS for Valkey8 ([#2256](https://github.com/valkey-io/valkey-glide/pull/2256)) * Node: Added binary variant to HSCAN command ([#2240](https://github.com/valkey-io/valkey-glide/pull/2240)) * Node: replace decoder by DecoderOption and route by RouteOption in API([#2234](https://github.com/valkey-io/valkey-glide/pull/2234/)) * Node: Added binary variant to sorted set commands ([#2190](https://github.com/valkey-io/valkey-glide/pull/2190), [#2210](https://github.com/valkey-io/valkey-glide/pull/2210)) diff --git a/python/python/glide/async_commands/bitmap.py b/python/python/glide/async_commands/bitmap.py index 87e501dfb5..d7e2415057 100644 --- a/python/python/glide/async_commands/bitmap.py +++ b/python/python/glide/async_commands/bitmap.py @@ -37,7 +37,8 @@ def __init__( Args: start (int): The starting offset index. - end (int): The ending offset index. Optional since Valkey version 8.0.0 and above. If not provided, it will default to the end of the string. + end (Optional[int]): The ending offset index. Optional since Valkey version 8.0.0 and above for the BITCOUNT + command. If not provided, it will default to the end of the string. index_type (Optional[BitmapIndexType]): The index offset type. This option can only be specified if you are using Valkey version 7.0.0 or above. Could be either `BitmapIndexType.BYTE` or `BitmapIndexType.BIT`. If no index type is provided, the indexes will be assumed to be byte indexes. @@ -260,8 +261,8 @@ class BitOverflowControl(Enum): WRAP = "WRAP" """ - Performs modulo when overflows occur with unsigned encoding. When overflows occur with signed encoding, the value - restarts at the most negative value. When underflows occur with signed encoding, the value restarts at the most + Performs modulo when overflows occur with unsigned encoding. When overflows occur with signed encoding, the value + restarts at the most negative value. When underflows occur with signed encoding, the value restarts at the most positive value. """ SAT = "SAT" diff --git a/python/python/glide/async_commands/core.py b/python/python/glide/async_commands/core.py index 7acb44ca60..f6cd6be97a 100644 --- a/python/python/glide/async_commands/core.py +++ b/python/python/glide/async_commands/core.py @@ -5593,7 +5593,7 @@ async def getbit(self, key: TEncodable, offset: int) -> int: ) async def bitpos( - self, key: TEncodable, bit: int, start: Optional[int] = None + self, key: TEncodable, bit: int, options: Optional[OffsetOptions] = None ) -> int: """ Returns the position of the first bit matching the given `bit` value. The optional starting offset @@ -5601,12 +5601,17 @@ async def bitpos( The offset can also be a negative number indicating an offset starting at the end of the list, with `-1` being the last byte of the list, `-2` being the penultimate, and so on. + If you are using Valkey 7.0.0 or above, the optional `index_type` can also be provided to specify whether the + `start` and `end` offsets specify BIT or BYTE offsets. If `index_type` is not provided, BYTE offsets + are assumed. If BIT is specified, `start=0` and `end=2` means to look at the first three bits. If BYTE is + specified, `start=0` and `end=2` means to look at the first three bytes. + See https://valkey.io/commands/bitpos for more details. Args: key (TEncodable): The key of the string. bit (int): The bit value to match. Must be `0` or `1`. - start (Optional[int]): The starting offset. + options (Optional[OffsetOptions]): The offset options. Returns: int: The position of the first occurrence of `bit` in the binary value of the string held at `key`. @@ -5616,60 +5621,18 @@ async def bitpos( >>> await client.set("key1", "A1") # "A1" has binary value 01000001 00110001 >>> await client.bitpos("key1", 1) 1 # The first occurrence of bit value 1 in the string stored at "key1" is at the second position. - >>> await client.bitpos("key1", 1, -1) + >>> await client.bitpos("key1", 1, OffsetOptions(-1)) 10 # The first occurrence of bit value 1, starting at the last byte in the string stored at "key1", is at the eleventh position. - """ - args = [key, str(bit)] if start is None else [key, str(bit), str(start)] - return cast( - int, - await self._execute_command(RequestType.BitPos, args), - ) - async def bitpos_interval( - self, - key: TEncodable, - bit: int, - start: int, - end: int, - index_type: Optional[BitmapIndexType] = None, - ) -> int: - """ - Returns the position of the first bit matching the given `bit` value. The offsets are zero-based indexes, with - `0` being the first element of the list, `1` being the next, and so on. These offsets can also be negative - numbers indicating offsets starting at the end of the list, with `-1` being the last element of the list, `-2` - being the penultimate, and so on. - - If you are using Valkey 7.0.0 or above, the optional `index_type` can also be provided to specify whether the - `start` and `end` offsets specify BIT or BYTE offsets. If `index_type` is not provided, BYTE offsets - are assumed. If BIT is specified, `start=0` and `end=2` means to look at the first three bits. If BYTE is - specified, `start=0` and `end=2` means to look at the first three bytes. - - See https://valkey.io/commands/bitpos for more details. - - Args: - key (TEncodable): The key of the string. - bit (int): The bit value to match. Must be `0` or `1`. - start (int): The starting offset. - end (int): The ending offset. - index_type (Optional[BitmapIndexType]): The index offset type. This option can only be specified if you are - using Valkey version 7.0.0 or above. Could be either `BitmapIndexType.BYTE` or `BitmapIndexType.BIT`. - If no index type is provided, the indexes will be assumed to be byte indexes. - - Returns: - int: The position of the first occurrence from the `start` to the `end` offsets of the `bit` in the binary - value of the string held at `key`. - - Examples: - >>> await client.set("key1", "A12") # "A12" has binary value 01000001 00110001 00110010 - >>> await client.bitpos_interval("key1", 1, 1, -1) + >>> await client.set("key2", "A12") # "A12" has binary value 01000001 00110001 00110010 + >>> await client.bitpos("key2", 1, OffsetOptions(1, -1)) 10 # The first occurrence of bit value 1 in the second byte to the last byte of the string stored at "key1" is at the eleventh position. - >>> await client.bitpos_interval("key1", 1, 2, 9, BitmapIndexType.BIT) + >>> await client.bitpos("key2", 1, OffsetOptions(2, 9, BitmapIndexType.BIT)) 7 # The first occurrence of bit value 1 in the third to tenth bits of the string stored at "key1" is at the eighth position. """ - if index_type is not None: - args = [key, str(bit), str(start), str(end), index_type.value] - else: - args = [key, str(bit), str(start), str(end)] + args: List[TEncodable] = [key, str(bit)] + if options is not None: + args.extend(options.to_args()) return cast( int, diff --git a/python/python/glide/async_commands/transaction.py b/python/python/glide/async_commands/transaction.py index 0e87f26dcd..fd49a69558 100644 --- a/python/python/glide/async_commands/transaction.py +++ b/python/python/glide/async_commands/transaction.py @@ -4087,7 +4087,7 @@ def bitpos( self: TTransaction, key: TEncodable, bit: int, - start: Optional[int] = None, + options: Optional[OffsetOptions] = None, ) -> TTransaction: """ Returns the position of the first bit matching the given `bit` value. The optional starting offset @@ -4100,53 +4100,15 @@ def bitpos( Args: key (TEncodable): The key of the string. bit (int): The bit value to match. Must be `0` or `1`. - start (Optional[int]): The starting offset. + options (Optional[OffsetOptions]): The offset options. Command response: int: The position of the first occurrence of `bit` in the binary value of the string held at `key`. If `start` was provided, the search begins at the offset indicated by `start`. """ - args = [key, str(bit)] if start is None else [key, str(bit), str(start)] - return self.append_command(RequestType.BitPos, args) - - def bitpos_interval( - self: TTransaction, - key: TEncodable, - bit: int, - start: int, - end: int, - index_type: Optional[BitmapIndexType] = None, - ) -> TTransaction: - """ - Returns the position of the first bit matching the given `bit` value. The offsets are zero-based indexes, with - `0` being the first element of the list, `1` being the next, and so on. These offsets can also be negative - numbers indicating offsets starting at the end of the list, with `-1` being the last element of the list, `-2` - being the penultimate, and so on. - - If you are using Valkey 7.0.0 or above, the optional `index_type` can also be provided to specify whether the - `start` and `end` offsets specify BIT or BYTE offsets. If `index_type` is not provided, BYTE offsets - are assumed. If BIT is specified, `start=0` and `end=2` means to look at the first three bits. If BYTE is - specified, `start=0` and `end=2` means to look at the first three bytes. - - See https://valkey.io/commands/bitpos for more details. - - Args: - key (TEncodable): The key of the string. - bit (int): The bit value to match. Must be `0` or `1`. - start (int): The starting offset. - end (int): The ending offset. - index_type (Optional[BitmapIndexType]): The index offset type. This option can only be specified if you are - using Valkey version 7.0.0 or above. Could be either `BitmapIndexType.BYTE` or `BitmapIndexType.BIT`. - If no index type is provided, the indexes will be assumed to be byte indexes. - - Command response: - int: The position of the first occurrence from the `start` to the `end` offsets of the `bit` in the binary - value of the string held at `key`. - """ - if index_type is not None: - args = [key, str(bit), str(start), str(end), index_type.value] - else: - args = [key, str(bit), str(start), str(end)] + args: List[TEncodable] = [key, str(bit)] + if options is not None: + args.extend(options.to_args()) return self.append_command(RequestType.BitPos, args) diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index e9897c7b2d..e293acaf40 100644 --- a/python/python/tests/test_async_client.py +++ b/python/python/tests/test_async_client.py @@ -7192,7 +7192,7 @@ async def test_getbit(self, glide_client: TGlideClient): @pytest.mark.parametrize("cluster_mode", [True, False]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) - async def test_bitpos_and_bitpos_interval(self, glide_client: TGlideClient): + async def test_bitpos(self, glide_client: TGlideClient): key = get_random_string(10) non_existing_key = get_random_string(10) set_key = get_random_string(10) @@ -7203,58 +7203,66 @@ async def test_bitpos_and_bitpos_interval(self, glide_client: TGlideClient): assert await glide_client.set(key, value) == OK assert await glide_client.bitpos(key, 0) == 0 assert await glide_client.bitpos(key, 1) == 2 - assert await glide_client.bitpos(key, 1, 1) == 9 - assert await glide_client.bitpos_interval(key, 0, 3, 5) == 24 + assert await glide_client.bitpos(key, 1, OffsetOptions(1)) == 9 + assert await glide_client.bitpos(key, 0, OffsetOptions(3, 5)) == 24 # `BITPOS` returns -1 for non-existing strings assert await glide_client.bitpos(non_existing_key, 1) == -1 - assert await glide_client.bitpos_interval(non_existing_key, 1, 3, 5) == -1 + assert await glide_client.bitpos(non_existing_key, 1, OffsetOptions(3, 5)) == -1 # invalid argument - bit value must be 0 or 1 with pytest.raises(RequestError): await glide_client.bitpos(key, 2) with pytest.raises(RequestError): - await glide_client.bitpos_interval(key, 2, 3, 5) + await glide_client.bitpos(key, 2, OffsetOptions(3, 5)) # key exists, but it is not a string assert await glide_client.sadd(set_key, [value]) == 1 with pytest.raises(RequestError): await glide_client.bitpos(set_key, 1) with pytest.raises(RequestError): - await glide_client.bitpos_interval(set_key, 1, 1, -1) + await glide_client.bitpos(set_key, 1, OffsetOptions(1, -1)) if await check_if_server_version_lt(glide_client, "7.0.0"): # error thrown because BIT and BYTE options were implemented after 7.0.0 with pytest.raises(RequestError): - await glide_client.bitpos_interval(key, 1, 1, -1, BitmapIndexType.BYTE) + await glide_client.bitpos( + key, 1, OffsetOptions(1, -1, BitmapIndexType.BYTE) + ) with pytest.raises(RequestError): - await glide_client.bitpos_interval(key, 1, 1, -1, BitmapIndexType.BIT) + await glide_client.bitpos( + key, 1, OffsetOptions(1, -1, BitmapIndexType.BIT) + ) else: assert ( - await glide_client.bitpos_interval(key, 0, 3, 5, BitmapIndexType.BYTE) + await glide_client.bitpos( + key, 0, OffsetOptions(3, 5, BitmapIndexType.BYTE) + ) == 24 ) assert ( - await glide_client.bitpos_interval(key, 1, 43, -2, BitmapIndexType.BIT) + await glide_client.bitpos( + key, 1, OffsetOptions(43, -2, BitmapIndexType.BIT) + ) == 47 ) assert ( - await glide_client.bitpos_interval( - non_existing_key, 1, 3, 5, BitmapIndexType.BYTE + await glide_client.bitpos( + non_existing_key, 1, OffsetOptions(3, 5, BitmapIndexType.BYTE) ) == -1 ) assert ( - await glide_client.bitpos_interval( - non_existing_key, 1, 3, 5, BitmapIndexType.BIT + await glide_client.bitpos( + non_existing_key, 1, OffsetOptions(3, 5, BitmapIndexType.BIT) ) == -1 ) # key exists, but it is not a string with pytest.raises(RequestError): - await glide_client.bitpos_interval( - set_key, 1, 1, -1, BitmapIndexType.BIT + await glide_client.bitpos( + set_key, 1, OffsetOptions(1, -1, BitmapIndexType.BIT) ) @pytest.mark.parametrize("cluster_mode", [True, False]) diff --git a/python/python/tests/test_transaction.py b/python/python/tests/test_transaction.py index dadef84200..8ff5ddbe4e 100644 --- a/python/python/tests/test_transaction.py +++ b/python/python/tests/test_transaction.py @@ -558,7 +558,7 @@ async def transaction_test( args.append(OK) transaction.bitcount(key20, OffsetOptions(5, 30, BitmapIndexType.BIT)) args.append(17) - transaction.bitpos_interval(key20, 1, 44, 50, BitmapIndexType.BIT) + transaction.bitpos(key20, 1, OffsetOptions(44, 50, BitmapIndexType.BIT)) args.append(46) if not await check_if_server_version_lt(glide_client, "8.0.0"):