From 041342d39080b940c62a0c8f18e63e1e1749d29e Mon Sep 17 00:00:00 2001 From: Jean Luc Szpyrka Date: Tue, 5 Nov 2024 14:30:17 +0100 Subject: [PATCH 01/13] typo --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 55e903c..30b35ae 100644 --- a/README.md +++ b/README.md @@ -16,5 +16,5 @@ Follow the instuctions from `INSTALL.md` ## Documentation -Corese documentation: https://corese-stack.github.io/corese-core -Corese python wrapper: https://corese-stack.github.io/corese-python +- Corese documentation: https://corese-stack.github.io/corese-core +- Corese python wrapper: https://corese-stack.github.io/corese-python From 5376a84df073ca60c456fc0f96db569687fb7b89 Mon Sep 17 00:00:00 2001 From: Jean Luc Szpyrka Date: Tue, 5 Nov 2024 14:30:39 +0100 Subject: [PATCH 02/13] maven-artifact python package not necessary --- pkg/env/corese-python.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/env/corese-python.yaml b/pkg/env/corese-python.yaml index 52fc07c..98c0311 100644 --- a/pkg/env/corese-python.yaml +++ b/pkg/env/corese-python.yaml @@ -7,5 +7,4 @@ dependencies: - py4j - pip - pip: - - maven-artifact - jpype1 From 466eab96ed15426b340da491f5fd3ae4f1f3c267 Mon Sep 17 00:00:00 2001 From: Jean Luc Szpyrka Date: Tue, 5 Nov 2024 14:31:48 +0100 Subject: [PATCH 03/13] call CoreseInfo.getVersion() instead of returning a static sting --- src/pycorese/jpype_bridge.py | 18 ++++++++++++++---- src/pycorese/py4J_bridge.py | 19 ++++++++++++++----- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/pycorese/jpype_bridge.py b/src/pycorese/jpype_bridge.py index bf24d27..f79514b 100644 --- a/src/pycorese/jpype_bridge.py +++ b/src/pycorese/jpype_bridge.py @@ -41,7 +41,7 @@ def __init__(self, if corese_path: self.corese_path = corese_path if not os.path.exists(self.corese_path): - msg = f'given CORESE library is not found at {self.corese_path}.' + msg = f'JPype: given CORESE library is not found at {self.corese_path}.' logging.critical(msg) raise FileNotFoundError('\n'+msg) @@ -50,7 +50,7 @@ def __init__(self, self.corese_path = os.environ.get("CORESE_PATH", package_jar_path) if not os.path.exists(self.corese_path): - msg = f'given CORESE library is not found at {self.corese_path}.' + msg = f'JPype: given CORESE library is not found at {self.corese_path}.' logging.critical(msg) raise FileNotFoundError('\n'+msg) @@ -68,7 +68,17 @@ def coreseVersion(self): """ TODO: call coreseVersion() from corese engine """ - return corese_version + version = None + try: + from fr.inria.corese.core.util import CoreseInfo + version = CoreseInfo.getVersion() + except: + pass + + if version is None: + loggingWarning(f"JPype: the CORESE library is too old. coreseVersion() is available since 4.6.0 only.") + + return version def unloadCorese(self, force=False): """ @@ -155,7 +165,7 @@ def loadCorese(self, memory_allocation=None) -> jpype: except Exception as e: - logging.error('JPype: CORESE failed to load: %s', str(e)) + logging.critical('JPype: CORESE failed to load: %s', str(e)) return jpype diff --git a/src/pycorese/py4J_bridge.py b/src/pycorese/py4J_bridge.py index 13822fe..4f0ab91 100644 --- a/src/pycorese/py4J_bridge.py +++ b/src/pycorese/py4J_bridge.py @@ -33,7 +33,7 @@ def __init__(self, if corese_path: self.corese_path = corese_path if not os.path.exists(self.corese_path): - msg = f'given CORESE library is not found at {self.corese_path}.' + msg = f'Py4j: given CORESE library is not found at {self.corese_path}.' logging.critical(msg) raise FileNotFoundError('\n'+msg) @@ -42,7 +42,7 @@ def __init__(self, self.corese_path = os.environ.get("CORESE_PATH", package_jar_path) if not os.path.exists(self.corese_path): - msg = f'given CORESE library is not found at {self.corese_path}.' + msg = f'Py4j: given CORESE library is not found at {self.corese_path}.' logging.critical(msg) raise FileNotFoundError('\n'+msg) @@ -59,9 +59,18 @@ def _exit_handler(self) -> None: def coreseVersion(self): """ - TODO: call coreseVersion() from corese engine + get corese version from the loaded corese engine """ - return corese_version + version = None + try: + version = corese.java_gateway.jvm.fr.inria.corese.core.util.CoreseInfo.getVersion() + except: + pass + + if version is None: + loggingWarning(f"Py4j: the CORESE library is too old. coreseVersion() is available since 4.6.0 only.") + + return version def unloadCorese(self): """ @@ -128,6 +137,6 @@ def loadCorese(self, memory_allocation=None) -> JavaGateway: logging.info('Py4J: CORESE is loaded') except Exception as e: - logging.error('Py4J: CORESE failed to load: %s', str(e)) + logging.critical('Py4J: CORESE failed to load: %s', str(e)) return self.java_gateway From 78be7a7fa40b9bba3938725233b1a93fcbf3ca3a Mon Sep 17 00:00:00 2001 From: Anna Bobasheva <33026767+AnnaBobasheva@users.noreply.github.com> Date: Tue, 3 Dec 2024 18:48:09 +0100 Subject: [PATCH 04/13] Feature/add example notebook (#11) * updated example dir and notebook * Update example1.ipynb * Changed GoogleColab link --- .gitignore | 12 +- .../data/beatles-validator.ttl | 0 .../data/beatles.rdf | 0 examples/example1.ipynb | 1464 +++++++++++++++++ {python_examples => examples}/simple_query.py | 0 python_examples/example1.ipynb | 446 ----- 6 files changed, 1471 insertions(+), 451 deletions(-) rename {python_examples => examples}/data/beatles-validator.ttl (100%) rename {python_examples => examples}/data/beatles.rdf (100%) create mode 100644 examples/example1.ipynb rename {python_examples => examples}/simple_query.py (100%) mode change 100755 => 100644 delete mode 100644 python_examples/example1.ipynb diff --git a/.gitignore b/.gitignore index 6947822..1d79d3f 100644 --- a/.gitignore +++ b/.gitignore @@ -160,18 +160,20 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. .idea/ -.gradle -build -log # VSCode .vscode/ - # garbage \#* .\#* # backup files resources/ -*.bak \ No newline at end of file +*.bak + +# Java class files +.gradle/ +build/ +log/ +bin/ diff --git a/python_examples/data/beatles-validator.ttl b/examples/data/beatles-validator.ttl similarity index 100% rename from python_examples/data/beatles-validator.ttl rename to examples/data/beatles-validator.ttl diff --git a/python_examples/data/beatles.rdf b/examples/data/beatles.rdf similarity index 100% rename from python_examples/data/beatles.rdf rename to examples/data/beatles.rdf diff --git a/examples/example1.ipynb b/examples/example1.ipynb new file mode 100644 index 0000000..0de6786 --- /dev/null +++ b/examples/example1.ipynb @@ -0,0 +1,1464 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "view-in-github" + }, + "source": [ + "\"Open" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "oCetcC1MQz13" + }, + "source": [ + "# Using pycorese\n", + "\n", + "This notebook demonstrates how to use the **pycorese** package:\n", + "\n", + "- to load knowledge graph\n", + "- to perform a SPARQL query\n", + "- to validate a SHACL form\n", + "- to access the classes of Corese Java API" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "tZjvQGgGe64i" + }, + "source": [ + "## Install pycorese" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "nFeJr1PbQz18" + }, + "source": [ + "Java Runtime Environment (JRE) 11 or higher is required to run **pycorese**.\n", + "\n", + "If you don't have Java installed please refer to the [official website](https://www.java.com/en/download/help/download_options.html) to download and install it." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "IKx255qaQz1_", + "outputId": "29b40851-6439-459b-c5f5-1e8cb89f7e84" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "openjdk version \"11.0.25\" 2024-10-15\n", + "OpenJDK Runtime Environment (build 11.0.25+9-post-Ubuntu-1ubuntu122.04)\n", + "OpenJDK 64-Bit Server VM (build 11.0.25+9-post-Ubuntu-1ubuntu122.04, mixed mode, sharing)\n" + ] + } + ], + "source": [ + "!java -version" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "QzKUfvL8Qz2G" + }, + "source": [ + "**pycorese** is available on PyPI and can be installed using pip:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "OdY7kuBeQz2I", + "outputId": "f0deca77-241c-4c58-970c-2906bcbc4078" + }, + "outputs": [], + "source": [ + "!pip install pycorese" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "pRlL21fgQz2M" + }, + "source": [ + "Download the data files from the GitHub repository:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "kOvrNs-ze64n", + "outputId": "731259ca-8854-4497-fadc-aca4b4ec3714" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "beatles.rdf beatles-validator.ttl\n" + ] + } + ], + "source": [ + "import os\n", + "import sys\n", + "if not os.path.exists('./data/beatles.rdf'):\n", + " print('Downloading the data files...')\n", + " !mkdir -p ./data\n", + " !wget https://raw.githubusercontent.com/corese-stack/corese-python/main/examples/data/beatles.rdf -O ./data/beatles.rdf\n", + " !wget https://raw.githubusercontent.com/corese-stack/corese-python/main/examples/data/beatles-validator.ttl -O ./data/beatles-validator.ttl\n", + "\n", + "if sys.platform == 'win32':\n", + " !dir /b .\\data\\*.*\n", + "else:\n", + " !ls ./data" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "PLBixnURe64o" + }, + "source": [ + "### Connect to Corese API\n", + "\n", + "Demonstrate loading and querying data with CoreseAPI connected through `Py4J` or `JPype` packages. If you don't specify the java bridge type, the default is `Py4J`." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "id": "wN4TDhjXe64p" + }, + "outputs": [], + "source": [ + "#%%timeit -n 1 -r 1\n", + "from pycorese.api import CoreseAPI\n", + "\n", + "python_to_java_bridge = 'py4j'\n", + "corese = CoreseAPI(java_bridge=python_to_java_bridge)\n", + "corese.loadCorese()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "7WzP7gCle64p" + }, + "source": [ + "### High-level API" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "1hHYhnIve64p" + }, + "source": [ + "#### Run SELECT query" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 206 + }, + "id": "KiVYUBGhe64p", + "outputId": "786d7754-23a2-4ba6-800d-e36bd199adc7" + }, + "outputs": [ + { + "data": { + "application/vnd.google.colaboratory.intrinsic+json": { + "summary": "{\n \"name\": \"results\",\n \"rows\": 5,\n \"fields\": [\n {\n \"column\": \"subject\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 3,\n \"samples\": [\n \"http://example.com/Please_Please_Me\",\n \"http://example.com/McCartney\",\n \"http://example.com/Imagine\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"p\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 2,\n \"samples\": [\n \"http://example.com/date\",\n \"http://example.com/artist\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"o\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 5,\n \"samples\": [\n \"http://example.com/Paul_McCartney\",\n \"1970-04-17\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}", + "type": "dataframe", + "variable_name": "results" + }, + "text/html": [ + "\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
subjectpo
0http://example.com/Please_Please_Mehttp://example.com/artisthttp://example.com/The_Beatles
1http://example.com/McCartneyhttp://example.com/artisthttp://example.com/Paul_McCartney
2http://example.com/Imaginehttp://example.com/artisthttp://example.com/John_Lennon
3http://example.com/Please_Please_Mehttp://example.com/date1963-03-22
4http://example.com/McCartneyhttp://example.com/date1970-04-17
\n", + "
\n", + "
\n", + "\n", + "
\n", + " \n", + "\n", + " \n", + "\n", + " \n", + "
\n", + "\n", + "\n", + "
\n", + " \n", + "\n", + "\n", + "\n", + " \n", + "
\n", + "\n", + "
\n", + " \n", + " \n", + " \n", + "
\n", + "\n", + "
\n", + "
\n" + ], + "text/plain": [ + " subject p \\\n", + "0 http://example.com/Please_Please_Me http://example.com/artist \n", + "1 http://example.com/McCartney http://example.com/artist \n", + "2 http://example.com/Imagine http://example.com/artist \n", + "3 http://example.com/Please_Please_Me http://example.com/date \n", + "4 http://example.com/McCartney http://example.com/date \n", + "\n", + " o \n", + "0 http://example.com/The_Beatles \n", + "1 http://example.com/Paul_McCartney \n", + "2 http://example.com/John_Lennon \n", + "3 1963-03-22 \n", + "4 1970-04-17 " + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import os\n", + "data_path = os.path.abspath('./data/beatles.rdf')\n", + "\n", + "query = '''\n", + "SELECT *\n", + "WHERE {?subject ?p ?o} LIMIT 5'''\n", + "\n", + "graph = corese.loadRDF(data_path)\n", + "results = corese.sparqlSelect(graph, query=query, return_dataframe=True)\n", + "\n", + "results" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "7iohNvoue64q" + }, + "source": [ + "#### Load inference rules" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "0kzC2BXSe64q", + "outputId": "67cdc27e-20b1-45ca-b045-a981abc2a6b5" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " s type\n", + "0 http://example.com/Please_Please_Me http://example.com/Album\n", + "1 http://example.com/McCartney http://example.com/Album\n", + "2 http://example.com/Imagine http://example.com/Album\n", + "3 http://example.com/The_Beatles http://example.com/Band\n", + "4 http://example.com/John_Lennon http://example.com/SoloArtist\n", + "5 http://example.com/Paul_McCartney http://example.com/SoloArtist\n", + "6 http://example.com/Ringo_Starr http://example.com/SoloArtist\n", + "7 http://example.com/George_Harrison http://example.com/SoloArtist\n", + "8 http://example.com/Love_Me_Do http://example.com/Song\n", + "Graph size: 29\n" + ] + } + ], + "source": [ + "corese.resetRuleEngine(graph)\n", + "query = \"select * where {?s a ?type} order by ?type\"\n", + "print(corese.sparqlSelect(graph, query=query))\n", + "print(\"Graph size: \", graph.graphSize())\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "SbSrRfsWe64q" + }, + "source": [ + "Adding inference rules to the Corese engine should change the results of the query by adding new triples." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "op-vR8rge64q", + "outputId": "4eb133de-a58c-43e0-9d90-04722db0f6b3" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Graph size: 33\n" + ] + } + ], + "source": [ + "corese.loadRuleEngine(graph, profile=corese.RuleEngine.Profile.RDFS)\n", + "print(\"Graph size: \", graph.graphSize())" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "0ILJiiQJe64q" + }, + "source": [ + "Let's see what was added." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "X_rFryste64q", + "outputId": "b1f67902-a052-4075-f4d8-66ce54d0fefa" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " s type\n", + "0 http://example.com/Please_Please_Me http://example.com/Album\n", + "1 http://example.com/McCartney http://example.com/Album\n", + "2 http://example.com/Imagine http://example.com/Album\n", + "3 http://example.com/The_Beatles http://example.com/Band\n", + "4 http://example.com/John_Lennon http://example.com/Person\n", + "5 http://example.com/Paul_McCartney http://example.com/Person\n", + "6 http://example.com/Ringo_Starr http://example.com/Person\n", + "7 http://example.com/George_Harrison http://example.com/Person\n", + "8 http://example.com/John_Lennon http://example.com/SoloArtist\n", + "9 http://example.com/Paul_McCartney http://example.com/SoloArtist\n", + "10 http://example.com/Ringo_Starr http://example.com/SoloArtist\n", + "11 http://example.com/George_Harrison http://example.com/SoloArtist\n", + "12 http://example.com/Love_Me_Do http://example.com/Song\n", + "Graph size: 33\n" + ] + } + ], + "source": [ + "query = \"select * where {?s a ?type} order by ?type\"\n", + "print(corese.sparqlSelect(graph, query=query))\n", + "print(\"Graph size: \", graph.graphSize())" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "yvIlPQVue64r" + }, + "source": [ + "The inference was that the solo artist is also a person although it was not explicitly stated in the data." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "enpoRBHve64r" + }, + "source": [ + "#### Run CONSTRUCT query" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "9Ii81qu1e64r", + "outputId": "c2e57c03-f591-433f-ebfb-397f49a2519e" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "\n", + " \n", + " \n", + "\n", + " \n", + " \n", + "\n", + " \n", + " \n", + "\n", + " \n", + " \n", + "\n", + "\n" + ] + } + ], + "source": [ + "prefixes = '@prefix ex: '\n", + "contruct = '''CONSTRUCT {?A_Beatle a ex:BandMember }\n", + " WHERE { ex:The_Beatles ex:member ?A_Beatle}'''\n", + "\n", + "results = corese.sparqlConstruct(graph, prefixes=prefixes, query=contruct)\n", + "\n", + "print(results)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "0PPF87F2e64r" + }, + "source": [ + "By default, the CONSTRUCT query returns the RDF/XML format. For more concise format convert the results to Turtle." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "TULma6_Ce64r", + "outputId": "88cd769a-f09f-49d4-f4cd-a0d031b73d60" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + " a .\n", + "\n", + " a .\n", + "\n", + " a .\n", + "\n", + " a .\n", + "\n", + "\n" + ] + } + ], + "source": [ + "ttl = corese.toTurtle(results)\n", + "\n", + "print(ttl)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "znZOJMz1e64r" + }, + "source": [ + "#### Run SHACL form validation" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "liQCAMpFe64s" + }, + "source": [ + "In the example below, we will use the the SHACL shape file that validates that the *beatles* graph follows the rules:\n", + "\n", + "- A band has a name and at least on member who is also a Solo Artist\n", + "- An album has one name, one date and one artist associated with it\n", + "- A song has one name, one duration and at least writer and at least one performer associated with it\n", + "\n", + "The validation should fail because the *beatles* graph does not contain the required information. " + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "MOsEDfB3e64s", + "outputId": "1c2e0577-c583-41a2-f4da-50633daa5bee" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "PREFIX sh: \n", + "PREFIX xsd: \n", + "PREFIX ex: \n", + "\n", + "# Shape for Bands\n", + "ex:BandShape a sh:NodeShape ;\n", + " sh:targetClass ex:Band ;\n", + " sh:property [\n", + " sh:path ex:name ;\n", + " sh:datatype xsd:string ;\n", + " sh:minCount 1 ;\n", + " sh:maxCount 1 ;\n", + " ] ;\n", + " sh:property [\n", + " sh:path ex:member ;\n", + " sh:class ex:SoloArtist ;\n", + " sh:minCount 1 ;\n", + " ] .\n", + "\n", + "# Shape for Solo Artists\n", + "ex:SoloArtistShape a sh:NodeShape ;\n", + " sh:targetClass ex:SoloArtist .\n", + "\n", + "# Shape for Albums\n", + "ex:AlbumShape a sh:NodeShape ;\n", + " sh:targetClass ex:Album ;\n", + " sh:property [\n", + " sh:path ex:name ;\n", + " sh:datatype xsd:string ;\n", + " sh:minCount 1 ;\n", + " sh:maxCount 1 ;\n", + " ] ;\n", + " sh:property [\n", + " sh:path ex:date ;\n", + " sh:datatype xsd:date ;\n", + " sh:minCount 1 ;\n", + " sh:maxCount 1 ;\n", + " ] ;\n", + " sh:property [\n", + " sh:path ex:artist ;\n", + " sh:nodeKind sh:IRI ;\n", + " sh:minCount 1 ;\n", + " sh:maxCount 1 ;\n", + " ] .\n", + "\n", + "# Shape for Songs\n", + "ex:SongShape a sh:NodeShape ;\n", + " sh:targetClass ex:Song ;\n", + " sh:property [\n", + " sh:path ex:name ;\n", + " sh:datatype xsd:string ;\n", + " sh:minCount 1 ;\n", + " sh:maxCount 1 ;\n", + " ] ;\n", + " sh:property [\n", + " sh:path ex:length ;\n", + " sh:datatype xsd:integer ;\n", + " sh:minCount 1 ;\n", + " sh:maxCount 1 ;\n", + " ] ;\n", + " sh:property [\n", + " sh:path ex:performer ;\n", + " sh:nodeKind sh:IRI ;\n", + " sh:minCount 1 ;\n", + " ] ;\n", + " sh:property [\n", + " sh:path ex:writer ;\n", + " sh:nodeKind sh:IRI ;\n", + " sh:minCount 1 ;\n", + " ] .\n", + "\n" + ] + } + ], + "source": [ + "data_shape_path = os.path.abspath('./data/beatles-validator.ttl')\n", + "\n", + "with open(data_shape_path, 'r') as file:\n", + " data_shape = file.read()\n", + " print(data_shape)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "Oa-7_Q_qe64s", + "outputId": "d6764304-7a25-4274-d010-593cbbbbd5a2" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "@prefix xsh: .\n", + "@prefix sh: .\n", + "\n", + " a sh:ValidationResult ;\n", + " sh:focusNode ;\n", + " sh:resultMessage \"Fail at: [sh:minCount 1 ;\\n sh:nodeKind sh:IRI ;\\n sh:path ]\" ;\n", + " sh:resultPath ;\n", + " sh:resultSeverity sh:Violation ;\n", + " sh:sourceConstraintComponent sh:MinCountConstraintComponent ;\n", + " sh:sourceShape _:b7 ;\n", + " sh:value 0 .\n", + "\n", + "[a sh:ValidationReport ;\n", + " sh:conforms false ;\n", + " sh:result ] .\n", + "\n", + "\n" + ] + } + ], + "source": [ + "prefixes = '@prefix ex: '\n", + "report = corese.shaclValidate(graph, shacl_shape_ttl=data_shape_path, prefixes=prefixes)\n", + "\n", + "print(report)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ErOwy8yMe64s" + }, + "source": [ + "The SHACL validation report is verbose and can be reshaped into a DataFrame for readability." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 184 + }, + "id": "K4_fQT0Ye64s", + "outputId": "db9698e5-9f18-4714-ade4-93c6218b6047" + }, + "outputs": [ + { + "data": { + "application/vnd.google.colaboratory.intrinsic+json": { + "summary": "{\n \"name\": \"report_dataframe\",\n \"rows\": 1,\n \"fields\": [\n {\n \"column\": \"o\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"urn:uuid:66d7b5ea-0065-4f84-b0e4-d65ba0b16a11\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"type\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"http://www.w3.org/ns/shacl#ValidationResult\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"focusNode\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"http://example.com/Love_Me_Do\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"resultMessage\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"Fail at: [sh:minCount 1 ;\\n sh:nodeKind sh:IRI ;\\n sh:path ]\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"resultPath\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"http://example.com/performer\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"resultSeverity\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"http://www.w3.org/ns/shacl#Violation\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"sourceConstraintComponent\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"http://www.w3.org/ns/shacl#MinCountConstraintComponent\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"sourceShape\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"_:b9\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"value\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"0\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}", + "type": "dataframe", + "variable_name": "report_dataframe" + }, + "text/html": [ + "\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
typefocusNoderesultMessageresultPathresultSeveritysourceConstraintComponentsourceShapevalue
o
urn:uuid:66d7b5ea-0065-4f84-b0e4-d65ba0b16a11http://www.w3.org/ns/shacl#ValidationResulthttp://example.com/Love_Me_DoFail at: [sh:minCount 1 ;\n", + " sh:nodeKind sh:IRI...http://example.com/performerhttp://www.w3.org/ns/shacl#Violationhttp://www.w3.org/ns/shacl#MinCountConstraintC..._:b90
\n", + "
\n", + "
\n", + "\n", + "
\n", + " \n", + "\n", + " \n", + "\n", + " \n", + "
\n", + "\n", + "\n", + "
\n", + " \n", + " \n", + " \n", + "
\n", + "\n", + "
\n", + "
\n" + ], + "text/plain": [ + " type \\\n", + "o \n", + "urn:uuid:66d7b5ea-0065-4f84-b0e4-d65ba0b16a11 http://www.w3.org/ns/shacl#ValidationResult \n", + "\n", + " focusNode \\\n", + "o \n", + "urn:uuid:66d7b5ea-0065-4f84-b0e4-d65ba0b16a11 http://example.com/Love_Me_Do \n", + "\n", + " resultMessage \\\n", + "o \n", + "urn:uuid:66d7b5ea-0065-4f84-b0e4-d65ba0b16a11 Fail at: [sh:minCount 1 ;\n", + " sh:nodeKind sh:IRI... \n", + "\n", + " resultPath \\\n", + "o \n", + "urn:uuid:66d7b5ea-0065-4f84-b0e4-d65ba0b16a11 http://example.com/performer \n", + "\n", + " resultSeverity \\\n", + "o \n", + "urn:uuid:66d7b5ea-0065-4f84-b0e4-d65ba0b16a11 http://www.w3.org/ns/shacl#Violation \n", + "\n", + " sourceConstraintComponent \\\n", + "o \n", + "urn:uuid:66d7b5ea-0065-4f84-b0e4-d65ba0b16a11 http://www.w3.org/ns/shacl#MinCountConstraintC... \n", + "\n", + " sourceShape value \n", + "o \n", + "urn:uuid:66d7b5ea-0065-4f84-b0e4-d65ba0b16a11 _:b9 0 " + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "report_dataframe = corese.shaclReportToDataFrame(report)\n", + "\n", + "report_dataframe" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "d6xlzpURe64t" + }, + "source": [ + "The report tells us that for the song *Love Me Do* a performer is not specified." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Nt-rvV1Pe64t" + }, + "source": [ + "## Low-level API" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "NgjIq4y-e64t" + }, + "source": [ + "### Adding triples manually to the graph." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "egIhirIce64t", + "outputId": "5a78aacf-06fd-4968-ad8d-9a3f7ed96147" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "JavaObject id=o37" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Namespace\n", + "ex = \"http://example.com/\"\n", + "\n", + "# Get the graph from either Graph or DataManager objects\n", + "graph = graph.getGraph()\n", + "\n", + "# Create and add statements: Help! is an album\n", + "new_album_IRI = graph.addResource(ex + \"Help\")\n", + "rdf_Type_Property = graph.addProperty(corese.Namespaces.RDF + 'type')\n", + "album_type_IRI = graph.addResource(ex + \"Album\")\n", + "\n", + "graph.addEdge(new_album_IRI, rdf_Type_Property, album_type_IRI)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "BqmK5N5Xe64t" + }, + "source": [ + "Let's see what was added." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "2l26SHjWe64u", + "outputId": "87add254-e354-427b-b614-4863a0be45d4" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "01 ?album = ; \n", + "02 ?album = ; \n", + "03 ?album = ; \n", + "04 ?album = ; \n", + "\n" + ] + } + ], + "source": [ + "query = f'''@prefix ex: <{ex}>\n", + " SELECT *\n", + " where {{?album a ex:Album }}'''\n", + "\n", + "exec = corese.QueryProcess.create(graph)\n", + "\n", + "results = exec.query(query)\n", + "\n", + "print(results)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "6IwJDXc4e64u" + }, + "source": [ + "The new triple (album *Help*) was added to the graph." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "o41J9o5We64u" + }, + "source": [ + "Wer can add some more detailes for the album *Help!* and see what was added." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "gSZwJp33e64u", + "outputId": "f2ab11b3-a89e-4d38-b5d7-0c5b40d6f26f" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "JavaObject id=o46" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Create and add statement: The name of the album is actually Help!\n", + "name_property_IRI = graph.addProperty(ex + \"name\")\n", + "name_literal = graph.addLiteral(\"Help!\")\n", + "\n", + "graph.addEdge(new_album_IRI, name_property_IRI, name_literal)\n", + "\n", + "# Create and add statement: The new album was released in 1965\n", + "xsd = \"http://www.w3.org/2001/XMLSchema#\"\n", + "release_property_IRI = graph.addProperty(ex + \"date\")\n", + "release_literal = graph.addLiteral(\"1965\", xsd + 'date')\n", + "\n", + "graph.addEdge(new_album_IRI, release_property_IRI, release_literal)\n", + "\n", + "\n", + "# Create and add statement: The Beatles is the creator of the album Help\n", + "artist_property_IRI = graph.addProperty(ex + \"artist\")\n", + "artist_IRI = graph.addLiteral(ex + \"The_Beatles\")\n", + "graph.addEdge(new_album_IRI, artist_property_IRI, artist_IRI)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "CTWzyXGUe64u", + "outputId": "7e2a01bd-3884-4694-a3c5-005a56fd2b60" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "@prefix xsd: .\n", + "@prefix ex: .\n", + "\n", + "ex:Help ex:artist \"http://example.com/The_Beatles\" ;\n", + " ex:date \"1965\"^^xsd:date ;\n", + " ex:name \"Help!\" ;\n", + " a ex:Album .\n", + "\n", + "\n" + ] + } + ], + "source": [ + "query = f'''@prefix ex: <{ex}>\n", + " CONSTRUCT {{ ?album ?p ?o }}\n", + " WHERE {{\n", + " VALUES ?album {{ ex:Help }}\n", + " ?album ?p ?o}} '''\n", + "\n", + "exec = corese.QueryProcess.create(graph)\n", + "\n", + "results = exec.query(query)\n", + "\n", + "results_ttl = corese.ResultFormat.create(results, corese.ResultFormat.TURTLE_FORMAT)\n", + "\n", + "print(results_ttl)" + ] + } + ], + "metadata": { + "colab": { + "provenance": [], + "toc_visible": true + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.14" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/python_examples/simple_query.py b/examples/simple_query.py old mode 100755 new mode 100644 similarity index 100% rename from python_examples/simple_query.py rename to examples/simple_query.py diff --git a/python_examples/example1.ipynb b/python_examples/example1.ipynb deleted file mode 100644 index 9055ddc..0000000 --- a/python_examples/example1.ipynb +++ /dev/null @@ -1,446 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [], - "source": [ - "import sys\n", - "sys.path.append('..\\\\src')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Py4J or JPype\n", - "\n", - "Demonstrate loading and querying data with CoreseAPI connected through `Py4J` or `JPype` packages." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Connect to Corese API" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [], - "source": [ - "python_to_java_bridge = 'py4j'" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2024-09-25 15:46:35,984 - INFO - JPype: CORESE is stopped\n", - "2024-09-25 15:46:36,030 - INFO - Py4J: Loading CORESE...\n", - "2024-09-25 15:46:50,983 - INFO - Py4J: CORESE is loaded\n" - ] - } - ], - "source": [ - "#%%timeit -n 1 -r 1\n", - "from pycorese.api import CoreseAPI\n", - "\n", - "corese = CoreseAPI(java_bridge=python_to_java_bridge)\n", - "corese.loadCorese()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### High-level API" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Run SELECT query" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
spo
0http://example.com/Please_Please_Mehttp://example.com/artisthttp://example.com/The_Beatles
1http://example.com/McCartneyhttp://example.com/artisthttp://example.com/Paul_McCartney
2http://example.com/Imaginehttp://example.com/artisthttp://example.com/John_Lennon
3http://example.com/Please_Please_Mehttp://example.com/date1963-03-22
4http://example.com/McCartneyhttp://example.com/date1970-04-17
\n", - "
" - ], - "text/plain": [ - " s p \\\n", - "0 http://example.com/Please_Please_Me http://example.com/artist \n", - "1 http://example.com/McCartney http://example.com/artist \n", - "2 http://example.com/Imagine http://example.com/artist \n", - "3 http://example.com/Please_Please_Me http://example.com/date \n", - "4 http://example.com/McCartney http://example.com/date \n", - "\n", - " o \n", - "0 http://example.com/The_Beatles \n", - "1 http://example.com/Paul_McCartney \n", - "2 http://example.com/John_Lennon \n", - "3 1963-03-22 \n", - "4 1970-04-17 " - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import os \n", - "data_path = os.path.abspath('data\\\\beatles.rdf')\n", - "\n", - "query = '''\n", - "SELECT *\n", - "WHERE {?s ?p ?o} LIMIT 5'''\n", - "\n", - "graph = corese.loadRDF(data_path) \n", - "results = corese.sparqlSelect(graph, query=query, return_dataframe=True)\n", - "\n", - "results" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Load inference rules " - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " s type\n", - "0 http://example.com/Please_Please_Me http://example.com/Album\n", - "1 http://example.com/McCartney http://example.com/Album\n", - "2 http://example.com/Imagine http://example.com/Album\n", - "3 http://example.com/The_Beatles http://example.com/Band\n", - "4 http://example.com/John_Lennon http://example.com/SoloArtist\n", - "5 http://example.com/Paul_McCartney http://example.com/SoloArtist\n", - "6 http://example.com/Ringo_Starr http://example.com/SoloArtist\n", - "7 http://example.com/George_Harrison http://example.com/SoloArtist\n", - "8 http://example.com/Love_Me_Do http://example.com/Song\n", - "Graph size: 29\n" - ] - } - ], - "source": [ - "corese.resetRuleEngine(graph)\n", - "query = \"select * where {?s a ?type} order by ?type\"\n", - "print(corese.sparqlSelect(graph, query=query))\n", - "print(\"Graph size: \", graph.graphSize())\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Adding inference rules to the Corese engine should change the results of the query by adding new triples." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Graph size: 33\n" - ] - } - ], - "source": [ - "corese.loadRuleEngine(graph, profile=corese.RuleEngine.Profile.RDFS)\n", - "print(\"Graph size: \", graph.graphSize())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's see what was added." - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " s type\n", - "0 http://example.com/Please_Please_Me http://example.com/Album\n", - "1 http://example.com/McCartney http://example.com/Album\n", - "2 http://example.com/Imagine http://example.com/Album\n", - "3 http://example.com/The_Beatles http://example.com/Band\n", - "4 http://example.com/John_Lennon http://example.com/Person\n", - "5 http://example.com/Paul_McCartney http://example.com/Person\n", - "6 http://example.com/Ringo_Starr http://example.com/Person\n", - "7 http://example.com/George_Harrison http://example.com/Person\n", - "8 http://example.com/John_Lennon http://example.com/SoloArtist\n", - "9 http://example.com/Paul_McCartney http://example.com/SoloArtist\n", - "10 http://example.com/Ringo_Starr http://example.com/SoloArtist\n", - "11 http://example.com/George_Harrison http://example.com/SoloArtist\n", - "12 http://example.com/Love_Me_Do http://example.com/Song\n", - "Graph size: 33\n" - ] - } - ], - "source": [ - "query = \"select * where {?s a ?type} order by ?type\"\n", - "print(corese.sparqlSelect(graph, query=query))\n", - "print(\"Graph size: \", graph.graphSize())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The inference was that the solo artist is also a person although it was not explicitly stated in the data." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Run CONSTRUCT query" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "\n", - "\n", - " \n", - " \n", - "\n", - " \n", - " \n", - "\n", - " \n", - " \n", - "\n", - " \n", - " \n", - "\n", - "\n" - ] - } - ], - "source": [ - "prefixes = '@prefix ex: '\n", - "contruct = '''CONSTRUCT {?Beatle a ex:BandMember }\n", - " WHERE { ex:The_Beatles ex:member ?Beatle}'''\n", - "\n", - "results = corese.sparqlConstruct(graph, prefixes=prefixes, query=contruct)\n", - "\n", - "print(results)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "By default, the CONSTRUCT query returns the RDF/XML format. For more concise format convert the results to Turtle." - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - " a .\n", - "\n", - " a .\n", - "\n", - " a .\n", - "\n", - " a .\n", - "\n", - "\n" - ] - } - ], - "source": [ - "ttl = corese.toTurtle(results)\n", - "\n", - "print(ttl)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Low-level API" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Adding triples manually to the graph." - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "01 ?s = ; ?type = ; \n", - "02 ?s = ; ?type = ; \n", - "03 ?s = ; ?type = ; \n", - "04 ?s = ; ?type = ; \n", - "\n" - ] - } - ], - "source": [ - "# Namespace\n", - "ex = \"http://example.com/\"\n", - "\n", - "# Get the graph from either Graph or DataManager objects\n", - "graph = graph.getGraph() \n", - "\n", - "# Create and add statement: Edith Piaf is an Singer\n", - "new_album_IRI = graph.addResource(ex + \"Help\")\n", - "rdf_Type_Property = graph.addProperty(corese.Namespaces.RDF + 'type')\n", - "album_type_IRI = graph.addResource(ex + \"Album\")\n", - "\n", - "graph.addEdge(new_album_IRI, rdf_Type_Property, album_type_IRI)\n", - "\n", - "query = f'''@prefix ex: <{ex}>\n", - " select * \n", - " where {{?s a ?type filter (?type = ex:Album) }} \n", - " order by ?type'''\n", - "\n", - "exec = corese.QueryProcess.create(graph)\n", - "\n", - "results = exec.query(query)\n", - "\n", - "print(results)\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.14" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} From d922cd326b8131382edd4ecce8242215a0d51576 Mon Sep 17 00:00:00 2001 From: Anna Bobasheva <33026767+AnnaBobasheva@users.noreply.github.com> Date: Fri, 6 Dec 2024 17:31:07 +0100 Subject: [PATCH 05/13] Updated README files, dependencies, and downgraded version (#12) --- INSTALL.md | 164 +++++++++++++++++++++++-------------- README.md | 94 +++++++++++++++++++-- VERSION.txt | 2 +- pkg/env/corese-python.yaml | 8 ++ pyproject.toml | 15 +++- 5 files changed, 207 insertions(+), 76 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 854aa3e..8350552 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,71 +1,120 @@ -# how to build/test/install pycorese java gateway and python wrapper from sources +# Development Installation -## build/install the python package +This document describes how to install the development environment for the **pycorese** package. -### prerequesite +**pycorese** is a Python wrapper for the [Corese-core](https://github.com/corese-stack/corese-core) Java library. To build and use this package, you need to have Java installed on your system. +**pycorese** provides two options for running the Corese Java code using the following Python packages to access Java objects: + +* [Py4J](https://www.py4j.org/) +* [JPype](https://jpype.readthedocs.io/en/latest/) + +That's the reason the installation process is a bit more complex than for a standard Python package. + +## Clone the GitHub repository + +```bash +git clone https://github.com/corese-stack/corese-python.git +cd corese-python ``` -pip install --upgrade pip setuptools wheel build + + +## Python build environment + +You can use the provided [conda](https://docs.conda.io/en/latest/) environment file to create a virtual environment with the necessary dependencies. + +```bash +conda env update -f pkg/env/corese-python.yaml +conda activate corese-python ``` +Or install the dependencies manually: + +* project dependencies: + ```bash + pip install py4j jpype1 pandas + ``` +* build dependencies: + ```bash + pip install --upgrade pip setuptools wheel build + ``` +* test dependencies: + ```bash + pip install pytest pytest-cov + ``` +* documentation dependencies: + ```bash + pip install sphinx pydata_sphinx_theme + ``` + + + + +## Java build environment + +To build the package Java Development Kit (JDK) version 11 or higher and the [Gradle](https://docs.gradle.org/current/userguide/userguide.html) build tool are required. + +If Java is not installed, visit the [official website](https://www.java.com/en/download/help/download_options.html) for installation instructions. + +Gradle can be installed as an extension to your IDE or as a standalone tool. + +* Gradle extension for VSCode is available from the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-gradle) -### clean all (not necessary if your just downloaded the sources) +* To install Gradle as a standalone tool, follow the instructions on the [official website](https://gradle.org/install/). + + +## Building the package + +Clean all the build directories (not necessary if you just downloaded the source code): ``` -rm -fr dist build resources +rm -fr dist build resources src/pycorese.egg-info ``` -### build the package +Build the package: ``` python -m build ``` -which build the packages into `./dist` +This command builds the packages into `./dist` directory. Note that the custom `sdist` command is implemented in [setup.py](./setup.py). -Remark: -- do not run `python setup.py` which will not build the full package -- the described install process will: +The custom `sdist` command adds the following steps: - 1/ compile the `corese-python-4.x.y-jar-with-dependencies.jar` file - 2/ download the `corese-core-4.x.y-jar-with-dependencies.jar` file from maven +* compile the `corese-python-x.y.z-jar-with-dependencies.jar` file using the Gradle build tool. This jar file is required to run Corese using the `Py4J` bridge. +* download the `corese-core-x.y.z-jar-with-dependencies.jar` file from the Maven repository. This jar file is required to run Corese using the `JPype` bridge. +* copy the jar files to the `./resources` directory. -- these two files are necessary to run the wrappers and are part of the distribution -### test -From the top directory, or in the `tests` sub-directory +> [!NOTE] +> - do not run `python setup.py` that will not build the full package. +> - the versions of `pycorese`, `corese-python`, `corese-core` are maintained separately. +> - the commands for the first two steps are provided in the [Obtaining Java libraries manually](#obtain-java-libraries-manually) section. -``` -pip install pytest -``` +## Testing the package + +From the top directory, or in the `./tests` sub-directory run the command: ``` pytest -v ``` -If a specific test fails, you can have more information, using: -(you need to know the filename, test class name, test name) +If a specific test fails, you can have more information, using the following command: -eg: ``` pytest tests/test_api.py::Test_api::test_bad_bridge ``` -### code coverage - -Install the coverage package: - -``` -pip install pytest-cov -``` +> [!NOTE] +> - substitute the filename, test class name, and test name with your specific test. -And run the test coverage: +Run the test coverage: ``` pytest --cov ``` -If you prefer a browsable coverage report: +For the HTML coverage report, run the following commands: ``` pytest --cov --cov-report=html @@ -73,69 +122,60 @@ open htmlcov/index.html ``` -### install the locally built package +## Installing the locally built package ``` -pip install dist/pycorese-1.0.1-py3-none-any.whl +pip install dist/pycorese-0.1.1-py3-none-any.whl ``` or ``` -pip install dist/pycorese-1.0.1.tar.gz +pip install dist/pycorese-0.1.1.tar.gz ``` -- verify your installation +## Verifying the installation ``` $ pip list | grep corese -pycorese 1.0.1 +pycorese 0.1.1 $ python -c 'import pycorese' ``` +> [!NOTE] +> - change the version number accordingly. -## Appendix 1: run local python example -### Conda environment +## Run a simple example -If necessary, we provide a conda environment: +Without installing the package you can run the following command (the default Java bridge is `py4j`): -```bash -conda env update -f pkg/env/corese-python.yaml -conda activate corese-python ``` - -This makes available the python libraries: `pandas`, `py4j`, `jpype1` - -### run a simple example using py4j bridge (without installing) - -``` -./python_examples/simple_query.py -j $PWD/build/libs/corese-python-4.6.0-jar-with-dependencies.jar +./examples/simple_query.py -j $PWD/build/libs/corese-python-4.6.0-jar-with-dependencies.jar ``` -Remark: to build this jar file, you must follow the Appendix 2 instructions - -### experimental: run a simple example using jpype bridge (without installing) - -We focus the development on the py4j wrapping. The (still provided) jpype interface -may still work (without garanty): +or change the bridge to `jpype`: ``` -./python_examples/simple_query.py -b jpype -j /somewhere/corese-core-4.6.0-jar-with-dependencies.jar +./examples/simple_query.py -b jpype -j $PWD/build/libs/corese-core-4.6.0-jar-with-dependencies.jar ``` + +> [!NOTE] +> - the jar files are obtained either by [building the package](#building-the-package) or [manually](#obtain-java-libraries-manually). +> - the primary development focus is on using the `py4j` bridge. -## Appendix 2: java compilation description -Remark: all these commands are launched then building/installing using the previous described process +## Obtain Java libraries manually -### build jar file locally +In case you want to build `corese-python-x.y.z-jar-with-dependencies.jar` Java library separately, use the following commands: ``` -./gradlew shadowJar +gradlew shadowJar ``` -### download +In case you want to download the `corese-core-x.y.z-jar-with-dependencies.jar` Java library separately, use the following commands: ``` -./gradlew downloadCoreseCore +gradlew downloadCoreseCore ``` +These tasks are defined in the [build.gradle.kts](./build.gradle.kts) file. \ No newline at end of file diff --git a/README.md b/README.md index 30b35ae..3a8e5d8 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,96 @@ -# pycorese +
+

