Skip to content

Commit

Permalink
Merge pull request #117 from dice-group/olwapy-serve
Browse files Browse the repository at this point in the history
Olwapy serve
  • Loading branch information
Demirrr authored Nov 27, 2024
2 parents 53462bd + d4b8a0b commit db1e03d
Show file tree
Hide file tree
Showing 3 changed files with 235 additions and 2 deletions.
128 changes: 128 additions & 0 deletions owlapy/scripts/owlapy_serve.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import os
import argparse
import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel
from owlapy.owl_ontology_manager import SyncOntologyManager
from owlapy.owl_reasoner import SyncReasoner
from owlapy.class_expression import OWLClass
from owlapy.static_funcs import stopJVM
from contextlib import asynccontextmanager
from enum import Enum

ontology = None
reasoner = None

class InferenceType(str, Enum):
InferredClassAssertionAxiomGenerator = "InferredClassAssertionAxiomGenerator"
InferredSubClassAxiomGenerator = "InferredSubClassAxiomGenerator"
InferredDisjointClassesAxiomGenerator = "InferredDisjointClassesAxiomGenerator"
InferredEquivalentClassAxiomGenerator = "InferredEquivalentClassAxiomGenerator"
InferredEquivalentDataPropertiesAxiomGenerator = "InferredEquivalentDataPropertiesAxiomGenerator"
InferredEquivalentObjectPropertyAxiomGenerator = "InferredEquivalentObjectPropertyAxiomGenerator"
InferredInverseObjectPropertiesAxiomGenerator = "InferredInverseObjectPropertiesAxiomGenerator"
InferredSubDataPropertyAxiomGenerator = "InferredSubDataPropertyAxiomGenerator"
InferredSubObjectPropertyAxiomGenerator = "InferredSubObjectPropertyAxiomGenerator"
InferredDataPropertyCharacteristicAxiomGenerator = "InferredDataPropertyCharacteristicAxiomGenerator"
InferredObjectPropertyCharacteristicAxiomGenerator = "InferredObjectPropertyCharacteristicAxiomGenerator"
All = "all"

class InfrenceTypeRequest(BaseModel):
inference_type: InferenceType

class ClassIRIRequest(BaseModel):
class_iri: str

class AxiomRequest(BaseModel):
axiom: str

def create_app(ontology_path: str, reasoner_name: str):
@asynccontextmanager
async def lifespan(app: FastAPI):
global ontology, reasoner
# Startup logic
# Load the ontology
if not os.path.exists(ontology_path):
raise FileNotFoundError(f"Ontology file not found at {ontology_path}")
ontology = SyncOntologyManager().load_ontology(ontology_path)

# Validate and initialize the reasoner
valid_reasoners = ['Pellet', 'HermiT', 'JFact', 'Openllet']
if reasoner_name not in valid_reasoners:
raise ValueError(f"Invalid reasoner '{reasoner_name}'. Valid options are: {', '.join(valid_reasoners)}")
reasoner = SyncReasoner(ontology=ontology, reasoner=reasoner_name)

yield
stopJVM()

app = FastAPI(title="OWLAPY API", lifespan=lifespan)

@app.post("/instances")
async def get_instances(request: ClassIRIRequest):
class_iri = request.class_iri
owl_class = OWLClass(class_iri)
instances = reasoner.instances(owl_class, direct=False)
instance_iris = [ind.__str__() for ind in instances]
return {"instances": instance_iris}

@app.get("/classes")
async def get_classes():
classes = [cls.__str__() for cls in ontology.classes_in_signature()]
return {"classes": classes}

@app.get("/object_properties")
async def get_object_properties():
object_properties = [op.__str__() for op in ontology.object_properties_in_signature()]
return {"object_properties": object_properties}

@app.get("/data_properties")
async def get_data_properties():
data_properties = [dp.__str__() for dp in ontology.data_properties_in_signature()]
return {"data_properties": data_properties}

@app.get("/individuals")
async def get_individuals():
individuals = [ind.__str__() for ind in ontology.individuals_in_signature()]
return {"individuals": individuals}

@app.get("/abox")
async def get_abox():
abox = ontology.get_abox_axioms()
return {"abox": [axiom.__str__() for axiom in abox]}

@app.get("/tbox")
async def get_tbox():
tbox = ontology.get_tbox_axioms()
return {"tbox": [axiom.__str__() for axiom in tbox]}

@app.post("/infer_axioms")
async def infer_axioms(request: InfrenceTypeRequest):
inference_type = request.inference_type
if inference_type == InferenceType.All:
inferred_axioms = []
for inference_type in {it for it in InferenceType if it != InferenceType.All}:
inferred_axioms.extend(reasoner.infer_axioms(inference_type.value))
else:
inferred_axioms = reasoner.infer_axioms(request.inference_type.value)

return {"inferred_axioms": [axiom.__str__() for axiom in inferred_axioms]}

return app

def main():
parser = argparse.ArgumentParser(description='Start OWLAPY API server.')
parser.add_argument('--path_kb', type=str, required=True,
help='Path to the ontology file')
parser.add_argument('--reasoner', type=str, default='HermiT',
help='Reasoner to use (Pellet, HermiT, JFact, Openllet)')
parser.add_argument('--host', type=str, default='0.0.0.0',
help='Host to listen on')
parser.add_argument('--port', type=int, default=8000,
help='Port to listen on')
args = parser.parse_args()

app = create_app(args.path_kb, args.reasoner)
uvicorn.run(app, host=args.host, port=args.port)

