biocypher 0.7.0__tar.gz → 0.9.0__tar.gz

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.

Files changed (44) hide show
  1. {biocypher-0.7.0 → biocypher-0.9.0}/PKG-INFO +1 -1
  2. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/_config/biocypher_config.yaml +21 -4
  3. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/_metadata.py +1 -1
  4. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/_ontology.py +144 -51
  5. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/_translate.py +84 -79
  6. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/output/write/_batch_writer.py +133 -52
  7. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/output/write/_get_writer.py +28 -11
  8. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/output/write/_writer.py +32 -14
  9. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/output/write/graph/_arangodb.py +44 -32
  10. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/output/write/graph/_neo4j.py +3 -4
  11. biocypher-0.9.0/biocypher/output/write/graph/_owl.py +569 -0
  12. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/output/write/graph/_rdf.py +234 -97
  13. {biocypher-0.7.0 → biocypher-0.9.0}/pyproject.toml +1 -1
  14. {biocypher-0.7.0 → biocypher-0.9.0}/LICENSE +0 -0
  15. {biocypher-0.7.0 → biocypher-0.9.0}/README.md +0 -0
  16. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/__init__.py +0 -0
  17. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/_config/__init__.py +0 -0
  18. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/_config/test_config.yaml +0 -0
  19. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/_config/test_schema_config.yaml +0 -0
  20. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/_config/test_schema_config_disconnected.yaml +0 -0
  21. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/_config/test_schema_config_extended.yaml +0 -0
  22. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/_core.py +0 -0
  23. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/_create.py +0 -0
  24. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/_deduplicate.py +0 -0
  25. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/_get.py +0 -0
  26. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/_logger.py +0 -0
  27. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/_mapping.py +0 -0
  28. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/_misc.py +0 -0
  29. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/output/__init__.py +0 -0
  30. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/output/connect/__init__.py +0 -0
  31. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/output/connect/_get_connector.py +0 -0
  32. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/output/connect/_neo4j_driver.py +0 -0
  33. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/output/in_memory/__init__.py +0 -0
  34. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/output/in_memory/_get_in_memory_kg.py +0 -0
  35. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/output/in_memory/_in_memory_kg.py +0 -0
  36. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/output/in_memory/_networkx.py +0 -0
  37. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/output/in_memory/_pandas.py +0 -0
  38. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/output/write/__init__.py +0 -0
  39. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/output/write/graph/__init__.py +0 -0
  40. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/output/write/graph/_networkx.py +0 -0
  41. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/output/write/relational/__init__.py +0 -0
  42. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/output/write/relational/_csv.py +0 -0
  43. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/output/write/relational/_postgresql.py +0 -0
  44. {biocypher-0.7.0 → biocypher-0.9.0}/biocypher/output/write/relational/_sqlite.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: biocypher
3
- Version: 0.7.0
3
+ Version: 0.9.0
4
4
  Summary: A unifying framework for biomedical research knowledge graphs
5
5
  Home-page: https://github.com/biocypher/biocypher
6
6
  License: MIT
@@ -30,12 +30,13 @@ biocypher:
30
30
  # switch_label_and_id: true
31
31
 
32
32
  ### Optional parameters ###
33
-
34
33
  ## Logging
35
34
  # Write log to disk
35
+
36
36
  log_to_disk: true
37
37
 
38
38
  # Activate more granular logging
39
+
39
40
  debug: true
40
41
 
41
42
  # Change the log directory
@@ -85,6 +86,14 @@ neo4j:
85
86
  array_delimiter: "|"
86
87
  quote_character: "'"
87
88
 
89
+ # How to write the labels in the export files.
90
+
91
+ labels_order: "Ascending" # Default: From more specific to more generic.
92
+ # Or:
93
+ # labels_order: "Descending" # From more generic to more specific.
94
+ # labels_order: "Alphabetical" # Alphabetically. Legacy option.
95
+ # labels_order: "Leaves" # Only the more specific label.
96
+
88
97
  ## MultiDB functionality
89
98
  ## Set to false for using community edition or older versions of Neo4j
90
99
 
@@ -102,8 +111,8 @@ neo4j:
102
111
 
103
112
  postgresql:
104
113
  ### PostgreSQL configuration ###
105
-
106
114
  # PostgreSQL connection credentials
115
+
107
116
  database_name: postgres # DB name
108
117
  user: postgres # user name
109
118
  password: postgres # password
@@ -111,6 +120,7 @@ postgresql:
111
120
  port: 5432 # port
112
121
 
113
122
  # PostgreSQL import batch writer settings
123
+
114
124
  quote_character: '"'
115
125
  delimiter: '\t'
116
126
  # import_call_bin_prefix: '' # path to "psql"
@@ -118,15 +128,22 @@ postgresql:
118
128
 
119
129
  rdf:
120
130
  ### RDF configuration ###
121
- rdf_format: turtle
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
122
138
 
123
139
  sqlite:
124
140
  ### SQLite configuration ###
125
-
126
141
  # SQLite connection credentials
142
+
127
143
  database_name: sqlite.db # DB name
128
144
 
129
145
  # SQLite import batch writer settings
146
+
130
147
  quote_character: '"'
131
148
  delimiter: '\t'
132
149
  # import_call_bin_prefix: '' # path to "sqlite3"
@@ -10,7 +10,7 @@ import pathlib
10
10
 
11
11
  import toml
12
12
 
13
- _VERSION = "0.7.0"
13
+ _VERSION = "0.9.0"
14
14
 
15
15
 
16
16
  def get_metadata():
@@ -1,11 +1,12 @@
1
- """BioCypher 'ontology' module. Contains classes and functions to handle parsing
2
- and representation of single ontologies as well as their hybridisation and
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. Can
31
- read from a variety of formats, including OWL, OBO, and RDF/XML. The
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
- Labels are formatted in lower sentence case and underscores are replaced by spaces.
37
- Identifiers are taken as defined and the prefixes are removed by default.
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 g.triples((None, rdflib.RDFS.subClassOf, node)):
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
- """Does the node have a label in g?
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 retrieves a linked list from RDF.
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
- raise ValueError(
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. URIs can contain either "#" or "/" as a
344
- separator between the prefix and the local name. The prefix is
345
- everything before the last separator.
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. The ontology file can be in
354
- OWL, OBO, or RDF/XML format.
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
- raise ValueError(f"Could not determine format of ontology file {ontology_file}")
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
- raise NotImplementedError("OBO format not yet supported")
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
- raise ValueError(f"Could not determine format of ontology file {ontology_file}")
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 returned,
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 BioCypher knowledge
426
- graph. The ontology can be built from a single resource, or hybridised from
427
- a combination of resources, with one resource being the "head" ontology,
428
- while an arbitrary number of other resources can become "tail" ontologies at
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
- """Main method to be run on instantiation. Loads the ontologies, joins
462
- them, and returns the hybrid ontology. Loads only the head ontology
463
- if nothing else is given. Adds user extensions and properties from
464
- the mapping.
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 and store it as an
485
- instance variable (head) or a dictionary (tail).
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
- """Tries to find the head join node of the given ontology adapter in the
510
- head ontology. If the join node is not found, the method will raise an
511
- error.
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
- raise ValueError(
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
- """Joins the ontologies by adding the tail ontology as a subgraph to the
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
- """For each entity in the mapping, update the ontology with the properties
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
- raise ValueError(f"Node {value['synonym_for']} not found in ontology.")
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
- raise ValueError(
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
- raise ValueError("Ontology not loaded.")
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
- """Returns a dictionary compatible with a BioCypher node for compatibility
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. For now does simple
796
- versioning using datetime.
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