pyobo 0.12.1__py3-none-any.whl → 0.12.3__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.
- pyobo/__init__.py +12 -4
- pyobo/getters.py +11 -3
- pyobo/identifier_utils/__init__.py +4 -1
- pyobo/identifier_utils/api.py +4 -3
- pyobo/sources/__init__.py +2 -0
- pyobo/sources/credit.py +17 -6
- pyobo/sources/drugbank/drugbank.py +1 -1
- pyobo/sources/gwascentral/gwascentral_study.py +1 -1
- pyobo/sources/intact.py +79 -0
- pyobo/struct/__init__.py +2 -1
- pyobo/struct/functional/ontology.py +2 -2
- pyobo/struct/obo/__init__.py +9 -0
- pyobo/{reader.py → struct/obo/reader.py} +21 -18
- pyobo/struct/obograph/__init__.py +16 -0
- pyobo/struct/obograph/export.py +315 -0
- pyobo/struct/obograph/reader.py +242 -0
- pyobo/struct/obograph/utils.py +47 -0
- pyobo/struct/struct.py +13 -23
- pyobo/struct/struct_utils.py +22 -14
- pyobo/struct/typedef.py +4 -0
- pyobo/struct/vocabulary.py +7 -0
- pyobo/version.py +1 -1
- {pyobo-0.12.1.dist-info → pyobo-0.12.3.dist-info}/METADATA +5 -16
- {pyobo-0.12.1.dist-info → pyobo-0.12.3.dist-info}/RECORD +171 -170
- {pyobo-0.12.1.dist-info → pyobo-0.12.3.dist-info}/WHEEL +1 -1
- pyobo/identifier_utils/preprocessing.json +0 -873
- pyobo/identifier_utils/preprocessing.py +0 -27
- pyobo/obographs.py +0 -147
- pyobo/resources/goc.py +0 -75
- pyobo/resources/goc.tsv +0 -188
- /pyobo/{reader_utils.py → struct/obo/reader_utils.py} +0 -0
- {pyobo-0.12.1.dist-info → pyobo-0.12.3.dist-info}/entry_points.txt +0 -0
- {pyobo-0.12.1.dist-info → pyobo-0.12.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
"""Exports to OBO Graph JSON."""
|
|
2
|
+
|
|
3
|
+
import tempfile
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import bioregistry
|
|
7
|
+
import curies
|
|
8
|
+
import obographs as og
|
|
9
|
+
from curies import Converter, ReferenceTuple
|
|
10
|
+
from curies import vocabulary as v
|
|
11
|
+
|
|
12
|
+
from pyobo.identifier_utils.api import get_converter
|
|
13
|
+
from pyobo.struct import Obo, OBOLiteral, Stanza, Term, TypeDef
|
|
14
|
+
from pyobo.struct import typedef as tdv
|
|
15
|
+
from pyobo.utils.io import safe_open
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"to_obograph",
|
|
19
|
+
"to_parsed_obograph",
|
|
20
|
+
"write_obograph",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def write_obograph(obo: Obo, path: str | Path, *, converter: Converter | None = None) -> None:
|
|
25
|
+
"""Write an ontology to a file as OBO Graph JSON."""
|
|
26
|
+
path = Path(path).expanduser().resolve()
|
|
27
|
+
raw_graph = to_obograph(obo, converter=converter)
|
|
28
|
+
with safe_open(path, read=False) as file:
|
|
29
|
+
file.write(raw_graph.model_dump_json(indent=2, exclude_none=True, exclude_unset=True))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def to_parsed_obograph_oracle(
|
|
33
|
+
obo: Obo, *, converter: Converter | None = None
|
|
34
|
+
) -> og.StandardizedGraphDocument:
|
|
35
|
+
"""Serialize to OBO, convert to OBO Graph JSON with ROBOT, load, then parse."""
|
|
36
|
+
import bioontologies.robot
|
|
37
|
+
|
|
38
|
+
if converter is None:
|
|
39
|
+
converter = get_converter()
|
|
40
|
+
|
|
41
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
42
|
+
stub = Path(directory).joinpath("test")
|
|
43
|
+
obo_path = stub.with_suffix(".obo")
|
|
44
|
+
obograph_path = stub.with_suffix(".json")
|
|
45
|
+
obo.write_obo(obo_path)
|
|
46
|
+
bioontologies.robot.convert(input_path=obo_path, output_path=obograph_path)
|
|
47
|
+
raw = og.read(obograph_path, squeeze=False)
|
|
48
|
+
rv = raw.standardize(converter)
|
|
49
|
+
for graph in rv.graphs:
|
|
50
|
+
if graph.meta and graph.meta.properties:
|
|
51
|
+
graph.meta.properties = [
|
|
52
|
+
p
|
|
53
|
+
for p in graph.meta.properties
|
|
54
|
+
if p.predicate.pair
|
|
55
|
+
!= ReferenceTuple(prefix="oboinowl", identifier="hasOBOFormatVersion")
|
|
56
|
+
] or None
|
|
57
|
+
return rv
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def to_obograph(obo: Obo, *, converter: Converter | None = None) -> og.GraphDocument:
|
|
61
|
+
"""Convert an ontology to a OBO Graph JSON document."""
|
|
62
|
+
if converter is None:
|
|
63
|
+
converter = get_converter()
|
|
64
|
+
return to_parsed_obograph(obo).to_raw(converter)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def to_parsed_obograph(obo: Obo) -> og.StandardizedGraphDocument:
|
|
68
|
+
"""Convert an ontology to a processed OBO Graph JSON document."""
|
|
69
|
+
return og.StandardizedGraphDocument(graphs=[_to_parsed_graph(obo)])
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _to_parsed_graph(obo: Obo) -> og.StandardizedGraph:
|
|
73
|
+
return og.StandardizedGraph(
|
|
74
|
+
id=f"http://purl.obolibrary.org/obo/{obo.ontology}.owl",
|
|
75
|
+
meta=_get_meta(obo),
|
|
76
|
+
nodes=_get_nodes(obo),
|
|
77
|
+
edges=_get_edges(obo),
|
|
78
|
+
equivalent_node_sets=_get_equivalent_node_sets(obo),
|
|
79
|
+
property_chain_axioms=_get_property_chain_axioms(obo),
|
|
80
|
+
domain_range_axioms=_get_domain_ranges(obo),
|
|
81
|
+
logical_definition_axioms=_get_logical_definition_axioms(obo),
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _get_logical_definition_axioms(obo: Obo) -> list[og.StandardizedLogicalDefinition]:
|
|
86
|
+
rv: list[og.StandardizedLogicalDefinition] = []
|
|
87
|
+
# TODO
|
|
88
|
+
return rv
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _get_domain_ranges(obo: Obo) -> list[og.StandardizedDomainRangeAxiom]:
|
|
92
|
+
rv = []
|
|
93
|
+
for typedef in obo.typedefs or []:
|
|
94
|
+
if typedef.domain or typedef.range:
|
|
95
|
+
rv.append(
|
|
96
|
+
og.StandardizedDomainRangeAxiom(
|
|
97
|
+
predicate=typedef.reference,
|
|
98
|
+
domains=[typedef.domain] if typedef.domain else [],
|
|
99
|
+
ranges=[typedef.range] if typedef.range else [],
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
return rv
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _get_equivalent_node_sets(obo: Obo) -> list[og.StandardizedEquivalentNodeSet]:
|
|
106
|
+
rv = []
|
|
107
|
+
for node in obo:
|
|
108
|
+
for e in node.equivalent_to:
|
|
109
|
+
rv.append(og.StandardizedEquivalentNodeSet(node=node.reference, equivalents=[e]))
|
|
110
|
+
return rv
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _get_property_chain_axioms(obo: Obo) -> list[og.StandardizedPropertyChainAxiom]:
|
|
114
|
+
rv = []
|
|
115
|
+
for typedef in obo.typedefs or []:
|
|
116
|
+
for chain in typedef.holds_over_chain:
|
|
117
|
+
rv.append(
|
|
118
|
+
og.StandardizedPropertyChainAxiom(
|
|
119
|
+
predicate=typedef.reference,
|
|
120
|
+
chain=chain,
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
# TODO typedef.equivalent_to_chain
|
|
124
|
+
return rv
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _get_meta(obo: Obo) -> og.StandardizedMeta | None:
|
|
128
|
+
properties = []
|
|
129
|
+
|
|
130
|
+
if description := bioregistry.get_description(obo.ontology):
|
|
131
|
+
properties.append(
|
|
132
|
+
og.StandardizedProperty(
|
|
133
|
+
predicate=v.has_description,
|
|
134
|
+
value=description,
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
for root_term in obo.root_terms or []:
|
|
139
|
+
properties.append(
|
|
140
|
+
og.StandardizedProperty(
|
|
141
|
+
predicate=v.has_ontology_root_term,
|
|
142
|
+
value=root_term,
|
|
143
|
+
)
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
if license_spdx_id := bioregistry.get_license(obo.ontology):
|
|
147
|
+
properties.append(
|
|
148
|
+
og.StandardizedProperty(
|
|
149
|
+
predicate=v.has_license,
|
|
150
|
+
value=license_spdx_id,
|
|
151
|
+
)
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
if obo.name:
|
|
155
|
+
properties.append(
|
|
156
|
+
og.StandardizedProperty(
|
|
157
|
+
predicate=v.has_title,
|
|
158
|
+
value=obo.name,
|
|
159
|
+
)
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
for p in obo.property_values or []:
|
|
163
|
+
properties.append(
|
|
164
|
+
og.StandardizedProperty(
|
|
165
|
+
predicate=p.predicate,
|
|
166
|
+
value=p.value.value if isinstance(p.value, OBOLiteral) else p.value,
|
|
167
|
+
)
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
if obo.data_version:
|
|
171
|
+
version_iri = (
|
|
172
|
+
f"http://purl.obolibrary.org/obo/{obo.ontology}/{obo.data_version}/{obo.ontology}.owl"
|
|
173
|
+
)
|
|
174
|
+
else:
|
|
175
|
+
version_iri = None
|
|
176
|
+
|
|
177
|
+
# comments don't make the round trip
|
|
178
|
+
subsets = [r for r, _ in obo.subsetdefs or []] or None
|
|
179
|
+
|
|
180
|
+
if not properties and not version_iri and not subsets:
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
return og.StandardizedMeta(
|
|
184
|
+
properties=properties or None,
|
|
185
|
+
version_iri=version_iri,
|
|
186
|
+
subsets=subsets,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _get_nodes(obo: Obo) -> list[og.StandardizedNode]:
|
|
191
|
+
rv = []
|
|
192
|
+
for term in obo:
|
|
193
|
+
rv.append(_get_class_node(term))
|
|
194
|
+
for typedef in _get_typedefs(obo):
|
|
195
|
+
rv.append(_get_typedef_node(typedef))
|
|
196
|
+
return rv
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _get_typedefs(obo: Obo) -> set[TypeDef]:
|
|
200
|
+
rv = set(obo.typedefs or [])
|
|
201
|
+
if obo.auto_generated_by:
|
|
202
|
+
rv.add(tdv.obo_autogenerated_by)
|
|
203
|
+
return rv
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _get_definition(stanza: Stanza) -> og.StandardizedDefinition | None:
|
|
207
|
+
if not stanza.definition:
|
|
208
|
+
return None
|
|
209
|
+
return og.StandardizedDefinition(
|
|
210
|
+
value=stanza.definition,
|
|
211
|
+
xrefs=[p for p in stanza.provenance if isinstance(p, curies.Reference)],
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _get_synonyms(stanza: Stanza) -> list[og.StandardizedSynonym] | None:
|
|
216
|
+
return [
|
|
217
|
+
og.StandardizedSynonym(
|
|
218
|
+
text=synonym.name,
|
|
219
|
+
predicate=v.synonym_scopes[synonym.specificity]
|
|
220
|
+
if synonym.specificity is not None
|
|
221
|
+
else v.has_related_synonym,
|
|
222
|
+
type=synonym.type,
|
|
223
|
+
xrefs=[p for p in synonym.provenance if isinstance(p, curies.Reference)],
|
|
224
|
+
)
|
|
225
|
+
for synonym in stanza.synonyms
|
|
226
|
+
] or None
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _get_properties(term: Stanza) -> list[og.StandardizedProperty] | None:
|
|
230
|
+
properties = []
|
|
231
|
+
for predicate, obj in term.iterate_object_properties():
|
|
232
|
+
properties.append(
|
|
233
|
+
og.StandardizedProperty(
|
|
234
|
+
predicate=predicate,
|
|
235
|
+
value=obj,
|
|
236
|
+
)
|
|
237
|
+
)
|
|
238
|
+
for predicate, literal in term.iterate_literal_properties():
|
|
239
|
+
properties.append(
|
|
240
|
+
og.StandardizedProperty(
|
|
241
|
+
predicate=predicate,
|
|
242
|
+
value=literal.value,
|
|
243
|
+
)
|
|
244
|
+
)
|
|
245
|
+
return properties or None
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _get_xrefs(stanza: Stanza) -> list[og.StandardizedXref] | None:
|
|
249
|
+
return [og.StandardizedXref(reference=xref) for xref in stanza.xrefs] or None
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _meta_or_none(meta: og.StandardizedMeta) -> og.StandardizedMeta | None:
|
|
253
|
+
if all(
|
|
254
|
+
x is None
|
|
255
|
+
for x in (
|
|
256
|
+
meta.definition,
|
|
257
|
+
meta.subsets,
|
|
258
|
+
meta.xrefs,
|
|
259
|
+
meta.synonyms,
|
|
260
|
+
meta.comments,
|
|
261
|
+
meta.version_iri,
|
|
262
|
+
meta.properties,
|
|
263
|
+
)
|
|
264
|
+
):
|
|
265
|
+
return None
|
|
266
|
+
return meta
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _get_class_node(term: Term) -> og.StandardizedNode:
|
|
270
|
+
meta = og.StandardizedMeta(
|
|
271
|
+
definition=_get_definition(term),
|
|
272
|
+
subsets=term.subsets or None,
|
|
273
|
+
xrefs=_get_xrefs(term),
|
|
274
|
+
synonyms=_get_synonyms(term),
|
|
275
|
+
comments=term.get_comments() or None,
|
|
276
|
+
deprecated=term.is_obsolete or False,
|
|
277
|
+
properties=_get_properties(term),
|
|
278
|
+
)
|
|
279
|
+
return og.StandardizedNode(
|
|
280
|
+
reference=term.reference,
|
|
281
|
+
label=term.name,
|
|
282
|
+
meta=_meta_or_none(meta),
|
|
283
|
+
type="CLASS" if term.type == "Term" else "INDIVIDUAL",
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _get_typedef_node(typedef: TypeDef) -> og.StandardizedNode:
|
|
288
|
+
meta = og.StandardizedMeta(
|
|
289
|
+
definition=_get_definition(typedef),
|
|
290
|
+
subsets=typedef.subsets or None,
|
|
291
|
+
xrefs=_get_xrefs(typedef),
|
|
292
|
+
synonyms=_get_synonyms(typedef),
|
|
293
|
+
comments=typedef.get_comments() or None,
|
|
294
|
+
deprecated=typedef.is_obsolete or False,
|
|
295
|
+
properties=_get_properties(typedef),
|
|
296
|
+
)
|
|
297
|
+
return og.StandardizedNode(
|
|
298
|
+
reference=typedef.reference,
|
|
299
|
+
label=typedef.name,
|
|
300
|
+
meta=_meta_or_none(meta),
|
|
301
|
+
type="PROPERTY",
|
|
302
|
+
property_type="ANNOTATION" if typedef.is_metadata_tag else "OBJECT",
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _get_edges(obo: Obo) -> list[og.StandardizedEdge]:
|
|
307
|
+
rv = [
|
|
308
|
+
og.StandardizedEdge(
|
|
309
|
+
subject=stanza.reference,
|
|
310
|
+
predicate=typedef.reference,
|
|
311
|
+
object=target,
|
|
312
|
+
)
|
|
313
|
+
for stanza, typedef, target in obo.iterate_edges(include_xrefs=False)
|
|
314
|
+
]
|
|
315
|
+
return rv
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""Import of OBO Graph JSON."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import curies
|
|
7
|
+
import obographs
|
|
8
|
+
from curies import Converter
|
|
9
|
+
from curies.vocabulary import SynonymScope, synonym_scopes
|
|
10
|
+
from obographs import (
|
|
11
|
+
Graph,
|
|
12
|
+
NodeType,
|
|
13
|
+
StandardizedGraph,
|
|
14
|
+
StandardizedMeta,
|
|
15
|
+
StandardizedNode,
|
|
16
|
+
StandardizedSynonym,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
from pyobo import Obo, Reference, StanzaType, Synonym, Term, TypeDef
|
|
20
|
+
from pyobo.identifier_utils import get_converter
|
|
21
|
+
from pyobo.struct import Annotation, OBOLiteral, make_ad_hoc_ontology
|
|
22
|
+
from pyobo.struct import vocabulary as v
|
|
23
|
+
from pyobo.struct.typedef import has_ontology_root_term
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"from_node",
|
|
27
|
+
"from_obograph",
|
|
28
|
+
"from_standardized_graph",
|
|
29
|
+
"read_obograph",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def read_obograph(
|
|
36
|
+
prefix: str, path: str | Path, *, converter: Converter | None = None, strict: bool = False
|
|
37
|
+
) -> Obo:
|
|
38
|
+
"""Read an OBO Graph JSON file using :func:`obographs.read` then process into a PyOBO structure."""
|
|
39
|
+
graph = obographs.read(path, squeeze=True)
|
|
40
|
+
return from_obograph(prefix=prefix, graph=graph, converter=converter, strict=strict)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def from_obograph(
|
|
44
|
+
prefix: str, graph: Graph, *, converter: Converter | None = None, strict: bool = False
|
|
45
|
+
) -> Obo:
|
|
46
|
+
"""Parse a raw OBO Graph JSON into a PyOBO structure."""
|
|
47
|
+
if converter is None:
|
|
48
|
+
converter = get_converter()
|
|
49
|
+
standardized_graph = graph.standardize(converter, strict=strict)
|
|
50
|
+
return from_standardized_graph(prefix, standardized_graph)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def from_standardized_graph(prefix: str, graph: StandardizedGraph) -> Obo:
|
|
54
|
+
"""Generate an OBO data structure from OBO Graph JSON."""
|
|
55
|
+
terms: dict[Reference, Term] = {}
|
|
56
|
+
typedefs: dict[Reference, TypeDef] = {}
|
|
57
|
+
for node in graph.nodes:
|
|
58
|
+
stanza = from_node(node)
|
|
59
|
+
match stanza:
|
|
60
|
+
case Term():
|
|
61
|
+
terms[stanza.reference] = stanza
|
|
62
|
+
case TypeDef():
|
|
63
|
+
typedefs[stanza.reference] = stanza
|
|
64
|
+
|
|
65
|
+
for edge in graph.edges:
|
|
66
|
+
s, p, o = (Reference.from_reference(r) for r in (edge.subject, edge.predicate, edge.object))
|
|
67
|
+
if s in terms:
|
|
68
|
+
stanza = terms[s]
|
|
69
|
+
stanza.append_relationship(p, o)
|
|
70
|
+
elif s in typedefs:
|
|
71
|
+
stanza = typedefs[s]
|
|
72
|
+
stanza.append_relationship(p, o)
|
|
73
|
+
|
|
74
|
+
root_terms: list[Reference] = []
|
|
75
|
+
property_values = []
|
|
76
|
+
auto_generated_by: str | None = None
|
|
77
|
+
if graph.meta:
|
|
78
|
+
for prop in graph.meta.properties or []:
|
|
79
|
+
predicate = Reference.from_reference(prop.predicate)
|
|
80
|
+
if predicate == has_ontology_root_term:
|
|
81
|
+
if isinstance(prop.value, str):
|
|
82
|
+
raise TypeError
|
|
83
|
+
else:
|
|
84
|
+
root_terms.append(Reference.from_reference(prop.value))
|
|
85
|
+
elif predicate == v.obo_autogenerated_by:
|
|
86
|
+
if not isinstance(prop.value, str):
|
|
87
|
+
raise TypeError
|
|
88
|
+
auto_generated_by = prop.value
|
|
89
|
+
# TODO specific subsetdef, imports
|
|
90
|
+
else:
|
|
91
|
+
property_values.append(
|
|
92
|
+
Annotation(
|
|
93
|
+
predicate=predicate,
|
|
94
|
+
# TODO obographs are limited by ability to specify datatype?
|
|
95
|
+
value=OBOLiteral.string(prop.value)
|
|
96
|
+
if isinstance(prop.value, str)
|
|
97
|
+
else Reference.from_reference(prop.value),
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
for equivalent_node_set in graph.equivalent_node_sets:
|
|
102
|
+
equivalent_reference = Reference.from_reference(equivalent_node_set.node)
|
|
103
|
+
if equivalent_reference in terms:
|
|
104
|
+
for equivalent in equivalent_node_set.equivalents:
|
|
105
|
+
terms[equivalent_reference].append_equivalent_to(
|
|
106
|
+
Reference.from_reference(equivalent)
|
|
107
|
+
)
|
|
108
|
+
elif equivalent_reference in typedefs:
|
|
109
|
+
for equivalent in equivalent_node_set.equivalents:
|
|
110
|
+
typedefs[equivalent_reference].append_equivalent_to(
|
|
111
|
+
Reference.from_reference(equivalent)
|
|
112
|
+
)
|
|
113
|
+
else:
|
|
114
|
+
logger.warning(
|
|
115
|
+
"unknown reference node in equivalent_node_set: %s", equivalent_reference.curie
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
for _domain_range_axiom in graph.domain_range_axioms or []:
|
|
119
|
+
p = Reference.from_reference(_domain_range_axiom.predicate)
|
|
120
|
+
if p not in typedefs:
|
|
121
|
+
continue
|
|
122
|
+
# the OBO Graph model allows for multiple ranges
|
|
123
|
+
# or domains, but OBO only one.
|
|
124
|
+
if _domain_range_axiom.ranges:
|
|
125
|
+
typedefs[p].range = Reference.from_reference(_domain_range_axiom.ranges[0])
|
|
126
|
+
if _domain_range_axiom.domains:
|
|
127
|
+
typedefs[p].domain = Reference.from_reference(_domain_range_axiom.domains[0])
|
|
128
|
+
|
|
129
|
+
for _property_chain_axiom in graph.property_chain_axioms:
|
|
130
|
+
p = Reference.from_reference(_property_chain_axiom.predicate)
|
|
131
|
+
if p not in typedefs or not _property_chain_axiom.chain:
|
|
132
|
+
continue
|
|
133
|
+
# TODO check if its also transitive_over and/or equivalent_to_chain
|
|
134
|
+
typedefs[p].holds_over_chain.append(
|
|
135
|
+
[Reference.from_reference(r) for r in _property_chain_axiom.chain]
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
for _logical_definition_axiom in graph.logical_definition_axioms:
|
|
139
|
+
pass # TODO
|
|
140
|
+
|
|
141
|
+
return make_ad_hoc_ontology(
|
|
142
|
+
_ontology=prefix,
|
|
143
|
+
_name=graph.name,
|
|
144
|
+
terms=list(terms.values()),
|
|
145
|
+
_typedefs=list(typedefs.values()),
|
|
146
|
+
_root_terms=root_terms,
|
|
147
|
+
_property_values=property_values,
|
|
148
|
+
_data_version=graph.version or (graph.meta.version_iri if graph.meta is not None else None),
|
|
149
|
+
_auto_generated_by=auto_generated_by,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
#: A mapping between OBO Graph JSON node types and OBO stanza types
|
|
154
|
+
MAPPING: dict[NodeType, StanzaType] = {
|
|
155
|
+
"CLASS": "Term",
|
|
156
|
+
"INDIVIDUAL": "Instance",
|
|
157
|
+
"PROPERTY": "TypeDef",
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def from_node(node: StandardizedNode) -> Term | TypeDef:
|
|
162
|
+
"""Generate a term from a node."""
|
|
163
|
+
if node.type == "PROPERTY":
|
|
164
|
+
return _from_property(node)
|
|
165
|
+
return _from_term(node)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _from_term(node: StandardizedNode) -> Term:
|
|
169
|
+
term = Term(
|
|
170
|
+
reference=_get_ref(node),
|
|
171
|
+
type=MAPPING[node.type] if node.type else "Term",
|
|
172
|
+
)
|
|
173
|
+
if node.meta is not None:
|
|
174
|
+
_process_term_meta(node.meta, term)
|
|
175
|
+
return term
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _from_property(node: StandardizedNode) -> TypeDef:
|
|
179
|
+
typedef = TypeDef(
|
|
180
|
+
reference=_get_ref(node),
|
|
181
|
+
is_metadata_tag=node.property_type == "ANNOTATION",
|
|
182
|
+
)
|
|
183
|
+
if node.meta is not None:
|
|
184
|
+
_process_typedef_meta(node.meta, typedef)
|
|
185
|
+
return typedef
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _get_ref(node: StandardizedNode) -> Reference:
|
|
189
|
+
return Reference(
|
|
190
|
+
prefix=node.reference.prefix,
|
|
191
|
+
identifier=node.reference.identifier,
|
|
192
|
+
name=node.label,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _process_term_meta(meta: StandardizedMeta, term: Term) -> None:
|
|
197
|
+
"""Process the ``meta`` object associated with a term node."""
|
|
198
|
+
if meta.definition:
|
|
199
|
+
term.definition = meta.definition.value
|
|
200
|
+
for definition_xref in meta.definition.xrefs or []:
|
|
201
|
+
term.append_definition_xref(definition_xref)
|
|
202
|
+
|
|
203
|
+
if meta.subsets:
|
|
204
|
+
term.subsets.extend(Reference.from_reference(r) for r in meta.subsets)
|
|
205
|
+
|
|
206
|
+
for xref in meta.xrefs or []:
|
|
207
|
+
term.append_xref(xref.reference)
|
|
208
|
+
|
|
209
|
+
for synonym in meta.synonyms or []:
|
|
210
|
+
if s := _from_synonym(synonym):
|
|
211
|
+
term.append_synonym(s)
|
|
212
|
+
|
|
213
|
+
for comment in meta.comments or []:
|
|
214
|
+
term.append_comment(comment)
|
|
215
|
+
|
|
216
|
+
if meta.deprecated:
|
|
217
|
+
term.is_obsolete = True
|
|
218
|
+
|
|
219
|
+
for prop in meta.properties or []:
|
|
220
|
+
match prop.value:
|
|
221
|
+
case Reference():
|
|
222
|
+
term.annotate_object(prop.predicate, prop.value)
|
|
223
|
+
case str():
|
|
224
|
+
# note, OBO Graph format does not allow for annotating data type
|
|
225
|
+
term.annotate_literal(prop.predicate, OBOLiteral.string(prop.value))
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
REV_SYNONYM_SCOPE: dict[curies.Reference, SynonymScope] = {v: k for k, v in synonym_scopes.items()}
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _from_synonym(syn: StandardizedSynonym) -> Synonym | None:
|
|
232
|
+
return Synonym(
|
|
233
|
+
name=syn.text,
|
|
234
|
+
specificity=REV_SYNONYM_SCOPE[syn.predicate],
|
|
235
|
+
type=Reference.from_reference(syn.type) if syn.type is not None else None,
|
|
236
|
+
provenance=[Reference.from_reference(r) for r in syn.xrefs or []],
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _process_typedef_meta(meta: StandardizedMeta, typedef: TypeDef) -> None:
|
|
241
|
+
"""Process the ``meta`` object associated with a property node."""
|
|
242
|
+
# TODO everything else is in here
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Testing utilities."""
|
|
2
|
+
|
|
3
|
+
import unittest
|
|
4
|
+
from typing import cast
|
|
5
|
+
|
|
6
|
+
from curies import Reference
|
|
7
|
+
from obographs import StandardizedGraph, StandardizedMeta
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"assert_graph_equal",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def assert_graph_equal(
|
|
15
|
+
test_case: unittest.TestCase, expected: StandardizedGraph, actual: StandardizedGraph
|
|
16
|
+
) -> None:
|
|
17
|
+
"""Assert two graphs are equal."""
|
|
18
|
+
if expected.meta is not None:
|
|
19
|
+
test_case.assertIsNotNone(actual.meta)
|
|
20
|
+
test_case.assertEqual(
|
|
21
|
+
expected.meta.model_dump(exclude_unset=True, exclude_none=True, exclude_defaults=True),
|
|
22
|
+
cast(StandardizedMeta, actual.meta).model_dump(
|
|
23
|
+
exclude_unset=True, exclude_none=True, exclude_defaults=True
|
|
24
|
+
),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# strip out extra info
|
|
28
|
+
for node in actual.nodes:
|
|
29
|
+
node.reference = Reference.from_reference(node.reference)
|
|
30
|
+
|
|
31
|
+
test_case.assertEqual(
|
|
32
|
+
{node.reference.curie: node for node in expected.nodes},
|
|
33
|
+
{node.reference.curie: node for node in actual.nodes},
|
|
34
|
+
)
|
|
35
|
+
test_case.assertEqual(
|
|
36
|
+
{node.as_str_triple(): node for node in expected.edges},
|
|
37
|
+
{node.as_str_triple(): node for node in actual.edges},
|
|
38
|
+
)
|
|
39
|
+
excludes = {"nodes", "edges", "meta"}
|
|
40
|
+
test_case.assertEqual(
|
|
41
|
+
expected.model_dump(
|
|
42
|
+
exclude_none=True, exclude_unset=True, exclude_defaults=True, exclude=excludes
|
|
43
|
+
),
|
|
44
|
+
actual.model_dump(
|
|
45
|
+
exclude_none=True, exclude_unset=True, exclude_defaults=True, exclude=excludes
|
|
46
|
+
),
|
|
47
|
+
)
|