Skip to content

Commit

Permalink
Importing rdfs schemas (#814)
Browse files Browse the repository at this point in the history
# Description

Partially closes #811.
Closes #797 

I is not possible to easily access classes of typ rdfs:Class. This is
needed if working simultaneously with an rdf-schema (as opposed to an
owl ontology). Owlready2 does not search for enties that are not of type
owl:XXX.
A search for rdfs:Class is now added so that when asking for classes in
an ontology also the rdfs:Class'es are returned as Python objects.

This is not as readily fixed with rdfs:Property. As a first fix, such
properties are now returned as a list of strings (instead of a list of
python objcets). Note that the nodes are present in the onotology, but
the rdfs:Poerty is not cast as a python object.

Owlready2 is designed to support owl ontologies (not rdfs). If we really
want to extend the suport for rdfs:Property the best solution is most
likely to cast it to an owl:Annotation_property. If so this should be
done in a next PR .
  • Loading branch information
francescalb authored Dec 16, 2024
1 parent de542a3 commit a64ffe3
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 72 deletions.
113 changes: 96 additions & 17 deletions ontopy/ontology.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from owlready2.entity import ThingClass
from owlready2.prop import ObjectPropertyClass, DataPropertyClass
from owlready2 import AnnotationPropertyClass
from owlready2.base import rdf_type

from ontopy.factpluspluswrapper.sync_factpp import sync_reasoner_factpp
from ontopy.utils import ( # pylint: disable=cyclic-import
Expand All @@ -51,7 +52,7 @@
)

if TYPE_CHECKING:
from typing import Iterator, List, Sequence
from typing import Iterator, List, Sequence, Generator


# Default annotations to look up
Expand Down Expand Up @@ -1135,19 +1136,50 @@ def rec_imported(onto, imported):
def get_entities( # pylint: disable=too-many-arguments
self,
*,
imported=True,
classes=True,
individuals=True,
object_properties=True,
data_properties=True,
annotation_properties=True,
):
"""Return a generator over (optionally) all classes, individuals,
object_properties, data_properties and annotation_properties.
imported: bool = True,
classes: bool = True,
individuals: bool = True,
object_properties: bool = True,
data_properties: bool = True,
annotation_properties: bool = True,
properties: bool = True,
) -> "Generator[Union[str, object], None, None]":
"""
This method returns a generator over entities in the ontology,
including the following categories:
- Classes (`owl:Class` or `rdfs:Class`)
- Individuals
- Object properties (`owl:ObjectProperty`)
- Data properties (`owl:DataProperty`)
- Annotation properties (`owl:AnnotationProperty`)
- Properties (`rdfs:Property`)
Notes:
- If `properties` is `True`, `rdfs:Property` entities will be returned
as IRIs (strings) rather than Python objects.
- When `imported` is `True`, entities from imported ontologies will
also be included.
Arguments:
imported (bool): Whether to include entities from imported
ontologies. Defaults to `True`.
classes (bool): Whether to include classes. Defaults to `True`.
individuals (bool): Whether to include individuals.
Defaults to `True`.
object_properties (bool): Whether to include object properties.
Defaults to `True`.
data_properties (bool): Whether to include data properties.
Defaults to `True`.
annotation_properties (bool): Whether to include annotation
properties. Defaults to `True`.
properties (bool): Whether to include `rdfs:Property` entities.
Defaults to `True`.
Yields:
Entities matching the specified filters.
If `imported` is `True`, entities in imported ontologies will also
be included.
"""

generator = []
if classes:
generator.append(self.classes(imported))
Expand All @@ -1159,6 +1191,8 @@ def get_entities( # pylint: disable=too-many-arguments
generator.append(self.data_properties(imported))
if annotation_properties:
generator.append(self.annotation_properties(imported))
if properties:
generator.append(self.properties(imported))
yield from itertools.chain(*generator)

def classes(self, imported=False):
Expand All @@ -1175,14 +1209,14 @@ def _entities(
): # pylint: disable=too-many-branches
"""Returns an generator over all entities of the desired type.
This is a helper function for `classes()`, `individuals()`,
`object_properties()`, `data_properties()` and
`annotation_properties()`.
`object_properties()`, `data_properties()`,
`annotation_properties()` and `properties`.
Arguments:
entity_type: The type of entity desired given as a string.
Can be any of `classes`, `individuals`,
`object_properties`, `data_properties` and
`annotation_properties`.
`object_properties`, `data_properties`,
`annotation_properties` or `properties`.
imported: if `True`, entities in imported ontologies
are also returned.
"""
Expand All @@ -1207,9 +1241,18 @@ def _entities(
elif entity_type == "annotation_properties":
for prop in list(onto.annotation_properties()):
generator.append(prop)
elif entity_type == "properties":
generator.append(list(onto.properties()))
else:
if entity_type == "classes":
generator = super().classes()
generator = list(super().classes())
# Add new triples of type rdfs:Class
rdf_schema_class = self._abbreviate(
"http://www.w3.org/2000/01/rdf-schema#Class"
)
for s in self._get_obj_triples_po_s(rdf_type, rdf_schema_class):
if not s < 0:
generator.append(self.world._get_by_storid(s))
elif entity_type == "individuals":
generator = super().individuals()
elif entity_type == "object_properties":
Expand All @@ -1218,6 +1261,8 @@ def _entities(
generator = super().data_properties()
elif entity_type == "annotation_properties":
generator = super().annotation_properties()
elif entity_type == "properties":
generator = self.properties()

yield from generator

Expand Down Expand Up @@ -1258,6 +1303,40 @@ def annotation_properties(self, imported=False):
"""
return self._entities("annotation_properties", imported=imported)

def properties(self, imported=False):
"""Returns an generator over all properties.
It searches for owl:object_properties, owl:data_properties,
owl:annotation_properties and rdf:Properties
Arguments:
imported: if `True`, entities in imported ontologies
are also returned.
"""
generator = []
for prop in list(
self._entities("object_properties", imported=imported)
):
generator.append(prop)

for prop in list(
self._entities("annotation_properties", imported=imported)
):
generator.append(prop)

for prop in list(self._entities("data_properties", imported=imported)):
generator.append(prop)

rdf_property = self._abbreviate(
"http://www.w3.org/1999/02/22-rdf-syntax-ns#Property"
)
for s in self._get_obj_triples_po_s(rdf_type, rdf_property):
if not s < 0:
# print(s, self._unabbreviate(s))
generator.append(self._unabbreviate(s))
# generator.append(self[self._unabbreviate(s)])
# generator.append(self.world._get_by_storid(s))
yield from generator

def get_root_classes(self, imported=False):
"""Returns a list or root classes."""
return [
Expand Down
36 changes: 0 additions & 36 deletions tests/test_import_foaf.py

This file was deleted.

29 changes: 10 additions & 19 deletions tests/test_load.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@
if TYPE_CHECKING:
from pathlib import Path

from pathlib import Path

def test_load(repo_dir: "Path", testonto: "Ontology") -> None:

def test_load() -> None:
# if True:
# from pathlib import Path
# from ontopy import get_ontology
# repo_dir = Path(__file__).resolve().parent.parent
# testonto = get_ontology(str(repo_dir / "tests" / "testonto" / "testonto.ttl")).load()
from pathlib import Path
from ontopy import get_ontology

repo_dir = Path(__file__).resolve().parent.parent
testonto = get_ontology(
str(repo_dir / "tests" / "testonto" / "testonto.ttl")
).load()

import pytest

Expand Down Expand Up @@ -52,17 +57,3 @@ def test_load(repo_dir: "Path", testonto: "Ontology") -> None:
"datamodel-ontology/master/datamodel.ttl"
).load()
assert onto.DataModel


def test_load_rdfs() -> None:
"""Test to load non-emmo based ontologies rdf and rdfs"""
from ontopy import get_ontology

rdf_onto = get_ontology(
"https://www.w3.org/1999/02/22-rdf-syntax-ns.ttl"
).load(emmo_based=False)
rdfs_onto = get_ontology("https://www.w3.org/2000/01/rdf-schema.ttl").load(
emmo_based=False
)
rdfs_onto.Class # Needed to initialize rdfs_onto
assert rdf_onto.HTML.is_a[0].iri == rdfs_onto.Datatype.iri
78 changes: 78 additions & 0 deletions tests/test_load_notemmo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from pathlib import Path

from pathlib import Path


def test_load_rdfs() -> None:
"""Test to load non-emmo based ontologies rdf and rdfs"""
from ontopy import get_ontology

rdf_onto = get_ontology(
"https://www.w3.org/1999/02/22-rdf-syntax-ns.ttl"
).load(emmo_based=False)
rdfs_onto = get_ontology("https://www.w3.org/2000/01/rdf-schema.ttl").load(
emmo_based=False
)
rdfs_onto.Class # Needed to initialize rdfs_onto
assert rdf_onto.HTML.is_a[0].iri == rdfs_onto.Datatype.iri


def test_load_schema() -> None:
"""Test to load non-emmo based ontologies rdf and rdfs"""
from ontopy import get_ontology

repo_dir = Path(__file__).resolve().parent
onto = get_ontology(repo_dir / "testonto" / "minischema.ttl").load(
emmo_based=False
)
assert list(onto.classes()) == [onto.AMRadioChannel]
onto_owlclass = get_ontology(
repo_dir / "testonto" / "minischema_owlclass.ttl"
).load(emmo_based=False)
assert list(onto_owlclass.classes()) == [onto_owlclass.AMRadioChannel]

assert list(onto.properties()) == ["https://schema.org/abridged"]

# Should be:
# assert list(onto.properties()) == [onto.abridged]


# @pytest.mark.skip("FOAF is currently unavailable.")
# def test_import_foaf(emmo: "Ontology") -> None:
if True:
"""Test importing foaf
foaf is the Friend-of-a-Friend ontology.
This test serves more like an example.
TODO: Move to `examples/`
"""
from ontopy import get_ontology

emmo = get_ontology("emmo").load()
# skos = get_ontology("https://www.w3.org/2009/08/skos-reference/skos.html#SKOS-RDF").load()
foaf = get_ontology("http://xmlns.com/foaf/spec/index.rdf").load()

# Needed since foaf refer to skos without importing it
# foaf.imported_ontologies.append(skos)

# Turn off label lookup. Needed because foaf uses labels that are not
# valid Python identifiers
# foaf._special_labels = ()

# Now we can load foaf
# foaf.load()

with emmo:

class Person(emmo.Interpreter):
equivalent_to = [foaf.Person]


# def test_load_qudt:
# if True:
# units = get_ontology("http://qudt.org/2.1/vocab/unit").load()
# rdflib comppains. Apparently it cannot serialize it once it is loaded.
23 changes: 23 additions & 0 deletions tests/testonto/minischema.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
@prefix dcat: <http://www.w3.org/ns/dcat#> .
@prefix dcmitype: <http://purl.org/dc/dcmitype/> .
@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix schema: <https://schema.org/> .
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
@prefix void: <http://rdfs.org/ns/void#> .

schema:AMRadioChannel a rdfs:Class ;
rdfs:label "AMRadioChannel" ;
rdfs:comment "A radio channel that uses AM." ;
rdfs:subClassOf schema:RadioChannel ;
schema:source <https://github.com/schemaorg/schemaorg/issues/1004> .

schema:abridged a rdf:Property ;
rdfs:label "abridged" ;
rdfs:comment "Indicates whether the book is an abridged edition." ;
schema:domainIncludes schema:Book ;
schema:isPartOf <https://bib.schema.org> ;
schema:rangeIncludes schema:Boolean .
16 changes: 16 additions & 0 deletions tests/testonto/minischema_owlclass.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@prefix dcat: <http://www.w3.org/ns/dcat#> .
@prefix dcmitype: <http://purl.org/dc/dcmitype/> .
@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix schema: <https://schema.org/> .
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
@prefix void: <http://rdfs.org/ns/void#> .

schema:AMRadioChannel a owl:Class ;
rdfs:label "AMRadioChannel" ;
rdfs:comment "A radio channel that uses AM." ;
rdfs:subClassOf schema:RadioChannel ;
schema:source <https://github.com/schemaorg/schemaorg/issues/1004> .

0 comments on commit a64ffe3

Please sign in to comment.