Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ontoconvert rdflib import #307

Merged
merged 5 commits into from
Dec 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci_cd_updated_master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ jobs:
- name: Release check
run: |
COMMIT_MSG="$(gh api /repos/${{ github.repository}}/commits/${{ env.DEFAULT_REPO_BRANCH }} --jq '.commit.message')"
if [[ "${COMMIT_MSG}" =~ ^Release\ v.*\ -\ Changelog$ ]]; then
if [[ "${COMMIT_MSG}" =~ ^Release\ v.*$ ]]; then
echo "In a release - do not run this job !"
echo "RELEASE_RUN=true" >> $GITHUB_ENV
else
Expand Down
40 changes: 40 additions & 0 deletions docs/developers/testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Testing and tooling

## Unit testing

The [PyTest](https://pytest.org) framework is used for testing the EMMOntoPy package.
It is a unit testing framework with a plugin system, sporting an extensive plugin library as well as a sound fixture injection system.

To run the tests locally install the package with the `dev` extra (see the [developer's setup guide](setup.md)) and run:

```console
$ pytest
=== test session starts ===
...
```

To understand what options you have, run `pytest --help`.

## Tools

Several tools are used to maintain the package, keeping it secure, readable, and easing maintenance.

### Mypy

[Mypy](http://mypy-lang.org/) is a static type checker for Python.

**Documentation**: [mypy.readthedocs.io](https://mypy.readthedocs.io/)

The signs of this tool will be found in the code especially through the `typing.TYPE_CHECKING` boolean variable, which will be used in the current way:

```python
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from typing import List
```

Since `TYPE_CHECKING` is `False` at runtime, the `if`-block will not be run as part of running the script or module or if importing the module.
However, when Mypy runs to check the static typing, it forcefully runs these blocks, considering `TYPE_CHECKING` to be `True` (see the [`typing.TYPE_CHECKING` section](https://mypy.readthedocs.io/en/stable/runtime_troubles.html#typing-type-checking) in the Mypy documentation).

This means the imports in the `if`-block are meant to *only* be used for static typing, helping developers to understand the intention of the code as well as to check the invoked methods make sense (through Mypy).
26 changes: 16 additions & 10 deletions emmopy/emmocheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,13 +411,8 @@ def test_namespace(self):
exceptions.update(self.get_config("test_namespace.exceptions", ()))

def checker(onto, ignore_namespace):
if (
list(
filter(
onto.base_iri.strip("#").endswith, self.ignore_namespace
)
)
!= []
if list(
filter(onto.base_iri.strip("#").endswith, self.ignore_namespace)
):
print(f"Skipping namespace: {onto.base_iri}")
return
Expand Down Expand Up @@ -463,8 +458,19 @@ def checker(onto, ignore_namespace):
checker(self.onto, self.ignore_namespace)


def main(): # pylint: disable=too-many-locals,too-many-branches,too-many-statements
"""Run all checks on ontology `iri`. Default is 'http://emmo.info/emmo'."""
def main(
argv: list = None,
CasperWA marked this conversation as resolved.
Show resolved Hide resolved
): # pylint: disable=too-many-locals,too-many-branches,too-many-statements
"""Run all checks on ontology `iri`.

Default is 'http://emmo.info/emmo'.

Parameters:
argv: List of arguments, similar to `sys.argv[1:]`.
Mainly for testing purposes, since it allows one to invoke the tool
manually / through Python.

"""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("iri", help="File name or URI to the ontology to test.")
parser.add_argument(
Expand Down Expand Up @@ -597,7 +603,7 @@ def main(): # pylint: disable=too-many-locals,too-many-branches,too-many-statem
help="Stop the test run on the first error or failure.",
)
try:
args = parser.parse_args()
args = parser.parse_args(args=argv)
sys.argv[1:] = args.unittest if args.unittest else []
if args.verbose:
sys.argv.append("-v")
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
35 changes: 35 additions & 0 deletions tests/tools/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Pytest fixtures for the `tools` dir only."""
from typing import TYPE_CHECKING
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not smart enough to see what actually are testing here. Maybe you can add a comment

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for type annotations only. It has nothing to do with testing, but rather to support IDEs as well as set the package up for future testing with MyPy (see #223).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation. I think it would be good to have as a module docstring.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be littered all over the code those in the future - Maybe I'll add it to the developer's documentation instead? Or do you want a comment in all module doc-strings where it'll be?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a new documentation page about testing and tools for developers :)


import pytest

if TYPE_CHECKING:
from types import ModuleType
from typing import Any, Dict


@pytest.fixture
def tool(request: "Dict[str, Any]") -> "ModuleType":
"""Import a tool as a module."""
from copy import deepcopy
import importlib
from pathlib import Path
import sys

original_sys_path = deepcopy(sys.path)
original_tool_path: Path = (
Path(__file__).resolve().parent.parent.parent / "tools" / request.param
francescalb marked this conversation as resolved.
Show resolved Hide resolved
)

assert (
original_tool_path.exists()
), f"The requested tool ({request.param}) was not found in {original_tool_path.parent}"
try:
tool_path = original_tool_path.rename(
original_tool_path.with_name(f"{request.param}.py")
)
yield importlib.import_module(f"tools.{request.param}")
finally:
if tool_path and tool_path.exists():
tool_path.rename(tool_path.with_name(request.param))
sys.path = original_sys_path
14 changes: 14 additions & 0 deletions tests/tools/test_emmocheck.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""Test the `emmocheck` tool."""
import pytest


@pytest.mark.parametrize("tool", ["emmocheck"], indirect=True)
def test_run(tool) -> None:
"""Check that running `emmocheck` works."""
from pathlib import Path

test_file = (
Path(__file__).resolve().parent.parent / "testonto" / "models.ttl"
)

tool.main([str(test_file)])
14 changes: 14 additions & 0 deletions tests/tools/test_ontoconvert.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""Test the `ontoconvert` tool."""
from pathlib import Path

import pytest


@pytest.mark.parametrize("tool", ["ontoconvert"], indirect=True)
def test_run(tool, tmpdir: Path) -> None:
"""Check that running `ontoconvert` works."""
test_file = (
Path(__file__).resolve().parent.parent / "testonto" / "models.ttl"
)

tool.main([str(test_file), str(tmpdir / "test.ttl")])
jesper-friis marked this conversation as resolved.
Show resolved Hide resolved
15 changes: 15 additions & 0 deletions tests/tools/test_ontodoc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Test the `ontodoc` tool."""
from pathlib import Path

import pytest


@pytest.mark.skip("ontodoc is tested in other ways")
@pytest.mark.parametrize("tool", ["ontodoc"], indirect=True)
def test_run(tool, tmpdir: Path) -> None:
"""Check that running `ontodoc` works."""
test_file = (
Path(__file__).resolve().parent.parent / "testonto" / "models.ttl"
)

tool.main([str(test_file), str(tmpdir / "test.md")])
14 changes: 14 additions & 0 deletions tests/tools/test_ontograph.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""Test the `ontograph` tool."""
from pathlib import Path

import pytest


@pytest.mark.parametrize("tool", ["ontograph"], indirect=True)
def test_run(tool, tmpdir: Path) -> None:
"""Check that running `ontograph` works."""
test_file = (
Path(__file__).resolve().parent.parent / "testonto" / "models.ttl"
)

tool.main([str(test_file), str(tmpdir / "test.png")])
14 changes: 14 additions & 0 deletions tests/tools/test_ontoversion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""Test the `ontoversion` tool."""
import pytest


@pytest.mark.parametrize("tool", ["ontoversion"], indirect=True)
def test_run(tool) -> None:
"""Check running `ontoversion` works."""
from pathlib import Path

test_file = (
Path(__file__).resolve().parent.parent / "testonto" / "testonto.ttl"
)

tool.main([str(test_file), "--format", "ttl"])
16 changes: 12 additions & 4 deletions tools/ontoconvert
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import argparse
import os
import warnings

from rdflib.util import Graph, guess_format
from rdflib.graph import Graph
from rdflib.util import guess_format

from ontopy.utils import (
convert_imported,
Expand All @@ -17,8 +18,15 @@ from ontopy.utils import (
from ontopy.factpluspluswrapper.factppgraph import FaCTPPGraph


def main():
"""Main run function."""
def main(argv: list = None):
"""Main run function.

Parameters:
argv: List of arguments, similar to `sys.argv[1:]`.
Mainly for testing purposes, since it allows one to invoke the tool
manually / through Python.

"""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("input", help="IRI/file to OWL source.")
parser.add_argument("output", help="Output file name.")
Expand Down Expand Up @@ -85,7 +93,7 @@ def main():
),
)

args = parser.parse_args()
args = parser.parse_args(args=argv)

# Inferred default input and output file formats
if args.input_format:
Expand Down
13 changes: 10 additions & 3 deletions tools/ontodoc
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,15 @@ from ontopy.ontodoc import (
import owlready2


def main():
"""Main run function."""
def main(argv: list = None):
"""Main run function.

Parameters:
argv: List of arguments, similar to `sys.argv[1:]`.
Mainly for testing purposes, since it allows one to invoke the tool
manually / through Python.

"""
parser = argparse.ArgumentParser(
description="Tool for documenting ontologies.",
epilog=(
Expand Down Expand Up @@ -192,7 +199,7 @@ def main():
"debugging)."
),
)
args = parser.parse_args()
args = parser.parse_args(args=argv)

# Append to onto_path
for paths in args.path:
Expand Down
15 changes: 12 additions & 3 deletions tools/ontograph
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,17 @@ from ontopy.utils import get_label
import owlready2


def main(): # pylint: disable=too-many-locals,too-many-branches,too-many-statements
"""Main run function."""
def main(
argv: list = None,
): # pylint: disable=too-many-locals,too-many-branches,too-many-statements
"""Main run function.

Parameters:
argv: List of arguments, similar to `sys.argv[1:]`.
Mainly for testing purposes, since it allows one to invoke the tool
manually / through Python.

"""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"iri",
Expand Down Expand Up @@ -193,7 +202,7 @@ def main(): # pylint: disable=too-many-locals,too-many-branches,too-many-statem
parser.add_argument(
"--display", "-D", action="store_true", help="Whether to display graph."
)
args = parser.parse_args()
args = parser.parse_args(args=argv)

# Append to onto_path
for paths in args.path:
Expand Down
13 changes: 10 additions & 3 deletions tools/ontoversion
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,15 @@ def infer_version(iri, version_iri):
return version.lstrip("/").rstrip("/#")


def main():
"""Main run function."""
def main(argv: list = None):
"""Main run function.

Parameters:
argv: List of arguments, similar to `sys.argv[1:]`.
Mainly for testing purposes, since it allows one to invoke the tool
manually / through Python.

"""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"iri",
Expand All @@ -37,7 +44,7 @@ def main():
"--format", "-f", default="xml", help='OWL format. Default is "xml".'
)
try:
args = parser.parse_args()
args = parser.parse_args(args=argv)
except SystemExit as exc:
sys.exit(exc.code) # Exit without traceback on invalid arguments

Expand Down