Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
4 changes: 3 additions & 1 deletion redis/commands/search/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
HybridQuery,
)
from redis.commands.search.hybrid_result import HybridCursorResult, HybridResult
from redis.utils import deprecated_function
from redis.utils import deprecated_function, experimental_method

from ..helpers import get_protocol_version
from ._util import to_string
Expand Down Expand Up @@ -560,6 +560,7 @@ def search(
SEARCH_CMD, res, query=query, duration=(time.monotonic() - st) * 1000.0
)

@experimental_method()
def hybrid_search(
self,
query: HybridQuery,
Expand Down Expand Up @@ -1053,6 +1054,7 @@ async def search(
SEARCH_CMD, res, query=query, duration=(time.monotonic() - st) * 1000.0
)

@experimental_method()
async def hybrid_search(
self,
query: HybridQuery,
Expand Down
5 changes: 3 additions & 2 deletions redis/commands/search/hybrid_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@ def query_string(self) -> str:
def scorer(self, scorer: str) -> "HybridSearchQuery":
"""
Scoring algorithm for text search query.
Allowed values are "TFIDF", "DISMAX", "DOCSCORE", "BM25", etc.
Allowed values are "TFIDF", "TFIDF.DOCNORM", "DISMAX", "DOCSCORE", "BM25",
"BM25STD", "BM25STD.TANH", "HAMMING", etc.

For more information about supported scroring algorithms,
For more information about supported scoring algorithms,
see https://redis.io/docs/latest/develop/ai/search-and-query/advanced-concepts/scoring/
"""
self._scorer = scorer
Expand Down
169 changes: 112 additions & 57 deletions redis/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import datetime
import inspect
import logging
import textwrap
import warnings
Expand Down Expand Up @@ -125,12 +126,22 @@ def deprecated_function(reason="", version="", name=None):
"""

def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
warn_deprecated(name or func.__name__, reason, version, stacklevel=3)
return func(*args, **kwargs)
if inspect.iscoroutinefunction(func):
# Create async wrapper for async functions
@wraps(func)
async def async_wrapper(*args, **kwargs):
warn_deprecated(name or func.__name__, reason, version, stacklevel=3)
return await func(*args, **kwargs)

return async_wrapper
else:
# Create regular wrapper for sync functions
@wraps(func)
def wrapper(*args, **kwargs):
warn_deprecated(name or func.__name__, reason, version, stacklevel=3)
return func(*args, **kwargs)

return wrapper
return wrapper

return decorator

Expand Down Expand Up @@ -158,47 +169,73 @@ def warn_deprecated_arg_usage(
C = TypeVar("C", bound=Callable)


def _get_filterable_args(
func: Callable, args: tuple, kwargs: dict, allowed_args: Optional[List[str]] = None
) -> dict:
"""
Extract arguments from function call that should be checked for deprecation/experimental warnings.
Excludes 'self' and any explicitly allowed args.
"""
arg_names = func.__code__.co_varnames[: func.__code__.co_argcount]
filterable_args = dict(zip(arg_names, args))
filterable_args.update(kwargs)
filterable_args.pop("self", None)
if allowed_args:
for allowed_arg in allowed_args:
filterable_args.pop(allowed_arg, None)
return filterable_args


def deprecated_args(
args_to_warn: list = ["*"],
allowed_args: list = [],
args_to_warn: Optional[List[str]] = None,
allowed_args: Optional[List[str]] = None,
reason: str = "",
version: str = "",
) -> Callable[[C], C]:
"""
Decorator to mark specified args of a function as deprecated.
If '*' is in args_to_warn, all arguments will be marked as deprecated.
"""
if args_to_warn is None:
args_to_warn = ["*"]
if allowed_args is None:
allowed_args = []

def _check_deprecated_args(func, filterable_args):
"""Check and warn about deprecated arguments."""
for arg in args_to_warn:
if arg == "*" and len(filterable_args) > 0:
warn_deprecated_arg_usage(
list(filterable_args.keys()),
func.__name__,
reason,
version,
stacklevel=4,
)
elif arg in filterable_args:
warn_deprecated_arg_usage(
arg, func.__name__, reason, version, stacklevel=4
)

def decorator(func: C) -> C:
@wraps(func)
def wrapper(*args, **kwargs):
# Get function argument names
arg_names = func.__code__.co_varnames[: func.__code__.co_argcount]

provided_args = dict(zip(arg_names, args))
provided_args.update(kwargs)

provided_args.pop("self", None)
for allowed_arg in allowed_args:
provided_args.pop(allowed_arg, None)

for arg in args_to_warn:
if arg == "*" and len(provided_args) > 0:
warn_deprecated_arg_usage(
list(provided_args.keys()),
func.__name__,
reason,
version,
stacklevel=3,
)
elif arg in provided_args:
warn_deprecated_arg_usage(
arg, func.__name__, reason, version, stacklevel=3
)

return func(*args, **kwargs)

return wrapper
if inspect.iscoroutinefunction(func):

@wraps(func)
async def async_wrapper(*args, **kwargs):
filterable_args = _get_filterable_args(func, args, kwargs, allowed_args)
_check_deprecated_args(func, filterable_args)
return await func(*args, **kwargs)

return async_wrapper
else:

@wraps(func)
def wrapper(*args, **kwargs):
filterable_args = _get_filterable_args(func, args, kwargs, allowed_args)
_check_deprecated_args(func, filterable_args)
return func(*args, **kwargs)

return wrapper

return decorator

Expand Down Expand Up @@ -368,12 +405,22 @@ def experimental_method() -> Callable[[C], C]:
"""

def decorator(func: C) -> C:
@wraps(func)
def wrapper(*args, **kwargs):
warn_experimental(func.__name__, stacklevel=2)
return func(*args, **kwargs)
if inspect.iscoroutinefunction(func):
# Create async wrapper for async functions
@wraps(func)
async def async_wrapper(*args, **kwargs):
warn_experimental(func.__name__, stacklevel=2)
return await func(*args, **kwargs)

return async_wrapper
else:
# Create regular wrapper for sync functions
@wraps(func)
def wrapper(*args, **kwargs):
warn_experimental(func.__name__, stacklevel=2)
return func(*args, **kwargs)

return wrapper
return wrapper

return decorator

Expand All @@ -393,32 +440,40 @@ def warn_experimental_arg_usage(


def experimental_args(
args_to_warn: list = ["*"],
args_to_warn: Optional[List[str]] = None,
) -> Callable[[C], C]:
"""
Decorator to mark specified args of a function as experimental.
"""
if args_to_warn is None:
args_to_warn = ["*"]

def _check_experimental_args(func, filterable_args):
"""Check and warn about experimental arguments."""
for arg in args_to_warn:
if arg in filterable_args:
warn_experimental_arg_usage(arg, func.__name__, stacklevel=4)

def decorator(func: C) -> C:
@wraps(func)
def wrapper(*args, **kwargs):
# Get function argument names
arg_names = func.__code__.co_varnames[: func.__code__.co_argcount]
if inspect.iscoroutinefunction(func):

provided_args = dict(zip(arg_names, args))
provided_args.update(kwargs)
@wraps(func)
async def async_wrapper(*args, **kwargs):
filterable_args = _get_filterable_args(func, args, kwargs)
if len(filterable_args) > 0:
_check_experimental_args(func, filterable_args)
return await func(*args, **kwargs)

provided_args.pop("self", None)
return async_wrapper
else:

if len(provided_args) == 0:
@wraps(func)
def wrapper(*args, **kwargs):
filterable_args = _get_filterable_args(func, args, kwargs)
if len(filterable_args) > 0:
_check_experimental_args(func, filterable_args)
return func(*args, **kwargs)

for arg in args_to_warn:
if arg in provided_args:
warn_experimental_arg_usage(arg, func.__name__, stacklevel=3)

return func(*args, **kwargs)

return wrapper
return wrapper

return decorator
Loading
Loading