Skip to content

Commit

Permalink
First tests for dic2owl.Generator class
Browse files Browse the repository at this point in the history
Implement better typing all-round.
  • Loading branch information
CasperWA committed Dec 1, 2021
1 parent 3e09cc7 commit 0e5ac7d
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 50 deletions.
3 changes: 3 additions & 0 deletions dic2owl/dic2owl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
terminal.
"""
# pylint: disable=line-too-long
from .dic2owl import Generator

__version__ = "0.2.0"
__author__ = "Jesper Friis <jesper.friis@sintef.no>, Casper Welzel Andersen <casper.w.andersen@sintef.no>, Francesca Lønstad Bleken <francesca.l.bleken@sintef.no>"
__author_email__ = "cif@emmo-repo.eu"

__all__ = ("Generator",)
42 changes: 25 additions & 17 deletions dic2owl/dic2owl/dic2owl.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

# import textwrap
import types
from typing import Any, Set, Union, Sequence
from typing import TYPE_CHECKING
import urllib.request

from CifFile import CifDic
Expand All @@ -19,24 +19,29 @@
with open(DEVNULL, "w") as handle: # pylint: disable=unspecified-encoding
with redirect_stderr(handle):
from emmo import World
from emmo.ontology import Ontology

from owlready2 import locstr


if TYPE_CHECKING:
from _typeshed import StrPath
from typing import Any, Sequence, Set, Union

from emmo.ontology import Ontology

# Workaround for flaw in EMMO-Python
# To be removed when EMMO-Python doesn't requires ontologies to import SKOS
import emmo.ontology # noqa: E402
import emmo.ontology # pylint: disable=wrong-import-position

emmo.ontology.DEFAULT_LABEL_ANNOTATIONS = [
"http://www.w3.org/2000/01/rdf-schema#label",
]

"""The absolute, normalized path to the `ontology` directory in this
repository"""
ONTOLOGY_DIR = (
Path(__file__).resolve().parent.parent.parent.joinpath("ontology")
)
"""The absolute, normalized path to the `ontology` directory in this
repository"""


def lang_en(string: str) -> locstr:
Expand Down Expand Up @@ -67,40 +72,41 @@ class Generator:
"""

CIF_DDL = (
"https://raw.githubusercontent.com/emmo-repo/CIF-ontology/main/"
"ontology/cif-ddl.ttl"
)

# TODO:
# Should `comments` be replaced with a dict `annotations` for annotating
# the ontology itself? If so, we should import Dublin Core.

def __init__(
self,
dicfile: str,
dicfile: "StrPath",
base_iri: str,
comments: Sequence[str] = (),
comments: "Sequence[str]" = (),
) -> None:
self.dicfile = dicfile
self.dic = CifDic(dicfile, do_dREL=False)
self.dic = CifDic(str(self.dicfile), do_dREL=False)
self.comments = comments

# Create new ontology
self.world = World()
self.onto = self.world.get_ontology(base_iri)

# Load cif-ddl ontology and append it to imported ontologies
cif_ddl = (
"https://raw.githubusercontent.com/emmo-repo/CIF-ontology/main/"
"ontology/cif-ddl.ttl"
)
self.ddl = self.world.get_ontology(str(cif_ddl)).load()
self.ddl = self.world.get_ontology(self.CIF_DDL).load()
self.ddl.sync_python_names()
self.onto.imported_ontologies.append(self.ddl)

# Load Dublin core for metadata and append it to imported ontologies
# dcterms = self.world.get_ontology('http://purl.org/dc/terms/').load()
# self.onto.imported_ontologies.append(dcterms)

self.items: Set[dict] = set()
self.items: "Set[dict]" = set()

def generate(self) -> Ontology:
def generate(self) -> "Ontology":
"""Generate ontology for the CIF dictionary.
Returns:
Expand Down Expand Up @@ -194,7 +200,7 @@ def _add_data_value(self, item: dict) -> None:

self._add_annotations(cls, item)

def _add_annotations(self, cls: Any, item: dict) -> None:
def _add_annotations(self, cls: "Any", item: dict) -> None:
"""Add annotations for dic item `item` to generated ontology
class `cls`.
Expand Down Expand Up @@ -228,7 +234,9 @@ def _add_metadata(self) -> None:
)


