From 28e700bd4d4000f3969636c9f459792208478131 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Mon, 19 Feb 2024 16:29:35 +0000 Subject: [PATCH 01/15] add copy() --- pyiron_workflow/snippets/files.py | 34 +++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/pyiron_workflow/snippets/files.py b/pyiron_workflow/snippets/files.py index bb2957a6..df9707af 100644 --- a/pyiron_workflow/snippets/files.py +++ b/pyiron_workflow/snippets/files.py @@ -1,4 +1,5 @@ from pathlib import Path +import shutil def delete_files_and_directories_recursively(path): @@ -107,3 +108,36 @@ def is_file(self): def delete(self): self.path.unlink() + + def _clean_directory_and_path( + self, new_file_name: str, directory: DirectoryObject | str | None=None + ): + new_path = Path(new_file_name) + file_name = new_path.name + if new_path.is_absolute(): + directory = new_path.cwd() + elif directory is None: + directory = self.directory + if isinstance(directory, str): + directory = DirectoryObject(directory) + return file_name, directory + + def copy( + self, new_file_name: str, directory: DirectoryObject | str | None=None + ): + """ + Copy an existing file to a new location. + + Args: + new_file_name (str): New file name. You can also set + an absolute path (in which case `directory` will be ignored) + directory (DirectoryObject): Directory. If None, the same + directory is used + + Returns: + (FileObject): file object of the new file + """ + file_name, directory = self._clean_directory_and_path(new_file_name, directory) + new_file = FileObject(file_name, directory) + shutil.copy(str(self.path), str(new_file.path)) + return new_file From 68b7927ddec87f2a03cbd5b7a26ce782ca43c163 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Mon, 19 Feb 2024 17:36:58 +0000 Subject: [PATCH 02/15] Add tests and corrections --- pyiron_workflow/snippets/files.py | 10 +++++++++- tests/unit/snippets/test_files.py | 13 +++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/pyiron_workflow/snippets/files.py b/pyiron_workflow/snippets/files.py index df9707af..4d3a924f 100644 --- a/pyiron_workflow/snippets/files.py +++ b/pyiron_workflow/snippets/files.py @@ -112,12 +112,20 @@ def delete(self): def _clean_directory_and_path( self, new_file_name: str, directory: DirectoryObject | str | None=None ): + """ + Internal routine to separate the file name and the directory in case + file name is given in absolute path etc. + """ new_path = Path(new_file_name) file_name = new_path.name if new_path.is_absolute(): - directory = new_path.cwd() + directory = str(new_path.resolve().parent) elif directory is None: directory = self.directory + else: + if isinstance(directory, DirectoryObject): + directory = directory.path + directory = str(directory / new_path.resolve().parent) if isinstance(directory, str): directory = DirectoryObject(directory) return file_name, directory diff --git a/tests/unit/snippets/test_files.py b/tests/unit/snippets/test_files.py index 93d8e90e..183a65b2 100644 --- a/tests/unit/snippets/test_files.py +++ b/tests/unit/snippets/test_files.py @@ -96,6 +96,19 @@ def test_remove(self): msg="Should be able to remove just one file", ) + def test_copy(self): + f = FileObject("test_copy.txt", self.directory) + f.write("sam wrote this wondrful thing") + new_file_1 = f.copy("another_test") + self.assertEqual(new_file_1.read(), "sam wrote this wondrful thing") + new_file_2 = f.copy("another_test", ".") + with open("another_test", "r") as file: + txt = file.read() + self.assertEqual(txt, "sam wrote this wondrful thing") + new_file_2.delete() # needed because current directory + new_file_3 = f.copy(str(f.path.parent / "another_test"), ".") + self.assertEqual(new_file_1.path.absolute(), new_file_3.path.absolute()) + if __name__ == '__main__': unittest.main() From e1ed537f94f894599477ebc1255ede9825c81e6d Mon Sep 17 00:00:00 2001 From: samwaseda Date: Mon, 19 Feb 2024 17:38:45 +0000 Subject: [PATCH 03/15] add additional information --- pyiron_workflow/snippets/files.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyiron_workflow/snippets/files.py b/pyiron_workflow/snippets/files.py index 4d3a924f..d93f6ce7 100644 --- a/pyiron_workflow/snippets/files.py +++ b/pyiron_workflow/snippets/files.py @@ -119,10 +119,16 @@ def _clean_directory_and_path( new_path = Path(new_file_name) file_name = new_path.name if new_path.is_absolute(): + # If absolute path, take that of new_file_name regardless of the + # name of directory directory = str(new_path.resolve().parent) elif directory is None: + # If directory is not given, take the directory of the current + # object directory = self.directory else: + # If the directory is given, use it as the main path and append + # additional path if given in new_file_name if isinstance(directory, DirectoryObject): directory = directory.path directory = str(directory / new_path.resolve().parent) From 8e6eec14f65475094f286db803315bc51f5f3377 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 20 Feb 2024 08:10:13 +0000 Subject: [PATCH 04/15] make it possible to resolve file path --- pyiron_workflow/snippets/files.py | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/pyiron_workflow/snippets/files.py b/pyiron_workflow/snippets/files.py index d93f6ce7..1d8d1edd 100644 --- a/pyiron_workflow/snippets/files.py +++ b/pyiron_workflow/snippets/files.py @@ -109,8 +109,11 @@ def is_file(self): def delete(self): self.path.unlink() - def _clean_directory_and_path( - self, new_file_name: str, directory: DirectoryObject | str | None=None + def _resolve_directory_and_path( + self, + file_name: str, + directory: DirectoryObject | str | None=None, + default_directory: str=".", ): """ Internal routine to separate the file name and the directory in case @@ -135,23 +138,3 @@ def _clean_directory_and_path( if isinstance(directory, str): directory = DirectoryObject(directory) return file_name, directory - - def copy( - self, new_file_name: str, directory: DirectoryObject | str | None=None - ): - """ - Copy an existing file to a new location. - - Args: - new_file_name (str): New file name. You can also set - an absolute path (in which case `directory` will be ignored) - directory (DirectoryObject): Directory. If None, the same - directory is used - - Returns: - (FileObject): file object of the new file - """ - file_name, directory = self._clean_directory_and_path(new_file_name, directory) - new_file = FileObject(file_name, directory) - shutil.copy(str(self.path), str(new_file.path)) - return new_file From 6c1c6f0cfcf77f3e1de7b752d0ab6e14da7425e8 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 20 Feb 2024 08:18:51 +0000 Subject: [PATCH 05/15] Make DirectoryObject recognize various init variables --- pyiron_workflow/snippets/files.py | 10 ++++++++-- tests/unit/snippets/test_files.py | 6 ++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/pyiron_workflow/snippets/files.py b/pyiron_workflow/snippets/files.py index bb2957a6..61139e0a 100644 --- a/pyiron_workflow/snippets/files.py +++ b/pyiron_workflow/snippets/files.py @@ -1,3 +1,4 @@ +from __future__ import annotations from pathlib import Path @@ -36,8 +37,13 @@ def categorize_folder_items(folder_path): class DirectoryObject: - def __init__(self, directory): - self.path = Path(directory) + def __init__(self, directory: str | Path | DirectoryObject): + if isinstance(directory, str): + self.path = Path(directory) + elif isinstance(directory, Path): + self.path = directory + elif isinstance(directory, DirectoryObject): + self.path = directory.path self.create() def create(self): diff --git a/tests/unit/snippets/test_files.py b/tests/unit/snippets/test_files.py index 93d8e90e..6d47aff7 100644 --- a/tests/unit/snippets/test_files.py +++ b/tests/unit/snippets/test_files.py @@ -10,6 +10,12 @@ def setUp(cls): def tearDown(cls): cls.directory.delete() + def test_directory_instantiation(self): + directory = DirectoryObject(Path("test")) + self.assertEqual(directory.path, self.directory.path) + directory = DirectoryObject(self.directory) + self.assertEqual(directory.path, self.directory.path) + def test_directory_exists(self): self.assertTrue(Path("test").exists() and Path("test").is_dir()) From 577cbbb6f11efb514997dc1ae96e005eff078a06 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 20 Feb 2024 08:45:32 +0000 Subject: [PATCH 06/15] allow absolute path and string directory in FileObject --- pyiron_workflow/snippets/files.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pyiron_workflow/snippets/files.py b/pyiron_workflow/snippets/files.py index 460829e1..bc504e22 100644 --- a/pyiron_workflow/snippets/files.py +++ b/pyiron_workflow/snippets/files.py @@ -91,8 +91,9 @@ def remove_files(self, *files: str): class FileObject: def __init__(self, file_name: str, directory: DirectoryObject): - self.directory = directory - self._file_name = file_name + self._file_name, self.directory = self._resolve_directory_and_path( + file_name=file_name, directory=directory, default_directory="." + ) @property def file_name(self): @@ -125,22 +126,21 @@ def _resolve_directory_and_path( Internal routine to separate the file name and the directory in case file name is given in absolute path etc. """ - new_path = Path(new_file_name) - file_name = new_path.name + path = Path(file_name) + file_name = path.name if new_path.is_absolute(): # If absolute path, take that of new_file_name regardless of the # name of directory - directory = str(new_path.resolve().parent) + directory = str(path.resolve().parent) elif directory is None: - # If directory is not given, take the directory of the current - # object - directory = self.directory + # If directory is not given, take default directory + directory = default_directory else: # If the directory is given, use it as the main path and append # additional path if given in new_file_name if isinstance(directory, DirectoryObject): directory = directory.path - directory = str(directory / new_path.resolve().parent) - if isinstance(directory, str): + directory = directory / new_path.resolve().parent + if not isinstance(directory, DirectoryObject): directory = DirectoryObject(directory) return file_name, directory From d421a6be3a1df69e2e289e405189739ce6890137 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 20 Feb 2024 08:51:15 +0000 Subject: [PATCH 07/15] remove resolve to allow relative path --- pyiron_workflow/snippets/files.py | 6 +++--- tests/unit/snippets/test_files.py | 13 ------------- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/pyiron_workflow/snippets/files.py b/pyiron_workflow/snippets/files.py index bc504e22..ab885780 100644 --- a/pyiron_workflow/snippets/files.py +++ b/pyiron_workflow/snippets/files.py @@ -128,10 +128,10 @@ def _resolve_directory_and_path( """ path = Path(file_name) file_name = path.name - if new_path.is_absolute(): + if path.is_absolute(): # If absolute path, take that of new_file_name regardless of the # name of directory - directory = str(path.resolve().parent) + directory = str(path.parent) elif directory is None: # If directory is not given, take default directory directory = default_directory @@ -140,7 +140,7 @@ def _resolve_directory_and_path( # additional path if given in new_file_name if isinstance(directory, DirectoryObject): directory = directory.path - directory = directory / new_path.resolve().parent + directory = directory / path.parent if not isinstance(directory, DirectoryObject): directory = DirectoryObject(directory) return file_name, directory diff --git a/tests/unit/snippets/test_files.py b/tests/unit/snippets/test_files.py index 9e6966f6..6d47aff7 100644 --- a/tests/unit/snippets/test_files.py +++ b/tests/unit/snippets/test_files.py @@ -102,19 +102,6 @@ def test_remove(self): msg="Should be able to remove just one file", ) - def test_copy(self): - f = FileObject("test_copy.txt", self.directory) - f.write("sam wrote this wondrful thing") - new_file_1 = f.copy("another_test") - self.assertEqual(new_file_1.read(), "sam wrote this wondrful thing") - new_file_2 = f.copy("another_test", ".") - with open("another_test", "r") as file: - txt = file.read() - self.assertEqual(txt, "sam wrote this wondrful thing") - new_file_2.delete() # needed because current directory - new_file_3 = f.copy(str(f.path.parent / "another_test"), ".") - self.assertEqual(new_file_1.path.absolute(), new_file_3.path.absolute()) - if __name__ == '__main__': unittest.main() From 2b29ec059ba3ba4e90fcf1709f00bb56ccb99066 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 20 Feb 2024 08:52:28 +0000 Subject: [PATCH 08/15] remove resolve to allow relative path --- tests/unit/snippets/test_files.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/unit/snippets/test_files.py b/tests/unit/snippets/test_files.py index 6d47aff7..fd032e17 100644 --- a/tests/unit/snippets/test_files.py +++ b/tests/unit/snippets/test_files.py @@ -16,6 +16,18 @@ def test_directory_instantiation(self): directory = DirectoryObject(self.directory) self.assertEqual(directory.path, self.directory.path) + def test_file_instantiation(self): + self.assertEqual( + FileObject("test_copy.txt", self.directory), + FileObject("test_copy.txt", "test"), + msg="DirectoryObject and str must give the same object" + ) + self.assertEqual( + FileObject("test/test_copy.txt"), + FileObject("test_copy.txt", "test"), + msg="File path not same as directory path" + ) + def test_directory_exists(self): self.assertTrue(Path("test").exists() and Path("test").is_dir()) From ce446fcd9fb3a514046b5aafbb3acf9b87a8636e Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 20 Feb 2024 09:01:00 +0000 Subject: [PATCH 09/15] bugfixes and add tests --- pyiron_workflow/snippets/files.py | 17 +++++++++-------- tests/unit/snippets/test_files.py | 8 ++++---- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/pyiron_workflow/snippets/files.py b/pyiron_workflow/snippets/files.py index ab885780..6048334d 100644 --- a/pyiron_workflow/snippets/files.py +++ b/pyiron_workflow/snippets/files.py @@ -90,7 +90,7 @@ def remove_files(self, *files: str): class FileObject: - def __init__(self, file_name: str, directory: DirectoryObject): + def __init__(self, file_name: str, directory: DirectoryObject=None): self._file_name, self.directory = self._resolve_directory_and_path( file_name=file_name, directory=directory, default_directory="." ) @@ -132,14 +132,15 @@ def _resolve_directory_and_path( # If absolute path, take that of new_file_name regardless of the # name of directory directory = str(path.parent) - elif directory is None: - # If directory is not given, take default directory - directory = default_directory else: - # If the directory is given, use it as the main path and append - # additional path if given in new_file_name - if isinstance(directory, DirectoryObject): - directory = directory.path + if directory is None: + # If directory is not given, take default directory + directory = default_directory + else: + # If the directory is given, use it as the main path and append + # additional path if given in new_file_name + if isinstance(directory, DirectoryObject): + directory = directory.path directory = directory / path.parent if not isinstance(directory, DirectoryObject): directory = DirectoryObject(directory) diff --git a/tests/unit/snippets/test_files.py b/tests/unit/snippets/test_files.py index fd032e17..5bc8fbc7 100644 --- a/tests/unit/snippets/test_files.py +++ b/tests/unit/snippets/test_files.py @@ -18,13 +18,13 @@ def test_directory_instantiation(self): def test_file_instantiation(self): self.assertEqual( - FileObject("test_copy.txt", self.directory), - FileObject("test_copy.txt", "test"), + FileObject("test.txt", self.directory).path, + FileObject("test.txt", "test").path, msg="DirectoryObject and str must give the same object" ) self.assertEqual( - FileObject("test/test_copy.txt"), - FileObject("test_copy.txt", "test"), + FileObject("test/test.txt").path, + FileObject("test.txt", "test").path, msg="File path not same as directory path" ) From f2b84f4006977a0d5adf862dc5de2a236c8f4f56 Mon Sep 17 00:00:00 2001 From: Sam Dareska <37879103+samwaseda@users.noreply.github.com> Date: Thu, 22 Feb 2024 10:44:20 +0100 Subject: [PATCH 10/15] Update pyiron_workflow/snippets/files.py Co-authored-by: Liam Huber --- pyiron_workflow/snippets/files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_workflow/snippets/files.py b/pyiron_workflow/snippets/files.py index 6048334d..70a014a2 100644 --- a/pyiron_workflow/snippets/files.py +++ b/pyiron_workflow/snippets/files.py @@ -119,7 +119,7 @@ def delete(self): def _resolve_directory_and_path( self, file_name: str, - directory: DirectoryObject | str | None=None, + directory: DirectoryObject | str | Path | None=None, default_directory: str=".", ): """ From fc4a69841b61350adeffbfe6335c2541bceed689 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Thu, 22 Feb 2024 09:58:48 +0000 Subject: [PATCH 11/15] add absolute path --- pyiron_workflow/snippets/files.py | 66 ++++++++++++++++--------------- tests/unit/snippets/test_files.py | 1 + 2 files changed, 36 insertions(+), 31 deletions(-) diff --git a/pyiron_workflow/snippets/files.py b/pyiron_workflow/snippets/files.py index 6048334d..5e455234 100644 --- a/pyiron_workflow/snippets/files.py +++ b/pyiron_workflow/snippets/files.py @@ -37,6 +37,40 @@ def categorize_folder_items(folder_path): return results +def _resolve_directory_and_path( + file_name: str, + directory: DirectoryObject | str | None=None, + default_directory: str=".", +): + """ + Internal routine to separate the file name and the directory in case + file name is given in absolute path etc. + """ + path = Path(file_name) + file_name = path.name + if path.is_absolute(): + if directory is not None: + raise ValueError( + "You cannot set `directory` when `file_name` is an absolute path" + ) + # If absolute path, take that of new_file_name regardless of the + # name of directory + directory = str(path.parent) + else: + if directory is None: + # If directory is not given, take default directory + directory = default_directory + else: + # If the directory is given, use it as the main path and append + # additional path if given in new_file_name + if isinstance(directory, DirectoryObject): + directory = directory.path + directory = directory / path.parent + if not isinstance(directory, DirectoryObject): + directory = DirectoryObject(directory) + return file_name, directory + + class DirectoryObject: def __init__(self, directory: str | Path | DirectoryObject): if isinstance(directory, str): @@ -91,7 +125,7 @@ def remove_files(self, *files: str): class FileObject: def __init__(self, file_name: str, directory: DirectoryObject=None): - self._file_name, self.directory = self._resolve_directory_and_path( + self._file_name, self.directory = _resolve_directory_and_path( file_name=file_name, directory=directory, default_directory="." ) @@ -115,33 +149,3 @@ def is_file(self): def delete(self): self.path.unlink() - - def _resolve_directory_and_path( - self, - file_name: str, - directory: DirectoryObject | str | None=None, - default_directory: str=".", - ): - """ - Internal routine to separate the file name and the directory in case - file name is given in absolute path etc. - """ - path = Path(file_name) - file_name = path.name - if path.is_absolute(): - # If absolute path, take that of new_file_name regardless of the - # name of directory - directory = str(path.parent) - else: - if directory is None: - # If directory is not given, take default directory - directory = default_directory - else: - # If the directory is given, use it as the main path and append - # additional path if given in new_file_name - if isinstance(directory, DirectoryObject): - directory = directory.path - directory = directory / path.parent - if not isinstance(directory, DirectoryObject): - directory = DirectoryObject(directory) - return file_name, directory diff --git a/tests/unit/snippets/test_files.py b/tests/unit/snippets/test_files.py index 5bc8fbc7..38a99746 100644 --- a/tests/unit/snippets/test_files.py +++ b/tests/unit/snippets/test_files.py @@ -27,6 +27,7 @@ def test_file_instantiation(self): FileObject("test.txt", "test").path, msg="File path not same as directory path" ) + self.assertRaises(ValueError, FileObject, "/test.txt", "test") def test_directory_exists(self): self.assertTrue(Path("test").exists() and Path("test").is_dir()) From 1326689122e4fc996bcdec0a8c23c7f8cfb80d52 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Thu, 22 Feb 2024 16:17:01 +0000 Subject: [PATCH 12/15] codacy --- pyiron_workflow/snippets/files.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyiron_workflow/snippets/files.py b/pyiron_workflow/snippets/files.py index 5e455234..ee9e96d8 100644 --- a/pyiron_workflow/snippets/files.py +++ b/pyiron_workflow/snippets/files.py @@ -39,8 +39,8 @@ def categorize_folder_items(folder_path): def _resolve_directory_and_path( file_name: str, - directory: DirectoryObject | str | None=None, - default_directory: str=".", + directory: DirectoryObject | str | None = None, + default_directory: str = ".", ): """ Internal routine to separate the file name and the directory in case @@ -124,7 +124,7 @@ def remove_files(self, *files: str): class FileObject: - def __init__(self, file_name: str, directory: DirectoryObject=None): + def __init__(self, file_name: str, directory: DirectoryObject = None): self._file_name, self.directory = _resolve_directory_and_path( file_name=file_name, directory=directory, default_directory="." ) From e8c88d4e16cb19d5d566b772e7214aa2ac393e29 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Thu, 22 Feb 2024 16:25:30 +0000 Subject: [PATCH 13/15] I should have taken a look at Codacy --- pyiron_workflow/snippets/files.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyiron_workflow/snippets/files.py b/pyiron_workflow/snippets/files.py index ee9e96d8..b9d6b099 100644 --- a/pyiron_workflow/snippets/files.py +++ b/pyiron_workflow/snippets/files.py @@ -1,6 +1,5 @@ from __future__ import annotations from pathlib import Path -import shutil def delete_files_and_directories_recursively(path): From 3ab53c56a6d75e38b4f050a9ba65b0874222356f Mon Sep 17 00:00:00 2001 From: samwaseda Date: Thu, 22 Feb 2024 16:36:43 +0000 Subject: [PATCH 14/15] I hope this is the correct way to put windows --- tests/unit/snippets/test_files.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/unit/snippets/test_files.py b/tests/unit/snippets/test_files.py index 38a99746..28b5a3f2 100644 --- a/tests/unit/snippets/test_files.py +++ b/tests/unit/snippets/test_files.py @@ -1,6 +1,7 @@ import unittest from pyiron_workflow.snippets.files import DirectoryObject, FileObject from pathlib import Path +import platform class TestFiles(unittest.TestCase): @@ -27,7 +28,12 @@ def test_file_instantiation(self): FileObject("test.txt", "test").path, msg="File path not same as directory path" ) - self.assertRaises(ValueError, FileObject, "/test.txt", "test") + + if platform.system() == "Windows": + self.assertRaises(ValueError, FileObject, "\\test.txt", "test") + else: + self.assertRaises(ValueError, FileObject, "/test.txt", "test") + def test_directory_exists(self): self.assertTrue(Path("test").exists() and Path("test").is_dir()) From 4adb00ac107bf3a9351093313e0399384e7f05c5 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Thu, 22 Feb 2024 16:47:37 +0000 Subject: [PATCH 15/15] one more try --- tests/unit/snippets/test_files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/snippets/test_files.py b/tests/unit/snippets/test_files.py index 28b5a3f2..32b28ac1 100644 --- a/tests/unit/snippets/test_files.py +++ b/tests/unit/snippets/test_files.py @@ -30,7 +30,7 @@ def test_file_instantiation(self): ) if platform.system() == "Windows": - self.assertRaises(ValueError, FileObject, "\\test.txt", "test") + self.assertRaises(ValueError, FileObject, "C:\\test.txt", "test") else: self.assertRaises(ValueError, FileObject, "/test.txt", "test")