Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
feat(onto creation): allow that resources and properties are not sort…
…ed by inheritance (DEV-626) (#167)

* finished & tested

* apply reviewer's feedback

* improve type annotations

* add unit tests for both new methods
  • Loading branch information
jnussbaum committed Mar 23, 2022
1 parent f53ee60 commit 2ebece3
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 2 deletions.
67 changes: 65 additions & 2 deletions knora/dsplib/utils/onto_create_ontology.py
@@ -1,6 +1,7 @@
"""This module handles the ontology creation, update and upload to a DSP server. This includes the creation and update
of the project, the creation of groups, users, lists, resource classes, properties and cardinalities."""
import json
import re
from typing import Union, Optional, Any

from knora.dsplib.models.connection import Connection
Expand Down Expand Up @@ -298,6 +299,66 @@ def create_users(con: Connection, users: list[dict[str, str]], groups: dict[str,
exit(1)


def sort_resources(unsorted_resources: list[dict[str, Any]], onto_name: str) -> list[dict[str, Any]]:
"""
This method sorts the resource classes in an ontology according their inheritance order
(parent classes first).
Args:
unsorted_resources: list of resources from a JSON ontology definition
onto_name: name of the onto
Returns:
sorted list of resource classes
"""

sorted_resources: list[dict[str, Any]] = list()
ok_resource_names: list[str] = list()
while len(unsorted_resources) > 0:
for res in unsorted_resources.copy():
res_name = f'{onto_name}:{res["name"]}'
parent_classes = res['super']
if isinstance(parent_classes, str):
parent_classes = [parent_classes]
parent_classes = [re.sub(r'^:([^:]+)$', f'{onto_name}:\\1', elem) for elem in parent_classes]
parent_classes_ok = [not parent.startswith(onto_name) or parent in ok_resource_names for parent in parent_classes]
if all(parent_classes_ok):
sorted_resources.append(res)
ok_resource_names.append(res_name)
unsorted_resources.remove(res)
return sorted_resources


def sort_prop_classes(unsorted_prop_classes: list[dict[str, Any]], onto_name: str) -> list[dict[str, Any]]:
"""
In case of inheritance, parent properties must be uploaded before their children. This method sorts the
properties.
Args:
unsorted_prop_classes: list of properties from a JSON ontology definition
onto_name: name of the onto
Returns:
sorted list of properties
"""

sorted_prop_classes: list[dict[str, Any]] = list()
ok_propclass_names: list[str] = list()
while len(unsorted_prop_classes) > 0:
for prop in unsorted_prop_classes.copy():
prop_name = f'{onto_name}:{prop["name"]}'
parent_classes = prop.get('super', 'hasValue')
if isinstance(parent_classes, str):
parent_classes = [parent_classes]
parent_classes = [re.sub(r'^:([^:]+)$', f'{onto_name}:\\1', elem) for elem in parent_classes]
parent_classes_ok = [not parent.startswith(onto_name) or parent in ok_propclass_names for parent in parent_classes]
if all(parent_classes_ok):
sorted_prop_classes.append(prop)
ok_propclass_names.append(prop_name)
unsorted_prop_classes.remove(prop)
return sorted_prop_classes


def create_ontology(input_file: str,
lists_file: str,
server: str,
Expand Down Expand Up @@ -418,7 +479,8 @@ def create_ontology(input_file: str,

# create the empty resource classes
new_res_classes: dict[str, ResourceClass] = {}
for res_class in ontology.get("resources"):
sorted_resources = sort_resources(ontology['resources'], ontology['name'])
for res_class in sorted_resources:
res_name = res_class.get("name")
super_classes = res_class.get("super")
if isinstance(super_classes, str):
Expand Down Expand Up @@ -460,7 +522,8 @@ def create_ontology(input_file: str,
new_res_class.print()

# create the property classes
for prop_class in ontology.get("properties"):
sorted_prop_classes = sort_prop_classes(ontology['properties'], ontology['name'])
for prop_class in sorted_prop_classes:
prop_name = prop_class.get("name")
prop_label = LangString(prop_class.get("labels"))

Expand Down
45 changes: 45 additions & 0 deletions test/unittests/test_create_ontology.py
@@ -0,0 +1,45 @@
"""unit tests for ontology creation"""
import unittest
import json
from typing import Any

from knora.dsplib.utils.onto_create_ontology import *


class TestOntoCreation(unittest.TestCase):
ontology: dict[str, Any] = json.loads('testdata/test-onto.json')['project']['ontologies'][0]

def test_sort_resources(self) -> None:
"""
The 'resources' section of an onto is a list of dictionaries. The safest way to test
that the sorted list contains the same dicts is to sort both lists according to the
same criteria, and then test for list equality.
"""
onto_name: str = self.ontology['name']
unsorted_resources: list[dict[str, Any]] = self.ontology['resources']
sorted_resources = sort_resources(unsorted_resources, onto_name)

unsorted_resources = sorted(unsorted_resources, key=lambda a: a['name'])
sorted_resources = sorted(sorted_resources, key=lambda a: a['name'])

self.assertListEqual(unsorted_resources, sorted_resources)


def test_sort_prop_classes(self) -> None:
"""
The 'properties' section of an onto is a list of dictionaries. The safest way to test
that the sorted list contains the same dicts is to sort both lists according to the
same criteria, and then test for list equality.
"""
onto_name: str = self.ontology['name']
unsorted_props: list[dict[str, Any]] = self.ontology['resources']
sorted_props = sort_prop_classes(unsorted_props, onto_name)

unsorted_props = sorted(unsorted_props, key=lambda a: a['name'])
sorted_props = sorted(sorted_props, key=lambda a: a['name'])

self.assertListEqual(unsorted_props, sorted_props)


if __name__ == '__main__':
unittest.main()

0 comments on commit 2ebece3

Please sign in to comment.