Source code for simphony_osp.tools.pretty_print

"""Utility functions for printing ontology entities objects in a nice way."""

import sys
from functools import reduce
from operator import add
from typing import Iterable, Optional, Set, Union

from simphony_osp.namespaces import owl
from simphony_osp.ontology.attribute import OntologyAttribute
from simphony_osp.ontology.composition import Composition
from simphony_osp.ontology.entity import OntologyEntity
from simphony_osp.ontology.individual import OntologyIndividual
from simphony_osp.ontology.oclass import OntologyClass
from simphony_osp.ontology.relationship import OntologyRelationship
from simphony_osp.ontology.restriction import Restriction


[docs]def pretty_print( entity: OntologyEntity, rel: Union[ OntologyRelationship, Iterable[OntologyRelationship] ] = owl.topObjectProperty, file=sys.stdout, ): """Print a tree-like, text representation stemming from an individual. Generates a tree-like, text-based representation stemming from a given ontology individual, that includes the IRI, ontology classes and attributes of the involved individuals, as well as the relationships connecting them. Args: entity: Ontology individual to be used as starting point of the text-based representation. file: A file to print the text to. Defaults to the standard output. rel: Restrict the relationships to consider when searching for attached individuals to subclasses of the given relationships. """ # Fix the order of each element by pre-populating a dictionary. pp = { x: "" for x in ( "title", "identifier", "classes", "superclasses", "attributes", "subelements", ) } # Title pp["title"] = f"{_pp_entity_name(entity)}:" # Superclasses superclasses = ( { superclass for class_ in entity.classes for superclass in class_.superclasses } if isinstance(entity, OntologyIndividual) else set(entity.superclasses) ) labels = _pp_list_of_labels_or_uids(superclasses, namespace=True) pp["superclasses"] = "\n superclasses: " + labels if isinstance(entity, OntologyIndividual): # Classes classes = set(entity.classes) labels = _pp_list_of_labels_or_uids(classes, namespace=True) pp["classes"] = f"\n type{'s' if len(classes) > 1 else ''}: {labels}" # Attribute values values_str = _pp_individual_values(entity) if values_str: pp["attributes"] = f"\n values: {values_str}" # Subelements pp["subelements"] = _pp_individual_subelements(entity, rel) pp["identifier"] = f"\n identifier: {entity.uid}" pp = reduce(add, pp.values()) print(pp, file=file)
def _pp_entity_name(entity: OntologyEntity, type_: bool = False): """Return the name of the given element following the pretty print format. Args: entity: element to be printed. Returns: String with the pretty printed text. """ type_names = { OntologyIndividual: "Ontology individual", OntologyClass: "Ontology class", OntologyRelationship: "Ontology relationship", OntologyAttribute: "Ontology attribute", Composition: "Composition", Restriction: "Restriction", } type_name = next( ( name for type_, name in type_names.items() if isinstance(entity, type_) ), "Ontology entity", ) title = "- %s" % type_name if type_ is True and isinstance(entity, OntologyIndividual): classes = entity.classes title += ( f" of class{'es' if len(classes) > 1 else ''} " f"{','.join(str(x) for x in classes)}" ) label = entity.label if label is not None: title += f" named {label}" # else: # title += f" {entity.uid}" return title def _pp_individual_subelements( individual: OntologyIndividual, rel: Union[ OntologyRelationship, Iterable[OntologyRelationship] ] = owl.topObjectProperty, level_indentation: str = "\n ", visited: Optional[Set] = None, ) -> str: """Recursively formats the subelements from an individual. The objects are grouped by ontology class. Args: individual: element to inspect. level_indentation: common characters to left-pad the text. Returns: String with the pretty printed text """ pp_sub = "" # Find relationships connecting the individual to its subelements. consider_relationships = set() if isinstance(rel, OntologyRelationship): rels = {rel} else: rels = rel for predicate in individual.session.graph.predicates( individual.identifier, None ): try: relationship = individual.session.ontology.from_identifier( predicate ) if isinstance(relationship, OntologyRelationship): consider_relationships |= {relationship} except KeyError: pass filtered_relationships = filter( lambda x: any(x.is_subclass_of(r) for r in rels), consider_relationships, ) sorted_relationships = sorted(filtered_relationships, key=str) visited = visited or set() visited.add(individual.uid) for i, relationship in enumerate(sorted_relationships): relationship_name = _pp_list_of_labels_or_uids({relationship}) pp_sub += level_indentation + ( " |_Relationship %s (%s):" % (relationship_name, relationship.namespace.name) ) sorted_elements = sorted( individual.iter(rel=relationship, return_rel=True), key=lambda x: ( "\0" + str( sorted( class_.label if class_.label is not None else class_.identifier for class_ in x[0].classes )[0] ), f"\0{x[1].label}" if x[1].label is not None else f"{x[1].identifier}", f"\0{x[0].label}" if x[0].label is not None else f"{x[0].uid}", ), ) for j, (element, rel) in enumerate(sorted_elements): if rel != relationship: continue if i == len(sorted_relationships) - 1: indentation = level_indentation + " " else: indentation = level_indentation + " | " pp_sub += indentation + _pp_entity_name(element, type_=True) if j == len(sorted_elements) - 1: indentation += " " else: indentation += " . " identifier_str = f"identifier: {element.uid}" pp_sub += indentation + identifier_str if element.uid in visited: pp_sub += indentation + "(already printed)" continue values_str = _pp_individual_values(element, indentation) if values_str: pp_sub += indentation + values_str pp_sub += _pp_individual_subelements( element, rels, indentation, visited ) return pp_sub def _pp_individual_values(cuds_object, indentation="\n "): r"""Print the attributes of a cuds object. Args: cuds_object (Cuds): Print the values of this cuds object. indentation (str): The indentation to prepend, defaults to "\n " Returns: str: The resulting string to print. """ result = [] sorted_attributes = sorted( cuds_object.attributes.items(), key=lambda x: ( f"\0{x[0].label}" if x[0].label is not None else f"{x[0].identifier}", str(x[1]), ), ) for attribute, value in sorted_attributes: result.append( "%s: %s" % ( f"\0{attribute.label}" if attribute.label is not None else f"{attribute.identifier}", value if not len(value) == 1 else next(iter(value)), ) ) if result: return indentation.join(result) def _pp_list_of_labels_or_uids( entities: Iterable[OntologyEntity], namespace: bool = False ) -> str: entities = set(entities) if namespace: labels = ( f"{entity.label} ({entity.namespace.name})" for entity in entities ) else: labels = (entity.label for entity in entities) labels = ( label if label is not None else str(entity.uid) for label, entity in zip(labels, entities) ) labels = ", ".join(labels) return labels