I can't give a formal proof, but this looks impossible. This is not what property paths were designed for, and why some extensions exist (1, 2).
Under certain commitments (e.g. with tree-like structures), it is possible to figure out something using FILTER NOT EXISTS, however, this is not a general solution.
The idea is to do this in two queries. In essence, this is SELECT over CONSTRUCT. By the way, such SPARQL extension has already been proposed.
Let's use rdflib on which Ontospy is based, because
Ontospy does not offer any ontology-editing features, nor it can be used to interrogate a triplestore.
Input (ontology.ttl)
@prefix : <http://www.example.org/ontology#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@base <http://www.example.org/ontology> .
<http://www.example.org/ontology> rdf:type owl:Ontology .
:hasPart rdf:type owl:ObjectProperty .
:Country rdf:type owl:Class ;
rdfs:subClassOf [ rdf:type owl:Restriction ;
owl:onProperty :hasPart ;
owl:someValuesFrom :State
] .
:State rdf:type owl:Class ;
rdfs:subClassOf [ rdf:type owl:Restriction ;
owl:onProperty :hasPart ;
owl:someValuesFrom :City
] .
:City rdf:type owl:Class .
Python code
import rdflib
g = rdflib.Graph()
g.parse("ontology.ttl", format="n3")
qres = g.update(
"""PREFIX : <http://www.example.org/ontology#>
INSERT { ?c :hasSome ?p }
WHERE { ?c rdfs:subClassOf [ owl:onProperty :hasPart ;
owl:someValuesFrom ?p ] }""")
qres = g.query(
"""PREFIX : <http://www.example.org/ontology#>
SELECT ?a ?b WHERE {?a :hasSome+ ?b }""")
for row in qres:
print("%s :hasSome+ %s" % row)
qres = g.update(
"""PREFIX : <http://www.example.org/ontology#>
DELETE { ?s :hasSome ?o } WHERE { ?s :hasSome ?o }""")
Output
:Country :hasSome+ :State
:State :hasSome+ :City
:Country :hasSome+ :City
If you don't want to modify initial RDFLib graph, just create another one:
import rdflib
g1 = rdflib.Graph()
g1.parse("ontology.ttl", format="n3")
qres = g1.query(
"""PREFIX : <http://www.example.org/ontology#>
CONSTRUCT {?c :hasSome ?p } WHERE {
?c rdfs:subClassOf [ owl:onProperty :hasPart ;
owl:someValuesFrom ?p ] }""")
g2 = rdflib.Graph();
for triple in qres: # quite a few triples
g2.add(triple)
qres = g2.query(
"""PREFIX : <http://www.example.org/ontology#>
SELECT ?a ?b WHERE { ?a :hasSome+ ?b }""")
for row in qres:
print("%s :hasSome+ %s" % row)
Probably you can use transitiveClosure() or transitive_objects() instead of the second query in both cases.