pyobo 0.11.2__py3-none-any.whl → 0.12.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. pyobo/.DS_Store +0 -0
  2. pyobo/__init__.py +95 -20
  3. pyobo/__main__.py +0 -0
  4. pyobo/api/__init__.py +81 -10
  5. pyobo/api/alts.py +52 -42
  6. pyobo/api/combine.py +39 -0
  7. pyobo/api/edges.py +68 -0
  8. pyobo/api/hierarchy.py +231 -203
  9. pyobo/api/metadata.py +14 -19
  10. pyobo/api/names.py +207 -127
  11. pyobo/api/properties.py +117 -117
  12. pyobo/api/relations.py +68 -94
  13. pyobo/api/species.py +24 -21
  14. pyobo/api/typedefs.py +11 -11
  15. pyobo/api/utils.py +66 -13
  16. pyobo/api/xrefs.py +107 -114
  17. pyobo/cli/__init__.py +0 -0
  18. pyobo/cli/cli.py +35 -50
  19. pyobo/cli/database.py +210 -160
  20. pyobo/cli/database_utils.py +155 -0
  21. pyobo/cli/lookup.py +163 -195
  22. pyobo/cli/utils.py +19 -6
  23. pyobo/constants.py +102 -3
  24. pyobo/getters.py +209 -191
  25. pyobo/gilda_utils.py +52 -250
  26. pyobo/identifier_utils/__init__.py +33 -0
  27. pyobo/identifier_utils/api.py +305 -0
  28. pyobo/identifier_utils/preprocessing.json +873 -0
  29. pyobo/identifier_utils/preprocessing.py +27 -0
  30. pyobo/identifier_utils/relations/__init__.py +8 -0
  31. pyobo/identifier_utils/relations/api.py +162 -0
  32. pyobo/identifier_utils/relations/data.json +5824 -0
  33. pyobo/identifier_utils/relations/data_owl.json +57 -0
  34. pyobo/identifier_utils/relations/data_rdf.json +1 -0
  35. pyobo/identifier_utils/relations/data_rdfs.json +7 -0
  36. pyobo/mocks.py +9 -6
  37. pyobo/ner/__init__.py +9 -0
  38. pyobo/ner/api.py +72 -0
  39. pyobo/ner/normalizer.py +33 -0
  40. pyobo/obographs.py +48 -40
  41. pyobo/plugins.py +5 -4
  42. pyobo/py.typed +0 -0
  43. pyobo/reader.py +1354 -395
  44. pyobo/reader_utils.py +155 -0
  45. pyobo/resource_utils.py +42 -22
  46. pyobo/resources/__init__.py +0 -0
  47. pyobo/resources/goc.py +75 -0
  48. pyobo/resources/goc.tsv +188 -0
  49. pyobo/resources/ncbitaxon.py +4 -5
  50. pyobo/resources/ncbitaxon.tsv.gz +0 -0
  51. pyobo/resources/ro.py +3 -2
  52. pyobo/resources/ro.tsv +0 -0
  53. pyobo/resources/so.py +0 -0
  54. pyobo/resources/so.tsv +0 -0
  55. pyobo/sources/README.md +12 -8
  56. pyobo/sources/__init__.py +52 -29
  57. pyobo/sources/agrovoc.py +0 -0
  58. pyobo/sources/antibodyregistry.py +11 -12
  59. pyobo/sources/bigg/__init__.py +13 -0
  60. pyobo/sources/bigg/bigg_compartment.py +81 -0
  61. pyobo/sources/bigg/bigg_metabolite.py +229 -0
  62. pyobo/sources/bigg/bigg_model.py +46 -0
  63. pyobo/sources/bigg/bigg_reaction.py +77 -0
  64. pyobo/sources/biogrid.py +1 -2
  65. pyobo/sources/ccle.py +7 -12
  66. pyobo/sources/cgnc.py +9 -6
  67. pyobo/sources/chebi.py +1 -1
  68. pyobo/sources/chembl/__init__.py +9 -0
  69. pyobo/sources/{chembl.py → chembl/chembl_compound.py} +13 -25
  70. pyobo/sources/chembl/chembl_target.py +160 -0
  71. pyobo/sources/civic_gene.py +55 -15
  72. pyobo/sources/clinicaltrials.py +160 -0
  73. pyobo/sources/complexportal.py +24 -24
  74. pyobo/sources/conso.py +14 -22
  75. pyobo/sources/cpt.py +0 -0
  76. pyobo/sources/credit.py +1 -9
  77. pyobo/sources/cvx.py +27 -5
  78. pyobo/sources/depmap.py +9 -12
  79. pyobo/sources/dictybase_gene.py +2 -7
  80. pyobo/sources/drugbank/__init__.py +9 -0
  81. pyobo/sources/{drugbank.py → drugbank/drugbank.py} +11 -16
  82. pyobo/sources/{drugbank_salt.py → drugbank/drugbank_salt.py} +3 -8
  83. pyobo/sources/drugcentral.py +17 -13
  84. pyobo/sources/expasy.py +31 -34
  85. pyobo/sources/famplex.py +13 -18
  86. pyobo/sources/flybase.py +8 -13
  87. pyobo/sources/gard.py +62 -0
  88. pyobo/sources/geonames/__init__.py +9 -0
  89. pyobo/sources/geonames/features.py +28 -0
  90. pyobo/sources/{geonames.py → geonames/geonames.py} +87 -26
  91. pyobo/sources/geonames/utils.py +115 -0
  92. pyobo/sources/gmt_utils.py +6 -7
  93. pyobo/sources/go.py +20 -13
  94. pyobo/sources/gtdb.py +154 -0
  95. pyobo/sources/gwascentral/__init__.py +9 -0
  96. pyobo/sources/{gwascentral_phenotype.py → gwascentral/gwascentral_phenotype.py} +5 -7
  97. pyobo/sources/{gwascentral_study.py → gwascentral/gwascentral_study.py} +1 -7
  98. pyobo/sources/hgnc/__init__.py +9 -0
  99. pyobo/sources/{hgnc.py → hgnc/hgnc.py} +56 -70
  100. pyobo/sources/{hgncgenefamily.py → hgnc/hgncgenefamily.py} +8 -18
  101. pyobo/sources/icd/__init__.py +9 -0
  102. pyobo/sources/{icd10.py → icd/icd10.py} +35 -37
  103. pyobo/sources/icd/icd11.py +148 -0
  104. pyobo/sources/{icd_utils.py → icd/icd_utils.py} +66 -20
  105. pyobo/sources/interpro.py +4 -9
  106. pyobo/sources/itis.py +0 -5
  107. pyobo/sources/kegg/__init__.py +0 -0
  108. pyobo/sources/kegg/api.py +16 -38
  109. pyobo/sources/kegg/genes.py +9 -20
  110. pyobo/sources/kegg/genome.py +1 -7
  111. pyobo/sources/kegg/pathway.py +9 -21
  112. pyobo/sources/mesh.py +58 -24
  113. pyobo/sources/mgi.py +3 -10
  114. pyobo/sources/mirbase/__init__.py +11 -0
  115. pyobo/sources/{mirbase.py → mirbase/mirbase.py} +8 -11
  116. pyobo/sources/{mirbase_constants.py → mirbase/mirbase_constants.py} +0 -0
  117. pyobo/sources/{mirbase_family.py → mirbase/mirbase_family.py} +4 -8
  118. pyobo/sources/{mirbase_mature.py → mirbase/mirbase_mature.py} +3 -7
  119. pyobo/sources/msigdb.py +74 -39
  120. pyobo/sources/ncbi/__init__.py +9 -0
  121. pyobo/sources/ncbi/ncbi_gc.py +162 -0
  122. pyobo/sources/{ncbigene.py → ncbi/ncbigene.py} +18 -19
  123. pyobo/sources/nih_reporter.py +60 -0
  124. pyobo/sources/nlm/__init__.py +9 -0
  125. pyobo/sources/nlm/nlm_catalog.py +48 -0
  126. pyobo/sources/nlm/nlm_publisher.py +36 -0
  127. pyobo/sources/nlm/utils.py +116 -0
  128. pyobo/sources/npass.py +6 -8
  129. pyobo/sources/omim_ps.py +11 -4
  130. pyobo/sources/pathbank.py +4 -8
  131. pyobo/sources/pfam/__init__.py +9 -0
  132. pyobo/sources/{pfam.py → pfam/pfam.py} +3 -8
  133. pyobo/sources/{pfam_clan.py → pfam/pfam_clan.py} +2 -7
  134. pyobo/sources/pharmgkb/__init__.py +15 -0
  135. pyobo/sources/pharmgkb/pharmgkb_chemical.py +89 -0
  136. pyobo/sources/pharmgkb/pharmgkb_disease.py +77 -0
  137. pyobo/sources/pharmgkb/pharmgkb_gene.py +108 -0
  138. pyobo/sources/pharmgkb/pharmgkb_pathway.py +63 -0
  139. pyobo/sources/pharmgkb/pharmgkb_variant.py +84 -0
  140. pyobo/sources/pharmgkb/utils.py +86 -0
  141. pyobo/sources/pid.py +1 -6
  142. pyobo/sources/pombase.py +6 -10
  143. pyobo/sources/pubchem.py +4 -9
  144. pyobo/sources/reactome.py +5 -11
  145. pyobo/sources/rgd.py +11 -16
  146. pyobo/sources/rhea.py +37 -36
  147. pyobo/sources/ror.py +69 -42
  148. pyobo/sources/selventa/__init__.py +0 -0
  149. pyobo/sources/selventa/schem.py +4 -7
  150. pyobo/sources/selventa/scomp.py +1 -6
  151. pyobo/sources/selventa/sdis.py +4 -7
  152. pyobo/sources/selventa/sfam.py +1 -6
  153. pyobo/sources/sgd.py +6 -11
  154. pyobo/sources/signor/__init__.py +7 -0
  155. pyobo/sources/signor/download.py +41 -0
  156. pyobo/sources/signor/signor_complexes.py +105 -0
  157. pyobo/sources/slm.py +12 -15
  158. pyobo/sources/umls/__init__.py +7 -1
  159. pyobo/sources/umls/__main__.py +0 -0
  160. pyobo/sources/umls/get_synonym_types.py +20 -4
  161. pyobo/sources/umls/sty.py +57 -0
  162. pyobo/sources/umls/synonym_types.tsv +1 -1
  163. pyobo/sources/umls/umls.py +18 -22
  164. pyobo/sources/unimod.py +46 -0
  165. pyobo/sources/uniprot/__init__.py +1 -1
  166. pyobo/sources/uniprot/uniprot.py +40 -32
  167. pyobo/sources/uniprot/uniprot_ptm.py +4 -34
  168. pyobo/sources/utils.py +3 -2
  169. pyobo/sources/wikipathways.py +7 -10
  170. pyobo/sources/zfin.py +5 -10
  171. pyobo/ssg/__init__.py +12 -16
  172. pyobo/ssg/base.html +0 -0
  173. pyobo/ssg/index.html +26 -13
  174. pyobo/ssg/term.html +12 -2
  175. pyobo/ssg/typedef.html +0 -0
  176. pyobo/struct/__init__.py +54 -8
  177. pyobo/struct/functional/__init__.py +1 -0
  178. pyobo/struct/functional/dsl.py +2572 -0
  179. pyobo/struct/functional/macros.py +423 -0
  180. pyobo/struct/functional/obo_to_functional.py +385 -0
  181. pyobo/struct/functional/ontology.py +272 -0
  182. pyobo/struct/functional/utils.py +112 -0
  183. pyobo/struct/reference.py +331 -136
  184. pyobo/struct/struct.py +1484 -657
  185. pyobo/struct/struct_utils.py +1078 -0
  186. pyobo/struct/typedef.py +162 -210
  187. pyobo/struct/utils.py +12 -5
  188. pyobo/struct/vocabulary.py +138 -0
  189. pyobo/utils/__init__.py +0 -0
  190. pyobo/utils/cache.py +16 -15
  191. pyobo/utils/io.py +51 -41
  192. pyobo/utils/iter.py +5 -5
  193. pyobo/utils/misc.py +41 -53
  194. pyobo/utils/ndex_utils.py +0 -0
  195. pyobo/utils/path.py +73 -70
  196. pyobo/version.py +3 -3
  197. pyobo-0.12.1.dist-info/METADATA +671 -0
  198. pyobo-0.12.1.dist-info/RECORD +201 -0
  199. pyobo-0.12.1.dist-info/WHEEL +4 -0
  200. {pyobo-0.11.2.dist-info → pyobo-0.12.1.dist-info}/entry_points.txt +1 -0
  201. pyobo-0.12.1.dist-info/licenses/LICENSE +21 -0
  202. pyobo/aws.py +0 -162
  203. pyobo/cli/aws.py +0 -47
  204. pyobo/identifier_utils.py +0 -142
  205. pyobo/normalizer.py +0 -232
  206. pyobo/registries/__init__.py +0 -16
  207. pyobo/registries/metaregistry.json +0 -507
  208. pyobo/registries/metaregistry.py +0 -135
  209. pyobo/sources/icd11.py +0 -105
  210. pyobo/xrefdb/__init__.py +0 -1
  211. pyobo/xrefdb/canonicalizer.py +0 -214
  212. pyobo/xrefdb/priority.py +0 -59
  213. pyobo/xrefdb/sources/__init__.py +0 -60
  214. pyobo/xrefdb/sources/biomappings.py +0 -36
  215. pyobo/xrefdb/sources/cbms2019.py +0 -91
  216. pyobo/xrefdb/sources/chembl.py +0 -83
  217. pyobo/xrefdb/sources/compath.py +0 -82
  218. pyobo/xrefdb/sources/famplex.py +0 -64
  219. pyobo/xrefdb/sources/gilda.py +0 -50
  220. pyobo/xrefdb/sources/intact.py +0 -113
  221. pyobo/xrefdb/sources/ncit.py +0 -133
  222. pyobo/xrefdb/sources/pubchem.py +0 -27
  223. pyobo/xrefdb/sources/wikidata.py +0 -116
  224. pyobo/xrefdb/xrefs_pipeline.py +0 -180
  225. pyobo-0.11.2.dist-info/METADATA +0 -711
  226. pyobo-0.11.2.dist-info/RECORD +0 -157
  227. pyobo-0.11.2.dist-info/WHEEL +0 -5
  228. pyobo-0.11.2.dist-info/top_level.txt +0 -1
