"""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),
)