diff options
| author | Nicholas Car <nicholas.car@surroundaustralia.com> | 2022-01-02 17:31:41 +1000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-01-02 17:31:41 +1000 |
| commit | f7c614654bd09d2aaa37f96ac5cd89d28244eed8 (patch) | |
| tree | ea6713ce2d2a6ec4a1f340839bc7f55bc62bb89d | |
| parent | 99499f1afa692fa7f5f0115b2098e0d1839f34fc (diff) | |
| parent | 0ee0161892468bc4487075c2de0ade992d348345 (diff) | |
| download | rdflib-f7c614654bd09d2aaa37f96ac5cd89d28244eed8.tar.gz | |
Merge pull request #1530 from gjhiggins/fix-for-issue1453-and-owlinfix
Fix for infixowl issues 1453 and 944
| -rw-r--r-- | rdflib/extras/infixowl.py | 89 | ||||
| -rw-r--r-- | test/test_infixowl.py | 419 |
2 files changed, 467 insertions, 41 deletions
diff --git a/rdflib/extras/infixowl.py b/rdflib/extras/infixowl.py index c6a5d2ad..c81297f2 100644 --- a/rdflib/extras/infixowl.py +++ b/rdflib/extras/infixowl.py @@ -39,26 +39,26 @@ We can then access the rdfs:subClassOf relationships This can also be used against already populated graphs: ->>> owlGraph = Graph().parse(str(OWL)) #doctest: +SKIP ->>> namespace_manager.bind('owl', OWL, override=False) #doctest: +SKIP ->>> owlGraph.namespace_manager = namespace_manager #doctest: +SKIP ->>> list(Class(OWL.Class, graph=owlGraph).subClassOf) #doctest: +SKIP +>>> owlGraph = Graph().parse(str(OWL)) +>>> namespace_manager.bind('owl', OWL, override=False) +>>> owlGraph.namespace_manager = namespace_manager +>>> list(Class(OWL.Class, graph=owlGraph).subClassOf) [Class: rdfs:Class ] Operators are also available. For instance we can add ex:Opera to the extension of the ex:CreativeWork class via the '+=' operator ->>> a #doctest: +SKIP +>>> a Class: ex:Opera SubClassOf: ex:MusicalWork >>> b = Class(exNs.CreativeWork, graph=g) >>> b += a ->>> print(sorted(a.subClassOf, key=lambda c:c.identifier)) #doctest: +SKIP +>>> print(sorted(a.subClassOf, key=lambda c:c.identifier)) [Class: ex:CreativeWork , Class: ex:MusicalWork ] And we can then remove it from the extension as well >>> b -= a ->>> a #doctest: +SKIP +>>> a Class: ex:Opera SubClassOf: ex:MusicalWork Boolean class constructions can also be created with Python operators. @@ -66,21 +66,21 @@ For example, The | operator can be used to construct a class consisting of a owl:unionOf the operands: >>> c = a | b | Class(exNs.Work, graph=g) ->>> c #doctest: +SKIP +>>> c ( ex:Opera OR ex:CreativeWork OR ex:Work ) Boolean class expressions can also be operated as lists (using python list operators) >>> del c[c.index(Class(exNs.Work, graph=g))] ->>> c #doctest: +SKIP +>>> c ( ex:Opera OR ex:CreativeWork ) The '&' operator can be used to construct class intersection: >>> woman = Class(exNs.Female, graph=g) & Class(exNs.Human, graph=g) >>> woman.identifier = exNs.Woman ->>> woman #doctest: +SKIP +>>> woman ( ex:Female AND ex:Human ) >>> len(woman) 2 @@ -88,20 +88,20 @@ The '&' operator can be used to construct class intersection: Enumerated classes can also be manipulated >>> contList = [Class(exNs.Africa, graph=g), Class(exNs.NorthAmerica, graph=g)] ->>> EnumeratedClass(members=contList, graph=g) #doctest: +SKIP +>>> EnumeratedClass(members=contList, graph=g) { ex:Africa ex:NorthAmerica } owl:Restrictions can also be instantiated: ->>> Restriction(exNs.hasParent, graph=g, allValuesFrom=exNs.Human) #doctest: +SKIP +>>> Restriction(exNs.hasParent, graph=g, allValuesFrom=exNs.Human) ( ex:hasParent ONLY ex:Human ) Restrictions can also be created using Manchester OWL syntax in 'colloquial' Python ->>> exNs.hasParent | some | Class(exNs.Physician, graph=g) #doctest: +SKIP +>>> exNs.hasParent << some >> Class(exNs.Physician, graph=g) ( ex:hasParent SOME ex:Physician ) ->>> Property(exNs.hasParent,graph=g) | max | Literal(1) #doctest: +SKIP +>>> Property(exNs.hasParent,graph=g) << max >> Literal(1) ( ex:hasParent MAX 1 ) >>> print(g.serialize(format='pretty-xml')) #doctest: +SKIP @@ -161,6 +161,7 @@ __all__ = [ "generateQName", "GetIdentifiedClasses", "Individual", + "Infix", "MalformedClass", "manchesterSyntax", "Ontology", @@ -195,6 +196,12 @@ class Infix: def __rshift__(self, other): return self.function(other) + def __rmatmul__(self, other): + return Infix(lambda x, self=self, other=other: self.function(other, x)) + + def __matmul__(self, other): + return self.function(other) + def __call__(self, value1, value2): return self.function(value1, value2) @@ -422,7 +429,7 @@ class Individual(object): """ >>> g = Graph() >>> b=Individual(OWL.Restriction,g) - >>> b.type = RDF.Resource + >>> b.type = RDFS.Resource >>> len(list(b.type)) 1 >>> del b.type @@ -773,32 +780,32 @@ def DeepClassClear(classToPrune): >>> classD = Class(EX.D) >>> classE = Class(EX.E) >>> classF = Class(EX.F) - >>> anonClass = EX.someProp | some | classD #doctest: +SKIP - >>> classF += anonClass #doctest: +SKIP - >>> list(anonClass.subClassOf) #doctest: +SKIP + >>> anonClass = EX.someProp << some >> classD + >>> classF += anonClass + >>> list(anonClass.subClassOf) [Class: ex:F ] - >>> classA = classE | classF | anonClass #doctest: +SKIP - >>> classB += classA #doctest: +SKIP - >>> classA.equivalentClass = [Class()] #doctest: +SKIP - >>> classB.subClassOf = [EX.someProp | some | classC] #doctest: +SKIP - >>> classA #doctest: +SKIP + >>> classA = classE | classF | anonClass + >>> classB += classA + >>> classA.equivalentClass = [Class()] + >>> classB.subClassOf = [EX.someProp << some >> classC] + >>> classA ( ex:E OR ex:F OR ( ex:someProp SOME ex:D ) ) - >>> DeepClassClear(classA) #doctest: +SKIP - >>> classA #doctest: +SKIP + >>> DeepClassClear(classA) + >>> classA ( ) - >>> list(anonClass.subClassOf) #doctest: +SKIP + >>> list(anonClass.subClassOf) [] - >>> classB #doctest: +SKIP + >>> classB Class: ex:B SubClassOf: ( ex:someProp SOME ex:C ) - >>> otherClass = classD | anonClass #doctest: +SKIP - >>> otherClass #doctest: +SKIP + >>> otherClass = classD | anonClass + >>> otherClass ( ex:D OR ( ex:someProp SOME ex:D ) ) - >>> DeepClassClear(otherClass) #doctest: +SKIP - >>> otherClass #doctest: +SKIP + >>> DeepClassClear(otherClass) + >>> otherClass ( ) - >>> otherClass.delete() #doctest: +SKIP - >>> list(g.triples((otherClass.identifier, None, None))) #doctest: +SKIP + >>> otherClass.delete() + >>> list(g.triples((otherClass.identifier, None, None))) [] """ @@ -1157,15 +1164,15 @@ class Class(AnnotatableTerms): >>> sister = Class(exNs.Sister) >>> sibling = brother | sister >>> sibling.identifier = exNs.Sibling - >>> sibling #doctest: +SKIP + >>> sibling ( ex:Brother OR ex:Sister ) - >>> first(brother.parents) #doctest: +SKIP + >>> first(brother.parents) Class: ex:Sibling EquivalentTo: ( ex:Brother OR ex:Sister ) >>> parent = Class(exNs.Parent) >>> male = Class(exNs.Male) >>> father = parent & male >>> father.identifier = exNs.Father - >>> list(father.parents) #doctest: +SKIP + >>> list(father.parents) [Class: ex:Parent , Class: ex:Male ] """ @@ -1393,8 +1400,8 @@ class EnumeratedClass(OWLRDFListProxy, Class): { ex:chime ex:uche ex:ejike } >>> col = Collection(g, first( ... g.objects(predicate=OWL.oneOf, subject=ogbujiBros.identifier))) - >>> [g.qname(item) for item in col] - [u'ex:chime', u'ex:uche', u'ex:ejike'] + >>> sorted([g.qname(item) for item in col]) + ['ex:chime', 'ex:ejike', 'ex:uche'] >>> print(g.serialize(format='n3')) #doctest: +SKIP @prefix ex: <http://example.com/> . @prefix owl: <http://www.w3.org/2002/07/owl#> . @@ -1649,7 +1656,7 @@ class Restriction(Class): (maxCardinality, OWL.maxCardinality), (minCardinality, OWL.minCardinality), ] - validRestrProps = [(i, oTerm) for (i, oTerm) in restrTypes if i] + validRestrProps = [(i, oTerm) for (i, oTerm) in restrTypes if i is not None] assert len(validRestrProps) restrictionRange, restrictionType = validRestrProps.pop() self.restrictionType = restrictionType @@ -1681,7 +1688,7 @@ class Restriction(Class): >>> prop = Property(EX.someProp, baseType=OWL.DatatypeProperty) >>> restr1 = (Property( ... EX.someProp, - ... baseType=OWL.DatatypeProperty)) | some | (Class(EX.Foo)) + ... baseType=OWL.DatatypeProperty)) << some >> (Class(EX.Foo)) >>> restr1 #doctest: +SKIP ( ex:someProp SOME ex:Foo ) >>> restr1.serialize(g2) @@ -2087,7 +2094,7 @@ class Property(AnnotatableTerms): ) rt = "\n".join([expr for expr in rt if expr]) rt += "\n)" - return str(rt).encode("utf-8") + return rt def _get_subPropertyOf(self): for anc in self.graph.objects( diff --git a/test/test_infixowl.py b/test/test_infixowl.py new file mode 100644 index 00000000..80f956a7 --- /dev/null +++ b/test/test_infixowl.py @@ -0,0 +1,419 @@ +from rdflib import BNode, Graph, Literal, Namespace, RDFS, OWL, XSD +from rdflib.namespace import NamespaceManager +from rdflib.util import first +from rdflib.extras.infixowl import ( + BooleanClass, + Class, + Collection, + DeepClassClear, + Individual, + some, + EnumeratedClass, + Property, + Restriction, + max, +) + + +def test_infix_owl_example1(): + + exNs = Namespace("http://example.com/") + + namespace_manager = NamespaceManager(Graph()) + namespace_manager.bind("ex", exNs, override=False) + namespace_manager.bind("owl", OWL, override=False) + g = Graph() + g.namespace_manager = namespace_manager + + Individual.factoryGraph = g + classD = Class(exNs.D) + anonClass = exNs.someProp << some >> classD + + assert str(anonClass) == "( ex:someProp SOME ex:D )" + + a = Class(exNs.Opera, graph=g) + + # Now we can assert rdfs:subClassOf and owl:equivalentClass relationships + # (in the underlying graph) with other classes using the 'subClassOf' + # and 'equivalentClass' descriptors which can be set to a list + # of objects for the corresponding predicates. + + a.subClassOf = [exNs.MusicalWork] + + # We can then access the rdfs:subClassOf relationships + + assert str(list(a.subClassOf)) == "[Class: ex:MusicalWork ]" + # [Class: ex:MusicalWork ] + + # This can also be used against already populated graphs: + + owlGraph = Graph().parse(str(OWL)) + namespace_manager.bind("owl", OWL, override=False) + owlGraph.namespace_manager = namespace_manager + assert ( + str(list(Class(OWL.Class, graph=owlGraph).subClassOf)) + == "[Class: rdfs:Class ]" + ) + + # Operators are also available. For instance we can add ex:Opera to the extension + # of the ex:CreativeWork class via the '+=' operator + + assert str(a) == "Class: ex:Opera SubClassOf: ex:MusicalWork" + b = Class(exNs.CreativeWork, graph=g) + b += a + assert ( + str(sorted(a.subClassOf, key=lambda c: c.identifier)) + == "[Class: ex:CreativeWork , Class: ex:MusicalWork ]" + ) + + # And we can then remove it from the extension as well + + b -= a + assert str(a) == "Class: ex:Opera SubClassOf: ex:MusicalWork" + + # Boolean class constructions can also be created with Python operators. + # For example, The | operator can be used to construct a class consisting of a + # owl:unionOf the operands: + + c = a | b | Class(exNs.Work, graph=g) + assert str(c) == "( ex:Opera OR ex:CreativeWork OR ex:Work )" + + # Boolean class expressions can also be operated as lists (using python list + # operators) + + del c[c.index(Class(exNs.Work, graph=g))] + assert str(c) == "( ex:Opera OR ex:CreativeWork )" + + # The '&' operator can be used to construct class intersection: + + woman = Class(exNs.Female, graph=g) & Class(exNs.Human, graph=g) + woman.identifier = exNs.Woman + assert str(woman) == "( ex:Female AND ex:Human )" + assert len(woman) == 2 + + # Enumerated classes can also be manipulated + + contList = [Class(exNs.Africa, graph=g), Class(exNs.NorthAmerica, graph=g)] + assert ( + str(EnumeratedClass(members=contList, graph=g)) + == "{ ex:Africa ex:NorthAmerica }" + ) + + # owl:Restrictions can also be instantiated: + + assert ( + str(Restriction(exNs.hasParent, graph=g, allValuesFrom=exNs.Human)) + == "( ex:hasParent ONLY ex:Human )" + ) + + # Restrictions can also be created using Manchester OWL syntax in 'colloquial' + # Python + assert ( + str(exNs.hasParent << some >> Class(exNs.Physician, graph=g)) + == "( ex:hasParent SOME ex:Physician )" + ) + + Property(exNs.hasParent, graph=g) << max >> Literal(1) + assert ( + str(Property(exNs.hasParent, graph=g) << max >> Literal(1)) + == "( ex:hasParent MAX 1 )" + ) + + +def test_infixowl_deepclassclear(): + EX = Namespace("http://example.com/") + + namespace_manager = NamespaceManager(Graph()) + namespace_manager.bind("ex", EX, override=False) + namespace_manager.bind("owl", OWL, override=False) + + g = Graph() + g.namespace_manager = namespace_manager + Individual.factoryGraph = g + + classB = Class(EX.B) + classC = Class(EX.C) + classD = Class(EX.D) + classE = Class(EX.E) + classF = Class(EX.F) + + anonClass = EX.someProp << some >> classD + + assert str(anonClass) == "( ex:someProp SOME ex:D )" + assert str(classF) == "Class: ex:F " + + classF += anonClass + + assert str(list(anonClass.subClassOf)) == "[Class: ex:F ]" + + classA = classE | classF | anonClass + classB += classA + classA.equivalentClass = [Class()] + classB.subClassOf = [EX.someProp << some >> classC] + + assert str(classA) == "( ex:E OR ex:F OR ( ex:someProp SOME ex:D ) )" + + DeepClassClear(classA) + assert str(classA) == "( )" + + assert list(anonClass.subClassOf) == [] + + assert str(classB) == "Class: ex:B SubClassOf: ( ex:someProp SOME ex:C )" + + otherClass = classD | anonClass + + assert str(otherClass) == "( ex:D OR ( ex:someProp SOME ex:D ) )" + + DeepClassClear(otherClass) + assert str(otherClass) == "( )" + + otherClass.delete() + assert list(g.triples((otherClass.identifier, None, None))) == [] + + +def test_infixowl_individual_type(): + g = Graph() + b = Individual(OWL.Restriction, g) + b.type = RDFS.Resource + assert len(list(b.type)) == 1 + + del b.type + assert len(list(b.type)) == 0 + + +def test_infixowl_individual_label(): + g = Graph() + b = Individual(OWL.Restriction, g) + b.label = Literal("boo") + + assert len(list(b.label)) == 3 + + del b.label + assert hasattr(b, "label") is False + + +def test_infixowl_class_hash(): + b = Class(OWL.Restriction) + c = Class(OWL.Restriction) + assert len(set([b, c])) == 1 + + +def test_infixowl_class_and(): + # Construct an anonymous class description consisting of the + # intersection of this class and 'other' and return it + + exNs = Namespace("http://example.com/") + namespace_manager = NamespaceManager(Graph()) + namespace_manager.bind("ex", exNs, override=False) + namespace_manager.bind("owl", OWL, override=False) + g = Graph() + g.namespace_manager = namespace_manager + + # Chaining 3 intersections + + female = Class(exNs.Female, graph=g) + human = Class(exNs.Human, graph=g) + youngPerson = Class(exNs.YoungPerson, graph=g) + youngWoman = female & human & youngPerson + + assert str(youngWoman) == "ex:YoungPerson THAT ( ex:Female AND ex:Human )" + + assert isinstance(youngWoman, BooleanClass) is True + assert isinstance(youngWoman.identifier, BNode) is True + + +def test_infix_owl_class_getparents(): + # computed attributes that returns a generator over taxonomic 'parents' + # by disjunction, conjunction, and subsumption + + exNs = Namespace("http://example.com/") + namespace_manager = NamespaceManager(Graph()) + namespace_manager.bind("ex", exNs, override=False) + namespace_manager.bind("owl", OWL, override=False) + g = Graph() + g.namespace_manager = namespace_manager + Individual.factoryGraph = g + brother = Class(exNs.Brother) + sister = Class(exNs.Sister) + sibling = brother | sister + sibling.identifier = exNs.Sibling + assert len(sibling) == 2 + + assert str(sibling) == "( ex:Brother OR ex:Sister )" + + assert ( + str(first(brother.parents)) + == "Class: ex:Sibling EquivalentTo: ( ex:Brother OR ex:Sister )" + ) + parent = Class(exNs.Parent) + male = Class(exNs.Male) + father = parent & male + father.identifier = exNs.Father + assert len(list(father.parents)) == 2 + assert str(list(father.parents)) == "[Class: ex:Parent , Class: ex:Male ]" + + +def test_infixowl_enumeratedclass(): + exNs = Namespace("http://example.com/") + namespace_manager = NamespaceManager(Graph()) + namespace_manager.bind("ex", exNs, override=False) + namespace_manager.bind("owl", OWL, override=False) + g = Graph() + g.namespace_manager = namespace_manager + Individual.factoryGraph = g + + ogbujiBros = EnumeratedClass( + exNs.ogbujicBros, members=[exNs.chime, exNs.uche, exNs.ejike] + ) + assert str(ogbujiBros) == "{ ex:chime ex:uche ex:ejike }" + + col = Collection( + g, first(g.objects(predicate=OWL.oneOf, subject=ogbujiBros.identifier)) + ) + assert ( + str(sorted([g.qname(item) for item in col])) + == "['ex:chime', 'ex:ejike', 'ex:uche']" + ) + # logger.debug(g.serialize(format="pretty-xml")) + + assert str(g.serialize(format="n3")) == str( + "@prefix ex: <http://example.com/> .\n" + "@prefix owl: <http://www.w3.org/2002/07/owl#> .\n" + "@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n" + "\n" + "ex:ogbujicBros a owl:Class ;\n" + " owl:oneOf ( ex:chime ex:uche ex:ejike ) .\n" + "\nex:chime a owl:Class .\n" + "\nex:ejike a owl:Class .\n" + "\nex:uche a owl:Class .\n" + "\n" + ) + + +def test_infixowl_booleanclassextenthelper(): + testGraph = Graph() + Individual.factoryGraph = testGraph + EX = Namespace("http://example.com/") + namespace_manager = NamespaceManager(Graph()) + namespace_manager.bind("ex", EX, override=False) + testGraph.namespace_manager = namespace_manager + + fire = Class(EX.Fire) + water = Class(EX.Water) + + testClass = BooleanClass(members=[fire, water]) + assert str(testClass) == "( ex:Fire AND ex:Water )" + + testClass2 = BooleanClass(operator=OWL.unionOf, members=[fire, water]) + assert str(testClass2) == "( ex:Fire OR ex:Water )" + + +def test_infixowl_changeoperator(): + # Converts a unionOf / intersectionOf class expression into one + # that instead uses the given operator + + testGraph = Graph() + Individual.factoryGraph = testGraph + EX = Namespace("http://example.com/") + namespace_manager = NamespaceManager(Graph()) + namespace_manager.bind("ex", EX, override=False) + testGraph.namespace_manager = namespace_manager + fire = Class(EX.Fire) + water = Class(EX.Water) + testClass = BooleanClass(members=[fire, water]) + + assert str(testClass) == "( ex:Fire AND ex:Water )" + + testClass.changeOperator(OWL.unionOf) + assert repr(testClass) == "( ex:Fire OR ex:Water )" + + try: + testClass.changeOperator(OWL.unionOf) + except Exception as e: + assert str(e) == "The new operator is already being used!" + + +def test_infixowl_serialization(): + g1 = Graph() + g2 = Graph() + + EX = Namespace("http://example.com/") + namespace_manager = NamespaceManager(g1) + namespace_manager.bind("ex", EX, override=False) + namespace_manager = NamespaceManager(g2) + namespace_manager.bind("ex", EX, override=False) + + # Individual.factoryGraph = g1 + + prop = Property(EX.someProp, baseType=OWL.DatatypeProperty) + + restr1 = prop << some >> (Class(EX.Foo)) + + assert str(restr1) == "( ex:someProp SOME ex:Foo )" + + assert ( + str(list(Property(EX.someProp, baseType=None).type)) + == "[rdflib.term.URIRef('http://www.w3.org/2002/07/owl#DatatypeProperty')]" + ) + + +def test_cardinality_zero(): + + graph = Graph() + EX = Namespace("http://example.com/") + namespace_manager = NamespaceManager(graph) + namespace_manager.bind("ex", EX, override=False) + prop = Property(EX.someProp, baseType=OWL.DatatypeProperty) + Restriction( + prop, graph=graph, cardinality=Literal(0, datatype=XSD.nonNegativeInteger) + ) + + +def test_lshift_rlshift_delimiters(): + EX = Namespace("http://example.com/") + namespace_manager = NamespaceManager(Graph()) + namespace_manager.bind("ex", EX, override=False) + namespace_manager.bind("owl", OWL, override=False) + g = Graph() + g.namespace_manager = namespace_manager + Individual.factoryGraph = g + classB = Class(EX.B) + classC = Class(EX.C) + classD = Class(EX.D) + classE = Class(EX.E) + classF = Class(EX.F) + + anonClass = EX.someProp << some >> classD + classF += anonClass + assert str(list(anonClass.subClassOf)) == "[Class: ex:F ]" + + classA = classE | classF | anonClass + classB += classA + classA.equivalentClass = [Class()] + classB.subClassOf = [EX.someProp << some >> classC] + assert str(classA) == "( ex:E OR ex:F OR ( ex:someProp SOME ex:D ) )" + + +def test_matmul_rmatmul_delimiters(): + EX = Namespace("http://example.com/") + namespace_manager = NamespaceManager(Graph()) + namespace_manager.bind("ex", EX, override=False) + namespace_manager.bind("owl", OWL, override=False) + g = Graph() + g.namespace_manager = namespace_manager + Individual.factoryGraph = g + classB = Class(EX.B) + classC = Class(EX.C) + classD = Class(EX.D) + classE = Class(EX.E) + classF = Class(EX.F) + + anonClass = EX.someProp @ some @ classD + classF += anonClass + assert str(list(anonClass.subClassOf)) == "[Class: ex:F ]" + + classA = classE | classF | anonClass + classB += classA + classA.equivalentClass = [Class()] + classB.subClassOf = [EX.someProp @ some @ classC] + assert str(classA) == "( ex:E OR ex:F OR ( ex:someProp SOME ex:D ) )" |
