Skip to content

Commit

Permalink
[8.4] Add custom API method for Elasticsearch Search API
Browse files Browse the repository at this point in the history
Co-authored-by: Seth Michael Larson <seth.larson@elastic.co>
  • Loading branch information
github-actions[bot] and sethmlarson committed Aug 9, 2022
1 parent 530f258 commit 1007ec5
Show file tree
Hide file tree
Showing 4 changed files with 243 additions and 72 deletions.
79 changes: 43 additions & 36 deletions elastic_enterprise_search/_async/client/app_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,49 @@


class AsyncAppSearch(BaseClient):
@_rewrite_parameters(body_name="body", ignore_deprecated_options={"body", "params"})
async def search_es_search(
self,
*,
engine_name: str,
params: t.Optional[t.Mapping[str, t.Any]] = None,
body: t.Optional[t.Mapping[str, t.Any]] = None,
analytics_query: t.Optional[str] = None,
analytics_tags: t.Optional[t.Union[str, t.Sequence[str]]] = None,
) -> ObjectApiResponse[t.Any]:
"""
Execute the provided Elasticsearch search query against an App Search Engine
`<https://www.elastic.co/guide/en/app-search/current/elasticsearch-search-api-reference.html>`_
:param engine_name: Name of the engine
:param params: Query parameters for the Elasticsearch Search request.
:param body: Body parameters for the Elasticsearch Search request.
:param analytics_query: The search query associated with this request when recording search analytics.
:param analytics_tags: The tags to apply to the query when recording search analytics.
"""
if engine_name in SKIP_IN_PATH:
raise ValueError("Empty value passed for parameter 'engine_name'")

if params is not None and any(not isinstance(x, str) for x in params.values()):
raise TypeError("Values for 'params' parameter must be of type 'str'")

__headers = {"accept": "application/json", "content-type": "application/json"}
if analytics_query is not None:
__headers["X-Enterprise-Search-Analytics"] = analytics_query
if analytics_tags is not None:
if isinstance(analytics_tags, str):
analytics_tags = (analytics_tags,)
__headers["X-Enterprise-Search-Analytics-Tags"] = ",".join(analytics_tags)

return await self.perform_request( # type: ignore[return-value]
"POST",
f"/api/as/v0/engines/{_quote(engine_name)}/elasticsearch/_search",
params=params,
body=body,
headers=__headers,
)

# AUTO-GENERATED-API-DEFINITIONS #

@_rewrite_parameters()
Expand Down Expand Up @@ -2132,42 +2175,6 @@ async def add_meta_engine_source(
headers=__headers,
)

@_rewrite_parameters(
body_fields=True,
)
async def search_es_search(
self,
*,
engine_name: str,
request: t.Mapping[str, t.Any],
analytics: t.Optional[t.Mapping[str, t.Any]] = None,
) -> ObjectApiResponse[t.Any]:
"""
Execute the provided Elasticsearch search query against an App Search Engine
`<https://www.elastic.co/guide/en/app-search/current/elasticsearch-search-api-reference.html>`_
:param engine_name: Name of the engine
:param request:
:param analytics:
"""
if engine_name in SKIP_IN_PATH:
raise ValueError("Empty value passed for parameter 'engine_name'")
if request is None:
raise ValueError("Empty value passed for parameter 'request'")
__body: t.Dict[str, t.Any] = {}
if request is not None:
__body["request"] = request
if analytics is not None:
__body["analytics"] = analytics
__headers = {"accept": "application/json", "content-type": "application/json"}
return await self.perform_request( # type: ignore[return-value]
"POST",
f"/api/as/v0/engines/{_quote(engine_name)}/elasticsearch/_search",
body=__body,
headers=__headers,
)

@_rewrite_parameters(
body_fields=True,
)
Expand Down
79 changes: 43 additions & 36 deletions elastic_enterprise_search/_sync/client/app_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,49 @@


class AppSearch(BaseClient):
@_rewrite_parameters(body_name="body", ignore_deprecated_options={"body", "params"})
def search_es_search(
self,
*,
engine_name: str,
params: t.Optional[t.Mapping[str, t.Any]] = None,
body: t.Optional[t.Mapping[str, t.Any]] = None,
analytics_query: t.Optional[str] = None,
analytics_tags: t.Optional[t.Union[str, t.Sequence[str]]] = None,
) -> ObjectApiResponse[t.Any]:
"""
Execute the provided Elasticsearch search query against an App Search Engine
`<https://www.elastic.co/guide/en/app-search/current/elasticsearch-search-api-reference.html>`_
:param engine_name: Name of the engine
:param params: Query parameters for the Elasticsearch Search request.
:param body: Body parameters for the Elasticsearch Search request.
:param analytics_query: The search query associated with this request when recording search analytics.
:param analytics_tags: The tags to apply to the query when recording search analytics.
"""
if engine_name in SKIP_IN_PATH:
raise ValueError("Empty value passed for parameter 'engine_name'")

if params is not None and any(not isinstance(x, str) for x in params.values()):
raise TypeError("Values for 'params' parameter must be of type 'str'")

__headers = {"accept": "application/json", "content-type": "application/json"}
if analytics_query is not None:
__headers["X-Enterprise-Search-Analytics"] = analytics_query
if analytics_tags is not None:
if isinstance(analytics_tags, str):
analytics_tags = (analytics_tags,)
__headers["X-Enterprise-Search-Analytics-Tags"] = ",".join(analytics_tags)

return self.perform_request( # type: ignore[return-value]
"POST",
f"/api/as/v0/engines/{_quote(engine_name)}/elasticsearch/_search",
params=params,
body=body,
headers=__headers,
)

