Source code for pyobo.struct.functional

"""Converters from OBO to functional OWL."""

from __future__ import annotations

from collections.abc import Iterable
from typing import TYPE_CHECKING

import curies
import rdflib
from curies import vocabulary as v
from functional_owl import Document, Ontology
from functional_owl import dsl as f
from functional_owl import macros as m
from rdflib import XSD

from . import vocabulary as pv
from .reference import OBOLiteral, Reference, _parse_datetime
from .struct import get_iris

if TYPE_CHECKING:
    from .reference import Referenced
    from .struct import Obo, Term, TypeDef
    from .struct_utils import Annotation as OBOAnnotation
    from .struct_utils import Stanza

__all__ = [
    "get_ofn_from_obo",
    "get_ontology_annotations",
    "get_ontology_axioms",
    "get_term_axioms",
    "get_typedef_axioms",
]


[docs] def get_ofn_from_obo( obo_ontology: Obo, *, iri: str | None = None, version_iri: str | None = None ) -> Document: """Convert an ontology.""" iri, version_iri = get_iris(obo_ontology, iri=iri, version_iri=version_iri, extension="ofn") ofn_ontology = Ontology( iri=iri, version_iri=version_iri, annotations=list(get_ontology_annotations(obo_ontology)), axioms=list(get_ontology_axioms(obo_ontology)), ) document = Document( ofn_ontology, obo_ontology._get_clean_idspaces(), ) return document
[docs] def get_ontology_axioms(obo_ontology: Obo) -> Iterable[f.Box]: """Get axioms from the ontology.""" if obo_ontology.root_terms: yield f.Declaration(v.has_ontology_root_term, type="AnnotationProperty") yield m.LabelMacro(v.has_ontology_root_term, v.has_ontology_root_term.name) if obo_ontology.subsetdefs: yield f.Declaration("oboInOwl:SubsetProperty", type="AnnotationProperty") for subset_typedef, subset_label in obo_ontology.subsetdefs.items(): yield f.Declaration(subset_typedef, type="AnnotationProperty") yield m.LabelMacro(subset_typedef, subset_label) yield f.SubAnnotationPropertyOf(subset_typedef, "oboInOwl:SubsetProperty") if obo_ontology.synonym_typedefs: used_has_scope = False for synonym_typedef in obo_ontology.synonym_typedefs: yield f.Declaration(synonym_typedef.reference, type="AnnotationProperty") if synonym_typedef.name is not None: yield m.LabelMacro(synonym_typedef.reference, synonym_typedef.name) yield f.SubAnnotationPropertyOf( synonym_typedef.reference, "oboInOwl:SynonymTypeProperty" ) if synonym_typedef.specificity: used_has_scope = True yield f.AnnotationAssertion( "oboInOwl:hasScope", synonym_typedef.reference, v.synonym_scopes[synonym_typedef.specificity], ) if used_has_scope: yield f.Declaration("oboInOwl:hasScope", type="AnnotationProperty") for typedef in obo_ontology.typedefs or []: yield from get_typedef_axioms(typedef) for term in obo_ontology: yield from get_term_axioms(term)
[docs] def get_ontology_annotations(obo_ontology: Obo) -> Iterable[f.Annotation]: """Get annotations from the ontology.""" for annotation in obo_ontology._iterate_property_pairs(): yield _convert_annotation(annotation) if obo_ontology.data_version: yield f.Annotation( v.owl_version_info, rdflib.Literal(obo_ontology.data_version, datatype=XSD.string) ) if obo_ontology.auto_generated_by is not None: yield f.Annotation( v.obo_autogenerated_by, rdflib.Literal(obo_ontology.auto_generated_by, datatype=XSD.string), )
def _oboliteral_to_literal(obo_literal: OBOLiteral) -> rdflib.Literal: if obo_literal.datatype.pair == ("xsd", "string") and obo_literal.language: return rdflib.Literal(obo_literal.value, lang=obo_literal.language) if obo_literal.datatype.prefix != "xsd": raise NotImplementedError( f"Automatic literal conversion is not implemented for prefix: {obo_literal.datatype.prefix}" ) if obo_literal.datatype.identifier == "dateTime": return rdflib.Literal(_parse_datetime(obo_literal.value), datatype=XSD.dateTime) return rdflib.Literal(obo_literal.value, datatype=XSD._NS.term(obo_literal.datatype.identifier))
[docs] def get_term_axioms(term: Term) -> Iterable[f.Box]: """Iterate over functional OWL axioms for a term.""" s = f.IdentifierBox(term.reference) # 1 and 13 if term.type == "Term": yield f.Declaration(s, type="Class") for parent in term.parents: yield f.SubClassOf(s, parent, annotations=_get_annotations(term, v.is_a, parent)) elif term.type == "Instance": yield f.Declaration(s, type="NamedIndividual") for parent in term.parents: yield f.ClassAssertion( parent, s, annotations=_get_annotations(term, v.rdf_type, parent) ) else: raise ValueError(f"invalid term type: {term.type}") # 2 if term.is_anonymous is not None: yield m.IsAnonymousMacro(s, term.is_anonymous) # 3 if term.name: yield m.LabelMacro(s, term.name) # 4 if term.namespace: yield m.OBONamespaceMacro(s, term.namespace) # 5 for alt in term.alt_ids: yield m.ReplacedByMacro(alt, s) # 6 yield from _yield_definition(term, s) # 7 comment is covered by properties # 8 for subset in term.subsets: yield m.OBOIsSubsetMacro(s, subset) # 9 yield from _yield_synonyms(term, s) # 10 yield from _yield_xrefs(term, s) # 11 if term.builtin is not None: yield m.IsOBOBuiltinMacro(s, term.builtin) # 12 yield from _yield_properties(term, s) # 13 parents - see top # 14 if term.intersection_of: yield m.ClassIntersectionMacro(s, term.intersection_of) # 15 if term.union_of: yield m.ClassUnionMacro(s, term.union_of) # 16 if term.equivalent_to: yield f.EquivalentClasses([s, *term.equivalent_to]) # 17 if term.disjoint_from: yield f.DisjointClasses([s, *term.disjoint_from]) # 18 for typedef, value in term.iterate_relations(): rel_annotations = _get_annotations(term, typedef, value) if term.type == "Term": yield m.RelationshipMacro(s=s, p=typedef, o=value, annotations=rel_annotations) else: yield f.ObjectPropertyAssertion(typedef, s, value, annotations=rel_annotations) # 19 TODO created_by # 20 TODO creation_date # 21 if term.is_obsolete is not None: yield m.IsObsoleteMacro(s, term.is_obsolete)
# 22 replaced_by is covered by properties # 23 consider is covered by properties def _get_annotations( term: Stanza, p: curies.Reference | Referenced, o: Reference | Referenced | OBOLiteral | str ) -> list[f.Annotation]: return _process_anns(term._get_annotations(p, o)) def _process_anns(annotations: list[OBOAnnotation]) -> list[f.Annotation]: """Convert OBO annotations to OFN annotations.""" return [_convert_annotation(a) for a in annotations] def _convert_annotation(annotation: OBOAnnotation) -> f.Annotation: """Convert OBO annotations to OFN annotations.""" return f.Annotation(annotation.predicate, _convert_literal_or_reference(annotation.value)) def _convert_literal_or_reference( value: OBOLiteral | curies.Reference, ) -> rdflib.Literal | curies.Reference: match value: case OBOLiteral(): return _oboliteral_to_literal(value) case curies.Reference(): return value
[docs] def get_typedef_axioms(typedef: TypeDef) -> Iterable[f.Box]: """Iterate over functional OWL axioms for a typedef.""" r = f.IdentifierBox(typedef.reference) # 40 if typedef.is_metadata_tag: yield f.Declaration(r, type="AnnotationProperty") else: yield f.Declaration(r, type="ObjectProperty") # 2 if typedef.is_anonymous is not None: yield m.IsAnonymousMacro(r, typedef.is_anonymous) # 3 if typedef.name: yield m.LabelMacro(r, typedef.name) # 4 if typedef.namespace: yield m.OBONamespaceMacro(r, typedef.namespace) # 5 the way this one works is that all alts get a term-replaced-by, # as well as getting their own deprecation axioms for alt_id in typedef.alt_ids: yield m.ReplacedByMacro(alt_id, r) # 6 yield from _yield_definition(typedef, r) # 7 if typedef.comment: yield m.CommentMacro(r, typedef.comment) # 8 for subset in typedef.subsets: yield m.OBOIsSubsetMacro(r, subset) # 9 yield from _yield_synonyms(typedef, r) # 10 yield from _yield_xrefs(typedef, r) # 11 yield from _yield_properties(typedef, r) # 12 if typedef.domain: if typedef.is_metadata_tag: yield f.AnnotationPropertyDomain(r, typedef.domain) else: yield f.ObjectPropertyDomain(r, typedef.domain) # 13 if typedef.range: if typedef.is_metadata_tag: yield f.AnnotationPropertyRange(r, typedef.range) else: yield f.ObjectPropertyRange(r, typedef.range) # 14 if typedef.builtin is not None: yield m.IsOBOBuiltinMacro(r, typedef.builtin) # 15 for chain in typedef.holds_over_chain: yield m.HoldsOverChain(r, chain) # 16 if typedef.is_anti_symmetric: yield f.AsymmetricObjectProperty(r) # 17 if typedef.is_cyclic is not None: yield m.IsCyclic(r, typedef.is_cyclic) # 18 if typedef.is_reflexive: yield f.ReflexiveObjectProperty(r) # 19 if typedef.is_symmetric: yield f.SymmetricObjectProperty(r) # 20 if typedef.is_transitive: yield f.TransitiveObjectProperty(r) # 21 if typedef.is_functional: yield f.FunctionalObjectProperty(r) # 22 if typedef.is_inverse_functional: yield f.InverseFunctionalObjectProperty(r) # 23 for parent in typedef.parents: if typedef.is_metadata_tag: yield f.SubAnnotationPropertyOf(r, parent) else: yield f.SubObjectPropertyOf(r, parent) # 24 TODO intersection_of, ROBOT does not create any output # 25 TODO union_of, ROBOT does not create any output # 26 if typedef.equivalent_to: yield f.EquivalentObjectProperties([r, *typedef.equivalent_to]) # 27 for x in typedef.disjoint_from: yield f.DisjointObjectProperties([x, r]) # 28 if typedef.inverse: if typedef.is_metadata_tag: pass # not sure what to do else: yield f.InverseObjectProperties(r, typedef.inverse) # 29 for to in typedef.transitive_over: yield m.TransitiveOver(r, to) # 30 for chain in typedef.equivalent_to_chain: yield f.SubObjectPropertyOf(f.ObjectPropertyChain(chain), r) # 31 TODO disjoint_over, ROBOT does not create any output # 32 TODO relationship, ROBOT does not create any output # 33 if typedef.is_obsolete is not None: yield m.IsObsoleteMacro(r, typedef.is_obsolete) # 34 TODO created_by # 35 TODO creation_date # 36 for rep in typedef.get_replaced_by(): yield m.ReplacedByMacro(rep, r) # 37 for ref in typedef.get_see_also(): yield m.OBOConsiderMacro(r, ref) # 38 TODO expand_assertion_to # 39 TODO expand_expression_to # 41 if typedef.is_class_level is not None: yield m.OBOIsClassLevelMacro(r, typedef.is_class_level)
def _yield_definition(term: Stanza, s: f.IdentifierBox) -> Iterable[m.DescriptionMacro]: if term.definition: yield m.DescriptionMacro( s, term.definition, annotations=_get_annotations(term, v.has_description, term.definition), ) def _yield_synonyms(stanza: Stanza, r: f.IdentifierBox) -> Iterable[m.SynonymMacro]: for synonym in stanza.synonyms: yield m.SynonymMacro( r, synonym.name, scope=synonym.specificity, synonym_type=synonym.type, provenance=[_convert_literal_or_reference(value) for value in synonym.provenance], annotations=_process_anns(synonym.annotations), language=synonym.language, ) def _yield_xrefs(term: Stanza, s: f.IdentifierBox) -> Iterable[m.XrefMacro]: for xref in term.xrefs: yield m.XrefMacro(s, xref, annotations=_get_annotations(term, v.has_dbxref, xref)) _SKIP = { # we skip alt terms since OFN # prefers to flip the triple and use term-replaced-by instead pv.alternative_term, } def _yield_properties(term: Stanza, s: f.IdentifierBox) -> Iterable[f.AnnotationAssertion]: for typedef, values in term.properties.items(): if typedef in _SKIP: continue for value in values: yield f.AnnotationAssertion( typedef, s, _convert_literal_or_reference(value), annotations=_get_annotations(term, typedef, value), )