@@ -0,0 +1,385 @@
1
+ """Converters from OBO to functional OWL."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterable
6
+ from typing import TYPE_CHECKING, cast
7
+
8
+ import rdflib
9
+ from curies import vocabulary as v
10
+ from rdflib import XSD
11
+
12
+ from pyobo.struct import Stanza
13
+ from pyobo.struct import vocabulary as pv
14
+ from pyobo.struct.functional import dsl as f
15
+ from pyobo.struct.functional import macros as m
16
+ from pyobo.struct.functional.ontology import Document, Ontology
17
+ from pyobo.struct.reference import OBOLiteral, Reference, _parse_datetime
18
+
19
+ if TYPE_CHECKING:
20
+ from pyobo.struct.struct import Obo, Referenced, Term
21
+ from pyobo.struct.struct_utils import Annotation as OBOAnnotation
22
+ from pyobo.struct.typedef import TypeDef
23
+
24
+ __all__ = [
25
+ "get_ofn_from_obo",
26
+ "get_ontology_annotations",
27
+ "get_ontology_axioms",
28
+ "get_term_axioms",
29
+ "get_typedef_axioms",
30
+ ]
31
+
32
+ _BASE = "https://w3id.org/biopragmatics/resources"
33
+
34
+
35
+ def get_ofn_from_obo(
36
+ obo_ontology: Obo, *, iri: str | None = None, version_iri: str | None = None
37
+ ) -> Document:
38
+ """Convert an ontology."""
39
+ prefix = obo_ontology.ontology
40
+ base = f"{_BASE}/{prefix}"
41
+ if iri is None:
42
+ iri = f"{base}/{prefix}.ofn"
43
+ if version_iri is None and obo_ontology.data_version:
44
+ version_iri = f"{base}/{obo_ontology.data_version}/{prefix}.ofn"
45
+ ofn_ontology = Ontology(
46
+ iri=iri,
47
+ version_iri=version_iri,
48
+ annotations=list(get_ontology_annotations(obo_ontology)),
49
+ axioms=list(get_ontology_axioms(obo_ontology)),
50
+ )
51
+ document = Document(
52
+ ofn_ontology,
53
+ obo_ontology._get_clean_idspaces(),
54
+ )
55
+ return document
56
+
57
+
58
+ def get_ontology_axioms(obo_ontology: Obo) -> Iterable[f.Box]:
59
+ """Get axioms from the ontology."""
60
+ if obo_ontology.root_terms:
61
+ yield f.Declaration(pv.has_ontology_root_term, type="AnnotationProperty")
62
+ yield m.LabelMacro(pv.has_ontology_root_term, cast(str, pv.has_ontology_root_term.name))
63
+
64
+ if obo_ontology.subsetdefs:
65
+ yield f.Declaration("oboInOwl:SubsetProperty", type="AnnotationProperty")
66
+ for subset_typedef, subset_label in obo_ontology.subsetdefs:
67
+ yield f.Declaration(subset_typedef, type="AnnotationProperty")
68
+ yield m.LabelMacro(subset_typedef, subset_label)
69
+ yield f.SubAnnotationPropertyOf(subset_typedef, "oboInOwl:SubsetProperty")
70
+
71
+ if obo_ontology.synonym_typedefs:
72
+ used_has_scope = False
73
+ for synonym_typedef in obo_ontology.synonym_typedefs:
74
+ yield f.Declaration(synonym_typedef, type="AnnotationProperty")
75
+ yield m.LabelMacro(synonym_typedef, synonym_typedef.name)
76
+ yield f.SubAnnotationPropertyOf(synonym_typedef, "oboInOwl:SynonymTypeProperty")
77
+ if synonym_typedef.specificity:
78
+ used_has_scope = True
79
+ yield f.AnnotationAssertion(
80
+ "oboInOwl:hasScope",
81
+ synonym_typedef,
82
+ v.synonym_scopes[synonym_typedef.specificity],
83
+ )
84
+ if used_has_scope:
85
+ yield f.Declaration("oboInOwl:hasScope", type="AnnotationProperty")
86
+
87
+ for typedef in obo_ontology.typedefs or []:
88
+ yield from get_typedef_axioms(typedef)
89
+
90
+ for term in obo_ontology:
91
+ yield from get_term_axioms(term)
92
+
93
+
94
+ def get_ontology_annotations(obo_ontology: Obo) -> Iterable[f.Annotation]:
95
+ """Get annotations from the ontology."""
96
+ for predicate, value in obo_ontology._iterate_property_pairs():
97
+ yield f.Annotation(predicate, value)
98
+ if obo_ontology.data_version:
99
+ yield f.Annotation(pv.version_info, OBOLiteral.string(obo_ontology.data_version))
100
+ if obo_ontology.auto_generated_by is not None:
101
+ yield f.Annotation(
102
+ pv.obo_autogenerated_by, OBOLiteral.string(obo_ontology.auto_generated_by)
103
+ )
104
+
105
+
106
+ def _oboliteral_to_literal(obo_literal: OBOLiteral) -> rdflib.Literal:
107
+ if obo_literal.datatype.pair == ("xsd", "string") and obo_literal.language:
108
+ return rdflib.Literal(obo_literal.value, lang=obo_literal.language)
109
+ if obo_literal.datatype.prefix != "xsd":
110
+ raise NotImplementedError(
111
+ f"Automatic literal conversion is not implemented for prefix: {obo_literal.datatype.prefix}"
112
+ )
113
+ if obo_literal.datatype.identifier == "dateTime":
114
+ return rdflib.Literal(_parse_datetime(obo_literal.value), datatype=XSD.dateTime)
115
+ return rdflib.Literal(obo_literal.value, datatype=XSD._NS.term(obo_literal.datatype.identifier))
116
+
117
+
118
+ def get_term_axioms(term: Term) -> Iterable[f.Box]:
119
+ """Iterate over functional OWL axioms for a term."""
120
+ s = f.IdentifierBox(term)
121
+ # 1 and 13
122
+ if term.type == "Term":
123
+ yield f.Declaration(s, type="Class")
124
+ for parent in term.parents:
125
+ yield f.SubClassOf(s, parent)
126
+ else:
127
+ yield f.Declaration(s, type="NamedIndividual")
128
+ for parent in term.parents:
129
+ yield f.ClassAssertion(parent, s)
130
+ # 2
131
+ if term.is_anonymous is not None:
132
+ yield m.IsAnonymousMacro(s, term.is_anonymous)
133
+ # 3
134
+ if term.name:
135
+ yield m.LabelMacro(s, term.name)
136
+ # 4
137
+ if term.namespace:
138
+ yield m.OBONamespaceMacro(s, term.namespace)
139
+ # 5
140
+ for alt in term.alt_ids:
141
+ yield m.ReplacedByMacro(alt, s)
142
+ # 6
143
+ yield from _yield_definition(term, s)
144
+ # 7 comment is covered by properties
145
+ # 8
146
+ for subset in term.subsets:
147
+ yield m.OBOIsSubsetMacro(s, subset)
148
+ # 9
149
+ yield from _yield_synonyms(term, s)
150
+ # 10
151
+ yield from _yield_xrefs(term, s)
152
+ # 11
153
+ if term.builtin is not None:
154
+ yield m.IsOBOBuiltinMacro(s, term.builtin)
155
+ # 12
156
+ yield from _yield_properties(term, s)
157
+ # 13 parents - see top
158
+ # 14
159
+ if term.intersection_of:
160
+ yield m.ClassIntersectionMacro(s, term.intersection_of)
161
+ # 15
162
+ if term.union_of:
163
+ yield m.ClassUnionMacro(s, term.union_of)
164
+ # 16
165
+ if term.equivalent_to:
166
+ yield f.EquivalentClasses([s, *term.equivalent_to])
167
+ # 17
168
+ if term.disjoint_from:
169
+ yield f.DisjointClasses([s, *term.disjoint_from])
170
+ # 18
171
+ for typedef, value in term.iterate_relations():
172
+ rel_annotations = _get_annotations(term, typedef, value)
173
+ if term.type == "Term":
174
+ yield m.RelationshipMacro(s=s, p=typedef, o=value, annotations=rel_annotations)
175
+ else:
176
+ yield f.ObjectPropertyAssertion(typedef, s, value, annotations=rel_annotations)
177
+
178
+ # 19 TODO created_by
179
+ # 20 TODO creation_date
180
+ # 21
181
+ if term.is_obsolete is not None:
182
+ yield m.IsObsoleteMacro(s, term.is_obsolete)
183
+ # 22 replaced_by is covered by properties
184
+ # 23 consider is covered by properties
185
+
186
+
187
+ def _get_annotations(
188
+ term: Stanza, p: Reference | Referenced, o: Reference | Referenced | OBOLiteral | str
189
+ ) -> list[f.Annotation]:
190
+ return _process_anns(term._get_annotations(p, o))
191
+
192
+
193
+ def _process_anns(annotations: list[OBOAnnotation]) -> list[f.Annotation]:
194
+ """Convert OBO anotations to OFN annotations."""
195
+ return [_convert_annotation(a) for a in annotations]
196
+
197
+
198
+ def _convert_annotation(annotation: OBOAnnotation) -> f.Annotation:
199
+ """Convert OBO anotations to OFN annotations."""
200
+ match annotation.value:
201
+ case OBOLiteral():
202
+ return f.Annotation(
203
+ annotation.predicate,
204
+ _oboliteral_to_literal(annotation.value),
205
+ )
206
+ case Reference():
207
+ return f.Annotation(
208
+ annotation.predicate,
209
+ annotation.value,
210
+ )
211
+ raise TypeError
212
+
213
+
214
+ def get_typedef_axioms(typedef: TypeDef) -> Iterable[f.Box]:
215
+ """Iterate over functional OWL axioms for a typedef."""
216
+ r = f.IdentifierBox(typedef)
217
+ # 40
218
+ if typedef.is_metadata_tag:
219
+ yield f.Declaration(r, type="AnnotationProperty")
220
+ else:
221
+ yield f.Declaration(r, type="ObjectProperty")
222
+ # 2
223
+ if typedef.is_anonymous is not None:
224
+ yield m.IsAnonymousMacro(r, typedef.is_anonymous)
225
+ # 3
226
+ if typedef.name:
227
+ yield m.LabelMacro(r, typedef.name)
228
+ # 4
229
+ if typedef.namespace:
230
+ yield m.OBONamespaceMacro(r, typedef.namespace)
231
+ # 5 the way this one works is that all of the alts get a term-replaced-by,
232
+ # as well as getting their own deprecation axioms
233
+ for alt_id in typedef.alt_ids:
234
+ yield m.ReplacedByMacro(alt_id, r)
235
+ # 6
236
+ yield from _yield_definition(typedef, r)
237
+ # 7
238
+ if typedef.comment:
239
+ yield m.CommentMacro(r, typedef.comment)
240
+ # 8
241
+ for subset in typedef.subsets:
242
+ yield m.OBOIsSubsetMacro(r, subset)
243
+ # 9
244
+ yield from _yield_synonyms(typedef, r)
245
+ # 10
246
+ yield from _yield_xrefs(typedef, r)
247
+ # 11
248
+ yield from _yield_properties(typedef, r)
249
+ # 12
250
+ if typedef.domain:
251
+ if typedef.is_metadata_tag:
252
+ yield f.AnnotationPropertyDomain(r, typedef.domain)
253
+ else:
254
+ yield f.ObjectPropertyDomain(r, typedef.domain)
255
+ # 13
256
+ if typedef.range:
257
+ if typedef.is_metadata_tag:
258
+ yield f.AnnotationPropertyRange(r, typedef.range)
259
+ else:
260
+ yield f.ObjectPropertyRange(r, typedef.range)
261
+ # 14
262
+ if typedef.builtin is not None:
263
+ yield m.IsOBOBuiltinMacro(r, typedef.builtin)
264
+ # 15
265
+ for chain in typedef.holds_over_chain:
266
+ yield m.HoldsOverChain(r, chain)
267
+ # 16
268
+ if typedef.is_anti_symmetric:
269
+ yield f.AsymmetricObjectProperty(r)
270
+ # 17
271
+ if typedef.is_cyclic is not None:
272
+ yield m.IsCyclic(r, typedef.is_cyclic)
273
+ # 18
274
+ if typedef.is_reflexive:
275
+ yield f.ReflexiveObjectProperty(r)
276
+ # 19
277
+ if typedef.is_symmetric:
278
+ yield f.SymmetricObjectProperty(r)
279
+ # 20
280
+ if typedef.is_transitive:
281
+ yield f.TransitiveObjectProperty(r)
282
+ # 21
283
+ if typedef.is_functional:
284
+ yield f.FunctionalObjectProperty(r)
285
+ # 22
286
+ if typedef.is_inverse_functional:
287
+ yield f.InverseFunctionalObjectProperty(r)
288
+ # 23
289
+ for parent in typedef.parents:
290
+ if typedef.is_metadata_tag:
291
+ yield f.SubAnnotationPropertyOf(r, parent)
292
+ else:
293
+ yield f.SubObjectPropertyOf(r, parent)
294
+ # 24 TODO intersection_of, ROBOT does not create any output
295
+ # 25 TODO union_of, ROBOT does not create any output
296
+ # 26
297
+ if typedef.equivalent_to:
298
+ yield f.EquivalentObjectProperties([r, *typedef.equivalent_to])
299
+ # 27
300
+ for x in typedef.disjoint_from:
301
+ yield f.DisjointObjectProperties([x, r])
302
+ # 28
303
+ if typedef.inverse:
304
+ yield f.InverseObjectProperties(r, typedef.inverse)
305
+ # 29
306
+ for to in typedef.transitive_over:
307
+ yield m.TransitiveOver(r, to)
308
+ # 30
309
+ for chain in typedef.equivalent_to_chain:
310
+ yield f.SubObjectPropertyOf(f.ObjectPropertyChain(chain), r)
311
+ # 31 TODO disjoint_over, ROBOT does not create any output
312
+ # 32 TODO relationship, ROBOT does not create any output
313
+ # 33
314
+ if typedef.is_obsolete is not None:
315
+ yield m.IsObsoleteMacro(r, typedef.is_obsolete)
316
+ # 34 TODO created_by
317
+ # 35 TODO creation_date
318
+ # 36
319
+ for rep in typedef.get_replaced_by():
320
+ yield m.ReplacedByMacro(rep, r)
321
+ # 37
322
+ for ref in typedef.get_see_also():
323
+ yield m.OBOConsiderMacro(r, ref)
324
+ # 38 TODO expand_assertion_to
325
+ # 39 TODO expand_expression_to
326
+ # 41
327
+ if typedef.is_class_level is not None:
328
+ yield m.OBOIsClassLevelMacro(r, typedef.is_class_level)
329
+
330
+
331
+ def _yield_definition(term: Stanza, s) -> Iterable[m.DescriptionMacro]:
332
+ if term.definition:
333
+ yield m.DescriptionMacro(
334
+ s,
335
+ term.definition,
336
+ annotations=_get_annotations(term, pv.has_description, term.definition),
337
+ )
338
+
339
+
340
+ def _yield_synonyms(stanza: Stanza, r) -> Iterable[m.SynonymMacro]:
341
+ for synonym in stanza.synonyms:
342
+ yield m.SynonymMacro(
343
+ r,
344
+ synonym.name,
345
+ scope=synonym.specificity,
346
+ synonym_type=synonym.type,
347
+ provenance=synonym.provenance,
348
+ annotations=_process_anns(synonym.annotations),
349
+ language=synonym.language,
350
+ )
351
+
352
+
353
+ def _yield_xrefs(term: Stanza, s) -> Iterable[m.XrefMacro]:
354
+ for xref in term.xrefs:
355
+ yield m.XrefMacro(s, xref, annotations=_get_annotations(term, pv.has_dbxref, xref))
356
+
357
+
358
+ _SKIP = {
359
+ # we skip alt terms since OFN
360
+ # prefers to flip the triple and use term-replaced-by instead
361
+ pv.alternative_term,
362
+ }
363
+
364
+
365
+ def _yield_properties(term: Stanza, s) -> Iterable[f.AnnotationAssertion]:
366
+ for typedef, values in term.properties.items():
367
+ for value in values:
368
+ annotations = _get_annotations(term, typedef, value)
369
+ match value:
370
+ case OBOLiteral():
371
+ yield f.AnnotationAssertion(
372
+ typedef,
373
+ s,
374
+ _oboliteral_to_literal(value),
375
+ annotations=annotations,
376
+ )
377
+ case Reference():
378
+ if typedef in _SKIP:
379
+ continue
380
+ yield f.AnnotationAssertion(
381
+ typedef,
382
+ s,
383
+ value,
384
+ annotations=annotations,
385
+ )
@@ -0,0 +1,272 @@
1
+ """High-level ontology object model."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import subprocess
6
+ import tempfile
7
+ from collections.abc import Sequence
8
+ from pathlib import Path
9
+
10
+ from curies import Converter
11
+ from rdflib import OWL, RDF, Graph, term
12
+
13
+ from pyobo.struct.functional.dsl import Annotation, Annotations, Axiom, Box
14
+ from pyobo.struct.functional.utils import (
15
+ EXAMPLE_ONTOLOGY_IRI,
16
+ FunctionalOWLSerializable,
17
+ list_to_funowl,
18
+ )
19
+ from pyobo.utils.io import safe_open
20
+
21
+ __all__ = [
22
+ "Document",
23
+ "Import",
24
+ "Ontology",
25
+ "Prefix",
26
+ "write_ontology",
27
+ ]
28
+
29
+
30
+ def write_ontology(
31
+ *,
32
+ prefixes: dict[str, str] | list[Prefix],
33
+ iri: str | None = None,
34
+ version_iri: str | None = None,
35
+ directly_imports_documents: list[Import | str] | None = None,
36
+ annotations: Annotations | None = None,
37
+ axioms: list[Axiom] | None = None,
38
+ file=None,
39
+ ) -> None:
40
+ """Print an ontology serialized as functional OWL."""
41
+ ontology = Ontology(
42
+ iri=iri,
43
+ version_iri=version_iri,
44
+ directly_imports_documents=directly_imports_documents,
45
+ annotations=annotations,
46
+ axioms=axioms,
47
+ )
48
+ document = Document(ontology, prefixes)
49
+ print(document.to_funowl(), file=file)
50
+
51
+
52
+ class Document:
53
+ """Represents a functional OWL document."""
54
+
55
+ prefixes: list[Prefix]
56
+ ontologies: list[Ontology]
57
+
58
+ def __init__(
59
+ self,
60
+ ontologies: Ontology | list[Ontology],
61
+ prefixes: dict[str, str] | list[Prefix],
62
+ ) -> None:
63
+ """Initialize a functional OWL document.
64
+
65
+ :param ontologies: An ontology or list of ontologies.
66
+
67
+ .. warning::
68
+
69
+ RDF export can only be used for a single ontology.
70
+
71
+ :param prefixes: A list of prefixes to define in the document
72
+
73
+ .. seealso::
74
+
75
+ `3.7 "Functional-Style Syntax"
76
+ <https://www.w3.org/TR/owl2-syntax/#Functional-Style_Syntax>`_
77
+ """
78
+ self.ontologies = ontologies if isinstance(ontologies, list) else [ontologies]
79
+ if isinstance(prefixes, dict):
80
+ self.prefixes = [
81
+ Prefix(prefix, uri_prefix)
82
+ for prefix, uri_prefix in sorted(prefixes.items(), key=lambda kv: kv[0].casefold())
83
+ ]
84
+ else:
85
+ self.prefixes = prefixes
86
+
87
+ @property
88
+ def prefix_map(self) -> dict[str, str]:
89
+ """Get a simple dictionary representation of prefixes."""
90
+ return {prefix.prefix: prefix.uri_prefix for prefix in self.prefixes}
91
+
92
+ def write_rdf(self, path: str | Path) -> None:
93
+ """Write RDF to a file."""
94
+ path = Path(path).expanduser().resolve()
95
+ graph = self.to_rdf()
96
+ graph.serialize(path, format="ttl")
97
+
98
+ def to_rdf(self) -> Graph:
99
+ """Get an RDFlib graph representing the ontology."""
100
+ if len(self.ontologies) != 1:
101
+ raise ValueError("Can only export one ontology to RDF")
102
+ graph = Graph()
103
+ for prefix_box in self.prefixes:
104
+ graph.namespace_manager.bind(prefix_box.prefix, prefix_box.uri_prefix)
105
+ converter = Converter.from_rdflib(graph)
106
+ for ontology in self.ontologies:
107
+ ontology.to_rdflib_node(graph, converter)
108
+ return graph
109
+
110
+ def write_funowl(self, path: str | Path) -> None:
111
+ """Write functional OWL to a file.."""
112
+ path = Path(path).expanduser().resolve()
113
+ with safe_open(path, read=False) as file:
114
+ file.write(self.to_funowl())
115
+
116
+ def to_funowl(self) -> str:
117
+ """Get the document as a functional OWL string."""
118
+ prefixes = list_to_funowl(self.prefixes, sep="\n")
119
+ ontologies = list_to_funowl(self.ontologies, sep="\n\n")
120
+ return prefixes + "\n\n" + ontologies
121
+
122
+
123
+ class Ontology(Box):
124
+ """Represents an OWL 2 ontology defined in `3 "Ontologies" <https://www.w3.org/TR/owl2-syntax/#Ontologies>`_."""
125
+
126
+ directly_imports_documents: Sequence[Import]
127
+ annotations: Sequence[Annotation]
128
+ axioms: Sequence[Box]
129
+
130
+ def __init__(
131
+ self,
132
+ iri: str | None = None,
133
+ version_iri: str | None = None,
134
+ directly_imports_documents: list[Import | str] | None = None,
135
+ annotations: Annotations | None = None,
136
+ axioms: Sequence[Box] | None = None,
137
+ ) -> None:
138
+ """Instantiate an ontology.
139
+
140
+ :param iri: The ontology IRI.
141
+
142
+ .. seealso::
143
+
144
+ `3.1 "Ontology IRI and Version IRI"
145
+ <https://www.w3.org/TR/owl2-syntax/#Ontology_IRI_and_Version_IRI>`_
146
+
147
+ :param version_iri: An optional version IRI
148
+ :param directly_imports_documents: Optional ontology imports
149
+
150
+ .. seealso::
151
+
152
+ `3.4 "Imports" <https://www.w3.org/TR/owl2-syntax/#Imports>`_
153
+
154
+ :param annotations: .. seealso::
155
+
156
+ `3.5 "Ontology Annotations"
157
+ <https://www.w3.org/TR/owl2-syntax/#Ontology_Annotations>`_
158
+ :param axioms: statements about what is true in the domain
159
+
160
+ .. seealso::
161
+
162
+ `9 "Axioms" <https://www.w3.org/TR/owl2-syntax/#Axioms>`_
163
+ """
164
+ self.iri = iri
165
+ self.version_iri = version_iri
166
+ self.directly_imports_documents = [
167
+ Import(i) if isinstance(i, str) else i for i in directly_imports_documents or []
168
+ ]
169
+ self.annotations = annotations or []
170
+ self.axioms = axioms or []
171
+ # this is the amount of leading whitespace on each
172
+ # when outputting to functional OWL
173
+ self._leading = ""
174
+
175
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.URIRef | term.BNode:
176
+ """Add the ontology to the triple store."""
177
+ ontology_node = term.URIRef(self.iri) if self.iri is not None else term.BNode()
178
+ graph.add((ontology_node, RDF.type, OWL.Ontology))
179
+ if self.version_iri:
180
+ graph.add((ontology_node, OWL.versionIRI, term.URIRef(self.version_iri)))
181
+ for imp in self.directly_imports_documents:
182
+ graph.add((ontology_node, OWL.imports, term.URIRef(imp.iri)))
183
+ for annotation in self.annotations:
184
+ annotation._add_to_triple(graph, ontology_node, converter=converter)
185
+ for axiom in self.axioms:
186
+ axiom.to_rdflib_node(graph, converter)
187
+ return ontology_node
188
+
189
+ def to_funowl(self) -> str:
190
+ """Make functional OWL."""
191
+ tag = self.__class__.__name__
192
+ return f"{tag}({self.to_funowl_args()}\n)"
193
+
194
+ def to_funowl_args(self) -> str:
195
+ """Get the inside of the functional OWL tag representing the ontology."""
196
+ rv = ""
197
+ if self.iri:
198
+ rv += f"<{self.iri}>"
199
+ if self.version_iri:
200
+ rv += f" <{self.version_iri}>"
201
+ rv += f"\n{self._leading}"
202
+
203
+ parts: list[Sequence[FunctionalOWLSerializable]] = []
204
+ if self.directly_imports_documents:
205
+ parts.append(self.directly_imports_documents)
206
+ if self.annotations:
207
+ parts.append(self.annotations)
208
+ if self.axioms:
209
+ parts.append(self.axioms)
210
+
211
+ rv += f"\n\n{self._leading}".join(
212
+ list_to_funowl(part, sep=f"\n{self._leading}") for part in parts
213
+ )
214
+ return rv
215
+
216
+
217
+ class Prefix(Box):
218
+ """A model for imports, as defined by `3.7 "Functional-Style Syntax" <https://www.w3.org/TR/owl2-syntax/#Functional-Style_Syntax>`_."""
219
+
220
+ def __init__(self, prefix: str, uri_prefix: str) -> None:
221
+ """Initialize the definition with a CURIE prefix and URI prefix."""
222
+ self.prefix = prefix
223
+ self.uri_prefix = uri_prefix
224
+
225
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.BNode:
226
+ """Add the prefix to an RDF graph."""
227
+ graph.namespace_manager.bind(self.prefix, self.uri_prefix)
228
+ return term.BNode() # dummy
229
+
230
+ def to_funowl_args(self) -> str:
231
+ """Get the inside of the functional OWL tag representing the prefix."""
232
+ return f"{self.prefix}:=<{self.uri_prefix}>"
233
+
234
+
235
+ class Import(Box):
236
+ """A model for imports, as defined by `3.4 "Imports" <https://www.w3.org/TR/owl2-syntax/#Imports>`_."""
237
+
238
+ def __init__(self, iri: str) -> None:
239
+ """Initialize the import."""
240
+ self.iri = iri
241
+
242
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.BNode:
243
+ """Add the import to an RDF graph."""
244
+ raise NotImplementedError
245
+
246
+ def to_funowl_args(self) -> str:
247
+ """Get the inside of the functional OWL tag representing the import."""
248
+ return f"<{self.iri}>"
249
+
250
+
251
+ def get_rdf_graph_oracle(boxes: list[Box], *, prefix_map: dict[str, str]) -> Graph:
252
+ """Serialize to turtle via OFN and conversion with ROBOT."""
253
+ from bioontologies.robot import convert
254
+
255
+ ontology = Ontology(
256
+ iri=EXAMPLE_ONTOLOGY_IRI,
257
+ axioms=boxes,
258
+ )
259
+ document = Document(ontology, prefix_map)
260
+ graph = Graph()
261
+ with tempfile.TemporaryDirectory() as directory:
262
+ stub = Path(directory).joinpath("test")
263
+ ofn_path = stub.with_suffix(".ofn")
264
+ text = document.to_funowl()
265
+ ofn_path.write_text(text)
266
+ ttl_path = stub.with_suffix(".ttl")
267
+ try:
268
+ convert(ofn_path, ttl_path)
269
+ except subprocess.CalledProcessError:
270
+ raise RuntimeError(f"failed to convert axioms from:\n\n{text}") from None
271
+ graph.parse(ttl_path)
272
+ return graph