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

Get restrictions as dicts #225

Merged
merged 9 commits into from
Jun 24, 2024
62 changes: 62 additions & 0 deletions docs/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,65 @@ True

```

### Class restrictions
When working with OWL ontologies, it is often required to inspect or add class [restriction]s.
The Triplestore class has two convenient methods for this, that do not require knowledge about how restrictions are represented in RDF.
Only support basic restrictions, without any nested logical constructs, are supoprted.
For more advanced restrictions, we recommend to use [EMMOntoPy] or [Owlready2].

A [restriction] is described by the following set of parameters.

* **cls**: IRI of class to which the restriction applies.
* **property**: IRI of restriction property.
* **type**: The type of the restriction. Should be one of:
- *some*: existential restriction (target is a class IRI)
- *only*: universal restriction (target is a class IRI)
- *exactly*: cardinality restriction (target is a class IRI)
- *min*: minimum cardinality restriction (target is a class IRI)
- *max*: maximum cardinality restriction (target is a class IRI)
- *value*: Value restriction (target is an IRI of an individual or a literal)

* **cardinality**: the cardinality value for cardinality restrictions.
* **value**: The IRI or literal value of the restriction target.

As an example, the class `onto:Bacteria` can be logically restricted to be unicellular.
In Manchester syntax, this can be stated as `onto:Bacteria emmo:hasPart exactly 1 onto:Cell`.
With Tripper this can be stated as:

```python
>>> iri = ts.add_restriction(
... cls=ONTO.Bacteria,
... property=EMMO.hasPart,
... type="exactly",
... cardinality=1,
... value=ONTO.Cell,
... )

```
The returned `iri` is the blank node IRI of the new restriction.


To find the above restriction, the `restrictions()` method can be used.
It returns an iterator over all restrictions that matches the provided criteria.
For example:

```python
>>> g = ts.restrictions(cls=ONTO.Bacteria, property=EMMO.hasPart, asdict=True)
>>> list(g) # doctest: +ELLIPSIS
[{'iri': '_:...', 'cls': 'http://example.com/onto#Bacteria', 'property': 'https://w3id.org/emmo#EMMO_17e27c22_37e1_468c_9dd7_95e137f73e7f', 'type': 'exactly', 'cardinality': 1, 'value': 'http://example.com/onto#Cell'}]

```

With the `return_dicts` argument set to false, an iterator over the IRIs of all matching restrictions is returned:

```python
>>> g = ts.restrictions(cls=ONTO.Bacteria, property=EMMO.hasPart, asdict=False)
>>> next(g) == iri
True