pycorese

+
-pycorese is a python wrapper to the corese "Software platform for the Semantic Web of Linked Data" + +

+ Python API for CORESE Semantic Web platform +

-## Easy installation + +

+ Python Versions + PyPI version + Documentation + codecov + License: LGPL +

-``` + + + [Corese](https://corese-stack.github.io/corese-core) is a software platform implementing and extending the standards of the Semantic Web. It allows to create, manipulate, parse, serialize, query, reason, and validate RDF data. Corese is based on the W3C standards RDF, RDFS, OWL 2, SPARQL and SHACL. Corese is implemented as a set of open-source Java libraries. + +**pycorese** is a Python package that provides a simple way to integrate the [corese-core](https://github.com/corese-stack/corese-core) Java library into Python applications. + +**pycorese** provides an intuitive API to interact with Corese's capabilities such as storage, SPARQL engine, RDFS and OWL reasoning, and SHACL validation. + +**pycorese** unlocks the potential of Semantic Web stack for applications such as semantic data analysis, knowledge graph construction, and Machine Learning. + +## Installation + +**pycorese** can be easily installed via `pip`: + +```bash pip install pycorese ``` -This will install the python wrappers and the corese jar files +This process installs both the Python wrappers and the Corese Java libraries. To run the Java libraries, ensure that Java is installed on your system. A Java Runtime Environment (JRE) version 11 or higher is required. If Java is not installed, visit the [official website](https://www.java.com/en/download/help/download_options.html) for installation instructions. + + + +## Development installation + +To install **pycorese** from the current [GitHub repository](https://github.com/corese-stack/corese-python) follow the instructions from [INSTALL.md](https://github.com/corese-stack/corese-python/blob/main/INSTALL.md). + +## Usage -## Install from sources +Here is a simple example of how to use **pycorese** to load and query RDF data: -Follow the instuctions from `INSTALL.md` +```python +from pycorese.api import CoreseAPI + +corese = CoreseAPI() +corese.loadCorese() + +# Load RDF data +data = """ +@prefix ex: . +ex:John ex:hasFriend ex:Jane, ex:Jill. +ex:Jane ex:age 25 . +ex:Jill ex:age 40 . +""" + +graph = corese.loadRDF(data) + +# Query the data to find out who is John's younger friend +query = """ +PREFIX ex: +SELECT ?friend ?age +WHERE { + ?x ex:age ?ageX . + ?x ex:hasFriend ?friend . + ?friend ex:age ?age . + FILTER (?ageX > ?age) +} +""" + +results = corese.sparqlSelect(graph, query=query, return_dataframe=True) +print(results) +``` +Expected output: +``` + friend age +0 http://example.org/Jane 25 +``` + +See the [GitHub repository]((https://github.com/corese-stack/corese-python/examples)) for more examples. ## Documentation -- Corese documentation: https://corese-stack.github.io/corese-core -- Corese python wrapper: https://corese-stack.github.io/corese-python +- pycorese GitHub pages: https://corese-stack.github.io/corese-python +- Corese GitHub pages: https://corese-stack.github.io/corese-core + + +## Contributing + +Contributions are welcome! If you have any ideas, suggestions, or bug reports, please [open an issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/creating-an-issue) or [submit a pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request) on the [GitHub repository](https://github.com/corese-stack/corese-python). diff --git a/VERSION.txt b/VERSION.txt index 7dea76e..845639e 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.0.1 +0.1.4 diff --git a/pkg/env/corese-python.yaml b/pkg/env/corese-python.yaml index 98c0311..a8ae57e 100644 --- a/pkg/env/corese-python.yaml +++ b/pkg/env/corese-python.yaml @@ -3,8 +3,16 @@ channels: - conda-forge - defaults dependencies: + - python>=3.10 - pandas - py4j - pip - pip: - jpype1 + - setuptools + - wheel + - build + - pytest + - pytest-cov + - sphinx + - pydata_sphinx_theme diff --git a/pyproject.toml b/pyproject.toml index 15667e0..6fda984 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ authors = [ { name = "Jean-Luc Szpyrka", email = "jean-luc.szpyrka@inria.fr" }, { name = "Remi Ceres", email = "remi.ceres@inria.fr"} ] -description = "corese-python: Corese Python API for Corese SPARQL engine." +description = "pycorese: Python API for CORESE Semantic Web platform" keywords = ["Query Engine", "SPARQL", "SHACL", "RDF", "RDFS", "OWL", "Reasoning", "Knowledge Graph"] readme = "README.md" @@ -27,10 +27,16 @@ dependencies = [ ] classifiers = [ + "Development Status :: 4 - Beta", "License :: OSI Approved :: GNU Lesser General Public License v2 (LGPLv2)", + "Operating System :: OS Independent", "Intended Audience :: Science/Research", "Topic :: Scientific/Engineering", - "Programming Language :: Python :: 3" + "Topic :: Software Development :: Libraries", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ] [project.urls] @@ -59,7 +65,8 @@ where = ["src"] # Adding the Java jars to the package. # The jar files are built by the custom build step in the setup.py. -# The files are built/downloaded by the gradle build system -# and copied to the `resources` directory. +# One jar is built by the gradle build system, +# the other is downloaded from the maven repository. +# Both are copied to the `resources` directory. [tool.setuptools.data-files] "share/pycorese" = ["resources/*.jar"] From a373b1ecc5724d64922c1c7f42ab74ad6335b1f5 Mon Sep 17 00:00:00 2001 From: Anna Bobasheva <33026767+AnnaBobasheva@users.noreply.github.com> Date: Fri, 6 Dec 2024 17:46:59 +0100 Subject: [PATCH 06/13] Update INSTALL.md --- INSTALL.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 8350552..8c8ddf6 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -80,15 +80,14 @@ This command builds the packages into `./dist` directory. Note that the custom ` The custom `sdist` command adds the following steps: -* compile the `corese-python-x.y.z-jar-with-dependencies.jar` file using the Gradle build tool. This jar file is required to run Corese using the `Py4J` bridge. -* download the `corese-core-x.y.z-jar-with-dependencies.jar` file from the Maven repository. This jar file is required to run Corese using the `JPype` bridge. -* copy the jar files to the `./resources` directory. - - +* compiling the `corese-python-x.y.z-jar-with-dependencies.jar` file using the Gradle build tool. This jar file is required to run Corese using the `Py4J` bridge. +* downloading the `corese-core-x.y.z-jar-with-dependencies.jar` file from the Maven repository. This jar file is required to run Corese using the `JPype` bridge. +* copying the jar files to the `./resources` directory. > [!NOTE] > - do not run `python setup.py` that will not build the full package. -> - the versions of `pycorese`, `corese-python`, `corese-core` are maintained separately. +> - the versions of `pycorese` and Java libraries are maintained separately. +> - `corese-python` version should be the same as `corese-core` it depends on, for simplicity reasons. > - the commands for the first two steps are provided in the [Obtaining Java libraries manually](#obtain-java-libraries-manually) section. ## Testing the package @@ -178,4 +177,4 @@ In case you want to download the `corese-core-x.y.z-jar-with-dependencies.jar` J ``` gradlew downloadCoreseCore ``` -These tasks are defined in the [build.gradle.kts](./build.gradle.kts) file. \ No newline at end of file +These tasks are defined in the [build.gradle.kts](./build.gradle.kts) file. From 771db55918568a8537c15de464a46e31e3a062f1 Mon Sep 17 00:00:00 2001 From: Anna Bobasheva <33026767+AnnaBobasheva@users.noreply.github.com> Date: Mon, 16 Dec 2024 11:32:04 +0100 Subject: [PATCH 07/13] Updated Sphinx documentation (#14) * UPdated Sphinx documentation New Index.rst page, Old index.rst page became corese.rst. Added new pages: python_api/api_root.rst with autogenerated API doc. Added python_api/bridges.rst page The example1.ipynb notebook is rendered as a uset_guide. INSTALL.md is rendered as an installation page * Update pyproject.toml --- INSTALL.md | 8 +- README.md | 2 +- docs/source/.gitignore | 2 + docs/source/apis.rst | 17 ++- docs/source/conf.py | 150 +++++++++++------- docs/source/corese.rst | 103 +++++++++++++ docs/source/index.rst | 85 +++-------- docs/source/python_api/bridges.rst | 54 +++++++ docs/source/python_api/index.rst | 86 +++++++++++ pkg/env/corese-python.yaml | 9 +- pyproject.toml | 4 +- src/pycorese/api.py | 237 ++++++++++++++++------------- src/pycorese/py4J_bridge.py | 2 +- 13 files changed, 519 insertions(+), 240 deletions(-) create mode 100644 docs/source/.gitignore create mode 100644 docs/source/corese.rst create mode 100644 docs/source/python_api/bridges.rst create mode 100644 docs/source/python_api/index.rst diff --git a/INSTALL.md b/INSTALL.md index 8c8ddf6..c8f2469 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -87,7 +87,7 @@ The custom `sdist` command adds the following steps: > [!NOTE] > - do not run `python setup.py` that will not build the full package. > - the versions of `pycorese` and Java libraries are maintained separately. -> - `corese-python` version should be the same as `corese-core` it depends on, for simplicity reasons. +> - `corese-python` version should be the same as `corese-core` it depends on, for simplicity reasons. > - the commands for the first two steps are provided in the [Obtaining Java libraries manually](#obtain-java-libraries-manually) section. ## Testing the package @@ -134,7 +134,7 @@ pip install dist/pycorese-0.1.1.tar.gz ## Verifying the installation -``` +```bash $ pip list | grep corese pycorese 0.1.1 @@ -148,13 +148,13 @@ $ python -c 'import pycorese' Without installing the package you can run the following command (the default Java bridge is `py4j`): -``` +```bash ./examples/simple_query.py -j $PWD/build/libs/corese-python-4.6.0-jar-with-dependencies.jar ``` or change the bridge to `jpype`: -``` +```bash ./examples/simple_query.py -b jpype -j $PWD/build/libs/corese-core-4.6.0-jar-with-dependencies.jar ``` diff --git a/README.md b/README.md index 3a8e5d8..4da256d 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ **pycorese** is a Python package that provides a simple way to integrate the [corese-core](https://github.com/corese-stack/corese-core) Java library into Python applications. -**pycorese** provides an intuitive API to interact with Corese's capabilities such as storage, SPARQL engine, RDFS and OWL reasoning, and SHACL validation. +**pycorese** offers an intuitive API to interact with Corese's capabilities such as storage, SPARQL engine, RDFS and OWL reasoning, and SHACL validation. **pycorese** unlocks the potential of Semantic Web stack for applications such as semantic data analysis, knowledge graph construction, and Machine Learning. diff --git a/docs/source/.gitignore b/docs/source/.gitignore new file mode 100644 index 0000000..b19b9b1 --- /dev/null +++ b/docs/source/.gitignore @@ -0,0 +1,2 @@ +user_guide.ipynb +dev_install.md \ No newline at end of file diff --git a/docs/source/apis.rst b/docs/source/apis.rst index 1536ca1..69cf531 100644 --- a/docs/source/apis.rst +++ b/docs/source/apis.rst @@ -1,17 +1,18 @@ -CORESE APIs +.. The links for corese-command and corese-server are not available in the corese-stack at the moment. +.. Using the old repository documentation at https://wimmics.github.io/corese. + +Corese APIs ########### .. toctree:: :hidden: - Python API - .. grid:: 2 .. grid-item-card:: :shadow: sm :class-card: sd-rounded-3 - :link: https://corese-stack.github.io/corese-core/ + :link: https://corese-stack.github.io/corese-core/v4.6.0/java_api/library_root.html Corese Java API ^^^^^^^^^^^^^^^^^^^^^^^ @@ -31,7 +32,7 @@ CORESE APIs .. grid-item-card:: :shadow: sm :class-card: sd-rounded-3 - :link: https://corese-stack.github.io/corese-command/ + :link: https://wimmics.github.io/corese/cli_ref/cli_root.html Corese command-line interface ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -47,7 +48,7 @@ CORESE APIs .. grid-item-card:: :shadow: sm :class-card: sd-rounded-3 - :link: https://github.com/corese-stack/corese-server/ + :link: https://wimmics.github.io/corese/rest_api/api_root.html Corese REST API ^^^^^^^^^^^^^^^^^^^^^^^ @@ -62,7 +63,7 @@ CORESE APIs .. grid-item-card:: :shadow: sm :class-card: sd-rounded-3 - :link: https://github.com/corese-stack/corese-python/ + :link: python_api/api_root.html Corese Python API ^^^^^^^^^^^^^^^^^^^^^^^ @@ -73,4 +74,4 @@ CORESE APIs * run SPARQL queries (SELECT, CONSTRUCT, ASK, UPDATE) * validate RDF data against SHACL shapes - in development... + and more... diff --git a/docs/source/conf.py b/docs/source/conf.py index 0dc9c8f..decbb82 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -10,57 +10,49 @@ # add these directories to sys.path here. import pathlib import sys -import os +import shutil -sys.path.insert(0, pathlib.Path(__file__).parents[1].resolve().as_posix()) -sys.path.insert(0, pathlib.Path(__file__).parents[2].resolve().as_posix()) -#sys.path.insert(0, pathlib.Path(__file__).parents[2].joinpath('code').resolve().as_posix()) +# -- Path to the Python source code ------------------------------------------------ +sys.path.insert(0, pathlib.Path(__file__).parents[2].joinpath('src').resolve().as_posix()) +# -- Copy files for docs -------------------------------------------------- +# +# To avoid duplicating the information and symlinks +shutil.copyfile(pathlib.Path(__file__).parents[2].joinpath('INSTALL.md'), "dev_install.md") +shutil.copyfile(pathlib.Path(__file__).parents[2].joinpath('examples/example1.ipynb'), "user_guide.ipynb") -project = 'CORESE-PYTHON' +# -- Project information ----------------------------------------------------- +project = 'corese-python' copyright = '2024, WIMMICS' -author = 'WIMMICS' - -# -- General configuration --------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration - -extensions = [ - 'sphinx.ext.duration', # to display the duration of Sphinx processing - 'sphinx.ext.todo', # to include todo items in the documentation - # Uncomment the following lines if/when include the python code (not used in this project yet) - #'sphinx.ext.doctest', # to test code snippets in the documentation - #'sphinx.ext.autodoc', # to automatically generate documentation from docstrings - #'sphinx.ext.autosummary', # this extension generates function/method/attribute summary lists - #'sphinx.ext.autosectionlabel', # to automatically generate section labels - 'sphinx_multiversion', - 'sphinx_design', # to render panels - 'myst_parser', # to parse markdown - 'sphinxcontrib.mermaid', # to render mermaid diagrams - # Alternative ways to include markdown files, cannot be used together with myst_parser - # advantages of sphynx_mdinclude/m2r3: it can include partial markdown files - # - #'sphinx_mdinclude', # to include partial markdown files - #'m2r3', # to include markdown files - 'sphinx_copybutton', # to add copy buttons to code blocks - ] - -templates_path = ['_templates'] -exclude_patterns = [] +author = 'Corese Team' # The suffix(es) of source filenames. source_suffix = ['.rst', '.md'] +#exclude_patterns = [] -# -- Options for HTML output ------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] -html_theme = 'pydata_sphinx_theme' +# Add any paths that contain data files here, relative to this directory. html_static_path = ['_static'] +# Define the css files to include in the documentation html_css_files = [ "css/custom.css", ] + +# Define the js files to include in the documentation html_js_files = [] +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output +html_sidebars = { + #"corese": [], # syntax for hiding the sidebar +} + +# Tell sphinx what the pygments highlight language should be. +highlight_language = 'python' + # Project logo, to place at the top of the sidebar. html_logo = "_static/corese.svg" @@ -68,13 +60,14 @@ html_favicon = "_static/Corese-square-logo-transparent.svg" # Modify the title to get good social-media links -html_title = "CORESE-PYTHON" -html_short_title = "CORESE-PYTHON" +#html_title = "CORESE-PYTHON" +#html_short_title = "CORESE-PYTHON" # -- Theme Options ----------------------------------------------------------- # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the -# documentation. +# documentation. https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output +html_theme = 'pydata_sphinx_theme' html_theme_options = { "logo": { "image_relative": "_static/corese.svg", @@ -93,27 +86,78 @@ "switcher": {"json_url": "https://corese-stack.github.io/corese-python/switcher.json", "version_match": r"v\d+\.\d+\.\d+"} } -html_sidebars = { - "install": [], -} - -# -- MySt-parcer extension Options ------------------------------------------- -# https://myst-parser.readthedocs.io/en/latest/ - -myst_heading_anchors = 4 -myst_fence_as_directive = ["mermaid"] -# Tell sphinx what the primary language being documented is. -primary_domain = 'python' +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration +extensions = [ + 'sphinx.ext.duration', # to display the duration of Sphinx processing + 'sphinx.ext.todo', # to include todo items in the documentation + #'sphinx.ext.githubpages', # to deploy the documentation on GitHub pages ??? + 'sphinx.ext.viewcode', # to add links to the source code + 'sphinx.ext.doctest', # to test code snippets in the documentation + 'sphinx.ext.autodoc', # to automatically generate documentation from docstrings + 'sphinx.ext.autosummary', # this extension generates function/method/attribute summary lists + 'sphinx.ext.autosectionlabel', # to automatically generate section labels + 'sphinx.ext.napoleon', # to parse Google-style docstrings + 'sphinx_design', # to render panels + #'myst_parser', # to parse markdown + "myst_nb", # to parse jupyter notebooks and markdown files + #'sphinxcontrib.mermaid', # to render mermaid diagrams + # Alternative ways to include markdown files, cannot be used together with myst_parser + # advantages of sphynx_mdinclude/m2r3: it can include partial markdown files + # + #'sphinx_mdinclude', # to include partial markdown files + #'m2r3', # to include markdown files + 'sphinx_copybutton', # to add copy buttons to code blocks + 'sphinx_multiversion', # to build documentation for multiple versions + ] -# Tell sphinx what the pygments highlight language should be. -highlight_language = 'python' +# -- Options for sphinx.ext.autodoc / sphinx.ext.autosummary----------------------------- +# generate autosummary even if no references +#autodoc_default_flags = ["members", "inherited-members"] +autosummary_generate = True + +autodoc_default_options = { + #"member-order": "bysource", + #"special-members": "__init__", + # "undoc-members": True, + # "show-inheritance": True, + # "template": "_templates/base.rst", # Path to your template +} -# Setup the sphinx.ext.todo extension +# -- Options for sphinx.ext.napoleon---------------------------------------- +napoleon_google_docstring = True +napoleon_numpy_docstring = True +napoleon_use_admonition_for_notes = True +napoleon_use_rtype = False -# Set to false in the final version +# -- Setup the sphinx.ext.todo extension ------------------------------------ +# TODO: Set to false in the final version todo_include_todos = True +# -- sphinx-multiversion extension configuration ----------------------------------- # Optional: Exclude certain branches or tags from multi-versioning #smv_branch_whitelist = r'develop' # TODO Build documentation only for feature/retrieve-doc, must be replaced with "main" for production smv_tag_whitelist = r'^v\d+\.\d+.*$' # Only build tags that match version pattern like v1.0 + +# -- MyST-NB configuration --------------------------------------------------- +# https://myst-nb.readthedocs.io/en/latest/ +# Take the example notebook as-is, without executing it +nb_execution_mode = "off" + +# Suppress warnings +suppress_warnings = [ + "myst.xref_missing", # Suppress warnings about missing references after fixing the one you can fix + "mystnb.unknown_mime_type" # Suppress warnings about Google Colab button in the notebook +] + +# Substitute the relative path in the markdown file for the GitHub repo root URL +def preprocess_markdown(app, docname, source): + base_url = "https://github.com/corese-stack/corese-python/blob/main/" + if docname == "dev_install": # Replace with the actual document using the Markdown file + content = source[0] + # Replace relative paths with appropriate links + source[0] = content.replace("./" , base_url) + +def setup(app): + app.connect("source-read", preprocess_markdown) \ No newline at end of file diff --git a/docs/source/corese.rst b/docs/source/corese.rst new file mode 100644 index 0000000..297afea --- /dev/null +++ b/docs/source/corese.rst @@ -0,0 +1,103 @@ +.. CORESE documentation master file, created by + sphinx-quickstart on Tue Apr 16 14:51:03 2024. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + + +.. image:: _static/corese.svg + :align: center + :width: 400px + +.. centered:: Software platform for the Semantic Web of Linked Data + +Corese is a software platform implementing and extending the standards of the Semantic Web. It allows to create, manipulate, parse, serialize, query, reason and validate RDF data. + + + +.. Define named hyperlinks for the references of W3C standards +.. _RDF: https://www.w3.org/RDF/ +.. _RDFS: https://www.w3.org/2001/sw/wiki/RDFS +.. _SPARQL1.1 Query & Update: https://www.w3.org/2001/sw/wiki/SPARQL +.. _OWL RL: https://www.w3.org/2005/rules/wiki/OWLRL +.. _SHACL: https://www.w3.org/TR/shacl/ + +.. Define named hyperlinks for the references of extensions +.. _STTL SPARQL: ./_static/extensions/sttl.html +.. _SPARQL Rule: ./_static/extensions/rule.html +.. _LDScript: ./_static/extensions/ldscript.html + +.. Original location of the extensions documentation +.. .. _STTL SPARQL: https://files.inria.fr/corese/doc/sttl.html +.. .. _SPARQL Rule: https://files.inria.fr/corese/doc/rule.html +.. .. _LDScript: https://files.inria.fr/corese/doc/ldscript.html + + +.. ############################################################################# +.. The statements below are to produce the grid of cards in the home page +.. grid:: 2 + + .. grid-item-card:: + :shadow: sm + :class-card: sd-rounded-3 + + Corese implements W3C standards and extensions + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * W3C standards + * `RDF`_ + * `RDFS`_ + * `SPARQL1.1 Query & Update`_ + * `OWL RL`_ + * `SHACL`_ + * Extensions + * `STTL SPARQL`_ + * `SPARQL Rule`_ + * `LDScript`_ + + .. grid-item-card:: + :shadow: sm + :class-card: sd-rounded-3 + + Corese offers several interfaces + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * `corese-core `_: Java library to process RDF data and use Corese features via an API. + * `corese-server `_: Tool to easily create, configure and manage SPARQL endpoints. + * `corese-GUI `_: Graphical interface that allows an easy and visual use of Corese features. + * `corese-python (beta) `_: Python wrapper for accessing and manipulating RDF data with Corese features using py4j. + * `corese-command (beta) `_: Command Line Interface for Corese that allows users to interact with Corese features from the terminal. + +.. .. raw:: html + +..

