From 28247243b2d02cfd85b14d4ef12cf50cdd14432a Mon Sep 17 00:00:00 2001 From: Matthias Urban <42069939+urbanmatthias@users.noreply.github.com> Date: Thu, 10 Sep 2020 14:28:28 +0200 Subject: [PATCH] Hotfix: Issues with EMMO Fixes #518 (#519) * Issues with EMMO Fixes #518 * Fixed some more tests * minor changes --- osp/core/ontology/attribute.py | 17 +- osp/core/ontology/docs/cuba.ttl | 7 +- osp/core/ontology/entity.py | 82 ++++-- osp/core/ontology/namespace_registry.py | 5 +- osp/core/ontology/oclass.py | 76 ++++-- osp/core/ontology/parser.py | 9 +- osp/core/ontology/relationship.py | 22 +- osp/core/ontology/yml/yml_parser.py | 5 +- osp/core/session/transport/transport_utils.py | 3 +- osp/core/tools/ontology2dot.py | 2 +- osp/core/utils/wrapper_development.py | 3 +- tests/parser_test.ttl | 8 +- tests/test_api_emmo.py | 252 ++++++++++++++++++ tests/test_namespace.py | 4 +- tests/test_ontology_entity.py | 31 +-- tests/test_yml_parser.py | 6 +- 16 files changed, 440 insertions(+), 92 deletions(-) create mode 100644 tests/test_api_emmo.py diff --git a/osp/core/ontology/attribute.py b/osp/core/ontology/attribute.py index 37f097ac..240d50fb 100644 --- a/osp/core/ontology/attribute.py +++ b/osp/core/ontology/attribute.py @@ -6,6 +6,9 @@ logger = logging.getLogger(__name__) +BLACKLIST = {rdflib.OWL.bottomDataProperty, rdflib.OWL.topDataProperty} + + class OntologyAttribute(OntologyEntity): def __init__(self, namespace, name, iri_suffix): super().__init__(namespace, name, iri_suffix) @@ -30,12 +33,14 @@ def datatype(self): Returns: URIRef: IRI of the datatype """ + blacklist = [rdflib.RDFS.Literal] superclasses = self.superclasses datatypes = set() for superclass in superclasses: triple = (superclass.iri, rdflib.RDFS.range, None) for _, _, o in self.namespace._graph.triples(triple): - datatypes.add(o) + if o not in blacklist: + datatypes.add(o) if len(datatypes) == 1: return datatypes.pop() if len(datatypes) == 0: @@ -64,17 +69,19 @@ def convert_to_basic_type(self, value): return convert_from(value, self.datatype) def _direct_superclasses(self): - return self._directly_connected(rdflib.RDFS.subPropertyOf) + return self._directly_connected(rdflib.RDFS.subPropertyOf, + blacklist=BLACKLIST) def _direct_subclasses(self): return self._directly_connected(rdflib.RDFS.subPropertyOf, - inverse=True) + inverse=True, blacklist=BLACKLIST) def _superclasses(self): yield self - yield from self._transitive_hull(rdflib.RDFS.subPropertyOf) + yield from self._transitive_hull(rdflib.RDFS.subPropertyOf, + blacklist=BLACKLIST) def _subclasses(self): yield self yield from self._transitive_hull(rdflib.RDFS.subPropertyOf, - inverse=True) + inverse=True, blacklist=BLACKLIST) diff --git a/osp/core/ontology/docs/cuba.ttl b/osp/core/ontology/docs/cuba.ttl index 3295cfc6..39ef5d35 100644 --- a/osp/core/ontology/docs/cuba.ttl +++ b/osp/core/ontology/docs/cuba.ttl @@ -59,4 +59,9 @@ cuba:Class a owl:Class ; rdfs:isDefinedBy "The root of the ontology." . - +cuba:Class rdfs:subClassOf owl:Thing . +cuba:relationship rdfs:subPropertyOf owl:topObjectProperty . +cuba:attribute rdfs:subPropertyOf owl:topDataProperty . +owl:Thing rdfs:subClassOf cuba:Class . +owl:topObjectProperty rdfs:subPropertyOf cuba:relationship . +owl:topDataProperty rdfs:subPropertyOf cuba:attribute . \ No newline at end of file diff --git a/osp/core/ontology/entity.py b/osp/core/ontology/entity.py index 29ab7285..676446f5 100644 --- a/osp/core/ontology/entity.py +++ b/osp/core/ontology/entity.py @@ -126,55 +126,91 @@ def _superclasses(self): def _subclasses(self): pass - def _transitive_hull(self, predicate_iri, inverse=False): + def _transitive_hull(self, predicate_iri, inverse=False, blacklist=()): """Get all the entities connected with the given predicate. Args: predicate_iri (URIRef): The IRI of the predicate inverse (bool, optional): Use the inverse instead. Defaults to False. + blacklist (collection): A collection of IRIs not to return. Yields: OntologyEntity: The connected entities """ - result = {self.iri} + visited = {self.iri} frontier = {self.iri} while frontier: current = frontier.pop() - triple = (current, predicate_iri, None) - if inverse: - triple = (None, predicate_iri, current) - for x in self.namespace._graph.triples(triple): - o = x[0 if inverse else 2] - if o not in result and not isinstance(o, rdflib.BNode) \ - and not str(o).startswith((str(rdflib.RDF), - str(rdflib.RDFS), - str(rdflib.OWL))): - frontier.add(o) - result.add(o) - yield self.namespace._namespace_registry.from_iri(o) + yield from self._directly_connected(predicate_iri=predicate_iri, + inverse=inverse, + blacklist=blacklist, + _frontier=frontier, + _visited=visited, + _iri=current) + + def _special_cases(self, triple): + """Some supclass statements are often omitted in the ontology. + Replace these with safer triple patterns. + + Args: + triple (Tuple[rdflib.term]): A triple pattern to possibly replace. - def _directly_connected(self, predicate_iri, inverse=False): + Returns: + triple (Tuple[rdflib.term]): Possibly replaced triple. + """ + if triple == (None, rdflib.RDFS.subClassOf, rdflib.OWL.Thing): + return (None, rdflib.RDF.type, rdflib.OWL.Class) + if triple == (rdflib.OWL.Nothing, rdflib.RDFS.subClassOf, None): + return (None, rdflib.RDF.type, rdflib.OWL.Class) + + if triple == (None, rdflib.RDFS.subPropertyOf, + rdflib.OWL.topObjectProperty): + return (None, rdflib.RDF.type, rdflib.OWL.ObjectProperty) + if triple == (rdflib.OWL.bottomObjectProperty, + rdflib.RDFS.subPropertyOf, None): + return (None, rdflib.RDF.type, rdflib.OWL.ObjectProperty) + + if triple == (None, rdflib.RDFS.subPropertyOf, + rdflib.OWL.topDataProperty): + return (None, rdflib.RDF.type, rdflib.OWL.DataProperty) + if triple == (rdflib.OWL.bottomDataProperty, + rdflib.RDFS.subPropertyOf, None): + return (None, rdflib.RDF.type, rdflib.OWL.DataProperty) + return triple + + def _directly_connected(self, predicate_iri, inverse=False, blacklist=(), + _frontier=None, _visited=None, _iri=None): """Get all the entities directly connected with the given predicate. Args: predicate_iri (URIRef): The IRI of the predicate inverse (bool, optional): Use the inverse instead. Defaults to False. + blacklist (collection): A collection of IRIs not to return. + Others: Helper for _transitive_hull method. Yields: OntologyEntity: The connected entities """ - triple = (self.iri, predicate_iri, None) + triple = (_iri or self.iri, predicate_iri, None) if inverse: - triple = (None, predicate_iri, self.iri) + triple = (None, predicate_iri, _iri or self.iri) + + if predicate_iri in [rdflib.RDFS.subClassOf, + rdflib.RDFS.subPropertyOf]: + triple = self._special_cases(triple) for x in self.namespace._graph.triples(triple): - o = x[0 if inverse else 2] - if not isinstance(o, rdflib.BNode) \ - and not str(o).startswith((str(rdflib.RDF), - str(rdflib.RDFS), - str(rdflib.OWL))): - yield self.namespace._namespace_registry.from_iri(o) + o = x[0 if triple[0] is None else 2] + if _visited and o in _visited: + continue + if not isinstance(o, rdflib.BNode): + if _visited is not None: + _visited.add(o) + if _frontier is not None: + _frontier.add(o) + if o not in blacklist: + yield self.namespace._namespace_registry.from_iri(o) def __hash__(self): return hash(self.iri) diff --git a/osp/core/ontology/namespace_registry.py b/osp/core/ontology/namespace_registry.py index 2876f6ef..62db0939 100644 --- a/osp/core/ontology/namespace_registry.py +++ b/osp/core/ontology/namespace_registry.py @@ -160,8 +160,9 @@ def load(self, path): def _load_cuba(self): """Load the cuba namespace""" - path_cuba = os.path.join(os.path.dirname(__file__), "docs", "cuba.ttl") - self._graph.parse(path_cuba, format="ttl") + for x in ["cuba"]: #, "rdf", "rdfs", "owl"]: + path = os.path.join(os.path.dirname(__file__), "docs", x + ".ttl") + self._graph.parse(path, format="ttl") self._graph.bind("cuba", rdflib.URIRef("http://www.osp-core.com/cuba#")) self.update_namespaces() diff --git a/osp/core/ontology/oclass.py b/osp/core/ontology/oclass.py index f70ba43d..ccbbc327 100644 --- a/osp/core/ontology/oclass.py +++ b/osp/core/ontology/oclass.py @@ -5,6 +5,9 @@ logger = logging.getLogger(__name__) +BLACKLIST = {rdflib.OWL.Nothing, rdflib.OWL.Thing, + rdflib.OWL.NamedIndividual} + class OntologyClass(OntologyEntity): def __init__(self, namespace, name, iri_suffix): @@ -20,7 +23,10 @@ def attributes(self): """ attributes = dict() for superclass in self.superclasses: - attributes.update(self._get_attributes(superclass.iri)) + for attr, v in self._get_attributes(superclass.iri).items(): + x = attributes.get(attr, (None, None, None)) + x = (x[0] or v[0], x[1] or v[1], x[2] or v[2]) + attributes[attr] = x return attributes @property @@ -43,28 +49,51 @@ def _get_attributes(self, iri): """ graph = self._namespace._graph attributes = dict() + + blacklist = [rdflib.OWL.topDataProperty, rdflib.OWL.bottomDataProperty] # Case 1: domain of Datatype triple = (None, rdflib.RDFS.domain, iri) for a_iri, _, _ in self.namespace._graph.triples(triple): triple = (a_iri, rdflib.RDF.type, rdflib.OWL.DatatypeProperty) - if triple in graph \ - and not isinstance(a_iri, rdflib.BNode): - a = self.namespace._namespace_registry.from_iri(a_iri) - attributes[a] = self._get_default(a_iri, iri) + if triple not in graph or isinstance(a_iri, rdflib.BNode) \ + or a_iri in blacklist: + continue + a = self.namespace._namespace_registry.from_iri(a_iri) + default = self._get_default(a_iri, iri) + triple = (a_iri, rdflib.RDF.type, rdflib.OWL.FunctionalProperty) + obligatory = default is None and triple in graph + attributes[a] = (self._get_default(a_iri, iri), obligatory, None) # Case 2: restrictions triple = (iri, rdflib.RDFS.subClassOf, None) for _, _, o in self.namespace._graph.triples(triple): - if (o, rdflib.RDF.type, rdflib.OWL.Restriction) in graph: - a_iri = graph.value(o, rdflib.OWL.onProperty) - triple = (a_iri, rdflib.RDF.type, rdflib.OWL.DatatypeProperty) - if triple in graph \ - and not isinstance(a_iri, rdflib.BNode): - a = self.namespace._namespace_registry.from_iri(a_iri) - attributes[a] = self._get_default(a_iri, iri) + if (o, rdflib.RDF.type, rdflib.OWL.Restriction) not in graph: + continue + a_iri = graph.value(o, rdflib.OWL.onProperty) + triple = (a_iri, rdflib.RDF.type, rdflib.OWL.DatatypeProperty) + if triple not in graph or isinstance(a_iri, rdflib.BNode): + continue + a = self.namespace._namespace_registry.from_iri(a_iri) + default = self._get_default(a_iri, iri) + dt, obligatory = self._get_datatype_for_restriction(o) + obligatory = default is None and obligatory + attributes[a] = (self._get_default(a_iri, iri), obligatory, dt) + # TODO more cases return attributes + def _get_datatype_for_restriction(self, r): + obligatory = False + dt = None + g = self.namespace._graph + + dt = g.value(r, rdflib.OWL.someValuesFrom) + obligatory = dt is not None + dt = dt or g.value(r, rdflib.OWL.allValuesFrom) + obligatory = obligatory or (r, rdflib.OWL.cardinality) != 0 + obligatory = obligatory or (r, rdflib.OWL.minCardinality) != 0 + return dt, obligatory + def _get_default(self, attribute_iri, superclass_iri): """Get the default of the attribute with the given iri. @@ -96,7 +125,7 @@ def _get_attributes_values(self, kwargs, _force): """ kwargs = dict(kwargs) attributes = dict() - for attribute, default in self.attributes.items(): + for attribute, (default, obligatory, dt) in self.attributes.items(): if attribute.argname in kwargs: attributes[attribute] = kwargs[attribute.argname] del kwargs[attribute.argname] @@ -112,7 +141,9 @@ def _get_attributes_values(self, kwargs, _force): f"to be ALL_CAPS. You can use the yaml2camelcase " f"commandline tool to transform entity names to CamelCase." ) - else: + elif not _force and obligatory: + raise TypeError("Missing keyword argument: %s" % attribute) + elif default is not None: attributes[attribute] = default # Check validity of arguments @@ -120,24 +151,27 @@ def _get_attributes_values(self, kwargs, _force): if kwargs: raise TypeError("Unexpected keyword arguments: %s" % kwargs.keys()) - missing = [k.argname for k, v in attributes.items() if v is None] - if missing: - raise TypeError("Missing keyword arguments: %s" % missing) return attributes def _direct_superclasses(self): - return self._directly_connected(rdflib.RDFS.subClassOf) + return self._directly_connected(rdflib.RDFS.subClassOf, + blacklist=BLACKLIST) def _direct_subclasses(self): - return self._directly_connected(rdflib.RDFS.subClassOf, inverse=True) + return self._directly_connected(rdflib.RDFS.subClassOf, + inverse=True, blacklist=BLACKLIST) def _superclasses(self): yield self - yield from self._transitive_hull(rdflib.RDFS.subClassOf) + yield from self._transitive_hull( + rdflib.RDFS.subClassOf, + blacklist=BLACKLIST) def _subclasses(self): yield self - yield from self._transitive_hull(rdflib.RDFS.subClassOf, inverse=True) + yield from self._transitive_hull( + rdflib.RDFS.subClassOf, inverse=True, + blacklist=BLACKLIST) def __call__(self, uid=None, session=None, _force=False, **kwargs): """Create a Cuds object from this ontology class. diff --git a/osp/core/ontology/parser.py b/osp/core/ontology/parser.py index 3ab8a293..3c5e69a7 100644 --- a/osp/core/ontology/parser.py +++ b/osp/core/ontology/parser.py @@ -247,18 +247,20 @@ def _parse_rdf(self, **kwargs): self.graph.parse(rdf_file, format=file_format) default_rels = dict() reference_styles = dict() + namespace_iris = set() for namespace, iri in namespaces.items(): if not ( iri.endswith("#") or iri.endswith("/") ): iri += "#" + namespace_iris.add(iri) logger.info(f"You can now use `from osp.core.namespaces import " f"{namespace}`.") self.graph.bind(namespace, rdflib.URIRef(iri)) default_rels[iri] = default_rel reference_styles[iri] = reference_style - self._check_namespaces() + self._check_namespaces(namespace_iris) self._add_cuba_triples(active_rels) self._add_default_rel_triples(default_rels) self._add_reference_style_triples(reference_styles) @@ -313,9 +315,8 @@ def _add_reference_style_triples(self, reference_styles): rdflib.Literal(True) )) - def _check_namespaces(self): - namespaces = set(x for _, x in self.graph.namespaces() - if not x.startswith("http://www.w3.org/")) + def _check_namespaces(self, namespace_iris): + namespaces = set(namespace_iris) for s, p, o in self.graph: pop = None for ns in namespaces: diff --git a/osp/core/ontology/relationship.py b/osp/core/ontology/relationship.py index ac0fc138..fa47cf43 100644 --- a/osp/core/ontology/relationship.py +++ b/osp/core/ontology/relationship.py @@ -6,6 +6,9 @@ # TODO characteristics +BLACKLIST = {rdflib.OWL.bottomObjectProperty, + rdflib.OWL.topObjectProperty} + class OntologyRelationship(OntologyEntity): def __init__(self, namespace, name, iri_suffix): @@ -23,9 +26,11 @@ def inverse(self): triple1 = (self.iri, rdflib.OWL.inverseOf, None) triple2 = (None, rdflib.OWL.inverseOf, self.iri) for _, _, o in self.namespace._graph.triples(triple1): - return self.namespace._namespace_registry.from_iri(o) + if not isinstance(o, rdflib.BNode): + return self.namespace._namespace_registry.from_iri(o) for s, _, _ in self.namespace._graph.triples(triple2): - return self.namespace._namespace_registry.from_iri(s) + if not isinstance(s, rdflib.BNode): + return self.namespace._namespace_registry.from_iri(s) return self._add_inverse() def _direct_superclasses(self): @@ -34,7 +39,8 @@ def _direct_superclasses(self): Returns: OntologyRelationship: The direct subrelationships """ - return self._directly_connected(rdflib.RDFS.subPropertyOf) + return self._directly_connected(rdflib.RDFS.subPropertyOf, + blacklist=BLACKLIST) def _direct_subclasses(self): """Get all the direct subclasses of this relationship. @@ -43,7 +49,7 @@ def _direct_subclasses(self): OntologyRelationship: The direct subrelationships """ return self._directly_connected(rdflib.RDFS.subPropertyOf, - inverse=True) + inverse=True, blacklist=BLACKLIST) def _superclasses(self): """Get all the superclasses of this relationship. @@ -52,7 +58,8 @@ def _superclasses(self): OntologyRelationship: The superrelationships. """ yield self - yield from self._transitive_hull(rdflib.RDFS.subPropertyOf) + yield from self._transitive_hull(rdflib.RDFS.subPropertyOf, + blacklist=BLACKLIST) def _subclasses(self): """Get all the subclasses of this relationship. @@ -62,7 +69,7 @@ def _subclasses(self): """ yield self yield from self._transitive_hull(rdflib.RDFS.subPropertyOf, - inverse=True) + inverse=True, blacklist=BLACKLIST) def _add_inverse(self): """Add the inverse of this relationship to the path. @@ -73,9 +80,12 @@ def _add_inverse(self): o = rdflib.URIRef(self.namespace.get_iri() + "INVERSE_OF_" + self.name) x = (self.iri, rdflib.OWL.inverseOf, o) y = (o, rdflib.RDF.type, rdflib.OWL.ObjectProperty) + z = (o, rdflib.RDFS.label, rdflib.Literal("INVERSE_OF_" + self.name, + lang="en")) self.namespace._graph.add(x) self.namespace._graph.add(y) + self.namespace._graph.add(z) for superclass in self.direct_superclasses: self.namespace._graph.add(( o, rdflib.RDFS.subPropertyOf, superclass.inverse.iri diff --git a/osp/core/ontology/yml/yml_parser.py b/osp/core/ontology/yml/yml_parser.py index c686cddf..1fbe403b 100644 --- a/osp/core/ontology/yml/yml_parser.py +++ b/osp/core/ontology/yml/yml_parser.py @@ -347,15 +347,14 @@ def _add_attribute(self, class_iri, attribute_iri, default): attribute_iri (URIRef): The IRI of the attribute to add default (Any): The default value. """ - datatype_iri = self.graph.value(attribute_iri, rdflib.RDFS.range) \ - or rdflib.XSD.string bnode = rdflib.BNode() self.graph.add( (class_iri, rdflib.RDFS.subClassOf, bnode)) self.graph.add( (bnode, rdflib.RDF.type, rdflib.OWL.Restriction)) self.graph.add( - (bnode, rdflib.OWL.someValuesFrom, datatype_iri)) + (bnode, rdflib.OWL.cardinality, + rdflib.Literal(1, datatype=rdflib.XSD.integer))) self.graph.add( (bnode, rdflib.OWL.onProperty, attribute_iri)) diff --git a/osp/core/session/transport/transport_utils.py b/osp/core/session/transport/transport_utils.py index a4795868..6697be2a 100644 --- a/osp/core/session/transport/transport_utils.py +++ b/osp/core/session/transport/transport_utils.py @@ -101,7 +101,8 @@ def deserialize_buffers(session_obj, buffer_context, data, buffer_context=buffer_context, _force=(k == "deleted")) deserialized[k] = d - move_files(get_file_cuds(d), temp_directory, target_directory) + if k != "deleted": + move_files(get_file_cuds(d), temp_directory, target_directory) deleted = deserialized["deleted"] if "deleted" in deserialized else [] for x in deleted: diff --git a/osp/core/tools/ontology2dot.py b/osp/core/tools/ontology2dot.py index 0e57f2e1..d0aa486a 100644 --- a/osp/core/tools/ontology2dot.py +++ b/osp/core/tools/ontology2dot.py @@ -108,7 +108,7 @@ def _add_oclass(self, oclass, graph): """ attr = "" for key, value in oclass.attributes.items(): - attr += self.attribute.format(key.argname, value) + attr += self.attribute.format(key.argname, value[0]) label = self.label.format(str(oclass), attr) if oclass.namespace in self._namespaces: graph.node(str(oclass), label=label, diff --git a/osp/core/utils/wrapper_development.py b/osp/core/utils/wrapper_development.py index 380edbc7..120ce510 100644 --- a/osp/core/utils/wrapper_development.py +++ b/osp/core/utils/wrapper_development.py @@ -147,8 +147,7 @@ def create_from_cuds_object(cuds_object, session): :rtype: Cuds """ assert cuds_object.session is not session - kwargs = {x.argname: getattr(cuds_object, x.argname) - for x in cuds_object.oclass.attributes} + kwargs = {k.argname: v for k, v in cuds_object.get_attributes().items()} clone = create_recycle(oclass=cuds_object.oclass, kwargs=kwargs, session=session, diff --git a/tests/parser_test.ttl b/tests/parser_test.ttl index bec9708d..62f15f72 100644 --- a/tests/parser_test.ttl +++ b/tests/parser_test.ttl @@ -18,7 +18,7 @@ ns1:_default_value "DEFAULT_D" ] ; rdfs:subClassOf [ a ns2:Restriction ; ns2:onProperty ; - ns2:someValuesFrom xsd:string ], + ns2:cardinality "1"^^xsd:integer ], , . @@ -26,7 +26,7 @@ rdfs:label "ClassE"@en ; rdfs:subClassOf [ a ns2:Restriction ; ns2:onProperty ; - ns2:someValuesFrom xsd:string ], + ns2:cardinality "1"^^xsd:integer ], ns1:Class . a ns2:DatatypeProperty, @@ -61,7 +61,7 @@ rdfs:isDefinedBy "Class A"@en ; rdfs:subClassOf [ a ns2:Restriction ; ns2:onProperty ; - ns2:someValuesFrom xsd:string ], + ns2:cardinality "1"^^xsd:integer ], ns1:Class . a ns2:Class ; @@ -70,7 +70,7 @@ ns1:_default_value "DEFAULT_B" ] ; rdfs:subClassOf [ a ns2:Restriction ; ns2:onProperty ; - ns2:someValuesFrom xsd:string ], + ns2:cardinality "1"^^xsd:integer ], ns1:Class . a ns2:DatatypeProperty, diff --git a/tests/test_api_emmo.py b/tests/test_api_emmo.py new file mode 100644 index 00000000..b8ce0c19 --- /dev/null +++ b/tests/test_api_emmo.py @@ -0,0 +1,252 @@ +import unittest2 as unittest +import uuid + +from osp.core.utils import create_from_cuds_object +from osp.core.session.core_session import CoreSession +from osp.core.namespaces import cuba + +try: + from osp.core.namespaces import math, holistic, mereotopology, perceptual +except ImportError: + from osp.core.ontology import Parser + from osp.core.namespaces import _namespace_registry + Parser(_namespace_registry._graph).parse("emmo") + _namespace_registry.update_namespaces() + math = _namespace_registry.math + holistic = _namespace_registry.holistic + mereotopology = _namespace_registry.mereotopology + perceptual = _namespace_registry.perceptual + + +class TestAPIEmmo(unittest.TestCase): + + def setUp(self): + pass + + def test_is_a(self): + """Test instance check.""" + # TODO update emmo + c = math.Real(hasNumericalData=12, hasSymbolData="12") + self.assertTrue(c.is_a(math.Number)) + self.assertTrue(c.is_a(math.Numerical)) + self.assertTrue(c.is_a(cuba.Class)) + self.assertFalse(c.is_a(cuba.relationship)) + self.assertFalse(c.is_a(math.Integer)) + self.assertFalse(c.is_a(holistic.Process)) + + def test_creation(self): + """ + Tests the instantiation and type of the objects + """ + self.assertRaises(TypeError, math.Real, hasNumericalData=1.2, + uid=0, unwanted="unwanted") + self.assertRaises(TypeError, math.Real) + + r = math.Real(hasNumericalData=1.2, hasSymbolData="1.2") + p = holistic.Process() + self.assertEqual(r.oclass, math.Real) + self.assertEqual(p.oclass, holistic.Process) + cuba.Wrapper(session=CoreSession()) + + def test_uid(self): + """ + Tests that the uid variable contains a UUID object + """ + c = holistic.Process() + self.assertIsInstance(c.uid, uuid.UUID) + + def test_set_throws_exception(self): + """ + Tests that setting a value for a key not in restricted + keys throws an exception. + """ + c = holistic.Process() + self.assertRaises(ValueError, c._neighbors.__setitem__, + "not an allowed key", 15) + + def test_add(self): + """ + Tests the standard, normal behavior of the add() method + """ + p = holistic.Process() + n = math.Real(hasNumericalData=1.2, hasSymbolData="1.2") + + p.add(n) + self.assertEqual(p.get(n.uid).uid, n.uid) + + # Test the inverse relationship + get_inverse = n.get(rel=mereotopology.hasPart.inverse) + self.assertEqual(get_inverse, [p]) + + def test_get(self): + """ + Tests the standard, normal behavior of the get() method. + + - get() + - get(*uids) + - get(rel) + - get(oclass) + - get(*uids, rel) + - get(rel, oclass) + """ + p = holistic.Process() + n = math.Real(hasNumericalData=1.2, hasSymbolData="1.2") + i = math.Integer(hasNumericalData=42, hasSymbolData="42") + p.add(n) + p.add(i, rel=mereotopology.hasProperPart) + + # get() + get_default = p.get() + self.assertEqual(set(get_default), {i, n}) + + # get(*uids) + get_n_uid = p.get(n.uid) + self.assertEqual(get_n_uid, n) + get_i_uid = p.get(i.uid) + self.assertEqual(get_i_uid, i) + get_ni_uid = p.get(n.uid, i.uid) + self.assertEqual(set(get_ni_uid), {n, i}) + get_new_uid = p.get(uuid.uuid4()) + self.assertEqual(get_new_uid, None) + + # get(rel) + get_has_part = p.get(rel=mereotopology.hasPart) + self.assertEqual(set(get_has_part), {n, i}) + get_encloses = p.get(rel=mereotopology.hasProperPart) + self.assertEqual(set(get_encloses), {i}) + get_inhabits = p.get(rel=mereotopology.hasPart.inverse) + self.assertEqual(get_inhabits, []) + + # get(oclass) + get_citizen = p.get(oclass=math.Numerical) + self.assertEqual(set(get_citizen), {i, n}) + get_real = p.get(oclass=math.Real) + self.assertEqual(set(get_real), {n}) + get_integer = p.get(oclass=math.Integer) + self.assertEqual(set(get_integer), {i}) + get_process = p.get(oclass=holistic.Process) + self.assertEqual(get_process, []) + + def test_update(self): + """ + Tests the standard, normal behavior of the update() method. + """ + c = holistic.Process() + n = math.Real(hasNumericalData=1.2, hasSymbolData="1.2") + new_n = create_from_cuds_object(n, CoreSession()) + new_s = math.Integer(hasNumericalData=42, hasSymbolData="42") + new_n.add(new_s) + c.add(n) + + old_real = c.get(n.uid) + old_integers = old_real.get(oclass=math.Integer) + self.assertEqual(old_integers, []) + + c.update(new_n) + + new_real = c.get(n.uid) + new_integers = new_real.get(oclass=math.Integer) + self.assertEqual(new_integers, [new_s]) + + self.assertRaises(ValueError, c.update, n) + + def test_remove(self): + """ + Tests the standard, normal behavior of the remove() method. + + - remove() + - remove(*uids/DataContainers) + - remove(rel) + - remove(oclass) + - remove(rel, oclass) + - remove(*uids/DataContainers, rel) + """ + c = holistic.Process() + n = math.Integer(hasNumericalData=12, hasSymbolData="12") + p = math.Real(hasNumericalData=1.2, hasSymbolData="1.2") + q = math.Real(hasNumericalData=4.2, hasSymbolData="4.2") + c.add(n) + c.add(q, p, rel=mereotopology.hasProperPart) + + self.assertIn(mereotopology.hasPart, c._neighbors) + self.assertIn(mereotopology.hasProperPart, c._neighbors) + + # remove() + c.remove() + self.assertFalse(c._neighbors) + # inverse + get_inverse = p.get(rel=mereotopology.hasPart.inverse) + self.assertEqual(get_inverse, []) + + # remove(*uids/DataContainers) + c.add(n) + c.add(p, q, rel=mereotopology.hasProperPart) + get_all = c.get() + self.assertIn(p, get_all) + c.remove(p.uid) + get_all = c.get() + self.assertNotIn(p, get_all) + # inverse + get_inverse = p.get(rel=mereotopology.hasPart.inverse) + self.assertEqual(get_inverse, []) + + # remove(rel) + c.remove(rel=mereotopology.hasProperPart) + self.assertNotIn(mereotopology.hasProperPart, c._neighbors) + # inverse + get_inverse = p.get(rel=mereotopology.hasProperPart.inverse) + self.assertEqual(get_inverse, []) + + # remove(oclass) + c.remove(oclass=math.Integer) + self.assertNotIn(n, c.get()) + # inverse + get_inverse = n.get(rel=mereotopology.hasProperPart.inverse) + self.assertEqual(get_inverse, []) + + # remove(*uids/DataContainers, rel) + c.add(p, q, rel=mereotopology.hasProperPart) + self.assertIn(mereotopology.hasProperPart, c._neighbors) + c.remove(q, p, rel=mereotopology.hasProperPart) + self.assertNotIn(mereotopology.hasProperPart, c._neighbors) + # inverse + get_inverse = p.get(rel=mereotopology.hasProperPart.inverse) + self.assertEqual(get_inverse, []) + + # remove(rel, oclass) + c.add(p, rel=mereotopology.hasProperPart) + c.add(n) + c.remove(rel=mereotopology.hasProperPart, + oclass=math.Numerical) + get_all = c.get() + self.assertIn(n, get_all) + self.assertNotIn(p, get_all) + # inverse + get_inverse = p.get(rel=mereotopology.hasProperPart.inverse) + self.assertEqual(get_inverse, []) + + def test_iter(self): + """ + Tests the iter() method when no ontology class is provided. + """ + c = holistic.Process() + n = math.Integer(hasNumericalData=12, hasSymbolData="12") + p = math.Real(hasNumericalData=1.2, hasSymbolData="1.2") + q = holistic.Process() + c.add(n) + c.add(p, q, rel=mereotopology.hasProperPart) + + elements = set(list(c.iter())) + self.assertEqual(elements, {n, p, q}) + + def test_get_attributes(self): + p = math.Real(hasNumericalData=1.2, hasSymbolData="1.2") + self.assertEqual( + p.get_attributes(), + {math.hasNumericalData: "1.2", # TODO type conversion + perceptual.hasSymbolData: "1.2"} + ) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_namespace.py b/tests/test_namespace.py index df39fa6f..80450460 100644 --- a/tests/test_namespace.py +++ b/tests/test_namespace.py @@ -191,8 +191,8 @@ def test_namespace_registry_get(self): "invalid") self.assertRaises(AttributeError, getattr, self.namespace_registry, "invalid") - self.assertEqual([x.get_name() for x in self.namespace_registry], [ - 'xml', 'rdf', 'rdfs', 'xsd', 'cuba', 'owl', 'city']) + self.assertEqual({x.get_name() for x in self.namespace_registry}, { + 'xml', 'rdf', 'rdfs', 'xsd', 'cuba', 'owl', 'city'}) def modify_labels(self): triples = list() diff --git a/tests/test_ontology_entity.py b/tests/test_ontology_entity.py index 221327c7..0ae48636 100644 --- a/tests/test_ontology_entity.py +++ b/tests/test_ontology_entity.py @@ -5,6 +5,7 @@ from osp.core.namespaces import cuba from osp.core.ontology.cuba import rdflib_cuba +from osp.core.namespaces import owl try: from osp.core.namespaces import city except ImportError: @@ -97,7 +98,8 @@ def test_get_triples(self): def test_transitive_hull(self): self.assertEqual(set( - city.PopulatedPlace._transitive_hull(rdflib.RDFS.subClassOf)), + city.PopulatedPlace._transitive_hull( + rdflib.RDFS.subClassOf, blacklist={rdflib.OWL.Thing})), {city.GeographicalPlace, cuba.Class} ) self.assertEqual( @@ -119,27 +121,28 @@ def test_directly_connected(self): def test_oclass_attributes(self): self.assertEqual(city.City.attributes, { - city.name: None, - city.coordinates: rdflib.term.Literal('[0, 0]') + city.name: (None, True, None), + city.coordinates: (rdflib.Literal('[0, 0]'), False, None), }) self.assertEqual(city.City.own_attributes, {}) self.assertEqual(city.GeographicalPlace.own_attributes, { - city.name: None, + city.name: (None, True, None), }) + self.maxDiff = None self.assertEqual(city.LivingBeing.own_attributes, { - city.name: rdflib.term.Literal("John Smith"), - city.age: rdflib.term.Literal(25) + city.name: (rdflib.Literal("John Smith"), False, None), + city.age: (rdflib.Literal(25), False, None) }) self.assertEqual(city.Person.attributes, { - city.name: rdflib.term.Literal("John Smith"), - city.age: rdflib.term.Literal(25) + city.name: (rdflib.Literal("John Smith"), False, None), + city.age: (rdflib.Literal(25), False, None) }) self.assertEqual(city.PopulatedPlace.attributes, { - city.name: None, - city.coordinates: rdflib.term.Literal('[0, 0]') + city.name: (None, True, None), + city.coordinates: (rdflib.Literal('[0, 0]'), False, None) }) self.assertEqual(city.GeographicalPlace.attributes, { - city.name: None, + city.name: (None, True, None), }) def test_oclass_get_default(self): @@ -163,12 +166,10 @@ def test_get_attribute_values(self): _force=False) self.assertEqual(city.City._get_attributes_values(kwargs={}, _force=True), - {city.name: None, - city.coordinates: rdflib.term.Literal('[0, 0]')}) + {city.coordinates: rdflib.term.Literal('[0, 0]')}) self.assertEqual(city.City._get_attributes_values(kwargs={}, _force=True), - {city.name: None, - city.coordinates: rdflib.term.Literal('[0, 0]')}) + {city.coordinates: rdflib.term.Literal('[0, 0]')}) self.assertEqual(city.City._get_attributes_values( kwargs={"name": "Freiburg"}, _force=True), {city.name: "Freiburg", diff --git a/tests/test_yml_parser.py b/tests/test_yml_parser.py index a3450433..922c66b6 100644 --- a/tests/test_yml_parser.py +++ b/tests/test_yml_parser.py @@ -119,7 +119,8 @@ def test_add_attributes(self): (bnode1, rdflib_cuba._default_value, rdflib.Literal("DEFAULT_A")), (bnode1, rdflib_cuba._default_attribute, self.parser._get_iri("attributeA")), - (bnode2, rdflib.OWL.someValuesFrom, rdflib.XSD.string), + (bnode2, rdflib.OWL.cardinality, + rdflib.Literal(1, datatype=rdflib.XSD.integer)), (bnode2, rdflib.RDF.type, rdflib.OWL.Restriction), (self.parser._get_iri("ClassA"), rdflib.RDFS.subClassOf, bnode2), (bnode2, rdflib.OWL.onProperty, self.parser._get_iri("attributeA")) @@ -136,7 +137,8 @@ def test_add_attributes(self): self.assertEqual(set(self.graph) - pre, { (bnode3, rdflib.OWL.onProperty, self.parser._get_iri("attributeA")), - (bnode3, rdflib.OWL.someValuesFrom, rdflib.XSD.string), + (bnode3, rdflib.OWL.cardinality, + rdflib.Literal(1, datatype=rdflib.XSD.integer)), (bnode3, rdflib.RDF.type, rdflib.OWL.Restriction), (self.parser._get_iri("ClassE"), rdflib.RDFS.subClassOf, bnode3) })