From ea6839835557784433669a43c763c296ce9afd21 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 1 Oct 2014 19:12:33 +0200 Subject: [PATCH] Closes #20218: Added convenience methods read_text/write_text and read_bytes/ write_bytes to pathlib.Path objects. Thanks to Christopher Welborn and Ram Rachum for original patches. --- Doc/library/pathlib.rst | 61 ++++++++++++++++++++++++++++++++++++++++ Lib/pathlib.py | 33 ++++++++++++++++++++++ Lib/test/test_pathlib.py | 17 +++++++++++ Misc/NEWS | 3 ++ 4 files changed, 114 insertions(+) diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst index 67ed914e9f8..0942b21df3f 100644 --- a/Doc/library/pathlib.rst +++ b/Doc/library/pathlib.rst @@ -834,6 +834,34 @@ call fails (for example because the path doesn't exist): if the file's uid isn't found in the system database. +.. method:: Path.read_bytes() + + Return the binary contents of the pointed-to file as a bytes object:: + + >>> p = Path('my_binary_file') + >>> p.write_bytes(b'Binary file contents') + 20 + >>> p.read_bytes() + b'Binary file contents' + + .. versionadded:: 3.5 + + +.. method:: Path.read_text(encoding=None, errors=None) + + Return the decoded contents of the pointed-to file as a string:: + + >>> p = Path('my_text_file') + >>> p.write_text('Text file contents') + 18 + >>> p.read_text() + 'Text file contents' + + The optional parameters have the same meaning as in :func:`open`. + + .. versionadded:: 3.5 + + .. method:: Path.rename(target) Rename this file or directory to the given *target*. *target* can be @@ -946,3 +974,36 @@ call fails (for example because the path doesn't exist): Remove this file or symbolic link. If the path points to a directory, use :func:`Path.rmdir` instead. + + +.. method:: Path.write_bytes(data) + + Open the file pointed to in bytes mode, write *data* to it, and close the + file:: + + >>> p = Path('my_binary_file') + >>> p.write_bytes(b'Binary file contents') + 20 + >>> p.read_bytes() + b'Binary file contents' + + An existing file of the same name is overwritten. + + .. versionadded:: 3.5 + + +.. method:: Path.write_text(data, encoding=None, errors=None) + + Open the file pointed to in text mode, write *data* to it, and close the + file:: + + >>> p = Path('my_text_file') + >>> p.write_text('Text file contents') + 18 + >>> p.read_text() + 'Text file contents' + + An existing file of the same name is overwritten. The optional parameters + have the same meaning as in :func:`open`. + + .. versionadded:: 3.5 diff --git a/Lib/pathlib.py b/Lib/pathlib.py index eff6ae3f0c9..5134eead647 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -1083,6 +1083,39 @@ class Path(PurePath): return io.open(str(self), mode, buffering, encoding, errors, newline, opener=self._opener) + def read_bytes(self): + """ + Open the file in bytes mode, read it, and close the file. + """ + with self.open(mode='rb') as f: + return f.read() + + def read_text(self, encoding=None, errors=None): + """ + Open the file in text mode, read it, and close the file. + """ + with self.open(mode='r', encoding=encoding, errors=errors) as f: + return f.read() + + def write_bytes(self, data): + """ + Open the file in bytes mode, write to it, and close the file. + """ + # type-check for the buffer interface before truncating the file + view = memoryview(data) + with self.open(mode='wb') as f: + return f.write(view) + + def write_text(self, data, encoding=None, errors=None): + """ + Open the file in text mode, write to it, and close the file. + """ + if not isinstance(data, str): + raise TypeError('data must be str, not %s' % + data.__class__.__name__) + with self.open(mode='w', encoding=encoding, errors=errors) as f: + return f.write(data) + def touch(self, mode=0o666, exist_ok=True): """ Create this file with the given access mode, if it doesn't exist. diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index 4f762176ba9..8839888c6af 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -1310,6 +1310,23 @@ class _BasePathTest(object): self.assertIsInstance(f, io.RawIOBase) self.assertEqual(f.read().strip(), b"this is file A") + def test_read_write_bytes(self): + p = self.cls(BASE) + (p / 'fileA').write_bytes(b'abcdefg') + self.assertEqual((p / 'fileA').read_bytes(), b'abcdefg') + # check that trying to write str does not truncate the file + self.assertRaises(TypeError, (p / 'fileA').write_bytes, 'somestr') + self.assertEqual((p / 'fileA').read_bytes(), b'abcdefg') + + def test_read_write_text(self): + p = self.cls(BASE) + (p / 'fileA').write_text('äbcdefg', encoding='latin-1') + self.assertEqual((p / 'fileA').read_text( + encoding='utf-8', errors='ignore'), 'bcdefg') + # check that trying to write bytes does not truncate the file + self.assertRaises(TypeError, (p / 'fileA').write_text, b'somebytes') + self.assertEqual((p / 'fileA').read_text(encoding='latin-1'), 'äbcdefg') + def test_iterdir(self): P = self.cls p = P(BASE) diff --git a/Misc/NEWS b/Misc/NEWS index 12d34ef7e5b..b26316f5d12 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -156,6 +156,9 @@ Core and Builtins Library ------- +- Issue #20218: Added convenience methods read_text/write_text and read_bytes/ + write_bytes to pathlib.Path objects. + - Issue #22437: Number of capturing groups in regular expression is no longer limited by 100.