Port rdfinfer to rdflib (fixing a hidden bug)
[htsworkflow.git] / htsworkflow / util / test / test_rdfinfer.py
1 from unittest import TestCase
2
3 from rdflib import ConjunctiveGraph, BNode, Literal, Namespace, URIRef
4 from rdflib.plugins.sparql import prepareQuery
5
6 from htsworkflow.util.rdfhelp import \
7      add_default_schemas, load_string_into_model, dump_model
8 from htsworkflow.util.rdfns import *
9 from htsworkflow.util.rdfinfer import Infer
10
11 from rdflib.namespace import FOAF
12
13 MINI_FOAF_ONTOLOGY = """
14 @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
15 @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
16 @prefix owl: <http://www.w3.org/2002/07/owl#> .
17 @prefix foaf: <http://xmlns.com/foaf/0.1/> .
18
19
20 foaf:Agent
21      a rdfs:Class, owl:Class ;
22      rdfs:comment "An agent (person, group, software or physical artifiact)."@en;
23      rdfs:label "Agent" .
24
25 foaf:Person
26      a rdfs:Class, owl:Class, foaf:Agent ;
27      rdfs:label "Person" .
28
29 foaf:age
30      a rdf:Property, owl:DatatypeProperty, owl:FunctionalProperty ;
31      rdfs:comment "The age in years of some agent." ;
32      rdfs:domain foaf:Agent ;
33      rdfs:label "age";
34      rdfs:range rdfs:Literal .
35
36 foaf:familyName
37      a rdf:Property, owl:DatatypeProperty ;
38      rdfs:comment "Family name of some person." ;
39      rdfs:label "familyName" ;
40      rdfs:domain foaf:Person ;
41      rdfs:range rdfs:Literal .
42
43 foaf:firstName
44      a rdf:Property, owl:DatatypeProperty ;
45      rdfs:comment "the first name of a person." ;
46      rdfs:domain foaf:Person ;
47      rdfs:label "firstname" ;
48      rdfs:range rdfs:Literal .
49
50 foaf:Document
51      a rdfs:Class, owl:Class ;
52      rdfs:comment "A document." .
53
54 foaf:Image
55      a rdfs:Class, owl:Class ;
56      rdfs:comment "An image." ;
57      rdfs:subClassOf foaf:Document .
58
59 foaf:depicts
60      a rdf:Property, owl:ObjectProperty ;
61      rdfs:comment "A thing depicted in this representation." ;
62      rdfs:domain foaf:Image ;
63      rdfs:range owl:Thing ;
64      owl:inverseOf foaf:depiction .
65
66 foaf:depiction
67      a rdf:Property, owl:ObjectProperty ;
68      rdfs:comment "Depiction of some thing." ;
69      rdfs:domain owl:Thing ;
70      rdfs:range foaf:Image ;
71      owl:inverseOf foaf:depicts .
72 """
73
74 FOAF_DATA = """
75 @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
76 @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
77 @prefix owl: <http://www.w3.org/2002/07/owl#> .
78 @prefix foaf: <http://xmlns.com/foaf/0.1/> .
79
80 _:me
81      foaf:firstName "Diane" ;
82      foaf:familyName "Trout" ;
83      a foaf:Person, owl:Thing ;
84      <http://example.org/other_literal> "value" ;
85      <http://example.org/other_resource> <http://example.org/resource> .
86
87 <http://example.org/me.jpg>
88      a foaf:Image, owl:Thing ;
89      foaf:depicts _:me .
90 """
91
92 class TestInfer(TestCase):
93     def setUp(self):
94         self.model = ConjunctiveGraph()
95         add_default_schemas(self.model)
96         self.model.parse(data=MINI_FOAF_ONTOLOGY, format='turtle')
97
98     def test_class(self):
99         fooNS = Namespace('http://example.org/')
100         self.model.parse(data=FOAF_DATA, format='turtle')
101         inference = Infer(self.model)
102
103         s = [fooNS['me.jpg'], RDF['type'], RDFS['Class']]
104         found = list(self.model.triples(s))
105         self.assertEqual(len(found), 0)
106         inference._rule_class()
107         s = [fooNS['me.jpg'], RDF['type'], RDFS['Class']]
108         found = list(self.model.triples(s))
109         self.assertEqual(len(found), 1)
110
111     def test_inverse_of(self):
112         fooNS = Namespace('http://example.org/')
113         self.model.parse(data=FOAF_DATA, format='turtle')
114         inference = Infer(self.model)
115         depiction = (None, FOAF['depiction'], fooNS['me.jpg'])
116         size = len(self.model)
117         found_statements = list(self.model.triples(depiction))
118         self.assertEqual(len(found_statements), 0)
119         inference._rule_inverse_of()
120         found_statements = list(self.model.triples(depiction))
121         self.assertEqual(len(found_statements), 1)
122
123         # we should've added one statement.
124         self.assertEqual(len(self.model), size + 1)
125
126         size = len(self.model)
127         inference._rule_inverse_of()
128         # we should already have both versions in our model
129         self.assertEqual(len(self.model), size)
130
131     def test_validate_types(self):
132         fooNS = Namespace('http://example.org/')
133         self.model.parse(data=FOAF_DATA, format='turtle')
134         inference = Infer(self.model)
135
136         errors = list(inference._validate_types())
137         self.assertEqual(len(errors), 0)
138
139         s = (fooNS['document'], DC['title'], Literal("bleem"))
140         self.model.add(s)
141         errors = list(inference._validate_types())
142         self.assertEqual(len(errors), 1)
143
144     def test_validate_undefined_properties_in_schemas(self):
145         fooNS = Namespace('http://example.org/')
146         inference = Infer(self.model)
147
148         errors = list(inference._validate_undefined_properties())
149         self.assertEqual(len(errors), 0)
150
151     def test_validate_undefined_properties_in_inference(self):
152         fooNS = Namespace('http://example.org/')
153         foafNS = Namespace('http://xmlns.com/foaf/0.1/')
154
155         self.model.parse(data=FOAF_DATA, format='turtle')
156
157         inference = Infer(self.model)
158         errors = list(inference._validate_undefined_properties())
159         self.assertEqual(len(errors), 2)
160
161         inference = Infer(self.model)
162         errors = list(inference._validate_property_types())
163         self.assertEqual(len(errors), 0)
164
165         s = (fooNS['me.jpg'], FOAF['firstName'], Literal("name"))
166         self.model.add(s)
167         errors = list(inference._validate_property_types())
168         self.assertEqual(len(errors), 1)
169         startswith = 'Domain of '
170         self.assertEqual(errors[0][:len(startswith)], startswith)
171         self.assertTrue('http://example.org/me.jpg' in errors[0])
172         endswith = 'http://xmlns.com/foaf/0.1/Person'
173         self.assertEqual(errors[0][-len(endswith):], endswith)
174         self.model.remove(s)
175
176         errors = list(inference._validate_property_types())
177         self.assertEqual(len(errors), 0)
178         s = (fooNS['foo.txt'], RDF['type'], FOAF['Document'])
179         self.model.add(s)
180         s = (fooNS['me.jpg'], FOAF['depicts'], FOAF['foo.txt'])
181         self.model.add(s)
182
183         errors = list(inference._validate_property_types())
184         self.assertEqual(len(errors), 1)
185         startswith = 'Range of '
186         self.assertEqual(errors[0][:len(startswith)], startswith)
187         self.assertTrue('http://example.org/me.jpg' in errors[0])
188         endswith = 'http://www.w3.org/2002/07/owl#Thing'
189         self.assertEqual(errors[0][-len(endswith):], endswith)
190         self.model.remove(s)
191
192     def test_property_multiple_domain_types(self):
193         """Can we process a property with multiple domain types?
194         """
195         turtle = """
196         @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
197         @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
198         @prefix foo: <http://example.org/> .
199         @prefix bar: <http://example.com/> .
200
201         foo:AClass a rdfs:Class .
202         foo:BClass a rdfs:Class .
203         bar:ABarClass a rdfs:Class .
204
205         foo:aprop a rdf:Property ;
206             rdfs:domain foo:AClass ;
207             rdfs:domain bar:ABarClass ;
208             rdfs:range foo:BClass .
209
210         foo:object a foo:BClass .
211         foo:subject a foo:AClass ;
212            foo:aprop foo:object .
213         bar:subject a bar:ABarClass ;
214            foo:aprop foo:object .
215         """
216         self.model.parse(data=turtle, format='turtle')
217         inference = Infer(self.model)
218
219         errmsg = list(inference._validate_property_types())
220         self.failUnlessEqual(len(errmsg), 0)
221
222
223 def suite():
224     from unittest import TestSuite, defaultTestLoader
225     suite = TestSuite()
226     suite.addTests(defaultTestLoader.loadTestsFromTestCase(TestInfer))
227     return suite
228
229
230 if __name__ == "__main__":
231     from unittest import main
232     main(defaultTest="suite")