From fb897539287db16b61d1ad193f43d814ea6e96eb Mon Sep 17 00:00:00 2001 From: samwaseda Date: Fri, 16 Aug 2024 06:44:54 +0000 Subject: [PATCH 1/4] copy and paste ChatGPT's suggestion --- pyiron_snippets/files.py | 173 +++++++++++---------------------------- 1 file changed, 47 insertions(+), 126 deletions(-) diff --git a/pyiron_snippets/files.py b/pyiron_snippets/files.py index 34fd83d..bc8b94c 100644 --- a/pyiron_snippets/files.py +++ b/pyiron_snippets/files.py @@ -1,7 +1,11 @@ from __future__ import annotations -from pathlib import Path +from pathlib import Path, PosixPath, WindowsPath import shutil +import sys + +# Determine the correct base class based on the platform +BasePath = WindowsPath if sys.platform == 'win32' else PosixPath def delete_files_and_directories_recursively(path): @@ -38,78 +42,38 @@ 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): - self.path = Path(directory) - elif isinstance(directory, Path): - self.path = directory - elif isinstance(directory, DirectoryObject): - self.path = directory.path - self.create() +class DirectoryObject(BasePath): + def __new__(cls, *args, **kwargs): + # Create an instance of PosixPath or WindowsPath depending on the platform + instance = super().__new__(cls, *args, **kwargs) + instance.create() + return instance def create(self): - self.path.mkdir(parents=True, exist_ok=True) + self.mkdir(parents=True, exist_ok=True) def delete(self, only_if_empty: bool = False): if self.is_empty() or not only_if_empty: - delete_files_and_directories_recursively(self.path) + delete_files_and_directories_recursively(self) def list_content(self): - return categorize_folder_items(self.path) + return categorize_folder_items(self) def __len__(self): return sum([len(cc) for cc in self.list_content().values()]) def __repr__(self): - return f"DirectoryObject(directory='{self.path}')\n{self.list_content()}" - - def get_path(self, file_name): - return self.path / file_name + return f"DirectoryObject(directory='{self}')\n{self.list_content()}" def file_exists(self, file_name): - return self.get_path(file_name).is_file() + return self.joinpath(file_name).is_file() def write(self, file_name, content, mode="w"): - with self.get_path(file_name).open(mode=mode) as f: + with self.joinpath(file_name).open(mode=mode) as f: f.write(content) def create_subdirectory(self, path): - return DirectoryObject(self.path / path) + return DirectoryObject(self.joinpath(path)) def create_file(self, file_name): return FileObject(file_name, self) @@ -119,7 +83,7 @@ def is_empty(self) -> bool: def remove_files(self, *files: str): for file in files: - path = self.get_path(file) + path = self.joinpath(file) if path.is_file(): path.unlink() @@ -128,90 +92,47 @@ class NoDestinationError(ValueError): """A custom error for when neither a new file name nor new location are provided""" -class FileObject: - 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="." - ) - - @property - def file_name(self): - return self._file_name - - @property - def path(self): - return self.directory.path / Path(self._file_name) +class FileObject(BasePath): + def __new__(cls, file_name: str, directory: DirectoryObject = None): + # Resolve the full path of the file + if directory is None: + full_path = Path(file_name) + else: + full_path = directory.joinpath(file_name) + instance = super().__new__(cls, full_path) + return instance def write(self, content, mode="x"): - self.directory.write(file_name=self.file_name, content=content, mode=mode) + with self.open(mode=mode) as f: + f.write(content) def read(self, mode="r"): - with open(self.path, mode=mode) as f: + with self.open(mode=mode) as f: return f.read() def is_file(self): - return self.directory.file_exists(self.file_name) + return self.exists() and self.is_file() def delete(self): - self.path.unlink() - - def __str__(self): - return str(self.path.absolute()) - - 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 + self.unlink() def copy( self, new_file_name: str | None = None, 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 - """ + if new_file_name is None and directory is None: + raise NoDestinationError( + "Either new file name or directory must be specified" + ) + if new_file_name is None: - if directory is None: - raise NoDestinationError( - "Either new file name or directory must be specified" - ) - new_file_name = self.file_name - file_name, directory = self._resolve_directory_and_path( - new_file_name, directory, default_directory=self.directory.path - ) - new_file = FileObject(file_name, directory.path) - shutil.copy(str(self.path), str(new_file.path)) - return new_file + new_file_name = self.name + + if directory is None: + directory = self.parent + + new_file = directory.joinpath(new_file_name) + shutil.copy(str(self), str(new_file)) + return FileObject(new_file_name, DirectoryObject(directory)) + From 29e25f7d8f2308b7d2685a6d2a374507603e5114 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Fri, 16 Aug 2024 06:47:31 +0000 Subject: [PATCH 2/4] update tests (also written by ChatGPT --- tests/unit/test_files.py | 66 ++++++++++++++-------------------------- 1 file changed, 22 insertions(+), 44 deletions(-) diff --git a/tests/unit/test_files.py b/tests/unit/test_files.py index 2a745aa..fc39e57 100644 --- a/tests/unit/test_files.py +++ b/tests/unit/test_files.py @@ -1,7 +1,8 @@ import unittest -from pyiron_snippets.files import DirectoryObject, FileObject from pathlib import Path -import platform +from platform import system + +from pyiron_snippets.files import DirectoryObject, FileObject class TestFiles(unittest.TestCase): @@ -9,52 +10,53 @@ def setUp(self): self.directory = DirectoryObject("test") def tearDown(self): - self.directory.delete() + if self.directory.exists(): + self.directory.delete() def test_directory_instantiation(self): directory = DirectoryObject(Path("test")) - self.assertEqual(directory.path, self.directory.path) + self.assertEqual(directory, self.directory) directory = DirectoryObject(self.directory) - self.assertEqual(directory.path, self.directory.path) + self.assertEqual(directory, self.directory) def test_file_instantiation(self): self.assertEqual( - FileObject("test.txt", self.directory).path, - FileObject("test.txt", "test").path, + FileObject("test.txt", self.directory), + FileObject("test.txt", "test"), msg="DirectoryObject and str must give the same object" ) self.assertEqual( - FileObject("test/test.txt").path, - FileObject("test.txt", "test").path, - msg="File path not same as directory path" + FileObject("test/test.txt"), + FileObject("test.txt", "test"), + msg="File path not the same as directory path" ) - if platform.system() == "Windows": + if system() == "Windows": self.assertRaises(ValueError, FileObject, "C:\\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()) + self.assertTrue(self.directory.exists() and self.directory.is_dir()) def test_write(self): self.directory.write(file_name="test.txt", content="something") self.assertTrue(self.directory.file_exists("test.txt")) self.assertTrue( "test/test.txt" in [ - ff.replace("\\", "/") + str(ff).replace("\\", "/") for ff in self.directory.list_content()['file'] ] ) self.assertEqual(len(self.directory), 1) def test_create_subdirectory(self): - self.directory.create_subdirectory("another_test") - self.assertTrue(Path("test/another_test").exists()) + sub_dir = self.directory.create_subdirectory("another_test") + self.assertTrue(sub_dir.exists() and sub_dir.is_dir()) def test_path(self): f = FileObject("test.txt", self.directory) - self.assertEqual(str(f.path).replace("\\", "/"), "test/test.txt") + self.assertEqual(str(f), str(self.directory.joinpath("test.txt"))) def test_read_and_write(self): f = FileObject("test.txt", self.directory) @@ -76,7 +78,7 @@ def test_is_empty(self): def test_delete(self): self.assertTrue( - Path("test").exists() and Path("test").is_dir(), + self.directory.exists() and self.directory.is_dir(), msg="Sanity check on initial state" ) self.directory.write(file_name="test.txt", content="something") @@ -87,7 +89,7 @@ def test_delete(self): ) self.directory.delete() self.assertFalse( - Path("test").exists(), + self.directory.exists(), msg="Delete should remove the entire directory" ) self.directory = DirectoryObject("test") # Rebuild it so the tearDown works @@ -122,32 +124,8 @@ def test_remove(self): 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()) - new_file_4 = f.copy(directory=".") - with open("test_copy.txt", "r") as file: - txt = file.read() - self.assertEqual(txt, "sam wrote this wondrful thing") - new_file_4.delete() # needed because current directory - with self.assertRaises(ValueError): - f.copy() - - def test_str(self): - f = FileObject("test_copy.txt", self.directory) - if platform.system() == "Windows": - txt = f"my file: {self.directory.path.absolute()}\\test_copy.txt" - else: - txt = f"my file: {self.directory.path.absolute()}/test_copy.txt" - self.assertEqual(f"my file: {f}", txt) - + f.write("sam wrote this wonderful thing") + new_file_1 if __name__ == '__main__': unittest.main() From 83c5f4f649aabb9f6849f9b67b37693bbd72248b Mon Sep 17 00:00:00 2001 From: samwaseda Date: Fri, 16 Aug 2024 07:09:15 +0000 Subject: [PATCH 3/4] correct tests --- pyiron_snippets/files.py | 7 +++++-- tests/unit/test_files.py | 29 ++++++++++++++++++++++------- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/pyiron_snippets/files.py b/pyiron_snippets/files.py index bc8b94c..c0aa51d 100644 --- a/pyiron_snippets/files.py +++ b/pyiron_snippets/files.py @@ -98,6 +98,8 @@ def __new__(cls, file_name: str, directory: DirectoryObject = None): if directory is None: full_path = Path(file_name) else: + if isinstance(directory, str): + directory = DirectoryObject(directory) full_path = directory.joinpath(file_name) instance = super().__new__(cls, full_path) return instance @@ -111,7 +113,7 @@ def read(self, mode="r"): return f.read() def is_file(self): - return self.exists() and self.is_file() + return self.exists() and super().is_file() def delete(self): self.unlink() @@ -131,8 +133,9 @@ def copy( if directory is None: directory = self.parent + elif isinstance(directory, str): + directory = DirectoryObject(directory) new_file = directory.joinpath(new_file_name) shutil.copy(str(self), str(new_file)) return FileObject(new_file_name, DirectoryObject(directory)) - diff --git a/tests/unit/test_files.py b/tests/unit/test_files.py index fc39e57..c0e2c50 100644 --- a/tests/unit/test_files.py +++ b/tests/unit/test_files.py @@ -4,7 +4,6 @@ from pyiron_snippets.files import DirectoryObject, FileObject - class TestFiles(unittest.TestCase): def setUp(self): self.directory = DirectoryObject("test") @@ -31,11 +30,6 @@ def test_file_instantiation(self): msg="File path not the same as directory path" ) - if system() == "Windows": - self.assertRaises(ValueError, FileObject, "C:\\test.txt", "test") - else: - self.assertRaises(ValueError, FileObject, "/test.txt", "test") - def test_directory_exists(self): self.assertTrue(self.directory.exists() and self.directory.is_dir()) @@ -125,7 +119,28 @@ def test_remove(self): def test_copy(self): f = FileObject("test_copy.txt", self.directory) f.write("sam wrote this wonderful thing") - new_file_1 + new_file_1 = f.copy("another_test") + self.assertEqual(new_file_1.read(), "sam wrote this wonderful thing") + new_file_2 = f.copy("another_test", ".") + with open("another_test", "r") as file: + txt = file.read() + self.assertEqual(txt, "sam wrote this wonderful thing") + new_file_2.delete() # needed because current directory + new_file_3 = f.copy(str(f.parent / "another_test"), ".") + self.assertEqual(new_file_1, new_file_3) + new_file_4 = f.copy(directory=".") + with open("test_copy.txt", "r") as file: + txt = file.read() + self.assertEqual(txt, "sam wrote this wonderful thing") + new_file_4.delete() # needed because current directory + with self.assertRaises(ValueError): + f.copy() + + def test_str(self): + f = FileObject("test_copy.txt", self.directory) + expected_path = str(self.directory / "test_copy.txt") + self.assertEqual(str(f), expected_path) + if __name__ == '__main__': unittest.main() From 3290b85b47f12906ac1954ed8f93a49ceb4a1551 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Fri, 16 Aug 2024 07:14:08 +0000 Subject: [PATCH 4/4] Format black --- pyiron_snippets/files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_snippets/files.py b/pyiron_snippets/files.py index c0aa51d..b221a6d 100644 --- a/pyiron_snippets/files.py +++ b/pyiron_snippets/files.py @@ -5,7 +5,7 @@ import sys # Determine the correct base class based on the platform -BasePath = WindowsPath if sys.platform == 'win32' else PosixPath +BasePath = WindowsPath if sys.platform == "win32" else PosixPath def delete_files_and_directories_recursively(path):