diff --git a/CHANGELOG.md b/CHANGELOG.md index 7126b0a9..a2a3075a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,11 +7,18 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [2.4.12] - (Unreleased) +### Added + +- Added `options` kwargs to several I/O operations, including: `readbytes`, + `writebytes`, `readtext`, `writetext` and `writefile`. + ### Changed - Start testing on PyPy. Due to [#342](https://github.com/PyFilesystem/pyfilesystem2/issues/342) we have to treat PyPy builds specially and allow them to fail, but at least we'll be able to see if we break something aside from known issues with FTP tests. +- Set `py36 = false` for black to prevent adding trailing comma in function + definition and cause `SyntaxError` in older python version. ## [2.4.11] - 2019-09-07 diff --git a/fs/base.py b/fs/base.py index a4b7aefb..f73e73db 100644 --- a/fs/base.py +++ b/fs/base.py @@ -586,12 +586,14 @@ def exclude_file(patterns, info): iter_info = itertools.islice(iter_info, start, end) return iter_info - def readbytes(self, path): - # type: (Text) -> bytes + def readbytes(self, path, **options): + # type: (Text, Any) -> bytes """Get the contents of a file as bytes. Arguments: path (str): A path to a readable file on the filesystem. + **options: keyword arguments for any additional information + required by the filesystem (if any). Returns: bytes: the file contents. @@ -600,7 +602,7 @@ def readbytes(self, path): fs.errors.ResourceNotFound: if ``path`` does not exist. """ - with closing(self.open(path, mode="rb")) as read_file: + with closing(self.open(path, mode="rb", **options)) as read_file: contents = read_file.read() return contents @@ -644,6 +646,7 @@ def readtext( encoding=None, # type: Optional[Text] errors=None, # type: Optional[Text] newline="", # type: Text + **options # type: Any ): # type: (...) -> Text """Get the contents of a file as a string. @@ -654,6 +657,8 @@ def readtext( in text mode (defaults to `None`, reading in binary mode). errors (str, optional): Unicode errors parameter. newline (str): Newlines parameter. + **options: keyword arguments for any additional information + required by the filesystem (if any). Returns: str: file contents. @@ -664,7 +669,12 @@ def readtext( """ with closing( self.open( - path, mode="rt", encoding=encoding, errors=errors, newline=newline + path, + mode="rt", + encoding=encoding, + errors=errors, + newline=newline, + **options ) ) as read_file: contents = read_file.read() @@ -1163,7 +1173,7 @@ def open( """ validate_open_mode(mode) bin_mode = mode.replace("t", "") - bin_file = self.openbin(path, mode=bin_mode, buffering=buffering) + bin_file = self.openbin(path, mode=bin_mode, buffering=buffering, **options) io_stream = iotools.make_stream( path, bin_file, @@ -1172,7 +1182,7 @@ def open( encoding=encoding or "utf-8", errors=errors, newline=newline, - **options + line_buffering=options.get("line_buffering", False), ) return io_stream @@ -1271,14 +1281,16 @@ def scandir( iter_info = itertools.islice(iter_info, start, end) return iter_info - def writebytes(self, path, contents): - # type: (Text, bytes) -> None + def writebytes(self, path, contents, **options): + # type: (Text, bytes, Any) -> None # FIXME(@althonos): accept bytearray and memoryview as well ? """Copy binary data to a file. Arguments: path (str): Destination path on the filesystem. contents (bytes): Data to be written. + **options: keyword arguments for any additional information + required by the filesystem (if any). Raises: TypeError: if contents is not bytes. @@ -1286,7 +1298,7 @@ def writebytes(self, path, contents): """ if not isinstance(contents, bytes): raise TypeError("contents must be bytes") - with closing(self.open(path, mode="wb")) as write_file: + with closing(self.open(path, mode="wb", **options)) as write_file: write_file.write(contents) setbytes = _new_name(writebytes, "setbytes") @@ -1331,6 +1343,7 @@ def writefile( encoding=None, # type: Optional[Text] errors=None, # type: Optional[Text] newline="", # type: Text + **options # type: Any ): # type: (...) -> None """Set a file to the contents of a file object. @@ -1343,6 +1356,8 @@ def writefile( errors (str, optional): How encoding errors should be treated (same as `io.open`). newline (str): Newline parameter (same as `io.open`). + **options: Implementation specific options required to open + the source file. This method is similar to `~FS.upload`, in that it copies data from a file-like object to a resource on the filesystem, but unlike ``upload``, @@ -1362,7 +1377,12 @@ def writefile( with self._lock: with self.open( - path, mode=mode, encoding=encoding, errors=errors, newline=newline + path, + mode=mode, + encoding=encoding, + errors=errors, + newline=newline, + **options ) as dst_file: tools.copy_file_data(file, dst_file) @@ -1401,6 +1421,7 @@ def writetext( encoding="utf-8", # type: Text errors=None, # type: Optional[Text] newline="", # type: Text + **options # type: Any ): # type: (...) -> None """Create or replace a file with text. @@ -1413,6 +1434,8 @@ def writetext( errors (str, optional): How encoding errors should be treated (same as `io.open`). newline (str): Newline parameter (same as `io.open`). + **options: keyword arguments for any additional information + required by the filesystem (if any). Raises: TypeError: if ``contents`` is not a unicode string. @@ -1422,7 +1445,12 @@ def writetext( raise TypeError("contents must be unicode") with closing( self.open( - path, mode="wt", encoding=encoding, errors=errors, newline=newline + path, + mode="wt", + encoding=encoding, + errors=errors, + newline=newline, + **options ) ) as write_file: write_file.write(contents) diff --git a/fs/ftpfs.py b/fs/ftpfs.py index 11d2c4cc..5329c13c 100644 --- a/fs/ftpfs.py +++ b/fs/ftpfs.py @@ -761,19 +761,19 @@ def upload(self, path, file, chunk_size=None, **options): str("STOR ") + _encode(_path, self.ftp.encoding), file ) - def writebytes(self, path, contents): - # type: (Text, ByteString) -> None + def writebytes(self, path, contents, **options): + # type: (Text, ByteString, Any) -> None if not isinstance(contents, bytes): raise TypeError("contents must be bytes") - self.upload(path, io.BytesIO(contents)) + self.upload(path, io.BytesIO(contents), **options) def setinfo(self, path, info): # type: (Text, RawInfo) -> None if not self.exists(path): raise errors.ResourceNotFound(path) - def readbytes(self, path): - # type: (Text) -> bytes + def readbytes(self, path, **options): + # type: (Text, Any) -> bytes _path = self.validatepath(path) data = io.BytesIO() with ftp_errors(self, path): diff --git a/fs/mountfs.py b/fs/mountfs.py index d51d7d9d..2e45a858 100644 --- a/fs/mountfs.py +++ b/fs/mountfs.py @@ -186,11 +186,11 @@ def removedir(self, path): fs, _path = self._delegate(path) return fs.removedir(_path) - def readbytes(self, path): - # type: (Text) -> bytes + def readbytes(self, path, **options): + # type: (Text, Any) -> bytes self.check() fs, _path = self._delegate(path) - return fs.readbytes(_path) + return fs.readbytes(_path, **options) def download(self, path, file, chunk_size=None, **options): # type: (Text, BinaryIO, Optional[int], **Any) -> None @@ -203,11 +203,14 @@ def readtext( encoding=None, # type: Optional[Text] errors=None, # type: Optional[Text] newline="", # type: Text + **options # type: Any ): # type: (...) -> Text self.check() fs, _path = self._delegate(path) - return fs.readtext(_path, encoding=encoding, errors=errors, newline=newline) + return fs.readtext( + _path, encoding=encoding, errors=errors, newline=newline, **options + ) def getsize(self, path): # type: (Text) -> int @@ -306,11 +309,11 @@ def upload(self, path, file, chunk_size=None, **options): fs, _path = self._delegate(path) return fs.upload(_path, file, chunk_size=chunk_size, **options) - def writebytes(self, path, contents): - # type: (Text, bytes) -> None + def writebytes(self, path, contents, **options): + # type: (Text, bytes, Any) -> None self.check() fs, _path = self._delegate(path) - return fs.writebytes(_path, contents) + return fs.writebytes(_path, contents, **options) def writetext( self, @@ -319,9 +322,15 @@ def writetext( encoding="utf-8", # type: Text errors=None, # type: Optional[Text] newline="", # type: Text + **options # type: Any ): # type: (...) -> None fs, _path = self._delegate(path) return fs.writetext( - _path, contents, encoding=encoding, errors=errors, newline=newline + _path, + contents, + encoding=encoding, + errors=errors, + newline=newline, + **options ) diff --git a/fs/multifs.py b/fs/multifs.py index e68d2c00..f38cfd95 100644 --- a/fs/multifs.py +++ b/fs/multifs.py @@ -280,24 +280,26 @@ def scandir( if not exists: raise errors.ResourceNotFound(path) - def readbytes(self, path): - # type: (Text) -> bytes + def readbytes(self, path, **options): + # type: (Text, Any) -> bytes self.check() fs = self._delegate(path) if fs is None: raise errors.ResourceNotFound(path) - return fs.readbytes(path) + return fs.readbytes(path, **options) def download(self, path, file, chunk_size=None, **options): # type: (Text, BinaryIO, Optional[int], **Any) -> None fs = self._delegate_required(path) return fs.download(path, file, chunk_size=chunk_size, **options) - def readtext(self, path, encoding=None, errors=None, newline=""): - # type: (Text, Optional[Text], Optional[Text], Text) -> Text + def readtext(self, path, encoding=None, errors=None, newline="", **options): + # type: (Text, Optional[Text], Optional[Text], Text, Any) -> Text self.check() fs = self._delegate_required(path) - return fs.readtext(path, encoding=encoding, errors=errors, newline=newline) + return fs.readtext( + path, encoding=encoding, errors=errors, newline=newline, **options + ) def getsize(self, path): # type: (Text) -> int @@ -406,9 +408,9 @@ def upload(self, path, file, chunk_size=None, **options): path, file, chunk_size=chunk_size, **options ) - def writebytes(self, path, contents): - # type: (Text, bytes) -> None - self._writable_required(path).writebytes(path, contents) + def writebytes(self, path, contents, **options): + # type: (Text, bytes, Any) -> None + self._writable_required(path).writebytes(path, contents, **options) def writetext( self, @@ -417,9 +419,10 @@ def writetext( encoding="utf-8", # type: Text errors=None, # type: Optional[Text] newline="", # type: Text + **options # type: Any ): # type: (...) -> None write_fs = self._writable_required(path) return write_fs.writetext( - path, contents, encoding=encoding, errors=errors, newline=newline + path, contents, encoding=encoding, errors=errors, newline=newline, **options ) diff --git a/fs/wrap.py b/fs/wrap.py index 7026bcbc..2e4b59ca 100644 --- a/fs/wrap.py +++ b/fs/wrap.py @@ -215,6 +215,7 @@ def writetext( encoding="utf-8", # type: Text errors=None, # type: Optional[Text] newline="", # type: Text + **options # type: Any ): # type: (...) -> None self.check() @@ -271,8 +272,8 @@ def open( **options ) - def writebytes(self, path, contents): - # type: (Text, bytes) -> None + def writebytes(self, path, contents, **options): + # type: (Text, bytes, Any) -> None self.check() raise ResourceReadOnly(path) @@ -288,6 +289,7 @@ def writefile( encoding=None, # type: Optional[Text] errors=None, # type: Optional[Text] newline="", # type: Text + **options # type: Any ): # type: (...) -> None self.check() diff --git a/fs/wrapfs.py b/fs/wrapfs.py index c09e9cf3..0abb2a5e 100644 --- a/fs/wrapfs.py +++ b/fs/wrapfs.py @@ -334,12 +334,12 @@ def filterdir( for info in iter_files: yield info - def readbytes(self, path): - # type: (Text) -> bytes + def readbytes(self, path, **options): + # type: (Text, Any) -> bytes self.check() _fs, _path = self.delegate_path(path) with unwrap_errors(path): - _bytes = _fs.readbytes(_path) + _bytes = _fs.readbytes(_path, **options) return _bytes def readtext( @@ -348,13 +348,14 @@ def readtext( encoding=None, # type: Optional[Text] errors=None, # type: Optional[Text] newline="", # type: Text + **options # type: Any ): # type: (...) -> Text self.check() _fs, _path = self.delegate_path(path) with unwrap_errors(path): _text = _fs.readtext( - _path, encoding=encoding, errors=errors, newline=newline + _path, encoding=encoding, errors=errors, newline=newline, **options ) return _text @@ -488,12 +489,12 @@ def opendir( with unwrap_errors(path): return factory(self, path) - def writebytes(self, path, contents): - # type: (Text, bytes) -> None + def writebytes(self, path, contents, **options): + # type: (Text, bytes, Any) -> None self.check() _fs, _path = self.delegate_path(path) with unwrap_errors(path): - _fs.writebytes(_path, contents) + _fs.writebytes(_path, contents, **options) def upload(self, path, file, chunk_size=None, **options): # type: (Text, BinaryIO, Optional[int], **Any) -> None @@ -509,13 +510,19 @@ def writefile( encoding=None, # type: Optional[Text] errors=None, # type: Optional[Text] newline="", # type: Text + **options # type: Any ): # type: (...) -> None self.check() _fs, _path = self.delegate_path(path) with unwrap_errors(path): _fs.writefile( - _path, file, encoding=encoding, errors=errors, newline=newline + _path, + file, + encoding=encoding, + errors=errors, + newline=newline, + **options ) def validatepath(self, path): diff --git a/fs/zipfs.py b/fs/zipfs.py index 8feb9e56..fbfd3245 100644 --- a/fs/zipfs.py +++ b/fs/zipfs.py @@ -439,13 +439,13 @@ def close(self): if hasattr(self, "_zip"): self._zip.close() - def readbytes(self, path): - # type: (Text) -> bytes + def readbytes(self, path, **options): + # type: (Text, Any) -> bytes self.check() if not self._directory.isfile(path): raise errors.ResourceNotFound(path) zip_name = self._path_to_zip_name(path) - zip_bytes = self._zip.read(zip_name) + zip_bytes = self._zip.read(zip_name, **options) return zip_bytes def geturl(self, path, purpose="download"): diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..2c32aebf --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,2 @@ +[tool.black] +py36 = false