Contributions and discussions

+ +.. .. _discussion forum: https://github.com/Wimmics/corese/discussions/ +.. .. _issue reports: https://github.com/Wimmics/corese/issues/ +.. .. _pull requests: https://github.com/Wimmics/corese/pulls/ + +.. For support questions, comments, and any ideas for improvements you’d like to discuss, please use our `discussion forum`_. We welcome everyone to contribute to `issue reports`_, suggest new features, and create `pull requests`_. + + +.. ############################################################################# +.. The statements below are to produce the title of the page in the tab + and a menu with the links to the pages of the documentation + +.. raw html below is used to hide the title of the page but retain it in the + tab title. https://github.com/sphinx-doc/sphinx/issues/8356 +.. raw:: html + +
+ +About Corese +==================== + +.. raw:: html + +
+ +.. toctree:: + :maxdepth: 2 + + Corese APIs + +.. Install +.. User Guide +.. API diff --git a/docs/source/index.rst b/docs/source/index.rst index af9dad6..2fb7704 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,77 +1,40 @@ -.. CORESE documentation master file, created by - sphinx-quickstart on Tue Apr 16 14:51:03 2024. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - +.. pycorese documentation master file, created by + sphinx-quickstart on Thu Oct 14 2023. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. .. image:: _static/corese.svg :align: center :width: 400px -.. centered:: Software platform for the Semantic Web of Linked Data - -Corese is a software platform implementing and extending the standards of the Semantic Web. It allows to create, manipulate, parse, serialize, query, reason and validate RDF data. - - +.. centered:: Python API for Corese Semantic Web platform .. Define named hyperlinks for the references of W3C standards +.. _Corese: corese.html +.. _corese-core: https://github.com/corese-stack/corese-core + .. _RDF: https://www.w3.org/RDF/ .. _RDFS: https://www.w3.org/2001/sw/wiki/RDFS -.. _SPARQL1.1 Query & Update: https://www.w3.org/2001/sw/wiki/SPARQL +.. _SPARQL: https://www.w3.org/2001/sw/wiki/SPARQL .. _OWL RL: https://www.w3.org/2005/rules/wiki/OWLRL .. _SHACL: https://www.w3.org/TR/shacl/ -.. Define named hyperlinks for the references of extensions -.. _STTL SPARQL: ./_static/extensions/sttl.html -.. _SPARQL Rule: ./_static/extensions/rule.html -.. _LDScript: ./_static/extensions/ldscript.html +`Corese`_ is a software platform implementing and extending the standards of the Semantic Web. It allows to create, manipulate, parse, serialize, query, reason, and validate RDF data. Corese is based on the W3C standards `RDF`_, `RDFS`_, `OWL RL`_, `SPARQL`_ and `SHACL`_. Corese is implemented as a set of open-source Java libraries. -.. Original location of the extensions documentation -.. .. _STTL SPARQL: https://files.inria.fr/corese/doc/sttl.html -.. .. _SPARQL Rule: https://files.inria.fr/corese/doc/rule.html -.. .. _LDScript: https://files.inria.fr/corese/doc/ldscript.html +**pycorese** is a Python package that provides a simple way to integrate the `corese-core`_ Java library into Python applications. +**pycorese** offers an intuitive API to interact with Corese's capabilities such as storage, SPARQL engine, RDFS and OWL reasoning, and SHACL validation. -.. ############################################################################# -.. The statements below are to produce the grid of cards in the home page -.. grid:: 2 - - .. grid-item-card:: - :shadow: sm - :class-card: sd-rounded-3 - - Corese implements W3C standards and extensions - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - * W3C standards - * `RDF`_ - * `RDFS`_ - * `SPARQL1.1 Query & Update`_ - * `OWL RL`_ - * `SHACL`_ - * Extensions - * `STTL SPARQL`_ - * `SPARQL Rule`_ - * `LDScript`_ - - .. grid-item-card:: - :shadow: sm - :class-card: sd-rounded-3 - - Corese offers several interfaces - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - * `corese-core `_: Java library to process RDF data and use Corese features via an API. - * `corese-server `_: Tool to easily create, configure and manage SPARQL endpoints. - * `corese-GUI `_: Graphical interface that allows an easy and visual use of Corese features. - * `corese-python (beta) `_: Python wrapper for accessing and manipulating RDF data with Corese features using py4j. - * `corese-command (beta) `_: Command Line Interface for Corese that allows users to interact with Corese features from the terminal. +**pycorese** unlocks the potential of Semantic Web stack for applications such as semantic data analysis, knowledge graph construction, and Machine Learning. .. raw:: html

