Skip to content

Commit e33b1aa

Browse files
committed
First working version, Add dockerfile
1 parent 017c55a commit e33b1aa

7 files changed

Lines changed: 151 additions & 10 deletions

File tree

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Python Semantic Matching Service
2+
3+
This is a proof-of-concept implementation of a semantic matching service
4+
using Python.
5+
6+
## Docker
7+
8+
```commandline
9+
docker build -t semantic_matching_service .
10+
```
11+
```commandline
12+
docker run -p 8000:8000 semantic_matching_service
13+
```

config.ini.default

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[SERVICE]
2+
endpoint=http://127.0.0.1
3+
port=8000
4+
equivalence_table_file=./data/equivalence_table.json

data/equivalence_table.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"matches": {
3+
"s-heppner.com/semanticID/one": [
4+
{
5+
"base_semantic_id": "s-heppner.com/semanticID/one",
6+
"match_semantic_id": "s-heppner.com/semanticID/1",
7+
"score": 1.0,
8+
"meta_information": {
9+
"matchSource": "Defined by Sebastian Heppner"
10+
}
11+
},
12+
{
13+
"base_semantic_id": "s-heppner.com/semanticID/one",
14+
"match_semantic_id": "s-heppner.com/semanticID/two",
15+
"score": 0.8,
16+
"meta_information": {
17+
"matchSource": "Defined by Sebastian Heppner"
18+
}
19+
}
20+
],
21+
"s-heppner.com/semanticID/two": [
22+
{
23+
"base_semantic_id": "s-heppner.com/semanticID/two",
24+
"match_semantic_id": "s-heppner.com/semanticID/2",
25+
"score": 1.0,
26+
"meta_information": {
27+
"matchSource": "Defined by Sebastian Heppner"
28+
}
29+
}
30+
]
31+
}
32+
}

dockerfile

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Use the official Alpine Linux as the base image
2+
FROM python:3.9-alpine
3+
4+
# Set the working directory in the container
5+
WORKDIR /app
6+
7+
# Copy the package files to the working directory
8+
COPY . .
9+
10+
# Install system dependencies
11+
# RUN apk --no-cache add build-base libffi-dev
12+
13+
# Install Python dependencies
14+
RUN pip install --no-cache-dir -r requirements.txt
15+
16+
# Expose the port that FastAPI will run on
17+
EXPOSE 8000
18+
19+
# Command to run the FastAPI server
20+
CMD ["python", "semantic_matcher/service.py"]

semantic_matcher/examples/__init__.py

Whitespace-only changes.

semantic_matcher/service.py

Lines changed: 81 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import List, Optional
1+
from typing import List
22

33
import requests
44
from fastapi import APIRouter
@@ -9,21 +9,52 @@
99

1010
class SemanticMatchingService:
1111
"""
12-
Todo
12+
A Semantic Matching Service
13+
14+
It offers two operations:
15+
16+
:func:`~.SemanticMatchingService.post_matches` allows to post
17+
:class:`model.SemanticMatch`es to the :class:`~.SemanticMatchingService`.
18+
19+
:func:`~.SemanticMatchingService.get_matches` lets users get the
20+
:class:`model.SemanticMatch`es of the :class:`~.SemanticMatchingService`
21+
and the respective remote :class:`~.SemanticMatchingService`s.
22+
23+
Additionally, the internal function
24+
:func:`~.SemanticMatchingService._get_matcher_from_semantic_id` lets the
25+
:class:`~.SemanticMatchingService` find the suiting remote
26+
:class:`~.SemanticMatchingService`s to a given `semantic_id`.
1327
"""
14-
def __init__(self, equivalences: model.EquivalenceTable):
28+
def __init__(
29+
self,
30+
endpoint: str,
31+
equivalences: model.EquivalenceTable
32+
):
33+
"""
34+
Initializer of :class:`~.SemanticMatchingService`
35+
36+
:ivar endpoint: The endpoint on which the service listens
37+
:ivar equivalences: The :class:`model.EquivalenceTable` of the semantic
38+
equivalences that this :class:`~.SemanticMatchingService` contains.
39+
"""
1540
self.router = APIRouter()
1641
self.router.add_api_route(
17-
"/get_match",
42+
"/get_matches",
1843
self.get_matches,
1944
methods=["GET"]
2045
)
46+
self.router.add_api_route(
47+
"/post_matches",
48+
self.post_matches,
49+
methods=["POST"]
50+
)
51+
self.endpoint: str = endpoint
2152
self.equivalence_table: model.EquivalenceTable = equivalences
2253

