Skip to content

Commit a474254

Browse files
authored
Merge pull request #679 from pauldmccarthy/bf/fix_keep_file_open
BF: Fix interaction between `indexed_gzip` presence and `keep_file_open` flag
2 parents 791678e + 9d7fb3f commit a474254

File tree

3 files changed

+200
-161
lines changed

3 files changed

+200
-161
lines changed

nibabel/arrayproxy.py

Lines changed: 84 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,14 @@
4242
used for the lifetime of the ``ArrayProxy``. It should be set to one of
4343
``True``, ``False``, or ``'auto'``.
4444
45-
If ``True``, a single file handle is created and used. If ``False``, a new
46-
file handle is created every time the image is accessed. For gzip files, if
47-
``'auto'``, and the optional ``indexed_gzip`` dependency is present, a single
48-
file handle is created and persisted. If ``indexed_gzip`` is not available,
49-
behaviour is the same as if ``keep_file_open is False``.
45+
Management of file handles will be performed either by ``ArrayProxy`` objects,
46+
or by the ``indexed_gzip`` package if it is used.
47+
48+
If this flag is set to ``True``, a single file handle is created and used. If
49+
``False``, a new file handle is created every time the image is accessed. For
50+
gzip files, if ``'auto'``, and the optional ``indexed_gzip`` dependency is
51+
present, a single file handle is created and persisted. If ``indexed_gzip`` is
52+
not available, behaviour is the same as if ``keep_file_open is False``.
5053
5154
If this is set to any other value, attempts to create an ``ArrayProxy`` without
5255
specifying the ``keep_file_open`` flag will result in a ``ValueError`` being
@@ -160,8 +163,10 @@ def __init__(self, file_like, spec, mmap=True, keep_file_open=None):
160163
# Permit any specifier that can be interpreted as a numpy dtype
161164
self._dtype = np.dtype(self._dtype)
162165
self._mmap = mmap
163-
self._keep_file_open = self._should_keep_file_open(file_like,
164-
keep_file_open)
166+
# Flags to keep track of whether a single ImageOpener is created, and
167+
# whether a single underlying file handle is created.
168+
self._keep_file_open, self._persist_opener = \
169+
self._should_keep_file_open(file_like, keep_file_open)
165170
self._lock = RLock()
166171

167172
def __del__(self):
@@ -184,16 +189,64 @@ def __setstate__(self, state):
184189
self._lock = RLock()
185190