Contributions and discussions

-.. _discussion forum: https://github.com/Wimmics/corese/discussions/ -.. _issue reports: https://github.com/Wimmics/corese/issues/ -.. _pull requests: https://github.com/Wimmics/corese/pulls/ + +.. _discussion forum: https://github.com/corese-stack/corese-python/discussions/ +.. _issue reports: https://github.com/corese-stack/corese-python/issues/ +.. _pull requests: https://github.com/corese-stack/corese-python/pulls/ For support questions, comments, and any ideas for improvements you’d like to discuss, please use our `discussion forum`_. We welcome everyone to contribute to `issue reports`_, suggest new features, and create `pull requests`_. @@ -80,13 +43,13 @@ For support questions, comments, and any ideas for improvements you’d like to .. The statements below are to produce the title of the page in the tab and a menu with the links to the pages of the documentation -.. raw html below is used to hide the title of the page but retain it in the +.. raw html below is used to hide the title of the page but retain it in the tab title. https://github.com/sphinx-doc/sphinx/issues/8356 .. raw:: html
-CORESE documentation +pycorese doc =================================== .. raw:: html @@ -94,10 +57,10 @@ CORESE documentation
.. toctree:: + :maxdepth: 2 :hidden: - Installation - User Guide - API Reference - Demo - + User Guide + API + Install + About Corese diff --git a/docs/source/python_api/bridges.rst b/docs/source/python_api/bridges.rst new file mode 100644 index 0000000..940acc4 --- /dev/null +++ b/docs/source/python_api/bridges.rst @@ -0,0 +1,54 @@ +Java bridge packages +==================== + +Corese is a stack of Java libraries that implement the standards of the Semantic Web. +To run and access Java libraries we tested two off-the-shelf packages: + +- `py4j `_ - this package establishes a bridge between Python and Java using sockets (IPC). The +**corese-python** Java library implements the listener and wrapper for ``corese-core`` library and is built with ``gradle`` specifically for **pycorese**. + +- `jpype `_ - this package utilizes a single shared memory space by embedding the JVM directly into the Python process. The actual ``corese-core`` Java library is downloaded from the Maven repository. + +At first we tried both packages to decide which one is better for our purposes. We found that both `py4j` and `jpype` have their pros and cons. See the comparison table below: + +.. list-table:: Compare Python- Java bridge packages + :widths: 24 38 38 + :header-rows: 1 + + * - + - JPype + - Py4J + + * - Core Technology + - JNI (direct JVM embedding) + - Reflection & Socket IPC + + * - Communication + - Shared memory (in-process) + - Socket based (out-of-process) + + * - Performance + - High (no serialization) + - Moderate (due to IPC overhead) + + * - Setup + - Requires JVM in-process + - *Requires a GatewayServer* + + * - Use case + - High-performance scenarios + - Lightweight, flexible usage + + * - Crash Tolearnce + - *Low: JVM crash affects Python (shared process) and cannot be restarted on its own* + - High: JVM and Python are separate processes, so a JVM crash won't take down Python. + + * - Distribution + - Download ``corese-core-{version}.jar`` + - Build ``corese-python-{version}.jar`` + +Although ``jpype`` is more performant and does not require an extra wrapper library, we decided to use ``py4j`` as a default because it is more crash tolerant. The main reason is that ``jpype`` requires the JVM to be embedded in the Python process and prohibits the restart of this subprocess in case of a crash (by design). This could be a critical issue for long-running applications or services. + +However we decided to leave both bridges in the package and let the user choose the one that fits their needs better. + +Both Java Libraries are installed with the package and can be used interchangeably. See the `User Guide <../user_guide.html>`_. diff --git a/docs/source/python_api/index.rst b/docs/source/python_api/index.rst new file mode 100644 index 0000000..b847899 --- /dev/null +++ b/docs/source/python_api/index.rst @@ -0,0 +1,86 @@ +.. .. currentmodule:: pycorese + + +Python API Reference +=================================== + +**pycorese** is a Python wrapper for accessing and manipulating RDF data with Corese features connected by one of the java bridge packages: ``py4j`` or ``jpype``. + +.. note:: + + **pycorese** is still in beta version and is under active development. The API may change in future releases. + + +In the following sections, you will find the documentation of the Python API of **pycorese**. + +High-level API +-------------- + +HIgh-level API is a set of convenience methods to facilitate the common tasks of working with Knowledge Graphs. + +.. automodule:: pycorese.api + :members: CoreseAPI + :undoc-members: + + +.. contents:: + :local: + :depth: 2 + +Low-level API +------------- + +Low-level API is a subset of ``corese-core`` classes exposed as Python objects. These are dynamically created classes +and can be accessed only after the Corese engine is loaded + +For the details of these classes and their methods, please refer to the `Corese Java documentation `_. + +.. autoattribute:: pycorese.api.CoreseAPI.Graph + :annotation: + + Corese ``fr.inria.corese.core.Graph`` object. + +.. autoattribute:: pycorese.api.CoreseAPI.Load + :annotation: + + Corese ``fr.inria.corese.core.load.Load`` object. + +.. autoattribute:: pycorese.api.CoreseAPI.QueryProcess + :annotation: + + Corese ``fr.inria.corese.core.query.QueryProcess`` object. + +.. autoattribute:: pycorese.api.CoreseAPI.ResultFormat + :annotation: + + Corese ``fr.inria.corese.core.print.ResultFormat`` object. + +.. autoattribute:: pycorese.api.CoreseAPI.RuleEngine + :annotation: + + Corese ``fr.inria.corese.core.rule.RuleEngine`` object. + +.. autoattribute:: pycorese.api.CoreseAPI.Transformer + :annotation: + + Corese ``fr.inria.corese.core.transform.Transformer`` object. + +.. autoattribute:: pycorese.api.CoreseAPI.Shacl + :annotation: + + Corese ``fr.inria.corese.core.shacl.Shacl`` object. + +.. autoattribute:: pycorese.api.CoreseAPI.DataManager + :annotation: + + Corese ``fr.inria.corese.core.storage.api.dataManager.DataManager`` object. + +.. autoattribute:: pycorese.api.CoreseAPI.CoreseGraphDataManager + :annotation: + + Corese ``fr.inria.corese.core.storage.CoreseGraphDataManager`` object. + +.. toctree:: + :maxdepth: 2 + + About Java bridges \ No newline at end of file diff --git a/pkg/env/corese-python.yaml b/pkg/env/corese-python.yaml index a8ae57e..08dd2de 100644 --- a/pkg/env/corese-python.yaml +++ b/pkg/env/corese-python.yaml @@ -6,6 +6,9 @@ dependencies: - python>=3.10 - pandas - py4j + - sphinx>=7.1.2 + - sphinx-multiversion>=0.2.4 + - sphinx-design==0.5.0 - pip - pip: - jpype1 @@ -14,5 +17,7 @@ dependencies: - build - pytest - pytest-cov - - sphinx - - pydata_sphinx_theme + - pydata_sphinx_theme>=0.14.4 + - sphinx-copybutton>=0.5.0 + - myst-parser==2.0.0 + - myst_nb==1.1.2 diff --git a/pyproject.toml b/pyproject.toml index 6fda984..e9501c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,9 +10,11 @@ version = { file = "VERSION.txt" } name = "pycorese" dynamic = ["version"] authors = [ + { name = "Corese Team", email = "corese@inria.fr" }, { name = "Anna Bobasheva", email = "anna.bobasheva@inria.fr" }, { name = "Jean-Luc Szpyrka", email = "jean-luc.szpyrka@inria.fr" }, - { name = "Remi Ceres", email = "remi.ceres@inria.fr"} + { name = "Remi Ceres", email = "remi.ceres@inria.fr"}, + { name = "Erwan Demairy", email = "erwan.demairy@inria.fr"}, ] description = "pycorese: Python API for CORESE Semantic Web platform" keywords = ["Query Engine", "SPARQL", "SHACL", "RDF", diff --git a/src/pycorese/api.py b/src/pycorese/api.py index 5cb47fd..c333fd0 100644 --- a/src/pycorese/api.py +++ b/src/pycorese/api.py @@ -1,6 +1,3 @@ -"""The module provides the capability to launch corese-python jar.""" - - import pandas as pd from io import StringIO import os @@ -29,14 +26,20 @@ def _is_turtle(content): class CoreseAPI: """ - Python implementation of Corese API. - - :param bridge: Bridge name to use for Java integration ('py4j' or 'jpype'). Default is 'py4j'. + Simplified API to leverage functionality of Corese Java library (``corese-core``). + + Parameters + ---------- + java_bridge : str, optional + Package name to use for Java integration. Options: 'py4j', 'jpype'. Default is 'py4j'. + corese_path : str, optional + Path to the corese-python library. If not specified (default), the jar + file that was installed with the package is used. """ def __init__(self, java_bridge: str = 'py4j', - corese_path: str = None): + corese_path: str|None = None): if java_bridge.lower() not in ['py4j', 'jpype']: raise ValueError('Invalid java bridge. Only "py4j" and "jpype" are supported.') @@ -46,24 +49,39 @@ def __init__(self, self.java_gateway = None self._bridge = None - self.Graph = None - self.QueryProcess = None - self.ResultFormat = None - self.Load = None + # This is a minimum set of Corese classes required for the API to work + self.Graph = None # Corese ``fr.inria.corese.core.Graph`` object + self.QueryProcess = None # Corese ``fr.inria.corese.core.query.QueryProcess`` object + self.ResultFormat = None # Corese ``fr.inria.corese.core.print.ResultFormat`` object + self.Load = None # Corese ``fr.inria.corese.core.load.Load`` object + self.RuleEngine = None # Corese ``fr.inria.corese.core.rule.RuleEngine`` object + self.Transformer = None # Corese ``fr.inria.corese.core.transform.Transformer`` object + self.Shacl = None # Corese ``fr.inria.corese.core.shacl.Shacl`` object + self.DataManager = None # Corese ``fr.inria.corese.core.storage.api.dataManager.DataManager`` object + self.CoreseGraphDataManager = None # Corese ``fr.inria.corese.core.storage.CoreseGraphDataManager`` object + self.CoreseGraphDataManagerBuilder = None # Corese ``fr.inria.corese.core.storage.CoreseGraphDataManagerBuilder`` object - def coreseVersion(self): + + def coreseVersion(self)-> str|None: """ - returns the corese-version + Get the version of the corese-core library. - Remark: corese engine must be loaded first. + Notes + ----- + Corese library must be loaded first. - TODO: implement this to call the coreseVersion() from - the corese engine (at the moment this method is staic and - may return bad result) + Returns + ------- + str + The version of the ``corese-core`` library used. If the library is not loaded, returns None. """ + #TODO: implement this to call the coreseVersion() from + # the corese engine (at the moment this method is static and + # may return bad result) + if self._bridge is None: - print(f"Corese engine not loaded yet") + print("Corese engine is not loaded yet") return None return self._bridge.coreseVersion() @@ -75,7 +93,9 @@ def unloadCorese(self): It's not necessary to call this method, as the library is automatically unloaded when the Python interpreter exits. - WARNING: After unloading Corese bridged by JPype it is not possible to restart it. + Warning + ------- + After unloading Corese bridged by ``JPype`` it is not possible to restart it. """ self._bridge.unloadCorese() @@ -87,8 +107,9 @@ def unloadCorese(self): self.Load = None def loadCorese(self) -> None: - """Load Corese library into JVM and expose the Corese classes.""" - + """Load Corese library into JVM and expose the Corese classes. + """ + #TODO: refactor if self.java_bridge == 'py4j': from .py4J_bridge import Py4JBridge @@ -136,21 +157,20 @@ def loadCorese(self) -> None: #TODO: Add support for the other RDF formats def loadRDF(self, rdf: str, graph=None)-> object: """ - Load RDF file/string into Corese graph. + Load RDF file/string into Corese graph. Supported formats are RDF/XML and Turtle. Parameters ---------- - rdf: str - Path to the RDF file or RDF content. - graph : object (fr.inria.corese.core.Graph or - fr.inria.corese.core.storage.CoreseGraphDataManager), optional - Corese graph object. Default is None. + rdf : str + Path to the RDF file or a string with RDF content. + graph : object, optional + Corese object of either ``fr.inria.corese.core.Graph`` or ``fr.inria.core.storage.CoreseGraphDataManager`` type. + If an object is not provided (default), new Graph and GraphManager will be created. Returns ------- - object (fr.inria.corese.core.Graph or - fr.inria.core.storage.CoreseGraphDataManager) - Corese Graph object. + object + Corese ``fr.inria.core.storage.CoreseGraphDataManager`` object. """ if not self.java_gateway: self.loadCorese() @@ -179,56 +199,52 @@ def loadRDF(self, rdf: str, graph=None)-> object: return graph_mgr def loadRuleEngine(self, graph: object, - profile: object, - replace:bool = False)-> object: - """ - Load rule engine for the given graph. - - Parameters - ---------- - graph : object (fr.inria.corese.core.Graph or fr.inria.core.storage.CoreseGraphDataManager) - Corese graph object or DataManager. - profile : object - Profile object for the rule engine. Accepted values: - - RuleEngine.Profile.RDFS - - RuleEngine.Profile.OWLRL - - RuleEngine.Profile.OWLRL_LITE - - RuleEngine.Profile.OWLRL_EXT - replace : bool, optional - Replace the existing rule engine. Default is False. - - Returns - ------- - object (fr.inria.core.rule.RuleEngine) - RuleEngine object. - """ - assert self.RuleEngine, 'Corese classes are not loaded properly.' - assert graph, 'Graph object is required.' - assert profile, 'Profile object is required.' - #TODO: assert profile is valid - - if replace: - self.resetRuleEngine(graph) - - rule_engine = self.RuleEngine.create(graph) - - rule_engine.setProfile(profile) - rule_engine.process() - - return rule_engine - - def resetRuleEngine(self, graph: object)-> None: + profile: object, + replace:bool = False)-> object: """ - Reset the rule engine for the given graph. + Load the rule engine for a given graph. Parameters ---------- - graph : object (fr.inria.corese.core.Graph or fr.inria.core.storage.CoreseGraphDataManager) - Corese graph object or DataManager. + graph : object + Corese Graph or DataManager object + profile : object + Profile object for the rule engine. Accepted values: + ``RuleEngine.Profile.RDFS``, + ``RuleEngine.Profile.OWLRL``, + ``RuleEngine.Profile.OWLRL_LITE``, + ``RuleEngine.Profile.OWLRL_EXT`` + replace : bool, optional + Replace the existing rule engine. Default is False. Returns ------- - None + object + Corese ``fr.inria.core.rule.RuleEngine`` object. + """ + assert self.RuleEngine, 'Corese classes are not loaded properly.' + assert graph, 'Graph object is required.' + assert profile, 'Profile object is required.' + #TODO: assert profile is valid + + if replace: + self.resetRuleEngine(graph) + + rule_engine = self.RuleEngine.create(graph) + + rule_engine.setProfile(profile) + rule_engine.process() + + return rule_engine + + def resetRuleEngine(self, graph: object)-> None: + """ + Reset the rule engine for a given graph. + + Parameters + ---------- + graph : object + Corese Graph or DataManager object """ assert self.RuleEngine, 'Corese classes are not loaded properly.' assert graph, 'Graph object is required.' @@ -241,23 +257,24 @@ def sparqlSelect(self, graph: object, query: str ='SELECT * WHERE {?s ?p ?o} LIMIT 5', return_dataframe: bool =True)-> object|pd.DataFrame: """ - Execute SPARQL SELECT or ASK query on Corese graph. + Execute SPARQL SELECT or ASK query on Corese graph. Optionally return the result as DataFrame. Parameters ---------- - graph : object (fr.inria.corese.core.Graph) - Corese graph object. - prefixes : str or list - SPARQL prefixes. Default is None. - query : str - SPARQL query. Default is 'SELECT * WHERE {?s ?p ?o} LIMIT 5'. - return_dataframe : bool, optional. Default is True. + graph : object + Corese Graph or DataManager object + prefixes : str or list, optional + namespace prefixes. Default is None. + query : str, optional + SPARQL query. By default five first triples of the graph are returned. + return_dataframe : bool, optional + Return the result as a DataFrame. Default is True. Returns ------- - object (fr.inria.core.print.ResultFormat) - Result of the SPARQL - + object or pd.DataFrame + Result of the SPARQL query in CSV-formatted ``fr.inria.core.print.ResultFormat`` + object or a DataFrame. """ assert self.QueryProcess, 'Corese classes are not loaded properly.' assert self.ResultFormat, 'Corese classes are not loaded properly.' @@ -285,21 +302,20 @@ def sparqlSelect(self, graph: object, def toDataFrame(self, queryResult: object, dtypes: list|dict|None = None)-> pd.DataFrame: """ - Convert Corese ResultFormat object to pandas DataFrame. + Convert Corese ResultFormat object to ``pandas.DataFrame``. Parameters ---------- - queryResult : csv resultFormat object (fr.inria.core.print.ResultFormat) - ResultFormat object. + queryResult : object + CSV-formatted ``fr.inria.core.print.ResultFormat`` object. dtypes : list or dict, optional - Data types for the columns in the format required by Pandas - read_csv method https://pandas.pydata.org/docs/reference/api/pandas.read_csv.html. - Default is None. + Optional column data types for the columns in the format as in ``panads.read_csv`` method. + https://pandas.pydata.org/docs/reference/api/pandas.read_csv.html Returns ------- pd.DataFrame - Result in DataFrame format. + Corese object converted to a DataFrame. """ assert self.ResultFormat, 'Corese classes are not loaded properly.' @@ -325,18 +341,18 @@ def sparqlConstruct(self, graph: object, Parameters ---------- - graph : object (fr.inria.corese.core.Graph) - Corese graph object. - prefixes : str or list - SPARQL prefixes. Default is None. - query : str + graph : object + Corese Graph or DataManager object + prefixes : str or list, optional + namespace prefixes. Default is None. + query : str, optional SPARQL query. Default is empty string resulting in empty graph. merge : bool, optional - Merge the result with the existing graph. Default is False. + Option to merge the result with the existing graph. Default is False. Returns ------- - object (fr.inria.core.print.ResultFormat) + object Result of the SPARQL CONSTRUCT query in RDF/XML format. """ assert self.QueryProcess, 'Corese classes are not loaded properly.' @@ -367,14 +383,15 @@ def toTurtle(self, rdf:object)-> str: Parameters ---------- - rdf : object (fr.inria.corese.core.Graph) - Corese graph object. + rdf : object + Corese RDF object Returns ------- str RDF in Turtle format. """ + assert self.Transformer, 'Corese classes are not loaded properly.' # TODO: ASk Remi about getGraph, the Graph and the right way to do the transformation @@ -390,20 +407,22 @@ def shaclValidate(self, graph: object, """ Validate RDF graph against SHACL shape. - This Version supports only Turtle format. + This version supports only Turtle format to define a SHACL shape. Parameters ---------- - graph : object (fr.inria.corese.core.Graph) - Corese graph object. - shacl_shape_ttl : str - SHACL shape in Turtle format. + graph : object + Corese Graph or DataManager object prefixes : str or list, optional - Prefixes. Default is None. + namespace prefixes. Default is None. + shacl_shape_ttl : str, optional + SHACL shape in Turtle format. If not provided, the validation will be skipped. + return_dataframe : bool, optional + Return the validation report as a DataFrame. Default is False. Returns ------- - str + object SHACL validation report in Turtle format. """ assert self.Shacl, 'Corese classes are not loaded properly.' @@ -442,7 +461,7 @@ def shaclValidate(self, graph: object, # Parse validation report def shaclReportToDataFrame(self, validation_report: str)-> pd.DataFrame: """ - Convert SHACL validation report to pandas DataFrame. + Convert SHACL validation report to ``pandas.DataFrame``. Parameters ---------- @@ -452,7 +471,7 @@ def shaclReportToDataFrame(self, validation_report: str)-> pd.DataFrame: Returns ------- pd.DataFrame - Validation report in DataFrame format. + Validation report as a DataFrame. """ prefix_shacl = f'@prefix sh: <{self.Namespaces.SHACL}> .' diff --git a/src/pycorese/py4J_bridge.py b/src/pycorese/py4J_bridge.py index 4f0ab91..f4f9aa6 100644 --- a/src/pycorese/py4J_bridge.py +++ b/src/pycorese/py4J_bridge.py @@ -68,7 +68,7 @@ def coreseVersion(self): pass if version is None: - loggingWarning(f"Py4j: the CORESE library is too old. coreseVersion() is available since 4.6.0 only.") + logging.Warning(f"Py4j: the CORESE library is too old. coreseVersion() is available since 4.6.0 only.") return version From 8094112c909cfc62733cb5fb75893bbdc785c601 Mon Sep 17 00:00:00 2001 From: Anna Bobasheva <33026767+AnnaBobasheva@users.noreply.github.com> Date: Mon, 16 Dec 2024 11:36:03 +0100 Subject: [PATCH 08/13] Update requirements.txt --- docs/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/requirements.txt b/docs/requirements.txt index b051c24..fec0404 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -6,6 +6,7 @@ docutils<0.21,>=0.18.1 Jinja2>=3.0 sphinx-design==0.5.0 myst-parser==2.0.0 +myst_nb==1.1.2 sphinxcontrib-mermaid==0.9.2 breathe==4.35.0 exhale==0.3.7 From 9324b1a3e5259395c00d7462e97af4066ce5d6a8 Mon Sep 17 00:00:00 2001 From: Anna Bobasheva <33026767+AnnaBobasheva@users.noreply.github.com> Date: Tue, 17 Dec 2024 10:27:17 +0100 Subject: [PATCH 09/13] Update sphinx-dev.yml Added the installation of the packages from the docs/requirements.txt in case a new extension is added. --- .github/workflows/sphinx-dev.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/sphinx-dev.yml b/.github/workflows/sphinx-dev.yml index e9a6a33..49fda49 100644 --- a/.github/workflows/sphinx-dev.yml +++ b/.github/workflows/sphinx-dev.yml @@ -27,6 +27,7 @@ jobs: - name: Build Sphinx documentation with multiversion run: | source /builds/miniconda3/etc/profile.d/conda.sh + pip install -r docs/requirements.txt conda activate corese-core-documentation sphinx-build docs/source/ build/html/dev/ ghp-import build/html/dev/ -p -x dev From 203b617575b07748b598f56394019d40b96c0639 Mon Sep 17 00:00:00 2001 From: Anna Bobasheva <33026767+AnnaBobasheva@users.noreply.github.com> Date: Tue, 17 Dec 2024 10:34:59 +0100 Subject: [PATCH 10/13] Revert "Update sphinx-dev.yml" This reverts commit 9324b1a3e5259395c00d7462e97af4066ce5d6a8. --- .github/workflows/sphinx-dev.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/sphinx-dev.yml b/.github/workflows/sphinx-dev.yml index 49fda49..e9a6a33 100644 --- a/.github/workflows/sphinx-dev.yml +++ b/.github/workflows/sphinx-dev.yml @@ -27,7 +27,6 @@ jobs: - name: Build Sphinx documentation with multiversion run: | source /builds/miniconda3/etc/profile.d/conda.sh - pip install -r docs/requirements.txt conda activate corese-core-documentation sphinx-build docs/source/ build/html/dev/ ghp-import build/html/dev/ -p -x dev From 13ab5bc97a62e1585646ddc0f856d836595e113e Mon Sep 17 00:00:00 2001 From: Anna Bobasheva <33026767+AnnaBobasheva@users.noreply.github.com> Date: Mon, 23 Dec 2024 15:22:45 +0100 Subject: [PATCH 11/13] Feature/update docs (#15) * Updated Sphinx documentation New Index.rst page, Old index.rst page became corese.rst. Added new pages: python_api/api_root.rst with autogenerated API doc. Added python_api/bridges.rst page The example1.ipynb notebook is rendered as a uset_guide. INSTALL.md is rendered as an installation page * Updated pyproject.toml * Moved VIESION.txt to src/pycorese to update the version only in one place. * Minor fixes and updates --- CHANGES.md | 13 ++++++++++++- VERSION.txt | 1 - docs/README.md | 6 +++--- pyproject.toml | 18 +++++++++++------- src/pycorese/VERSION.txt | 1 + src/pycorese/__init__.py | 13 ++++++++++--- 6 files changed, 37 insertions(+), 15 deletions(-) delete mode 100644 VERSION.txt create mode 100644 src/pycorese/VERSION.txt diff --git a/CHANGES.md b/CHANGES.md index f738841..35c31d6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,8 +1,19 @@ # Changelog +## Version 0.1.5 + +- added corese-python/pycorese API documentation +- wraps corese-core v4.6.0 + +## Version 0.1.4 + +- downgraded version to 0.1.4 since it's not ready for 1.0.0 yet +- updated markdown documentation +- wraps corese-core v4.6.0 + ## Version 1.0.1 -- document local installaiton process (INSTALL.md) +- document local installation process (INSTALL.md) - build jar file then running `python -m build` (may need more work) - add doc skeleton and github action files - add coreseVersion (not fully implemented yet) diff --git a/VERSION.txt b/VERSION.txt deleted file mode 100644 index 845639e..0000000 --- a/VERSION.txt +++ /dev/null @@ -1 +0,0 @@ -0.1.4 diff --git a/docs/README.md b/docs/README.md index 739336c..2a275bb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -22,8 +22,8 @@ sphinx-multiversion docs/source build/html ## Switcher generation -- To navigate between versions by means of the switcher (the dropdown list indicating the available version), the switcher.json object must be generated. -- To improve navigability, a landing page must also be generated to redirect to the latest version of the documentation. +- To navigate between versions by means of the switcher (the dropdown list indicating the available version), the switcher.json object must be generated. +- To improve navigability, a landing page must also be generated to redirect to the latest version of the documentation. To this end a script must be executed and write the output to the output html directory: @@ -33,4 +33,4 @@ To this end a script must be executed and write the output to the output html di Both sphinx-multiversion and switcher_generator work on tags following the ```^v[0-9]+\.[0-9]+\.[0-9]+$``` syntax and ordered by refname. -The minimal version set in the switcher_generator allows to not generate entries in the switcher and landing page for unexisting or uncompatible documentation. +The minimal version set in the switcher_generator allows to not generate entries in the switcher and landing page for nonexisting or incompatible documentation. diff --git a/pyproject.toml b/pyproject.toml index e9501c3..33bb634 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,11 +4,13 @@ build-backend = "setuptools.build_meta" [tool.setuptools.dynamic] -version = { file = "VERSION.txt" } +version = { file = "src/pycorese/VERSION.txt" } +readme = { file = "README.md" } + [project] name = "pycorese" -dynamic = ["version"] +dynamic = ["version", "readme"] authors = [ { name = "Corese Team", email = "corese@inria.fr" }, { name = "Anna Bobasheva", email = "anna.bobasheva@inria.fr" }, @@ -19,7 +21,7 @@ authors = [ description = "pycorese: Python API for CORESE Semantic Web platform" keywords = ["Query Engine", "SPARQL", "SHACL", "RDF", "RDFS", "OWL", "Reasoning", "Knowledge Graph"] -readme = "README.md" + license = {file = "LICENSE"} requires-python = ">=3.10" dependencies = [ @@ -66,9 +68,11 @@ include-package-data = true where = ["src"] # Adding the Java jars to the package. -# The jar files are built by the custom build step in the setup.py. -# One jar is built by the gradle build system, -# the other is downloaded from the maven repository. -# Both are copied to the `resources` directory. +# The jar files are obtained by the custom `sdist` step in the setup.py. +# (1) corese-python-{version}-with-dependencies.jar is built by the gradle build system, +# (2) corese-core-{version}-with-dependencies.jar is downloaded from the maven repository +# (https://mvnrepository.com/artifact/fr.inria.corese/corese-core). +# Both files are copied to the `resources` directory +# and installed to the `share/pycorese` directory of the venv. [tool.setuptools.data-files] "share/pycorese" = ["resources/*.jar"] diff --git a/src/pycorese/VERSION.txt b/src/pycorese/VERSION.txt new file mode 100644 index 0000000..9faa1b7 --- /dev/null +++ b/src/pycorese/VERSION.txt @@ -0,0 +1 @@ +0.1.5 diff --git a/src/pycorese/__init__.py b/src/pycorese/__init__.py index 5b1e960..b643642 100644 --- a/src/pycorese/__init__.py +++ b/src/pycorese/__init__.py @@ -1,10 +1,17 @@ """The module provides a wrapper to corese library (Software platform for the Semantic Web of Linked Data)""" +from .api import CoreseAPI -__version__ = '1.0.1' -__version_info__ = tuple([ int(num) for num in __version__.split('.')]) +all = ['CoreseAPI'] -from .api import CoreseAPI +# Read version of the package from the file +# that is in the project rood directory +from pathlib import Path + +with open(Path(__file__).parent / "VERSION.txt") as _f: + __version__ = _f.read().strip() + +__version_info__ = tuple([ int(num) for num in __version__.split('.')]) import logging From 32eafa36b01bd09d3477af4942695b198efaa051 Mon Sep 17 00:00:00 2001 From: Anna Bobasheva <33026767+AnnaBobasheva@users.noreply.github.com> Date: Mon, 23 Dec 2024 16:38:37 +0100 Subject: [PATCH 12/13] Revert "Merge branch 'main' into develop" This reverts commit 85d2fd016d39113a1a7b0640ce6091ee1bbe9712, reversing changes made to 13ab5bc97a62e1585646ddc0f856d836595e113e. --- INSTALL.md | 37 +++++++++++++++++++------------------ pyproject.toml | 17 ++++++----------- src/pycorese/py4J_bridge.py | 11 ++++++++++- 3 files changed, 35 insertions(+), 30 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 37b6e89..c8f2469 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -76,18 +76,21 @@ Build the package: python -m build ``` -which build the packages into `./dist` +This command builds the packages into `./dist` directory. Note that the custom `sdist` command is implemented in [setup.py](./setup.py). -Remark: -- do not run `python setup.py` which will not build the full package -- the described install process will: +The custom `sdist` command adds the following steps: - 1/ compile the `corese-python-4.x.y-jar-with-dependencies.jar` file - 2/ download the `corese-core-4.x.y-jar-with-dependencies.jar` file from maven +* compiling the `corese-python-x.y.z-jar-with-dependencies.jar` file using the Gradle build tool. This jar file is required to run Corese using the `Py4J` bridge. +* downloading the `corese-core-x.y.z-jar-with-dependencies.jar` file from the Maven repository. This jar file is required to run Corese using the `JPype` bridge. +* copying the jar files to the `./resources` directory. -- these two files are necessary to run the wrappers and are part of the distribution +> [!NOTE] +> - do not run `python setup.py` that will not build the full package. +> - the versions of `pycorese` and Java libraries are maintained separately. +> - `corese-python` version should be the same as `corese-core` it depends on, for simplicity reasons. +> - the commands for the first two steps are provided in the [Obtaining Java libraries manually](#obtain-java-libraries-manually) section. -### test +## Testing the package From the top directory, or in the `./tests` sub-directory run the command: @@ -137,24 +140,22 @@ pycorese 0.1.1 $ python -c 'import pycorese' ``` +> [!NOTE] +> - change the version number accordingly. -## Appendix 1: run local python example -### Conda environment +## Run a simple example -If necessary, we provide a conda environment: +Without installing the package you can run the following command (the default Java bridge is `py4j`): ```bash -conda env update -f pkg/env/corese-python.yaml -conda activate corese-python +./examples/simple_query.py -j $PWD/build/libs/corese-python-4.6.0-jar-with-dependencies.jar ``` -This makes available the python libraries: `pandas`, `py4j`, `jpype1` +or change the bridge to `jpype`: -### run a simple example using py4j bridge (without installing) - -``` -./python_examples/simple_query.py -j $PWD/build/libs/corese-python-4.6.0-jar-with-dependencies.jar +```bash +./examples/simple_query.py -b jpype -j $PWD/build/libs/corese-core-4.6.0-jar-with-dependencies.jar ``` diff --git a/pyproject.toml b/pyproject.toml index a6bcfa9..33bb634 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,6 @@ authors = [ { name = "Erwan Demairy", email = "erwan.demairy@inria.fr"}, ] description = "pycorese: Python API for CORESE Semantic Web platform" -description = "pycorese: Python API for CORESE Semantic Web platform" keywords = ["Query Engine", "SPARQL", "SHACL", "RDF", "RDFS", "OWL", "Reasoning", "Knowledge Graph"] @@ -32,11 +31,9 @@ dependencies = [ ] classifiers = [ - "Development Status :: 4 - Beta", "Development Status :: 4 - Beta", "License :: OSI Approved :: GNU Lesser General Public License v2 (LGPLv2)", "Operating System :: OS Independent", - "Operating System :: OS Independent", "Intended Audience :: Science/Research", "Topic :: Scientific/Engineering", "Topic :: Software Development :: Libraries", @@ -44,11 +41,6 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", - "Topic :: Software Development :: Libraries", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", ] [project.urls] @@ -76,8 +68,11 @@ include-package-data = true where = ["src"] # Adding the Java jars to the package. -# The jar files are built by the custom build step in the setup.py. -# The files are built/downloaded by the gradle build system -# and copied to the `resources` directory. +# The jar files are obtained by the custom `sdist` step in the setup.py. +# (1) corese-python-{version}-with-dependencies.jar is built by the gradle build system, +# (2) corese-core-{version}-with-dependencies.jar is downloaded from the maven repository +# (https://mvnrepository.com/artifact/fr.inria.corese/corese-core). +# Both files are copied to the `resources` directory +# and installed to the `share/pycorese` directory of the venv. [tool.setuptools.data-files] "share/pycorese" = ["resources/*.jar"] diff --git a/src/pycorese/py4J_bridge.py b/src/pycorese/py4J_bridge.py index d246f9f..f4f9aa6 100644 --- a/src/pycorese/py4J_bridge.py +++ b/src/pycorese/py4J_bridge.py @@ -61,7 +61,16 @@ def coreseVersion(self): """ get corese version from the loaded corese engine """ - return corese_version + version = None + try: + version = corese.java_gateway.jvm.fr.inria.corese.core.util.CoreseInfo.getVersion() + except: + pass + + if version is None: + logging.Warning(f"Py4j: the CORESE library is too old. coreseVersion() is available since 4.6.0 only.") + + return version def unloadCorese(self): """ From 6e1dfec7545076c8e4e8ec5df45df334c8d175f2 Mon Sep 17 00:00:00 2001 From: Anna Bobasheva <33026767+AnnaBobasheva@users.noreply.github.com> Date: Mon, 23 Dec 2024 18:17:12 +0100 Subject: [PATCH 13/13] Update pyproject.toml readme = { file = "README.md", content-type = "text/markdown" } --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 33bb634..8293a9a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta" [tool.setuptools.dynamic] version = { file = "src/pycorese/VERSION.txt" } -readme = { file = "README.md" } +readme = { file = "README.md", content-type = "text/markdown" } [project]