```


### Utilities
*Todo: Describe the `tripper.utils` module*

Expand Down Expand Up @@ -272,3 +331,6 @@ https://emmc-asbl.github.io/tripper/latest/api_reference/literal/#tripper.litera
[EMMO]: https://emmc.eu/emmo/
[Function Ontology (FnO)]: https://fno.io/
[list of currently supported backends]: https://github.com/EMMC-ASBL/tripper?tab=readme-ov-file#available-backends
[EMMOntoPy]: https://emmo-repo.github.io/EMMOntoPy/
[Owlready2]: https://pypi.org/project/owlready2/
[restriction]: https://www.w3.org/TR/owl-ref/#Restriction
102 changes: 79 additions & 23 deletions tests/test_triplestore.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def test_triplestore( # pylint: disable=too-many-locals


# if True:
def test_restriction() -> None:
def test_restriction() -> None: # pylint: disable=too-many-statements
"""Test add_restriction() method."""
pytest.importorskip("rdflib")

Expand All @@ -109,7 +109,7 @@ def test_restriction() -> None:
iri = ts.add_restriction(
cls=EX.Animal,
property=EX.hasPart,
target=EX.Cell,
value=EX.Cell,
type="some",
)
txt = ts.serialize(format="ntriples")
Expand All @@ -122,7 +122,7 @@ def test_restriction() -> None:
iri2 = ts.add_restriction(
cls=EX.Kerberos,
property=EX.hasBodyPart,
target=EX.Head,
value=EX.Head,
type="exactly",
cardinality=3,
)
Expand All @@ -140,7 +140,7 @@ def test_restriction() -> None:
iri3 = ts.add_restriction(
cls=EX.Kerberos,
property=EX.position,
target=Literal("The port of Hades"),
value=Literal("The port of Hades"),
type="value",
)
txt3 = ts.serialize(format="ntriples")
Expand All @@ -155,41 +155,97 @@ def test_restriction() -> None:
ts.add_restriction(
cls=EX.Kerberos,
property=EX.hasBodyPart,
target=EX.Head,
value=EX.Head,
type="wrong_type",
)
with pytest.raises(ArgumentTypeError):
ts.add_restriction(
cls=EX.Kerberos,
property=EX.hasBodyPart,
target=EX.Head,
value=EX.Head,
type="min",
)

# Test find restriction
assert set(ts.restrictions()) == {iri, iri2, iri3}
assert set(ts.restrictions(cls=EX.Kerberos)) == {iri2, iri3}
assert set(ts.restrictions(cls=EX.Animal)) == {iri}
assert not set(ts.restrictions(cls=EX.Dog))
assert set(ts.restrictions(cls=EX.Kerberos, cardinality=3)) == {iri2}
assert set(ts.restrictions(cardinality=3)) == {iri2}
assert not set(ts.restrictions(cls=EX.Kerberos, cardinality=2))
assert set(ts.restrictions(property=EX.hasBodyPart)) == {iri2}
assert set(ts.restrictions(property=EX.hasPart)) == {iri}
assert not set(ts.restrictions(property=EX.hasNoPart))
assert set(ts.restrictions(target=EX.Cell)) == {iri}
assert set(ts.restrictions(target=EX.Head)) == {iri2}
assert not set(ts.restrictions(target=EX.Leg))
assert set(ts.restrictions(target=EX.Cell, type="some")) == {iri}
assert set(ts.restrictions(target=EX.Head, type="exactly")) == {iri2}
assert set(ts.restrictions(target=Literal("The port of Hades"))) == {iri3}
assert set(ts.restrictions(asdict=False)) == {iri, iri2, iri3}
assert set(ts.restrictions(cls=EX.Kerberos, asdict=False)) == {iri2, iri3}
assert set(ts.restrictions(cls=EX.Animal, asdict=False)) == {iri}
assert not set(ts.restrictions(cls=EX.Dog, asdict=False))
assert set(
ts.restrictions(cls=EX.Kerberos, cardinality=3, asdict=False)
) == {iri2}
assert set(ts.restrictions(cardinality=3, asdict=False)) == {iri2}
assert not set(
ts.restrictions(cls=EX.Kerberos, cardinality=2, asdict=False)
)
assert set(ts.restrictions(property=EX.hasBodyPart, asdict=False)) == {
iri2
}
assert set(ts.restrictions(property=EX.hasPart, asdict=False)) == {iri}
assert not set(ts.restrictions(property=EX.hasNoPart, asdict=False))
assert set(ts.restrictions(value=EX.Cell, asdict=False)) == {iri}
assert set(ts.restrictions(value=EX.Head, asdict=False)) == {iri2}
assert not set(ts.restrictions(value=EX.Leg, asdict=False))
assert set(ts.restrictions(value=EX.Cell, type="some", asdict=False)) == {
iri
}
assert set(
ts.restrictions(value=EX.Head, type="exactly", asdict=False)
) == {iri2}
assert set(
ts.restrictions(value=Literal("The port of Hades"), asdict=False)
) == {iri3}

with pytest.raises(ArgumentValueError):
set(ts.restrictions(target=EX.Cell, type="wrong_type"))
set(ts.restrictions(value=EX.Cell, type="wrong_type"))

with pytest.raises(ArgumentValueError):
set(ts.restrictions(type="value", cardinality=2))

# Test returning as dicts (asdict=True)
dicts = sorted(ts.restrictions(asdict=True), key=lambda d: d["value"])
for d in dicts: # Remove iri keys, since they refer to blank nodes
d.pop("iri")
assert dicts == sorted(
[
{
"cls": "http://example.com/onto#Animal",
"property": "http://example.com/onto#hasPart",
"type": "some",
"value": "http://example.com/onto#Cell",
"cardinality": None,
},
{
"cls": "http://example.com/onto#Kerberos",
"property": "http://example.com/onto#hasBodyPart",
"type": "exactly",
"value": "http://example.com/onto#Head",
"cardinality": 3,
},
{
"cls": "http://example.com/onto#Kerberos",
"property": "http://example.com/onto#position",
"type": "value",
"value": Literal("The port of Hades", datatype=XSD.string),
"cardinality": None,
},
],
key=lambda d: d["value"],
)

dicts = list(ts.restrictions(type="some", asdict=True))
for d in dicts: # Remove iri keys, since they refer to blank nodes
d.pop("iri")
assert dicts == [
{
"cls": "http://example.com/onto#Animal",
"property": "http://example.com/onto#hasPart",
"type": "some",
"value": "http://example.com/onto#Cell",
"cardinality": None,
},
]


def test_backend_rdflib(expected_function_triplestore: str) -> None:
"""Specifically test the rdflib backend Triplestore.
Expand Down
Loading