Skip to content

Commit bc16768

Browse files
committed
Fall back to Python bindings when lz4 binary not available
Also, the dependency on python-lz4 is no longer optional, so there is no need to test `if lz4 is None` everywhere
1 parent 4c18c24 commit bc16768

File tree

2 files changed

+12
-57
lines changed

2 files changed

+12
-57
lines changed

src/xopen/__init__.py

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
)
3535
from types import ModuleType
3636

37+
import lz4.frame # type: ignore
38+
3739
from ._version import version as __version__
3840

3941
# 128K buffer size also used by cat, pigz etc. It is faster than the 8K default.
@@ -71,11 +73,6 @@
7173
except ImportError:
7274
zstandard = None # type: ignore
7375

74-
try:
75-
import lz4.frame # type: ignore
76-
except ImportError:
77-
lz4 = None
78-
7976
try:
8077
import fcntl
8178

@@ -568,11 +565,11 @@ def _open_lz4(
568565
if compresslevel is None:
569566
compresslevel = XOPEN_DEFAULT_LZ4_COMPRESSION
570567

571-
if lz4 is not None and (mode == "rb" or (mode in ("ab", "wb") and threads == 0)):
572-
# use Python bindings
568+
if mode == "rb" or threads == 0:
569+
# Use Python bindings
573570
f = lz4.frame.LZ4FrameFile(filename, mode, compression_level=compresslevel)
574571
return f
575-
# use CLI program
572+
# Use CLI program
576573
try:
577574
return _PipedCompressionProgram(
578575
filename,
@@ -582,15 +579,8 @@ def _open_lz4(
582579
program_settings=_PROGRAM_SETTINGS["lz4"],
583580
)
584581
except OSError:
585-
_program_settings = _PROGRAM_SETTINGS["lz4"]
586-
_program_settings.threads_flag = None
587-
return _PipedCompressionProgram(
588-
filename,
589-
mode,
590-
compresslevel,
591-
threads,
592-
program_settings=_program_settings,
593-
)
582+
# lz4 binary not available, fall back to Python bindings
583+
return _open_lz4(filename, mode, compresslevel, threads=0)
594584

595585

596586
def _open_gz(

tests/test_xopen.py

Lines changed: 5 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
Tests for the xopen.xopen function
33
"""
4+
45
import bz2
56
import functools
67
import gzip
@@ -19,10 +20,8 @@
1920

2021
from xopen import _detect_format_from_content, xopen
2122

22-
try:
23-
import lz4.frame
24-
except ImportError:
25-
lz4 = None
23+
import lz4.frame # type: ignore
24+
2625
try:
2726
import zstandard
2827
except ImportError:
@@ -113,8 +112,6 @@ def test_binary(fname):
113112
def test_roundtrip(ext, tmp_path, threads, mode):
114113
if ext == ".zst" and threads == 0 and zstandard is None:
115114
return
116-
if ext == ".lz4" and shutil.which("lz4") is None:
117-
pytest.skip("lz4 not installed")
118115
path = tmp_path / f"file{ext}"
119116
data = b"Hello" if mode == "b" else "Hello"
120117
with xopen(path, "w" + mode, threads=threads) as f:
@@ -209,8 +206,6 @@ def test_next(fname):
209206

210207

211208
def test_has_iter_method(ext, tmp_path):
212-
if ext == ".lz4" and shutil.which("lz4") is None:
213-
pytest.skip("lz4 not installed")
214209
path = tmp_path / f"out{ext}"
215210
with xopen(path, mode="w") as f:
216211
# Writing anything isn’t strictly necessary, but if we don’t, then
@@ -278,8 +273,6 @@ def test_invalid_compression_level(tmp_path):
278273
def test_append(ext, threads, tmp_path):
279274
if ext == ".zst" and zstandard is None and threads == 0:
280275
pytest.skip("No zstandard installed")
281-
if ext == ".lz4" and shutil.which("lz4") is None:
282-
pytest.skip("lz4 not installed")
283276
text = b"AB"
284277
reference = text + text
285278
path = tmp_path / f"the-file{ext}"
@@ -296,8 +289,6 @@ def test_append(ext, threads, tmp_path):
296289

297290
@pytest.mark.parametrize("ext", extensions)
298291
def test_append_text(ext, tmp_path):
299-
if ext == ".lz4" and shutil.which("lz4") is None:
300-
pytest.skip("lz4 not installed")
301292
text = "AB"
302293
reference = text + text
303294
path = tmp_path / f"the-file{ext}"
@@ -377,22 +368,17 @@ def test_read_no_threads(ext):
377368
".gz": gzip.GzipFile,
378369
".xz": lzma.LZMAFile,
379370
".zst": io.BufferedReader,
371+
".lz4": lz4.frame.LZ4FrameFile,
380372
"": io.BufferedReader,
381373
}
382374
if ext == ".zst" and zstandard is None:
383375
return
384-
if ext == ".lz4" and lz4 is None:
385-
return
386-
if ext == ".lz4" and lz4.frame is not None:
387-
klasses[".lz4"] = lz4.frame.LZ4FrameFile
388376
klass = klasses[ext]
389377
with xopen(TEST_DIR / f"file.txt{ext}", "rb", threads=0) as f:
390378
assert isinstance(f, klass), f
391379

392380

393381
def test_write_threads(tmp_path, ext):
394-
if ext == ".lz4" and shutil.which("lz4") is None:
395-
pytest.skip("lz4 not installed")
396382
path = tmp_path / f"out.{ext}"
397383
with xopen(path, mode="w", threads=3) as f:
398384
f.write("hello")
@@ -413,17 +399,13 @@ def test_write_no_threads(tmp_path, ext):
413399
".bz2": bz2.BZ2File,
414400
".gz": gzip.GzipFile,
415401
".xz": lzma.LZMAFile,
402+
".lz4": lz4.frame.LZ4FrameFile,
416403
"": io.BufferedWriter,
417404
}
418405
if ext == ".zst":
419406
# Skip zst because if python-zstandard is not installed,
420407
# we fall back to an external process even when threads=0
421408
return
422-
if ext == ".lz4" and lz4 is None:
423-
# Skip lz4 if lz4 is not installed
424-
return
425-
if ext == ".lz4" and lz4.frame is not None:
426-
klasses[".lz4"] = lz4.frame.LZ4FrameFile
427409
klass = klasses[ext]
428410
with xopen(tmp_path / f"out{ext}", "wb", threads=0) as f:
429411
if isinstance(f, io.BufferedWriter):
@@ -473,8 +455,6 @@ def test_read_pathlib_binary(fname):
473455

474456

475457
def test_write_pathlib(ext, tmp_path):
476-
if ext == ".lz4" and shutil.which("lz4") is None:
477-
pytest.skip("lz4 not installed")
478458
path = tmp_path / f"hello.txt{ext}"
479459
with xopen(path, mode="wt") as f:
480460
f.write("hello")
@@ -483,8 +463,6 @@ def test_write_pathlib(ext, tmp_path):
483463

484464

485465
def test_write_pathlib_binary(ext, tmp_path):
486-
if ext == ".lz4" and shutil.which("lz4") is None:
487-
pytest.skip("lz4 not installed")
488466
path = tmp_path / f"hello.txt{ext}"
489467
with xopen(path, mode="wb") as f:
490468
f.write(b"hello")
@@ -522,8 +500,6 @@ def test_falls_back_to_lzma_open(lacking_xz_permissions):
522500

523501

524502
def test_open_many_writers(tmp_path, ext):
525-
if ext == ".lz4" and shutil.which("lz4") is None:
526-
pytest.skip("lz4 not installed")
527503
files = []
528504
# Because lzma.open allocates a lot of memory,
529505
# open fewer files to avoid MemoryError on 32-bit architectures
@@ -569,8 +545,6 @@ def test_override_output_format_wrong_format(tmp_path):
569545
@pytest.mark.parametrize("opener", OPENERS)
570546
@pytest.mark.parametrize("extension", extensions)
571547
def test_text_encoding_newline_passthrough(opener, extension, tmp_path):
572-
if extension == ".lz4" and shutil.which("lz4") is None:
573-
pytest.skip("lz4 not installed")
574548
if extension == ".zst" and zstandard is None:
575549
return
576550
# "Eén ree\nTwee reeën\n" latin-1 encoded with \r for as line separator.
@@ -586,8 +560,6 @@ def test_text_encoding_newline_passthrough(opener, extension, tmp_path):
586560
@pytest.mark.parametrize("opener", OPENERS)
587561
@pytest.mark.parametrize("extension", extensions)
588562
def test_text_encoding_errors(opener, extension, tmp_path):
589-
if extension == ".lz4" and shutil.which("lz4") is None:
590-
pytest.skip("lz4 not installed")
591563
if extension == ".zst" and zstandard is None:
592564
return
593565
# "Eén ree\nTwee reeën\n" latin-1 encoded. This is not valid ascii.
@@ -673,11 +645,6 @@ def test_pass_bytesio_for_reading_and_writing(ext, threads):
673645
format = None
674646
if ext == ".zst" and zstandard is None:
675647
return
676-
if ext == ".lz4" and lz4 is None and threads == 0:
677-
pytest.skip("lz4 not working for BytesIO in piped write mode")
678-
if ext == ".lz4" and threads != 0:
679-
# _PipedCompressionProgram not working on write mode
680-
pytest.skip("lz4 not working for BytesIO in piped write mode")
681648
first_line = CONTENT_LINES[0].encode("utf-8")
682649
writer = xopen(filelike, "wb", format=format, threads=threads)
683650
writer.write(first_line)
@@ -727,8 +694,6 @@ def test_xopen_read_from_pipe(ext, threads):
727694

728695
@pytest.mark.parametrize("threads", (0, 1))
729696
def test_xopen_write_to_pipe(threads, ext):
730-
if ext == ".lz4" and shutil.which("lz4") is None:
731-
pytest.skip("lz4 not installed")
732697
if ext == ".zst" and zstandard is None:
733698
return
734699
format = ext.lstrip(".")

0 commit comments

Comments
 (0)