Skip to content

Inconsistent behavior with body and index parameter order in the search method #2706

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

Closed
ISMendys opened this issue Nov 12, 2024 · 2 comments
Closed
Labels
Area: Client Manually written code that fits in no other area Category: Question

Comments

@ISMendys
Copy link

ISMendys commented Nov 12, 2024

When using the search method in the Elasticsearch client library, I encountered an unexpected behavior where the order of the body, index, and size parameters affects the function's operation. Here are the details:

Working code (as expected):

es = get_elastic()
body = {"query": {"terms": {"_id": ids, "boost": 1.0}}}
try:
    response = es.search(index=index, body=body, size=len(ids))
except Exception as error:
    logging.error(error, exc_info=True)
    raise error

In this case, with index first, followed by body and size, the search works as expected.

Code that throws an error:

When the order of parameters is reversed, placing body before index, the search fails:

es = get_elastic()
body = {"query": {"terms": {"_id": ids, "boost": 1.0}}}
try:
    response = es.search(body=body, index=index, size=len(ids))
except Exception as error:
    logging.error(error, exc_info=True)
    raise error

Here, the search does not work, and the only relevant difference between these cases is the parameter order.

Code where the reversed order does not cause an error:

Even with body and index reversed, the search works when size is not used:

response = es.search(body=body, index=index)

Summary of unexpected behavior: This inconsistency appears specifically when adding the size parameter (elasticsearch version 8.15.0) while reversing the order of body and index. The expected behavior is for the search method to work regardless of parameter order as long as all provided parameters are valid.

@pquentin
Copy link
Member

Hello, and thank you for your clear report. I find the claim that the behavior changes based on the order of parameters really surprising. The point of forcing our users to use keyword arguments since elasticsearch-py 8.0 is to avoid this class of errors entirely. The client code itself does not care about the order, we let the Python interpreter handle it for us. Here's how search() is defined:

def search(
self,
*,
index: t.Optional[t.Union[str, t.Sequence[str]]] = None,
aggregations: t.Optional[t.Mapping[str, t.Mapping[str, t.Any]]] = None,
aggs: t.Optional[t.Mapping[str, t.Mapping[str, t.Any]]] = None,
allow_no_indices: t.Optional[bool] = None,
allow_partial_search_results: t.Optional[bool] = None,
analyze_wildcard: t.Optional[bool] = None,
analyzer: t.Optional[str] = None,
batched_reduce_size: t.Optional[int] = None,
ccs_minimize_roundtrips: t.Optional[bool] = None,
collapse: t.Optional[t.Mapping[str, t.Any]] = None,
default_operator: t.Optional[t.Union[str, t.Literal["and", "or"]]] = None,
df: t.Optional[str] = None,
docvalue_fields: t.Optional[t.Sequence[t.Mapping[str, t.Any]]] = None,
error_trace: t.Optional[bool] = None,
expand_wildcards: t.Optional[
t.Union[
t.Sequence[
t.Union[str, t.Literal["all", "closed", "hidden", "none", "open"]]
],
t.Union[str, t.Literal["all", "closed", "hidden", "none", "open"]],
]
] = None,
explain: t.Optional[bool] = None,
ext: t.Optional[t.Mapping[str, t.Any]] = None,
fields: t.Optional[t.Sequence[t.Mapping[str, t.Any]]] = None,
filter_path: t.Optional[t.Union[str, t.Sequence[str]]] = None,
force_synthetic_source: t.Optional[bool] = None,
from_: t.Optional[int] = None,
highlight: t.Optional[t.Mapping[str, t.Any]] = None,
human: t.Optional[bool] = None,
ignore_throttled: t.Optional[bool] = None,
ignore_unavailable: t.Optional[bool] = None,
include_named_queries_score: t.Optional[bool] = None,
indices_boost: t.Optional[t.Sequence[t.Mapping[str, float]]] = None,
knn: t.Optional[
t.Union[t.Mapping[str, t.Any], t.Sequence[t.Mapping[str, t.Any]]]
] = None,
lenient: t.Optional[bool] = None,
max_concurrent_shard_requests: t.Optional[int] = None,
min_score: t.Optional[float] = None,
pit: t.Optional[t.Mapping[str, t.Any]] = None,
post_filter: t.Optional[t.Mapping[str, t.Any]] = None,
pre_filter_shard_size: t.Optional[int] = None,
preference: t.Optional[str] = None,
pretty: t.Optional[bool] = None,
profile: t.Optional[bool] = None,
q: t.Optional[str] = None,
query: t.Optional[t.Mapping[str, t.Any]] = None,
rank: t.Optional[t.Mapping[str, t.Any]] = None,
request_cache: t.Optional[bool] = None,
rescore: t.Optional[
t.Union[t.Mapping[str, t.Any], t.Sequence[t.Mapping[str, t.Any]]]
] = None,
rest_total_hits_as_int: t.Optional[bool] = None,
retriever: t.Optional[t.Mapping[str, t.Any]] = None,
routing: t.Optional[str] = None,
runtime_mappings: t.Optional[t.Mapping[str, t.Mapping[str, t.Any]]] = None,
script_fields: t.Optional[t.Mapping[str, t.Mapping[str, t.Any]]] = None,
scroll: t.Optional[t.Union[str, t.Literal[-1], t.Literal[0]]] = None,
search_after: t.Optional[
t.Sequence[t.Union[None, bool, float, int, str, t.Any]]
] = None,
search_type: t.Optional[
t.Union[str, t.Literal["dfs_query_then_fetch", "query_then_fetch"]]
] = None,
seq_no_primary_term: t.Optional[bool] = None,
size: t.Optional[int] = None,
slice: t.Optional[t.Mapping[str, t.Any]] = None,
sort: t.Optional[
t.Union[
t.Sequence[t.Union[str, t.Mapping[str, t.Any]]],
t.Union[str, t.Mapping[str, t.Any]],
]
] = None,
source: t.Optional[t.Union[bool, t.Mapping[str, t.Any]]] = None,
source_excludes: t.Optional[t.Union[str, t.Sequence[str]]] = None,
source_includes: t.Optional[t.Union[str, t.Sequence[str]]] = None,
stats: t.Optional[t.Sequence[str]] = None,
stored_fields: t.Optional[t.Union[str, t.Sequence[str]]] = None,
suggest: t.Optional[t.Mapping[str, t.Any]] = None,
suggest_field: t.Optional[str] = None,
suggest_mode: t.Optional[
t.Union[str, t.Literal["always", "missing", "popular"]]
] = None,
suggest_size: t.Optional[int] = None,
suggest_text: t.Optional[str] = None,
terminate_after: t.Optional[int] = None,
timeout: t.Optional[str] = None,
track_scores: t.Optional[bool] = None,
track_total_hits: t.Optional[t.Union[bool, int]] = None,
typed_keys: t.Optional[bool] = None,
version: t.Optional[bool] = None,
body: t.Optional[t.Dict[str, t.Any]] = None,
) -> ObjectApiResponse[t.Any]:

  • Are you certain that the only thing that changed is the order of parameters?
  • How does the search fail when the order is different?
  • Did you try printing them all in both cases to check they are the same?
  • Is there a way for me to reproduce your issue?

@pquentin pquentin added Area: Client Manually written code that fits in no other area Category: Question labels Nov 13, 2024
@pquentin
Copy link
Member

I'm going to go ahead and close this, but I would still be happy to know if you fixed your problem or not. And I can always reopen if needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: Client Manually written code that fits in no other area Category: Question
Projects
None yet
Development

No branches or pull requests

2 participants