Skip to content

Commit 121e9ec

Browse files
clbarnesjrbourbeau
authored andcommitted
Group as context manager (#510)
For compatibility with other group-like things (e.g. `h5py.File`), `Groups` now have enter/exit methods for use as a context manager. If the underlying store has a `close` method, that will be called on exit; otherwise this does nothing.
1 parent 640d441 commit 121e9ec

File tree

5 files changed

+45
-2
lines changed

5 files changed

+45
-2
lines changed

docs/api/hierarchy.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ Groups (``zarr.hierarchy``)
1111
.. automethod:: __iter__
1212
.. automethod:: __contains__
1313
.. automethod:: __getitem__
14+
.. automethod:: __enter__
15+
.. automethod:: __exit__
1416
.. automethod:: group_keys
1517
.. automethod:: groups
1618
.. automethod:: array_keys

docs/release.rst

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Upcoming Release
2121
* Upgrade dependencies in the test matrices and resolve a
2222
compatibility issue with testing against the Azure Storage
2323
Emulator. By :user:`alimanfoo`; :issue:`468`, :issue:`467`.
24-
24+
2525
* Do not rename Blosc parameters in n5 backend and add `blocksize` parameter,
2626
compatible with n5-blosc. By :user:`axtimwalde`, :issue:`485`.
2727

@@ -49,6 +49,9 @@ Upcoming Release
4949
* Refactor out ``_tofile``/``_fromfile`` from ``DirectoryStore``.
5050
By :user:`John Kirkham <jakirkham>`; :issue:`503`.
5151

52+
* Add ``__enter__``/``__exit__`` methods to ``Group`` for ``h5py.File`` compatibility.
53+
By :user:`Chris Barnes <clbarnes>`; :issue:`509`.
54+
5255
* Add documentation build to CI.
5356
By :user:`James Bourbeau <jrbourbeau>`; :issue:`516`.
5457

@@ -97,7 +100,7 @@ Enhancements
97100
~~~~~~~~~~~~
98101

99102
* New storage backend, backed by Azure Blob Storage, class :class:`zarr.storage.ABSStore`.
100-
All data is stored as block blobs. By :user:`Shikhar Goenka <shikarsg>`,
103+
All data is stored as block blobs. By :user:`Shikhar Goenka <shikarsg>`,
101104
:user:`Tim Crone <tjcrone>` and :user:`Zain Patel <mzjp2>`; :issue:`345`.
102105

103106
* Add "consolidated" metadata as an experimental feature: use

docs/tutorial.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,9 @@ sub-directories, e.g.::
346346
>>> z
347347
<zarr.core.Array '/foo/bar/baz' (10000, 10000) int32>
348348

349+
Groups can be used as context managers (in a ``with`` statement).
350+
If the underlying store has a ``close`` method, it will be called on exit.
351+
349352
For more information on groups see the :mod:`zarr.hierarchy` and
350353
:mod:`zarr.convenience` API docs.
351354

zarr/hierarchy.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ class Group(MutableMapping):
2626
----------
2727
store : MutableMapping
2828
Group store, already initialized.
29+
If the Group is used in a context manager, and the store has a ``close`` method,
30+
it will be called on exit.
2931
path : string, optional
3032
Group path.
3133
read_only : bool, optional
@@ -57,6 +59,8 @@ class Group(MutableMapping):
5759
__iter__
5860
__contains__
5961
__getitem__
62+
__enter__
63+
__exit__
6064
group_keys
6165
groups
6266
array_keys
@@ -225,6 +229,17 @@ def __repr__(self):
225229
r += '>'
226230
return r
227231

232+
def __enter__(self):
233+
"""Return the Group for use as a context manager."""
234+
return self
235+
236+
def __exit__(self, exc_type, exc_val, exc_tb):
237+
"""If the underlying Store has a ``close`` method, call it."""
238+
try:
239+
self.store.close()
240+
except AttributeError:
241+
pass
242+
228243
def info_items(self):
229244

230245
def typestr(o):

zarr/tests/test_hierarchy.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import os
44
import pickle
55
import shutil
6+
import sys
67
import tempfile
78
import textwrap
89
import unittest
@@ -860,6 +861,12 @@ def test_pickle(self):
860861
assert isinstance(g2['foo'], Group)
861862
assert isinstance(g2['foo/bar'], Array)
862863

864+
def test_context_manager(self):
865+
866+
with self.create_group() as g:
867+
d = g.create_dataset('foo/bar', shape=100, chunks=10)
868+
d[:] = np.arange(100)
869+
863870

864871
class TestGroupWithMemoryStore(TestGroup):
865872

@@ -912,6 +919,19 @@ def create_store():
912919
store = ZipStore(path)
913920
return store, None
914921

922+
def test_context_manager(self):
923+
924+
with self.create_group() as g:
925+
store = g.store
926+
d = g.create_dataset('foo/bar', shape=100, chunks=10)
927+
d[:] = np.arange(100)
928+
929+
# Check that exiting the context manager closes the store,
930+
# and therefore the underlying ZipFile.
931+
error = ValueError if sys.version_info >= (3, 6) else RuntimeError
932+
with pytest.raises(error):
933+
store.zf.extractall()
934+
915935

916936
class TestGroupWithDBMStore(TestGroup):
917937

0 commit comments

Comments
 (0)