biocypher 0.8.0__py3-none-any.whl → 0.9.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.
Potentially problematic release.
This version of biocypher might be problematic. Click here for more details.
- biocypher/_config/biocypher_config.yaml +7 -1
- biocypher/_core.py +25 -4
- biocypher/_metadata.py +1 -1
- biocypher/_ontology.py +144 -51
- biocypher/_translate.py +84 -79
- biocypher/output/write/_batch_writer.py +99 -50
- biocypher/output/write/_get_writer.py +29 -12
- biocypher/output/write/graph/_arangodb.py +44 -32
- biocypher/output/write/graph/_neo4j.py +3 -4
- biocypher/output/write/graph/_owl.py +569 -0
- biocypher/output/write/graph/_rdf.py +234 -97
- {biocypher-0.8.0.dist-info → biocypher-0.9.1.dist-info}/METADATA +1 -1
- {biocypher-0.8.0.dist-info → biocypher-0.9.1.dist-info}/RECORD +15 -14
- {biocypher-0.8.0.dist-info → biocypher-0.9.1.dist-info}/LICENSE +0 -0
- {biocypher-0.8.0.dist-info → biocypher-0.9.1.dist-info}/WHEEL +0 -0
|
@@ -128,7 +128,13 @@ postgresql:
|
|
|
128
128
|
|
|
129
129
|
rdf:
|
|
130
130
|
### RDF configuration ###
|
|
131
|
-
|
|
131
|
+
file_format: turtle # turtle or ttl, xml, json-ld, ntriples, n3, trig, trix or nquads
|
|
132
|
+
|
|
133
|
+
owl:
|
|
134
|
+
### OWL configuration ###
|
|
135
|
+
file_format: turtle # turtle or ttl, xml, json-ld, ntriples, n3, trig, trix or nquads
|
|
136
|
+
edge_model: Association # or: ObjectProperty
|
|
137
|
+
file_stem: biocypher # without the extension
|
|
132
138
|
|
|
133
139
|
sqlite:
|
|
134
140
|
### SQLite configuration ###
|
biocypher/_core.py
CHANGED
|
@@ -118,6 +118,15 @@ class BioCypher:
|
|
|
118
118
|
else:
|
|
119
119
|
self._offline = offline
|
|
120
120
|
|
|
121
|
+
# Check if pandas/tabular is being used in offline mode
|
|
122
|
+
if self._offline and self._dbms.lower() in ["pandas", "tabular"]:
|
|
123
|
+
msg = (
|
|
124
|
+
f"The '{self._dbms}' DBMS is only available in online mode. "
|
|
125
|
+
f"If you want to write CSV files, use 'csv' as the DBMS. "
|
|
126
|
+
f"If you want to use pandas, set 'offline: false' in your configuration."
|
|
127
|
+
)
|
|
128
|
+
raise ValueError(msg)
|
|
129
|
+
|
|
121
130
|
if strict_mode is None:
|
|
122
131
|
self._strict_mode = self.base_config["strict_mode"]
|
|
123
132
|
else:
|
|
@@ -225,19 +234,31 @@ class BioCypher:
|
|
|
225
234
|
The knowledge graph is returned based on the `dbms` parameter in
|
|
226
235
|
the biocypher configuration file.
|
|
227
236
|
|
|
237
|
+
TODO: These conditionals are a hack, we need to refactor the in-memory
|
|
238
|
+
KG to be generic, and simplify access and conversion to output formats.
|
|
239
|
+
|
|
228
240
|
Returns
|
|
229
241
|
-------
|
|
230
242
|
Any: knowledge graph.
|
|
231
243
|
|
|
232
244
|
"""
|
|
245
|
+
# If we're using an in-memory KG and it already exists, return it directly
|
|
246
|
+
if self._in_memory_kg and self._is_online_and_in_memory():
|
|
247
|
+
return self._in_memory_kg.get_kg()
|
|
248
|
+
|
|
249
|
+
# Otherwise, initialize and populate the in-memory KG
|
|
233
250
|
if not self._in_memory_kg:
|
|
234
251
|
self._initialize_in_memory_kg()
|
|
235
252
|
if not self._translator:
|
|
236
253
|
self._get_translator()
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
self
|
|
240
|
-
|
|
254
|
+
|
|
255
|
+
# These attributes might not exist when using in-memory KG directly
|
|
256
|
+
if hasattr(self, "_nodes") and hasattr(self, "_edges"):
|
|
257
|
+
tnodes = self._translator.translate_entities(self._nodes)
|
|
258
|
+
tedges = self._translator.translate_entities(self._edges)
|
|
259
|
+
self._in_memory_kg.add_nodes(tnodes)
|
|
260
|
+
self._in_memory_kg.add_edges(tedges)
|
|
261
|
+
|
|
241
262
|
return self._in_memory_kg.get_kg()
|
|
242
263
|
|
|
243
264
|
def _get_deduplicator(self) -> Deduplicator:
|
biocypher/_metadata.py
CHANGED
biocypher/_ontology.py
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
"""BioCypher 'ontology' module
|
|
2
|
-
|
|
3
|
-
other advanced operations.
|
|
1
|
+
"""BioCypher 'ontology' module to parse and represent ontologies.
|
|
2
|
+
|
|
3
|
+
Also performs ontology hybridisation and other advanced operations.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import os
|
|
7
7
|
|
|
8
8
|
from datetime import datetime
|
|
9
|
+
from itertools import chain
|
|
9
10
|
from typing import Optional
|
|
10
11
|
|
|
11
12
|
import networkx as nx
|
|
@@ -27,14 +28,15 @@ logger.debug(f"Loading module {__name__}.")
|
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
class OntologyAdapter:
|
|
30
|
-
"""Class that represents an ontology to be used in the Biocypher framework.
|
|
31
|
-
|
|
31
|
+
"""Class that represents an ontology to be used in the Biocypher framework.
|
|
32
|
+
|
|
33
|
+
Can read from a variety of formats, including OWL, OBO, and RDF/XML. The
|
|
32
34
|
ontology is represented by a networkx.DiGraph object; an RDFlib graph is
|
|
33
35
|
also kept. By default, the DiGraph reverses the label and identifier of the
|
|
34
36
|
nodes, such that the node name in the graph is the human-readable label. The
|
|
35
|
-
edges are oriented from child to parent.
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
edges are oriented from child to parent. Labels are formatted in lower
|
|
38
|
+
sentence case and underscores are replaced by spaces. Identifiers are taken
|
|
39
|
+
as defined and the prefixes are removed by default.
|
|
38
40
|
"""
|
|
39
41
|
|
|
40
42
|
def __init__(
|
|
@@ -123,7 +125,13 @@ class OntologyAdapter:
|
|
|
123
125
|
|
|
124
126
|
"""
|
|
125
127
|
one_to_one_inheritance_graph = Graph()
|
|
126
|
-
for s, p, o in g.triples((None, rdflib.RDFS.subClassOf, None)):
|
|
128
|
+
# for s, p, o in g.triples((None, rdflib.RDFS.subClassOf, None)):
|
|
129
|
+
for s, p, o in chain(
|
|
130
|
+
g.triples((None, rdflib.RDFS.subClassOf, None)), # Node classes
|
|
131
|
+
g.triples((None, rdflib.RDF.type, rdflib.RDFS.Class)), # Root classes
|
|
132
|
+
g.triples((None, rdflib.RDFS.subPropertyOf, None)), # OWL "edges" classes
|
|
133
|
+
g.triples((None, rdflib.RDF.type, rdflib.OWL.ObjectProperty)), # OWL "edges" root classes
|
|
134
|
+
):
|
|
127
135
|
if self.has_label(s, g):
|
|
128
136
|
one_to_one_inheritance_graph.add((s, p, o))
|
|
129
137
|
return one_to_one_inheritance_graph
|
|
@@ -149,7 +157,10 @@ class OntologyAdapter:
|
|
|
149
157
|
) in multiple_inheritance:
|
|
150
158
|
parents = self._retrieve_rdf_linked_list(first_node_of_intersection_list)
|
|
151
159
|
child_name = None
|
|
152
|
-
for s_, _, _ in
|
|
160
|
+
for s_, _, _ in chain(
|
|
161
|
+
g.triples((None, rdflib.RDFS.subClassOf, node)),
|
|
162
|
+
g.triples((None, rdflib.RDFS.subPropertyOf, node)),
|
|
163
|
+
):
|
|
153
164
|
child_name = s_
|
|
154
165
|
|
|
155
166
|
# Handle Snomed CT post coordinated expressions
|
|
@@ -165,7 +176,7 @@ class OntologyAdapter:
|
|
|
165
176
|
return intersection
|
|
166
177
|
|
|
167
178
|
def has_label(self, node: rdflib.URIRef, g: rdflib.Graph) -> bool:
|
|
168
|
-
"""
|
|
179
|
+
"""Check if the node has a label in the graph.
|
|
169
180
|
|
|
170
181
|
Args:
|
|
171
182
|
----
|
|
@@ -178,16 +189,22 @@ class OntologyAdapter:
|
|
|
178
189
|
return (node, rdflib.RDFS.label, None) in g
|
|
179
190
|
|
|
180
191
|
def _retrieve_rdf_linked_list(self, subject: rdflib.URIRef) -> list:
|
|
181
|
-
"""Recursively
|
|
192
|
+
"""Recursively retrieve a linked list from RDF.
|
|
193
|
+
|
|
182
194
|
Example RDF list with the items [item1, item2]:
|
|
183
195
|
list_node - first -> item1
|
|
184
196
|
list_node - rest -> list_node2
|
|
185
197
|
list_node2 - first -> item2
|
|
186
198
|
list_node2 - rest -> nil
|
|
199
|
+
|
|
187
200
|
Args:
|
|
201
|
+
----
|
|
188
202
|
subject (rdflib.URIRef): One list_node of the RDF list
|
|
203
|
+
|
|
189
204
|
Returns:
|
|
205
|
+
-------
|
|
190
206
|
list: The items of the RDF list
|
|
207
|
+
|
|
191
208
|
"""
|
|
192
209
|
g = self._rdf_graph
|
|
193
210
|
rdf_list = []
|
|
@@ -247,11 +264,13 @@ class OntologyAdapter:
|
|
|
247
264
|
switch_label_and_id: bool,
|
|
248
265
|
rename_nodes: bool = True,
|
|
249
266
|
) -> nx.DiGraph:
|
|
250
|
-
"""Change the nodes in the networkx graph to BioCypher format
|
|
251
|
-
- remove the prefix of the identifier
|
|
252
|
-
- switch id and label
|
|
253
|
-
- adapt the labels (replace _ with space and convert to lower sentence case)
|
|
267
|
+
"""Change the nodes in the networkx graph to BioCypher format.
|
|
254
268
|
|
|
269
|
+
This involves:
|
|
270
|
+
- removing the prefix of the identifier
|
|
271
|
+
- switching the id and label if requested
|
|
272
|
+
- adapting the labels (replace _ with space and convert to lower
|
|
273
|
+
sentence case)
|
|
255
274
|
Args:
|
|
256
275
|
----
|
|
257
276
|
nx_graph (nx.DiGraph): The networkx graph
|
|
@@ -333,16 +352,28 @@ class OntologyAdapter:
|
|
|
333
352
|
labels_in_ontology = []
|
|
334
353
|
for label_subject, _, label_in_ontology in g.triples((None, rdflib.RDFS.label, None)):
|
|
335
354
|
labels_in_ontology.append(str(label_in_ontology))
|
|
336
|
-
|
|
355
|
+
msg = (
|
|
337
356
|
f"Could not find root node with label '{root_label}'. "
|
|
338
|
-
f"The ontology contains the following labels: {labels_in_ontology}"
|
|
357
|
+
f"The ontology contains the following labels: {labels_in_ontology}"
|
|
339
358
|
)
|
|
359
|
+
logger.error(msg)
|
|
360
|
+
raise ValueError(msg)
|
|
340
361
|
return root
|
|
341
362
|
|
|
342
363
|
def _remove_prefix(self, uri: str) -> str:
|
|
343
|
-
"""Remove the prefix of a URI.
|
|
344
|
-
|
|
345
|
-
|
|
364
|
+
"""Remove the prefix of a URI.
|
|
365
|
+
|
|
366
|
+
URIs can contain either "#" or "/" as a separator between the prefix
|
|
367
|
+
and the local name. The prefix is everything before the last separator.
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
----
|
|
371
|
+
uri (str): The URI to remove the prefix from
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
-------
|
|
375
|
+
str: The URI without the prefix
|
|
376
|
+
|
|
346
377
|
"""
|
|
347
378
|
if self._remove_prefixes:
|
|
348
379
|
return uri.rsplit("#", 1)[-1].rsplit("/", 1)[-1]
|
|
@@ -350,8 +381,18 @@ class OntologyAdapter:
|
|
|
350
381
|
return uri
|
|
351
382
|
|
|
352
383
|
def _load_rdf_graph(self, ontology_file):
|
|
353
|
-
"""Load the ontology into an RDFlib graph.
|
|
354
|
-
|
|
384
|
+
"""Load the ontology into an RDFlib graph.
|
|
385
|
+
|
|
386
|
+
The ontology file can be in OWL, OBO, or RDF/XML format.
|
|
387
|
+
|
|
388
|
+
Args:
|
|
389
|
+
----
|
|
390
|
+
ontology_file (str): The path to the ontology file
|
|
391
|
+
|
|
392
|
+
Returns:
|
|
393
|
+
-------
|
|
394
|
+
rdflib.Graph: The RDFlib graph
|
|
395
|
+
|
|
355
396
|
"""
|
|
356
397
|
g = rdflib.Graph()
|
|
357
398
|
g.parse(ontology_file, format=self._get_format(ontology_file))
|
|
@@ -369,18 +410,24 @@ class OntologyAdapter:
|
|
|
369
410
|
elif self._format == "ttl":
|
|
370
411
|
return self._format
|
|
371
412
|
else:
|
|
372
|
-
|
|
413
|
+
msg = f"Could not determine format of ontology file {ontology_file}"
|
|
414
|
+
logger.error(msg)
|
|
415
|
+
raise ValueError(msg)
|
|
373
416
|
|
|
374
417
|
if ontology_file.endswith(".owl"):
|
|
375
418
|
return "application/rdf+xml"
|
|
376
419
|
elif ontology_file.endswith(".obo"):
|
|
377
|
-
|
|
420
|
+
msg = "OBO format not yet supported"
|
|
421
|
+
logger.error(msg)
|
|
422
|
+
raise NotImplementedError(msg)
|
|
378
423
|
elif ontology_file.endswith(".rdf"):
|
|
379
424
|
return "application/rdf+xml"
|
|
380
425
|
elif ontology_file.endswith(".ttl"):
|
|
381
426
|
return "ttl"
|
|
382
427
|
else:
|
|
383
|
-
|
|
428
|
+
msg = f"Could not determine format of ontology file {ontology_file}"
|
|
429
|
+
logger.error(msg)
|
|
430
|
+
raise ValueError(msg)
|
|
384
431
|
|
|
385
432
|
def get_nx_graph(self):
|
|
386
433
|
"""Get the networkx graph representing the ontology."""
|
|
@@ -395,8 +442,8 @@ class OntologyAdapter:
|
|
|
395
442
|
|
|
396
443
|
Returns
|
|
397
444
|
-------
|
|
398
|
-
root_node: If _switch_label_and_id is True, the root node label is
|
|
399
|
-
otherwise the root node id is returned.
|
|
445
|
+
root_node: If _switch_label_and_id is True, the root node label is
|
|
446
|
+
returned, otherwise the root node id is returned.
|
|
400
447
|
|
|
401
448
|
"""
|
|
402
449
|
root_node = None
|
|
@@ -422,10 +469,11 @@ class OntologyAdapter:
|
|
|
422
469
|
|
|
423
470
|
|
|
424
471
|
class Ontology:
|
|
425
|
-
"""A class that represents the ontological "backbone" of a
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
472
|
+
"""A class that represents the ontological "backbone" of a KG.
|
|
473
|
+
|
|
474
|
+
The ontology can be built from a single resource, or hybridised from a
|
|
475
|
+
combination of resources, with one resource being the "head" ontology, while
|
|
476
|
+
an arbitrary number of other resources can become "tail" ontologies at
|
|
429
477
|
arbitrary fusion points inside the "head" ontology.
|
|
430
478
|
"""
|
|
431
479
|
|
|
@@ -458,10 +506,11 @@ class Ontology:
|
|
|
458
506
|
self._main()
|
|
459
507
|
|
|
460
508
|
def _main(self) -> None:
|
|
461
|
-
"""
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
the
|
|
509
|
+
"""Instantiate the ontology.
|
|
510
|
+
|
|
511
|
+
Loads the ontologies, joins them, and returns the hybrid ontology.
|
|
512
|
+
Loads only the head ontology if nothing else is given. Adds user
|
|
513
|
+
extensions and properties from the mapping.
|
|
465
514
|
"""
|
|
466
515
|
self._load_ontologies()
|
|
467
516
|
|
|
@@ -481,8 +530,10 @@ class Ontology:
|
|
|
481
530
|
self._add_properties()
|
|
482
531
|
|
|
483
532
|
def _load_ontologies(self) -> None:
|
|
484
|
-
"""For each ontology, load the OntologyAdapter object
|
|
485
|
-
|
|
533
|
+
"""For each ontology, load the OntologyAdapter object.
|
|
534
|
+
|
|
535
|
+
Store it as an instance variable (head) or in an instance dictionary
|
|
536
|
+
(tail).
|
|
486
537
|
"""
|
|
487
538
|
logger.info("Loading ontologies...")
|
|
488
539
|
|
|
@@ -506,15 +557,24 @@ class Ontology:
|
|
|
506
557
|
)
|
|
507
558
|
|
|
508
559
|
def _get_head_join_node(self, adapter: OntologyAdapter) -> str:
|
|
509
|
-
"""
|
|
510
|
-
|
|
511
|
-
|
|
560
|
+
"""Try to find the head join node of the given ontology adapter.
|
|
561
|
+
|
|
562
|
+
Find the node in the head ontology that is the head join node. If the
|
|
563
|
+
join node is not found, the method will raise an error.
|
|
512
564
|
|
|
513
565
|
Args:
|
|
514
566
|
----
|
|
515
567
|
adapter (OntologyAdapter): The ontology adapter of which to find the
|
|
516
568
|
join node in the head ontology.
|
|
517
569
|
|
|
570
|
+
Returns:
|
|
571
|
+
-------
|
|
572
|
+
str: The head join node in the head ontology.
|
|
573
|
+
|
|
574
|
+
Raises:
|
|
575
|
+
------
|
|
576
|
+
ValueError: If the head join node is not found in the head ontology.
|
|
577
|
+
|
|
518
578
|
"""
|
|
519
579
|
head_join_node = None
|
|
520
580
|
user_defined_head_join_node_label = adapter.get_head_join_node()
|
|
@@ -535,14 +595,18 @@ class Ontology:
|
|
|
535
595
|
self._head_ontology._switch_label_and_id,
|
|
536
596
|
rename_nodes=False,
|
|
537
597
|
)
|
|
538
|
-
|
|
598
|
+
msg = (
|
|
539
599
|
f"Head join node '{head_join_node}' not found in head ontology. "
|
|
540
|
-
f"The head ontology contains the following nodes: {head_ontology.nodes}."
|
|
600
|
+
f"The head ontology contains the following nodes: {head_ontology.nodes}."
|
|
541
601
|
)
|
|
602
|
+
logger.error(msg)
|
|
603
|
+
raise ValueError(msg)
|
|
542
604
|
return head_join_node
|
|
543
605
|
|
|
544
606
|
def _join_ontologies(self, adapter: OntologyAdapter, head_join_node) -> None:
|
|
545
|
-
"""
|
|
607
|
+
"""Join the present ontologies.
|
|
608
|
+
|
|
609
|
+
Join two ontologies by adding the tail ontology as a subgraph to the
|
|
546
610
|
head ontology at the specified join nodes.
|
|
547
611
|
|
|
548
612
|
Args:
|
|
@@ -593,10 +657,13 @@ class Ontology:
|
|
|
593
657
|
self._nx_graph = self._head_ontology.get_nx_graph().copy()
|
|
594
658
|
|
|
595
659
|
for key, value in self.mapping.extended_schema.items():
|
|
660
|
+
# If this class is either a root or a synonym.
|
|
596
661
|
if not value.get("is_a"):
|
|
662
|
+
# If it is a synonym.
|
|
597
663
|
if self._nx_graph.has_node(value.get("synonym_for")):
|
|
598
664
|
continue
|
|
599
665
|
|
|
666
|
+
# If this class is in the schema, but not in the loaded vocabulary.
|
|
600
667
|
if not self._nx_graph.has_node(key):
|
|
601
668
|
msg = (
|
|
602
669
|
f"Node {key} not found in ontology, but also has no inheritance definition. Please check your "
|
|
@@ -606,8 +673,10 @@ class Ontology:
|
|
|
606
673
|
logger.error(msg)
|
|
607
674
|
raise ValueError(msg)
|
|
608
675
|
|
|
676
|
+
# It is a root and it is in the loaded vocabulary.
|
|
609
677
|
continue
|
|
610
678
|
|
|
679
|
+
# It is not a root.
|
|
611
680
|
parents = to_list(value.get("is_a"))
|
|
612
681
|
child = key
|
|
613
682
|
|
|
@@ -661,7 +730,9 @@ class Ontology:
|
|
|
661
730
|
self._nx_graph.add_edge(node, "entity")
|
|
662
731
|
|
|
663
732
|
def _add_properties(self) -> None:
|
|
664
|
-
"""
|
|
733
|
+
"""Add properties to the ontology.
|
|
734
|
+
|
|
735
|
+
For each entity in the mapping, update the ontology with the properties
|
|
665
736
|
specified in the mapping. Updates synonym information in the graph,
|
|
666
737
|
setting the synonym as the primary node label.
|
|
667
738
|
"""
|
|
@@ -672,7 +743,9 @@ class Ontology:
|
|
|
672
743
|
if value.get("synonym_for"):
|
|
673
744
|
# change node label to synonym
|
|
674
745
|
if value["synonym_for"] not in self._nx_graph.nodes:
|
|
675
|
-
|
|
746
|
+
msg = f"Node {value['synonym_for']} not found in ontology."
|
|
747
|
+
logger.error(msg)
|
|
748
|
+
raise ValueError(msg)
|
|
676
749
|
|
|
677
750
|
self._nx_graph = nx.relabel_nodes(self._nx_graph, {value["synonym_for"]: key})
|
|
678
751
|
|
|
@@ -706,16 +779,20 @@ class Ontology:
|
|
|
706
779
|
|
|
707
780
|
"""
|
|
708
781
|
if not full and not self.mapping.extended_schema:
|
|
709
|
-
|
|
782
|
+
msg = (
|
|
710
783
|
"You are attempting to visualise a subset of the loaded"
|
|
711
784
|
"ontology, but have not provided a schema configuration. "
|
|
712
785
|
"To display a partial ontology graph, please provide a schema "
|
|
713
786
|
"configuration file; to visualise the full graph, please use "
|
|
714
787
|
"the parameter `full = True`.",
|
|
715
788
|
)
|
|
789
|
+
logger.error(msg)
|
|
790
|
+
raise ValueError(msg)
|
|
716
791
|
|
|
717
792
|
if not self._nx_graph:
|
|
718
|
-
|
|
793
|
+
msg = "Ontology not loaded."
|
|
794
|
+
logger.error(msg)
|
|
795
|
+
raise ValueError(msg)
|
|
719
796
|
|
|
720
797
|
if not self._tail_ontologies:
|
|
721
798
|
msg = f"Showing ontology structure based on {self._head_ontology._ontology_file}"
|
|
@@ -778,7 +855,9 @@ class Ontology:
|
|
|
778
855
|
return True
|
|
779
856
|
|
|
780
857
|
def get_dict(self) -> dict:
|
|
781
|
-
"""
|
|
858
|
+
"""Return a dictionary representation of the ontology.
|
|
859
|
+
|
|
860
|
+
The dictionary is compatible with a BioCypher node for compatibility
|
|
782
861
|
with the Neo4j driver.
|
|
783
862
|
"""
|
|
784
863
|
d = {
|
|
@@ -792,11 +871,25 @@ class Ontology:
|
|
|
792
871
|
return d
|
|
793
872
|
|
|
794
873
|
def _get_current_id(self):
|
|
795
|
-
"""Instantiate a version ID for the current session.
|
|
796
|
-
|
|
874
|
+
"""Instantiate a version ID for the current session.
|
|
875
|
+
|
|
876
|
+
For now does simple versioning using datetime.
|
|
797
877
|
|
|
798
878
|
Can later implement incremental versioning, versioning from
|
|
799
879
|
config file, or manual specification via argument.
|
|
800
880
|
"""
|
|
801
881
|
now = datetime.now()
|
|
802
882
|
return now.strftime("v%Y%m%d-%H%M%S")
|
|
883
|
+
|
|
884
|
+
def get_rdf_graph(self):
|
|
885
|
+
"""Return the merged RDF graph.
|
|
886
|
+
|
|
887
|
+
Return the merged graph of all loaded ontologies (head and tails).
|
|
888
|
+
"""
|
|
889
|
+
graph = self._head_ontology.get_rdf_graph()
|
|
890
|
+
if self._tail_ontologies:
|
|
891
|
+
for key, onto in self._tail_ontologies.items():
|
|
892
|
+
assert type(onto) == OntologyAdapter
|
|
893
|
+
# RDFlib uses the + operator for merging.
|
|
894
|
+
graph += onto.get_rdf_graph()
|
|
895
|
+
return graph
|