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

Update to comply with owlready2>0.41 #639

Merged
merged 17 commits into from
Oct 9, 2023
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
6 changes: 1 addition & 5 deletions ontopy/excelparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,12 +319,10 @@ def create_ontology_from_pandas( # pylint:disable=too-many-locals,too-many-bran
# Set given or default base_iri if base_iri_from_metadata is False.
if not base_iri_from_metadata:
onto.base_iri = base_iri

onto.sync_python_names()
# onto.sync_python_names()
# prefLabel, label, and altLabel
# are default label annotations
onto.set_default_label_annotations()

# Add object properties
if objectproperties is not None:
objectproperties = _clean_dataframe(objectproperties)
Expand Down Expand Up @@ -367,7 +365,6 @@ def create_ontology_from_pandas( # pylint:disable=too-many-locals,too-many-bran
onto.sync_attributes(
name_policy="uuid", name_prefix="EMMO_", class_docstring="elucidation"
)

# Clean up data frame with new concepts
data = _clean_dataframe(data)
# Add entities
Expand Down Expand Up @@ -562,7 +559,6 @@ def get_metadata_from_dataframe( # pylint: disable=too-many-locals,too-many-bra
)
except AttributeError:
pass

return onto, catalog


Expand Down
6 changes: 3 additions & 3 deletions ontopy/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -720,11 +720,11 @@ def _relation_styles(
"""
for relation in entity.mro():
if relation in rels:
if get_label(relation) in relations:
rattrs = relations[get_label(relation)]
if str(get_label(relation)) in relations:
rattrs = relations[str(get_label(relation))]
else:
for alt_label in relation.get_annotations()["altLabel"]:
rattrs = relations[alt_label]
rattrs = relations[str(alt_label)]

break
else:
Expand Down
1 change: 0 additions & 1 deletion ontopy/ontology.py
Original file line number Diff line number Diff line change
Expand Up @@ -904,7 +904,6 @@ def save(
)

revmap = {value: key for key, value in FMAP.items()}

if filename is None:
if format:
fmt = revmap.get(format, format)
Expand Down
38 changes: 32 additions & 6 deletions ontopy/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from owlready2 import AnnotationPropertyClass, ThingClass, PropertyClass
from owlready2 import Metadata, Thing, Restriction, Namespace
from ontopy.utils import EMMOntoPyException
from ontopy.ontology import Ontology as OntopyOntology


def render_func(entity):
Expand All @@ -27,6 +28,10 @@ def render_func(entity):
#
# Extending ThingClass (classes)
# ==============================

save_getattr = ThingClass.__getattr__


def get_preferred_label(self):
"""Returns the preferred label as a string (not list).

Expand Down Expand Up @@ -69,7 +74,7 @@ def get_parents(self, strict=False):
def _dir(self):
"""Extend dir() listing of ontology classes."""
set_dir = set(object.__dir__(self))
props = self.namespace.world._props.keys()
props = [str(key) for key in self.namespace.world._props.keys()]
set_dir.update(props)
return sorted(set_dir)

Expand All @@ -91,14 +96,16 @@ def _setitem(self, name, value):

Example:
>>> from emmopy import get_emmo
>>> from owlready2 import locstr
>>> emmo = get_emmo()
>>> emmo.Atom['altLabel']
['ChemicalElement']
[locstr('ChemicalElement', 'en')]
>>> emmo.Atom['altLabel'] = 'Element'
>>> emmo.Atom['altLabel'] = locstr('Atomo', 'it')
>>> emmo.Atom['altLabel']
['ChemicalElement', 'Element']

[locstr('ChemicalElement', 'en'), 'Element', locstr('Atomo', 'it')]
"""

item = _getitem(self, name)
item.append(value)

Expand All @@ -112,6 +119,24 @@ def _delitem(self, name):
item.clear()


def _getattr(self, name):
"""Provide attribute access to annotation properties.

This upates __getattr__ in owlready2. If name is not found as
attribute it tries using the iriname of the annotation property.
"""
try:
return save_getattr(self, name)
except AttributeError as err:
# make sure we are using and ontopy Ontology which has get_by_label
if isinstance(self.namespace.ontology, OntopyOntology):
entity = self.namespace.ontology.get_by_label(name)
# add annotation property to world._props for faster access later
self.namespace.world._props[name] = entity
return save_getattr(self, entity.name)
raise err


def get_annotations(
self, all=False, imported=True
): # pylint: disable=redefined-builtin
Expand All @@ -125,7 +150,7 @@ def get_annotations(
onto = self.namespace.ontology

annotations = {
get_preferred_label(_): _._get_values_for_class(self)
str(get_preferred_label(_)): _._get_values_for_class(self)
for _ in onto.annotation_properties(imported=imported)
}
if all:
Expand Down Expand Up @@ -197,6 +222,7 @@ def get_indirect_is_a(self, skip_classes=True):
setattr(ThingClass, "__getitem__", _getitem)
setattr(ThingClass, "__setitem__", _setitem)
setattr(ThingClass, "__delitem__", _delitem)
setattr(ThingClass, "__getattr__", _getattr)
setattr(ThingClass, "get_preferred_label", get_preferred_label)
setattr(ThingClass, "get_parents", get_parents)
setattr(ThingClass, "get_annotations", get_annotations)
Expand Down Expand Up @@ -252,7 +278,7 @@ def namespace_init(self, world_or_ontology, base_iri, name=None):
# Extending Metadata
# ==================
def keys(self):
"""Return a generator over annotation property names associates
"""Return a generator over annotation property names associated
with this ontology."""
namespace = self.namespace
for annotation in namespace.annotation_properties():
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defusedxml>=0.7.1,<1
graphviz>=0.16,<0.21
numpy>=1.19.5,<2
openpyxl>=3.0.9,<3.2
Owlready2>=0.28,!=0.32,!=0.34,<0.42
Owlready2>=0.28,!=0.32,!=0.34,<0.44
packaging>=21.0,<24
pandas>=1.2,<2.1
Pygments>=2.7.4,<3
Expand Down
14 changes: 13 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@

if TYPE_CHECKING:
from ontopy.ontology import Ontology
from typing import Sequence
import sys, os

# Add test-specific utilities to path
sys.path.append(os.path.join(os.path.dirname(__file__), "utilities"))
jesper-friis marked this conversation as resolved.
Show resolved Hide resolved


@pytest.fixture(scope="session")
Expand All @@ -16,7 +21,14 @@ def repo_dir() -> Path:

@pytest.fixture
def emmo() -> "Ontology":
"""Load and return EMMO."""
"""Load and return EMMO.

Note that this loads the version of EMMO that is
the current defailt in ontopy.

When updating the defeault EMMO for ontopy,
tests might need to be updated.
"""
from emmopy import get_emmo

emmo = get_emmo()
Expand Down
40 changes: 0 additions & 40 deletions tests/ontopy_tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,43 +5,3 @@

# Files to skip
collect_ignore = ["interactive_test.py"]


# Utilities
def abbreviate(onto, iri, must_exist=True):
"""Returns existing Owlready2 storid for `iri`."""
if iri is None:
return None
abbreviater = getattr(onto, "_abbreviate")
storid = abbreviater(iri, create_if_missing=False)
if storid is None and must_exist:
raise ValueError(f"no such IRI in ontology: {iri}")
return storid


def get_triples(onto, s=None, p=None, o=None) -> list:
"""Returns a list of triples matching spo."""
return [
(
onto._unabbreviate(s_) if isinstance(s_, int) and s_ > 0 else s_,
onto._unabbreviate(p_) if isinstance(p_, int) and p_ > 0 else p_,
onto._unabbreviate(o_) if isinstance(o_, int) and o_ > 0 else o_,
)
for s_, p_, o_, d in onto._get_triples_spod_spod(
abbreviate(onto, s),
abbreviate(onto, p),
abbreviate(onto, o, False) or o,
None,
)
]


def has_triple(onto, s=None, p=None, o=None) -> bool:
"""Returns true if ontology `onto` contains the given triple.

None may be used as a wildcard for of `s`, `p` or `o`.
"""
try:
return bool(get_triples(onto, s, p, o))
except ValueError:
return False
113 changes: 78 additions & 35 deletions tests/ontopy_tests/test_patch.py
Original file line number Diff line number Diff line change
@@ -1,48 +1,91 @@
"""Tests Owlready2 patches implemented in ontopy/patch.py

Implemented as a script, such that it easy to understand and use for debugging.
"""

import pytest

from ontopy import get_ontology

from owlready2 import owl, Inverse

from utilities import setassert


def test_get_by_label_onto(emmo: "Ontology") -> None:
Copy link
Collaborator

Choose a reason for hiding this comment

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

What version of EMMO does the emmo argument correspond to? Does it correspond to a stable tag or will it change with time?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It imporst 1.0.0-beta4 (this is currently the default in get_ontology)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ok. Since the pytest fixtures are so opaque, it might be good to add a comment saying this (since we might have to update the tests when get_ontology() is updated to return a newer version of EMMO).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I do not agree that they are opaque, but to make it more readily clear I added a comment in the emmo fixture on conftest.

# Test some ThingClass extensions implemented in patch.py
assert str(emmo.Atom.get_preferred_label()) == "Atom"

assert emmo.Atom.get_parents() == {emmo.MolecularEntity}

setassert(
emmo.Atom.get_annotations().keys(),
{
"prefLabel",
"altLabel",
"elucidation",
"comment",
},
)
setassert(
emmo.Atom.get_annotations(all=True).keys(),
{
"qualifiedCardinality",
"minQualifiedCardinality",
"prefLabel",
"abstract",
"hiddenLabel",
"etymology",
"altLabel",
"example",
"elucidation",
"OWLDLRestrictedAxiom",
"wikipediaReference",
"conceptualisation",
"logo",
"comment",
"dbpediaReference",
"definition",
"VIMTerm",
"creator",
"iupacReference",
"contact",
"omReference",
"ISO9000Reference",
"ISO80000Reference",
"qudtReference",
"contributor",
"license",
"ISO14040Reference",
"figure",
"title",
"publisher",
},
)

# Test item access/assignment/deletion for classes
setassert(emmo.Atom["altLabel"], {"ChemicalElement"})

with pytest.raises(KeyError):
emmo.Atom["hasPart"]

emmo.Atom["altLabel"] = "Element"
setassert(emmo.Atom["altLabel"], {"ChemicalElement", "Element"})

del emmo.Atom["altLabel"]
assert emmo.Atom["altLabel"] == []

emmo.Atom.altLabel = "ChemicalElement"
assert emmo.Atom["altLabel"] == ["ChemicalElement"]

assert emmo.Atom.is_defined == False
assert emmo.Holistic.is_defined == True
assert (
emmo.wikipediaReference
) # Check that wikipediaReference is in ontology
assert (
emmo.Atom.wikipediaReference == []
) # Check that wikipediaReference can be acceses as attribute

emmo = get_ontology().load()


# Test some ThingClass extensions implemented in patch.py
assert emmo.Atom.get_preferred_label() == "Atom"

assert emmo.Atom.get_parents() == {emmo.MolecularEntity}

assert set(emmo.Atom.get_annotations().keys()) == {
"prefLabel",
"altLabel",
"elucidation",
"comment",
}


# Test item access/assignment/deletion for classes
assert set(emmo.Atom["altLabel"]) == {"ChemicalElement"}

with pytest.raises(KeyError):
emmo.Atom["hasPart"]

emmo.Atom["altLabel"] = "Element"
assert set(emmo.Atom["altLabel"]) == {"ChemicalElement", "Element"}

del emmo.Atom["altLabel"]
assert emmo.Atom["altLabel"] == []

emmo.Atom["altLabel"] = "ChemicalElement"
assert emmo.Atom["altLabel"] == ["ChemicalElement"]


assert emmo.Atom.is_defined == False
assert emmo.Holistic.is_defined == True

# TODO: Fix disjoint_with().
# It seems not to take into account disjoint unions.
Expand Down
7 changes: 7 additions & 0 deletions tests/ontopy_tests/test_prefix.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import TYPE_CHECKING
import pytest
from ontopy.utils import NoSuchLabelError
import warnings

if TYPE_CHECKING:
from pathlib import Path
Expand Down Expand Up @@ -37,6 +38,12 @@ def test_prefix(testonto: "Ontology", emmo: "Ontology") -> None:
"TestClass", prefix="models"
)

with pytest.raises(ValueError):
testonto.get_by_label_all(" ")

with pytest.raises(TypeError):
testonto.get_by_label(1)


def test_prefix_emmo(emmo: "Ontology") -> None:
"""Test prefix in ontology"""
Expand Down
2 changes: 1 addition & 1 deletion tests/ontopy_tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ontopy.utils as utils
from testutils import get_triples, has_triple
from utilities import get_triples, has_triple


def test_annotate_source(testonto: "Ontology"):
Expand Down
1 change: 0 additions & 1 deletion tests/test_dir.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from ontopy import get_ontology


thisdir = Path(__file__).resolve().parent

onto = get_ontology(
Expand Down
Binary file modified tests/test_excelparser/onto.xlsx
100755 → 100644
Binary file not shown.
Binary file modified tests/test_excelparser/onto_only_classes.xlsx
100755 → 100644
Binary file not shown.
Binary file modified tests/test_excelparser/onto_update.xlsx
Binary file not shown.
Binary file modified tests/test_excelparser/onto_update_only_classes.xlsx
Binary file not shown.
Loading