Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ env:
# for example after 8.2.1 is published, 8.2 image contains 8.2.1 content
CURRENT_REDIS_VERSION: '8.6.1'
REDIS_VERSION_CUSTOM_MAP: >-
8.8:unstable-24805570909-debian
8.8:8.8-rc1

jobs:
dependency-audit:
Expand Down
184 changes: 178 additions & 6 deletions redis/commands/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -3264,7 +3264,15 @@ def getex(

For more information, see https://redis.io/commands/getex
"""
if not at_most_one_value_set((ex, px, exat, pxat, persist)):
if not at_most_one_value_set(
(
ex is not None,
px is not None,
exat is not None,
pxat is not None,
persist,
)
):
raise DataError(
"``ex``, ``px``, ``exat``, ``pxat``, "
"and ``persist`` are mutually exclusive."
Expand Down Expand Up @@ -3386,6 +3394,129 @@ def incrbyfloat(self, name: KeyT, amount: float = 1.0) -> float | Awaitable[floa
"""
return self.execute_command("INCRBYFLOAT", name, amount)

@overload
def increx(
self: SyncClientProtocol,
name: KeyT,
*,
byfloat: EncodableT | None = None,
byint: EncodableT | None = None,
lbound: EncodableT | None = None,
ubound: EncodableT | None = None,
overflow: Literal["FAIL", "REJECT", "SAT"] | None = None,
ex: ExpiryT | None = None,
px: ExpiryT | None = None,
exat: AbsExpiryT | None = None,
pxat: AbsExpiryT | None = None,
persist: bool = False,
enx: bool = False,
) -> list[Number | bytes | str]: ...

@overload
def increx(
self: AsyncClientProtocol,
name: KeyT,
*,
byfloat: EncodableT | None = None,
byint: EncodableT | None = None,
lbound: EncodableT | None = None,
ubound: EncodableT | None = None,
overflow: Literal["FAIL", "REJECT", "SAT"] | None = None,
ex: ExpiryT | None = None,
px: ExpiryT | None = None,
exat: AbsExpiryT | None = None,
pxat: AbsExpiryT | None = None,
persist: bool = False,
enx: bool = False,
) -> Awaitable[list[Number | bytes | str]]: ...

def increx(
self,
name: KeyT,
*,
byfloat: EncodableT | None = None,
byint: EncodableT | None = None,
lbound: EncodableT | None = None,
ubound: EncodableT | None = None,
overflow: Literal["FAIL", "REJECT", "SAT"] | None = None,
ex: ExpiryT | None = None,
px: ExpiryT | None = None,
exat: AbsExpiryT | None = None,
pxat: AbsExpiryT | None = None,
persist: bool = False,
enx: bool = False,
) -> list[Number | bytes | str] | Awaitable[list[Number | bytes | str]]:
"""
Increment the numeric value at key ``name`` and return the new value
and the actual increment.

``byfloat`` increments a floating point value by the provided amount.

``byint`` increments an integer value by the provided amount.

If neither ``byfloat`` nor ``byint`` is specified, the value is
incremented by one.

``lbound`` and ``ubound`` constrain the valid range of the result.

``overflow`` controls bound handling and can be ``FAIL``, ``REJECT``,
or ``SAT``.

``enx`` applies the expiration only when the key does not already
have an expiration, and requires ``ex``, ``px``, ``exat``, or ``pxat``.
"""
if not at_most_one_value_set(
(
byfloat is not None,
byint is not None,
)
):
raise DataError("``byfloat`` and ``byint`` are mutually exclusive.")

if not at_most_one_value_set(
(
ex is not None,
px is not None,
exat is not None,
pxat is not None,
persist,
)
):
raise DataError(
"``ex``, ``px``, ``exat``, ``pxat``, "
"and ``persist`` are mutually exclusive."
)

if overflow is not None and overflow not in {"FAIL", "REJECT", "SAT"}:
raise DataError("INCREX overflow must be one of: FAIL, REJECT, SAT")

if enx and ex is None and px is None and exat is None and pxat is None:
raise DataError(
"``enx`` requires one of ``ex``, ``px``, ``exat``, or ``pxat``."
)

pieces: list[EncodableT] = [name]

if byfloat is not None:
pieces.extend(("BYFLOAT", byfloat))
elif byint is not None:
pieces.extend(("BYINT", byint))

if lbound is not None:
pieces.extend(("LBOUND", lbound))
if ubound is not None:
pieces.extend(("UBOUND", ubound))
if overflow is not None:
pieces.extend(("OVERFLOW", overflow))

pieces.extend(extract_expire_flags(ex, px, exat, pxat))
if persist:
pieces.append("PERSIST")
if enx:
pieces.append("ENX")

return self.execute_command("INCREX", *pieces)

@overload
def keys(
self: SyncClientProtocol, pattern: PatternT = "*", **kwargs
Expand Down Expand Up @@ -3598,7 +3729,15 @@ def msetex(
Available since Redis 8.4
For more information, see https://redis.io/commands/msetex
"""
if not at_most_one_value_set((ex, px, exat, pxat, keepttl)):
if not at_most_one_value_set(
(
ex is not None,
px is not None,
exat is not None,
pxat is not None,
keepttl,
)
):
raise DataError(
"``ex``, ``px``, ``exat``, ``pxat``, "
"and ``keepttl`` are mutually exclusive."
Expand Down Expand Up @@ -4117,14 +4256,31 @@ def set(
For more information, see https://redis.io/commands/set
"""

if not at_most_one_value_set((ex, px, exat, pxat, keepttl)):
if not at_most_one_value_set(
(
ex is not None,
px is not None,
exat is not None,
pxat is not None,
keepttl,
)
):
raise DataError(
"``ex``, ``px``, ``exat``, ``pxat``, "
"and ``keepttl`` are mutually exclusive."
)

# Enforce mutual exclusivity among all conditional switches.
if not at_most_one_value_set((nx, xx, ifeq, ifne, ifdeq, ifdne)):
if not at_most_one_value_set(
(
nx,
xx,
ifeq is not None,
ifne is not None,
ifdeq is not None,
ifdne is not None,
)
):
raise DataError(
"``nx``, ``xx``, ``ifeq``, ``ifne``, ``ifdeq``, ``ifdne`` are mutually exclusive."
)
Expand Down Expand Up @@ -8897,7 +9053,15 @@ def hgetex(
if not keys:
raise DataError("'hgetex' should have at least one key provided")

if not at_most_one_value_set((ex, px, exat, pxat, persist)):
if not at_most_one_value_set(
(
ex is not None,
px is not None,
exat is not None,
pxat is not None,
persist,
)
):
raise DataError(
"``ex``, ``px``, ``exat``, ``pxat``, "
"and ``persist`` are mutually exclusive."
Expand Down Expand Up @@ -9126,7 +9290,15 @@ def hsetex(
"'items' must contain a list of key/value pairs."
)

if not at_most_one_value_set((ex, px, exat, pxat, keepttl)):
if not at_most_one_value_set(
(
ex is not None,
px is not None,
exat is not None,
pxat is not None,
keepttl,
)
):
raise DataError(
"``ex``, ``px``, ``exat``, ``pxat``, "
"and ``keepttl`` are mutually exclusive."
Expand Down
23 changes: 16 additions & 7 deletions tests/test_asyncio/test_cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -1107,6 +1107,17 @@ class TestClusterRedisCommands:
Tests for RedisCluster unique commands
"""

async def _wait_for_bgsave(self, r: RedisCluster, timeout=10) -> None:
loop = asyncio.get_running_loop()
deadline = loop.time() + timeout
while True:
info = await r.info("persistence", target_nodes=r.get_default_node())
if int(info.get("rdb_bgsave_in_progress", 0)) == 0:
return
if loop.time() > deadline:
pytest.fail("Timed out waiting for BGSAVE to finish")
await asyncio.sleep(0.05)

async def test_get_and_set(self, r: RedisCluster) -> None:
# get and set can't be tested independently of each other
assert await r.get("a") is None
Expand Down Expand Up @@ -1536,13 +1547,11 @@ async def test_readwrite(self) -> None:

@skip_if_redis_enterprise()
async def test_bgsave(self, r: RedisCluster) -> None:
try:
assert await r.bgsave()
await asyncio.sleep(0.3)
assert await r.bgsave(True)
except ResponseError as e:
if "Background save already in progress" not in e.__str__():
raise
await self._wait_for_bgsave(r)
assert await r.bgsave()
await self._wait_for_bgsave(r)
assert await r.bgsave(True)
await self._wait_for_bgsave(r)

async def test_info(self, r: RedisCluster) -> None:
# Map keys to same slot
Expand Down
Loading
Loading