def get_datatype_constraints(onto_class_uri, triples): ''' Build and return a DatatypeConstraints object for the specified list of triples Arguments: onto_class_uri An rdflib.term.URIRef for the source ontology class triples A list of triples from an ontospy.core.entities.OntoClass (e.g. onto_class.triples) Return: A tuple (DatatypeConstraints, LIST of ErrorMessage objects) ''' # Convert list of triples to {subject:{predicate:{objects}}} spo_dict = get_spo_dict(triples) # Get parent_po_dict. It's the only one whose key is not a BNode non_bnode_keys = [ key for key in spo_dict.keys() if not isinstance(key, rdflib.term.BNode) ] if not non_bnode_keys: errmsg = OntologyError(message='No parent node in triples: {}'.format( pprint.pformat(triples)), onto_class_uri=onto_class_uri) return None, [errmsg] elif len(non_bnode_keys) > 1: errmsg = OntologyError( message='Multiple parent nodes in triples: {}'.format( pprint.pformat(triples)), onto_class_uri=onto_class_uri) return None, [errmsg] else: parent_po_dict = spo_dict[non_bnode_keys[0]] # Proceed depending on database constraint type # Case 1: List of strings if parent_po_dict.get(RDFS.subClassOf) == { RDFS.Resource } and parent_po_dict.get(OWL.oneOf): try: datatype_constraints = VocabularyDatatypeConstraints( onto_class_uri, spo_dict) errmsgs = [] except DatatypeError as exc: datatype_constraints = None errmsgs = exc.error_messages # Case 2: TBD # Otherwise else: datatype_constraints = None errmsg = UnsupportedFeature( message='Cannot figure out datatype for these triples:\n{}'.format( pprint.pformat(triples)), onto_class_uri=onto_class_uri) errmsgs = [errmsg] # Return results return datatype_constraints, errmsgs
def _get_property_ranges(ontospy_property_ranges, ontospy_property_triples, context): ''' This function belongs in the Ontology class. It is implemented outside the Ontology class to facilitate unit testing Compute the range for all properties in the ontology. Add error message for each property with no or multiple ranges, and omit those from the returned property_ranges Arguments: ontospy_property_ranges {property_uri:[range_uris]} ontospy_property_triples {property_uri:[triples]} context Ontology Context object Return: property_ranges {property_uri:range_uri|None} errmsgs List of error messages ''' # Start with empty list of error messages error_messages = [] property_ranges = {} # Do for all properties in ontospy.all_properties for property_uri, range_uris in ontospy_property_ranges.items(): property_ranges[property_uri] = None # If ontospy thinks there are no ranges for this property... if not range_uris: # If the property has a triple with predicate RDFS.range, this is an unsupported feature if RDFS.range in [ pred for subj, pred, obj in ontospy_property_triples[property_uri] ]: error_messages.append( UnsupportedFeature( message='Property has unsupported property type', property_uri=property_uri)) else: error_messages.append( OntologyError(message='Property has no ranges', property_uri=property_uri)) continue # If ontospy thinks there multiple ranges for this property, error message if len(range_uris) > 1: error_messages.append( OntologyError(message='Property has {} ranges {}'.format( len(range_uris), context.format(range_uris)), property_uri=property_uri)) continue # If we are here, there is one range. Add it to return value property_ranges[property_uri] = range_uris[0] # Return property_ranges and error messages return property_ranges, error_messages
def _get_ontology_error(self, message): ''' Arguments: message A message string describing some kind of error condition Return: An OntologyError object using self.onto_class_uri and self.property_uri with the message ''' return OntologyError(message='constraint violation: ' + message, onto_class_uri=self.onto_class_uri, property_uri=self.property_uri)
def __init__(self, onto_class_uri, spo_dict): ''' Create and initialize an instance of this class Arguments: onto_class_uri The ontology class that is this Datatype spo_dict Ontology {subject:{predicate:{value})} Raise: DatatypeError([ErrorMessage]): an Exception containing a LIST of ErrorMessages Algorithm: Naively assume the BNodes form a simple linked list, where the object of every RDF.first predicate is a Literal. You don't have to follow the links in order, just catch all the RDF.firsts. ''' super().__init__(onto_class_uri) errmsgs = [] self.vocabulary = set() for subject, po_dict in spo_dict.items(): if not isinstance(subject, rdflib.term.BNode): continue literals = po_dict.get(RDF.first) if len(literals) > 1: errmsgs.append( OntologyError( message= 'Multiple values for RDF.first in linked list: {}'. format(literals), onto_class_uri=onto_class_uri)) elif literals: self.vocabulary.update([str(literal) for literal in literals ]) # nondestructive if errmsgs: raise DatatypeError(errmsgs)
def get_class_constraints(onto_class_uri, triples): ''' Build and return a ClassConstraints object for the specified list of triples Arguments: onto_class_uri A rdflib.term.URIRef of the source ontology class (used ONLY for building messages) triples A list of triples from an ontospy.core.entities.OntoClass (e.g. onto_class.triples) Return: A tuple (ClassConstraints object, LIST of ErrorMessage objects) ''' # Create empty ClassConstraints object and empty list of ErrorMessages class_constraints = ClassConstraints(onto_class_uri) error_messages = [] # Convert list of triples to {subject:{predicate:{objects}}} spo_dict = get_spo_dict(triples) # Do for each node... for subject, po_dict in spo_dict.items(): # Skip this node if it is the parent node if not isinstance(subject, rdflib.term.BNode): continue # If we're here, then subject is a BNode # Remove and report predicates with multiple objects for this BNode # Sets of a single object by that single object. for predicate in set(po_dict.keys()): objects = po_dict[predicate] if len(objects) > 1: error_messages.append(OntologyError( message = 'conflicting specification of {} as {}'.format(predicate, objects), onto_class_uri = onto_class_uri, property_uri = po_dict.get(OWL.onProperty))) po_dict.pop(predicate) # remove predicate and its multiple object from dictionary else: po_dict[predicate] = objects.pop() # replace set of one object by the one object # Now po_dict maps predicates to single objects: {predicate:object} # Get RDF.type and onProperty for this BNode constraints_type = po_dict.get(RDF.type) property_uri = po_dict.get(OWL.onProperty) # Skip this BNode if its RDF.type is not OWL.Restriction if constraints_type != OWL.Restriction: error_messages.append(UnsupportedFeature( onto_class_uri = onto_class_uri, property_uri = property_uri, message='unsupported constraints type {}'.format(constraints_type))) continue # Skip this BNode if its OWL.onProperty is missing if not property_uri: error_messages.append(OntologyError( onto_class_uri = onto_class_uri, message='constraints has no property value')) continue # Create empty PropertyConstraints object and attach to property in Constraints object property_constraints = PropertyConstraints(onto_class_uri, property_uri) class_constraints.set_property_constraints(property_uri, property_constraints) # Do for each predicate for predicate, obj in po_dict.items(): # Skip RDF.type and OWL.onProperty because we already accounted for them if predicate in (RDF.type, OWL.onProperty): continue # Populate property_constraints depending on the predicate and its value if predicate == OWL.onDataRange: error_messages.extend(property_constraints.add_value_range(obj)) elif predicate == OWL.onClass: error_messages.extend(property_constraints.add_value_range(obj)) elif predicate == OWL.minCardinality: error_messages.extend(property_constraints.add_min_cardinality(int(obj.value))) elif predicate == OWL.maxCardinality: error_messages.extend(property_constraints.add_max_cardinality(int(obj.value))) elif predicate == OWL.cardinality: error_messages.extend(property_constraints.add_cardinality(int(obj.value))) elif predicate == OWL.minQualifiedCardinality: error_messages.extend(property_constraints.add_qualified_min_cardinality(int(obj.value))) elif predicate == OWL.maxQualifiedCardinality: error_messages.extend(property_constraints.add_qualified_max_cardinality(int(obj.value))) elif predicate == OWL.qualifiedCardinality: error_messages.extend(property_constraints.add_qualified_cardinality(int(obj.value))) else: error_messages.append(UnsupportedFeature( message = 'unsupported predicate {}'.format(predicate), onto_class_uri = onto_class_uri, property_uri = property_uri)) # Check property constraints for consistency error_messages.extend(property_constraints.check_consistency()) # Return the Constraints object and the ErrorMessages return class_constraints, error_messages
def _check_range_consistency(all_constraints, property_ranges, context): ''' This function belongs in the Ontology class. It is implemented outside the Ontology class to facilitate unit testing Make sure the property range constraints are consistent with the property ranges directly specified in the *-da files. Arguments: all_constraints {URIRef(onto_class):ClassConstraints|DatatypeConstraints|None} (ontology.constraints) property_ranges {property_uri:range_uri|None} (derived from ontospy.all_properties) context Ontology Context object Return: List of ErrorMessage objects ''' # Start with empty list of error messages error_messages = [] # Do for all owl property constraints for onto_class_uri, constraints in all_constraints.items(): # We are only checking ClassConstraints for now. if not isinstance(constraints, ClassConstraints): #error_messages.append(UnsupportedFeature( # message='range consistency check not implemented for {}'.format(type(constraints)), # onto_class_uri=onto_class_uri)) continue # For each property constraint in the class constrain... class_constraints = constraints for property_uri, property_constraints in class_constraints.property_constraints_dict.items( ): # If property has no range constraint, nothing to check if property_constraints.value_range is None: continue # Make sure property is in onto_property_ranges if not property_uri in property_ranges: error_messages.append( OntologyError( message='property {} missing from ontology property list' .format(context.format(property_uri)), onto_class_uri=onto_class_uri, property_uri=property_uri)) continue # Get ontospy's opinion of this property's range constraint range_uri = property_ranges.get(property_uri) # could be None # Compare the owl property constraint with ontospy's opinion if range_uri and range_uri != property_constraints.value_range: error_messages.append( OntologyError( message= 'owl subclass constraint {} does not match property constraint {}' .format( context.format(property_constraints.value_range), context.format(range_uri)), onto_class_uri=onto_class_uri, property_uri=property_uri)) return error_messages