if __name__ == '__main__':
main()
7 changes: 5 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
"sortedcontainers>=2.4.0",
"owlready2>=0.40",
"JPype1>=1.5.0",
"tqdm>=4.66.5"],
"tqdm>=4.66.5",
"fastapi>=0.115.5",
"httpx>=0.27.2",
"uvicorn>=0.32.1"],
author='Caglar Demir',
author_email='[email protected]',
url='https://github.com/dice-group/owlapy',
Expand All @@ -29,7 +32,7 @@
"License :: OSI Approved :: MIT License",
"Topic :: Scientific/Engineering"],
python_requires='>=3.10.13',
entry_points={"console_scripts": ["owlapy=owlapy.scripts.run:main"]},
entry_points={"console_scripts": ["owlapy=owlapy.scripts.run:main", "owlapy-serve=owlapy.scripts.owlapy_serve:main"]},
long_description=long_description,
long_description_content_type="text/markdown",
)
102 changes: 102 additions & 0 deletions tests/test_owlapy_serve.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import pytest
from unittest.mock import patch
from fastapi.testclient import TestClient
from owlapy.owl_ontology_manager import SyncOntologyManager
from owlapy.owl_reasoner import SyncReasoner
from owlapy.class_expression import OWLClass
from owlapy.scripts.owlapy_serve import create_app
from owlapy.scripts.owlapy_serve import InferenceType

ontology_path = "KGs/Family/family-benchmark_rich_background.owl"
reasoner_name = "HermiT"
ontology = SyncOntologyManager().load_ontology(ontology_path)
reasoner = SyncReasoner(ontology=ontology, reasoner=reasoner_name)

@pytest.fixture()
def mock_stop_jvm():
patcher = patch("owlapy.scripts.owlapy_serve.stopJVM")
patcher.start()
yield
patcher.stop()

def test_get_classes (mock_stop_jvm):
with TestClient(create_app(ontology_path, reasoner_name)) as client:
response = client.get("/classes")
expected_classes = [cls.__str__() for cls in ontology.classes_in_signature()]
assert response.status_code == 200
assert set(response.json()["classes"]) == set(expected_classes)

def test_get_object_properties(mock_stop_jvm):
with TestClient(create_app(ontology_path, reasoner_name)) as client:
response = client.get("/object_properties")
expected_object_properties = [op.__str__() for op in ontology.object_properties_in_signature()]
assert response.status_code == 200
assert set(response.json()["object_properties"]) == set(expected_object_properties)

def test_get_data_properties(mock_stop_jvm):
with TestClient(create_app(ontology_path, reasoner_name)) as client:
response = client.get("/data_properties")
expected_data_properties = [dp.__str__() for dp in ontology.data_properties_in_signature()]
assert response.status_code == 200
assert set(response.json()["data_properties"]) == set(expected_data_properties)

def test_get_individuals(mock_stop_jvm):
with TestClient(create_app(ontology_path, reasoner_name)) as client:
response = client.get("/individuals")
expected_individuals = [ind.__str__() for ind in ontology.individuals_in_signature()]
assert response.status_code == 200
assert set(response.json()["individuals"]) == set(expected_individuals)

def test_get_abox(mock_stop_jvm):
with TestClient(create_app(ontology_path, reasoner_name)) as client:
response = client.get("/abox")
expected_abox = [axiom.__str__() for axiom in ontology.get_abox_axioms()]
assert response.status_code == 200
assert set(response.json()["abox"]) == set(expected_abox)

def test_get_tbox(mock_stop_jvm):
with TestClient(create_app(ontology_path, reasoner_name)) as client:
response = client.get("/tbox")
expected_tbox = [axiom.__str__() for axiom in ontology.get_tbox_axioms()]
assert response.status_code == 200
assert set(response.json()["tbox"]) == set(expected_tbox)

def test_get_instances(mock_stop_jvm):
with TestClient(create_app(ontology_path, reasoner_name)) as client:
test_class_iri = "http://www.benchmark.org/family#Child"
owl_class = OWLClass(test_class_iri)
expected_instances = [ind.__str__() for ind in reasoner.instances(owl_class, direct=False)]
response = client.post("/instances", json={"class_iri": test_class_iri})
assert response.status_code == 200
assert set(response.json()["instances"]) == set(expected_instances)

def test_infer_axioms(mock_stop_jvm):
with TestClient(create_app(ontology_path, reasoner_name)) as client:
valid_inference_type = "InferredClassAssertionAxiomGenerator"
response = client.post(
"/infer_axioms",
json={"inference_type": valid_inference_type}
)
assert response.status_code == 200
expected_axioms = [
axiom.__str__() for axiom in reasoner.infer_axioms(valid_inference_type)
]
assert set(response.json()["inferred_axioms"]) == set(expected_axioms)
invalid_inference_type = "InvalidAxiomGenerator"
response = client.post(
"/infer_axioms",
json={"inference_type": invalid_inference_type}
)
assert response.status_code == 422

def test_infer_axioms_all(mock_stop_jvm):
with TestClient(create_app(ontology_path, reasoner_name)) as client:
response = client.post("/infer_axioms", json={"inference_type": "all"})
assert response.status_code == 200

expected_axioms = []
for inference_type in InferenceType:
if inference_type != InferenceType.All:
expected_axioms.extend(reasoner.infer_axioms(inference_type.value))

assert set(response.json()["inferred_axioms"]) == set([axiom.__str__() for axiom in expected_axioms])

0 comments on commit db1e03d

Please sign in to comment.