2354
def get_matches(
2455
self,
2556
request_body: service_model.MatchRequest
26-
) -> service_model.MatchResponse:
57+
) -> service_model.MatchesList:
2758
"""
2859
A query to match two SubmodelElements semantically.
2960
@@ -36,7 +67,7 @@ def get_matches(
3667
)
3768
# If the request asks us to only locally look, we're done already
3869
if request_body.local_only:
39-
return service_model.MatchResponse(matches=matches)
70+
return service_model.MatchesList(matches=matches)
4071
# Now look for remote matches:
4172
additional_remote_matches: List[model.SemanticMatch] = []
4273
for match in matches:
@@ -56,18 +87,59 @@ def get_matches(
5687
definition=request_body.definition
5788
)
5889
new_matches_response = requests.get(remote_matching_service, data=remote_matching_request)
59-
match_response: service_model.MatchResponse = service_model.MatchResponse.model_dump_json(
90+
match_response: service_model.MatchesList = service_model.MatchesList.model_validate_json(
6091
new_matches_response.json()
6192
)
6293
additional_remote_matches.extend(match_response.matches)
6394
# Finally, put all matches together and return
6495
matches.extend(additional_remote_matches)
65-
return service_model.MatchResponse(matches=matches)
96+
return service_model.MatchesList(matches=matches)
97+
98+
def post_matches(
99+
self,
100+
request_body: service_model.MatchesList
101+
) -> None:
102+
for match in request_body.matches:
103+
self.equivalence_table.add_semantic_match(match)
104+
# Todo: Figure out how to properly return 200
66105

67106
def _get_matcher_from_semantic_id(self, semantic_id: str) -> str:
68107
"""
69108
Finds the suiting `SemanticMatchingService` for the given `semantic_id`.
70109
71110
:returns: The endpoint with which the `SemanticMatchingService` can be accessed
72111
"""
73-
return "Todo" # todo
112+
return self.endpoint # todo
113+
114+
115+
if __name__ == '__main__':
116+
import os
117+
import configparser
118+
from fastapi import FastAPI
119+
import uvicorn
120+
121+
config = configparser.ConfigParser()
122+
config.read([
123+
os.path.abspath(os.path.join(os.path.dirname(__file__), "../config.ini.default")),
124+
os.path.abspath(os.path.join(os.path.dirname(__file__), "../config.ini")),
125+
])
126+
127+
# Read in equivalence table
128+
# Note, this construct takes the path in the config.ini relative to the
129+
# location of the config.ini
130+
EQUIVALENCES = model.EquivalenceTable.from_file(
131+
filename=os.path.abspath(os.path.join(
132+
os.path.dirname(__file__),
133+
"..",
134+
config["SERVICE"]["equivalence_table_file"]
135+
))
136+
)
137+
SEMANTIC_MATCHING_SERVICE = SemanticMatchingService(
138+
endpoint=config["SERVICE"]["endpoint"],
139+
equivalences=EQUIVALENCES
140+
)
141+
APP = FastAPI()
142+
APP.include_router(
143+
SEMANTIC_MATCHING_SERVICE.router
144+
)
145+
uvicorn.run(APP, host="127.0.0.1", port=int(config["SERVICE"]["PORT"]))

semantic_matcher/service_model.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,5 @@ class MatchRequest(BaseModel):
2121
definition: Optional[str] = None
2222

2323

24-
class MatchResponse(BaseModel):
24+
class MatchesList(BaseModel):
2525
matches: List[model.SemanticMatch]

0 commit comments

Comments
 (0)