diff --git a/bluepysnap/circuit.py b/bluepysnap/circuit.py index 1e991fcb..30e203f8 100644 --- a/bluepysnap/circuit.py +++ b/bluepysnap/circuit.py @@ -49,6 +49,7 @@ def __init__(self, config): Circuit: A Circuit object. """ self._config = Config(config).resolve() + self.is_open = True @property def config(self): @@ -58,15 +59,42 @@ def config(self): @cached_property def nodes(self): """Access to node population(s). See :py:class:`~bluepysnap.nodes.NodePopulation`.""" - return _collect_populations( - self._config['networks']['nodes'], - lambda cfg: NodeStorage(cfg, self) - ) + if self.is_open: + return _collect_populations( + self._config['networks']['nodes'], + lambda cfg: NodeStorage(cfg, self) + ) + raise BluepySnapError("I/O error. Cannot access the h5 files with closed context.") @cached_property def edges(self): """Access to edge population(s). See :py:class:`~bluepysnap.edges.EdgePopulation`.""" - return _collect_populations( - self._config['networks']['edges'], - lambda cfg: EdgeStorage(cfg, self) - ) + if self.is_open: + return _collect_populations( + self._config['networks']['edges'], + lambda cfg: EdgeStorage(cfg, self) + ) + raise BluepySnapError("I/O error. Cannot access the h5 files with closed context.") + + def __exit__(self, exc_type, exc_val, exc_tb): + """Close the context for all populations.""" + + def _close_context(pop): + """Close the h5 context for population.""" + if "_population" in pop.__dict__: + del pop.__dict__["_population"] + + if self.nodes: + for population in self.nodes.values(): + _close_context(population) + if self.edges: + for population in self.edges.values(): + _close_context(population) + + del self.__dict__["nodes"] + del self.__dict__["edges"] + self.is_open = False + + def __enter__(self): + """Enter the context manager for circuit.""" + return self diff --git a/bluepysnap/edges.py b/bluepysnap/edges.py index 0ededf38..a889e001 100644 --- a/bluepysnap/edges.py +++ b/bluepysnap/edges.py @@ -50,7 +50,7 @@ def __init__(self, config, circuit): self._circuit = circuit self._populations = {} - @cached_property + @property def storage(self): """Access to the libsonata edge storage.""" return libsonata.EdgeStorage(self._h5_filepath) @@ -111,9 +111,11 @@ def __init__(self, edge_storage, population_name): @cached_property def _population(self): - return self._edge_storage.storage.open_population(self.name) + if self._edge_storage.circuit.is_open: + return self._edge_storage.storage.open_population(self.name) + raise BluepySnapError("I/O error. Cannot access the h5 files with closed context.") - @property + @cached_property def size(self): """Population size.""" return self._population.size @@ -396,7 +398,6 @@ def _optimal_direction(): secondary_node_ids = np.unique(secondary_node_ids) secondary_node_ids_used = set() - for key_node_id in primary_node_ids: connected_node_ids = get_connected_node_ids(key_node_id, unique=False) connected_node_ids_with_count = np.stack( diff --git a/bluepysnap/nodes.py b/bluepysnap/nodes.py index bb289bb1..4dc8bde7 100644 --- a/bluepysnap/nodes.py +++ b/bluepysnap/nodes.py @@ -54,7 +54,7 @@ def __init__(self, config, circuit): self._circuit = circuit self._populations = {} - @cached_property + @property def storage(self): """Access to the libsonata node storage.""" return libsonata.NodeStorage(self._h5_filepath) @@ -174,7 +174,9 @@ def _data(self): @cached_property def _population(self): - return self._node_storage.storage.open_population(self.name) + if self._node_storage.circuit.is_open: + return self._node_storage.storage.open_population(self.name) + raise BluepySnapError("I/O error. Cannot access the h5 files with closed context.") @cached_property def size(self): diff --git a/tests/test_circuit.py b/tests/test_circuit.py index 10972d44..c6d0a722 100644 --- a/tests/test_circuit.py +++ b/tests/test_circuit.py @@ -1,4 +1,5 @@ import pytest +import h5py from bluepysnap.nodes import NodePopulation from bluepysnap.edges import EdgePopulation @@ -11,12 +12,12 @@ def test_all(): circuit = test_module.Circuit( str(TEST_DATA_DIR / 'circuit_config.json')) - assert( - circuit.config['networks']['nodes'][0] == - { - 'nodes_file': str(TEST_DATA_DIR / 'nodes.h5'), - 'node_types_file': None, - } + assert ( + circuit.config['networks']['nodes'][0] == + { + 'nodes_file': str(TEST_DATA_DIR / 'nodes.h5'), + 'node_types_file': None, + } ) assert isinstance(circuit.nodes, dict) assert isinstance(circuit.edges, dict) @@ -34,3 +35,37 @@ def test_duplicate_population(): with pytest.raises(BluepySnapError): circuit.nodes + +def test_close_contexts(): + with test_module.Circuit(str(TEST_DATA_DIR / 'circuit_config.json')) as circuit: + edge = circuit.edges['default'] + edge.size + node = circuit.nodes['default'] + node.size + node_file = circuit.config['networks']['nodes'][0]['nodes_file'] + edge_file = circuit.config['networks']['edges'][0]['edges_file'] + + with h5py.File(node_file, "r+") as h5: + list(h5) + + with h5py.File(edge_file, "r+") as h5: + list(h5) + +def test_close_contexts_errors_extracted_obj(): + with test_module.Circuit(str(TEST_DATA_DIR / 'circuit_config.json')) as circuit: + edges = circuit.edges['default'] + edges.size + nodes = circuit.nodes['default'] + nodes.size + + with pytest.raises(BluepySnapError): + circuit.nodes + + with pytest.raises(BluepySnapError): + circuit.edges + + with pytest.raises(BluepySnapError): + nodes.property_names + + with pytest.raises(BluepySnapError): + edges.property_names diff --git a/tests/test_edges.py b/tests/test_edges.py index 366b3f03..e820ba43 100644 --- a/tests/test_edges.py +++ b/tests/test_edges.py @@ -4,6 +4,7 @@ import pandas as pd import pandas.testing as pdt import pytest +import h5py import libsonata from mock import Mock diff --git a/tests/test_nodes.py b/tests/test_nodes.py index 5870fda6..377d9c4c 100644 --- a/tests/test_nodes.py +++ b/tests/test_nodes.py @@ -1,8 +1,9 @@ import numpy as np import numpy.testing as npt import pandas as pd -import pandas.util.testing as pdt +import pandas.testing as pdt import pytest +import h5py import libsonata from mock import Mock