diff --git a/docs/api_reference/ontopy/testutils.md b/docs/api_reference/ontopy/testutils.md new file mode 100644 index 000000000..ea138234f --- /dev/null +++ b/docs/api_reference/ontopy/testutils.md @@ -0,0 +1,3 @@ +# testutils + +::: ontopy.testutils diff --git a/ontopy/ontology.py b/ontopy/ontology.py index 45428ed66..566f49270 100644 --- a/ontopy/ontology.py +++ b/ontopy/ontology.py @@ -1000,21 +1000,42 @@ def save( ) elif squash: URIRef, RDF, OWL = rdflib.URIRef, rdflib.RDF, rdflib.OWL - iri = self.iri if self.iri else self.base_iri - graph = self.world.as_rdflib_graph() - graph.namespace_manager.bind("", rdflib.Namespace(iri)) + + # Make a copy of the owlready2 graph object to not mess with + # owlready2 internals + graph = rdflib.Graph() + graph_owlready2 = self.world.as_rdflib_graph() + for triple in graph_owlready2.triples((None, None, None)): + graph.add(triple) + + # Add namespaces + graph.namespace_manager.bind("", rdflib.Namespace(self.base_iri)) + graph.namespace_manager.bind( + "swrl", rdflib.Namespace("http://www.w3.org/2003/11/swrl#") + ) # Remove all ontology-declarations in the graph that are # not the current ontology. - for s, _, _ in graph.triples((None, RDF.type, OWL.Ontology)): + for s, _, _ in graph.triples( # pylint: disable=not-an-iterable + (None, RDF.type, OWL.Ontology) + ): if str(s).rstrip("/#") != self.base_iri.rstrip("/#"): - for _, p, o in graph.triples((s, None, None)): + for ( + _, + p, + o, + ) in graph.triples( # pylint: disable=not-an-iterable + (s, None, None) + ): graph.remove((s, p, o)) graph.remove((s, OWL.imports, None)) + # Insert correct IRI of the ontology if self.iri: base_iri = URIRef(self.base_iri) - for s, p, o in graph.triples((base_iri, None, None)): + for s, p, o in graph.triples( # pylint: disable=not-an-iterable + (base_iri, None, None) + ): graph.remove((s, p, o)) graph.add((URIRef(self.iri), p, o)) diff --git a/ontopy/testutils.py b/ontopy/testutils.py new file mode 100644 index 000000000..33492a37e --- /dev/null +++ b/ontopy/testutils.py @@ -0,0 +1,29 @@ +"""Module primarly intended to be imported by tests. + +It defines some directories and some utility functions that can be used +with and without conftest. +""" +import sys +from pathlib import Path +from importlib.util import spec_from_loader, module_from_spec +from importlib.machinery import SourceFileLoader + + +rootdir = Path(__file__).resolve().parent.parent +testdir = rootdir / "tests" +ontodir = testdir / "testonto" +outdir = testdir / "output" +toolsdir = rootdir / "tools" + + +def get_tool_module(name): + """Imports and returns the module for the EMMOntoPy tool + corresponding to `name`.""" + if str(toolsdir) not in sys.path: + sys.path.append(str(toolsdir)) + + # For Python 3.4+ + spec = spec_from_loader(name, SourceFileLoader(name, str(toolsdir / name))) + module = module_from_spec(spec) + spec.loader.exec_module(module) + return module diff --git a/tests/tools/conftest.py b/tests/tools/conftest.py deleted file mode 100644 index 61910dd74..000000000 --- a/tests/tools/conftest.py +++ /dev/null @@ -1,114 +0,0 @@ -"""Pytest fixtures for the `tools` dir only.""" -from typing import TYPE_CHECKING - -import pytest - -if TYPE_CHECKING: - from types import ModuleType - from typing import Callable - - -@pytest.fixture(scope="module", autouse=True) -def rename_tools() -> None: - """Add a `.py` extension to all tools. - - Run prior to all tests in this module. - First, rename all tools (adding a `.py` suffix) to make them importable as a - module. Then stop executing this fixture for a while (yield) and run all the tests - in the module. Then after they're done, rename the tools back (remove the `.py` - suffix) and also return `sys.path` to its original state prior to running the - tests. - To make the importability work, the `tools` folder had to be added to the - `sys.path`. - """ - from copy import deepcopy - import os - from pathlib import Path - import shutil - import sys - - original_sys_path = deepcopy(sys.path) - tools_path: Path = Path(__file__).resolve().parent.parent.parent / "tools" - - if str(tools_path) not in sys.path: - sys.path.append(str(tools_path)) - - # Add ".py" suffix to all tools - for ( - dirpath, - dirnames, - filenames, - ) in os.walk(tools_path): - if dirpath != str(tools_path): - continue - - if dirnames: - for dirname in dirnames: - if dirname == "__pycache__": - shutil.rmtree( - Path(dirpath) / "__pycache__", ignore_errors=True - ) - - for filename in filenames: - filepath = Path(dirpath) / filename - assert ( - filepath.suffix == "" - ), f"A suffix was found (not expected) for file: {filepath}" - - filepath.rename(filepath.with_suffix(".py")) - - yield - - # Remove ".py" suffix from all tools - for ( - dirpath, - dirnames, - filenames, - ) in os.walk(tools_path): - if dirpath != str(tools_path): - continue - - if dirnames: - for dirname in dirnames: - if dirname == "__pycache__": - shutil.rmtree( - Path(dirpath) / "__pycache__", ignore_errors=True - ) - - for filename in filenames: - filepath = Path(dirpath) / filename - assert ( - filepath.suffix == ".py" - ), f"A suffix was NOT found (not expected) for file: {filepath}" - - filepath.rename(filepath.with_suffix("")) - - sys.path = original_sys_path - - -@pytest.fixture -def get_tool() -> "Callable[[str], ModuleType]": - """Import a tool as a module. - - Requires the fixture `rename_tools` to have been run already. - """ - import importlib - from pathlib import Path - import sys - - def _get_tool(name: str) -> "ModuleType": - """Import and return named tool.""" - tool_path: Path = ( - Path(__file__).resolve().parent.parent.parent / "tools" / name - ).with_suffix(".py") - assert ( - str(tool_path.parent) in sys.path - ), f"'tools' dir not found in sys.path. Did `rename_tools` fixture run?\nsys.path: {sys.path}" - - assert ( - tool_path.exists() - ), f"The requested tool ({name}) was not found in {tool_path.parent}." - - return importlib.import_module(name) - - return _get_tool diff --git a/tests/tools/test_emmocheck.py b/tests/tools/test_emmocheck.py index bc3316610..c403b307c 100644 --- a/tests/tools/test_emmocheck.py +++ b/tests/tools/test_emmocheck.py @@ -1,24 +1,12 @@ """Test the `emmocheck` tool.""" -from typing import TYPE_CHECKING -if TYPE_CHECKING: - from types import ModuleType - from typing import Callable +# if True: +def test_run() -> None: + """Check that running `emmocheck` works.""" + from ontopy.testutils import ontodir, get_tool_module -def test_run(get_tool: "Callable[[str], ModuleType]") -> None: - """Check that running `emmocheck` works. - - Parameters: - get_tool: Local module fixture to load a named tool as a module. - See the current folder's `conftest.py` file. - - """ - from pathlib import Path - - test_file = ( - Path(__file__).resolve().parent.parent / "testonto" / "models.ttl" - ) - emmocheck = get_tool("emmocheck") + test_file = ontodir / "models.ttl" + emmocheck = get_tool_module("emmocheck") emmocheck.main([str(test_file)]) diff --git a/tests/tools/test_excel2onto.py b/tests/tools/test_excel2onto.py index 3a047a75a..124ccd048 100644 --- a/tests/tools/test_excel2onto.py +++ b/tests/tools/test_excel2onto.py @@ -1,16 +1,9 @@ """Test the `ontograph` tool.""" -from typing import TYPE_CHECKING - import pytest -if TYPE_CHECKING: - from pathlib import Path - from types import ModuleType - from typing import Callable - @pytest.mark.filterwarnings("ignore::UserWarning") -def test_run(get_tool: "Callable[[str], ModuleType]", tmpdir: "Path") -> None: +def test_run() -> None: """Check that running `excel2onto` works. Parameters: @@ -20,36 +13,33 @@ def test_run(get_tool: "Callable[[str], ModuleType]", tmpdir: "Path") -> None: exist only for the lifetime of this test function. """ - from pathlib import Path + from ontopy.testutils import ontodir, outdir, testdir, get_tool_module - test_file = ( - Path(__file__).resolve().parent.parent - / "test_excelparser" - / "onto.xlsx" - ) - test_file2 = ( - Path(__file__).resolve().parent.parent - / "test_excelparser" - / "onto_update.xlsx" - ) - excel2onto = get_tool("excel2onto") + test_file = testdir / "test_excelparser" / "onto.xlsx" + test_file2 = testdir / "test_excelparser" / "onto_update.xlsx" + excel2onto = get_tool_module("excel2onto") - excel2onto.main( - [f"--output={str(tmpdir)}/onto.ttl", "--force", str(test_file)] - ) + outfile = outdir / "onto.ttl" + if outfile.exists(): # consider to add an --overwrite option to excel2onto + outfile.unlink() + excel2onto.main([f"--output={outfile}", "--force", str(test_file)]) + # Append to outfile excel2onto.main( [ - f"--output={str(tmpdir)}/onto.ttl", + f"--output={outdir}/onto.ttl", "--force", "--input_ontology=newonto.ttl", str(test_file2), ] ) + outfile = outdir / "ontology.ttl" + if outfile.exists(): + outfile.unlink() excel2onto.main( [ - f"--output={str(tmpdir)}/ontology.ttl", + f"--output={outfile}", "--force", "--update=False", str(test_file), diff --git a/tests/tools/test_ontoconvert.py b/tests/tools/test_ontoconvert.py index fda0af10f..c8989ede7 100644 --- a/tests/tools/test_ontoconvert.py +++ b/tests/tools/test_ontoconvert.py @@ -1,27 +1,34 @@ """Test the `ontoconvert` tool.""" -from typing import TYPE_CHECKING -if TYPE_CHECKING: - from pathlib import Path - from types import ModuleType - from typing import Callable +# if True: +def test_run() -> None: + """Check that running `ontoconvert` works.""" + import re + from ontopy.testutils import ontodir, outdir, get_tool_module -def test_run(get_tool: "Callable[[str], ModuleType]", tmpdir: "Path") -> None: - """Check that running `ontoconvert` works. + ontoconvert = get_tool_module("ontoconvert") - Parameters: - get_tool: Local module fixture to load a named tool as a module. - See the current folder's `conftest.py` file. - tmpdir: A generic pytest fixture to generate a temporary directory, which will - exist only for the lifetime of this test function. - - """ - from pathlib import Path - - test_file = ( - Path(__file__).resolve().parent.parent / "testonto" / "models.ttl" + # Test 1 + ontoconvert.main( + [str(ontodir / "models.ttl"), str(outdir / "test_ontoconvert1.ttl")] ) - ontoconvert = get_tool("ontoconvert") + output1 = (outdir / "test_ontoconvert1.ttl").read_text() + assert re.search("@prefix : ", output1) + assert re.search(" .* owl:Ontology", output1) + assert re.search("testclass .* owl:Class", output1) - ontoconvert.main([str(test_file), str(tmpdir / "test.ttl")]) + # Test 2 - squash + ontoconvert.main( + [ + "-asw", + "--iri=https://w3id.org/ex/testonto", + "--base-iri=https://w3id.org/ex/testonto#", + str(ontodir / "testonto.ttl"), + str(outdir / "test_ontoconvert2.ttl"), + ] + ) + output2 = (outdir / "test_ontoconvert2.ttl").read_text() + assert re.search("@prefix : ", output2) + assert re.search(" .* owl:Ontology", output2) + assert re.search("testclass .* owl:Class", output2) diff --git a/tests/tools/test_ontodoc.py b/tests/tools/test_ontodoc.py index 5c52a7415..067c31568 100644 --- a/tests/tools/test_ontodoc.py +++ b/tests/tools/test_ontodoc.py @@ -1,90 +1,48 @@ """Test the `ontodoc` tool.""" -from typing import TYPE_CHECKING - import pytest -if TYPE_CHECKING: - from pathlib import Path - from types import ModuleType - from typing import Callable - - -def test_run(get_tool: "Callable[[str], ModuleType]", tmpdir: "Path") -> None: - """Check that running `ontodoc` works. - Parameters: - get_tool: Local module fixture to load a named tool as a module. - See the current folder's `conftest.py` file. - tmpdir: A generic pytest fixture to generate a temporary directory, which will - exist only for the lifetime of this test function. +def test_run() -> None: + """Check that running `ontodoc` works.""" + from ontopy.testutils import ontodir, outdir, get_tool_module - """ - from pathlib import Path + ontodoc = get_tool_module("ontodoc") - test_file = ( - Path(__file__).resolve().parent.parent / "testonto" / "models.ttl" - ) - ontodoc = get_tool("ontodoc") + test_file = ontodir / "models.ttl" + ontodoc = get_tool_module("ontodoc") - ontodoc.main([str(test_file), str(tmpdir / "test.md")]) + ontodoc.main([str(test_file), str(outdir / "test.md")]) ontodoc.main( - [str(test_file), "--format=simple-html", str(tmpdir / "test.html")] + [str(test_file), "--format=simple-html", str(outdir / "test.html")] ) -def test_run_w_individual( - get_tool: "Callable[[str], ModuleType]", tmpdir: "Path" -) -> None: - """Check that running `ontodoc` works when there is an individual. - - Parameters: - get_tool: Local module fixture to load a named tool as a module. - See the current folder's `conftest.py` file. - tmpdir: A generic pytest fixture to generate a temporary directory, which will - exist only for the lifetime of this test function. +def test_run_w_individual() -> None: + """Check that running `ontodoc` works when there is an individual.""" + from ontopy.testutils import ontodir, outdir, get_tool_module - """ - from pathlib import Path - - test_file = ( - Path(__file__).resolve().parent.parent - / "testonto" - / "testonto_w_individual.ttl" - ) - ontodoc = get_tool("ontodoc") + test_file = ontodir / "testonto_w_individual.ttl" + ontodoc = get_tool_module("ontodoc") - ontodoc.main([str(test_file), str(tmpdir / "test.md")]) + ontodoc.main([str(test_file), str(outdir / "test.md")]) ontodoc.main( - [str(test_file), "--format=simple-html", str(tmpdir / "test.html")] + [str(test_file), "--format=simple-html", str(outdir / "test.html")] ) @pytest.mark.filterwarnings( "ignore:Ignoring instance" ) # currently pytest is set to accept warnings, but this might change in the future -def test_run_w_punning( - get_tool: "Callable[[str], ModuleType]", tmpdir: "Path" -) -> None: +def test_run_w_punning() -> None: """Check that running `ontodoc` works even if there is a punned individual. This will throw and extra warning as the punned individual will be ignored. - - Parameters: - get_tool: Local module fixture to load a named tool as a module. - See the current folder's `conftest.py` file. - tmpdir: A generic pytest fixture to generate a temporary directory, which will - exist only for the lifetime of this test function. - """ - from pathlib import Path + from ontopy.testutils import ontodir, outdir, get_tool_module - test_file = ( - Path(__file__).resolve().parent.parent - / "testonto" - / "testonto_w_punning.ttl" - ) - ontodoc = get_tool("ontodoc") + test_file = ontodir / "testonto_w_punning.ttl" + ontodoc = get_tool_module("ontodoc") - ontodoc.main([str(test_file), str(tmpdir / "test.md")]) + ontodoc.main([str(test_file), str(outdir / "test.md")]) ontodoc.main( - [str(test_file), "--format=simple-html", str(tmpdir / "test.html")] + [str(test_file), "--format=simple-html", str(outdir / "test.html")] ) diff --git a/tests/tools/test_ontograph.py b/tests/tools/test_ontograph.py index 552ff1a88..f972bb261 100644 --- a/tests/tools/test_ontograph.py +++ b/tests/tools/test_ontograph.py @@ -1,27 +1,11 @@ """Test the `ontograph` tool.""" -from typing import TYPE_CHECKING -if TYPE_CHECKING: - from pathlib import Path - from types import ModuleType - from typing import Callable +def test_run() -> None: + """Check that running `ontograph` works.""" + from ontopy.testutils import ontodir, outdir, get_tool_module -def test_run(get_tool: "Callable[[str], ModuleType]", tmpdir: "Path") -> None: - """Check that running `ontograph` works. + test_file = ontodir / "models.ttl" + ontograph = get_tool_module("ontograph") - Parameters: - get_tool: Local module fixture to load a named tool as a module. - See the current folder's `conftest.py` file. - tmpdir: A generic pytest fixture to generate a temporary directory, which will - exist only for the lifetime of this test function. - - """ - from pathlib import Path - - test_file = ( - Path(__file__).resolve().parent.parent / "testonto" / "models.ttl" - ) - ontograph = get_tool("ontograph") - - ontograph.main([str(test_file), str(tmpdir / "test.png")]) + ontograph.main([str(test_file), str(outdir / "test.png")]) diff --git a/tests/tools/test_ontoversion.py b/tests/tools/test_ontoversion.py index c0b1a8658..810be8535 100644 --- a/tests/tools/test_ontoversion.py +++ b/tests/tools/test_ontoversion.py @@ -1,26 +1,11 @@ """Test the `ontoversion` tool.""" -from typing import TYPE_CHECKING -if TYPE_CHECKING: - from types import ModuleType - from typing import Callable +def test_run() -> None: + """Check running `ontoversion` works.""" + from ontopy.testutils import ontodir, outdir, get_tool_module -def test_run(get_tool: "Callable[[str], ModuleType]") -> None: - """Check running `ontoversion` works. - - Parameters: - get_tool: Local module fixture to load a named tool as a module. - See the current folder's `conftest.py` file. - tmpdir: A generic pytest fixture to generate a temporary directory, which will - exist only for the lifetime of this test function. - - """ - from pathlib import Path - - test_file = ( - Path(__file__).resolve().parent.parent / "testonto" / "testonto.ttl" - ) - ontoversion = get_tool("ontoversion") + test_file = ontodir / "testonto.ttl" + ontoversion = get_tool_module("ontoversion") ontoversion.main([str(test_file), "--format", "ttl"]) diff --git a/tests/tools/test_redirectioncheck.py b/tests/tools/test_redirectioncheck.py index 139c5ef1f..7f6409850 100644 --- a/tests/tools/test_redirectioncheck.py +++ b/tests/tools/test_redirectioncheck.py @@ -1,26 +1,12 @@ """Test the `redirectioncheck` tool.""" -from typing import TYPE_CHECKING -if TYPE_CHECKING: - from pathlib import Path - from types import ModuleType - from typing import Callable +def test_run() -> None: + """Check that running `redirectioncheck` works.""" + from ontopy.testutils import testdir, get_tool_module -def test_run(get_tool: "Callable[[str], ModuleType]") -> None: - """Check that running `redirectioncheck` works. - - Parameters: - get_tool: Local module fixture to load a named tool as a module. - See the current folder's `conftest.py` file. - - """ - from pathlib import Path - - yamlfile = ( - Path(__file__).resolve().parent / "input" / "expected_redirections.yaml" - ) - redirectioncheck = get_tool("redirectioncheck") + yamlfile = testdir / "tools" / "input" / "expected_redirections.yaml" + redirectioncheck = get_tool_module("redirectioncheck") # Make output more readable when running pytest with -s option print("\n\n") diff --git a/tools/ontoconvert b/tools/ontoconvert index 0088cb8ba..934d12df8 100755 --- a/tools/ontoconvert +++ b/tools/ontoconvert @@ -87,14 +87,20 @@ def main(argv: list = None): help="Do not infer imported ontologies.", ) # To be implemented... + parser.add_argument( + "--iri", + "-I", + help="IRI of converted ontology.", + ) parser.add_argument( "--base-iri", "-b", help=( - "Base iri of output ontology. The default is the base iri of " + "Base IRI of converted ontology. The default is the base iri of " "the input ontology." - "\n\nNOTE: Currently this option does nothing. Kept to not break " - "existing workflows using it." + "\n\nThis argument can be used to workaround the bug in Owlready2 " + "that changes the base IRI of the ontology to always end with a " + "slash." ), ) parser.add_argument( @@ -191,6 +197,12 @@ def main(argv: list = None): url_from_catalog=args.url_from_catalog, ) + if args.iri: + onto.iri = args.iri + + if args.base_iri: + onto.base_iri = args.base_iri + if args.annotate_source: annotate_source(onto)