Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ff443be
Added support for multiple labels in keywords
jesper-friis Feb 26, 2026
dcb1e08
Remove duplicated labels in keyworkds
jesper-friis Feb 26, 2026
be46b4d
Allow duplicated labels
jesper-friis Feb 26, 2026
2f1db7c
Do not include blank nodes when testing for klasses in keywords
francescalb Mar 2, 2026
034423b
Merge branch 'master' into multiple-labels
francescalb Mar 2, 2026
2d2a930
Apply suggestion from @francescalb
francescalb Mar 2, 2026
79b7ca8
Allow epansion on individuals (not in context) when expanding restric…
francescalb Mar 3, 2026
dc1264f
Added new function: update_context()
jesper-friis Mar 3, 2026
6138d7b
Updated tests
jesper-friis Mar 3, 2026
60a331d
Merge branch 'multiple-labels' of github.com:EMMC-ASBL/tripper into m…
jesper-friis Mar 3, 2026
89981df
latest state
jesper-friis Mar 9, 2026
cef25d6
Merge branch 'master' into keywords-with-context-input
jesper-friis Mar 9, 2026
d6615fc
cleanup
jesper-friis Mar 9, 2026
12cb61a
Merge remote-tracking branch 'origin/restrictions_to_iris' into keywo…
jesper-friis Mar 9, 2026
328861f
Updated test
jesper-friis Mar 9, 2026
636c4bd
Fixed expansion of subPropertyOf
jesper-friis Mar 9, 2026
f2e53be
Fixed test_infer_restriction_types()
jesper-friis Mar 9, 2026
1ef9b39
Merge branch 'master' into keywords-with-context-input
jesper-friis Mar 9, 2026
a4556e2
Added missing tests
jesper-friis Mar 10, 2026
583992d
Merge branch 'master' into keywords-with-context-input
jesper-friis Mar 10, 2026
5b51fb5
Added more tests and ignored some warnings
jesper-friis Mar 10, 2026
38c17fd
Moved configurations for ignoring warnings to pyproject.toml
jesper-friis Mar 11, 2026
e307f5c
Cleaned up some additional warnings
jesper-friis Mar 11, 2026
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
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2022-2025 SINTEF
Copyright (c) 2022-2026 SINTEF

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,12 @@ addopts = """-rs --cov=tripper --cov-report=term \
"""
filterwarnings = [
"ignore:.*imp module.*:DeprecationWarning",
"ignore:ConjunctiveGraph.*:DeprecationWarning", # in pyld
"ignore:builtin type SwigPy.*:DeprecationWarning", # in pyld
"ignore:::tripper.literal:243", # Ignore warning in doctest
]


[tool.setuptools.package-data]
"tripper.context" = ["*.json", "*.yaml"]

Expand Down
27 changes: 27 additions & 0 deletions tests/datadoc/test_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,33 @@ def test_get_prefixes():
assert "mediaType" not in prefixes


def test_get_properties():
"""Test get_properties() method."""
properties = ctx.get_properties()
assert "adms" not in properties # prefix is not a property
assert "Document" not in properties # class is not a property
assert properties["mediaType"] == "http://www.w3.org/ns/dcat#mediaType"


def test_get_object_properties():
"""Test get_object_properties() method."""
from tripper import DCTERMS

objprop = ctx.get_object_properties()
assert "adms" not in objprop # prefix is not an object property
assert "Document" not in objprop # class is not an object property
assert "title" not in objprop # annotation is not an object property
assert objprop["hasPart"] == DCTERMS.hasPart


def test_get_classes():
"""Test get_prefixes() method."""
classes = ctx.get_classes()
assert "adms" not in classes
assert "mediaType" not in classes
assert classes["Document"] == "http://xmlns.com/foaf/0.1/Document"


def test_sync_prefixes():
"""Test sync_prefixes() method."""
from tripper import Triplestore
Expand Down
29 changes: 29 additions & 0 deletions tests/datadoc/test_datadoc_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,32 @@ def test_iriname():
assert iriname("abc") == "abc"
assert iriname("rdf:JSON") == "JSON"
assert iriname("https://w3id.org/emmo#Ampere") == "Ampere"


