Skip to content

Commit 0df65cc

Browse files
authored
Bug fixes and recursive searching for all matches (#2)
This implements: - a feature to search all fitting matches recursively - an endpoint `get_all_matches` of the server - a bug fix, changing the listen address from `127.0.0.1` to `0.0.0.0` to work inside of docker networks - a bug fix, to not empty the matches list and actually return all relevant matches
1 parent e33b1aa commit 0df65cc

6 files changed

Lines changed: 78 additions & 14 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ using Python.
99
docker build -t semantic_matching_service .
1010
```
1111
```commandline
12-
docker run -p 8000:8000 semantic_matching_service
12+
docker run -d -p 8000:8000 semantic_matching_service
1313
```

config.ini.default

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
[SERVICE]
22
endpoint=http://127.0.0.1
33
port=8000
4-
equivalence_table_file=./data/equivalence_table.json
4+
equivalence_table_file=./data/equivalence_table.json
5+
6+
[RESOLVER]
7+
endpoint=http://semantic_id_resolver
8+
port=8125

dockerfile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# Use the official Alpine Linux as the base image
22
FROM python:3.9-alpine
33

4+
# Install Git
5+
RUN apk update && apk add --no-cache git
6+
47
# Set the working directory in the container
58
WORKDIR /app
69

@@ -13,6 +16,9 @@ COPY . .
1316
# Install Python dependencies
1417
RUN pip install --no-cache-dir -r requirements.txt
1518

19+
# Set PYTHONPATH to the app directory
20+
ENV PYTHONPATH=/app
21+
1622
# Expose the port that FastAPI will run on
1723
EXPOSE 8000
1824

semantic_matcher/model.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ class EquivalenceTable(BaseModel):
2222

2323
def add_semantic_match(self, match: SemanticMatch) -> None:
2424
if self.matches.get(match.base_semantic_id) is not None:
25-
self.matches[match.base_semantic_id].append(match)
25+
if match not in self.matches[match.base_semantic_id]:
26+
self.matches[match.base_semantic_id].append(match)
2627
else:
2728
self.matches[match.base_semantic_id] = [match]
2829

@@ -32,16 +33,31 @@ def remove_semantic_match(self, match: SemanticMatch) -> None:
3233
if len(self.matches.get(match.base_semantic_id)) == 0:
3334
self.matches.pop(match.base_semantic_id)
3435

36+
def remove_all_semantic_matches(self):
37+
self.matches.clear()
38+
3539
def get_local_matches(self, semantic_id: str, score_limit: float) -> List[SemanticMatch]:
3640
equivalence_table_result = self.matches.get(semantic_id)
3741
if equivalence_table_result is None:
3842
return []
43+
matching_result = []
3944
for match in equivalence_table_result:
40-
matching_result = []
4145
if match.score > score_limit:
4246
matching_result.append(match)
47+
rec_result = self.get_local_matches(match.match_semantic_id, score_limit/match.score)
48+
for rec_match in rec_result:
49+
rec_match.base_semantic_id = match.base_semantic_id
50+
rec_match.score *= match.score
51+
if "path" not in rec_match.meta_information:
52+
rec_match.meta_information["path"] = []
53+
rec_match.meta_information["path"].insert(0, match.match_semantic_id)
54+
if rec_result is not None:
55+
matching_result += rec_result
4356
return matching_result
4457

58+
def get_all_matches(self) -> List[SemanticMatch]:
59+
return self.matches
60+
4561
def to_file(self, filename: str) -> None:
4662
with open(filename, "w") as file:
4763
file.write(self.model_dump_json(indent=4))

semantic_matcher/service.py

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
import requests
44
from fastapi import APIRouter
55

6-
import model
7-
import service_model
6+
from semantic_matcher import model, service_model
87

98

109
class SemanticMatchingService:
@@ -38,6 +37,12 @@ def __init__(
3837
equivalences that this :class:`~.SemanticMatchingService` contains.
3938
"""
4039
self.router = APIRouter()
40+
41+
self.router.add_api_route(
42+
"/all_matches",
43+
self.get_all_matches,
44+
methods=["GET"]
45+
)
4146
self.router.add_api_route(
4247
"/get_matches",
4348
self.get_matches,
@@ -48,9 +53,24 @@ def __init__(
4853
self.post_matches,
4954
methods=["POST"]
5055
)
56+
self.router.add_api_route(
57+
"/clear",
58+
self.remove_all_matches,
59+
methods=["POST"]
60+
)
5161
self.endpoint: str = endpoint
5262
self.equivalence_table: model.EquivalenceTable = equivalences
5363

64+
def get_all_matches(self):
65+
"""
66+
Returns all matches stored in the equivalence table-
67+
"""
68+
matches = self.equivalence_table.get_all_matches()
69+
return matches
70+
71+
def remove_all_matches(self):
72+
self.equivalence_table.remove_all_semantic_matches()
73+
5474
def get_matches(
5575
self,
5676
request_body: service_model.MatchRequest
@@ -71,7 +91,12 @@ def get_matches(
7191
# Now look for remote matches:
7292
additional_remote_matches: List[model.SemanticMatch] = []
7393
for match in matches:
94+
if match.base_semantic_id.split("/")[0] == match.match_semantic_id.split("/")[0]:
95+
#match_id is local
96+
continue
7497
remote_matching_service = self._get_matcher_from_semantic_id(match.match_semantic_id)
98+
if remote_matching_service is None:
99+
continue
75100
remote_matching_request = service_model.MatchRequest(
76101
semantic_id=match.match_semantic_id,
77102
# This is a simple "Ungleichung"
@@ -86,14 +111,14 @@ def get_matches(
86111
name=request_body.name,
87112
definition=request_body.definition
88113
)
89-
new_matches_response = requests.get(remote_matching_service, data=remote_matching_request)
90-
match_response: service_model.MatchesList = service_model.MatchesList.model_validate_json(
91-
new_matches_response.json()
92-
)
114+
url = f"{remote_matching_service}/get_matches"
115+
new_matches_response = requests.get(url, json=remote_matching_request.dict())
116+
match_response = service_model.MatchesList.model_validate_json(new_matches_response.text)
93117
additional_remote_matches.extend(match_response.matches)
94118
# Finally, put all matches together and return
95119
matches.extend(additional_remote_matches)
96-
return service_model.MatchesList(matches=matches)
120+
res = service_model.MatchesList(matches=matches)
121+
return res
97122

98123
def post_matches(
99124
self,
@@ -109,7 +134,20 @@ def _get_matcher_from_semantic_id(self, semantic_id: str) -> str:
109134
110135
:returns: The endpoint with which the `SemanticMatchingService` can be accessed
111136
"""
112-
return self.endpoint # todo
137+
request_body = {"semantic_id": semantic_id}
138+
endpoint = config['RESOLVER']['endpoint']
139+
port = config['RESOLVER'].getint('port')
140+
url = f"{endpoint}:{port}/get_semantic_matching_service"
141+
response = requests.get(url, json=request_body.dict())
142+
143+
# Check if the response is successful (status code 200)
144+
if response.status_code == 200:
145+
# Parse the JSON response and construct SMSResponse object
146+
response_json = response.json()
147+
response_endpoint = response_json['semantic_matching_service_endpoint']
148+
return response_endpoint
149+
150+
return None
113151

114152

115153
if __name__ == '__main__':
@@ -142,4 +180,4 @@ def _get_matcher_from_semantic_id(self, semantic_id: str) -> str:
142180
APP.include_router(
143181
SEMANTIC_MATCHING_SERVICE.router
144182
)
145-
uvicorn.run(APP, host="127.0.0.1", port=int(config["SERVICE"]["PORT"]))
183+
uvicorn.run(APP, host="0.0.0.0", port=int(config["SERVICE"]["PORT"]))

semantic_matcher/service_model.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from pydantic import BaseModel
44

5-
import model
5+
from semantic_matcher import model
66

77

88
class MatchRequest(BaseModel):

0 commit comments

Comments
 (0)