Skip to content

Commit f7a90fe

Browse files
committed
RF: Pull compression detection logic into a central private module
1 parent 3a4cc5e commit f7a90fe

File tree

4 files changed

+53
-32
lines changed

4 files changed

+53
-32
lines changed

nibabel/_compression.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*-
2+
# vi: set ft=python sts=4 ts=4 sw=4 et:
3+
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
4+
#
5+
# See COPYING file distributed along with the NiBabel package for the
6+
# copyright and license terms.
7+
#
8+
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
9+
"""Constants and types for dealing transparently with compression"""
10+
from __future__ import annotations
11+
12+
import bz2
13+
import gzip
14+
import io
15+
import typing as ty
16+
17+
from .optpkg import optional_package
18+
19+
if ty.TYPE_CHECKING: # pragma: no cover
20+
import indexed_gzip # type: ignore
21+
import pyzstd
22+
23+
HAVE_INDEXED_GZIP = True
24+
HAVE_ZSTD = True
25+
else:
26+
indexed_gzip, HAVE_INDEXED_GZIP, _ = optional_package('indexed_gzip')
27+
pyzstd, HAVE_ZSTD, _ = optional_package('pyzstd')
28+
29+
30+
# Collections of types for isinstance or exception matching
31+
COMPRESSED_FILE_LIKES: tuple[type[io.IOBase], ...] = (
32+
bz2.BZ2File,
33+
gzip.GzipFile,
34+
)
35+
COMPRESSION_ERRORS: tuple[type[BaseException], ...] = (
36+
OSError, # BZ2File
37+
gzip.BadGzipFile,
38+
)
39+
40+
if HAVE_INDEXED_GZIP:
41+
COMPRESSED_FILE_LIKES += (indexed_gzip.IndexedGzipFile,)
42+
COMPRESSION_ERRORS += (indexed_gzip.ZranError,)
43+
from indexed_gzip import IndexedGzipFile # type: ignore
44+
else:
45+
IndexedGzipFile = gzip.GzipFile
46+
47+
if HAVE_ZSTD:
48+
COMPRESSED_FILE_LIKES += (pyzstd.ZstdFile,)
49+
COMPRESSION_ERRORS += (pyzstd.ZstdError,)

nibabel/filebasedimages.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from typing import Type
1616
from urllib import request
1717

18+
from ._compression import COMPRESSION_ERRORS
1819
from .fileholders import FileHolder, FileMap
1920
from .filename_parser import TypesFilenamesError, _stringify_path, splitext_addext, types_filenames
2021
from .openers import ImageOpener
@@ -421,7 +422,7 @@ def _sniff_meta_for(
421422
try:
422423
with ImageOpener(meta_fname, 'rb') as fobj:
423424
binaryblock = fobj.read(sniff_nbytes)
424-
except (OSError, EOFError):
425+
except COMPRESSION_ERRORS + (OSError, EOFError):
425426
return None
426427
return (binaryblock, meta_fname)
427428

nibabel/openers.py

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,11 @@
1515
from bz2 import BZ2File
1616
from os.path import splitext
1717

18-
from nibabel.optpkg import optional_package
18+
from ._compression import HAVE_INDEXED_GZIP, IndexedGzipFile, pyzstd
1919

2020
if ty.TYPE_CHECKING: # pragma: no cover
2121
from types import TracebackType
2222

23-
import pyzstd
2423
from _typeshed import WriteableBuffer
2524

2625
ModeRT = ty.Literal['r', 'rt']
@@ -32,8 +31,6 @@
3231
Mode = ty.Union[ModeR, ModeW]
3332

3433
OpenerDef = tuple[ty.Callable[..., io.IOBase], tuple[str, ...]]
35-
else:
36-
pyzstd = optional_package('pyzstd')[0]
3734

3835

3936
@ty.runtime_checkable
@@ -45,17 +42,6 @@ def write(self, b: bytes, /) -> int | None:
4542
... # pragma: no cover
4643

4744

48-
try:
49-
from indexed_gzip import IndexedGzipFile # type: ignore
50-
51-
HAVE_INDEXED_GZIP = True
52-
except ImportError:
53-
# nibabel.openers.IndexedGzipFile is imported by nibabel.volumeutils
54-
# to detect compressed file types, so we give a fallback value here.
55-
IndexedGzipFile = gzip.GzipFile
56-
HAVE_INDEXED_GZIP = False
57-
58-
5945
class DeterministicGzipFile(gzip.GzipFile):
6046
"""Deterministic variant of GzipFile
6147

nibabel/volumeutils.py

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,36 +9,28 @@
99
"""Utility functions for analyze-like formats"""
1010
from __future__ import annotations
1111

12-
import gzip
1312
import io
1413
import sys
1514
import typing as ty
1615
import warnings
17-
from bz2 import BZ2File
1816
from functools import reduce
1917
from operator import getitem, mul
2018
from os.path import exists, splitext
2119

2220
import numpy as np
2321

22+
from ._compression import COMPRESSED_FILE_LIKES
2423
from .casting import OK_FLOATS, shared_range
2524
from .externals.oset import OrderedSet
26-
from .openers import IndexedGzipFile
27-
from .optpkg import optional_package
2825

2926
if ty.TYPE_CHECKING: # pragma: no cover
3027
import numpy.typing as npt
31-
import pyzstd
32-
33-
HAVE_ZSTD = True
3428

3529
Scalar = np.number | float
3630

3731
K = ty.TypeVar('K')
3832
V = ty.TypeVar('V')
3933
DT = ty.TypeVar('DT', bound=np.generic)
40-
else:
41-
pyzstd, HAVE_ZSTD, _ = optional_package('pyzstd')
4234

4335
sys_is_le = sys.byteorder == 'little'
4436
native_code = sys_is_le and '<' or '>'
@@ -55,13 +47,6 @@
5547
#: default compression level when writing gz and bz2 files
5648
default_compresslevel = 1
5749

58-
#: file-like classes known to hold compressed data
59-
COMPRESSED_FILE_LIKES: tuple[type[io.IOBase], ...] = (gzip.GzipFile, BZ2File, IndexedGzipFile)
60-
61-
# Enable .zst support if pyzstd installed.
62-
if HAVE_ZSTD:
63-
COMPRESSED_FILE_LIKES = (*COMPRESSED_FILE_LIKES, pyzstd.ZstdFile)
64-
6550

6651
class Recoder:
6752
"""class to return canonical code(s) from code or aliases

0 commit comments

Comments
 (0)