def test_getlabel():
"""Test utility function getlabel()."""
from tripper import SKOS
from tripper.datadoc.errors import InvalidDatadocError
from tripper.datadoc.utils import getlabel

assert getlabel({"@id": "ex:A", "prefLabel": "a"}) == "a"
assert getlabel({"@id": "ex:A", "label": "a"}) == "a"
assert getlabel({"@id": "ex:A", "rdfs:label": "a"}) == "a"
assert getlabel({"@id": "ex:A"}, default="a") == "a"
assert getlabel({"@id": "ex:A"}) == "A"

# Check for precedence of labels
assert (
getlabel({"@id": "ex:A", "rdfs:label": "a", "prefLabel": "b"}) == "a"
)
assert (
getlabel({"@id": "ex:A", "rdfs:label": "a", "skos:prefLabel": "b"})
== "b"
)
assert (
getlabel({"@id": "ex:A", "rdfs:label": "a", SKOS.prefLabel: "b"})
== "b"
)

with pytest.raises(InvalidDatadocError):
getlabel({"x": "ex:A"})
150 changes: 102 additions & 48 deletions tests/datadoc/test_dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,48 @@ def test_store():
}


def test_update_context():
"""Test update_context()."""
from tripper import HUME, OWL, Namespace
from tripper.datadoc import get_context
from tripper.datadoc.dataset import update_context

EX = Namespace("http://example.com/")
sources = {
"@context": {
"ex": str(EX),
"hume": str(HUME),
},
"@graph": [
{
# Instances are not added to context
"@id": "ex:instr",
"@type": "hume:Device",
},
{
# Not added to context, since there is no @type
"@id": "ex:instr2",
},
{
"@id": "ex:MyDevice",
"skos:prefLabel": "MyDevice",
"subClassOf": "hume:Device",
},
],
}
context = get_context(default_theme=None)
update_context(sources, context)
c = context.get_context_dict()
assert "instr" not in c
assert "instr2" not in c
assert "MyDevice" in c
assert c["MyDevice"] == {"@id": EX.MyDevice, "@type": OWL.Class}
assert c["Device"] == {"@id": HUME.Device, "@type": OWL.Class}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should have som check on what happens if there is mismatch between previously added context and updated_context.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not prioritised for now. Added a TODO in test_dataset.py


# TODO: add tests for what happens if there is mismatch between
# previously added context and updated_context...


def test_infer_restriction_types():
"""Test infer_restriction_types()."""
from tripper import DCTERMS, HUME, RDFS, Namespace
Expand All @@ -426,7 +468,7 @@ def test_infer_restriction_types():
"http://example.org#A": {
DCTERMS.creator: "some",
DCTERMS.hasPart: "value",
DCTERMS.issued: "value",
# DCTERMS.issued: "value",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why is this commented out?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Because dcterms:issued is a data property which should result in a value restriction. However, since there are no configuration options for value restrictions (except for creating a blank node, which we currently doesn't support), there are no reasons to include value restriction in the data structure returned by infer_restriction_types().

The value restriction for dcterms:hasPart should probably also be removed.

That will be addressed in upcoming PRs.

}
}

Expand All @@ -452,7 +494,7 @@ def test_infer_restriction_types():
"@id": "ex:MyDevice",
# "@type": "owl:Class",
"subClassOf": HUME.Device,
"hasPart": HUME.MeasuringInstrument,
"hasPart": [HUME.MeasuringInstrument, "ex:MyDevice"],
},
],
}
Expand Down Expand Up @@ -577,6 +619,13 @@ def test_update_restrictions():
"@type": HUME.Device,
"isDefinedBy": HUME.MeasuringInstrument,
},
{
# An individial relating to two classes and an individual.
# Should be converted to an existential restriction.
"@id": "ex:instr3",
"@type": HUME.Device,
"hasPart": [HUME.MeasuringInstrument, "MyDevice", "ex:instr"],
},
Comment on lines +622 to +628
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For completeness we should have individual relating to one individual and individual related to a list of individuals

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added a TODO about this in test_dataset.py. Will be addressed in the next PR that focuses on the update_restrictions() funtions.

