diff --git a/.gitignore b/.gitignore index 82f9275..7b6caf3 100644 --- a/.gitignore +++ b/.gitignore @@ -159,4 +159,4 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # 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/ +.idea/ diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..b3c5940 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "kevinrose.vsc-python-indent", + "ms-python.python", + "njpwerner.autodocstring", + "donjayamanne.python-environment-manager" + ] +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..bda247b --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,36 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387"version": "0.2.0", + "configurations": [ + { + "name": "Current File", + "type": "debugpy", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal" + }, + { + "name": "Parse RDF File", + "type": "debugpy", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal", + "args": [ + "-f", + "tests/data/doap.rdf" + ] + }, + { + "name": "Parse n3 File", + "type": "debugpy", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal", + "args": [ + "-f", + "tests/data/doap.n3" + ] + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9b38853 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "python.testing.pytestArgs": [ + "tests" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true +} \ No newline at end of file diff --git a/README.md b/README.md index c59d42b..77449f4 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,41 @@ # be-graph-local-py -Creation and editing of local graph files for publication and automated discovery + +Creation and editing of local graph files for publication and automated discovery. + +## Getting Started + +1. Install required packages with Poetry. +2. Run `python main.py -f "tests/data/doap.rdf"` to run the program on an example file. + +## Poetry Installation + +To install Poetry, follow these steps: + +1. Open your terminal or command prompt. +2. Run the following command to install Poetry: + + ```bash + curl -sSL https://install.python-poetry.org | python3 - + ``` + + If you don't have `curl` installed, you can also use `wget`: + + ```bash + wget -O - https://install.python-poetry.org | python3 - + ``` + +3. Once the installation is complete, you can verify it by running: + + ```bash + poetry --version + ``` + + This should display the version number of Poetry installed on your system. + +Congratulations! You have successfully installed Poetry. You can now use it to manage your Python dependencies and projects. + +For more information on how to use Poetry, refer to the [official documentation](https://python-poetry.org/docs/). + +## Tests + +Run `pytest` to run all the tests in the `tests/` directory. diff --git a/be_graph_local_py/__init__.py b/be_graph_local_py/__init__.py new file mode 100644 index 0000000..3338601 --- /dev/null +++ b/be_graph_local_py/__init__.py @@ -0,0 +1 @@ +from .rdf import read_rdf, get_project_name diff --git a/be_graph_local_py/cli/__init__.py b/be_graph_local_py/cli/__init__.py new file mode 100644 index 0000000..fc35067 --- /dev/null +++ b/be_graph_local_py/cli/__init__.py @@ -0,0 +1 @@ +from .arguments import Arguments diff --git a/be_graph_local_py/cli/arguments.py b/be_graph_local_py/cli/arguments.py new file mode 100644 index 0000000..c654ed1 --- /dev/null +++ b/be_graph_local_py/cli/arguments.py @@ -0,0 +1,14 @@ +import argparse + + +def Arguments() -> argparse.ArgumentParser: + """ + Create and configure the argument parser for the command-line interface. + + Returns: + argparse.ArgumentParser: The configured argument parser. + """ + parser = argparse.ArgumentParser(description="Process some RDF data.") + parser.add_argument('-f', metavar="Files", type=str, required=False, help="Parse RDF or n3 file.") + parser.add_argument('files', metavar="Files", type=str, nargs='*', help='Parse RDF or n3 file.') + return parser diff --git a/be_graph_local_py/rdf/__init__.py b/be_graph_local_py/rdf/__init__.py new file mode 100644 index 0000000..3338601 --- /dev/null +++ b/be_graph_local_py/rdf/__init__.py @@ -0,0 +1 @@ +from .rdf import read_rdf, get_project_name diff --git a/be_graph_local_py/rdf/rdf.py b/be_graph_local_py/rdf/rdf.py new file mode 100644 index 0000000..166d0a5 --- /dev/null +++ b/be_graph_local_py/rdf/rdf.py @@ -0,0 +1,41 @@ +from typing import List +from rdflib import Graph, Namespace, Literal, XSD + + +def read_rdf(file_path) -> Graph: + """ + Reads an RDF file and returns the parsed RDF graph. + + Args: + file_path (str): The path to the RDF file. + + Returns: + rdflib.Graph: The parsed RDF graph. + + """ + rdfGraph = Graph() + rdfGraph.parse(file_path, format='xml') + return rdfGraph + + +def get_project_name(rdfGraph: Graph) -> str: + """ + Get the name of the project. + + Args: + rdfGraph (Graph): The RDF graph. + + Returns: + str: The name of the project. + """ + DOAP = Namespace("http://usefulinc.com/ns/doap#") + query = """ + SELECT ?name WHERE { + ?project a doap:Project . + ?project doap:name ?name . + } + """ + result = rdfGraph.query(query, initNs={'doap': DOAP}) + for row in result: + return str(row.name) + return "Project name not found" diff --git a/be_graph_local_py/utils/__init__.py b/be_graph_local_py/utils/__init__.py new file mode 100644 index 0000000..16281fe --- /dev/null +++ b/be_graph_local_py/utils/__init__.py @@ -0,0 +1 @@ +from .utils import * diff --git a/be_graph_local_py/utils/argparse_examples.py b/be_graph_local_py/utils/argparse_examples.py new file mode 100644 index 0000000..c04a6a6 --- /dev/null +++ b/be_graph_local_py/utils/argparse_examples.py @@ -0,0 +1,9 @@ +import argparse + +parser = argparse.ArgumentParser(description="Process some RDF data.") +parser.add_argument('-f', metavar="Files", type=str, required=False, help="Parse RDF or n3 file.") +parser.add_argument('files', metavar="Files", type=str, nargs='*', help='Parse RDF or n3 file.') +# parser.add_argument('--sum', dest='accumulate', action='store_const', required=False, +# const=sum, default=max, +# help='sum the integers (default: find the max)') +parser.parse_args() diff --git a/be_graph_local_py/utils/rdflib_examples.py b/be_graph_local_py/utils/rdflib_examples.py new file mode 100644 index 0000000..81d454f --- /dev/null +++ b/be_graph_local_py/utils/rdflib_examples.py @@ -0,0 +1,110 @@ +from rdflib import Graph, URIRef, Literal, XSD +from rdflib.namespace import RDF, FOAF + + +def example_01(): + """ + Creates an RDF graph, adds triples to the graph, and queries the graph for name and age information. + + Returns: + None + """ + # Create a new RDF graph + g = Graph() + + # Define some URIs + person_uri = URIRef("http://example.org/person/JohnDoe") + name_uri = FOAF.name + age_uri = FOAF.age + + # Add triples to the graph + g.add((person_uri, RDF.type, FOAF.Person)) + g.add((person_uri, name_uri, Literal("John Doe", datatype=XSD.string))) + g.add((person_uri, age_uri, Literal(42, datatype=XSD.integer))) + + # Query the graph + query = """ + SELECT ?name ?age + WHERE { + ?person a foaf:Person . + ?person foaf:name ?name . + ?person foaf:age ?age . + } + """ + results = g.query(query) + + # Print the results + for row in results: + print(f"Name: {row.name}, Age: {row.age}") + + +def example_02(): + """ + Read a graph from a file. + + This function creates a Graph object and parses an RDF file hosted on the Internet. + It then loops through each triple in the graph and checks if there is at least one triple. + Finally, it prints the number of triples in the graph and the entire graph in the RDF Turtle format. + """ + # Create a Graph + g = Graph() + + # Parse in an RDF file hosted on the Internet + g.parse("http://www.w3.org/People/Berners-Lee/card") + + # Loop through each triple in the graph (subj, pred, obj) + for subj, pred, obj in g: + # Check if there is at least one triple in the Graph + if (subj, pred, obj) not in g: + raise Exception("It better be!") + + # Print the number of "triples" in the Graph + print(f"Graph g has {len(g)} statements.") + # Prints: Graph g has 86 statements. + + # Print out the entire Graph in the RDF Turtle format + print(g.serialize(format="turtle")) + + +def example_03(): + """ + This function demonstrates how to create a Graph using RDFLib and add triples to it. + It also shows how to iterate over the triples in the graph and print them out. + """ + # Create a Graph + g = Graph() + + # Create an RDF URI node to use as the subject for multiple triples + donna = URIRef("http://example.org/donna") + + # Add triples using store's add() method. + g.add((donna, RDF.type, FOAF.Person)) + g.add((donna, FOAF.nick, Literal("donna", lang="en"))) + g.add((donna, FOAF.name, Literal("Donna Fales"))) + g.add((donna, FOAF.mbox, URIRef("mailto:donna@example.org"))) + + # Add another person + ed = URIRef("http://example.org/edward") + + # Add triples using store's add() method. + g.add((ed, RDF.type, FOAF.Person)) + g.add((ed, FOAF.nick, Literal("ed", datatype=XSD.string))) + g.add((ed, FOAF.name, Literal("Edward Scissorhands"))) + g.add((ed, FOAF.mbox, Literal("e.scissorhands@example.org", datatype=XSD.anyURI))) + + # Iterate over triples in store and print them out. + print("--- printing raw triples ---") + for s, p, o in g: + print((s, p, o)) + + # For each foaf:Person in the store, print out their mbox property's value. + print("--- printing mboxes ---") + for person in g.subjects(RDF.type, FOAF.Person): + for mbox in g.objects(person, FOAF.mbox): + print(mbox) + + # Bind the FOAF namespace to a prefix for more readable output + g.bind("foaf", FOAF) + + # print all the data in the Notation3 format + print("--- printing mboxes ---") diff --git a/be_graph_local_py/utils/utils.py b/be_graph_local_py/utils/utils.py new file mode 100644 index 0000000..e69de29 diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000..96dbe9f --- /dev/null +++ b/doc/README.md @@ -0,0 +1,30 @@ + + +# Backend Graph Local Python Documents + +In order to be inclusive across language barriers, the documentation for this project is made translatable into other +languages using the [GitLocalize](https://gitlocalize.com/) tools which have, as of this writing, been made available in +the organization as an application to use for any repository. + +## Project DOAP source + +The general source file for the project DOAP file, if defined declaratively using languages such as Notation3 (.n3) or +Terse Triple Language (.ttl) may be found in this directory, with optional localized terms such as description, short +description, and others found in language subdirectories (en, es, etc). diff --git a/doc/en/README.md b/doc/en/README.md new file mode 100644 index 0000000..96dbe9f --- /dev/null +++ b/doc/en/README.md @@ -0,0 +1,30 @@ + + +# Backend Graph Local Python Documents + +In order to be inclusive across language barriers, the documentation for this project is made translatable into other +languages using the [GitLocalize](https://gitlocalize.com/) tools which have, as of this writing, been made available in +the organization as an application to use for any repository. + +## Project DOAP source + +The general source file for the project DOAP file, if defined declaratively using languages such as Notation3 (.n3) or +Terse Triple Language (.ttl) may be found in this directory, with optional localized terms such as description, short +description, and others found in language subdirectories (en, es, etc). diff --git a/main.py b/main.py new file mode 100644 index 0000000..edcbcfa --- /dev/null +++ b/main.py @@ -0,0 +1,21 @@ +from be_graph_local_py.cli.arguments import Arguments +from be_graph_local_py.rdf import read_rdf +from be_graph_local_py.rdf.rdf import get_project_name + + +def main(): + """ + Entry point of the program. + """ + arguments = Arguments().parse_args() + + # Read an RDF file from the path provided by the "f" argument. + rdf_graph = read_rdf(arguments.f) + + # Print project name. + result = get_project_name(rdf_graph) + print(result) + + +if __name__ == "__main__": + main() diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..c46c363 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,134 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "isodate" +version = "0.6.1" +description = "An ISO 8601 date/time/duration parser and formatter" +optional = false +python-versions = "*" +files = [ + {file = "isodate-0.6.1-py2.py3-none-any.whl", hash = "sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96"}, + {file = "isodate-0.6.1.tar.gz", hash = "sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9"}, +] + +[package.dependencies] +six = "*" + +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pyparsing" +version = "3.1.2" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.6.8" +files = [ + {file = "pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"}, + {file = "pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pytest" +version = "8.3.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, + {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "rdflib" +version = "7.0.0" +description = "RDFLib is a Python library for working with RDF, a simple yet powerful language for representing information." +optional = false +python-versions = ">=3.8.1,<4.0.0" +files = [ + {file = "rdflib-7.0.0-py3-none-any.whl", hash = "sha256:0438920912a642c866a513de6fe8a0001bd86ef975057d6962c79ce4771687cd"}, + {file = "rdflib-7.0.0.tar.gz", hash = "sha256:9995eb8569428059b8c1affd26b25eac510d64f5043d9ce8c84e0d0036e995ae"}, +] + +[package.dependencies] +isodate = ">=0.6.0,<0.7.0" +pyparsing = ">=2.1.0,<4" + +[package.extras] +berkeleydb = ["berkeleydb (>=18.1.0,<19.0.0)"] +html = ["html5lib (>=1.0,<2.0)"] +lxml = ["lxml (>=4.3.0,<5.0.0)"] +networkx = ["networkx (>=2.0.0,<3.0.0)"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "^3.12" +content-hash = "cd2f1b13b369de573c9f94c3c2d42c21c3e0de12f07139bd6fbda16951c45530" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..7e114db --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,16 @@ +[tool.poetry] +name = "be-graph-local-py" +version = "0.1.0" +description = "Creation and editing of local graph files for publication and automated discovery." +authors = ["natebass "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.12" +rdflib = "^7.0.0" +pytest = "^8.3.2" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/data/doap.n3 b/tests/data/doap.n3 new file mode 100644 index 0000000..97263e1 --- /dev/null +++ b/tests/data/doap.n3 @@ -0,0 +1,47 @@ +# Description of a Project in machine-readable form +# +# This is a DOAP file - see https://github.com/ewilderj/doap/wiki +# This is RDF data - see https://www.w3.org/RDF/ +# This is written in Notation3 - see https://notation3.org/ +# This is serialized to RDF/XML format for publication, with an .rdf extension + +@prefix doap: . +@prefix foaf: . + + + a doap:Project ; + doap:name "Tessellation Project Overview" ; + doap:shortdesc "The goal of the Tessellation Project is to establish an opt-in, decentralized means of project discovery to capture, communicate, and collaborate." ; + doap:description + """ + The Tessellation Overview is a starting point for specification, documentation, and ideation toward development of essential and useful components + which will make up a functional system of opt-in advertisement of organization and project participatns in one or more development communities to + fascilitate collaboration without a top down management infrastructure. + """ ; + doap:homepage ; + doap:bug-database ; + doap:Specification ; + doap:license ; + doap:maintainer ; + doap:documenter ; + doap:repository ; + + # Author of this DOAP document + foaf:maker . + + + a doap:GitRepository ; + doap:location ; + doap:browse . + + + a foaf:Person ; + foaf:name "DevOps - Project-ACT" ; + foaf:homepage ; + foaf:mbox . + + + a foaf:person ; + foaf:name "Dan Hugo" ; + foaf:homepage ; + foaf:mbox . diff --git a/tests/data/doap.rdf b/tests/data/doap.rdf new file mode 100644 index 0000000..9248c3a --- /dev/null +++ b/tests/data/doap.rdf @@ -0,0 +1,41 @@ + + + + Tessellation Project Overview + The goal of the Tessellation Project is to establish an opt-in, decentralized means of project discovery to capture, communicate, and collaborate. + + The Tessellation Overview is a starting point for specification, documentation, and ideation toward development of essential and useful components + which will make up a functional system of opt-in advertisement of organization and project participatns in one or more development communities to + fascilitate collaboration without a top down management infrastructure. + + + + + + + + DevOps - Project-ACT + + + + + + + Dan Hugo + + + + + + + + + + + + + diff --git a/tests/test_arguments.py b/tests/test_arguments.py new file mode 100644 index 0000000..cf88363 --- /dev/null +++ b/tests/test_arguments.py @@ -0,0 +1,7 @@ +import os +from be_graph_local_py.cli.arguments import Arguments + +def test_is_valid_file_path(): + parser = Arguments() + arguments = parser.parse_args(['-f', 'tests/data/doap.rdf']) + assert os.path.isfile(arguments.f) \ No newline at end of file