Skip to content

Commit 70aa89e

Browse files
committed
Add experimental annotations for the CAS/CAD methods and new args
1 parent c2dbc0a commit 70aa89e

File tree

2 files changed

+103
-4
lines changed

2 files changed

+103
-4
lines changed

redis/commands/core.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
)
4848
from redis.utils import (
4949
deprecated_function,
50+
experimental_args,
51+
experimental_method,
5052
extract_expire_flags,
5153
)
5254

@@ -1729,6 +1731,7 @@ def delete(self, *names: KeyT) -> ResponseT:
17291731
def __delitem__(self, name: KeyT):
17301732
self.delete(name)
17311733

1734+
@experimental_method()
17321735
def delex(
17331736
self,
17341737
name: KeyT,
@@ -1740,10 +1743,17 @@ def delex(
17401743
"""
17411744
Conditionally removes the specified key.
17421745
1743-
ifeq match-value - Delete the key only if its value is equal to match-value
1744-
ifne match-value - Delete the key only if its value is not equal to match-value
1745-
ifdeq match-digest - Delete the key only if the digest of its value is equal to match-digest
1746-
ifdne match-digest - Delete the key only if the digest of its value is not equal to match-digest
1746+
Warning:
1747+
**Experimental** since 7.1.
1748+
This API may change or be removed without notice.
1749+
The API may change based on feedback.
1750+
1751+
Arguments:
1752+
name: KeyT - the key to delete
1753+
ifeq match-valu: Optional[Union[bytes, str]] - Delete the key only if its value is equal to match-value
1754+
ifne match-value: Optional[Union[bytes, str]] - Delete the key only if its value is not equal to match-value
1755+
ifdeq match-digest: Optional[str] - Delete the key only if the digest of its value is equal to match-digest
1756+
ifdne match-digest: Optional[str] - Delete the key only if the digest of its value is not equal to match-digest
17471757
17481758
Returns:
17491759
int: 1 if the key was deleted, 0 otherwise.
@@ -1878,10 +1888,19 @@ def expiretime(self, key: str) -> int:
18781888
"""
18791889
return self.execute_command("EXPIRETIME", key)
18801890

1891+
@experimental_method()
18811892
def digest(self, name: KeyT) -> Optional[str]:
18821893
"""
18831894
Return the digest of the value stored at the specified key.
18841895
1896+
Warning:
1897+
**Experimental** since 7.1.
1898+
This API may change or be removed without notice.
1899+
The API may change based on feedback.
1900+
1901+
Arguments:
1902+
- name: KeyT - the key to get the digest of
1903+
18851904
Returns:
18861905
- None if the key does not exist
18871906
- (bulk string) the XXH3 digest of the value as a hex string
@@ -2385,6 +2404,7 @@ def restore(
23852404

23862405
return self.execute_command("RESTORE", *params)
23872406

2407+
@experimental_args(["ifeq", "ifne", "ifdeq", "ifdne"])
23882408
def set(
23892409
self,
23902410
name: KeyT,
@@ -2405,6 +2425,12 @@ def set(
24052425
"""
24062426
Set the value at key ``name`` to ``value``
24072427
2428+
Warning:
2429+
**Experimental** since 7.1.
2430+
The usage of the arguments ``ifeq``, ``ifne``, ``ifdeq``, and ``ifdne``
2431+
is experimental. The API or returned results when those parameters are used
2432+
may change based on feedback.
2433+
24082434
``ex`` sets an expire flag on key ``name`` for ``ex`` seconds.
24092435
24102436
``px`` sets an expire flag on key ``name`` for ``px`` milliseconds.

redis/utils.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,3 +346,76 @@ def new_init(self, *args, **kwargs):
346346

347347
cls.__init__ = new_init
348348
return cls
349+
350+
351+
def warn_experimental(name, stacklevel=2):
352+
import warnings
353+
354+
msg = (
355+
f"Call to experimental method {name}. "
356+
"Be aware that the function arguments can "
357+
"change or be removed in future versions."
358+
)
359+
warnings.warn(msg, category=UserWarning, stacklevel=stacklevel)
360+
361+
362+
def experimental_method() -> Callable[[C], C]:
363+
"""
364+
Decorator to mark a function as experimental.
365+
"""
366+
367+
def decorator(func: C) -> C:
368+
@wraps(func)
369+
def wrapper(*args, **kwargs):
370+
warn_experimental(func.__name__, stacklevel=2)
371+
return func(*args, **kwargs)
372+
373+
return wrapper
374+
375+
return decorator
376+
377+
378+
def warn_experimental_arg_usage(
379+
arg_name: Union[list, str],
380+
function_name: str,
381+
stacklevel: int = 2,
382+
):
383+
import warnings
384+
385+
msg = (
386+
f"Call to '{function_name}' method with experimental"
387+
f" usage of input argument/s '{arg_name}'."
388+
)
389+
warnings.warn(msg, category=UserWarning, stacklevel=stacklevel)
390+
391+
392+
def experimental_args(
393+
args_to_warn: list = ["*"],
394+
) -> Callable[[C], C]:
395+
"""
396+
Decorator to mark specified args of a function as experimental.
397+
"""
398+
399+
def decorator(func: C) -> C:
400+
@wraps(func)
401+
def wrapper(*args, **kwargs):
402+
# Get function argument names
403+
arg_names = func.__code__.co_varnames[: func.__code__.co_argcount]
404+
405+
provided_args = dict(zip(arg_names, args))
406+
provided_args.update(kwargs)
407+
408+
provided_args.pop("self", None)
409+
410+
if len(provided_args) == 0:
411+
return func(*args, **kwargs)
412+
413+
for arg in args_to_warn:
414+
if arg in provided_args:
415+
warn_experimental_arg_usage(arg, func.__name__, stacklevel=3)
416+
417+
return func(*args, **kwargs)
418+
419+
return wrapper
420+
421+
return decorator

0 commit comments

Comments
 (0)