186191
def _should_keep_file_open(self, file_like, keep_file_open):
187-
"""Called by ``__init__``, and used to determine the final value of
188-
``keep_file_open``.
192+
"""Called by ``__init__``.
193+
194+
This method determines how to manage ``ImageOpener`` instances,
195+
and the underlying file handles - the behaviour depends on:
196+
197+
- whether ``file_like`` is an an open file handle, or a path to a
198+
``'.gz'`` file, or a path to a non-gzip file.
199+
- whether ``indexed_gzip`` is present (see
200+
:attr:`.openers.HAVE_INDEXED_GZIP`).
201+
202+
An ``ArrayProxy`` object uses two internal flags to manage
203+
``ImageOpener`` instances and underlying file handles.
204+
205+
- The ``_persist_opener`` flag controls whether a single
206+
``ImageOpener`` should be created and used for the lifetime of
207+
this ``ArrayProxy``, or whether separate ``ImageOpener`` instances
208+
should be created on each file access.
209+
210+
- The ``_keep_file_open`` flag controls qwhether the underlying file
211+
handle should be kept open for the lifetime of this
212+
``ArrayProxy``, or whether the file handle should be (re-)opened
213+
and closed on each file access.
214+
215+
The internal ``_keep_file_open`` flag is only relevant if
216+
``file_like`` is a ``'.gz'`` file, and the ``indexed_gzip`` library is
217+
present.
189218
190-
The return value is derived from these rules:
219+
This method returns the values to be used for the internal
220+
``_persist_opener`` and ``_keep_file_open`` flags; these values are
221+
derived according to the following rules:
191222
192-
- If ``file_like`` is a file(-like) object, ``False`` is returned.
193-
Otherwise, ``file_like`` is assumed to be a file name.
194-
- If ``keep_file_open`` is ``auto``, and ``indexed_gzip`` is
195-
not available, ``False`` is returned.
196-
- Otherwise, the value of ``keep_file_open`` is returned unchanged.
223+
1. If ``file_like`` is a file(-like) object, both flags are set to
224+
``False``.
225+
226+
2. If ``keep_file_open`` (as passed to :meth:``__init__``) is
227+
``True``, both internal flags are set to ``True``.
228+
229+
3. If ``keep_file_open`` is ``False``, but ``file_like`` is not a path
230+
to a ``.gz`` file or ``indexed_gzip`` is not present, both flags
231+
are set to ``False``.
232+
233+
4. If ``keep_file_open`` is ``False``, ``file_like`` is a path to a
234+
``.gz`` file, and ``indexed_gzip`` is present, ``_persist_opener``
235+
is set to ``True``, and ``_keep_file_open`` is set to ``False``.
236+
In this case, file handle management is delegated to the
237+
``indexed_gzip`` library.
238+
239+
5. If ``keep_file_open`` is ``'auto'``, ``file_like`` is a path to a
240+
``.gz`` file, and ``indexed_gzip`` is present, both internal flags
241+
are set to ``True``.
242+
243+
6. If ``keep_file_open`` is ``'auto'``, and ``file_like`` is not a
244+
path to a ``.gz`` file, or ``indexed_gzip`` is not present, both
245+
internal flags are set to ``False``.
246+
247+
Note that a value of ``'auto'`` for ``keep_file_open`` will become
248+
deprecated behaviour in version 2.4.0, and support for ``'auto'`` will
249+
be removed in version 3.0.0.
197250
198251
Parameters
199252
----------
@@ -206,8 +259,10 @@ def _should_keep_file_open(self, file_like, keep_file_open):
206259
Returns
207260
-------
208261
209-
The value of ``keep_file_open`` that will be used by this
210-
``ArrayProxy``, and passed through to ``ImageOpener`` instances.
262+
A tuple containing:
263+
- ``keep_file_open`` flag to control persistence of file handles
264+
- ``persist_opener`` flag to control persistence of ``ImageOpener``
265+
objects.
211266
"""
212267
if keep_file_open is None:
213268
keep_file_open = KEEP_FILE_OPEN_DEFAULT
@@ -216,12 +271,15 @@ def _should_keep_file_open(self, file_like, keep_file_open):
216271
'\'auto\', True, False}')
217272
# file_like is a handle - keep_file_open is irrelevant
218273
if hasattr(file_like, 'read') and hasattr(file_like, 'seek'):
219-
return False
220-
# don't have indexed_gzip - auto -> False
221-
if keep_file_open == 'auto' and not (openers.HAVE_INDEXED_GZIP and
222-
file_like.endswith('.gz')):
223-
return False
224-
return keep_file_open
274+
return False, False
275+
# if the file is a gzip file, and we have_indexed_gzip,
276+
have_igzip = openers.HAVE_INDEXED_GZIP and file_like.endswith('.gz')
277+
if keep_file_open == 'auto':
278+
return have_igzip, have_igzip
279+
elif keep_file_open:
280+
return True, True
281+
else:
282+
return False, have_igzip
225283

226284
@property
227285
@deprecate_with_version('ArrayProxy.header deprecated', '2.2', '3.0')
@@ -269,13 +327,14 @@ def _get_fileobj(self):
269327
A newly created ``ImageOpener`` instance, or an existing one,
270328
which provides access to the file.
271329
"""
272-
if self._keep_file_open:
330+
if self._persist_opener:
273331
if not hasattr(self, '_opener'):
274332
self._opener = openers.ImageOpener(
275333
self.file_like, keep_open=self._keep_file_open)
276334
yield self._opener
277335
else:
278-
with openers.ImageOpener(self.file_like) as opener:
336+
with openers.ImageOpener(
337+
self.file_like, keep_open=False) as opener:
279338
yield opener
280339

281340
def get_unscaled(self):

0 commit comments

Comments
 (0)