def main(dicfile: Union[str, Path], ttlfile: Union[str, Path]) -> Generator:
def main(
dicfile: "Union[str, Path]", ttlfile: "Union[str, Path]"
) -> Generator:
"""Main function for ontology generation.
Parameters:
Expand Down
66 changes: 51 additions & 15 deletions tests/dic2owl/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# pylint: disable=redefined-outer-name,inconsistent-return-statements
from enum import Enum
from pathlib import Path
from typing import List, Optional, TYPE_CHECKING, Union
from typing import TYPE_CHECKING

import pytest

Expand All @@ -17,7 +17,7 @@ class CLI(Enum):

if TYPE_CHECKING:
from subprocess import CompletedProcess
from typing import Callable
from typing import Callable, List, Optional, Union

CLIRunner = Callable[
[
Expand All @@ -32,17 +32,17 @@ class CLI(Enum):

@pytest.fixture(scope="session")
def clirunner() -> "CLIRunner":
"""Call `dic2owl` CLI"""
"""Call a CLI"""
from subprocess import run, CalledProcessError
from tempfile import TemporaryDirectory

def _clirunner(
options: Optional[List[str]] = None,
cli: Optional[Union[CLI, str]] = None,
expected_error: Optional[str] = None,
run_dir: Optional[Union[Path, str]] = None,
) -> Union["CompletedProcess", CalledProcessError]:
"""Call `dic2owl` CLI
options: "Optional[List[str]]" = None,
cli: "Optional[Union[CLI, str]]" = None,
expected_error: "Optional[str]" = None,
run_dir: "Optional[Union[Path, str]]" = None,
) -> "Union[CompletedProcess, CalledProcessError]":
"""Call a CLI
Parameters:
options: Options with which to call `cli`, e.g., `--version`.
Expand Down Expand Up @@ -140,11 +140,47 @@ def cif_ttl(top_dir: Path) -> str:
"""Read and return CIF-Core minimized Turtle file (generated from the
accompanying dictionary).
NOTE: The commend conerning the file location has been removed manually
NOTE: The comment conerning the file location has been removed manually
from this file.
"""
with open(
top_dir / "tests/dic2owl/static/cif_core_minimized.ttl", "r"
) as handle:
content = handle.read()
return content
return (
top_dir / "tests/dic2owl/static/cif_core_minimized.ttl"
).read_text()


@pytest.fixture(scope="session")
def base_iri() -> str:
"""Return standard CIF-Core base IRI."""
return "http://emmo.info/CIF-ontology/ontology/cif_core#"


@pytest.fixture(scope="session")
def cif_dic_path(top_dir: Path) -> Path:
"""Return path to minimized CIF-Core dictionary."""
return top_dir / "tests" / "dic2owl" / "static" / "cif_core_minimized.dic"


@pytest.fixture
def create_location_free_ttl() -> "Callable[[Path], str]":
"""Create file location comment-free turtle file."""

def _create_location_free_ttl(ttlfile: Path) -> str:
"""Create file location comment-free turtle file.
Parameters:
ttlfile: Path to turtle file.
Returns:
Content of turtle file without the file location line.
"""
generated_ttl = ""
with ttlfile.open() as handle:
for line in handle.readlines():
if "dic2owl" in line:
# Skip comment line concerning the file location
pass
else:
generated_ttl += line
return generated_ttl

return _create_location_free_ttl
34 changes: 16 additions & 18 deletions tests/dic2owl/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from typing import Callable

from .conftest import CLIRunner


Expand All @@ -16,7 +18,10 @@ def test_version(clirunner: "CLIRunner") -> None:


def test_local_file(
clirunner: "CLIRunner", top_dir: Path, cif_ttl: str
clirunner: "CLIRunner",
top_dir: Path,
cif_ttl: str,
create_location_free_ttl: "Callable[[Path], str]",
) -> None:
"""Test a normal/default run with minimum input.
Expand All @@ -35,14 +40,9 @@ def test_local_file(
"downloading" in output.stdout
), f"STDOUT: {output.stdout}\nSTDERR: {output.stderr}"

