1import lz4
2import io
3import os
4import builtins
5import sys
6from ._frame import (  # noqa: F401
7    compress,
8    decompress,
9    create_compression_context,
10    compress_begin,
11    compress_chunk,
12    compress_flush,
13    create_decompression_context,
14    reset_decompression_context,
15    decompress_chunk,
16    get_frame_info,
17    BLOCKSIZE_DEFAULT as _BLOCKSIZE_DEFAULT,
18    BLOCKSIZE_MAX64KB as _BLOCKSIZE_MAX64KB,
19    BLOCKSIZE_MAX256KB as _BLOCKSIZE_MAX256KB,
20    BLOCKSIZE_MAX1MB as _BLOCKSIZE_MAX1MB,
21    BLOCKSIZE_MAX4MB as _BLOCKSIZE_MAX4MB,
22    __doc__ as _doc
23)
24
25__doc__ = _doc
26
27try:
28    import _compression   # Python 3.6 and later
29except ImportError:
30    from . import _compression
31
32
33BLOCKSIZE_DEFAULT = _BLOCKSIZE_DEFAULT
34"""Specifier for the default block size.
35
36Specifying ``block_size=lz4.frame.BLOCKSIZE_DEFAULT`` will instruct the LZ4
37library to use the default maximum blocksize. This is currently equivalent to
38`lz4.frame.BLOCKSIZE_MAX64KB`
39
40"""
41
42BLOCKSIZE_MAX64KB = _BLOCKSIZE_MAX64KB
43"""Specifier for a maximum block size of 64 kB.
44
45Specifying ``block_size=lz4.frame.BLOCKSIZE_MAX64KB`` will instruct the LZ4
46library to create blocks containing a maximum of 64 kB of uncompressed data.
47
48"""
49
50BLOCKSIZE_MAX256KB = _BLOCKSIZE_MAX256KB
51"""Specifier for a maximum block size of 256 kB.
52
53Specifying ``block_size=lz4.frame.BLOCKSIZE_MAX256KB`` will instruct the LZ4
54library to create blocks containing a maximum of 256 kB of uncompressed data.
55
56"""
57
58BLOCKSIZE_MAX1MB = _BLOCKSIZE_MAX1MB
59"""Specifier for a maximum block size of 1 MB.
60
61Specifying ``block_size=lz4.frame.BLOCKSIZE_MAX1MB`` will instruct the LZ4
62library to create blocks containing a maximum of 1 MB of uncompressed data.
63
64"""
65
66BLOCKSIZE_MAX4MB = _BLOCKSIZE_MAX4MB
67"""Specifier for a maximum block size of 4 MB.
68
69Specifying ``block_size=lz4.frame.BLOCKSIZE_MAX4MB`` will instruct the LZ4
70library to create blocks containing a maximum of 4 MB of uncompressed data.
71
72"""
73
74COMPRESSIONLEVEL_MIN = 0
75"""Specifier for the minimum compression level.
76
77Specifying ``compression_level=lz4.frame.COMPRESSIONLEVEL_MIN`` will
78instruct the LZ4 library to use a compression level of 0
79
80"""
81
82COMPRESSIONLEVEL_MINHC = 3
83"""Specifier for the minimum compression level for high compression mode.
84
85Specifying ``compression_level=lz4.frame.COMPRESSIONLEVEL_MINHC`` will
86instruct the LZ4 library to use a compression level of 3, the minimum for the
87high compression mode.
88
89"""
90
91COMPRESSIONLEVEL_MAX = 16
92"""Specifier for the maximum compression level.
93
94Specifying ``compression_level=lz4.frame.COMPRESSIONLEVEL_MAX`` will
95instruct the LZ4 library to use a compression level of 16, the highest
96compression level available.
97
98"""
99
100
101class LZ4FrameCompressor(object):
102    """Create a LZ4 frame compressor object.
103
104    This object can be used to compress data incrementally.
105
106    Args:
107        block_size (int): Specifies the maximum blocksize to use.
108            Options:
109
110            - `lz4.frame.BLOCKSIZE_DEFAULT`: the lz4 library default
111            - `lz4.frame.BLOCKSIZE_MAX64KB`: 64 kB
112            - `lz4.frame.BLOCKSIZE_MAX256KB`: 256 kB
113            - `lz4.frame.BLOCKSIZE_MAX1MB`: 1 MB
114            - `lz4.frame.BLOCKSIZE_MAX4MB`: 4 MB
115
116            If unspecified, will default to `lz4.frame.BLOCKSIZE_DEFAULT` which
117            is equal to `lz4.frame.BLOCKSIZE_MAX64KB`.
118        block_linked (bool): Specifies whether to use block-linked
119            compression. If ``True``, the compression ratio is improved,
120            especially for small block sizes. If ``False`` the blocks are
121            compressed independently. The default is ``True``.
122        compression_level (int): Specifies the level of compression used.
123            Values between 0-16 are valid, with 0 (default) being the
124            lowest compression (0-2 are the same value), and 16 the highest.
125            Values above 16 will be treated as 16.
126            Values between 4-9 are recommended. 0 is the default.
127            The following module constants are provided as a convenience:
128
129            - `lz4.frame.COMPRESSIONLEVEL_MIN`: Minimum compression (0)
130            - `lz4.frame.COMPRESSIONLEVEL_MINHC`: Minimum high-compression (3)
131            - `lz4.frame.COMPRESSIONLEVEL_MAX`: Maximum compression (16)
132
133        content_checksum (bool): Specifies whether to enable checksumming of
134            the payload content. If ``True``, a checksum of the uncompressed
135            data is stored at the end of the compressed frame which is checked
136            during decompression. The default is ``False``.
137        block_checksum (bool): Specifies whether to enable checksumming of
138            the content of each block. If ``True`` a checksum of the
139            uncompressed data in each block in the frame is stored at the end
140            of each block. If present, these checksums will be used to
141            validate the data during decompression. The default is ``False``,
142            meaning block checksums are not calculated and stored. This
143            functionality is only supported if the underlying LZ4 library has
144            version >= 1.8.0. Attempting to set this value to ``True`` with a
145            version of LZ4 < 1.8.0 will cause a ``RuntimeError`` to be raised.
146        auto_flush (bool): When ``False``, the LZ4 library may buffer data
147            until a block is full. When ``True`` no buffering occurs, and
148            partially full blocks may be returned. The default is ``False``.
149        return_bytearray (bool): When ``False`` a ``bytes`` object is returned
150            from the calls to methods of this class. When ``True`` a
151            ``bytearray`` object will be returned. The default is ``False``.
152
153    """
154
155    def __init__(self,
156                 block_size=BLOCKSIZE_DEFAULT,
157                 block_linked=True,
158                 compression_level=COMPRESSIONLEVEL_MIN,
159                 content_checksum=False,
160                 block_checksum=False,
161                 auto_flush=False,
162                 return_bytearray=False):
163        self.block_size = block_size
164        self.block_linked = block_linked
165        self.compression_level = compression_level
166        self.content_checksum = content_checksum
167        if block_checksum and lz4.library_version_number() < 10800:
168            raise RuntimeError(
169                'Attempt to set block_checksum to True with LZ4 library'
170                'version < 10800'
171            )
172        self.block_checksum = block_checksum
173        self.auto_flush = auto_flush
174        self.return_bytearray = return_bytearray
175        self._context = None
176        self._started = False
177
178    def __enter__(self):
179        # All necessary initialization is done in __init__
180        return self
181
182    def __exit__(self, exception_type, exception, traceback):
183        self.block_size = None
184        self.block_linked = None
185        self.compression_level = None
186        self.content_checksum = None
187        self.block_checksum = None
188        self.auto_flush = None
189        self.return_bytearray = None
190        self._context = None
191        self._started = False
192
193    def begin(self, source_size=0):
194        """Begin a compression frame.
195
196        The returned data contains frame header information. The data returned
197        from subsequent calls to ``compress()`` should be concatenated with
198        this header.
199
200        Keyword Args:
201            source_size (int): Optionally specify the total size of the
202                uncompressed data. If specified, will be stored in the
203                compressed frame header as an 8-byte field for later use
204                during decompression. Default is 0 (no size stored).
205
206        Returns:
207            bytes or bytearray: frame header data
208
209        """
210
211        if self._started is False:
212            self._context = create_compression_context()
213            result = compress_begin(
214                self._context,
215                block_size=self.block_size,
216                block_linked=self.block_linked,
217                compression_level=self.compression_level,
218                content_checksum=self.content_checksum,
219                block_checksum=self.block_checksum,
220                auto_flush=self.auto_flush,
221                return_bytearray=self.return_bytearray,
222                source_size=source_size
223            )
224            self._started = True
225            return result
226        else:
227            raise RuntimeError(
228                'LZ4FrameCompressor.begin() called after already initialized'
229            )
230
231    def compress(self, data):  # noqa: F811
232        """Compresses data and returns it.
233
234        This compresses ``data`` (a ``bytes`` object), returning a bytes or
235        bytearray object containing compressed data the input.
236
237        If ``auto_flush`` has been set to ``False``, some of ``data`` may be
238        buffered internally, for use in later calls to
239        `LZ4FrameCompressor.compress()` and `LZ4FrameCompressor.flush()`.
240
241        The returned data should be concatenated with the output of any
242        previous calls to `compress()` and a single call to
243        `compress_begin()`.
244
245        Args:
246            data (str, bytes or buffer-compatible object): data to compress
247
248        Returns:
249            bytes or bytearray: compressed data
250
251        """
252        if self._context is None:
253            raise RuntimeError('compress called after flush()')
254
255        if self._started is False:
256            raise RuntimeError('compress called before compress_begin()')
257
258        result = compress_chunk(
259            self._context, data,
260            return_bytearray=self.return_bytearray
261        )
262
263        return result
264
265    def flush(self):
266        """Finish the compression process.
267
268        This returns a ``bytes`` or ``bytearray`` object containing any data
269        stored in the compressor's internal buffers and a frame footer.
270
271        The LZ4FrameCompressor instance may be re-used after this method has
272        been called to create a new frame of compressed data.
273
274        Returns:
275            bytes or bytearray: compressed data and frame footer.
276
277        """
278        result = compress_flush(
279            self._context,
280            end_frame=True,
281            return_bytearray=self.return_bytearray
282        )
283        self._context = None
284        self._started = False
285        return result
286
287    def reset(self):
288        """Reset the `LZ4FrameCompressor` instance.
289
290        This allows the `LZ4FrameCompression` instance to be re-used after an
291        error.
292
293        """
294        self._context = None
295        self._started = False
296
297
298class LZ4FrameDecompressor(object):
299    """Create a LZ4 frame decompressor object.
300
301    This can be used to decompress data incrementally.
302
303    For a more convenient way of decompressing an entire compressed frame at
304    once, see `lz4.frame.decompress()`.
305
306    Args:
307        return_bytearray (bool): When ``False`` a bytes object is returned from
308            the calls to methods of this class. When ``True`` a bytearray
309            object will be returned. The default is ``False``.
310
311    Attributes:
312        eof (bool): ``True`` if the end-of-stream marker has been reached.
313            ``False`` otherwise.
314        unused_data (bytes): Data found after the end of the compressed stream.
315            Before the end of the frame is reached, this will be ``b''``.
316        needs_input (bool): ``False`` if the ``decompress()`` method can
317            provide more decompressed data before requiring new uncompressed
318            input. ``True`` otherwise.
319
320    """
321
322    def __init__(self, return_bytearray=False):
323        self._context = create_decompression_context()
324        self.eof = False
325        self.needs_input = True
326        self.unused_data = None
327        self._unconsumed_data = b''
328        self._return_bytearray = return_bytearray
329
330    def __enter__(self):
331        # All necessary initialization is done in __init__
332        return self
333
334    def __exit__(self, exception_type, exception, traceback):
335        self._context = None
336        self.eof = None
337        self.needs_input = None
338        self.unused_data = None
339        self._unconsumed_data = None
340        self._return_bytearray = None
341
342    def reset(self):
343        """Reset the decompressor state.
344
345        This is useful after an error occurs, allowing re-use of the instance.
346
347        """
348        reset_decompression_context(self._context)
349        self.eof = False
350        self.needs_input = True
351        self.unused_data = None
352        self._unconsumed_data = b''
353
354    def decompress(self, data, max_length=-1):  # noqa: F811
355        """Decompresses part or all of an LZ4 frame of compressed data.
356
357        The returned data should be concatenated with the output of any
358        previous calls to `decompress()`.
359
360        If ``max_length`` is non-negative, returns at most ``max_length`` bytes
361        of decompressed data. If this limit is reached and further output can
362        be produced, the `needs_input` attribute will be set to ``False``. In
363        this case, the next call to `decompress()` may provide data as
364        ``b''`` to obtain more of the output. In all cases, any unconsumed data
365        from previous calls will be prepended to the input data.
366
367        If all of the input ``data`` was decompressed and returned (either
368        because this was less than ``max_length`` bytes, or because
369        ``max_length`` was negative), the `needs_input` attribute will be set
370        to ``True``.
371
372        If an end of frame marker is encountered in the data during
373        decompression, decompression will stop at the end of the frame, and any
374        data after the end of frame is available from the `unused_data`
375        attribute. In this case, the `LZ4FrameDecompressor` instance is reset
376        and can be used for further decompression.
377
378        Args:
379            data (str, bytes or buffer-compatible object): compressed data to
380                decompress
381
382        Keyword Args:
383            max_length (int): If this is non-negative, this method returns at
384                most ``max_length`` bytes of decompressed data.
385
386        Returns:
387            bytes: Uncompressed data
388
389        """
390
391        if self._unconsumed_data:
392            data = self._unconsumed_data + data
393
394        decompressed, bytes_read, eoframe = decompress_chunk(
395            self._context,
396            data,
397            max_length=max_length,
398            return_bytearray=self._return_bytearray,
399        )
400
401        if bytes_read < len(data):
402            if eoframe:
403                self.unused_data = data[bytes_read:]
404            else:
405                self._unconsumed_data = data[bytes_read:]
406                self.needs_input = False
407        else:
408            self._unconsumed_data = b''
409            self.needs_input = True
410            self.unused_data = None
411
412        self.eof = eoframe
413
414        return decompressed
415
416
417_MODE_CLOSED = 0
418_MODE_READ = 1
419# Value 2 no longer used
420_MODE_WRITE = 3
421
422
423class LZ4FrameFile(_compression.BaseStream):
424    """A file object providing transparent LZ4F (de)compression.
425
426    An LZ4FFile can act as a wrapper for an existing file object, or refer
427    directly to a named file on disk.
428
429    Note that LZ4FFile provides a *binary* file interface - data read is
430    returned as bytes, and data to be written must be given as bytes.
431
432    When opening a file for writing, the settings used by the compressor can be
433    specified. The underlying compressor object is
434    `lz4.frame.LZ4FrameCompressor`. See the docstrings for that class for
435    details on compression options.
436
437    Args:
438        filename(str, bytes, PathLike, file object): can be either an actual
439            file name (given as a str, bytes, or
440            PathLike object), in which case the named file is opened, or it
441            can be an existing file object to read from or write to.
442
443    Keyword Args:
444        mode(str): mode can be ``'r'`` for reading (default), ``'w'`` for
445            (over)writing, ``'x'`` for creating exclusively, or ``'a'``
446            for appending. These can equivalently be given as ``'rb'``,
447            ``'wb'``, ``'xb'`` and ``'ab'`` respectively.
448        return_bytearray (bool): When ``False`` a bytes object is returned from
449            the calls to methods of this class. When ``True`` a ``bytearray``
450            object will be returned. The default is ``False``.
451        source_size (int): Optionally specify the total size of the
452            uncompressed data. If specified, will be stored in the compressed
453            frame header as an 8-byte field for later use during decompression.
454            Default is ``0`` (no size stored). Only used for writing
455            compressed files.
456        block_size (int): Compressor setting. See
457            `lz4.frame.LZ4FrameCompressor`.
458        block_linked (bool): Compressor setting. See
459            `lz4.frame.LZ4FrameCompressor`.
460        compression_level (int): Compressor setting. See
461            `lz4.frame.LZ4FrameCompressor`.
462        content_checksum (bool): Compressor setting. See
463            `lz4.frame.LZ4FrameCompressor`.
464        block_checksum (bool): Compressor setting. See
465            `lz4.frame.LZ4FrameCompressor`.
466        auto_flush (bool): Compressor setting. See
467            `lz4.frame.LZ4FrameCompressor`.
468
469    """
470
471    def __init__(self, filename=None, mode='r',
472                 block_size=BLOCKSIZE_DEFAULT,
473                 block_linked=True,
474                 compression_level=COMPRESSIONLEVEL_MIN,
475                 content_checksum=False,
476                 block_checksum=False,
477                 auto_flush=False,
478                 return_bytearray=False,
479                 source_size=0):
480
481        self._fp = None
482        self._closefp = False
483        self._mode = _MODE_CLOSED
484
485        if mode in ('r', 'rb'):
486            mode_code = _MODE_READ
487        elif mode in ('w', 'wb', 'a', 'ab', 'x', 'xb'):
488            mode_code = _MODE_WRITE
489            self._compressor = LZ4FrameCompressor(
490                block_size=block_size,
491                block_linked=block_linked,
492                compression_level=compression_level,
493                content_checksum=content_checksum,
494                block_checksum=block_checksum,
495                auto_flush=auto_flush,
496                return_bytearray=return_bytearray,
497            )
498            self._pos = 0
499        else:
500            raise ValueError('Invalid mode: {!r}'.format(mode))
501
502        if sys.version_info > (3, 6):
503            path_test = isinstance(filename, (str, bytes, os.PathLike))
504        else:
505            path_test = isinstance(filename, (str, bytes))
506
507        if path_test is True:
508            if 'b' not in mode:
509                mode += 'b'
510            self._fp = builtins.open(filename, mode)
511            self._closefp = True
512            self._mode = mode_code
513        elif hasattr(filename, 'read') or hasattr(filename, 'write'):
514            self._fp = filename
515            self._mode = mode_code
516        else:
517            raise TypeError(
518                'filename must be a str, bytes, file or PathLike object'
519            )
520
521        if self._mode == _MODE_READ:
522            raw = _compression.DecompressReader(self._fp, LZ4FrameDecompressor)
523            self._buffer = io.BufferedReader(raw)
524
525        if self._mode == _MODE_WRITE:
526            self._fp.write(
527                self._compressor.begin(source_size=source_size)
528            )
529
530    def close(self):
531        """Flush and close the file.
532
533        May be called more than once without error. Once the file is
534        closed, any other operation on it will raise a ValueError.
535        """
536        if self._mode == _MODE_CLOSED:
537            return
538        try:
539            if self._mode == _MODE_READ:
540                self._buffer.close()
541                self._buffer = None
542            elif self._mode == _MODE_WRITE:
543                self._fp.write(self._compressor.flush())
544                self._compressor = None
545        finally:
546            try:
547                if self._closefp:
548                    self._fp.close()
549            finally:
550                self._fp = None
551                self._closefp = False
552                self._mode = _MODE_CLOSED
553
554    @property
555    def closed(self):
556        """Returns ``True`` if this file is closed.
557
558        Returns:
559            bool: ``True`` if the file is closed, ``False`` otherwise.
560
561        """
562        return self._mode == _MODE_CLOSED
563
564    def fileno(self):
565        """Return the file descriptor for the underlying file.
566
567        Returns:
568            file object: file descriptor for file.
569
570        """
571        self._check_not_closed()
572        return self._fp.fileno()
573
574    def seekable(self):
575        """Return whether the file supports seeking.
576
577        Returns:
578            bool: ``True`` if the file supports seeking, ``False`` otherwise.
579
580        """
581        return self.readable() and self._buffer.seekable()
582
583    def readable(self):
584        """Return whether the file was opened for reading.
585
586        Returns:
587            bool: ``True`` if the file was opened for reading, ``False``
588                otherwise.
589
590        """
591        self._check_not_closed()
592        return self._mode == _MODE_READ
593
594    def writable(self):
595        """Return whether the file was opened for writing.
596
597        Returns:
598            bool: ``True`` if the file was opened for writing, ``False``
599                otherwise.
600
601        """
602        self._check_not_closed()
603        return self._mode == _MODE_WRITE
604
605    def peek(self, size=-1):
606        """Return buffered data without advancing the file position.
607
608        Always returns at least one byte of data, unless at EOF. The exact
609        number of bytes returned is unspecified.
610
611        Returns:
612            bytes: uncompressed data
613
614        """
615        self._check_can_read()
616        # Relies on the undocumented fact that BufferedReader.peek() always
617        # returns at least one byte (except at EOF)
618        return self._buffer.peek(size)
619
620    def read(self, size=-1):
621        """Read up to ``size`` uncompressed bytes from the file.
622
623        If ``size`` is negative or omitted, read until ``EOF`` is reached.
624        Returns ``b''`` if the file is already at ``EOF``.
625
626        Args:
627            size(int): If non-negative, specifies the maximum number of
628                uncompressed bytes to return.
629
630        Returns:
631            bytes: uncompressed data
632
633        """
634        self._check_can_read()
635        return self._buffer.read(size)
636
637    def read1(self, size=-1):
638        """Read up to ``size`` uncompressed bytes.
639
640        This method tries to avoid making multiple reads from the underlying
641        stream.
642
643        This method reads up to a buffer's worth of data if ``size`` is
644        negative.
645
646        Returns ``b''`` if the file is at EOF.
647
648        Args:
649            size(int): If non-negative, specifies the maximum number of
650                uncompressed bytes to return.
651
652        Returns:
653            bytes: uncompressed data
654
655        """
656        self._check_can_read()
657        if size < 0:
658            size = io.DEFAULT_BUFFER_SIZE
659        return self._buffer.read1(size)
660
661    def readline(self, size=-1):
662        """Read a line of uncompressed bytes from the file.
663
664        The terminating newline (if present) is retained. If size is
665        non-negative, no more than size bytes will be read (in which case the
666        line may be incomplete). Returns b'' if already at EOF.
667
668        Args:
669            size(int): If non-negative, specifies the maximum number of
670                uncompressed bytes to return.
671
672        Returns:
673            bytes: uncompressed data
674
675        """
676        self._check_can_read()
677        return self._buffer.readline(size)
678
679    def write(self, data):
680        """Write a bytes object to the file.
681
682        Returns the number of uncompressed bytes written, which is always
683        ``len(data)``. Note that due to buffering, the file on disk may not
684        reflect the data written until close() is called.
685
686        Args:
687            data(bytes): uncompressed data to compress and write to the file
688
689        Returns:
690            int: the number of uncompressed bytes written to the file
691
692        """
693        self._check_can_write()
694        compressed = self._compressor.compress(data)
695        self._fp.write(compressed)
696        self._pos += len(data)
697        return len(data)
698
699    def seek(self, offset, whence=io.SEEK_SET):
700        """Change the file position.
701
702        The new position is specified by ``offset``, relative to the position
703        indicated by ``whence``. Possible values for ``whence`` are:
704
705        - ``io.SEEK_SET`` or 0: start of stream (default): offset must not be
706          negative
707        - ``io.SEEK_CUR`` or 1: current stream position
708        - ``io.SEEK_END`` or 2: end of stream; offset must not be positive
709
710        Returns the new file position.
711
712        Note that seeking is emulated, so depending on the parameters, this
713        operation may be extremely slow.
714
715        Args:
716            offset(int): new position in the file
717            whence(int): position with which ``offset`` is measured. Allowed
718                values are 0, 1, 2. The default is 0 (start of stream).
719
720        Returns:
721            int: new file position
722
723        """
724        self._check_can_seek()
725        return self._buffer.seek(offset, whence)
726
727    def tell(self):
728        """Return the current file position.
729
730        Args:
731            None
732
733        Returns:
734            int: file position
735
736        """
737        self._check_not_closed()
738        if self._mode == _MODE_READ:
739            return self._buffer.tell()
740        return self._pos
741
742
743def open(filename, mode="rb",
744         encoding=None,
745         errors=None,
746         newline=None,
747         block_size=BLOCKSIZE_DEFAULT,
748         block_linked=True,
749         compression_level=COMPRESSIONLEVEL_MIN,
750         content_checksum=False,
751         block_checksum=False,
752         auto_flush=False,
753         return_bytearray=False,
754         source_size=0):
755    """Open an LZ4Frame-compressed file in binary or text mode.
756
757    ``filename`` can be either an actual file name (given as a str, bytes, or
758    PathLike object), in which case the named file is opened, or it can be an
759    existing file object to read from or write to.
760
761    The ``mode`` argument can be ``'r'``, ``'rb'`` (default), ``'w'``,
762    ``'wb'``, ``'x'``, ``'xb'``, ``'a'``, or ``'ab'`` for binary mode, or
763    ``'rt'``, ``'wt'``, ``'xt'``, or ``'at'`` for text mode.
764
765    For binary mode, this function is equivalent to the `LZ4FrameFile`
766    constructor: `LZ4FrameFile(filename, mode, ...)`.
767
768    For text mode, an `LZ4FrameFile` object is created, and wrapped in an
769    ``io.TextIOWrapper`` instance with the specified encoding, error handling
770    behavior, and line ending(s).
771
772    Args:
773        filename (str, bytes, os.PathLike): file name or file object to open
774
775    Keyword Args:
776        mode (str): mode for opening the file
777        encoding (str): the name of the encoding that will be used for
778            encoding/deconging the stream. It defaults to
779            ``locale.getpreferredencoding(False)``. See ``io.TextIOWrapper``
780            for further details.
781        errors (str): specifies how encoding and decoding errors are to be
782            handled. See ``io.TextIOWrapper`` for further details.
783        newline (str): controls how line endings are handled. See
784            ``io.TextIOWrapper`` for further details.
785        return_bytearray (bool): When ``False`` a bytes object is returned
786            from the calls to methods of this class. When ``True`` a bytearray
787            object will be returned. The default is ``False``.
788        source_size (int): Optionally specify the total size of the
789            uncompressed data. If specified, will be stored in the compressed
790            frame header as an 8-byte field for later use during decompression.
791            Default is 0 (no size stored). Only used for writing compressed
792            files.
793        block_size (int): Compressor setting. See
794            `lz4.frame.LZ4FrameCompressor`.
795        block_linked (bool): Compressor setting. See
796            `lz4.frame.LZ4FrameCompressor`.
797        compression_level (int): Compressor setting. See
798            `lz4.frame.LZ4FrameCompressor`.
799        content_checksum (bool): Compressor setting. See
800            `lz4.frame.LZ4FrameCompressor`.
801        block_checksum (bool): Compressor setting. See
802            `lz4.frame.LZ4FrameCompressor`.
803        auto_flush (bool): Compressor setting. See
804            `lz4.frame.LZ4FrameCompressor`.
805
806    """
807    if 't' in mode:
808        if 'b' in mode:
809            raise ValueError('Invalid mode: %r' % (mode,))
810    else:
811        if encoding is not None:
812            raise ValueError(
813                "Argument 'encoding' not supported in binary mode"
814            )
815        if errors is not None:
816            raise ValueError("Argument 'errors' not supported in binary mode")
817        if newline is not None:
818            raise ValueError("Argument 'newline' not supported in binary mode")
819
820    _mode = mode.replace('t', '')
821
822    binary_file = LZ4FrameFile(
823        filename,
824        mode=_mode,
825        block_size=block_size,
826        block_linked=block_linked,
827        compression_level=compression_level,
828        content_checksum=content_checksum,
829        block_checksum=block_checksum,
830        auto_flush=auto_flush,
831        return_bytearray=return_bytearray,
832    )
833
834    if 't' in mode:
835        return io.TextIOWrapper(binary_file, encoding, errors, newline)
836    else:
837        return binary_file
838