diff --git a/README.md b/README.md index 5c6aece..6ab5f63 100644 --- a/README.md +++ b/README.md @@ -188,6 +188,8 @@ optional arguments: Title to use for graph. If not supplied, the repo URI will be used if graphing an endpoint, or 'Gist' if graphing local files. + --show-bnode-subjects Use triples with blank nodes in the subject to generate + the graphic. Sampling Limits: --instance-limit INSTANCE_LIMIT diff --git a/onto_tool/command_line.py b/onto_tool/command_line.py index e961494..966e346 100644 --- a/onto_tool/command_line.py +++ b/onto_tool/command_line.py @@ -198,6 +198,8 @@ def define_graphic_parser(subparsers): " graphing an endpoint, or 'Gist' if graphing local files.") graphic_parser.add_argument('ontology', nargs="*", default=[], help="Ontology file, directory or name pattern") + graphic_parser.add_argument("--show-bnode-subjects", action="store_true", + help="Include blank node subjects when generating a graph.") def define_export_parser(subparsers): diff --git a/onto_tool/onto_tool.py b/onto_tool/onto_tool.py index bfee329..1c0fccd 100644 --- a/onto_tool/onto_tool.py +++ b/onto_tool/onto_tool.py @@ -169,6 +169,8 @@ def generate_graphic(action, onto_files, endpoint, **kwargs): Read cached query results save_cache: TextIOWrapper Save query results as JSON to use with --cache + show_bnode_subjects: boolean + If true, triples with blank nodes in the subject will not be used to filtered out. Returns ------- @@ -308,7 +310,8 @@ def main(arguments): exclude_pattern=args.exclude_pattern, show_shacl=args.show_shacl, cache=args.cache, - save_cache=args.save_cache) + save_cache=args.save_cache, + show_bnode_subjects=args.show_bnode_subjects) return of = 'pretty-xml' if args.format == 'xml' else args.format diff --git a/onto_tool/ontograph.py b/onto_tool/ontograph.py index b7bc896..8bbb910 100644 --- a/onto_tool/ontograph.py +++ b/onto_tool/ontograph.py @@ -84,6 +84,7 @@ def __init__(self, files, repo=None, **kwargs): self.show_shacl = kwargs.get('show_shacl') self.shapes = defaultdict(list) + self.show_bnode_subjects = kwargs.get('show_bnode_subjects') def __configure_data_source(self, repo, kwargs, title, version): """Determine graph title and output location from data source.""" @@ -172,7 +173,7 @@ def gather_schema_info_from_files(self): ontology = next(graph.subjects(RDF.type, OWL.Ontology)) ontology_name = self.__strip_uri(ontology) classes = [self.__strip_uri(c) for c in graph.subjects(RDF.type, OWL.Class) - if not isinstance(c, BNode)] + if not isinstance(c, BNode) or self.show_bnode_subjects] obj_props = [self.__strip_uri(c) for c in graph.subjects(RDF.type, OWL.ObjectProperty)] data_props = [self.__strip_uri(c) @@ -182,8 +183,8 @@ def gather_schema_info_from_files(self): all_seen = set(classes + obj_props + data_props + annotation_props) gist_things = [ self.__strip_uri(s) for (s, o) in graph.subject_objects(RDF.type) - if not isinstance(s, BNode) and not s == ontology and not self.__strip_uri(s) - in all_seen] + if (not isinstance(s, BNode) or self.show_bnode_subjects) and + not s == ontology and not self.__strip_uri(s) in all_seen] imports = [self.__strip_uri(c) for c in graph.objects(ontology, OWL.imports)] @@ -202,6 +203,7 @@ def gather_schema_info_from_files(self): def gather_schema_info_from_repo(self): """Load schema data from SPARQL endpoint.""" onto_data = defaultdict(lambda: defaultdict(list)) + bnode_filter = "filter(!ISBLANK(?entity))" if not self.show_bnode_subjects else "" if self.single_graph: onto_query = """ prefix owl: @@ -221,7 +223,7 @@ def gather_schema_info_from_repo(self): { ?entity a ?type . FILTER(?type != owl:Ontology) - filter(!ISBLANK(?entity)) + $bnode_filter } } } @@ -243,7 +245,7 @@ def gather_schema_info_from_repo(self): UNION { ?entity rdfs:isDefinedBy ?ontology; a ?type . - filter(!ISBLANK(?entity)) + $bnode_filter } } """ @@ -254,7 +256,8 @@ def gather_schema_info_from_repo(self): str(OWL.AnnotationProperty): 'annotation_propertiesList', str(OWL.imports): 'imports' } - for entity in self.__remote_select_query(onto_query): + for entity in self.__remote_select_query( + Template(onto_query).substitute(bnode_filter=bnode_filter)): key = mapping.get(entity['type'], 'gist_thingsList') onto_data[entity['ontology']][key].append( self.__strip_uri(entity['entity'])) @@ -481,6 +484,8 @@ def gather_instance_info(self): json.dump(self.cached_data, self.save_cache) def __build_class_hierarchy(self): + bnode_filter = "filter (!isblank(?class) && !isblank(?parent))\n" \ + if not self.show_bnode_subjects else "" inheritance_query = Template(""" prefix owl: prefix rdf: @@ -498,7 +503,7 @@ def __build_class_hierarchy(self): rdf:rest*/rdf:first ?parent . ?parent a owl:Class } - filter (!isblank(?class) && !isblank(?parent)) + $bnode_filter OPTIONAL { ?class rdfs:label|skos:prefLabel ?c_label FILTER(lang(?c_label) = '$language' || lang(?c_label) = '') @@ -508,7 +513,7 @@ def __build_class_hierarchy(self): FILTER(lang(?p_label) = '$language' || lang(?p_label) = '') } } - """).substitute(language=self.label_lang) + """).substitute(bnode_filter=bnode_filter, language=self.label_lang) parents = self.__select_query(inheritance_query, 'parents') for inheritance_info in parents: @@ -634,6 +639,7 @@ def __add_shacl_coloring(self): self.shapes[row['class']].append(row['property']) def __create_class_count_query(self, limit): + bnode_filter = "FILTER(!ISBLANK(?s))" if not self.show_bnode_subjects else "" class_query = """ prefix owl: prefix rdf: @@ -646,7 +652,7 @@ def __create_class_count_query(self, limit): { select (group_concat(?o;separator=' ') as ?src) where { $pattern - FILTER(!ISBLANK(?s)) + $bnode_filter FILTER (!STRSTARTS(STR(?o), 'http://www.w3.org/2002/07/owl#')) } group by ?s LIMIT $limit } @@ -654,10 +660,12 @@ def __create_class_count_query(self, limit): """ query_text = Template(class_query).substitute( pattern=self.__filtered_graph_pattern(str(RDF.type)), + bnode_filter=bnode_filter, limit=limit) return query_text def __create_predicate_query(self, predicate, predicate_type, limit): + bnode_filter = "FILTER(!ISBLANK(?s))" if not self.show_bnode_subjects else "" if predicate_type == str(OWL.ObjectProperty): type_query = """ prefix owl: @@ -674,7 +682,7 @@ def __create_predicate_query(self, predicate, predicate_type, limit): (group_concat(?tgt_c;separator=' ') as ?tgt) where { $pattern - FILTER(!ISBLANK(?s)) + $bnode_filter ?s a ?src_c . FILTER (!STRSTARTS(STR(?src_c), 'http://www.w3.org/2002/07/owl#')) ?o a ?tgt_c . @@ -695,7 +703,8 @@ def __create_predicate_query(self, predicate, predicate_type, limit): { select (group_concat(?src_c;separator=' ') as ?src) (SAMPLE(COALESCE(?dtype, xsd:string)) as ?dt) where { $pattern - FILTER(!ISBLANK(?s) && ISLITERAL(?o)) + FILTER(ISLITERAL(?o)) + $bnode_filter ?s a ?src_c . FILTER (!STRSTARTS(STR(?src_c), 'http://www.w3.org/2002/07/owl#')) BIND(DATATYPE(?o) as ?dtype) . @@ -724,7 +733,7 @@ def __create_predicate_query(self, predicate, predicate_type, limit): select (group_concat(?src_c;separator=' ') as ?src) (group_concat(?tgt_c;separator=' ') as ?tgt) where { $pattern - FILTER(!ISBLANK(?s)) + $bnode_filter ?s a ?src_c . FILTER (!STRSTARTS(STR(?src_c), 'http://www.w3.org/2002/07/owl#')) FILTER (!STRSTARTS(STR(?src_c), 'http://www.w3.org/ns/shacl#')) @@ -742,7 +751,8 @@ def __create_predicate_query(self, predicate, predicate_type, limit): select (group_concat(?src_c;separator=' ') as ?src) (SAMPLE(COALESCE(?dtype, xsd:string)) as ?dt) where { $pattern - FILTER(!ISBLANK(?s) && ISLITERAL(?o)) + FILTER(ISLITERAL(?o)) + $bnode_filter ?s a ?src_c . FILTER (!STRSTARTS(STR(?src_c), 'http://www.w3.org/2002/07/owl#')) FILTER (!STRSTARTS(STR(?src_c), 'http://www.w3.org/ns/shacl#')) @@ -756,6 +766,7 @@ def __create_predicate_query(self, predicate, predicate_type, limit): """ query_text = Template(type_query).substitute( pattern=self.__filtered_graph_pattern(predicate), + bnode_filter=bnode_filter, limit=limit) return query_text diff --git a/requirements.txt b/requirements.txt index 26570dd..af9b8e9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,8 +13,9 @@ isodate>=0.6.0 jsonschema>=3.2.0 attrs>=19.3.0 pyrsistent>=0.16.0 -setuptools>=40.8.0 +setuptools<58.0.0 namedentities>=1.5.2 zipp>=3.1.0 pytest~=6.1.2 SPARQLWrapper~=1.8.5 +sparql-endpoint-fixture>=0.5.0 diff --git a/tests/__init__.py b/tests/__init__.py index e69de29..7477c34 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1,3 @@ +pytest_plugins = [ + "sparql_endpoint_fixture.endpoint" +] diff --git a/tests/bundle/__init__.py b/tests/bundle/__init__.py index a8dd25a..e69de29 100644 --- a/tests/bundle/__init__.py +++ b/tests/bundle/__init__.py @@ -1,3 +0,0 @@ -import os -if not os.path.isdir('tests-output/bundle'): - os.makedirs('tests-output/bundle') diff --git a/tests/bundle/test_markdown.py b/tests/bundle/test_markdown.py index eac761c..ff05bae 100644 --- a/tests/bundle/test_markdown.py +++ b/tests/bundle/test_markdown.py @@ -3,28 +3,28 @@ from os.path import basename -def test_markdown_table(): +def test_markdown_table(tmp_path): onto_tool.main([ - 'bundle', '-v', 'output', 'tests-output/bundle', + 'bundle', '-v', 'output', f'{tmp_path}', 'tests/bundle/markdown.yaml' ]) - html_text = open('tests-output/bundle/Table.html').read() + html_text = open(f'{tmp_path}/Table.html').read() assert ' . +@prefix rdfs: . +@prefix xsd: . +@prefix owl: . +@prefix : . + +:DomainOntGraph { + :Domain a owl:Ontology; owl:imports :Upper; rdfs:label "Domain Ontology" . + + :Student a owl:Class ; + rdfs:subClassOf :Person ; + rdfs:isDefinedBy :Domain ; + rdfs:label "Student" . + + :Teacher a owl:Class ; + rdfs:subClassOf :Person ; + rdfs:isDefinedBy :Domain ; + rdfs:label "Teacher" . + + :School a owl:Class ; + rdfs:isDefinedBy :Domain ; + rdfs:label "School" . + + :worksFor a owl:ObjectProperty ; + rdfs:isDefinedBy :Domain ; + rdfs:domain :Teacher ; + rdfs:range :School ; + rdfs:label "works for" . + + :teaches a owl:ObjectProperty ; + rdfs:label "teaches" . +} diff --git a/tests/graphic/instance_data.trig b/tests/graphic/instance_data.trig new file mode 100644 index 0000000..8fb45c6 --- /dev/null +++ b/tests/graphic/instance_data.trig @@ -0,0 +1,16 @@ +@prefix rdf: . +@prefix rdfs: . +@prefix xsd: . +@prefix owl: . +@prefix sh: . +@prefix : . + +:InstanceDataGraph { + :Instances a owl:Ontology; owl:imports :Domain; rdfs:label "Instance Data" . + + :_t1 a :Teacher; :worksFor :_u1; :teaches :_s1; rdfs:isDefinedBy :Instances . + :_u1 a :School; rdfs:isDefinedBy :Instances . + :_s1 a :Student; :isFriendOf :_p1; rdfs:isDefinedBy :Instances . + :_p1 a :Person; :hasPhoneNumber "123-456-7890"; rdfs:isDefinedBy :Instances . + :_h1 a sh:NodeShape ; sh:targetClass :School ; rdfs:isDefinedBy :Instances . +} diff --git a/tests/graphic/issue-116-bnode-subjects.ttl b/tests/graphic/issue-116-bnode-subjects.ttl new file mode 100644 index 0000000..c3bfc09 --- /dev/null +++ b/tests/graphic/issue-116-bnode-subjects.ttl @@ -0,0 +1,14 @@ +@prefix : . +@prefix rdf: . + +[] a :Person ; + :name "James" ; + :residesIn :_Virginia ; + . +[] a :Person ; + :name "Betty" ; + :residesIn :_Maryland ; + . + +:_Virginia a :State . +:_Maryland a :State . diff --git a/tests/graphic/issue-116-named-subjects.ttl b/tests/graphic/issue-116-named-subjects.ttl new file mode 100644 index 0000000..b5f18f0 --- /dev/null +++ b/tests/graphic/issue-116-named-subjects.ttl @@ -0,0 +1,14 @@ +@prefix : . +@prefix rdf: . + +:_James a :Person ; + :name "James" ; + :residesIn :_Virginia ; + . +:_Betty a :Person ; + :name "Betty" ; + :residesIn :_Maryland ; + . + +:_Virginia a :State . +:_Maryland a :State . diff --git a/tests/graphic/issue-116-ontology.trig b/tests/graphic/issue-116-ontology.trig new file mode 100644 index 0000000..3f244b8 --- /dev/null +++ b/tests/graphic/issue-116-ontology.trig @@ -0,0 +1,18 @@ +@prefix : . +@prefix owl: . +@prefix rdfs: . + +:OntGraph { + :SmallOnt a owl:Ontology . + + :Person a rdfs:Class . + :State a rdfs:Class . + :residesIn a owl:ObjectProperty . + :name a owl:DatatypeProperty . + + :Class1 a rdfs:Class . + :Class2 a rdfs:Class . + [] a rdfs:Class ; + rdfs:subClassOf :Class1, :Class2 ; + rdfs:label "Unnamed Class" . +} diff --git a/tests/graphic/issue-116-ontology.ttl b/tests/graphic/issue-116-ontology.ttl new file mode 100644 index 0000000..53ac746 --- /dev/null +++ b/tests/graphic/issue-116-ontology.ttl @@ -0,0 +1,23 @@ +@prefix : . +@prefix owl: . +@prefix rdfs: . + +:SmallOnt a owl:Ontology . + +:Person a rdfs:Class ; + rdfs:isDefinedBy :SmallOnt . +:State a rdfs:Class ; + rdfs:isDefinedBy :SmallOnt . +:residesIn a owl:ObjectProperty ; + rdfs:isDefinedBy :SmallOnt . +:name a owl:DatatypeProperty ; + rdfs:isDefinedBy :SmallOnt . + +:Class1 a rdfs:Class ; + rdfs:isDefinedBy :SmallOnt . +:Class2 a rdfs:Class ; + rdfs:isDefinedBy :SmallOnt . +[] a rdfs:Class ; + rdfs:isDefinedBy :SmallOnt ; + rdfs:subClassOf :Class1, :Class2 ; + rdfs:label "Unnamed Class" . diff --git a/tests/graphic/test_bnode_subject.py b/tests/graphic/test_bnode_subject.py new file mode 100644 index 0000000..ee17e0f --- /dev/null +++ b/tests/graphic/test_bnode_subject.py @@ -0,0 +1,97 @@ +import os +from onto_tool import onto_tool +import pydot +import re + + +def test_filter_bnode_subjects(tmp_path): + onto_tool.main([ + 'graphic', '--predicate-threshold', '0', '--data', + '-t', 'Blank Node Subjects Filtered', + '--no-image', + '-o', f'{tmp_path}/test_filter_bnode_subjects', + 'tests/graphic/issue-116-bnode-subjects.ttl' + ]) + assert not os.path.isfile(f'{tmp_path}/test_filter_bnode_subjects.dot') + + +def test_show_bnode_subjects(tmp_path): + onto_tool.main([ + 'graphic', '--predicate-threshold', '0', '--data', + '--show-bnode-subjects', + '-t', 'Blank Node Subjects Shown', + '--no-image', + '-o', f'{tmp_path}/test_show_bnode_subjects', + 'tests/graphic/issue-116-bnode-subjects.ttl', + 'tests/graphic/issue-116-ontology.ttl' + ]) + (instance_graph,) = pydot.graph_from_dot_file(f'{tmp_path}/test_show_bnode_subjects.dot') + edges = list(sorted((e.get_source(), e.get_destination()) for e in instance_graph.get_edges())) + assert edges == [('"http://example.org/Person"', '"http://example.org/State"')] + + +def test_show_named_subjects(tmp_path): + onto_tool.main([ + 'graphic', '--predicate-threshold', '0', '--data', + '--show-bnode-subjects', + '-t', 'Named Node Subjects', + '--no-image', + '-o', f'{tmp_path}/test_named_subjects', + 'tests/graphic/issue-116-named-subjects.ttl', + ]) + (instance_graph,) = pydot.graph_from_dot_file(f'{tmp_path}/test_named_subjects.dot') + edges = list(sorted((e.get_source(), e.get_destination()) for e in instance_graph.get_edges())) + assert edges == [('"http://example.org/Person"', '"http://example.org/State"')] + + +def test_show_ont_bnode_subjects(tmp_path): + onto_tool.main([ + 'graphic', '--predicate-threshold', '0', '--schema', + '--show-bnode-subjects', + '-t', 'Ontology Named Node Subjects', + '--no-image', + '-o', f'{tmp_path}/test_ont_bnode_subjects', + 'tests/graphic/issue-116-ontology.ttl', + ]) + (instance_graph,) = pydot.graph_from_dot_file(f'{tmp_path}/test_ont_bnode_subjects.dot') + label = instance_graph.get_nodes()[1].get("label") + assert re.sub(r"n[0-9a-fA-F]{34}", "BNODE_ID", label) == '"{issue-116-ontology.ttl\l\lSmallOnt||residesIn|name||Person\lState\lClass1\lClass2\lBNODE_ID}"' + + +def test_remote_show_ont_bnode_subjects(tmp_path, sparql_endpoint): + repo_uri = 'https://my.rdfdb.com/repo/sparql' + rdf_files = ['tests/graphic/issue-116-ontology.ttl'] + sparql_endpoint(repo_uri, rdf_files) + + print(tmp_path) + onto_tool.main([ + 'graphic', '--predicate-threshold', '0', '--schema', + '--endpoint', f'{repo_uri}', + '--show-bnode-subjects', + '-t', 'Ontology Named Node Subjects', + '--no-image', + '-o', f'{tmp_path}/test_ont_bnode_subjects' + ]) + (instance_graph,) = pydot.graph_from_dot_file(f'{tmp_path}/test_ont_bnode_subjects.dot') + label = instance_graph.get_nodes()[1].get("label") + assert re.sub(r"n[0-9a-fA-F]{34}", "BNODE_ID", label) == '"{http://example.org/SmallOnt\l\lSmallOnt||residesIn|name||Class1\lClass2\lPerson\lState\lBNODE_ID}"' + + +def test_remote_show_ont_trig(tmp_path, sparql_endpoint): + repo_uri = 'https://my.rdfdb.com/repo/sparql' + rdf_files = ['tests/graphic/issue-116-ontology.trig'] + sparql_endpoint(repo_uri, rdf_files) + + print(tmp_path) + onto_tool.main([ + 'graphic', '--predicate-threshold', '0', '--schema', + '--single-ontology-graphs', + '--endpoint', f'{repo_uri}', + '--show-bnode-subjects', + '-t', 'Ontology Named Node Subjects', + '--no-image', + '-o', f'{tmp_path}/test_ont_bnode_subjects' + ]) + (instance_graph,) = pydot.graph_from_dot_file(f'{tmp_path}/test_ont_bnode_subjects.dot') + label = instance_graph.get_nodes()[1].get("label") + assert re.sub(r"n[0-9a-fA-F]{34}", "BNODE_ID", label) == '"{http://example.org/SmallOnt\l\lSmallOnt||residesIn|name||Class1\lClass2\lPerson\lState\lBNODE_ID}"' diff --git a/tests/graphic/test_instance.py b/tests/graphic/test_instance.py index e8a9405..f4c7551 100644 --- a/tests/graphic/test_instance.py +++ b/tests/graphic/test_instance.py @@ -3,17 +3,17 @@ import pydot -def test_local_instance(): +def test_local_instance(tmp_path): onto_tool.main([ 'graphic', '--predicate-threshold', '0', '--data', '-t', 'Local Instance Data', '--no-image', - '-o', 'tests-output/graphic/test_instance', + '-o', f'{tmp_path}/test_instance', 'tests/graphic/domain_ontology.ttl', 'tests/graphic/upper_ontology.ttl', 'tests/graphic/instance_data.ttl' ]) - (instance_graph,) = pydot.graph_from_dot_file('tests-output/graphic/test_instance.dot') + (instance_graph,) = pydot.graph_from_dot_file(f'{tmp_path}/test_instance.dot') edges = list(sorted((e.get_source(), e.get_destination()) for e in instance_graph.get_edges())) assert edges == [ ('"http://example.com/Student"', '"http://example.com/Person"'), @@ -22,15 +22,38 @@ def test_local_instance(): ] -def test_multi_language(): +def test_remote_instance(tmp_path, sparql_endpoint): + repo_uri = 'https://my.rdfdb.com/repo/sparql' + rdf_files = ['tests/graphic/domain_ontology.ttl', + 'tests/graphic/upper_ontology.ttl', + 'tests/graphic/instance_data.ttl'] + sparql_endpoint(repo_uri, rdf_files) + + onto_tool.main([ + 'graphic', '--predicate-threshold', '0', '--data', + '--endpoint', f'{repo_uri}', + '-t', 'Remote Instance Data', + '--no-image', + '-o', f'{tmp_path}/test_instance' + ]) + (instance_graph,) = pydot.graph_from_dot_file(f'{tmp_path}/test_instance.dot') + edges = list(sorted((e.get_source(), e.get_destination()) for e in instance_graph.get_edges())) + assert edges == [ + ('"http://example.com/Student"', '"http://example.com/Person"'), + ('"http://example.com/Teacher"', '"http://example.com/School"'), + ('"http://example.com/Teacher"', '"http://example.com/Student"') + ] + + +def test_multi_language(tmp_path): onto_tool.main([ 'graphic', '--predicate-threshold', '0', '--data', '-t', 'Multi Language Labels', '--no-image', - '-o', 'tests-output/graphic/test_multi_lingual_en', + '-o', f'{tmp_path}/test_multi_lingual_en', 'tests/graphic/multi_language.ttl' ]) - (instance_graph,) = pydot.graph_from_dot_file('tests-output/graphic/test_multi_lingual_en.dot') + (instance_graph,) = pydot.graph_from_dot_file(f'{tmp_path}/test_multi_lingual_en.dot') edges = list(sorted((e.get_source(), e.get_label(), e.get_destination()) for e in instance_graph.get_edges())) assert edges == [ ('"http://example.com/Person"', 'likes', '"http://example.com/Dessert"') @@ -40,25 +63,25 @@ def test_multi_language(): 'graphic', '--predicate-threshold', '0', '--data', '-t', 'Multi Language Labels', '--no-image', '--label-language', 'fr', - '-o', 'tests-output/graphic/test_multi_lingual_fr', + '-o', f'{tmp_path}/test_multi_lingual_fr', 'tests/graphic/multi_language.ttl' ]) - (instance_graph,) = pydot.graph_from_dot_file('tests-output/graphic/test_multi_lingual_fr.dot') + (instance_graph,) = pydot.graph_from_dot_file(f'{tmp_path}/test_multi_lingual_fr.dot') edges = list(sorted((e.get_source(), e.get_label(), e.get_destination()) for e in instance_graph.get_edges())) assert edges == [ ('"http://example.com/Person"', 'aime', '"http://example.com/Dessert"') ] -def test_inheritance(): +def test_inheritance(tmp_path): onto_tool.main([ 'graphic', '--predicate-threshold', '0', '--data', '-t', 'Inheritance is Difficult', '--no-image', - '-o', 'tests-output/graphic/test_inheritance', + '-o', f'{tmp_path}/test_inheritance', 'tests/graphic/inheritance_hierarchy.ttl' ]) - (instance_graph,) = pydot.graph_from_dot_file('tests-output/graphic/test_inheritance.dot') + (instance_graph,) = pydot.graph_from_dot_file(f'{tmp_path}/test_inheritance.dot') edges = list(sorted((e.get_source(), e.get_label() or '', e.get_destination()) for e in instance_graph.get_edges())) assert edges == [ ('"http://example.org/Person"', 'memberOf', '"http://example.org/Organization"'), @@ -73,28 +96,28 @@ def test_inheritance(): assert 'age' not in instance_graph.get_node('"http://example.org/Student"')[0].get_label() -def test_verify_construct(caplog): +def test_verify_construct(caplog, tmp_path): onto_tool.main([ 'graphic', '--data', '-t', 'Local Instance Data', '--no-image', - '-o', 'tests-output/graphic/test_instance' + '-o', f'{tmp_path}/test_instance' ] + glob.glob('tests/graphic/*_ontology.ttl')) logs = caplog.text assert 'No data found' in logs -def test_concentration(): +def test_concentration(tmp_path): # First, with concentration onto_tool.main([ 'graphic', '--predicate-threshold', '0', '--data', '--debug', '-t', 'Looney Tunes', '--no-image', - '-o', 'tests-output/graphic/concentration', + '-o', f'{tmp_path}/concentration', 'tests/graphic/concentration.ttl' ]) - (instance_graph,) = pydot.graph_from_dot_file('tests-output/graphic/concentration.dot') + (instance_graph,) = pydot.graph_from_dot_file(f'{tmp_path}/concentration.dot') assert 1 == sum(1 for e in instance_graph.get_edges() if e.get_label() == 'playsWith') # Then, without @@ -103,24 +126,24 @@ def test_concentration(): '-t', 'Looney Tunes', '--no-image', '--link-concentrator-threshold', '0', - '-o', 'tests-output/graphic/concentration', + '-o', f'{tmp_path}/concentration', 'tests/graphic/concentration.ttl' ]) - (instance_graph,) = pydot.graph_from_dot_file('tests-output/graphic/concentration.dot') + (instance_graph,) = pydot.graph_from_dot_file(f'{tmp_path}/concentration.dot') assert 4 == sum(1 for e in instance_graph.get_edges() if e.get_label() == 'playsWith') -def test_shacl_instances(): +def test_shacl_instances(tmp_path): onto_tool.main([ 'graphic', '--predicate-threshold', '0', '--data', '-t', 'Local Instance Data', '--no-image', - '-o', 'tests-output/graphic/test_instance', + '-o', f'{tmp_path}/test_instance', 'tests/graphic/domain_ontology.ttl', 'tests/graphic/upper_ontology.ttl', 'tests/graphic/instance_data.ttl' ]) - (instance_graph,) = pydot.graph_from_dot_file('tests-output/graphic/test_instance.dot') + (instance_graph,) = pydot.graph_from_dot_file(f'{tmp_path}/test_instance.dot') edges = list(sorted((e.get_source(), e.get_destination()) for e in instance_graph.get_edges())) shacl_namespace = "http://www.w3.org/ns/shacl#" shacl_edges = [edge for edge in edges if any(shacl_namespace in part for part in edge)] diff --git a/tests/graphic/test_schema.py b/tests/graphic/test_schema.py index 9f50428..068b4f3 100644 --- a/tests/graphic/test_schema.py +++ b/tests/graphic/test_schema.py @@ -2,17 +2,61 @@ import pydot -def test_local_instance(): +def test_local_instance(tmp_path): onto_tool.main([ 'graphic', '-t', 'Local Ontology', '--no-image', - '-o', 'tests-output/graphic/test_schema', + '-o', f'{tmp_path}/test_schema', 'tests/graphic/domain_ontology.ttl', 'tests/graphic/upper_ontology.ttl', 'tests/graphic/instance_data.ttl' ]) - (instance_graph,) = pydot.graph_from_dot_file('tests-output/graphic/test_schema.dot') + (instance_graph,) = pydot.graph_from_dot_file(f'{tmp_path}/test_schema.dot') + edges = list(sorted((e.get_source(), e.get_destination()) for e in instance_graph.get_edges())) + assert edges == [ + ('Domain', 'Upper'), + ('Instances', 'Domain') + ] + + +def test_remote_instance(tmp_path, sparql_endpoint): + repo_uri = 'https://my.rdfdb.com/repo/sparql' + rdf_files = ['tests/graphic/domain_ontology.ttl', + 'tests/graphic/upper_ontology.ttl', + 'tests/graphic/instance_data.ttl'] + sparql_endpoint(repo_uri, rdf_files) + + onto_tool.main([ + 'graphic', + '--endpoint', f'{repo_uri}', + '-t', 'Remote Ontology', + '--no-image', + '-o', f'{tmp_path}/test_schema' + ]) + (instance_graph,) = pydot.graph_from_dot_file(f'{tmp_path}/test_schema.dot') + edges = list(sorted((e.get_source(), e.get_destination()) for e in instance_graph.get_edges())) + assert edges == [ + ('Domain', 'Upper'), + ('Instances', 'Domain') + ] + + +def test_remote_graphs(tmp_path, sparql_endpoint): + repo_uri = 'https://my.rdfdb.com/repo/sparql' + rdf_files = ['tests/graphic/domain_ontology.trig', + 'tests/graphic/upper_ontology.trig', + 'tests/graphic/instance_data.trig'] + sparql_endpoint(repo_uri, rdf_files) + + onto_tool.main([ + 'graphic', '--single-ontology-graphs', + '--endpoint', f'{repo_uri}', + '-t', 'Remote Ontology', + '--no-image', + '-o', f'{tmp_path}/test_schema' + ]) + (instance_graph,) = pydot.graph_from_dot_file(f'{tmp_path}/test_schema.dot') edges = list(sorted((e.get_source(), e.get_destination()) for e in instance_graph.get_edges())) assert edges == [ ('Domain', 'Upper'), diff --git a/tests/graphic/upper_ontology.trig b/tests/graphic/upper_ontology.trig new file mode 100644 index 0000000..f9d4d96 --- /dev/null +++ b/tests/graphic/upper_ontology.trig @@ -0,0 +1,29 @@ +@prefix rdf: . +@prefix rdfs: . +@prefix xsd: . +@prefix owl: . +@prefix : . + +:UpperOntGraph { + :Upper a owl:Ontology; rdfs:label "Upper Ontology" . + + :Person a owl:Class ; + rdfs:isDefinedBy :Upper ; + rdfs:label "Person" . + + :isPrivate a owl:AnnotationProperty ; + rdfs:isDefinedBy :Upper ; + rdfs:range xsd:boolean ; + rdfs:label "is private" . + + :hasPhoneNumber a owl:DatatypeProperty ; + rdfs:isDefinedBy :Upper ; + rdfs:range xsd:string ; + rdfs:label "has phone number" . + + :isFriendOf a owl:ObjectProperty ; + rdfs:isDefinedBy :Upper ; + rdfs:domain :Person ; + rdfs:range :Person ; + rdfs:label "is friend of" . +}