generated_ttl = ""
with open(Path(tmpdir) / "cif_core_minimized.ttl", "r") as handle:
for line in handle.readlines():
if "dic2owl" in line:
# Skip comment line concerning the file location
pass
else:
generated_ttl += line
generated_ttl = create_location_free_ttl(
Path(tmpdir) / "cif_core_minimized.ttl"
)

assert generated_ttl == cif_ttl

Expand All @@ -54,7 +54,12 @@ def test_local_file(
)


def test_output(clirunner: "CLIRunner", top_dir: Path, cif_ttl: str) -> None:
def test_output(
clirunner: "CLIRunner",
top_dir: Path,
cif_ttl: str,
create_location_free_ttl: "Callable[[Path], str]",
) -> None:
"""Test `--output`.
NOTE: The commend conerning the file location has been removed from the
Expand All @@ -75,14 +80,7 @@ def test_output(clirunner: "CLIRunner", top_dir: Path, cif_ttl: str) -> None:
"downloading" in output.stdout
), f"STDOUT: {output.stdout}\nSTDERR: {output.stderr}"

generated_ttl = ""
with open(out_ttl, "r") as handle:
for line in handle.readlines():
if "dic2owl" in line:
# Skip comment line concerning the file location
pass
else:
generated_ttl += line
generated_ttl = create_location_free_ttl(Path(out_ttl))

assert generated_ttl == cif_ttl

Expand Down
92 changes: 92 additions & 0 deletions tests/dic2owl/test_dic2owl_generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"""Test the `dic2owl.dic2owl.Generator` class."""
# pylint: disable=redefined-outer-name,import-outside-toplevel
from pathlib import Path
from typing import TYPE_CHECKING

import pytest

if TYPE_CHECKING:
from typing import Callable, List, Optional

from dic2owl import Generator


@pytest.fixture(scope="session")
def sample_generator_comments() -> "List[str]":
"""The comments to be used for the `sample_generator` fixture."""
return ["This is a test."]


@pytest.fixture
def sample_generator(
base_iri: str, cif_dic_path: Path, sample_generator_comments: "List[str]"
) -> "Callable[[Optional[List[str]]], Generator]":
"""Create a generator similar to what is tested in
`test_initialization()`."""
from dic2owl import Generator

def _sample_generator(comments: "Optional[List[str]]" = None) -> Generator:
"""Create and return a `Generator` with specific list of metadata
comments. By default, the fixture `sample_generator_comments` is
used."""
return Generator(
dicfile=cif_dic_path,
base_iri=base_iri,
comments=sample_generator_comments
if comments is None
else comments,
)

return _sample_generator


def test_initialization(
base_iri: str, cif_dic_path: Path, sample_generator_comments: "List[str]"
) -> None:
"""Ensure a newly initialized Generator has intended ontologies and
properties."""
from CifFile import CifDic
from dic2owl import Generator

cif_dictionary = CifDic(str(cif_dic_path), do_dREL=False)

generator = Generator(
dicfile=cif_dic_path,
base_iri=base_iri,
comments=sample_generator_comments,
)

assert generator
assert generator.dic.WriteOut() == cif_dictionary.WriteOut()
assert generator.ddl
assert generator.ddl in generator.onto.imported_ontologies
assert generator.comments == sample_generator_comments


def test_generate(
cif_ttl: str,
create_location_free_ttl: "Callable[[Path], str]",
sample_generator: "Callable[[Optional[List[str]]], Generator]",
sample_generator_comments: "List[str]",
) -> None:
"""Test the `generate()` method."""
from tempfile import NamedTemporaryFile

generator = sample_generator(None)
generated_ontology = generator.generate()

for comment in sample_generator_comments:
assert comment in generated_ontology.metadata.comment
assert (
f"Generated with dic2owl from {generator.dicfile}"
in generated_ontology.metadata.comment
)

generated_ontology = sample_generator([]).generate()

with NamedTemporaryFile() as output_turtle:
generated_ontology.save(output_turtle.name, format="turtle")

generated_ttl = create_location_free_ttl(Path(output_turtle.name))

assert generated_ttl == cif_ttl

0 comments on commit 0e5ac7d

Please sign in to comment.