Skip to content

Commit 1448039

Browse files
[DAGE-20] added ElasticSearch support
- updated SearchEngineFactory with elasticsearch support - created elasticsearch_search_engine.py file with the class needed for the integration and related unit test file - changed the names of the files used for test in the resources folder - added eleasticsearch-init folder with test environment for elasticsearch
1 parent 21da275 commit 1448039

12 files changed

Lines changed: 709 additions & 3 deletions
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import json
2+
import requests
3+
from urllib.parse import urljoin
4+
from pydantic import HttpUrl
5+
from requests.exceptions import HTTPError, ConnectionError, Timeout, RequestException
6+
from typing import List, Dict, Any, Union
7+
8+
from requests import Response
9+
10+
from src.utils import clean_text
11+
import logging
12+
13+
log = logging.getLogger(__name__)
14+
15+
from src.search_engine.search_engine_base import BaseSearchEngine
16+
from src.model.document import Document
17+
18+
class ElasticsearchSearchEngine(BaseSearchEngine):
19+
"""
20+
Elasticsearch implementation to search into a given collection
21+
"""
22+
def __init__(self, endpoint: HttpUrl | str):
23+
super().__init__(endpoint)
24+
self.HEADERS = {'Content-Type': 'application/json'}
25+
log.debug(f"Working on endpoint: {self.endpoint}")
26+
self.UNIQUE_KEY = "_id"
27+
28+
def fetch_for_query_generation(self,
29+
documents_filter: Union[None, List[Dict[str, List[str]]]],
30+
doc_number: int,
31+
doc_fields: List[str]) -> List[Document]:
32+
33+
# Build base query
34+
query = {"match_all": {}}
35+
36+
# Add filters, if provided
37+
filter_clauses = []
38+
if documents_filter is not None:
39+
for dict_field in documents_filter:
40+
for field, values in dict_field.items():
41+
if not values:
42+
continue
43+
if len(values) == 1:
44+
filter_clauses.append({"term": {field: values[0]}})
45+
else:
46+
filter_clauses.append({"terms": {field: values}})
47+
48+
# Wrap in a bool query if there are any filters
49+
if filter_clauses:
50+
query = {
51+
"bool": {
52+
"must": {"match_all": {}},
53+
"filter": filter_clauses
54+
}
55+
}
56+
57+
# Construct the payload (Elasticsearch query body)
58+
payload = {
59+
"size": doc_number,
60+
"query": query,
61+
"_source": doc_fields
62+
}
63+
64+
return self._search(payload)
65+
66+
def fetch_for_evaluation(self, query_template: str, doc_fields: List[str], keyword: str=None) -> List[Document]:
67+
"""Search for documents using a query."""
68+
if keyword:
69+
payload = json.loads(query_template.replace(self.PLACEHOLDER, keyword))
70+
else:
71+
payload = {
72+
"query": {"match_all": {}}
73+
}
74+
payload["_source"] = doc_fields
75+
return self._search(payload)
76+
77+
def _search(self, payload: Dict[str, Any]) -> List[Document]:
78+
"""Search for documents using a query."""
79+
search_url = urljoin(self.endpoint.encoded_string(), '_search')
80+
81+
try:
82+
response = requests.post(search_url, headers=self.HEADERS, json=payload)
83+
except ConnectionError as e:
84+
log.error(f"Connection failed while accessing {search_url}\nError: {e}")
85+
raise ConnectionError(f"Connection failed while accessing {search_url}\nError: {e}")
86+
except Timeout as e:
87+
log.error(f"Request to {search_url} timed out\nError: {e}")
88+
raise Timeout(f"Request to {search_url} timed out\nError: {e}")
89+
except RequestException as e:
90+
log.error(f"Unexpected error during request to {search_url}\nError: {e}")
91+
raise RequestException(f"Unexpected error during request to {search_url}\nError: {e}")
92+
93+
match response.status_code:
94+
case 200:
95+
log.debug("Elasticsearch query successful.")
96+
log.debug(f"URL: {search_url}")
97+
log.debug(f"Payload: {payload}")
98+
# log.debug(f"Response: {response.json()}")
99+
raw_docs = response.json()['hits']['hits']
100+
reformat_raw_doc = []
101+
for doc in raw_docs:
102+
clean_doc = dict()
103+
clean_doc['id'] = doc[self.UNIQUE_KEY]
104+
clean_doc['fields'] = doc['_source']
105+
reformat_raw_doc.append(Document(**clean_doc))
106+
return reformat_raw_doc
107+
case 400:
108+
error_msg = f"400 Bad Request: The request was invalid.\nURL: {search_url}\nPayload: {payload}"
109+
case 401:
110+
error_msg = f"401 Unauthorized: Authentication is required.\nURL: {search_url}"
111+
case 403:
112+
error_msg = f"403 Forbidden: Access is denied.\nURL: {search_url}"
113+
case 404:
114+
error_msg = f"404 Not Found: The Elasticsearch endpoint was not found.\nURL: {search_url}"
115+
case 500:
116+
error_msg = f"500 Internal Server Error: Elasticsearch encountered a problem.\nURL: {search_url}"
117+
case _:
118+
error_msg = f"Unexpected status code {response.status_code}.\nURL: {search_url}\nPayload: {payload}"
119+
log.error(error_msg)
120+
raise HTTPError(error_msg)

rre-dataset-generator/src/search_engine/search_engine_factory.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22

33
from .search_engine_base import BaseSearchEngine
44
from .solr_search_engine import SolrSearchEngine
5+
from .elasticsearch_search_engine import ElasticsearchSearchEngine
6+
57
import logging
68

79
log = logging.getLogger(__name__)
810

911
class SearchEngineFactory:
1012
SEARCH_ENGINE_REGISTRY = {
1113
"solr": SolrSearchEngine,
14+
"elasticsearch": ElasticsearchSearchEngine
1215
}
1316
@classmethod
1417
def build(cls, search_engine_type: str, endpoint: HttpUrl) -> BaseSearchEngine:
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
services:
2+
elasticsearch:
3+
image: docker.elastic.co/elasticsearch/elasticsearch:9.0.4
4+
container_name: elasticsearch_test
5+
ports:
6+
- "9200:9200"
7+
environment:
8+
- discovery.type=single-node
9+
- xpack.security.enabled=false
10+
- xpack.security.enrollment.enabled=false
11+
12+
eslasticsearch-init:
13+
build: ./elasticsearch-init
14+
depends_on:
15+
- elasticsearch
16+
command: ["sh", "-c", "/opt/rre-dataset-generator/elasticsearch-init.sh"]

rre-dataset-generator/tests/integration/docker-compose.yml renamed to rre-dataset-generator/tests/integration/docker-compose-solr.yml

File renamed without changes.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
FROM docker.elastic.co/elasticsearch/elasticsearch:9.0.4
2+
3+
COPY data/dataset.jsonl /opt/rre-dataset-generator/data/dataset.jsonl
4+
COPY --chmod=777 elasticsearch-init.sh /opt/rre-dataset-generator/elasticsearch-init.sh

0 commit comments

Comments
 (0)