Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Python: bitpos valkey8 #2256

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
7 changes: 4 additions & 3 deletions python/python/glide/async_commands/bitmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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"
Expand Down
65 changes: 14 additions & 51 deletions python/python/glide/async_commands/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -5593,20 +5593,25 @@ 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
`start` is a zero-based index, with `0` being the first byte of the list, `1` being the next byte and so on.
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`.
Expand All @@ -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,
Expand Down
48 changes: 5 additions & 43 deletions python/python/glide/async_commands/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)

Expand Down
40 changes: 24 additions & 16 deletions python/python/tests/test_async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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])
Expand Down
2 changes: 1 addition & 1 deletion python/python/tests/test_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"):
Expand Down
Loading