# AUTO-GENERATED-API-DEFINITIONS #

@_rewrite_parameters()
Expand Down Expand Up @@ -2132,42 +2175,6 @@ def add_meta_engine_source(
headers=__headers,
)

@_rewrite_parameters(
body_fields=True,
)
def search_es_search(
self,
*,
engine_name: str,
request: t.Mapping[str, t.Any],
analytics: t.Optional[t.Mapping[str, t.Any]] = None,
) -> ObjectApiResponse[t.Any]:
"""
Execute the provided Elasticsearch search query against an App Search Engine
`<https://www.elastic.co/guide/en/app-search/current/elasticsearch-search-api-reference.html>`_
:param engine_name: Name of the engine
:param request:
:param analytics:
"""
if engine_name in SKIP_IN_PATH:
raise ValueError("Empty value passed for parameter 'engine_name'")
if request is None:
raise ValueError("Empty value passed for parameter 'request'")
__body: t.Dict[str, t.Any] = {}
if request is not None:
__body["request"] = request
if analytics is not None:
__body["analytics"] = analytics
__headers = {"accept": "application/json", "content-type": "application/json"}
return self.perform_request( # type: ignore[return-value]
"POST",
f"/api/as/v0/engines/{_quote(engine_name)}/elasticsearch/_search",
body=__body,
headers=__headers,
)

@_rewrite_parameters(
body_fields=True,
)
Expand Down
49 changes: 49 additions & 0 deletions tests/client/app_search/cassettes/test_search_es_search.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
interactions:
- request:
body: '{"query":{"match":{"*":"client"}}}'
headers:
accept:
- application/json
authorization:
- Bearer private-ybzoyx7cok65hpxyxkwaarnn
connection:
- keep-alive
content-type:
- application/json
method: POST
uri: https://my-deployment-c6095a.ent.us-central1.gcp.cloud.es.io/api/as/v0/engines/elastic-docs/elasticsearch/_search?sort=_score
response:
body:
string: '{"took":0,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":{"value":0,"relation":"eq"},"max_score":null,"hits":[]}}'
headers:
Cache-Control:
- max-age=0, private, must-revalidate
Content-Length:
- '160'
Content-Type:
- application/json;charset=utf-8
Date:
- Mon, 08 Aug 2022 19:44:31 GMT
Etag:
- W/"95041d5366989a0ed1304624d63355eb"
Server:
- Jetty(9.4.43.v20210629)
Vary:
- Origin
- Accept-Encoding, User-Agent
X-App-Search-Version:
- 8.4.0
X-Cloud-Request-Id:
- opbW3tOmRSOqGzamTfbLOQ
X-Found-Handling-Cluster:
- c99499ef963b4d11a9312e341f6d32cb
X-Found-Handling-Instance:
- instance-0000000001
X-Request-Id:
- opbW3tOmRSOqGzamTfbLOQ
X-Runtime:
- '0.084247'
status:
code: 200
message: OK
version: 1
108 changes: 108 additions & 0 deletions tests/client/app_search/test_search_es_search.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Licensed to Elasticsearch B.V. under one or more contributor
# license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright
# ownership. Elasticsearch B.V. licenses this file to you under
# the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

import pytest
from elastic_transport.client_utils import DEFAULT

from elastic_enterprise_search import AppSearch
from tests.conftest import DummyNode


def test_mock_request():
client = AppSearch(node_class=DummyNode, meta_header=False)
client.search_es_search(
engine_name="test",
params={"key": "val"},
body={"k": ["v", 2]},
analytics_query="analytics-query",
)

calls = client.transport.node_pool.get().calls
assert len(calls) == 1
assert calls[-1][1].pop("request_timeout") is DEFAULT
assert calls[-1] == (
(
"POST",
"/api/as/v0/engines/test/elasticsearch/_search?key=val",
),
{
"body": b'{"k":["v",2]}',
"headers": {
"accept": "application/json",
"content-type": "application/json",
"x-enterprise-search-analytics": "analytics-query",
},
},
)


@pytest.mark.parametrize("analytics_tags", ["a,b", ["a", "b"]])
def test_analytics_tags(analytics_tags):
client = AppSearch(node_class=DummyNode, meta_header=False)
client.options(headers={"Extra": "value"}).search_es_search(
engine_name="test", analytics_tags=analytics_tags
)

calls = client.transport.node_pool.get().calls
assert len(calls) == 1
assert calls[-1][1].pop("request_timeout") is DEFAULT
assert calls[-1] == (
(
"POST",
"/api/as/v0/engines/test/elasticsearch/_search",
),
{
"body": None,
"headers": {
"extra": "value",
"accept": "application/json",
"content-type": "application/json",
"x-enterprise-search-analytics-tags": "a,b",
},
},
)


@pytest.mark.parametrize("param_value", [object(), 1, 2.0, (), [3]])
def test_search_es_search_params_type_error(param_value):
client = AppSearch(node_class=DummyNode)

with pytest.raises(TypeError) as e:
client.search_es_search(
engine_name="test",
params={"key": param_value},
)
assert str(e.value) == "Values for 'params' parameter must be of type 'str'"


@pytest.mark.vcr()
def test_search_es_search(app_search):
resp = app_search.search_es_search(
engine_name="elastic-docs",
params={"sort": "_score"},
body={"query": {"match": {"*": "client"}}},
)
assert resp.body == {
"took": 0,
"timed_out": False,
"_shards": {"total": 1, "successful": 1, "skipped": 0, "failed": 0},
"hits": {
"total": {"value": 0, "relation": "eq"},
"max_score": None,
"hits": [],
},
}

0 comments on commit 1007ec5

Please sign in to comment.