From 037f3d6089de7cea676739616f9f37f7fb5e7cef Mon Sep 17 00:00:00 2001 From: WerLaj Date: Tue, 9 Jan 2024 16:05:11 +0100 Subject: [PATCH 01/31] Test with KG visualzation tools --- pkg_api/connector.py | 16 ++++++++++++++++ pkg_api/pkg.py | 2 ++ 2 files changed, 18 insertions(+) diff --git a/pkg_api/connector.py b/pkg_api/connector.py index a95f976..8618411 100644 --- a/pkg_api/connector.py +++ b/pkg_api/connector.py @@ -1,8 +1,12 @@ """Connector to triplestore.""" +import io from enum import Enum +import pydotplus +from IPython.display import Image, display from rdflib import Graph from rdflib.query import Result +from rdflib.tools.rdf2dot import rdf2dot from pkg_api.pkg_types import URI @@ -37,6 +41,18 @@ def __init__( self._graph = Graph(rdf_store.value, identifier=owner) self._graph.open(rdf_store_path, create=True) + def visualize(self): + # https://stackoverflow.com/questions/39274216/visualize-an-rdflib-graph-in-python + stream = io.StringIO() + rdf2dot(self._graph, stream, opts = {display}) + dg = pydotplus.graph_from_dot_data(stream.getvalue()) + png = dg.create_png() + print(type(png)) + print(type(Image(png))) + with open("test.png", "wb") as test_png: + test_png.write(png) + + def execute_sparql_query(self, query: str) -> Result: """Execute SPARQL query. diff --git a/pkg_api/pkg.py b/pkg_api/pkg.py index b4070bf..0611fa8 100644 --- a/pkg_api/pkg.py +++ b/pkg_api/pkg.py @@ -238,3 +238,5 @@ def remove_owner_fact(self, predicate: URI, entity: URI) -> None: print(pkg.get_owner_preference("http://example.org/coffee")) print(pkg.get_owner_preference("http://example.org/tea")) + + pkg._connector.visualize() From 5d827960515e474aa8c6f7c0bf6367d2918bf3b8 Mon Sep 17 00:00:00 2001 From: WerLaj Date: Thu, 11 Jan 2024 21:28:49 +0100 Subject: [PATCH 02/31] Draft impelmentation of backend for visualizing PKG --- pkg_api/connector.py | 34 +++++----- pkg_api/pkg.py | 41 +++++++++++- pkg_api/pkg_visualizations/README.md | 1 + pkg_api/server/__init__.py | 10 ++- pkg_api/server/pkg_exploration.py | 57 +++++++++++++++-- pkg_api/server/utils.py | 56 +++++++++++++++++ tests/pkg_api/server/conftest.py | 4 +- tests/pkg_api/server/test_pkg_exploration.py | 66 ++++++++++++++++++-- 8 files changed, 239 insertions(+), 30 deletions(-) create mode 100644 pkg_api/pkg_visualizations/README.md create mode 100644 pkg_api/server/utils.py diff --git a/pkg_api/connector.py b/pkg_api/connector.py index 8618411..8ef591d 100644 --- a/pkg_api/connector.py +++ b/pkg_api/connector.py @@ -1,12 +1,9 @@ """Connector to triplestore.""" -import io +import os from enum import Enum -import pydotplus -from IPython.display import Image, display -from rdflib import Graph +from rdflib import Graph, Namespace from rdflib.query import Result -from rdflib.tools.rdf2dot import rdf2dot from pkg_api.pkg_types import URI @@ -14,6 +11,7 @@ # Method to execute the SPARQL query DEFAULT_STORE_PATH = "data/RDFStore" +DEFAULT_PKG_NAMESPACE = Namespace("http://example.com/pkg") class RDFStore(Enum): @@ -38,21 +36,13 @@ def __init__( rdf_store: Type of RDF store to use. rdf_store_path: Path to the RDF store. """ + self._rdf_store_path = f"{rdf_store_path}.ttl" self._graph = Graph(rdf_store.value, identifier=owner) + self._graph.bind("pkg", DEFAULT_PKG_NAMESPACE) + if os.path.exists(self._rdf_store_path): + self._graph.parse(self._rdf_store_path, format="turtle") self._graph.open(rdf_store_path, create=True) - def visualize(self): - # https://stackoverflow.com/questions/39274216/visualize-an-rdflib-graph-in-python - stream = io.StringIO() - rdf2dot(self._graph, stream, opts = {display}) - dg = pydotplus.graph_from_dot_data(stream.getvalue()) - png = dg.create_png() - print(type(png)) - print(type(Image(png))) - with open("test.png", "wb") as test_png: - test_png.write(png) - - def execute_sparql_query(self, query: str) -> Result: """Execute SPARQL query. @@ -70,5 +60,15 @@ def execute_sparql_update(self, query: str) -> None: self._graph.update(query) def close(self) -> None: + """Closes the connection to the triplestore.""" + self.save_graph() + self._graph.close() + + def save_graph(self) -> None: + """Saves the graph to a file.""" + if not os.path.exists(self._rdf_store_path): + parent_dir = os.path.dirname(self._rdf_store_path) + os.makedirs(parent_dir, exist_ok=True) + self._graph.serialize(self._rdf_store_path, format="turtle") """Close the connection to the triplestore.""" self._graph.close() diff --git a/pkg_api/pkg.py b/pkg_api/pkg.py index 0611fa8..0229f2f 100644 --- a/pkg_api/pkg.py +++ b/pkg_api/pkg.py @@ -16,14 +16,21 @@ this representation is left to the utils class. """ -from typing import Dict, List, Optional +import io +from typing import Any, Dict, List, Optional +import numpy as np +import pydotplus +from IPython.display import Image, display +from rdflib.query import Result from rdflib.term import Variable +from rdflib.tools.rdf2dot import rdf2dot import pkg_api.utils as utils from pkg_api.connector import Connector, RDFStore from pkg_api.pkg_types import URI +NS = "http://example.org/pkg/" class PKG: def __init__(self, owner: URI, rdf_store: RDFStore, rdf_path: str) -> None: @@ -211,6 +218,37 @@ def remove_owner_fact(self, predicate: URI, entity: URI) -> None: """ self.remove_fact(self._owner_uri, predicate, entity) + def execute_sparql_query(self, query: str) -> Result: + """Executes a SPARQL query. + + Args: + query: SPARQL query. + """ + self._connector.execute_sparql_query(query) + + def visualize_graph(self) -> str: + """Visualizes the PKG. + + https://stackoverflow.com/questions/39274216/visualize-an-rdflib-graph-in-python + + Args: + pkg: PKG to visualize. + + Returns: + The image visualizing the PKG. + """ + stream = io.StringIO() + rdf2dot(self._connector._graph, stream, opts={display}) + dg = pydotplus.graph_from_dot_data(stream.getvalue()) + png = dg.create_png() + + path = "pkg_api/pkg_visualizations/" + self._owner_uri.replace(NS, "") + ".png" + + with open(path, "wb") as test_png: + test_png.write(png) + + return path + if __name__ == "__main__": pkg = PKG("http://example.org/user1", RDFStore.MEMORY, "data/RDFStore") @@ -239,4 +277,3 @@ def remove_owner_fact(self, predicate: URI, entity: URI) -> None: print(pkg.get_owner_preference("http://example.org/tea")) - pkg._connector.visualize() diff --git a/pkg_api/pkg_visualizations/README.md b/pkg_api/pkg_visualizations/README.md new file mode 100644 index 0000000..748ea57 --- /dev/null +++ b/pkg_api/pkg_visualizations/README.md @@ -0,0 +1 @@ +# PKG Visualizations \ No newline at end of file diff --git a/pkg_api/server/__init__.py b/pkg_api/server/__init__.py index ea1e08c..97c38e1 100644 --- a/pkg_api/server/__init__.py +++ b/pkg_api/server/__init__.py @@ -6,19 +6,27 @@ from flask import Flask from flask_restful import Api +from pkg_api.connector import DEFAULT_STORE_PATH from pkg_api.server.auth import AuthResource from pkg_api.server.facts_management import PersonalFactsResource from pkg_api.server.pkg_exploration import PKGExplorationResource from pkg_api.server.service_management import ServiceManagementResource -def create_app() -> Flask: +def create_app(testing: bool = False) -> Flask: """Create the Flask app and add the API resources. Returns: The Flask app. """ app = Flask(__name__) + + if testing: + app.config["TESTING"] = True + app.config["STORE_PATH"] = "tests/data/RDFStore" + else: + app.config["STORE_PATH"] = DEFAULT_STORE_PATH + api = Api(app) api.add_resource(AuthResource, "/auth") diff --git a/pkg_api/server/pkg_exploration.py b/pkg_api/server/pkg_exploration.py index 3e011da..9f06baf 100644 --- a/pkg_api/server/pkg_exploration.py +++ b/pkg_api/server/pkg_exploration.py @@ -1,10 +1,59 @@ """PKG Exploration Resource.""" -from typing import Dict +from typing import Any, Dict, Tuple +from flask import request from flask_restful import Resource +from pkg_api.server.utils import open_pkg, parse_query_request_data + class PKGExplorationResource(Resource): - def get(self) -> Dict[str, str]: - """Returns the data for PKG exploration.""" - return {"message": "PKG Exploration"} + def get(self) -> Tuple[Dict[str, Any], int]: + """Returns the PKG visualization. + + Returns: + A dictionary with the PKG visualization and the HTTP status code. + """ + data = request.json + try: + pkg = open_pkg(data) + except Exception as e: + return {"message": str(e)}, 400 + + graph_img_path = pkg.visualize_graph() + pkg.close() + + return { + "message": "PKG visualized successfully.", + "img_path": graph_img_path, + }, 200 + + def post(self) -> Tuple[Dict[str, Any], int]: + """Executes the SPARQL query and visualizes the resulting PKG. + + Returns: + A dictionary with the resulting PKG, its visualization, and the HTTP + status code. + """ + + data = request.json + try: + pkg = open_pkg(data) + except Exception as e: + return {"message": str(e)}, 400 + + sparql_query = parse_query_request_data(data) + + if "SELECT" in sparql_query: + result = pkg.execute_sparql_query(sparql_query) + else: + return { + "message": "Operation is not supported. Provide SPARQL select query." + }, 400 + + pkg.close() + + return { + "message": "SPARQL query executed successfully.", + "data": result, + }, 200 diff --git a/pkg_api/server/utils.py b/pkg_api/server/utils.py new file mode 100644 index 0000000..e469f47 --- /dev/null +++ b/pkg_api/server/utils.py @@ -0,0 +1,56 @@ +"""Utility functions for the server.""" +from typing import Any, Dict, Tuple + +from flask import current_app + +from pkg_api.connector import RDFStore +from pkg_api.pkg import PKG + + +def open_pkg(data: Dict[str, str]) -> PKG: + """Opens a connection to the PKG. + Args: + data: Request data. + Returns: + A PKG instance. + """ + owner_uri = data.get("owner_uri", None) + owner_username = data.get("owner_username", None) + if owner_uri is None: + raise Exception("Missing owner URI") + + store_path = current_app.config["STORE_PATH"] + + return PKG( + owner_uri, + RDFStore.MEMORY, + f"{store_path}/{owner_username}", + ) + + +def parse_pouplation_request_data( + data: Dict[str, Any] +) -> Tuple[str, str, str, float]: + """Parses the request data to retrieve query parameters. + Args: + data: Request data. + Returns: + A tuple containing subject, predicate, entity, and preference. + """ + subject_uri = data.get("subject_uri", None) + predicate = data.get("predicate", None) + entity_uri = data.get("entity_uri", None) + preference = data.get("preference", None) + return subject_uri, predicate, entity_uri, preference + + +def parse_query_request_data(data: Dict[str, Any]) -> str: + """Parses the request data to execute SPARQL query. + + Args: + data: Request data. + + Returns: + A string containing SPARQL query. + """ + return data.get("sparql_query", None) diff --git a/tests/pkg_api/server/conftest.py b/tests/pkg_api/server/conftest.py index 645dcec..f101637 100644 --- a/tests/pkg_api/server/conftest.py +++ b/tests/pkg_api/server/conftest.py @@ -5,13 +5,13 @@ from pkg_api.server import create_app -@pytest.fixture +@pytest.fixture(scope="session") def client() -> Flask: """Create the Flask test client and add the API resources. Yields: The Flask client. """ - app = create_app() + app = create_app(testing=True) client = app.test_client() yield client diff --git a/tests/pkg_api/server/test_pkg_exploration.py b/tests/pkg_api/server/test_pkg_exploration.py index 37800b9..961734a 100644 --- a/tests/pkg_api/server/test_pkg_exploration.py +++ b/tests/pkg_api/server/test_pkg_exploration.py @@ -1,8 +1,66 @@ """Tests for the pkg exploration endpoints.""" +from flask import Flask -def test_pkg_exploration_endpoint(client) -> None: - """Test the pkg exploration endpoint.""" - response = client.get("/explore") + +def test_pkg_exploration_endpoint_errors(client: Flask) -> None: + """Test /explore endpoint with invalid data.""" + response = client.get( + "/explore", + json={ + "owner_username": "test", + }, + ) + assert response.status_code == 400 + assert response.json["message"] == "Missing owner URI" + + response = client.post( + "/explore", + json={ + "owner_username": "test", + }, + ) + assert response.status_code == 400 + assert response.json["message"] == "Missing owner URI" + + response = client.post( + "/explore", + json={ + "owner_uri": "http://example.org/pkg/test", + "owner_username": "test", + "sparql_query": "INSERT DATA { . }", + }, + ) + assert response.status_code == 400 + assert ( + response.json["message"] + == "Operation is not supported. Provide SPARQL select query." + ) + + +def test_pkg_visualization(client: Flask) -> None: + """Test /explore endpoint.""" + response = client.get( + "/explore", + json={ + "owner_uri": "http://example.org/pkg/test", + "owner_username": "test", + }, + ) + assert response.status_code == 200 + assert response.json["message"] == "PKG visualized successfully." + assert response.json["img_path"] == "pkg_api/pkg_visualizations/test.png" + + +def test_pkg_sparql_query(client: Flask) -> None: + """Tests the POST /explore endpoint.""" + response = client.post( + "/explore", + json={ + "owner_uri": "http://example.org/pkg/test", + "owner_username": "test", + "sparql_query": "SELECT ?object WHERE { ?object . }", + }, + ) assert response.status_code == 200 - assert response.get_json() == {"message": "PKG Exploration"} + assert response.json["message"] == "SPARQL query executed successfully." From 876d6500c3706f3057d0ee7f32d10f5ceaa37453 Mon Sep 17 00:00:00 2001 From: WerLaj Date: Thu, 11 Jan 2024 22:08:32 +0100 Subject: [PATCH 03/31] Formatting issues --- pkg_api/pkg.py | 15 +++++++++------ pkg_api/server/pkg_exploration.py | 8 +++++--- pkg_api/server/utils.py | 4 ++++ tests/pkg_api/server/test_pkg_exploration.py | 12 ++++++++++-- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/pkg_api/pkg.py b/pkg_api/pkg.py index 0229f2f..753e71e 100644 --- a/pkg_api/pkg.py +++ b/pkg_api/pkg.py @@ -17,11 +17,10 @@ """ import io -from typing import Any, Dict, List, Optional +from typing import Dict, List, Optional -import numpy as np import pydotplus -from IPython.display import Image, display +from IPython.display import display from rdflib.query import Result from rdflib.term import Variable from rdflib.tools.rdf2dot import rdf2dot @@ -32,6 +31,7 @@ NS = "http://example.org/pkg/" + class PKG: def __init__(self, owner: URI, rdf_store: RDFStore, rdf_path: str) -> None: """Initializes PKG of a given user. @@ -224,7 +224,7 @@ def execute_sparql_query(self, query: str) -> Result: Args: query: SPARQL query. """ - self._connector.execute_sparql_query(query) + return self._connector.execute_sparql_query(query) def visualize_graph(self) -> str: """Visualizes the PKG. @@ -242,7 +242,11 @@ def visualize_graph(self) -> str: dg = pydotplus.graph_from_dot_data(stream.getvalue()) png = dg.create_png() - path = "pkg_api/pkg_visualizations/" + self._owner_uri.replace(NS, "") + ".png" + path = ( + "pkg_api/pkg_visualizations/" + + self._owner_uri.replace(NS, "") + + ".png" + ) with open(path, "wb") as test_png: test_png.write(png) @@ -276,4 +280,3 @@ def visualize_graph(self) -> str: print(pkg.get_owner_preference("http://example.org/coffee")) print(pkg.get_owner_preference("http://example.org/tea")) - diff --git a/pkg_api/server/pkg_exploration.py b/pkg_api/server/pkg_exploration.py index 9f06baf..05c6c7f 100644 --- a/pkg_api/server/pkg_exploration.py +++ b/pkg_api/server/pkg_exploration.py @@ -35,7 +35,6 @@ def post(self) -> Tuple[Dict[str, Any], int]: A dictionary with the resulting PKG, its visualization, and the HTTP status code. """ - data = request.json try: pkg = open_pkg(data) @@ -45,10 +44,13 @@ def post(self) -> Tuple[Dict[str, Any], int]: sparql_query = parse_query_request_data(data) if "SELECT" in sparql_query: - result = pkg.execute_sparql_query(sparql_query) + result = str(pkg.execute_sparql_query(sparql_query)) else: return { - "message": "Operation is not supported. Provide SPARQL select query." + "message": ( + "Operation is not supported. Provide SPARQL select " + "query." + ) }, 400 pkg.close() diff --git a/pkg_api/server/utils.py b/pkg_api/server/utils.py index e469f47..f737a1d 100644 --- a/pkg_api/server/utils.py +++ b/pkg_api/server/utils.py @@ -9,8 +9,10 @@ def open_pkg(data: Dict[str, str]) -> PKG: """Opens a connection to the PKG. + Args: data: Request data. + Returns: A PKG instance. """ @@ -32,8 +34,10 @@ def parse_pouplation_request_data( data: Dict[str, Any] ) -> Tuple[str, str, str, float]: """Parses the request data to retrieve query parameters. + Args: data: Request data. + Returns: A tuple containing subject, predicate, entity, and preference. """ diff --git a/tests/pkg_api/server/test_pkg_exploration.py b/tests/pkg_api/server/test_pkg_exploration.py index 961734a..052e0f9 100644 --- a/tests/pkg_api/server/test_pkg_exploration.py +++ b/tests/pkg_api/server/test_pkg_exploration.py @@ -28,7 +28,11 @@ def test_pkg_exploration_endpoint_errors(client: Flask) -> None: json={ "owner_uri": "http://example.org/pkg/test", "owner_username": "test", - "sparql_query": "INSERT DATA { . }", + "sparql_query": ( + "INSERT DATA { " + " " + " . }" + ), }, ) assert response.status_code == 400 @@ -59,7 +63,11 @@ def test_pkg_sparql_query(client: Flask) -> None: json={ "owner_uri": "http://example.org/pkg/test", "owner_username": "test", - "sparql_query": "SELECT ?object WHERE { ?object . }", + "sparql_query": ( + "SELECT ?object WHERE { " + " " + " ?object . }" + ), }, ) assert response.status_code == 200 From e0ac3ccc14de635f84be83c40e015727426c4780 Mon Sep 17 00:00:00 2001 From: WerLaj Date: Thu, 11 Jan 2024 22:11:43 +0100 Subject: [PATCH 04/31] Missing package --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8dfbe98..cefb483 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,5 @@ toml rdflib==6.3.2 pytest-cov Flask>=2.3.3 -Flask-RESTful>=0.3.10 \ No newline at end of file +Flask-RESTful>=0.3.10 +pydotplus \ No newline at end of file From c6ae304200cef50be1e63517ae21c0522847f8e2 Mon Sep 17 00:00:00 2001 From: WerLaj Date: Thu, 11 Jan 2024 22:13:55 +0100 Subject: [PATCH 05/31] Missing package --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index cefb483..45aa115 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,5 @@ rdflib==6.3.2 pytest-cov Flask>=2.3.3 Flask-RESTful>=0.3.10 -pydotplus \ No newline at end of file +pydotplus +IPython \ No newline at end of file From b5d95077fdcd6ff836f034e04fe82aeb8bde1859 Mon Sep 17 00:00:00 2001 From: WerLaj Date: Thu, 11 Jan 2024 22:27:52 +0100 Subject: [PATCH 06/31] Missing package --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 45aa115..620ad0a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,4 +12,5 @@ pytest-cov Flask>=2.3.3 Flask-RESTful>=0.3.10 pydotplus -IPython \ No newline at end of file +IPython +graphviz \ No newline at end of file From f91a5656ccc437bcf4aeaecd71b8236e60c43adc Mon Sep 17 00:00:00 2001 From: WerLaj Date: Thu, 11 Jan 2024 22:30:26 +0100 Subject: [PATCH 07/31] Missing package --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 620ad0a..6fa6515 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,6 +11,6 @@ rdflib==6.3.2 pytest-cov Flask>=2.3.3 Flask-RESTful>=0.3.10 +graphviz pydotplus -IPython -graphviz \ No newline at end of file +IPython \ No newline at end of file From df51de5cd8374ecced51f1a465b50e4f7d7ec217 Mon Sep 17 00:00:00 2001 From: WerLaj Date: Thu, 11 Jan 2024 22:34:07 +0100 Subject: [PATCH 08/31] Missing package --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6fa6515..6f861d0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,6 +11,6 @@ rdflib==6.3.2 pytest-cov Flask>=2.3.3 Flask-RESTful>=0.3.10 -graphviz +python-graphviz pydotplus IPython \ No newline at end of file From 946d2e76f7cb3f90bde2de416fe6b01c1b6cc791 Mon Sep 17 00:00:00 2001 From: WerLaj Date: Fri, 12 Jan 2024 07:42:08 +0100 Subject: [PATCH 09/31] Missing package --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 6f861d0..a0b2431 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,6 +11,6 @@ rdflib==6.3.2 pytest-cov Flask>=2.3.3 Flask-RESTful>=0.3.10 -python-graphviz -pydotplus -IPython \ No newline at end of file +python-graphviz==0.20.1 +pydotplus==2.0.2 +ipython==8.19.0 \ No newline at end of file From ec7b53a29234c5b0f9a324f4858fdcede09f6114 Mon Sep 17 00:00:00 2001 From: WerLaj Date: Fri, 12 Jan 2024 07:48:14 +0100 Subject: [PATCH 10/31] Missing package --- requirements.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index a0b2431..5396ec7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,6 +11,7 @@ rdflib==6.3.2 pytest-cov Flask>=2.3.3 Flask-RESTful>=0.3.10 -python-graphviz==0.20.1 -pydotplus==2.0.2 -ipython==8.19.0 \ No newline at end of file +pyparsing>=2.4.7 +graphviz>=0.20.1 +pydotplus>=2.0.2 +ipython>=8.19.0 \ No newline at end of file From daff0d53bcb215e3006b376996d147500544a1f8 Mon Sep 17 00:00:00 2001 From: WerLaj Date: Fri, 12 Jan 2024 08:10:53 +0100 Subject: [PATCH 11/31] Missing package --- .github/workflows/ci.yaml | 1 + requirements.txt | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3525738..b4beadb 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -32,6 +32,7 @@ jobs: run: | pip install --upgrade pip pip install -r requirements.txt + conda install graphviz - name: Run black shell: bash diff --git a/requirements.txt b/requirements.txt index 5396ec7..2d252fd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,6 +12,6 @@ pytest-cov Flask>=2.3.3 Flask-RESTful>=0.3.10 pyparsing>=2.4.7 -graphviz>=0.20.1 +graphviz pydotplus>=2.0.2 -ipython>=8.19.0 \ No newline at end of file +ipython>=7.28.0 \ No newline at end of file From a350ae42be7008d984ef4bb1dba07e3f42b3106f Mon Sep 17 00:00:00 2001 From: WerLaj Date: Fri, 12 Jan 2024 08:20:34 +0100 Subject: [PATCH 12/31] Missing package --- .github/workflows/ci.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b4beadb..f4d8ad0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -32,7 +32,10 @@ jobs: run: | pip install --upgrade pip pip install -r requirements.txt - conda install graphviz + + - name: Install graphviz + run: | + sudo apt install graphviz - name: Run black shell: bash From 9f8c163d47873f78270f808df272bbd4cc45b2a0 Mon Sep 17 00:00:00 2001 From: WerLaj Date: Fri, 12 Jan 2024 08:23:56 +0100 Subject: [PATCH 13/31] Missing package --- .github/workflows/ci.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f4d8ad0..5544506 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -34,8 +34,9 @@ jobs: pip install -r requirements.txt - name: Install graphviz - run: | - sudo apt install graphviz + uses: awalsh128/cache-apt-pkgs-action@latest + with: + packages: graphviz - name: Run black shell: bash From 6ad2043bbe9b1b782db9349ea0573bb588c34b39 Mon Sep 17 00:00:00 2001 From: WerLaj Date: Fri, 12 Jan 2024 08:28:44 +0100 Subject: [PATCH 14/31] Missing package --- .github/workflows/ci.yaml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5544506..8726836 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -33,10 +33,7 @@ jobs: pip install --upgrade pip pip install -r requirements.txt - - name: Install graphviz - uses: awalsh128/cache-apt-pkgs-action@latest - with: - packages: graphviz + - uses: tlylt/install-graphviz@v1 - name: Run black shell: bash From c4fd9377d9f67165a30805f9ccff58c6782258d5 Mon Sep 17 00:00:00 2001 From: WerLaj Date: Fri, 12 Jan 2024 08:32:18 +0100 Subject: [PATCH 15/31] Missing package --- .github/workflows/ci.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8726836..471f3e1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -33,7 +33,9 @@ jobs: pip install --upgrade pip pip install -r requirements.txt - - uses: tlylt/install-graphviz@v1 + - uses: actions/checkout@v3 + - name: Setup Graphviz + uses: ts-graphviz/setup-graphviz@v1 - name: Run black shell: bash From 438c8c30af1b6f73abf72fff99459c5296d7bf3f Mon Sep 17 00:00:00 2001 From: WerLaj Date: Fri, 12 Jan 2024 08:40:35 +0100 Subject: [PATCH 16/31] Missing package --- .github/workflows/ci.yaml | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 471f3e1..7054333 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -32,10 +32,18 @@ jobs: run: | pip install --upgrade pip pip install -r requirements.txt - - - uses: actions/checkout@v3 - - name: Setup Graphviz - uses: ts-graphviz/setup-graphviz@v1 + + - name: Download and install Graphviz + run: | + wget https://gitlab.com/api/v4/projects/4207231/packages/generic/graphviz-releases/9.0.0/graphviz-9.0.0.tar.gz + tar -xzvf garphviz-9.0.0.tar.gz + cd graphviz-9.0.0 + sudo apt-get update + sudo apt-get install -y build-essential libexpat1-dev libexpat1 + ./configure + make + sudo make install + dot -V - name: Run black shell: bash From 7ff6d05b64d4500e7e05035d1153f5f463aab1ae Mon Sep 17 00:00:00 2001 From: WerLaj Date: Fri, 12 Jan 2024 08:42:01 +0100 Subject: [PATCH 17/31] Missing package --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7054333..ba3dc08 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -36,7 +36,7 @@ jobs: - name: Download and install Graphviz run: | wget https://gitlab.com/api/v4/projects/4207231/packages/generic/graphviz-releases/9.0.0/graphviz-9.0.0.tar.gz - tar -xzvf garphviz-9.0.0.tar.gz + tar -xzvf graphviz-9.0.0.tar.gz cd graphviz-9.0.0 sudo apt-get update sudo apt-get install -y build-essential libexpat1-dev libexpat1 From fd3cf4f2ef6adbce8bdc4afab48157aa3098fb02 Mon Sep 17 00:00:00 2001 From: WerLaj Date: Fri, 12 Jan 2024 08:44:23 +0100 Subject: [PATCH 18/31] Missing package --- .github/workflows/ci.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ba3dc08..16a9622 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -104,6 +104,10 @@ jobs: pip install --upgrade pip pip install -r requirements.txt pip install pytest-github-actions-annotate-failures + + - name: Install graphviz + uses: tlylt/install-graphviz@v1 + - name: PyTest with code coverage continue-on-error: true From fb79bd68d0c9ce1ffc2cf9e0b6b5fe645a954221 Mon Sep 17 00:00:00 2001 From: WerLaj Date: Fri, 12 Jan 2024 08:47:09 +0100 Subject: [PATCH 19/31] Missing package --- .github/workflows/ci.yaml | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 16a9622..dbaeb4e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -33,17 +33,8 @@ jobs: pip install --upgrade pip pip install -r requirements.txt - - name: Download and install Graphviz - run: | - wget https://gitlab.com/api/v4/projects/4207231/packages/generic/graphviz-releases/9.0.0/graphviz-9.0.0.tar.gz - tar -xzvf graphviz-9.0.0.tar.gz - cd graphviz-9.0.0 - sudo apt-get update - sudo apt-get install -y build-essential libexpat1-dev libexpat1 - ./configure - make - sudo make install - dot -V + - name: Install Graphviz + uses: tlylt/install-graphviz@v1 - name: Run black shell: bash @@ -80,6 +71,9 @@ jobs: pip install --upgrade pip pip install -r requirements.txt + - name: Install Graphviz + uses: tlylt/install-graphviz@v1 + - name: Run mypy shell: bash run: pre-commit run mypy --all-file From 931e4a0bd7cdfbba0f3ff1d981c791617267e23b Mon Sep 17 00:00:00 2001 From: WerLaj Date: Fri, 12 Jan 2024 08:49:45 +0100 Subject: [PATCH 20/31] Missing package --- .github/workflows/merge.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/merge.yaml b/.github/workflows/merge.yaml index 5659390..1120385 100644 --- a/.github/workflows/merge.yaml +++ b/.github/workflows/merge.yaml @@ -24,6 +24,9 @@ jobs: pip install --upgrade pip pip install -r requirements.txt pip install pytest-github-actions-annotate-failures + + - name: Install Graphviz + uses: tlylt/install-graphviz@v1 - name: PyTest with code coverage continue-on-error: true From 3ead65f9d0286bd3f162d78c9b4ffc2160030ee3 Mon Sep 17 00:00:00 2001 From: WerLaj Date: Fri, 12 Jan 2024 08:54:23 +0100 Subject: [PATCH 21/31] Update docstrings --- pkg_api/server/pkg_exploration.py | 6 +++--- tests/pkg_api/server/test_pkg_exploration.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg_api/server/pkg_exploration.py b/pkg_api/server/pkg_exploration.py index 05c6c7f..7cc0742 100644 --- a/pkg_api/server/pkg_exploration.py +++ b/pkg_api/server/pkg_exploration.py @@ -12,7 +12,7 @@ def get(self) -> Tuple[Dict[str, Any], int]: """Returns the PKG visualization. Returns: - A dictionary with the PKG visualization and the HTTP status code. + A dictionary with the path to PKG visualization and the status code. """ data = request.json try: @@ -32,8 +32,8 @@ def post(self) -> Tuple[Dict[str, Any], int]: """Executes the SPARQL query and visualizes the resulting PKG. Returns: - A dictionary with the resulting PKG, its visualization, and the HTTP - status code. + A dictionary with the result of running SPARQL query and the status + code. """ data = request.json try: diff --git a/tests/pkg_api/server/test_pkg_exploration.py b/tests/pkg_api/server/test_pkg_exploration.py index 052e0f9..02d4619 100644 --- a/tests/pkg_api/server/test_pkg_exploration.py +++ b/tests/pkg_api/server/test_pkg_exploration.py @@ -43,7 +43,7 @@ def test_pkg_exploration_endpoint_errors(client: Flask) -> None: def test_pkg_visualization(client: Flask) -> None: - """Test /explore endpoint.""" + """Test the GET /explore endpoint.""" response = client.get( "/explore", json={ From a50e064db726d3911cc781af85bf0172d03c6996 Mon Sep 17 00:00:00 2001 From: WerLaj Date: Mon, 29 Jan 2024 14:30:24 +0100 Subject: [PATCH 22/31] Fixed failing tests --- pkg_api/server/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg_api/server/utils.py b/pkg_api/server/utils.py index f737a1d..0e7b5a7 100644 --- a/pkg_api/server/utils.py +++ b/pkg_api/server/utils.py @@ -21,7 +21,7 @@ def open_pkg(data: Dict[str, str]) -> PKG: if owner_uri is None: raise Exception("Missing owner URI") - store_path = current_app.config["STORE_PATH"] + store_path = current_app.config["SQLALCHEMY_DATABASE_URI"] return PKG( owner_uri, From 1ecb2727e5b5c6aa0af0f3239d84c658b4d5d998 Mon Sep 17 00:00:00 2001 From: WerLaj Date: Mon, 29 Jan 2024 14:43:46 +0100 Subject: [PATCH 23/31] Fixed typos --- pkg_api/server/pkg_exploration.py | 2 +- pkg_api/server/utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg_api/server/pkg_exploration.py b/pkg_api/server/pkg_exploration.py index 7cc0742..bc16f67 100644 --- a/pkg_api/server/pkg_exploration.py +++ b/pkg_api/server/pkg_exploration.py @@ -29,7 +29,7 @@ def get(self) -> Tuple[Dict[str, Any], int]: }, 200 def post(self) -> Tuple[Dict[str, Any], int]: - """Executes the SPARQL query and visualizes the resulting PKG. + """Executes the SPARQL query. Returns: A dictionary with the result of running SPARQL query and the status diff --git a/pkg_api/server/utils.py b/pkg_api/server/utils.py index 0e7b5a7..7a9bea8 100644 --- a/pkg_api/server/utils.py +++ b/pkg_api/server/utils.py @@ -30,7 +30,7 @@ def open_pkg(data: Dict[str, str]) -> PKG: ) -def parse_pouplation_request_data( +def parse_population_request_data( data: Dict[str, Any] ) -> Tuple[str, str, str, float]: """Parses the request data to retrieve query parameters. From 858150d2db0122f6ae6b309aaee60dfc32b3956a Mon Sep 17 00:00:00 2001 From: WerLaj Date: Thu, 1 Feb 2024 11:58:31 +0100 Subject: [PATCH 24/31] Fixes after code review --- .../pkg_visualizations/README.md | 0 pkg_api/pkg.py | 15 +++++--------- pkg_api/server/utils.py | 20 +------------------ tests/pkg_api/server/test_pkg_exploration.py | 8 ++++---- 4 files changed, 10 insertions(+), 33 deletions(-) rename {pkg_api => data}/pkg_visualizations/README.md (100%) diff --git a/pkg_api/pkg_visualizations/README.md b/data/pkg_visualizations/README.md similarity index 100% rename from pkg_api/pkg_visualizations/README.md rename to data/pkg_visualizations/README.md diff --git a/pkg_api/pkg.py b/pkg_api/pkg.py index 753e71e..193f23b 100644 --- a/pkg_api/pkg.py +++ b/pkg_api/pkg.py @@ -26,11 +26,9 @@ from rdflib.tools.rdf2dot import rdf2dot import pkg_api.utils as utils -from pkg_api.connector import Connector, RDFStore +from pkg_api.connector import DEFAULT_PKG_NAMESPACE, Connector, RDFStore from pkg_api.pkg_types import URI -NS = "http://example.org/pkg/" - class PKG: def __init__(self, owner: URI, rdf_store: RDFStore, rdf_path: str) -> None: @@ -229,13 +227,10 @@ def execute_sparql_query(self, query: str) -> Result: def visualize_graph(self) -> str: """Visualizes the PKG. - https://stackoverflow.com/questions/39274216/visualize-an-rdflib-graph-in-python - - Args: - pkg: PKG to visualize. + https://stackoverflow.com/questions/39274216/visualize-an-rdflib-graph-in-python # noqa: E501 Returns: - The image visualizing the PKG. + The path to the image visualizing the PKG. """ stream = io.StringIO() rdf2dot(self._connector._graph, stream, opts={display}) @@ -243,8 +238,8 @@ def visualize_graph(self) -> str: png = dg.create_png() path = ( - "pkg_api/pkg_visualizations/" - + self._owner_uri.replace(NS, "") + "data/pkg_visualizations" + + self._owner_uri.replace(DEFAULT_PKG_NAMESPACE, "") + ".png" ) diff --git a/pkg_api/server/utils.py b/pkg_api/server/utils.py index 7a9bea8..b6e753b 100644 --- a/pkg_api/server/utils.py +++ b/pkg_api/server/utils.py @@ -1,5 +1,5 @@ """Utility functions for the server.""" -from typing import Any, Dict, Tuple +from typing import Any, Dict from flask import current_app @@ -30,24 +30,6 @@ def open_pkg(data: Dict[str, str]) -> PKG: ) -def parse_population_request_data( - data: Dict[str, Any] -) -> Tuple[str, str, str, float]: - """Parses the request data to retrieve query parameters. - - Args: - data: Request data. - - Returns: - A tuple containing subject, predicate, entity, and preference. - """ - subject_uri = data.get("subject_uri", None) - predicate = data.get("predicate", None) - entity_uri = data.get("entity_uri", None) - preference = data.get("preference", None) - return subject_uri, predicate, entity_uri, preference - - def parse_query_request_data(data: Dict[str, Any]) -> str: """Parses the request data to execute SPARQL query. diff --git a/tests/pkg_api/server/test_pkg_exploration.py b/tests/pkg_api/server/test_pkg_exploration.py index 02d4619..8151e7f 100644 --- a/tests/pkg_api/server/test_pkg_exploration.py +++ b/tests/pkg_api/server/test_pkg_exploration.py @@ -47,13 +47,13 @@ def test_pkg_visualization(client: Flask) -> None: response = client.get( "/explore", json={ - "owner_uri": "http://example.org/pkg/test", + "owner_uri": "http://example.com/pkg/test", "owner_username": "test", }, ) assert response.status_code == 200 assert response.json["message"] == "PKG visualized successfully." - assert response.json["img_path"] == "pkg_api/pkg_visualizations/test.png" + assert response.json["img_path"] == "data/pkg_visualizations/test.png" def test_pkg_sparql_query(client: Flask) -> None: @@ -64,9 +64,9 @@ def test_pkg_sparql_query(client: Flask) -> None: "owner_uri": "http://example.org/pkg/test", "owner_username": "test", "sparql_query": ( - "SELECT ?object WHERE { " + "SELECT ?statement WHERE { " " " - " ?object . }" + " ?statement . }" ), }, ) From 84bd4a4c2e0d296b45aa76583a34fa6f47a29d40 Mon Sep 17 00:00:00 2001 From: WerLaj Date: Thu, 1 Feb 2024 12:32:40 +0100 Subject: [PATCH 25/31] Fix mypy issues --- pkg_api/server/__init__.py | 3 +++ pkg_api/server/utils.py | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg_api/server/__init__.py b/pkg_api/server/__init__.py index 8a8c86f..12eb105 100644 --- a/pkg_api/server/__init__.py +++ b/pkg_api/server/__init__.py @@ -6,6 +6,7 @@ from flask import Flask from flask_restful import Api +from pkg_api.connector import DEFAULT_STORE_PATH from pkg_api.server.auth import AuthResource from pkg_api.server.facts_management import PersonalFactsResource from pkg_api.server.models import db @@ -28,8 +29,10 @@ def create_app(testing: bool = False) -> Flask: if testing: app.config["TESTING"] = True app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///test.sqlite" + app.config["STORE_PATH"] = "tests/data/RDFStore" else: app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///db.sqlite" + app.config["STORE_PATH"] = DEFAULT_STORE_PATH db.init_app(app) diff --git a/pkg_api/server/utils.py b/pkg_api/server/utils.py index b6e753b..b73b107 100644 --- a/pkg_api/server/utils.py +++ b/pkg_api/server/utils.py @@ -4,6 +4,7 @@ from flask import current_app from pkg_api.connector import RDFStore +from pkg_api.core.pkg_types import URI from pkg_api.pkg import PKG @@ -16,12 +17,12 @@ def open_pkg(data: Dict[str, str]) -> PKG: Returns: A PKG instance. """ - owner_uri = data.get("owner_uri", None) + owner_uri: URI = data.get("owner_uri", None) owner_username = data.get("owner_username", None) if owner_uri is None: raise Exception("Missing owner URI") - store_path = current_app.config["SQLALCHEMY_DATABASE_URI"] + store_path = current_app.config["STORE_PATH"] return PKG( owner_uri, From 2cf8ccbe4cd8c53067cd5c1c7b2e6656b7c260c5 Mon Sep 17 00:00:00 2001 From: WerLaj Date: Thu, 1 Feb 2024 12:40:21 +0100 Subject: [PATCH 26/31] Fixing tests --- tests/data/RDFStore.ttl | 14 ++++++++++++++ tests/data/RDFStore/test.ttl | 1 + tests/data/RDFStore/testuser.ttl | 1 + 3 files changed, 16 insertions(+) create mode 100644 tests/data/RDFStore.ttl create mode 100644 tests/data/RDFStore/test.ttl create mode 100644 tests/data/RDFStore/testuser.ttl diff --git a/tests/data/RDFStore.ttl b/tests/data/RDFStore.ttl new file mode 100644 index 0000000..4098247 --- /dev/null +++ b/tests/data/RDFStore.ttl @@ -0,0 +1,14 @@ +@prefix ns1: . +@prefix ns2: . + + ns2:preference [ ns2:entity ns1:pizza ; + ns2:weight ns2:1.0 ] ; + [ ns1:pizza ; + <1.0> ] . + + ns2:preference [ ns2:entity ns1:pizza ; + ns2:weight ns2:1.0 ], + [ ns2:entity ns1:pizza ; + ns2:weight ns2:1.0 ] ; + ns1:likes ns1:pizza . + diff --git a/tests/data/RDFStore/test.ttl b/tests/data/RDFStore/test.ttl new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/data/RDFStore/test.ttl @@ -0,0 +1 @@ + diff --git a/tests/data/RDFStore/testuser.ttl b/tests/data/RDFStore/testuser.ttl new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/data/RDFStore/testuser.ttl @@ -0,0 +1 @@ + From 4cef2ab7e5103fefa721037e131cd144b0d5409a Mon Sep 17 00:00:00 2001 From: WerLaj Date: Thu, 1 Feb 2024 12:44:10 +0100 Subject: [PATCH 27/31] Fix mypy issues --- pkg_api/server/utils.py | 4 ++-- tests/data/RDFStore.ttl | 14 -------------- tests/data/RDFStore/testuser.ttl | 1 - 3 files changed, 2 insertions(+), 17 deletions(-) delete mode 100644 tests/data/RDFStore.ttl delete mode 100644 tests/data/RDFStore/testuser.ttl diff --git a/pkg_api/server/utils.py b/pkg_api/server/utils.py index b73b107..32c038d 100644 --- a/pkg_api/server/utils.py +++ b/pkg_api/server/utils.py @@ -17,7 +17,7 @@ def open_pkg(data: Dict[str, str]) -> PKG: Returns: A PKG instance. """ - owner_uri: URI = data.get("owner_uri", None) + owner_uri = data.get("owner_uri", None) owner_username = data.get("owner_username", None) if owner_uri is None: raise Exception("Missing owner URI") @@ -25,7 +25,7 @@ def open_pkg(data: Dict[str, str]) -> PKG: store_path = current_app.config["STORE_PATH"] return PKG( - owner_uri, + URI(owner_uri), RDFStore.MEMORY, f"{store_path}/{owner_username}", ) diff --git a/tests/data/RDFStore.ttl b/tests/data/RDFStore.ttl deleted file mode 100644 index 4098247..0000000 --- a/tests/data/RDFStore.ttl +++ /dev/null @@ -1,14 +0,0 @@ -@prefix ns1: . -@prefix ns2: . - - ns2:preference [ ns2:entity ns1:pizza ; - ns2:weight ns2:1.0 ] ; - [ ns1:pizza ; - <1.0> ] . - - ns2:preference [ ns2:entity ns1:pizza ; - ns2:weight ns2:1.0 ], - [ ns2:entity ns1:pizza ; - ns2:weight ns2:1.0 ] ; - ns1:likes ns1:pizza . - diff --git a/tests/data/RDFStore/testuser.ttl b/tests/data/RDFStore/testuser.ttl deleted file mode 100644 index 8b13789..0000000 --- a/tests/data/RDFStore/testuser.ttl +++ /dev/null @@ -1 +0,0 @@ - From 1d124abde29f818a597c751da181c70c80f96f11 Mon Sep 17 00:00:00 2001 From: WerLaj Date: Fri, 2 Feb 2024 07:55:59 +0100 Subject: [PATCH 28/31] Fixes after code review --- pkg_api/connector.py | 3 +-- pkg_api/pkg.py | 20 +++++++++++----- tests/pkg_api/server/test_pkg_exploration.py | 24 +++++++++++--------- 3 files changed, 28 insertions(+), 19 deletions(-) diff --git a/pkg_api/connector.py b/pkg_api/connector.py index 4cd4143..121547a 100644 --- a/pkg_api/connector.py +++ b/pkg_api/connector.py @@ -2,7 +2,7 @@ import os from enum import Enum -from rdflib import Graph, Namespace +from rdflib import Graph from rdflib.query import Result from pkg_api.core.namespaces import PKGPrefixes @@ -12,7 +12,6 @@ # Method to execute the SPARQL query DEFAULT_STORE_PATH = "data/RDFStore" -DEFAULT_PKG_NAMESPACE = Namespace("http://example.com/pkg") class RDFStore(Enum): diff --git a/pkg_api/pkg.py b/pkg_api/pkg.py index 34c46c1..64cbc6f 100644 --- a/pkg_api/pkg.py +++ b/pkg_api/pkg.py @@ -18,9 +18,12 @@ from rdflib.tools.rdf2dot import rdf2dot import pkg_api.utils as utils -from pkg_api.connector import DEFAULT_PKG_NAMESPACE, Connector, RDFStore +from pkg_api.connector import Connector, RDFStore +from pkg_api.core.namespaces import PKGPrefixes from pkg_api.core.pkg_types import URI +DEFAULT_VISUALIZATION_PATH = "data/pkg_visualizations/" + class PKG: def __init__(self, owner: URI, rdf_store: RDFStore, rdf_path: str) -> None: @@ -127,6 +130,9 @@ def execute_sparql_query(self, query: str) -> Result: Args: query: SPARQL query. + + Returns: + Result of the SPARQL query. """ return self._connector.execute_sparql_query(query) @@ -143,11 +149,13 @@ def visualize_graph(self) -> str: dg = pydotplus.graph_from_dot_data(stream.getvalue()) png = dg.create_png() - path = ( - "data/pkg_visualizations" - + self._owner_uri.replace(DEFAULT_PKG_NAMESPACE, "") - + ".png" - ) + owner_name = "" + + for _, namespace in PKGPrefixes.__members__.items(): + if namespace.value in str(self._owner_uri): + owner_name = self._owner_uri.replace(str(namespace.value), "") + + path = DEFAULT_VISUALIZATION_PATH + owner_name + ".png" with open(path, "wb") as test_png: test_png.write(png) diff --git a/tests/pkg_api/server/test_pkg_exploration.py b/tests/pkg_api/server/test_pkg_exploration.py index 8151e7f..cf3ab8b 100644 --- a/tests/pkg_api/server/test_pkg_exploration.py +++ b/tests/pkg_api/server/test_pkg_exploration.py @@ -2,9 +2,11 @@ from flask import Flask +from pkg_api.pkg import DEFAULT_VISUALIZATION_PATH + def test_pkg_exploration_endpoint_errors(client: Flask) -> None: - """Test /explore endpoint with invalid data.""" + """Tests /explore endpoints with invalid data.""" response = client.get( "/explore", json={ @@ -26,12 +28,12 @@ def test_pkg_exploration_endpoint_errors(client: Flask) -> None: response = client.post( "/explore", json={ - "owner_uri": "http://example.org/pkg/test", + "owner_uri": "http://example.com#test", "owner_username": "test", "sparql_query": ( - "INSERT DATA { " - " " - " . }" + "INSERT DATA { " + " " + " . }" ), }, ) @@ -43,17 +45,17 @@ def test_pkg_exploration_endpoint_errors(client: Flask) -> None: def test_pkg_visualization(client: Flask) -> None: - """Test the GET /explore endpoint.""" + """Tests the GET /explore endpoint.""" response = client.get( "/explore", json={ - "owner_uri": "http://example.com/pkg/test", + "owner_uri": "http://example.com#test", "owner_username": "test", }, ) assert response.status_code == 200 assert response.json["message"] == "PKG visualized successfully." - assert response.json["img_path"] == "data/pkg_visualizations/test.png" + assert response.json["img_path"] == DEFAULT_VISUALIZATION_PATH + "test.png" def test_pkg_sparql_query(client: Flask) -> None: @@ -61,12 +63,12 @@ def test_pkg_sparql_query(client: Flask) -> None: response = client.post( "/explore", json={ - "owner_uri": "http://example.org/pkg/test", + "owner_uri": "http://example.com#test", "owner_username": "test", "sparql_query": ( "SELECT ?statement WHERE { " - " " - " ?statement . }" + " " + " ?statement . }" ), }, ) From f27bdb8024860463959d4fbf87cb0b37052831a1 Mon Sep 17 00:00:00 2001 From: WerLaj Date: Fri, 2 Feb 2024 08:24:40 +0100 Subject: [PATCH 29/31] Fixes after code review --- pkg_api/pkg.py | 6 ++++-- pkg_api/server/__init__.py | 3 +++ tests/data/RDFStore/test.ttl | 1 - tests/pkg_api/server/test_pkg_exploration.py | 21 ++++++++++++-------- 4 files changed, 20 insertions(+), 11 deletions(-) delete mode 100644 tests/data/RDFStore/test.ttl diff --git a/pkg_api/pkg.py b/pkg_api/pkg.py index 64cbc6f..4bc6c61 100644 --- a/pkg_api/pkg.py +++ b/pkg_api/pkg.py @@ -12,6 +12,7 @@ from typing import Dict, Optional import pydotplus +from flask import current_app from IPython.display import display from rdflib.query import Result from rdflib.term import Variable @@ -22,7 +23,7 @@ from pkg_api.core.namespaces import PKGPrefixes from pkg_api.core.pkg_types import URI -DEFAULT_VISUALIZATION_PATH = "data/pkg_visualizations/" +DEFAULT_VISUALIZATION_PATH = "data/pkg_visualizations" class PKG: @@ -155,7 +156,8 @@ def visualize_graph(self) -> str: if namespace.value in str(self._owner_uri): owner_name = self._owner_uri.replace(str(namespace.value), "") - path = DEFAULT_VISUALIZATION_PATH + owner_name + ".png" + visualization_path = current_app.config["VISUALIZATION_PATH"] + path = visualization_path + "/" + owner_name + ".png" with open(path, "wb") as test_png: test_png.write(png) diff --git a/pkg_api/server/__init__.py b/pkg_api/server/__init__.py index 12eb105..cf9ee15 100644 --- a/pkg_api/server/__init__.py +++ b/pkg_api/server/__init__.py @@ -7,6 +7,7 @@ from flask_restful import Api from pkg_api.connector import DEFAULT_STORE_PATH +from pkg_api.pkg import DEFAULT_VISUALIZATION_PATH from pkg_api.server.auth import AuthResource from pkg_api.server.facts_management import PersonalFactsResource from pkg_api.server.models import db @@ -30,9 +31,11 @@ def create_app(testing: bool = False) -> Flask: app.config["TESTING"] = True app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///test.sqlite" app.config["STORE_PATH"] = "tests/data/RDFStore" + app.config["VISUALIZATION_PATH"] = "tests/data/pkg_visualizations" else: app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///db.sqlite" app.config["STORE_PATH"] = DEFAULT_STORE_PATH + app.config["VISUALIZATION_PATH"] = DEFAULT_VISUALIZATION_PATH db.init_app(app) diff --git a/tests/data/RDFStore/test.ttl b/tests/data/RDFStore/test.ttl deleted file mode 100644 index 8b13789..0000000 --- a/tests/data/RDFStore/test.ttl +++ /dev/null @@ -1 +0,0 @@ - diff --git a/tests/pkg_api/server/test_pkg_exploration.py b/tests/pkg_api/server/test_pkg_exploration.py index cf3ab8b..74db2c5 100644 --- a/tests/pkg_api/server/test_pkg_exploration.py +++ b/tests/pkg_api/server/test_pkg_exploration.py @@ -2,8 +2,6 @@ from flask import Flask -from pkg_api.pkg import DEFAULT_VISUALIZATION_PATH - def test_pkg_exploration_endpoint_errors(client: Flask) -> None: """Tests /explore endpoints with invalid data.""" @@ -31,9 +29,16 @@ def test_pkg_exploration_endpoint_errors(client: Flask) -> None: "owner_uri": "http://example.com#test", "owner_username": "test", "sparql_query": ( - "INSERT DATA { " - " " - " . }" + "INSERT DATA { _:st a rdf:Statement ; " + 'rdf:predicate [ a skos:Concept ; dc:description "like" ] ;' + "rdf:object" + '[ a skos:Concept ; dc:description "icecream"] ;' + " . " + " wi:preference [" + "pav:derivedFrom _:st ;" + 'wi:topic [ a skos:Concept ; dc:description "icecream"] ;' + "wo:weight" + "[ wo:weight_value -1.0 ; wo:scale pkg:StandardScale]] . }" ), }, ) @@ -55,7 +60,7 @@ def test_pkg_visualization(client: Flask) -> None: ) assert response.status_code == 200 assert response.json["message"] == "PKG visualized successfully." - assert response.json["img_path"] == DEFAULT_VISUALIZATION_PATH + "test.png" + assert response.json["img_path"] == "tests/data/pkg_visualizations/test.png" def test_pkg_sparql_query(client: Flask) -> None: @@ -67,8 +72,8 @@ def test_pkg_sparql_query(client: Flask) -> None: "owner_username": "test", "sparql_query": ( "SELECT ?statement WHERE { " - " " - " ?statement . }" + "?statement rdf:predicate " + '[ a skos:Concept ; dc:description "like" ] . }' ), }, ) From 7f82a76598934fbcc237fb7b2258415aba843040 Mon Sep 17 00:00:00 2001 From: WerLaj Date: Fri, 2 Feb 2024 08:33:32 +0100 Subject: [PATCH 30/31] Fix failing test --- tests/pkg_api/server/test_pkg_exploration.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/pkg_api/server/test_pkg_exploration.py b/tests/pkg_api/server/test_pkg_exploration.py index 74db2c5..4f1990a 100644 --- a/tests/pkg_api/server/test_pkg_exploration.py +++ b/tests/pkg_api/server/test_pkg_exploration.py @@ -1,5 +1,7 @@ """Tests for the pkg exploration endpoints.""" +import os + from flask import Flask @@ -51,6 +53,10 @@ def test_pkg_exploration_endpoint_errors(client: Flask) -> None: def test_pkg_visualization(client: Flask) -> None: """Tests the GET /explore endpoint.""" + if not os.path.exists("tests/data/pkg_visualizations/"): + os.makedirs("tests/data/pkg_visualizations/", exist_ok=True) + if not os.path.exists("tests/data/RDFStore/"): + os.makedirs("tests/data/RDFStore/", exist_ok=True) response = client.get( "/explore", json={ @@ -65,6 +71,8 @@ def test_pkg_visualization(client: Flask) -> None: def test_pkg_sparql_query(client: Flask) -> None: """Tests the POST /explore endpoint.""" + if not os.path.exists("tests/data/RDFStore/"): + os.makedirs("tests/data/RDFStore/", exist_ok=True) response = client.post( "/explore", json={ From f17f23e0cdffa36970968afbad5fcd41dca2a621 Mon Sep 17 00:00:00 2001 From: WerLaj Date: Fri, 2 Feb 2024 11:13:31 +0100 Subject: [PATCH 31/31] Fixes after code review --- pkg_api/pkg.py | 15 +++++++++++---- pkg_api/server/utils.py | 2 ++ tests/pkg_api/server/test_pkg_exploration.py | 5 ++--- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/pkg_api/pkg.py b/pkg_api/pkg.py index 4bc6c61..81135cd 100644 --- a/pkg_api/pkg.py +++ b/pkg_api/pkg.py @@ -12,7 +12,6 @@ from typing import Dict, Optional import pydotplus -from flask import current_app from IPython.display import display from rdflib.query import Result from rdflib.term import Variable @@ -27,16 +26,25 @@ class PKG: - def __init__(self, owner: URI, rdf_store: RDFStore, rdf_path: str) -> None: + def __init__( + self, + owner: URI, + rdf_store: RDFStore, + rdf_path: str, + visualization_path: str = DEFAULT_VISUALIZATION_PATH, + ) -> None: """Initializes PKG of a given user. Args: owner: Owner URI. rdf_store: Type of RDF store. rdf_path: Path to the RDF store. + visualization_path: Path to the visualization of PKG. Defaults to + DEFAULT_VISUALIZATION_PATH. """ self._owner_uri = owner self._connector = Connector(owner, rdf_store, rdf_path) + self._visualization_path = visualization_path @property def owner_uri(self) -> URI: @@ -156,8 +164,7 @@ def visualize_graph(self) -> str: if namespace.value in str(self._owner_uri): owner_name = self._owner_uri.replace(str(namespace.value), "") - visualization_path = current_app.config["VISUALIZATION_PATH"] - path = visualization_path + "/" + owner_name + ".png" + path = self._visualization_path + "/" + owner_name + ".png" with open(path, "wb") as test_png: test_png.write(png) diff --git a/pkg_api/server/utils.py b/pkg_api/server/utils.py index 32c038d..ab85684 100644 --- a/pkg_api/server/utils.py +++ b/pkg_api/server/utils.py @@ -23,11 +23,13 @@ def open_pkg(data: Dict[str, str]) -> PKG: raise Exception("Missing owner URI") store_path = current_app.config["STORE_PATH"] + visualization_path = current_app.config["VISUALIZATION_PATH"] return PKG( URI(owner_uri), RDFStore.MEMORY, f"{store_path}/{owner_username}", + visualization_path=visualization_path, ) diff --git a/tests/pkg_api/server/test_pkg_exploration.py b/tests/pkg_api/server/test_pkg_exploration.py index 4f1990a..5cffe0d 100644 --- a/tests/pkg_api/server/test_pkg_exploration.py +++ b/tests/pkg_api/server/test_pkg_exploration.py @@ -34,13 +34,12 @@ def test_pkg_exploration_endpoint_errors(client: Flask) -> None: "INSERT DATA { _:st a rdf:Statement ; " 'rdf:predicate [ a skos:Concept ; dc:description "like" ] ;' "rdf:object" - '[ a skos:Concept ; dc:description "icecream"] ;' - " . " + '[ a skos:Concept ; dc:description "icecream"] . ' " wi:preference [" "pav:derivedFrom _:st ;" 'wi:topic [ a skos:Concept ; dc:description "icecream"] ;' "wo:weight" - "[ wo:weight_value -1.0 ; wo:scale pkg:StandardScale]] . }" + "[ wo:weight_value 1.0 ; wo:scale pkg:StandardScale]] . }" ), }, )