{
# A class relating to a class.
# Should be converted to an existential restriction.
Expand All @@ -586,63 +635,68 @@ def test_update_restrictions():
"@id": "ex:MyDevice",
# "@type": "owl:Class",
"subClassOf": HUME.Device,
"label": "MyDevice",
"hasPart": HUME.MeasuringInstrument,
},
{
# A class relating to two classes
"@id": "ex:MyDevice2",
"@type": "owl:Class",
"subClassOf": HUME.Device,
"label": "MyDevice2",
"hasPart": [HUME.MeasuringInstrument, "MyDevice"],
},
# TODO: for completeness, add tests for individual
# relating to one individual and individual related to a
# list of individuals
],
}
r6 = deepcopy(d6)
update_restrictions(r6, ctx)
assert r6 == {
"@context": {
"MeasuringInstrument": {
"@id": "https://w3id.org/emmo/hume#MeasuringInstrument",
"@type": "owl:Class",
}
},
"@graph": [
{
"@id": "ex:instr",
"@type": "https://w3id.org/emmo/hume#Device",
"isDefinedBy": "https://w3id.org/emmo/hume#MeasuringSystem",
},
res6 = {d["@id"]: d for d in r6["@graph"]}
assert res6["ex:instr"] == {
"@id": "ex:instr",
"@type": "https://w3id.org/emmo/hume#Device",
"isDefinedBy": "https://w3id.org/emmo/hume#MeasuringSystem",
}
assert res6["ex:instr2"] == {
"@id": "ex:instr2",
"@type": [
"https://w3id.org/emmo/hume#Device",
{
"@id": "ex:instr2",
"@type": [
"https://w3id.org/emmo/hume#Device",
{
"@type": "owl:Restriction",
"owl:onProperty": {
"@id": (
"http://www.w3.org/2000/01/rdf-schema#"
"isDefinedBy"
)
},
"owl:someValuesFrom": {
"@id": (
"https://w3id.org/emmo/hume#MeasuringInstrument"
)
},
},
],
"@type": "owl:Restriction",
"owl:onProperty": {
"@id": "http://www.w3.org/2000/01/rdf-schema#isDefinedBy",
},
"owl:someValuesFrom": {
"@id": "https://w3id.org/emmo/hume#MeasuringInstrument",
},
},
],
}
assert res6["ex:instr3"] == {
# WRONG! Should be converted to restrictions
"@id": "ex:instr3",
Comment on lines +678 to +679
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should be fixed

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, working on this in PR #514

"@type": "https://w3id.org/emmo/hume#Device",
"hasPart": [
"https://w3id.org/emmo/hume#MeasuringInstrument",
"MyDevice",
"ex:instr",
],
}
assert res6["ex:MyDevice"] == {
"@id": "ex:MyDevice",
"subClassOf": [
"https://w3id.org/emmo/hume#Device",
{
"@id": "ex:MyDevice",
"subClassOf": [
"https://w3id.org/emmo/hume#Device",
{
"@type": "owl:Restriction",
"owl:onProperty": {
"@id": "http://purl.org/dc/terms/hasPart"
},
"owl:someValuesFrom": {
"@id": (
"https://w3id.org/emmo/hume#MeasuringInstrument"
)
},
},
],
"@type": "owl:Restriction",
"owl:onProperty": {"@id": "http://purl.org/dc/terms/hasPart"},
"owl:someValuesFrom": {
"@id": "https://w3id.org/emmo/hume#MeasuringInstrument"
},
},
],
"label": "MyDevice",
}


Expand Down
58 changes: 51 additions & 7 deletions tests/datadoc/test_keywords.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
"""Test the Keywords class."""

# pylint: disable=too-many-statements,wrong-import-position

import pytest

pytest.importorskip("yaml")
pytest.importorskip("pyld")

# pylint: disable=wrong-import-position
from tripper.datadoc import Keywords

# A fixture used by all the tests
Expand All @@ -14,10 +15,13 @@

def test_get_keywords():
"""Test get_keywords() function."""
import warnings

from dataset_paths import testdir # pylint: disable=import-error

from tripper import DDOC
from tripper.datadoc import get_keywords
from tripper import DDOC, OWL, XSD
from tripper.datadoc import get_context, get_keywords
from tripper.errors import TripperWarning

kw1 = get_keywords()
assert kw1.data == keywords.data
Expand Down Expand Up @@ -65,6 +69,38 @@ def test_get_keywords():
assert kw6.data.theme == ["ddoc:datadoc", "ddoc:prefixes", "ddoc:process"]
assert "batchNumber" in kw6

kw7 = get_keywords(theme=None)
assert len(kw7) == 0
kw7.add({"resources": {"MyClass": {"iri": "http://example.com/MyClass"}}})
assert len(kw7) == 0 # no properties in keywords

ctx = get_context(default_theme=None)
ctx.add_context(
{
"ex": "http://example.com/",
"owl": str(OWL),
"xsd": str(XSD),
"objprop": {"@id": "ex:objprop", "@type": "@id"},
"dataprop": {"@id": "ex:dataprop", "@type": "xsd:string"},
"cls": {"@id": "ex:cls", "@type": "owl:Class"},
}
)

# Test `context` argument to get_keywords(). Ignore expected
# warnings about loss of information
with warnings.catch_warnings():
warnings.simplefilter("ignore", category=TripperWarning)

kw8 = get_keywords(kw7, context=ctx, theme=None)
assert len(kw8) == 2 # 2 properties in keywords
assert kw8.get_prefixes()["ex"] == "http://example.com/"
assert set(kw8.classnames()) == {"Resource", "MyClass", "cls"}

kw9 = get_keywords(context=ctx, theme=None)
assert len(kw9) == 2
assert kw9.get_prefixes()["ex"] == "http://example.com/"
assert set(kw9.classnames()) == {"Resource", "cls"}


def test_iter():
"""Test __iter__() method."""
Expand Down Expand Up @@ -155,7 +191,11 @@ def test_load_yaml():
"""
from dataset_paths import indir # pylint: disable=import-error

from tripper.datadoc.errors import ParseError
from tripper.datadoc.errors import (
ParseError,
RedefineKeywordWarning,
SkipRedefineKeywordWarning,
)

kw = keywords.copy()

Expand Down Expand Up @@ -195,10 +235,12 @@ def test_load_yaml():
# keywords are unchanged by failures
# assert kw == keywords

kw.load_yaml(indir / "invalid_keywords9.yaml", redefine="skip")
with pytest.warns(SkipRedefineKeywordWarning):
kw.load_yaml(indir / "invalid_keywords9.yaml", redefine="skip")
assert kw["title"].iri == "dcterms:title"

kw.load_yaml(indir / "invalid_keywords9.yaml", redefine="allow")
with pytest.warns(RedefineKeywordWarning):
kw.load_yaml(indir / "invalid_keywords9.yaml", redefine="allow")
assert kw["title"].iri == "myonto:a"

kw.load_yaml(indir / "valid_keywords.yaml")
Expand Down Expand Up @@ -485,6 +527,7 @@ def test_load2():

from tripper import Triplestore
from tripper.datadoc import get_keywords
from tripper.datadoc.errors import RedefineKeywordWarning
from tripper.utils import AttrDict

ts = Triplestore("rdflib")
Expand Down Expand Up @@ -539,7 +582,8 @@ def test_load2():
# Create a new Keywords object with
# default keywords and load from the triplestore
kw2 = get_keywords()
kw2.load_rdf(ts, redefine="allow")
with pytest.warns(RedefineKeywordWarning):
kw2.load_rdf(ts, redefine="allow")

# Ensure that the specified keywords are in kw2
assert not {
Expand Down
Loading
Loading