From 4a9cf290f0612be836d07404a08b21136f8c89e8 Mon Sep 17 00:00:00 2001 From: matfax Date: Tue, 15 Oct 2019 23:19:57 +0200 Subject: [PATCH] fix: returned generators and iterators yield wrapped Paths now --- mutapath/decorator.py | 20 +++++++++------- mutapath/immutapath.py | 29 ++++++++++++++++++----- tests/test_immutapath.py | 50 +++++++++++++++++++++++++--------------- tests/test_mutapath.py | 29 ++++++++++++++++------- tests/test_with_path.py | 26 +++++++++++++++++++++ 5 files changed, 114 insertions(+), 40 deletions(-) diff --git a/mutapath/decorator.py b/mutapath/decorator.py index 6944db9..cc67ec6 100644 --- a/mutapath/decorator.py +++ b/mutapath/decorator.py @@ -21,20 +21,21 @@ def __is_def(member): return inspect.isroutine(member) +def _convert_path(result): + if isinstance(result, path.Path): + return mutapath.Path(result) + return result + + def __path_func(orig_func): def wrap_decorator(*args, **kwargs): result = orig_func(*args, **kwargs) - if isinstance(result, path.Path): - return mutapath.Path(result) - return result + return _convert_path(result) return wrap_decorator def path_wrap(cls): - for name, method in inspect.getmembers(path.Path, __is_def): - if not name.startswith("__"): - setattr(path.Path, name, __path_func(method)) for name, method in inspect.getmembers(cls, __is_def): if name not in __EXCLUDE_FROM_WRAPPING: setattr(cls, name, __path_func(method)) @@ -48,8 +49,11 @@ def mutation_decorator(self, *args, **kwargs): orig_func = getattr(path.Path, method_name) if isinstance(self, Path): result = orig_func(self._contained, *args, **kwargs) - if isinstance(result, mutapath.Path): - self._contained = result._contained + if isinstance(result, path.Path): + self._contained = result + return self + elif isinstance(result, mutapath.Path): + self._contained = getattr(result, "_contained") return self return result else: diff --git a/mutapath/immutapath.py b/mutapath/immutapath.py index 425e8e5..193776b 100644 --- a/mutapath/immutapath.py +++ b/mutapath/immutapath.py @@ -4,12 +4,14 @@ import pathlib from contextlib import contextmanager from dataclasses import dataclass -from typing import Union, Iterable, ClassVar, Callable +from types import GeneratorType +from typing import Union, Iterable, ClassVar, Callable, List +from xml.dom.minicompat import StringTypes import path import mutapath -from mutapath.decorator import path_wrap +from mutapath.decorator import path_wrap, _convert_path from mutapath.exceptions import PathException @@ -36,8 +38,23 @@ def __post_init__(self): def __dir__(self) -> Iterable[str]: return sorted(super(Path, self).__dir__()) + dir(path.Path) + @staticmethod + def __wrap_attribute(orig_func): + def __wrap_decorator(*args, **kwargs): + result = orig_func(*args, **kwargs) + if isinstance(result, List) and not isinstance(result, StringTypes): + return list(map(_convert_path, result)) + if isinstance(result, Iterable) and not isinstance(result, StringTypes): + return iter(map(_convert_path, result)) + if isinstance(result, GeneratorType): + return map(_convert_path, result) + return _convert_path(result) + + return __wrap_decorator + def __getattr__(self, item): - return getattr(self._contained, item) + attr = getattr(self._contained, item) + return Path.__wrap_attribute(attr) def __setattr__(self, key, value): if key == "_contained": @@ -152,7 +169,7 @@ def getcwd(cls) -> Path: @path.multimethod def joinpath(self, first, *others) -> Path: - contained_others = map(str, list(others)) + contained_others = map(str, others) joined = path.Path.joinpath(self._contained, str(first), *contained_others) return Path(joined) @@ -315,12 +332,12 @@ def _op_context(self, name: str, op: Callable): except FileExistsError as e: raise PathException( f"{name.capitalize()} to {current_file.normpath()} failed because the file already exists. " - f"Falling back to original value {self._contained}.") from e + f"Falling back to original value {self._contained}.") from e else: if not current_file.exists(): raise PathException( f"{name.capitalize()} to {current_file.normpath()} failed because can not be found. " - f"Falling back to original value {self._contained}.") + f"Falling back to original value {self._contained}.") self._contained = current_file diff --git a/tests/test_immutapath.py b/tests/test_immutapath.py index 0d2d933..59f36fb 100644 --- a/tests/test_immutapath.py +++ b/tests/test_immutapath.py @@ -11,107 +11,119 @@ class TestPath(unittest.TestCase): def test_with_name_posix(self): expected = Path("/A/B/other") actual = Path("/A/B/test1.txt").with_name("other") - self.assertEqual(expected.normpath(), actual.normpath()) + self.assertEqual(expected, actual) + self.assertIsInstance(actual, Path) def test_with_name_win(self): if os.name == 'nt': expected = Path("C:/B/other") actual = Path("C:/B/test1.txt").with_name("other") - self.assertEqual(expected.abspath(), actual.abspath()) + self.assertEqual(expected, actual) + self.assertIsInstance(actual, Path) def test_with_base_posix(self): expected = Path("/home/joe/folder/sub") actual = Path("/home/doe/folder/sub").with_base("/home/joe") - self.assertEqual(expected.normpath(), actual.normpath()) + self.assertEqual(expected, actual) + self.assertIsInstance(actual, Path) def test_with_base_length_posix(self): expected = Path("/home/joe/doe/folder/sub") actual = Path("/home/doe/folder/sub").with_base("/home/joe", 1) - self.assertEqual(expected.normpath(), actual.normpath()) + self.assertEqual(expected, actual) + self.assertIsInstance(actual, Path) def test_with_base_win(self): if os.name == 'nt': expected = Path("C:/Users/joe/folder/sub") actual = Path("C:/Users/doe/folder/sub").with_base("C:/Users/joe") - self.assertEqual(expected.normpath(), actual.normpath()) + self.assertEqual(expected, actual) + self.assertIsInstance(actual, Path) def test_with_base_length_win(self): if os.name == 'nt': expected = Path("C:/Users/joe/doe/folder/sub").abspath() actual = Path("C:/Users/doe/folder/sub").abspath().with_base("C:/Users/joe", 1) - self.assertEqual(expected.normpath(), actual.normpath()) + self.assertEqual(expected, actual) + self.assertIsInstance(actual, Path) def test_with_base_fail(self): - def too_long(): + with self.assertRaises(ValueError): Path("/A/B/other.txt").with_base("/A/B/C") - self.assertRaises(ValueError, too_long) - def test_with_stem(self): expected = Path("/A/B/other.txt") actual = Path("/A/B/test1.txt").with_stem("other") - self.assertEqual(expected.normpath(), actual.normpath()) + self.assertEqual(expected, actual) + self.assertIsInstance(actual, Path) def test_with_parent(self): other = Path("/A/D/other.txt") expected = Path("D/other.txt") actual = Path("/A/B/other.txt").with_parent(other.dirname.name) - self.assertEqual(expected.normpath(), actual.normpath()) + self.assertEqual(expected, actual) + self.assertIsInstance(actual, Path) def test_static_joinpath(self): expected = Path("/A/B/C/D/other.txt") actual = Path.joinpath("/A/B", "C/", Path("D"), MutaPath("other.txt")) - self.assertEqual(expected.normpath(), actual.normpath()) + self.assertEqual(expected, actual) + self.assertIsInstance(actual, Path) def test_joinpath(self): expected = Path("/A/B/C/D/other.txt") actual = Path("/A/B").joinpath("C", Path("D"), MutaPath("other.txt")) - self.assertEqual(expected.normpath(), actual.normpath()) + self.assertEqual(expected, actual) + self.assertIsInstance(actual, Path) def test_pathlib_path(self): expected = Path("/A/B/other.txt") actual = Path(pathlib.Path("/A/B")).joinpath(pathlib.PurePosixPath("other.txt")) - self.assertEqual(expected.normpath(), actual.normpath()) + self.assertEqual(expected, actual) + self.assertIsInstance(actual, Path) def test_block_setter(self): some = Path("/A/B/other.txt") - - def set_name(): + with self.assertRaises(AttributeError): some.name = "try" - self.assertRaises(AttributeError, set_name) - def test_eq(self): some = Path("/A/B/other.txt") other = path.Path("/A/B/other.txt") third = pathlib.Path("/A/B/other.txt") self.assertEqual(some, other) self.assertEqual(some, third) + self.assertIsInstance(some, Path) def test_add(self): expected = Path("/A/B/other.txt") actual = Path("/A/") + "/B/" + "/other.txt" self.assertEqual(expected, actual) + self.assertIsInstance(actual, Path) def test_radd(self): expected = Path("/A/B/other.txt") actual = "/A/" + Path("/B/") + "/other.txt" self.assertEqual(expected, actual) + self.assertIsInstance(actual, Path) def test_div(self): expected = Path("/A/B/other.txt") actual = Path("/A/") / "B/other.txt" self.assertEqual(expected, actual) + self.assertIsInstance(actual, Path) def test_rdiv(self): expected = Path("/A/B/other.txt") actual = "/A/" / Path("B") / "other.txt" self.assertEqual(expected, actual) + self.assertIsInstance(actual, Path) def test_capsulation(self): excpected = Path("/A/B") actual = Path(Path(excpected)) self.assertEqual(excpected, actual) + self.assertIsInstance(actual, Path) def test_repr(self): excpected = Path("/A/B") @@ -127,12 +139,14 @@ def test_home(self): actual = Path("/A/B/C").relpath("/A").home self.assertEqual(excpected, actual) self.assertEqual(excpected.abspath(), actual.abspath()) + self.assertIsInstance(actual, Path) def test_home_root(self): excpected = Path(".") actual = Path("/").home self.assertEqual(excpected, actual) self.assertEqual(excpected.abspath(), actual.abspath()) + self.assertIsInstance(actual, Path) def test_hash(self): expected = hash(Path("/A") / "B") diff --git a/tests/test_mutapath.py b/tests/test_mutapath.py index 480983e..fa55593 100644 --- a/tests/test_mutapath.py +++ b/tests/test_mutapath.py @@ -6,6 +6,7 @@ class TestMutaPath(unittest.TestCase): def _gen_start_path(self): self.test_base = Path.getcwd() / "mutapath_test" + self.test_base.rmtree_p() self.test_base.mkdir() new_file = self.test_base / "test.file" new_file.touch() @@ -39,6 +40,7 @@ def test_name(self): expected = "test.file" actual = test_file.name self.assertEqual(expected, actual) + self.assertIsInstance(actual, Path) finally: self._clean() @@ -49,6 +51,7 @@ def test_set_name(self): test_file.name = expected actual = test_file.name self.assertEqual(expected, actual) + self.assertIsInstance(test_file, MutaPath) finally: self._clean() @@ -58,6 +61,7 @@ def test_base(self): expected = self.test_base actual = test_file.base self.assertEqual(expected, actual) + self.assertIsInstance(actual, Path) finally: self._clean() @@ -66,26 +70,29 @@ def test_set_base(self): actual = MutaPath("/A/B/other.txt") actual.base = "/A/D" self.assertEqual(expected, actual) + self.assertIsInstance(actual, MutaPath) def test_set_stem(self): expected = Path("/A/B/other2.txt") actual = MutaPath("/A/B/other.txt") actual.stem += "2" self.assertEqual(expected, actual) + self.assertIsInstance(actual, MutaPath) def test_set_parent(self): expected = Path("/A/B/C/other.txt") actual = MutaPath("/A/other.txt") actual.parent /= "B/C" self.assertEqual(expected, actual) + self.assertIsInstance(actual, MutaPath) def test_rename(self): try: test_file = self._gen_start_path() expected = test_file.with_name("new.txt") test_file.rename(expected) - actual = test_file - self.assertEqual(expected, actual) + self.assertEqual(expected, test_file) + self.assertIsInstance(test_file, MutaPath) finally: self._clean() @@ -94,8 +101,8 @@ def test_renames(self): test_file = self._gen_start_path() expected = test_file.parent / "other/new" test_file.renames(expected) - actual = test_file - self.assertEqual(expected, actual) + self.assertEqual(expected, test_file) + self.assertIsInstance(test_file, MutaPath) finally: self._clean() @@ -104,8 +111,8 @@ def test_copy(self): test_file = self._gen_start_path() expected = test_file.with_name("new.txt") test_file.copy(expected) - actual = test_file - self.assertEqual(expected, actual) + self.assertEqual(expected, test_file) + self.assertIsInstance(test_file, MutaPath) finally: self._clean() @@ -115,8 +122,8 @@ def test_copy2(self): expected = test_file.parent / "other/new" expected.parent.mkdir() test_file.copy2(expected) - actual = test_file - self.assertEqual(expected, actual) + self.assertEqual(expected, test_file) + self.assertIsInstance(test_file, MutaPath) finally: self._clean() @@ -127,6 +134,7 @@ def test_copyfile(self): test_file.copyfile(expected) actual = test_file self.assertEqual(expected, actual) + self.assertIsInstance(actual, MutaPath) finally: self._clean() @@ -139,6 +147,7 @@ def test_copytree(self): expected = self.test_base / "to" from_here.copytree(expected) self.assertEqual(expected, from_here) + self.assertIsInstance(from_here, MutaPath) finally: self._clean() @@ -151,6 +160,7 @@ def test_move(self): expected = self.test_base / "to" from_here.move(expected) self.assertEqual(expected, from_here) + self.assertIsInstance(from_here, MutaPath) finally: self._clean() @@ -163,6 +173,7 @@ def test_merge_tree(self): expected = self.test_base / "to" from_here.merge_tree(expected) self.assertEqual(expected, from_here) + self.assertIsInstance(from_here, MutaPath) finally: self._clean() @@ -170,11 +181,13 @@ def test_static_joinpath(self): expected = MutaPath("/A/B/C/D/other.txt") actual = MutaPath.joinpath("/A/B", "C/", MutaPath("D"), Path("other.txt")) self.assertEqual(expected.normpath(), actual.normpath()) + self.assertIsInstance(actual, MutaPath) def test_joinpath(self): expected = MutaPath("/A/B/C/D/other.txt") actual = MutaPath("/A/B").joinpath("C", MutaPath("D"), Path("other.txt")) self.assertEqual(expected.normpath(), actual.normpath()) + self.assertIsInstance(actual, MutaPath) def test_capsulation(self): expected = MutaPath("/A/B") diff --git a/tests/test_with_path.py b/tests/test_with_path.py index ed12781..4be13e0 100644 --- a/tests/test_with_path.py +++ b/tests/test_with_path.py @@ -16,6 +16,26 @@ def _gen_start_path(self): def _clean(self): self.test_base.rmtree_p() + def test_wrapped_iterable(self): + try: + test_file = self._gen_start_path() + expected = [test_file] + actual = self.test_base.listdir() + self.assertEqual(expected, actual) + self.assertIsInstance(actual[0], Path) + finally: + self._clean() + + def test_wrapped_generator(self): + try: + test_file = self._gen_start_path() + expected = [test_file] + actual = list(self.test_base.walk()) + self.assertEqual(expected, actual) + self.assertIsInstance(actual[0], Path) + finally: + self._clean() + def test_mutate(self): try: test_file = self._gen_start_path() @@ -25,6 +45,7 @@ def test_mutate(self): mut.suffix = ".txt" actual = test_file self.assertEqual(expected, actual) + self.assertIsInstance(actual, Path) finally: self._clean() @@ -37,6 +58,7 @@ def test_renaming(self): mut.suffix = ".txt" actual = test_file self.assertEqual(expected, actual) + self.assertIsInstance(actual, Path) self.assertTrue(actual.exists(), "File has to exist") finally: self._clean() @@ -49,6 +71,7 @@ def test_rename_in_mutate(self): mut.rename(expected) actual = test_file self.assertEqual(expected, actual) + self.assertIsInstance(actual, Path) self.assertTrue(actual.exists(), "File has to exist") finally: self._clean() @@ -64,6 +87,7 @@ def test_rename_fail(self): mut.name = wrong.name actual = test_file self.assertEqual(expected, actual) + self.assertIsInstance(actual, Path) self.assertTrue(actual.exists(), "File has to exist") finally: self._clean() @@ -78,6 +102,7 @@ def test_copying(self): actual = test_file self.assertEqual(expected, actual) self.assertEqual(test_file.text(), actual.text()) + self.assertIsInstance(actual, Path) self.assertTrue(actual.exists(), "File has to exist") finally: self._clean() @@ -92,6 +117,7 @@ def test_move(self): with from_here.moving() as mut: mut.joinpath(self.test_base, "to") self.assertEqual(expected, from_here) + self.assertIsInstance(from_here, Path) self.assertTrue(from_here.exists(), "File has to exist") finally: self._clean()