From d9795ebd8bc8945c44fdb0a15226dd6d39edf547 Mon Sep 17 00:00:00 2001 From: Marcel Martin Date: Sun, 12 Nov 2023 15:14:30 +0100 Subject: [PATCH] Let PipedCompressionWriter/-Reader derive from IOBase This is the proper thing to do and also gives us a couple of methods for free, in particular readlines(). This also allows us to get rid of the Closing mixin. Closes #129 --- src/xopen/__init__.py | 34 ++++------------------------------ tests/test_piped.py | 6 ++++++ 2 files changed, 10 insertions(+), 30 deletions(-) diff --git a/src/xopen/__init__.py b/src/xopen/__init__.py index 4c317ab..8a55b23 100644 --- a/src/xopen/__init__.py +++ b/src/xopen/__init__.py @@ -33,7 +33,6 @@ import subprocess import tempfile import time -from abc import ABC, abstractmethod from subprocess import Popen, PIPE, DEVNULL from typing import ( Optional, @@ -157,30 +156,7 @@ def _can_read_concatenated_gz(program: str) -> bool: os.remove(temp_path) -class Closing(ABC): - """ - Inherit from this class and implement a close() method to offer context - manager functionality. - """ - - def __enter__(self): - return self - - def __exit__(self, *exc_info): - self.close() - - def __del__(self): - try: - self.close() - except Exception: - pass - - @abstractmethod - def close(self): - """Called when exiting the context manager""" - - -class PipedCompressionWriter(Closing): +class PipedCompressionWriter(io.IOBase): """ Write Compressed files by running an external process and piping into it. """ @@ -216,7 +192,6 @@ def __init__( # TODO use a context manager self.outfile = open(path, mode[0] + "b") - self.closed: bool = False self.name: str = str(os.fspath(path)) self._mode: str = mode self._program_args: List[str] = program_args @@ -283,7 +258,7 @@ def write(self, arg: AnyStr) -> int: def close(self) -> None: if self.closed: return - self.closed = True + super().close() self._file.close() retcode = self.process.wait() self.outfile.close() @@ -311,7 +286,7 @@ def __next__(self): raise io.UnsupportedOperation("not readable") -class PipedCompressionReader(Closing): +class PipedCompressionReader(io.IOBase): """ Open a pipe to a process for reading a compressed file. """ @@ -371,7 +346,6 @@ def __init__( ) else: self._file = self.process.stdout - self.closed = False self._wait_for_output_or_process_exit() self._raise_if_error() @@ -387,7 +361,7 @@ def __repr__(self): def close(self) -> None: if self.closed: return - self.closed = True + super().close() retcode = self.process.poll() check_allowed_code_and_message = False if retcode is None: diff --git a/tests/test_piped.py b/tests/test_piped.py index c512412..ed309f4 100644 --- a/tests/test_piped.py +++ b/tests/test_piped.py @@ -175,6 +175,12 @@ def test_reader_readline_text(reader): assert f.readline() == CONTENT_LINES[0] +def test_reader_readlines(reader): + opener, extension = reader + with opener(TEST_DIR / f"file.txt{extension}", "r") as f: + assert f.readlines() == CONTENT_LINES + + @pytest.mark.parametrize("threads", [None, 1, 2]) def test_piped_reader_iter(threads, threaded_reader): opener, extension = threaded_reader