From 381711015d13a8cfe4bd3b0280a7328e99c19aea Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Mon, 1 Aug 2022 20:50:09 +0200 Subject: [PATCH 01/25] Added URL to the message of HTTPError exceptions when loading an ontology --- ontopy/ontology.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ontopy/ontology.py b/ontopy/ontology.py index 3f41a291a..0ecfcfeee 100644 --- a/ontopy/ontology.py +++ b/ontopy/ontology.py @@ -20,6 +20,7 @@ from pathlib import Path from collections import defaultdict from collections.abc import Iterable +from urllib.request import HTTPError import rdflib from rdflib.util import guess_format @@ -610,6 +611,12 @@ def getmtime(path): format="rdfxml", **kwargs, ) + except HTTPError as exc: + # The string representation of HTTPError does unfortunately + # not include the URL. Include it in the message and + # re-raise the exception. This makes debugging easier... + exc.msg = f"{exc.url}: {exc.msg}" + raise exc def save( self, From 09a3460cf1efcf87cc6cfc2dabb9bd646d654b12 Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Mon, 1 Aug 2022 21:34:13 +0200 Subject: [PATCH 02/25] Improved implementation of more verbose error messages --- ontopy/ontology.py | 40 ++++++++++++++++++++++++++-------------- ontopy/utils.py | 11 +++++++++-- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/ontopy/ontology.py b/ontopy/ontology.py index 0ecfcfeee..f2344c7c1 100644 --- a/ontopy/ontology.py +++ b/ontopy/ontology.py @@ -603,20 +603,32 @@ def getmtime(path): self.loaded = False with open(output, "rb") as handle: - return super().load( - only_local=True, - fileobj=handle, - reload=reload, - reload_if_newer=reload_if_newer, - format="rdfxml", - **kwargs, - ) - except HTTPError as exc: - # The string representation of HTTPError does unfortunately - # not include the URL. Include it in the message and - # re-raise the exception. This makes debugging easier... - exc.msg = f"{exc.url}: {exc.msg}" - raise exc + try: + return super().load( + only_local=True, + fileobj=handle, + reload=reload, + reload_if_newer=reload_if_newer, + format="rdfxml", + **kwargs, + ) + except HTTPError as exc: # Add url to HTTPError message + raise HTTPError( + url=exc.url, + code=exc.code, + msg=f"{exc.url}: {exc.msg}", + hdrs=exc.hdrs, + fp=exc.fp, + ).with_traceback(exc.__traceback__) + + except HTTPError as exc: # Add url to HTTPError message + raise HTTPError( + url=exc.url, + code=exc.code, + msg=f"{exc.url}: {exc.msg}", + hdrs=exc.hdrs, + fp=exc.fp, + ).with_traceback(exc.__traceback__) def save( self, diff --git a/ontopy/utils.py b/ontopy/utils.py index 9574bbe85..d193973f4 100644 --- a/ontopy/utils.py +++ b/ontopy/utils.py @@ -15,6 +15,7 @@ from rdflib import Graph, URIRef from rdflib.util import guess_format +from rdflib.plugin import PluginException import owlready2 @@ -26,9 +27,10 @@ # Format mappings: file extension -> rdflib format name FMAP = { + "": "turtle", + "ttl": "turtle", "n3": "ntriples", "nt": "ntriples", - "ttl": "turtle", "owl": "xml", "rdfxml": "xml", } @@ -579,7 +581,12 @@ def recur(graph, outext): ) graph = Graph() - graph.parse(input_ontology, format=fmt) + try: + graph.parse(input_ontology, format=fmt) + except PluginException as exc: # Add input_ontology to exception msg + raise PluginException( + f'Cannot load "{input_ontology}": {exc.msg}' + ).with_traceback(exc.__traceback__) graph.serialize(destination=output_ontology, format=output_format) recur(graph, outext) From bde56fae9e3fcd86655a6363346d28e6f7d4224a Mon Sep 17 00:00:00 2001 From: francescalb Date: Thu, 11 Aug 2022 10:47:50 +0200 Subject: [PATCH 03/25] Use prefix as arguemnt instead of namespace in get_by_label Changed argument to prefix in get_by_label and get_by_label_all when only entities from a given ontology with a given prefix are desired. The prefix is currently being compared to entity._namspace.name but a solution for prefix should be implemented. --- ontopy/ontology.py | 39 ++++++++++++++++++++++--------------- tests/testonto/testonto.ttl | 5 +++++ 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/ontopy/ontology.py b/ontopy/ontology.py index e38480aaa..d3d08a481 100644 --- a/ontopy/ontology.py +++ b/ontopy/ontology.py @@ -153,6 +153,11 @@ class Ontology( # pylint: disable=too-many-public-methods ): """A generic class extending owlready2.Ontology.""" + # def __init__(self, *args, **kwargs): + # # Set ontology prefix to ontology._namespace.name as default + # self.prefix = self._namespace.name + # super().__init__(*args, **kwargs) + # Properties controlling what annotations that are considered by # get_by_label() _label_annotations = [] @@ -258,7 +263,7 @@ def get_unabbreviated_triples(self, label=None): return World.get_unabbreviated_triples(self, label) def get_by_label( - self, label, label_annotations=None, namespace=None + self, label, label_annotations=None, prefix=None ): # pylint: disable=too-many-arguments,too-many-branches """Returns entity with label annotation `label`. @@ -289,20 +294,22 @@ def get_by_label( f"Invalid label definition, {label!r} contains spaces." ) - if "namespaces" in self.__dict__: - if namespace: - if namespace in self.namespaces: - for entity in self.get_by_label_all( - label, label_annotations=label_annotations - ): - if entity.namespace == self.namespaces[namespace]: - return entity + if "_namespaces" in self.__dict__: + if prefix: + entitylist = self.get_by_label_all( + label, + label_annotations=label_annotations, + prefix=prefix, + ) + if len(entitylist) > 0: + return entitylist[0] + raise NoSuchLabelError( - f"No label annotations matches {label!r} in namespace " - f"{namespace!r}" + f"No label annotations matches {label!r} with prefix " + f"{prefix!r}" ) - if label in self.namespaces: - return self.namespaces[label] + # if label in self._namespaces: + # return self._namespaces[label] if label_annotations is None: annotations = (a.name for a in self.label_annotations) @@ -324,7 +331,7 @@ def get_by_label( raise NoSuchLabelError(f"No label annotations matches {label!r}") - def get_by_label_all(self, label, label_annotations=None, namespace=None): + def get_by_label_all(self, label, label_annotations=None, prefix=None): """Like get_by_label(), but returns a list with all matching labels. Returns an empty list if no matches could be found. @@ -349,8 +356,8 @@ def get_by_label_all(self, label, label_annotations=None, namespace=None): entity.extend(self.world.search(**{key: label})) if self._special_labels and label in self._special_labels: entity.append(self._special_labels[label]) - if namespace: - return [_ for _ in entity if _.namespace.name == namespace] + if prefix: + return [_ for _ in entity if _.namespace.name == prefix] return entity def add_label_annotation(self, iri): diff --git a/tests/testonto/testonto.ttl b/tests/testonto/testonto.ttl index 2398f875a..1e75e59e3 100644 --- a/tests/testonto/testonto.ttl +++ b/tests/testonto/testonto.ttl @@ -11,3 +11,8 @@ owl:versionIRI ; owl:imports ; owl:versionInfo "0.1.0" . + + +:testclass rdf:type owl:Class ; + rdfs:subClassOf owl:Thing ; + skos:prefLabel "TestClass"@en . From 8d1215a5aaa410c1e47341de554a65d2df6887a1 Mon Sep 17 00:00:00 2001 From: francescalb Date: Thu, 11 Aug 2022 10:56:34 +0200 Subject: [PATCH 04/25] Updated documentation with prefix for get_by_label --- ontopy/ontology.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/ontopy/ontology.py b/ontopy/ontology.py index d3d08a481..c35128b1e 100644 --- a/ontopy/ontology.py +++ b/ontopy/ontology.py @@ -267,13 +267,14 @@ def get_by_label( ): # pylint: disable=too-many-arguments,too-many-branches """Returns entity with label annotation `label`. - `label_annotations` is a sequence of label annotation names to look up. - Defaults to the `label_annotations` property. + Args: + label_annotations: a sequence of label annotation names to look up. + Defaults to the `label_annotations` property. - If `namespace` is provided, it should be the last component of - the base iri of an ontology (with trailing slash (/) or hash - (#) stripped off). The search for a matching label will be - limited to this namespace. + prefix: if provided, it should be the last component of + the base iri of an ontology (with trailing slash (/) or hash + (#) stripped off). The search for a matching label will be + limited to this namespace. If several entities have the same label, only the one which is found first is returned.Use get_by_label_all() to get all matches. From e84067c1623c47428600792d073d0884d2ab5ed4 Mon Sep 17 00:00:00 2001 From: francescalb Date: Thu, 11 Aug 2022 14:54:26 +0200 Subject: [PATCH 05/25] Added automatic detection of prefix for the loaded ontology and tests --- ontopy/ontology.py | 37 +++++++++++++++++++++++-------------- tests/ontopy/test_prefix.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 14 deletions(-) create mode 100644 tests/ontopy/test_prefix.py diff --git a/ontopy/ontology.py b/ontopy/ontology.py index c35128b1e..536ca37f7 100644 --- a/ontopy/ontology.py +++ b/ontopy/ontology.py @@ -169,6 +169,7 @@ class Ontology( # pylint: disable=too-many-public-methods # Name of special unlabeled entities, like Thing, Nothing, etc... _special_labels = None + prefix = None # Some properties for customising dir() listing - useful in # interactive sessions... _dir_preflabel = isinteractive() @@ -295,20 +296,20 @@ def get_by_label( f"Invalid label definition, {label!r} contains spaces." ) - if "_namespaces" in self.__dict__: - if prefix: - entitylist = self.get_by_label_all( - label, - label_annotations=label_annotations, - prefix=prefix, - ) - if len(entitylist) > 0: - return entitylist[0] + # if "_namespaces" in self.__dict__: + if prefix: + entitylist = self.get_by_label_all( + label, + label_annotations=label_annotations, + prefix=prefix, + ) + if len(entitylist) > 0: + return entitylist[0] - raise NoSuchLabelError( - f"No label annotations matches {label!r} with prefix " - f"{prefix!r}" - ) + raise NoSuchLabelError( + f"No label annotations matches {label!r} with prefix " + f"{prefix!r}" + ) # if label in self._namespaces: # return self._namespaces[label] @@ -392,6 +393,7 @@ def load( # pylint: disable=too-many-arguments,arguments-renamed url_from_catalog=None, catalog_file="catalog-v001.xml", emmo_based=True, + prefix=None, **kwargs, ): """Load the ontology. @@ -422,6 +424,7 @@ def load( # pylint: disable=too-many-arguments,arguments-renamed defaults to "catalog-v001.xml". emmo_based : bool Whether this is an EMMO-based ontology or not, default `True`. + prefix : defaults to self.get_namespace.name if kwargs Additional keyword arguments are passed on to owlready2.Ontology.load(). @@ -454,7 +457,13 @@ def load( # pylint: disable=too-many-arguments,arguments-renamed "owl:Nothing": owlready2.Nothing, "owl:topObjectProperty": top, } - + # set prefix if anotehr prefix is desired + # if we do this, shouldn't we make the name of all + # entities of the given ontology to the same? + if prefix: + self.prefix = prefix + else: + self.prefix = self.name return self def _load( # pylint: disable=too-many-arguments,too-many-locals,too-many-branches,too-many-statements diff --git a/tests/ontopy/test_prefix.py b/tests/ontopy/test_prefix.py new file mode 100644 index 000000000..7075db060 --- /dev/null +++ b/tests/ontopy/test_prefix.py @@ -0,0 +1,33 @@ +from typing import TYPE_CHECKING +import pytest +from ontopy.utils import NoSuchLabelError + +if TYPE_CHECKING: + from pathlib import Path + + +def test_prefix(repo_dir: "Path") -> None: + """Test get_version function in ontology""" + from ontopy import get_ontology + + ontopath = repo_dir / "tests" / "testonto" + testonto = get_ontology(str(ontopath) + "/testonto.ttl").load() + + assert len(testonto.get_by_label_all("*")) == 2 + assert testonto.get_by_label_all("*", prefix="testonto") == [ + testonto.TestClass + ] + assert ( + testonto.get_by_label("TestClass", prefix="testonto") + == testonto.TestClass + ) + assert ( + str(testonto.get_by_label("TestClass", prefix="models")) + == "models.TestClass" + ) + + with pytest.raises(NoSuchLabelError): + testonto.get_by_label("TestClassClass", prefix="models") + + assert testonto.prefix == "testonto" + assert testonto.get_imported_ontologies()[0].prefix == "models" From cb82c760adccee7b65189f3547cc532da0edb56a Mon Sep 17 00:00:00 2001 From: francescalb Date: Thu, 11 Aug 2022 15:45:05 +0200 Subject: [PATCH 06/25] Corrected to that prefix actually cahcek for the prefix of the ontology where the entity is from and not the name of the namespace as given in owldreay2 --- ontopy/ontology.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ontopy/ontology.py b/ontopy/ontology.py index 536ca37f7..4452a3a66 100644 --- a/ontopy/ontology.py +++ b/ontopy/ontology.py @@ -359,7 +359,7 @@ def get_by_label_all(self, label, label_annotations=None, prefix=None): if self._special_labels and label in self._special_labels: entity.append(self._special_labels[label]) if prefix: - return [_ for _ in entity if _.namespace.name == prefix] + return [_ for _ in entity if _.namespace.ontology.prefix == prefix] return entity def add_label_annotation(self, iri): From cf9d7a8f28275897f94a881b0c86c84dc99c5936 Mon Sep 17 00:00:00 2001 From: francescalb Date: Thu, 11 Aug 2022 17:16:45 +0200 Subject: [PATCH 07/25] Added support for get_by_label('prefix:entity') --- emmopy/emmopy.py | 2 +- ontopy/ontology.py | 47 ++++++++++++++++++++++++++++++++++++- tests/ontopy/test_prefix.py | 16 +++++++++++++ 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/emmopy/emmopy.py b/emmopy/emmopy.py index 082940134..fa0b99914 100644 --- a/emmopy/emmopy.py +++ b/emmopy/emmopy.py @@ -22,4 +22,4 @@ def get_emmo(inferred: Optional[bool] = True) -> "Ontology": The loaded emmo ontology. """ name = "emmo-inferred" if inferred in [True, None] else "emmo" - return get_ontology(name).load() + return get_ontology(name).load(prefix_emmo=True) diff --git a/ontopy/ontology.py b/ontopy/ontology.py index 4452a3a66..df2cabadc 100644 --- a/ontopy/ontology.py +++ b/ontopy/ontology.py @@ -296,7 +296,24 @@ def get_by_label( f"Invalid label definition, {label!r} contains spaces." ) - # if "_namespaces" in self.__dict__: + splitlabel = label.split(":") + if len(splitlabel) > 2: + raise ValueError( + f"Invalid label definition, {label!r}" + " contains more than one ':' ." + "The string before ':' indicates the prefix. " + "The string after ':' indicates the label." + ) + if len(splitlabel) == 2: + label = splitlabel[1] + if prefix and prefix != splitlabel[0]: + warnings.warn( + f"Prefix given both as argument ({prefix}) " + f"and in label ({splitlabel[0]}). " + "Prefix given in label takes presendence " + ) + prefix = splitlabel[0] + if prefix: entitylist = self.get_by_label_all( label, @@ -383,6 +400,24 @@ def remove_label_annotation(self, iri): raise ValueError(f"IRI not in ontology: {iri}") self._label_annotations.remove(label_annotation) + def set_common_prefix( + self, + iri_base: str = "http://emmo.info/emmo", + prefix: str = "emmo", + ) -> None: + """Set a common prefix for all imported ontologies + with the same first part of the base_iri. + + Args: + iri_base: The start of the base_iri to look for. Defaults to + the emmo base_iri http://emmo.info/emmo + prefix: the desired prefix. Defaults to emmo. + """ + if self.base_iri.startswith(iri_base): + self.prefix = prefix + for onto in self.imported_ontologies: + onto.set_common_prefix(iri_base=iri_base, prefix=prefix) + def load( # pylint: disable=too-many-arguments,arguments-renamed self, only_local=False, @@ -394,6 +429,7 @@ def load( # pylint: disable=too-many-arguments,arguments-renamed catalog_file="catalog-v001.xml", emmo_based=True, prefix=None, + prefix_emmo=None, **kwargs, ): """Load the ontology. @@ -425,6 +461,9 @@ def load( # pylint: disable=too-many-arguments,arguments-renamed emmo_based : bool Whether this is an EMMO-based ontology or not, default `True`. prefix : defaults to self.get_namespace.name if + prefix_emmo: bool, default None. If emmo_based is True it + defaults to True and sets the prefix of all imported ontologies + with base_iri starting with 'http://emmo.info/emmo' to emmo kwargs Additional keyword arguments are passed on to owlready2.Ontology.load(). @@ -464,6 +503,12 @@ def load( # pylint: disable=too-many-arguments,arguments-renamed self.prefix = prefix else: self.prefix = self.name + + if emmo_based and prefix_emmo is None: + prefix_emmo = True + if prefix_emmo: + self.set_common_prefix() + return self def _load( # pylint: disable=too-many-arguments,too-many-locals,too-many-branches,too-many-statements diff --git a/tests/ontopy/test_prefix.py b/tests/ontopy/test_prefix.py index 7075db060..331e15dfd 100644 --- a/tests/ontopy/test_prefix.py +++ b/tests/ontopy/test_prefix.py @@ -9,6 +9,7 @@ def test_prefix(repo_dir: "Path") -> None: """Test get_version function in ontology""" from ontopy import get_ontology + from emmopy import get_emmo ontopath = repo_dir / "tests" / "testonto" testonto = get_ontology(str(ontopath) + "/testonto.ttl").load() @@ -31,3 +32,18 @@ def test_prefix(repo_dir: "Path") -> None: assert testonto.prefix == "testonto" assert testonto.get_imported_ontologies()[0].prefix == "models" + + assert testonto.get_by_label("models:TestClass") == testonto.get_by_label( + "TestClass", prefix="models" + ) + + emmo = get_emmo("emmo") + + assert emmo.Atom.namespace.ontology.prefix == "emmo" + assert emmo.get_by_label("Atom", prefix="emmo") == emmo.Atom + + # To be added when emmo-inferred is released with the correcto iri + # emmo_inferred = get_emmo() + + # assert emmo_inferred.Atom.namespace.ontology.prefix == 'emmo' + # assert emmo_inferred.get_by_label('Atom', prefix='emmo') == emmo_inferred.Atom From fcdf20ae17228b797e501b50795f66592e8e0474 Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Thu, 11 Aug 2022 23:19:43 +0200 Subject: [PATCH 08/25] Added test --- tests/test_load.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_load.py b/tests/test_load.py index d18ff60df..0b966c70f 100755 --- a/tests/test_load.py +++ b/tests/test_load.py @@ -5,7 +5,7 @@ def test_load(repo_dir: "Path") -> None: - from ontopy import get_ontology + from ontopy import get_ontology, HTTPError # Check that the defaults works emmo = get_ontology("emmo").load() # ttl format @@ -34,6 +34,9 @@ def test_load(repo_dir: "Path") -> None: ).load() assert onto.Electrolyte.prefLabel.first() == "Electrolyte" + with pytest.raises(HTTPError): + get_ontology("http://emmo.info/non-existing/ontology#").load() + def test_load_rdfs() -> None: from ontopy import get_ontology From d068c3b6ac6284ced210abb3067e31e494a18b9a Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Fri, 12 Aug 2022 00:11:51 +0200 Subject: [PATCH 09/25] Corrected import of HTTPError --- tests/test_load.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_load.py b/tests/test_load.py index 0b966c70f..5b80cfb2d 100755 --- a/tests/test_load.py +++ b/tests/test_load.py @@ -5,7 +5,8 @@ def test_load(repo_dir: "Path") -> None: - from ontopy import get_ontology, HTTPError + from ontopy import get_ontology + from ontopy.ontology import HTTPError # Check that the defaults works emmo = get_ontology("emmo").load() # ttl format From 224fb57dd5384e89447d74fb68d63d4236d50502 Mon Sep 17 00:00:00 2001 From: francescalb Date: Fri, 12 Aug 2022 10:10:17 +0200 Subject: [PATCH 10/25] Updated docstring of get_by_label --- ontopy/ontology.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/ontopy/ontology.py b/ontopy/ontology.py index df2cabadc..544ebf5b8 100644 --- a/ontopy/ontology.py +++ b/ontopy/ontology.py @@ -264,18 +264,21 @@ def get_unabbreviated_triples(self, label=None): return World.get_unabbreviated_triples(self, label) def get_by_label( - self, label, label_annotations=None, prefix=None + self, label: str, label_annotations: str = None, prefix: str = None ): # pylint: disable=too-many-arguments,too-many-branches """Returns entity with label annotation `label`. Args: + label: label so serach for. + May be written as 'label' or 'prefix:label'. + get_by_label('prefix:label') == + get_by_label('label', prefix='prefix'). label_annotations: a sequence of label annotation names to look up. - Defaults to the `label_annotations` property. - + Defaults to the `label_annotations` property. prefix: if provided, it should be the last component of - the base iri of an ontology (with trailing slash (/) or hash - (#) stripped off). The search for a matching label will be - limited to this namespace. + the base iri of an ontology (with trailing slash (/) or hash + (#) stripped off). The search for a matching label will be + limited to this namespace. If several entities have the same label, only the one which is found first is returned.Use get_by_label_all() to get all matches. From d76b91d2d73f44d381a2ba90f1eeacccda57ab45 Mon Sep 17 00:00:00 2001 From: francescalb Date: Wed, 17 Aug 2022 09:44:31 +0200 Subject: [PATCH 11/25] Added testonto to fixtures. This revealed a different issue that should be adressed separately. Test will fail. --- ontopy/ontology.py | 2 +- tests/conftest.py | 12 +++++++++ tests/ontopy_tests/conftest.py | 10 ------- tests/ontopy_tests/test_get_version.py | 3 +-- tests/{ontopy => ontopy_tests}/test_prefix.py | 26 ++++++++----------- tests/ontopy_tests/test_utils.py | 22 ++++++++-------- tests/test_basic.py | 8 ++---- tests/test_load.py | 6 ++--- 8 files changed, 40 insertions(+), 49 deletions(-) rename tests/{ontopy => ontopy_tests}/test_prefix.py (55%) diff --git a/ontopy/ontology.py b/ontopy/ontology.py index fc6305852..0baee32dc 100644 --- a/ontopy/ontology.py +++ b/ontopy/ontology.py @@ -501,7 +501,7 @@ def load( # pylint: disable=too-many-arguments,arguments-renamed "owl:Nothing": owlready2.Nothing, "owl:topObjectProperty": top, } - # set prefix if anotehr prefix is desired + # set prefix if another prefix is desired # if we do this, shouldn't we make the name of all # entities of the given ontology to the same? if prefix: diff --git a/tests/conftest.py b/tests/conftest.py index 49bbf9137..47c216155 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -32,3 +32,15 @@ def tmpdir() -> Path: res = TemporaryDirectory() yield Path(res.name) res.cleanup() + + +@pytest.fixture +def testonto() -> "Ontology": + """Load and return the local testonto.""" + from ontopy import get_ontology + + path = Path(__file__).parent.parent.resolve() / "tests" / "testonto" + + testonto = get_ontology(str(path) + "/testonto.ttl").load() + + return testonto diff --git a/tests/ontopy_tests/conftest.py b/tests/ontopy_tests/conftest.py index f6b2bac99..b17d6ba12 100644 --- a/tests/ontopy_tests/conftest.py +++ b/tests/ontopy_tests/conftest.py @@ -45,13 +45,3 @@ def has_triple(onto, s=None, p=None, o=None) -> bool: return bool(get_triples(onto, s, p, o)) except ValueError: return False - - -@pytest.fixture -def onto() -> "ontopy.Ontology": - """Test ontology.""" - from ontopy import get_ontology - - url = Path(__file__).parent.parent / "testonto" / "testonto.ttl" - onto = get_ontology(url).load() - return onto diff --git a/tests/ontopy_tests/test_get_version.py b/tests/ontopy_tests/test_get_version.py index 3bd7b5b13..dc2c5d606 100644 --- a/tests/ontopy_tests/test_get_version.py +++ b/tests/ontopy_tests/test_get_version.py @@ -5,12 +5,11 @@ from pathlib import Path -def test_get_version(repo_dir: "Path") -> None: +def test_get_version(repo_dir: "Path", testonto: "Ontology") -> None: """Test get_version function in ontology""" from ontopy import get_ontology ontopath = repo_dir / "tests" / "testonto" - testonto = get_ontology(str(ontopath) + "/testonto.ttl").load() assert ( testonto.get_version(as_iri=True) == "http://emmo.info/testonto/0.1.0" ) diff --git a/tests/ontopy/test_prefix.py b/tests/ontopy_tests/test_prefix.py similarity index 55% rename from tests/ontopy/test_prefix.py rename to tests/ontopy_tests/test_prefix.py index 331e15dfd..90862e098 100644 --- a/tests/ontopy/test_prefix.py +++ b/tests/ontopy_tests/test_prefix.py @@ -6,13 +6,8 @@ from pathlib import Path -def test_prefix(repo_dir: "Path") -> None: - """Test get_version function in ontology""" - from ontopy import get_ontology - from emmopy import get_emmo - - ontopath = repo_dir / "tests" / "testonto" - testonto = get_ontology(str(ontopath) + "/testonto.ttl").load() +def test_prefix(testonto: "Ontology", emmo: "Ontology") -> None: + """Test prefix in ontology""" assert len(testonto.get_by_label_all("*")) == 2 assert testonto.get_by_label_all("*", prefix="testonto") == [ @@ -37,13 +32,14 @@ def test_prefix(repo_dir: "Path") -> None: "TestClass", prefix="models" ) - emmo = get_emmo("emmo") - - assert emmo.Atom.namespace.ontology.prefix == "emmo" - assert emmo.get_by_label("Atom", prefix="emmo") == emmo.Atom + # def test_prefix_emmo(emmo: "Ontology") -> None: + # """Test prefix in ontology""" + # from emmopy import get_emmo - # To be added when emmo-inferred is released with the correcto iri - # emmo_inferred = get_emmo() + # Check that the prefix of emmo-inferred becomes emmo + # assert emmo.Atom.namespace.ontology.prefix == 'emmo' + # assert emmo.get_by_label('Atom', prefix='emmo') == emmo.Atom - # assert emmo_inferred.Atom.namespace.ontology.prefix == 'emmo' - # assert emmo_inferred.get_by_label('Atom', prefix='emmo') == emmo_inferred.Atom + # emmo_asserted = get_emmo('emmo') + # assert emmo_asserted.Atom.namespace.ontology.prefix == "emmo" + # assert emmo_asserted.get_by_label("Atom", prefix="emmo") == emmo_asserted.Atom diff --git a/tests/ontopy_tests/test_utils.py b/tests/ontopy_tests/test_utils.py index d9f45f2eb..4b5647682 100644 --- a/tests/ontopy_tests/test_utils.py +++ b/tests/ontopy_tests/test_utils.py @@ -2,37 +2,37 @@ from testutils import get_triples, has_triple -def test_annotate_source(onto): +def test_annotate_source(testonto: "Ontology"): assert not has_triple( - onto, + testonto, "http://emmo.info/models#testclass", "http://www.w3.org/2000/01/rdf-schema#isDefinedBy", "http://emmo.info/models#", ) - utils.annotate_source(onto, imported=False) + utils.annotate_source(testonto, imported=False) assert not has_triple( - onto, + testonto, "http://emmo.info/models#testclass", "http://www.w3.org/2000/01/rdf-schema#isDefinedBy", "http://emmo.info/models#", ) - utils.annotate_source(onto, imported=True) + utils.annotate_source(testonto, imported=True) assert has_triple( - onto, + testonto, "http://emmo.info/models#testclass", "http://www.w3.org/2000/01/rdf-schema#isDefinedBy", "http://emmo.info/models#", ) -def test_rename_iris(onto): - assert not has_triple(onto, s="http://emmo.info/models#TestClass") - utils.rename_iris(onto) - assert has_triple(onto, s="http://emmo.info/models#TestClass") +def test_rename_iris(testonto: "Ontology"): + assert not has_triple(testonto, s="http://emmo.info/models#TestClass") + utils.rename_iris(testonto) + assert has_triple(testonto, s="http://emmo.info/models#TestClass") assert has_triple( - onto, + testonto, "http://emmo.info/models#TestClass", "http://www.w3.org/2004/02/skos/core#exactMatch", "http://emmo.info/models#testclass", diff --git a/tests/test_basic.py b/tests/test_basic.py index 5c5c24287..28ee9f880 100755 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -52,11 +52,7 @@ class H2O(emmo.Molecule): assert len(water.name) == len(name_prefix) + 36 -def test_sync_reasoner(repo_dir: "Path") -> None: +def test_sync_reasoner(testonto: "Ontology") -> None: """Test `ontopy:Ontology.sync_reasoner()`.""" - from ontopy import get_ontology - - ontodir = repo_dir / "tests" / "testonto" - onto: "Ontology" = get_ontology((ontodir / "testonto.ttl").as_uri()) - onto.sync_reasoner() + testonto.sync_reasoner() diff --git a/tests/test_load.py b/tests/test_load.py index d18ff60df..c3366f5fb 100755 --- a/tests/test_load.py +++ b/tests/test_load.py @@ -4,7 +4,7 @@ from pathlib import Path -def test_load(repo_dir: "Path") -> None: +def test_load(repo_dir: "Path", testonto: "Ontology") -> None: from ontopy import get_ontology # Check that the defaults works @@ -23,9 +23,7 @@ def test_load(repo_dir: "Path") -> None: assert emmo.Atom.prefLabel.first() == "Atom" # Load a local ontology with catalog - testonto = repo_dir / "tests" / "testonto" / "testonto.ttl" - onto = get_ontology(testonto).load() - assert onto.TestClass.prefLabel.first() == "TestClass" + assert testonto.TestClass.prefLabel.first() == "TestClass" # Use catalog file when downloading from web onto = get_ontology( From 9ac54f2ba69ee130f35863d63fede892e09da693 Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Wed, 17 Aug 2022 14:49:19 +0200 Subject: [PATCH 12/25] Removed dependency on pydot --- ontopy/ontology.py | 41 +++++++++++++++++------------------------ 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/ontopy/ontology.py b/ontopy/ontology.py index e38480aaa..b7dbf156c 100644 --- a/ontopy/ontology.py +++ b/ontopy/ontology.py @@ -1,12 +1,7 @@ # -*- coding: utf-8 -*- -"""A module adding additional functionality to owlready2. The main additions -includes: - - Visualisation of taxonomy and ontology as graphs (using pydot, see - ontograph.py). +"""A module adding additional functionality to owlready2. -The class extension is defined within. - -If desirable some of this may be moved back into owlready2. +If desirable some of these additions may be moved back into owlready2. """ # pylint: disable=too-many-lines,fixme,arguments-differ,protected-access from typing import TYPE_CHECKING, Union, Sequence @@ -46,8 +41,6 @@ ThingClassDefinitionError, ) -from ontopy.ontograph import OntoGraph # FIXME: deprecate... - if TYPE_CHECKING: from typing import List @@ -126,9 +119,15 @@ def get_ontology(self, base_iri="emmo-inferred"): return onto - def get_unabbreviated_triples(self, label=None): + def get_unabbreviated_triples( + self, subject=None, predicate=None, obj=None, label=None + ): + # pylint: disable=invalid-name """Returns all triples unabbreviated. + If any of the `subject`, `predicate` or `object` arguments are given, + only matching triples will be returned. + If `label` is given, it will be used to represent blank nodes. """ @@ -140,17 +139,11 @@ def _unabbreviate(i): return BlankNode(self, i) if label is None else label return i - for subject, predicate, obj in self.get_triples(): - yield ( - _unabbreviate(subject), - _unabbreviate(predicate), - _unabbreviate(obj), - ) + for s, p, o in self.get_triples(subject, predicate, obj): + yield (_unabbreviate(s), _unabbreviate(p), _unabbreviate(o)) -class Ontology( # pylint: disable=too-many-public-methods - owlready2.Ontology, OntoGraph -): +class Ontology(owlready2.Ontology): # pylint: disable=too-many-public-methods """A generic class extending owlready2.Ontology.""" # Properties controlling what annotations that are considered by @@ -518,9 +511,9 @@ def getmtime(path): resolved_url = self.world._iri_mappings.get(url, url) # Append paths from catalog file to onto_path - for _ in sorted(dirs, reverse=True): - if _ not in owlready2.onto_path: - owlready2.onto_path.append(_) + for path in sorted(dirs, reverse=True): + if path not in owlready2.onto_path: + owlready2.onto_path.append(path) # Use catalog file to update IRIs of imported ontologies # in internal store and try to load again... @@ -1299,9 +1292,9 @@ def get_graph(self, **kwargs): Note that this method requires the Python graphviz package. """ # pylint: disable=import-outside-toplevel,cyclic-import - from ontopy.graph import OntoGraph as NewOntoGraph + from ontopy.graph import OntoGraph - return NewOntoGraph(self, **kwargs) + return OntoGraph(self, **kwargs) @staticmethod def common_ancestors(cls1, cls2): From 063e477bd901789f84e6842ac20144ae532f5e0f Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Wed, 17 Aug 2022 15:58:50 +0200 Subject: [PATCH 13/25] Apply suggestions from code review Co-authored-by: Francesca L. Bleken <48128015+francescalb@users.noreply.github.com> --- ontopy/ontology.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ontopy/ontology.py b/ontopy/ontology.py index b7dbf156c..e3eed07b3 100644 --- a/ontopy/ontology.py +++ b/ontopy/ontology.py @@ -120,7 +120,7 @@ def get_ontology(self, base_iri="emmo-inferred"): return onto def get_unabbreviated_triples( - self, subject=None, predicate=None, obj=None, label=None + self, subject=None, predicate=None, obj=None, blank=None ): # pylint: disable=invalid-name """Returns all triples unabbreviated. @@ -128,7 +128,7 @@ def get_unabbreviated_triples( If any of the `subject`, `predicate` or `object` arguments are given, only matching triples will be returned. - If `label` is given, it will be used to represent blank nodes. + If `blank` is given, it will be used to represent blank nodes. """ def _unabbreviate(i): @@ -136,7 +136,7 @@ def _unabbreviate(i): # negative storid corresponds to blank nodes if i >= 0: return self._unabbreviate(i) - return BlankNode(self, i) if label is None else label + return BlankNode(self, i) if blank is None else blank return i for s, p, o in self.get_triples(subject, predicate, obj): From 7989cb688aeeb706b132fa29dab5fb584fd80ef4 Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Wed, 17 Aug 2022 16:04:17 +0200 Subject: [PATCH 14/25] Removed pydot from requirements --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 954a0b6af..06b6b04f3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,6 @@ openpyxl>=3.0.9,<3.1 Owlready2>=0.28,!=0.32,!=0.34,<0.39 packaging>=21.0<22 pandas>=1.2,<1.5 -pydot>=1.4.1,<2 Pygments>=2.7.4,<3 pyparsing>=2.4.7 PyYAML>=5.4.1,<7 From ed5686beb18966a8d0a3a8278e0f6e125f19c00a Mon Sep 17 00:00:00 2001 From: francescalb Date: Wed, 17 Aug 2022 16:07:56 +0200 Subject: [PATCH 15/25] Updated so that label_annotations are taken care of even if load is not called --- ontopy/ontology.py | 28 ++++++++++++++++++++++------ tests/test_basic.py | 2 ++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/ontopy/ontology.py b/ontopy/ontology.py index 0baee32dc..81ed96d42 100644 --- a/ontopy/ontology.py +++ b/ontopy/ontology.py @@ -155,14 +155,18 @@ class Ontology( # pylint: disable=too-many-public-methods ): """A generic class extending owlready2.Ontology.""" - # def __init__(self, *args, **kwargs): - # # Set ontology prefix to ontology._namespace.name as default - # self.prefix = self._namespace.name - # super().__init__(*args, **kwargs) + def __init__(self, *args, **kwargs): + # Properties controlling what annotations that are considered by + # get_by_label() + super().__init__(*args, **kwargs) + self._label_annotations = None + #if emmo_based: + # self_label_annotations = DEFAULT_LABEL_ANNOTATIONS + #print(self._label_annotations) + # Properties controlling what annotations that are considered by # get_by_label() - _label_annotations = [] label_annotations = property( fget=lambda self: self._label_annotations, doc="List of label annotation searched for by get_by_label().", @@ -221,6 +225,7 @@ def __getitem__(self, name): return item def __getattr__(self, name): + print('attr', name) attr = super().__getattr__(name) if not attr: attr = self.get_by_label(name) @@ -300,6 +305,13 @@ def get_by_label( raise ValueError( f"Invalid label definition, {label!r} contains spaces." ) + if self._label_annotations is None: + for iri in DEFAULT_LABEL_ANNOTATIONS: + try: + self.add_label_annotation(iri) + except ValueError: + pass + splitlabel = label.split(":") if len(splitlabel) > 2: @@ -334,7 +346,7 @@ def get_by_label( ) # if label in self._namespaces: # return self._namespaces[label] - + if label_annotations is None: annotations = (a.name for a in self.label_annotations) else: @@ -389,6 +401,10 @@ def add_label_annotation(self, iri): May be provided either as an IRI or as its owlready2 representation. """ + if self._label_annotations is None: + self._label_annotations = [] + print('iri',iri) + print(hasattr(iri, "storid")) label_annotation = iri if hasattr(iri, "storid") else self.world[iri] if not label_annotation: raise ValueError(f"IRI not in ontology: {iri}") diff --git a/tests/test_basic.py b/tests/test_basic.py index 28ee9f880..c6b614728 100755 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -40,6 +40,8 @@ class H2O(emmo.Molecule): water = H2O() water.hasSpatialDirectPart = [H1, H2, O] + print(onto.label_annotations) + print(onto._label_annotations) name_prefix = "myonto_" onto.sync_attributes(name_policy="sequential", name_prefix=name_prefix) assert f"{onto.base_iri}{name_prefix}0" in onto From fe6a7507102bd7cb60c698b3cd96cffb9fb04c4d Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Wed, 17 Aug 2022 16:41:07 +0200 Subject: [PATCH 16/25] Removed old test_graph.py --- ontopy/ontology.py | 10 +--------- tests/ontopy_tests/test_graph.py | 30 ------------------------------ 2 files changed, 1 insertion(+), 39 deletions(-) delete mode 100644 tests/ontopy_tests/test_graph.py diff --git a/ontopy/ontology.py b/ontopy/ontology.py index 06a200aff..f2c66122c 100644 --- a/ontopy/ontology.py +++ b/ontopy/ontology.py @@ -153,10 +153,6 @@ def __init__(self, *args, **kwargs): # get_by_label() super().__init__(*args, **kwargs) self._label_annotations = None - #if emmo_based: - # self_label_annotations = DEFAULT_LABEL_ANNOTATIONS - #print(self._label_annotations) - # Properties controlling what annotations that are considered by # get_by_label() @@ -218,7 +214,6 @@ def __getitem__(self, name): return item def __getattr__(self, name): - print('attr', name) attr = super().__getattr__(name) if not attr: attr = self.get_by_label(name) @@ -304,7 +299,6 @@ def get_by_label( self.add_label_annotation(iri) except ValueError: pass - splitlabel = label.split(":") if len(splitlabel) > 2: @@ -339,7 +333,7 @@ def get_by_label( ) # if label in self._namespaces: # return self._namespaces[label] - + if label_annotations is None: annotations = (a.name for a in self.label_annotations) else: @@ -396,8 +390,6 @@ def add_label_annotation(self, iri): """ if self._label_annotations is None: self._label_annotations = [] - print('iri',iri) - print(hasattr(iri, "storid")) label_annotation = iri if hasattr(iri, "storid") else self.world[iri] if not label_annotation: raise ValueError(f"IRI not in ontology: {iri}") diff --git a/tests/ontopy_tests/test_graph.py b/tests/ontopy_tests/test_graph.py deleted file mode 100644 index eb067a605..000000000 --- a/tests/ontopy_tests/test_graph.py +++ /dev/null @@ -1,30 +0,0 @@ -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from pathlib import Path - - from ontopy.ontology import Ontology - - -def test_graph(emmo: "Ontology", tmpdir: "Path") -> None: - graph = emmo.get_dot_graph(relations="is_a") - graph.write_svg(tmpdir / "taxonomy.svg") - graph.write_pdf(tmpdir / "taxonomy.pdf") - - entity_graph = emmo.get_dot_graph("EMMO") - entity_graph.write_svg(tmpdir / "taxonomy2.svg") - - substrate_graph = emmo.get_dot_graph( - "Item", relations=True, leafs=("Physical",), parents="Item", style="uml" - ) - substrate_graph.write_svg(tmpdir / "merotopology_graph.svg") - - property_graph = emmo.get_dot_graph("Property") - property_graph.write_svg(tmpdir / "property_graph.svg") - - # Update the default style - emmo._default_style["graph"]["rankdir"] = "BT" - - relations_graph = emmo.get_dot_graph("EMMORelation") - relations_graph.write_pdf(tmpdir / "relation_graph.pdf") - relations_graph.write_png(tmpdir / "relation_graph.png") From cb570aca12c922dfcc75d6d96c0ef66ee3145f41 Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Wed, 17 Aug 2022 16:45:14 +0200 Subject: [PATCH 17/25] Updated Ontology.get_unabbreviated_triples() to match changes in World.get_unabbreviated_triples() --- ontopy/ontology.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ontopy/ontology.py b/ontopy/ontology.py index f2c66122c..eac3ba5df 100644 --- a/ontopy/ontology.py +++ b/ontopy/ontology.py @@ -247,16 +247,16 @@ def __eq__(self, other): i.e. blank nodes are not distinguished, but relations to blank nodes are included. """ - return set(self.get_unabbreviated_triples(label="_:b")) == set( - other.get_unabbreviated_triples(label="_:b") + return set(self.get_unabbreviated_triples(blank="_:b")) == set( + other.get_unabbreviated_triples(blank="_:b") ) - def get_unabbreviated_triples(self, label=None): + def get_unabbreviated_triples(self, blank=None): """Returns all triples unabbreviated. If `label` is given, it will be used to represent blank nodes. """ - return World.get_unabbreviated_triples(self, label) + return World.get_unabbreviated_triples(self, blank=blank) def get_by_label( self, label: str, label_annotations: str = None, prefix: str = None From 909a645ee1b1ed508c8bccca14faa3dc3d35c4f6 Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Wed, 17 Aug 2022 16:51:10 +0200 Subject: [PATCH 18/25] Changes form pylint and blacken --- demo/horizontal/step4_map_instance.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/demo/horizontal/step4_map_instance.py b/demo/horizontal/step4_map_instance.py index cf79e8233..449d5a064 100755 --- a/demo/horizontal/step4_map_instance.py +++ b/demo/horizontal/step4_map_instance.py @@ -72,13 +72,11 @@ def map_app2common(instance, meta_collection, out_id=None): # Load metadata collection from step 1 metacoll = dlite.Collection( - "json://usercase_metadata.json?mode=r#usercase_ontology", True + "json://usercase_metadata.json?mode=r#usercase_ontology" ) # Load dlite-representation of atoms structure from step 3 -coll = dlite.Collection( - "json://usercase_appdata.json?mode=r#usercase_appdata", False -) +coll = dlite.Collection("json://usercase_appdata.json?mode=r#usercase_appdata") inst = coll.get("atoms") # Do the mapping From 0a834cc58f19093ec184d4563833f318750bb878 Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Wed, 17 Aug 2022 16:53:14 +0200 Subject: [PATCH 19/25] Removed ontopy/ontograph.py --- ontopy/ontograph.py | 571 -------------------------------------------- 1 file changed, 571 deletions(-) delete mode 100644 ontopy/ontograph.py diff --git a/ontopy/ontograph.py b/ontopy/ontograph.py deleted file mode 100644 index 1d221ea9f..000000000 --- a/ontopy/ontograph.py +++ /dev/null @@ -1,571 +0,0 @@ -# -*- coding: utf-8 -*- -""" -A module adding graphing functionality to ontopy.ontology -""" -# pylint: disable=fixme,no-member -# -# This module was written before I had a good understanding of DL. -# Should be simplified and improved: -# - Rewrite OntoGraph to be a standalone class instead of as an mixin -# for Ontology. -# - Make it possible to have different styles for different types of -# relations (by default differentiate between is_a, has_part, -# has_subdimension, has_sign and has_member). -# - Factor out methods for finding node trees (may go into Ontology). -# - Factor out methods to finding relation triplets ``(node1, relation, -# node2)`` (may go into Ontology). -# - Consider to switch to graphviz Python package since it seems to have -# very useful interface to Jupyter Notebook and Qt Console integration, -# see https://pypi.org/project/graphviz/. -# -import os -import re -import warnings -import tempfile -import defusedxml.ElementTree as ET - -import owlready2 -import pydot - -from ontopy.utils import asstring, NoSuchLabelError - - -class OntoGraph: - """A mixin class used by ontopy.ontology.Ontology that adds - functionality for generating graph representations of the ontology. - """ - - _default_style = { - "graph": { - "graph_type": "digraph", - "rankdir": "RL", - "fontsize": 8, - # "fontname": "Bitstream Vera Sans", "splines": "ortho", - # "engine": "neato", - }, - "class": { - "style": "filled", - "fillcolor": "#ffffcc", - }, - "defined_class": { - "style": "filled", - "fillcolor": "#ffc880", - }, - "individuals": {}, - "is_a": {"arrowhead": "empty"}, - "equivalent_to": { - "color": "green3", - }, - "disjoint_with": { - "color": "red", - }, - "inverse_of": { - "color": "orange", - }, - # "other": {"color": "blue", }, - "relations": { - "enclosing": { - "color": "red", - "arrowtail": "diamond", - "dir": "back", - }, - "has_subdimension": { - "color": "red", - "arrowtail": "diamond", - "dir": "back", - "style": "dashed", - }, - "has_sign": {"color": "blue", "style": "dotted"}, - "has_property": {"color": "blue"}, - "has_unit": {"color": "magenta"}, - "has_type": {"color": "forestgreen"}, - }, - "other": {"color": "olivedrab"}, - } - - _uml_style = { - "graph": { - "graph_type": "digraph", - "rankdir": "RL", - "fontsize": 8, - # "splines": "ortho", - }, - "class": { - # "shape": "record", - "shape": "box", - "fontname": "Bitstream Vera Sans", - "style": "filled", - "fillcolor": "#ffffe0", - }, - "defined_class": { - # "shape": "record", - "shape": "box", - "fontname": "Bitstream Vera Sans", - "style": "filled", - "fillcolor": "#ffc880", - }, - "individuals": {}, - "is_a": {"arrowhead": "empty"}, - "equivalent_to": {"color": "green3"}, - "disjoint_with": {"color": "red", "arrowhead": "none"}, - "inverse_of": {"color": "orange", "arrowhead": "none"}, - # "other": {"color": "blue", "arrowtail": "diamond", "dir": "back"}, - "relations": { - "enclosing": { - "color": "red", - "arrowtail": "diamond", - "dir": "back", - }, - "has_subdimension": { - "color": "red", - "arrowtail": "diamond", - "dir": "back", - "style": "dashed", - }, - "has_sign": {"color": "blue", "style": "dotted"}, - "has_property": {"color": "blue"}, - "has_unit": {"color": "magenta"}, - "has_type": {"color": "forestgreen"}, - }, - "other": {"color": "blue"}, - } - - def get_dot_graph( # pylint: disable=too-many-arguments,too-many-locals,too-many-branches - self, - root=None, - graph=None, - relations="is_a", - leafs=None, - parents=False, - style=None, - edgelabels=True, - constraint=False, - ): - """Returns a pydot graph object for visualising the ontology. - - Parameters - ---------- - root : None | string | owlready2.ThingClass instance - Name or owlready2 entity of root node to plot subgraph - below. If `root` is None, all classes will be included in the - subgraph. - graph : None | pydot.Dot instance - Pydot graph object to plot into. If None, a new graph object - is created using the keyword arguments. - relations : True | str | sequence - Sequence of relations to visualise. If True, all relations are - included. - leafs : None | sequence - A sequence of leaf node names for generating sub-graphs. - parents : bool | str - Whether to include parent nodes. If `parents` is a string, - only parent nodes down to the given name will included. - style : None | dict | "uml" - A dict mapping the name of the different graphical elements - to dicts of pydot style settings. Supported graphical elements - include: - - graph : overall settings pydot graph - - class : nodes for classes - - individual : nodes for invididuals - - is_a : edges for is_a relations - - equivalent_to : edges for equivalent_to relations - - disjoint_with : edges for disjoint_with relations - - inverse_of : edges for inverse_of relations - - relations : with relation names - XXX - - other : edges for other relations and restrictions - If style is None, a very simple default style is used. - Some pre-defined styles can be selected by name (currently - only "uml"). - edgelabels : bool | dict - Whether to add labels to the edges of the generated graph. - It is also possible to provide a dict mapping the - full labels (with cardinality stripped off for restrictions) - to some abbriviations. - constraint : None | bool - - Note: This method requires pydot. - """ - warnings.warn( - """The ontopy.ontology.get_dot_graph() method is deprecated. - Use ontopy.ontology.get_graph() instead. - - This requires that you install graphviz instead of the old - pydot package.""", - DeprecationWarning, - ) - - # FIXME - double inheritance leads to dublicated nodes. Make sure - # to only add a node once! - - if style is None or style == "default": - style = self._default_style - elif style == "uml": - style = self._uml_style - graph = self._get_dot_graph( - root=root, - graph=graph, - relations=relations, - leafs=leafs, - style=style, - edgelabels=edgelabels, - ) - # Add parents - # FIXME - factor out into a recursive function to support - # multiple inheritance - - if parents and root: - root_entity = ( - self.get_by_label(root) if isinstance(root, str) else root - ) - while True: - parent = root_entity.is_a.first() - if parent is None or parent is owlready2.Thing: - break - label = asstring(parent) - if self.is_defined(label): - node = pydot.Node(label, **style.get("defined_class", {})) - # If label contains a hyphen, the node name will - # be quoted (bug in pydot?). To work around, set - # the name explicitly... - node.set_name(label) - else: - node = pydot.Node(label, **style.get("class", {})) - node.set_name(label) - graph.add_node(node) - if relations is True or "is_a" in relations: - kwargs = style.get("is_a", {}).copy() - if isinstance(edgelabels, dict): - kwargs["label"] = edgelabels.get("is_a", "is_a") - elif edgelabels: - kwargs["label"] = "is_a" - - rootnode = graph.get_node(asstring(root_entity))[0] - edge = pydot.Edge(rootnode, node, **kwargs) - graph.add_edge(edge) - if isinstance(parents, str) and label == parents: - break - root_entity = parent - # Add edges - for node in graph.get_nodes(): - try: - entity = self.get_by_label(node.get_name()) - except (KeyError, NoSuchLabelError): - continue - # Add is_a edges - targets = [ - e - for e in entity.is_a - if not isinstance( - e, - ( - owlready2.ThingClass, - owlready2.ObjectPropertyClass, - owlready2.PropertyClass, - ), - ) - ] - - self._get_dot_add_edges( - graph, - entity, - targets, - "relations", - relations, - # style=style.get('relations', style.get('other', {})), - style=style.get("other", {}), - edgelabels=edgelabels, - constraint=constraint, - ) - - # Add equivalent_to edges - if relations is True or "equivalent_to" in relations: - self._get_dot_add_edges( - graph, - entity, - entity.equivalent_to, - "equivalent_to", - relations, - style.get("equivalent_to", {}), - edgelabels=edgelabels, - constraint=constraint, - ) - - # disjoint_with - if hasattr(entity, "disjoints") and ( - relations is True or "disjoint_with" in relations - ): - self._get_dot_add_edges( - graph, - entity, - entity.disjoints(), - "disjoint_with", - relations, - style.get("disjoint_with", {}), - edgelabels=edgelabels, - constraint=constraint, - ) - - # Add inverse_of - if ( - hasattr(entity, "inverse_property") - and (relations is True or "inverse_of" in relations) - and entity.inverse_property not in (None, entity) - ): - self._get_dot_add_edges( - graph, - entity, - [entity.inverse_property], - "inverse_of", - relations, - style.get("inverse_of", {}), - edgelabels=edgelabels, - constraint=constraint, - ) - - return graph - - def _get_dot_add_edges( # pylint: disable=too-many-arguments,too-many-locals,too-many-branches - self, - graph, - entity, - targets, - relation, - relations, - style, - edgelabels=True, - constraint=None, - ): - """Adds edges to `graph` for relations between `entity` and all - members in `targets`. `style` is a dict with options to pydot.Edge(). - """ - nodes = graph.get_node(asstring(entity)) - if not nodes: - return - node = nodes[0] - - for target in targets: - entity_string = asstring(target) - if isinstance(target, owlready2.ThingClass): - pass - elif isinstance( - target, (owlready2.ObjectPropertyClass, owlready2.PropertyClass) - ): - label = asstring(target) - nodes = graph.get_node(label) - if nodes: - kwargs = style.copy() - if isinstance(edgelabels, dict): - kwargs["label"] = edgelabels.get(label, label) - elif edgelabels: - kwargs["label"] = label - edge = pydot.Edge(node, nodes[0], **kwargs) - if constraint is not None: - edge.set_constraint(constraint) - graph.add_edge(edge) - elif isinstance(target, owlready2.Restriction): - rname = asstring(target.property) - rtype = owlready2.class_construct._restriction_type_2_label[ # pylint: disable=protected-access - target.type - ] - - if relations or rname in relations: - vname = asstring(target.value) - others = graph.get_node(vname) - - # Only proceede if there is only one node named `vname` - # and an edge to that node does not already exists - if ( - len(others) == 1 - and (node.get_name(), vname) - not in graph.obj_dict["edges"].keys() - ): - other = others[0] - else: - continue - - if rtype in ("min", "max", "exactly"): - label = f"{rname} {rtype} {target.cardinality}" - else: - label = f"{rname} {rtype}" - - kwargs = style.copy() - if isinstance(edgelabels, dict): - slabel = f"{rname} {rtype}" - kwargs["label"] = edgelabels.get(slabel, label) + " " - elif edgelabels: - kwargs["label"] = label + " " # Add some extra space - - edge = pydot.Edge(node, other, **kwargs) - if constraint is not None: - edge.set_constraint(constraint) - graph.add_edge(edge) - - elif hasattr(self, "_verbose") and self._verbose: - print( - f"* get_dot_graph() * Ignoring: {node.get_name()} " - f"{relation} {entity_string}" - ) - - def _get_dot_graph( # pylint: disable=too-many-arguments,too-many-locals,too-many-branches,too-many-statements - self, - root=None, - graph=None, - relations="is_a", - leafs=None, - style=None, - visited=None, - edgelabels=True, - ): - """Help method. See get_dot_graph(). `visited` is used to filter - out circular dependencies. - """ - if graph is None: - kwargs = style.get("graph", {}) - kwargs.setdefault("newrank", True) - graph = pydot.Dot(**kwargs) - - if relations is True: - relations = ["is_a"] + list(self.get_relations()) - elif isinstance(relations, str): - relations = [relations] - relations = set( - r - if isinstance(r, str) - else asstring(r) - if len(r.label) == 1 - else r.name - for r in relations - ) - - if visited is None: - visited = set() - - if root is None: - for root_cls in self.get_root_classes(): - self._get_dot_graph( - root=root_cls, - graph=graph, - relations=relations, - leafs=leafs, - style=style, - visited=visited, - edgelabels=edgelabels, - ) - return graph - if isinstance(root, (list, tuple, set)): - for _ in root: - self._get_dot_graph( - root=_, - graph=graph, - relations=relations, - leafs=leafs, - style=style, - visited=visited, - edgelabels=edgelabels, - ) - return graph - if isinstance(root, str): - root = self.get_by_label(root) - - if root in visited: - if hasattr(self, "_verbose") and self._verbose: - warnings.warn( - f"Circular dependency of class {asstring(root)!r}" - ) - return graph - visited.add(root) - - label = asstring(root) - nodes = graph.get_node(label) - if nodes: - if len(nodes) > 1: - warnings.warn( - f"More than one node corresponding to label: {label}" - ) - node = nodes[0] - else: - if self.is_individual(label): - node = pydot.Node(label, **style.get("individual", {})) - node.set_name(label) - elif self.is_defined(label): - node = pydot.Node(label, **style.get("defined_class", {})) - node.set_name(label) - else: - node = pydot.Node(label, **style.get("class", {})) - node.set_name(label) - graph.add_node(node) - - if leafs and label in leafs: - return graph - - for sub_cls in root.subclasses(): - label = asstring(sub_cls) - if self.is_individual(label): - subnode = pydot.Node(label, **style.get("individual", {})) - subnode.set_name(label) - elif self.is_defined(label): - subnode = pydot.Node(label, **style.get("defined_class", {})) - subnode.set_name(label) - else: - subnode = pydot.Node(label, **style.get("class", {})) - subnode.set_name(label) - graph.add_node(subnode) - if relations is True or "is_a" in relations: - kwargs = style.get("is_a", {}).copy() - if isinstance(edgelabels, dict): - kwargs["label"] = edgelabels.get("is_a", "is_a") - elif edgelabels: - kwargs["label"] = "is_a" - edge = pydot.Edge(subnode, node, **kwargs) - graph.add_edge(edge) - self._get_dot_graph( - root=sub_cls, - graph=graph, - relations=relations, - leafs=leafs, - style=style, - visited=visited, - edgelabels=edgelabels, - ) - - return graph - - def get_dot_relations_graph(self, graph=None, relations="is_a", style=None): - """Returns a disjoined graph of all relations. - - This method simply calls get_dot_graph() with all root relations. - All arguments are passed on. - """ - rels = tuple(self.get_relations()) - roots = [ - relation - for relation in rels - if not any(_ in rels for _ in relation.is_a) - ] - return self.get_dot_graph( - root=roots, graph=graph, relations=relations, style=style - ) - - -def get_figsize(graph): - """Returns figure size (width, height) in points of figures for the - current pydot graph object `graph`.""" - with tempfile.TemporaryDirectory() as tmpdir: - tmpfile = os.path.join(tmpdir, "graph.svg") - graph.write_svg(tmpfile) - xml = ET.parse(tmpfile) - svg = xml.getroot() - width = svg.attrib["width"] - height = svg.attrib["height"] - if not width.endswith("pt"): - # ensure that units are in points - raise ValueError( - "The width attribute should always be given in 'pt', " - f"but it is: {width}" - ) - - def asfloat(string): - return float(re.match(r"^[\d.]+", string).group()) - - return asfloat(width), asfloat(height) From b5974746e56e1aec386c950691ba15f7286ce3ba Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Wed, 17 Aug 2022 16:55:53 +0200 Subject: [PATCH 20/25] Removed docs for deleted ontograph --- docs/api_reference/ontopy/ontograph.md | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 docs/api_reference/ontopy/ontograph.md diff --git a/docs/api_reference/ontopy/ontograph.md b/docs/api_reference/ontopy/ontograph.md deleted file mode 100644 index de409c8c5..000000000 --- a/docs/api_reference/ontopy/ontograph.md +++ /dev/null @@ -1,3 +0,0 @@ -# ontograph - -::: ontopy.ontograph From 381cf0b997c4fa4bbab42e68a578349242257b94 Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Wed, 17 Aug 2022 17:07:10 +0200 Subject: [PATCH 21/25] Fixed indentation in documentation --- ontopy/ontology.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/ontopy/ontology.py b/ontopy/ontology.py index eac3ba5df..e5805fecd 100644 --- a/ontopy/ontology.py +++ b/ontopy/ontology.py @@ -416,7 +416,7 @@ def set_common_prefix( Args: iri_base: The start of the base_iri to look for. Defaults to - the emmo base_iri http://emmo.info/emmo + the emmo base_iri http://emmo.info/emmo prefix: the desired prefix. Defaults to emmo. """ if self.base_iri.startswith(iri_base): @@ -442,35 +442,35 @@ def load( # pylint: disable=too-many-arguments,arguments-renamed Parameters ---------- - only_local : bool + only_local: bool Whether to only read local files. This requires that you have appended the path to the ontology to owlready2.onto_path. - filename : str + filename: str Path to file to load the ontology from. Defaults to `base_iri` provided to get_ontology(). - format : str + format: str Format of `filename`. Default is inferred from `filename` extension. - reload : bool + reload: bool Whether to reload the ontology if it is already loaded. - reload_if_newer : bool + reload_if_newer: bool Whether to reload the ontology if the source has changed since last time it was loaded. - url_from_catalog : bool | None + url_from_catalog: bool | None Whether to use catalog file to resolve the location of `base_iri`. If None, the catalog file is used if it exists in the same directory as `filename`. - catalog_file : str + catalog_file: str Name of Protègè catalog file in the same folder as the ontology. This option is used together with `only_local` and defaults to "catalog-v001.xml". - emmo_based : bool + emmo_based: bool Whether this is an EMMO-based ontology or not, default `True`. - prefix : defaults to self.get_namespace.name if + prefix: defaults to self.get_namespace.name if prefix_emmo: bool, default None. If emmo_based is True it defaults to True and sets the prefix of all imported ontologies with base_iri starting with 'http://emmo.info/emmo' to emmo - kwargs + kwargs: Additional keyword arguments are passed on to owlready2.Ontology.load(). """ From c86679d054a571854c7134f7eae05dc29f1531d1 Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Wed, 17 Aug 2022 17:09:47 +0200 Subject: [PATCH 22/25] Typo in docstring --- ontopy/ontology.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ontopy/ontology.py b/ontopy/ontology.py index e5805fecd..d64f2c3b7 100644 --- a/ontopy/ontology.py +++ b/ontopy/ontology.py @@ -127,7 +127,7 @@ def get_unabbreviated_triples( # pylint: disable=invalid-name """Returns all triples unabbreviated. - If any of the `subject`, `predicate` or `object` arguments are given, + If any of the `subject`, `predicate` or `obj` arguments are given, only matching triples will be returned. If `blank` is given, it will be used to represent blank nodes. From e3dd4c0788a850985fdc7b3a762813db36de0cdf Mon Sep 17 00:00:00 2001 From: francescalb Date: Wed, 17 Aug 2022 17:26:48 +0200 Subject: [PATCH 23/25] Put prefix in init for Ontology and added emmo prefix tests --- ontopy/ontology.py | 2 +- tests/ontopy_tests/test_prefix.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/ontopy/ontology.py b/ontopy/ontology.py index d64f2c3b7..7e8b2958e 100644 --- a/ontopy/ontology.py +++ b/ontopy/ontology.py @@ -153,6 +153,7 @@ def __init__(self, *args, **kwargs): # get_by_label() super().__init__(*args, **kwargs) self._label_annotations = None + self.prefix = None # Properties controlling what annotations that are considered by # get_by_label() @@ -164,7 +165,6 @@ def __init__(self, *args, **kwargs): # Name of special unlabeled entities, like Thing, Nothing, etc... _special_labels = None - prefix = None # Some properties for customising dir() listing - useful in # interactive sessions... _dir_preflabel = isinteractive() diff --git a/tests/ontopy_tests/test_prefix.py b/tests/ontopy_tests/test_prefix.py index 90862e098..ca21339b7 100644 --- a/tests/ontopy_tests/test_prefix.py +++ b/tests/ontopy_tests/test_prefix.py @@ -32,14 +32,14 @@ def test_prefix(testonto: "Ontology", emmo: "Ontology") -> None: "TestClass", prefix="models" ) - # def test_prefix_emmo(emmo: "Ontology") -> None: - # """Test prefix in ontology""" - # from emmopy import get_emmo +def test_prefix_emmo(emmo: "Ontology") -> None: + """Test prefix in ontology""" + from emmopy import get_emmo # Check that the prefix of emmo-inferred becomes emmo - # assert emmo.Atom.namespace.ontology.prefix == 'emmo' - # assert emmo.get_by_label('Atom', prefix='emmo') == emmo.Atom + assert emmo.Atom.namespace.ontology.prefix == 'emmo' + assert emmo.get_by_label('Atom', prefix='emmo') == emmo.Atom - # emmo_asserted = get_emmo('emmo') - # assert emmo_asserted.Atom.namespace.ontology.prefix == "emmo" - # assert emmo_asserted.get_by_label("Atom", prefix="emmo") == emmo_asserted.Atom + emmo_asserted = get_emmo('emmo') + assert emmo_asserted.Atom.namespace.ontology.prefix == "emmo" + assert emmo_asserted.get_by_label("Atom", prefix="emmo") == emmo_asserted.Atom From a1a5a438830a104ae78c7b178289c977ecef5773 Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Wed, 17 Aug 2022 17:28:50 +0200 Subject: [PATCH 24/25] Added asserts --- tests/ontopy_tests/test_prefix.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/ontopy_tests/test_prefix.py b/tests/ontopy_tests/test_prefix.py index ca21339b7..71b8b3029 100644 --- a/tests/ontopy_tests/test_prefix.py +++ b/tests/ontopy_tests/test_prefix.py @@ -32,14 +32,19 @@ def test_prefix(testonto: "Ontology", emmo: "Ontology") -> None: "TestClass", prefix="models" ) + def test_prefix_emmo(emmo: "Ontology") -> None: """Test prefix in ontology""" from emmopy import get_emmo # Check that the prefix of emmo-inferred becomes emmo - assert emmo.Atom.namespace.ontology.prefix == 'emmo' - assert emmo.get_by_label('Atom', prefix='emmo') == emmo.Atom + assert emmo.Atom.namespace.ontology.prefix == "emmo" + assert emmo.get_by_label("Atom", prefix="emmo") == emmo.Atom + assert emmo.get_by_label("emmo:Atom") == emmo.Atom + assert emmo["emmo:Atom"] == emmo.Atom - emmo_asserted = get_emmo('emmo') + emmo_asserted = get_emmo("emmo") assert emmo_asserted.Atom.namespace.ontology.prefix == "emmo" - assert emmo_asserted.get_by_label("Atom", prefix="emmo") == emmo_asserted.Atom + assert ( + emmo_asserted.get_by_label("Atom", prefix="emmo") == emmo_asserted.Atom + ) From 14cdde60205deeae9eff2e4475d4fef5ac1cd435 Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Wed, 17 Aug 2022 17:48:13 +0200 Subject: [PATCH 25/25] Added test --- tests/test_load.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_load.py b/tests/test_load.py index 55a52280e..f660df8e7 100755 --- a/tests/test_load.py +++ b/tests/test_load.py @@ -5,6 +5,7 @@ def test_load(repo_dir: "Path", testonto: "Ontology") -> None: + import pytest from ontopy import get_ontology from ontopy.ontology import HTTPError @@ -33,7 +34,10 @@ def test_load(repo_dir: "Path", testonto: "Ontology") -> None: ).load() assert onto.Electrolyte.prefLabel.first() == "Electrolyte" - with pytest.raises(HTTPError): + with pytest.raises( + HTTPError, + match="HTTP Error 404: https://emmo.info/non-existing/ontology: Not Found", + ): get_ontology("http://emmo.info/non-existing/ontology#").load()