diff --git a/Lib/bz2.py b/Lib/bz2.py --- a/Lib/bz2.py +++ b/Lib/bz2.py @@ -4,20 +4,21 @@ This module provides a file interface, c (de)compression, and functions for one-shot (de)compression. """ __all__ = ["BZ2File", "BZ2Compressor", "BZ2Decompressor", "open", "compress", "decompress"] __author__ = "Nadeem Vawda " from builtins import open as _builtin_open import io +from os import PathLike import warnings import _compression try: from threading import RLock except ImportError: from dummy_threading import RLock from _bz2 import BZ2Compressor, BZ2Decompressor @@ -35,23 +36,23 @@ class BZ2File(_compression.BaseStream): A BZ2File can act as a wrapper for an existing file object, or refer directly to a named file on disk. Note that BZ2File provides a *binary* file interface - data read is returned as bytes, and data to be written should be given as bytes. """ def __init__(self, filename, mode="r", buffering=None, compresslevel=9): """Open a bzip2-compressed file. - If filename is a str or bytes object, it gives the name - of the file to be opened. Otherwise, it should be a file object, - which will be used to read or write the compressed data. + If filename is a str, bytes, or PathLike object, it gives the + name of the file to be opened. Otherwise, it should be a file + object, which will be used to read or write the compressed data. mode can be 'r' for reading (default), 'w' for (over)writing, 'x' for creating exclusively, or 'a' for appending. These can equivalently be given as 'rb', 'wb', 'xb', and 'ab'. buffering is ignored. Its use is deprecated. If mode is 'w', 'x' or 'a', compresslevel can be a number between 1 and 9 specifying the level of compression: 1 produces the least compression, and 9 (default) produces the most compression. @@ -84,21 +85,21 @@ class BZ2File(_compression.BaseStream): mode = "xb" mode_code = _MODE_WRITE self._compressor = BZ2Compressor(compresslevel) elif mode in ("a", "ab"): mode = "ab" mode_code = _MODE_WRITE self._compressor = BZ2Compressor(compresslevel) else: raise ValueError("Invalid mode: %r" % (mode,)) - if isinstance(filename, (str, bytes)): + if isinstance(filename, (str, bytes, PathLike)): self._fp = _builtin_open(filename, mode) self._closefp = True self._mode = mode_code elif hasattr(filename, "read") or hasattr(filename, "write"): self._fp = filename self._mode = mode_code else: raise TypeError("filename must be a str or bytes object, or a file") if self._mode == _MODE_READ: @@ -282,22 +283,23 @@ class BZ2File(_compression.BaseStream): self._check_not_closed() if self._mode == _MODE_READ: return self._buffer.tell() return self._pos def open(filename, mode="rb", compresslevel=9, encoding=None, errors=None, newline=None): """Open a bzip2-compressed file in binary or text mode. - The filename argument can be an actual filename (a str or bytes - object), or an existing file object to read from or write to. + The filename argument can be an actual filename (a str, bytes, or + PathLike object), or an existing file object to read from or write + to. The mode argument can be "r", "rb", "w", "wb", "x", "xb", "a" or "ab" for binary mode, or "rt", "wt", "xt" or "at" for text mode. The default mode is "rb", and the default compresslevel is 9. For binary mode, this function is equivalent to the BZ2File constructor: BZ2File(filename, mode, compresslevel). In this case, the encoding, errors and newline arguments must not be provided. For text mode, a BZ2File object is created, and wrapped in an diff --git a/Lib/test/test_bz2.py b/Lib/test/test_bz2.py --- a/Lib/test/test_bz2.py +++ b/Lib/test/test_bz2.py @@ -1,18 +1,19 @@ from test import support from test.support import bigmemtest, _4G import unittest from io import BytesIO, DEFAULT_BUFFER_SIZE import os import pickle import glob +from pathlib import Path import random import subprocess import sys from test.support import unlink import _compression try: import threading except ImportError: threading = None @@ -553,20 +554,31 @@ class BZ2FileTest(BaseTest): except UnicodeEncodeError: self.skipTest("Temporary file name needs to be ASCII") with BZ2File(bytes_filename, "wb") as f: f.write(self.DATA) with BZ2File(bytes_filename, "rb") as f: self.assertEqual(f.read(), self.DATA) # Sanity check that we are actually operating on the right file. with BZ2File(str_filename, "rb") as f: self.assertEqual(f.read(), self.DATA) + def testOpenPathLikeFilename(self): + str_filename = self.filename + pathlike_filename = Path(str_filename) + with BZ2File(pathlike_filename, "wb") as f: + f.write(self.DATA) + with BZ2File(pathlike_filename, "rb") as f: + self.assertEqual(f.read(), self.DATA) + # Sanity check that we are actually operating on the right file. + with BZ2File(str_filename, "rb") as f: + self.assertEqual(f.read(), self.DATA) + def testDecompressLimited(self): """Decompressed data buffering should be limited""" bomb = bz2.compress(b'\0' * int(2e6), compresslevel=9) self.assertLess(len(bomb), _compression.BUFFER_SIZE) decomp = BZ2File(BytesIO(bomb)) self.assertEqual(decomp.read(1), b'\0') max_decomp = 1 + DEFAULT_BUFFER_SIZE self.assertLessEqual(decomp._buffer.raw.tell(), max_decomp, "Excessive amount of data was decompressed")