Skip to content

Commit

Permalink
add model processing for event loops
Browse files Browse the repository at this point in the history
- add models for event loops, event and flag reactions, which query
  relevant info from an RDF graph
- add relevant URIs and namespace
- add unittest for event loop models
- change SHACL check func to use Dataset
  • Loading branch information
minhnh committed Nov 15, 2024
1 parent 503d1ee commit bbc0168
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 9 deletions.
4 changes: 2 additions & 2 deletions src/rdf_utils/constraints.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: MPL-2.0
from typing import Dict
from rdflib import ConjunctiveGraph, Graph
from rdflib import Dataset, Graph
import pyshacl


Expand All @@ -20,7 +20,7 @@ def check_shacl_constraints(graph: Graph, shacl_dict: Dict[str, str], quiet=Fals
:param shacl_dict: mapping from SHACL path to graph format, e.g. URL -> "turtle"
:param quiet: if true will not throw an exception
"""
shacl_g = ConjunctiveGraph()
shacl_g = Dataset()
for mm_url, fmt in shacl_dict.items():
shacl_g.parse(mm_url, format=fmt)

Expand Down
90 changes: 90 additions & 0 deletions src/rdf_utils/models/event_loop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# SPDX-License-Identifier: MPL-2.0
from rdflib import Graph, URIRef
from rdf_utils.models.common import ModelBase
from rdf_utils.namespace import NS_MM_EL


URI_EL_TYPE_EVT_LOOP = NS_MM_EL["EventLoop"]
URI_EL_TYPE_EVT = NS_MM_EL["Event"]
URI_EL_TYPE_FLG = NS_MM_EL["Flag"]
URI_EL_TYPE_EVT_REACT = NS_MM_EL["EventReaction"]
URI_EL_TYPE_FLG_REACT = NS_MM_EL["FlagReaction"]
URI_EL_PRED_REF_EVT = NS_MM_EL["ref-event"]
URI_EL_PRED_HAS_EVT = NS_MM_EL["has-event"]
URI_EL_PRED_REF_FLG = NS_MM_EL["ref-flag"]
URI_EL_PRED_HAS_FLG = NS_MM_EL["has-flag"]
URI_EL_PRED_HAS_EVT_REACT = NS_MM_EL["has-evt-reaction"]
URI_EL_PRED_HAS_FLG_REACT = NS_MM_EL["has-flg-reaction"]


class EventReactionModel(ModelBase):
event_id: URIRef

def __init__(self, graph: Graph, reaction_id: URIRef) -> None:
super().__init__(graph=graph, node_id=reaction_id)

evt_uri = graph.value(subject=self.id, predicate=URI_EL_PRED_REF_EVT)
assert evt_uri is not None and isinstance(
evt_uri, URIRef
), f"EventReaction '{self.id}' does not refer to a valid event URI: {evt_uri}"
self.event_id = evt_uri


class FlagReactionModel(ModelBase):
flag_id: URIRef

def __init__(self, graph: Graph, reaction_id: URIRef) -> None:
super().__init__(graph=graph, node_id=reaction_id)

flg_uri = graph.value(subject=self.id, predicate=URI_EL_PRED_REF_FLG)
assert flg_uri is not None and isinstance(
flg_uri, URIRef
), f"FlagReaction '{self.id}' does not refer to a valid flag URI: {flg_uri}"
self.flag_id = flg_uri


class EventLoopModel(ModelBase):
events_triggered: dict[URIRef, bool]
flag_values: dict[URIRef, bool]
event_reactions: dict[URIRef, EventReactionModel]
flag_reactions: dict[URIRef, FlagReactionModel]

def __init__(self, graph: Graph, el_id: URIRef) -> None:
super().__init__(graph=graph, node_id=el_id)

self.events_triggered = {}
self.flag_values = {}
self.event_reactions = {}
self.flag_reactions = {}

for evt_uri in graph.objects(subject=self.id, predicate=URI_EL_PRED_HAS_EVT):
assert isinstance(
evt_uri, URIRef
), f"Event '{evt_uri}' is not of type URIRef: {type(evt_uri)}"
self.events_triggered[evt_uri] = False

for flg_uri in graph.objects(subject=self.id, predicate=URI_EL_PRED_HAS_FLG):
assert isinstance(
flg_uri, URIRef
), f"Flag '{flg_uri}' is not of type URIRef: {type(flg_uri)}"
self.flag_values[flg_uri] = False

for evt_re_uri in graph.objects(subject=self.id, predicate=URI_EL_PRED_HAS_EVT_REACT):
assert isinstance(
evt_re_uri, URIRef
), f"EventReaction '{evt_re_uri}' is not of type URIRef: {type(evt_re_uri)}"
evt_re_model = EventReactionModel(graph=graph, reaction_id=evt_re_uri)
assert (
evt_re_model.event_id in self.events_triggered
), f"'{evt_re_model.id}' reacts to event '{evt_re_model.event_id}', which is not in event loop '{self.id}'"
self.event_reactions[evt_re_model.event_id] = evt_re_model

for flg_re_uri in graph.objects(subject=self.id, predicate=URI_EL_PRED_HAS_FLG_REACT):
assert isinstance(
flg_re_uri, URIRef
), f"FlagReaction '{flg_re_uri}' is not of type URIRef: {type(flg_re_uri)}"
flg_re_model = FlagReactionModel(graph=graph, reaction_id=flg_re_uri)
assert (
flg_re_model.flag_id in self.flag_values
), f"'{flg_re_model.id}' reacts to flag '{flg_re_model.flag_id}', which is not in event loop '{self.id}'"
self.flag_reactions[flg_re_model.flag_id] = flg_re_model
2 changes: 2 additions & 0 deletions src/rdf_utils/namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
URI_MM_PYTHON,
URI_MM_ENV,
URI_MM_TIME,
URI_MM_EL,
)


Expand All @@ -20,3 +21,4 @@
NS_MM_ENV = Namespace(URI_MM_ENV)
NS_MM_AGN = Namespace(URI_MM_AGN)
NS_MM_TIME = Namespace(URI_MM_TIME)
NS_MM_EL = Namespace(URI_MM_EL)
8 changes: 6 additions & 2 deletions src/rdf_utils/uri.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@
URI_MM_GEOM_REL = f"{URL_COMP_ROB2B}/metamodels/geometry/spatial-relations#"
URI_MM_GEOM_COORD = f"{URL_COMP_ROB2B}/metamodels/geometry/coordinates#"

URL_MM_PYTHON_SHACL = f"{URL_SECORO_MM}/languages/python.shacl.ttl"
URI_MM_PYTHON = f"{URL_SECORO_MM}/languages/python#"
URL_MM_PYTHON_JSON = f"{URL_SECORO_MM}/languages/python.json"
URL_MM_PYTHON_SHACL = f"{URL_SECORO_MM}/languages/python.shacl.ttl"

URI_MM_PYTHON = f"{URL_SECORO_MM}/languages/python#"
URI_MM_ENV = f"{URL_SECORO_MM}/environment#"
URI_MM_AGN = f"{URL_SECORO_MM}/agent#"
URI_MM_TIME = f"{URL_SECORO_MM}/time#"

URI_MM_EL = f"{URL_SECORO_MM}/behaviour/event_loop#"
URL_MM_EL_JSON = f"{URL_SECORO_MM}/behaviour/event_loop.json"
URL_MM_EL_SHACL = f"{URL_SECORO_MM}/behaviour/event_loop.shacl.ttl"
128 changes: 128 additions & 0 deletions tests/test_event_loop_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# SPDX-License-Identifier: MPL-2.0
import unittest
from rdflib import Graph, URIRef
from rdf_utils.resolver import install_resolver
from rdf_utils.constraints import check_shacl_constraints
from rdf_utils.uri import URL_MM_EL_JSON, URL_MM_EL_SHACL, URL_SECORO_M
from rdf_utils.models.event_loop import (
URI_EL_TYPE_EVT_LOOP,
URI_EL_TYPE_EVT,
URI_EL_TYPE_EVT_REACT,
URI_EL_TYPE_FLG,
URI_EL_TYPE_FLG_REACT,
EventLoopModel,
)


URI_TEST_EL = f"{URL_SECORO_M}/models/tests/el"
URI_TEST_LOOP = f"{URI_TEST_EL}/test-loop"
URIREF_TEST_LOOP = URIRef(URI_TEST_LOOP)

EVT_LOOP_MODEL_NODES = f"""
{{
"@context": [ "{URL_MM_EL_JSON}" ],
"@graph": [
{{ "@id": "{URI_TEST_EL}/event1", "@type": "{URI_EL_TYPE_EVT.toPython()}" }},
{{ "@id": "{URI_TEST_EL}/event2", "@type": "{URI_EL_TYPE_EVT.toPython()}" }},
{{ "@id": "{URI_TEST_EL}/flag1", "@type": "{URI_EL_TYPE_FLG.toPython()}" }},
{{ "@id": "{URI_TEST_EL}/flag2", "@type": "{URI_EL_TYPE_FLG.toPython()}" }},
{{ "@id": "{URI_TEST_EL}/evt_reaction", "@type": "{URI_EL_TYPE_EVT_REACT.toPython()}" }},
{{ "@id": "{URI_TEST_EL}/flg_reaction", "@type": "{URI_EL_TYPE_FLG_REACT.toPython()}" }},
{{ "@id": "{URI_TEST_LOOP}", "@type": "{URI_EL_TYPE_EVT_LOOP.toPython()}" }}
]
}}
"""
EVT_LOOP_MODEL_CORRECT_COMP = f"""
{{
"@context": [ "{URL_MM_EL_JSON}" ],
"@graph": [
{{
"@id": "{URI_TEST_EL}/evt_reaction", "@type": "{URI_EL_TYPE_EVT_REACT.toPython()}",
"ref-event" : "{URI_TEST_EL}/event1"
}},
{{
"@id": "{URI_TEST_EL}/flg_reaction", "@type": "{URI_EL_TYPE_FLG_REACT.toPython()}",
"ref-flag" : "{URI_TEST_EL}/flag1"
}},
{{
"@id": "{URI_TEST_LOOP}", "@type": "{URI_EL_TYPE_EVT_LOOP.toPython()}",
"has-event": [ "{URI_TEST_EL}/event1", "{URI_TEST_EL}/event2" ],
"has-evt-reaction": "{URI_TEST_EL}/evt_reaction",
"has-flag": [ "{URI_TEST_EL}/flag1", "{URI_TEST_EL}/flag2" ],
"has-flg-reaction": "{URI_TEST_EL}/flg_reaction"
}}
]
}}
"""
EVT_LOOP_MODEL_WRONG_EVT = f"""
{{
"@context": [ "{URL_MM_EL_JSON}" ],
"@graph": [
{{
"@id": "{URI_TEST_EL}/evt_reaction", "@type": "{URI_EL_TYPE_EVT_REACT.toPython()}",
"ref-event" : "{URI_TEST_EL}/event1"
}},
{{
"@id": "{URI_TEST_LOOP}", "@type": "{URI_EL_TYPE_EVT_LOOP.toPython()}",
"has-event": [ "{URI_TEST_EL}/event2" ],
"has-evt-reaction": "{URI_TEST_EL}/evt_reaction"
}}
]
}}
"""
EVT_LOOP_MODEL_WRONG_FLG = f"""
{{
"@context": [ "{URL_MM_EL_JSON}" ],
"@graph": [
{{
"@id": "{URI_TEST_EL}/flg_reaction", "@type": "{URI_EL_TYPE_FLG_REACT.toPython()}",
"ref-flag" : "{URI_TEST_EL}/flag1"
}},
{{
"@id": "{URI_TEST_LOOP}", "@type": "{URI_EL_TYPE_EVT_LOOP.toPython()}",
"has-flag": [ "{URI_TEST_EL}/flag2" ],
"has-flg-reaction": "{URI_TEST_EL}/flg_reaction"
}}
]
}}
"""


class EventLoopModelTest(unittest.TestCase):
def setUp(self):
install_resolver()

def test_correct_el_model(self):
graph = Graph()
graph.parse(data=EVT_LOOP_MODEL_NODES, format="json-ld")

self.assertFalse(
check_shacl_constraints(
graph=graph, shacl_dict={URL_MM_EL_SHACL: "turtle"}, quiet=True
),
"SHACL violation not raised for missing refs from reactions to events and flags",
)

graph.parse(data=EVT_LOOP_MODEL_CORRECT_COMP, format="json-ld")

self.assertTrue(
check_shacl_constraints(graph=graph, shacl_dict={URL_MM_EL_SHACL: "turtle"})
)

_ = EventLoopModel(graph=graph, el_id=URIREF_TEST_LOOP)

def test_wrong_reactions(self):
wrong_evt_g = Graph()
wrong_evt_g.parse(data=EVT_LOOP_MODEL_WRONG_EVT, format="json-ld")
with self.assertRaises(
AssertionError, msg="not raised for reaction to an event not in loop"
):
_ = EventLoopModel(graph=wrong_evt_g, el_id=URIREF_TEST_LOOP)
wrong_flg_g = Graph()
wrong_flg_g.parse(data=EVT_LOOP_MODEL_WRONG_FLG, format="json-ld")
with self.assertRaises(AssertionError, msg="not raised for reaction to a flag not in loop"):
_ = EventLoopModel(graph=wrong_flg_g, el_id=URIREF_TEST_LOOP)


if __name__ == "__main__":
unittest.main()
10 changes: 5 additions & 5 deletions tests/test_python_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import unittest
from urllib.request import urlopen
import pyshacl
from rdflib import Dataset, URIRef
from rdflib import Graph, URIRef
from rdf_utils.models.common import ModelBase, ModelLoader
from rdf_utils.uri import URL_MM_PYTHON_JSON, URL_MM_PYTHON_SHACL, URL_SECORO_M
from rdf_utils.resolver import install_resolver
Expand All @@ -13,8 +13,8 @@
)


TEST_URL = f"{URL_SECORO_M}/models/tests"
URI_OS_PATH_EXISTS = f"{TEST_URL}/test-os-path-exists"
URI_TEST = f"{URL_SECORO_M}/models/tests"
URI_OS_PATH_EXISTS = f"{URI_TEST}/test-os-path-exists"
PYTHON_MODEL = f"""
{{
"@context": [
Expand All @@ -40,10 +40,10 @@ def setUp(self):
self.model_loader.register(load_py_module_attr)

def test_python_import(self):
graph = Dataset()
graph = Graph()
graph.parse(data=PYTHON_MODEL, format="json-ld")

shacl_g = Dataset()
shacl_g = Graph()
shacl_g.parse(URL_MM_PYTHON_SHACL, format="turtle")
conforms, _, report_text = pyshacl.validate(
graph,
Expand Down

0 comments on commit bbc0168

Please sign in to comment.