diff --git a/typesafety/test_upath_signatures.yml b/typesafety/test_upath_signatures.yml index 781a801a..94470af7 100644 --- a/typesafety/test_upath_signatures.yml +++ b/typesafety/test_upath_signatures.yml @@ -454,6 +454,24 @@ reveal_type(p.copy_into("target_dir")) # N: Revealed type is "upath.core.UPath" reveal_type(p.copy_into(UPath("target_dir"))) # N: Revealed type is "upath.core.UPath" +- case: upath_method_move + disable_cache: false + main: | + from upath import UPath + + p = UPath("") + reveal_type(p.move("target")) # N: Revealed type is "upath.core.UPath" + reveal_type(p.move(UPath("target"))) # N: Revealed type is "upath.core.UPath" + +- case: upath_method_move_into + disable_cache: false + main: | + from upath import UPath + + p = UPath("") + reveal_type(p.move_into("target_dir")) # N: Revealed type is "upath.core.UPath" + reveal_type(p.move_into(UPath("target_dir"))) # N: Revealed type is "upath.core.UPath" + - case: upath_method_symlink_to disable_cache: false main: | @@ -1017,6 +1035,10 @@ reveal_type(p.copy({{ cls }}("target"))) # N: Revealed type is "{{ module }}.{{ cls }}" reveal_type(p.copy_into("target_dir")) # N: Revealed type is "{{ module }}.{{ cls }}" reveal_type(p.copy_into({{ cls }}("target_dir"))) # N: Revealed type is "{{ module }}.{{ cls }}" + reveal_type(p.move("target")) # N: Revealed type is "{{ module }}.{{ cls }}" + reveal_type(p.move({{ cls }}("target"))) # N: Revealed type is "{{ module }}.{{ cls }}" + reveal_type(p.move_into("target_dir")) # N: Revealed type is "{{ module }}.{{ cls }}" + reveal_type(p.move_into({{ cls }}("target_dir"))) # N: Revealed type is "{{ module }}.{{ cls }}" reveal_type(p.rename("new_name")) # N: Revealed type is "{{ module }}.{{ cls }}" reveal_type(p.rename({{ cls }}("new_name"))) # N: Revealed type is "{{ module }}.{{ cls }}" reveal_type(p.replace("target")) # N: Revealed type is "{{ module }}.{{ cls }}" diff --git a/upath/core.py b/upath/core.py index a4e1127c..36af6dd9 100644 --- a/upath/core.py +++ b/upath/core.py @@ -648,6 +648,35 @@ def copy_into( else: return super().copy_into(target_dir, **kwargs) + @overload + def move(self, target: _WT, **kwargs: Any) -> _WT: ... + + @overload + def move(self, target: SupportsPathLike | str, **kwargs: Any) -> Self: ... + + def move(self, target: _WT | SupportsPathLike | str, **kwargs: Any) -> _WT | UPath: + target = self.copy(target, **kwargs) + self.fs.rm(self.path, recursive=self.is_dir()) + return target + + @overload + def move_into(self, target_dir: _WT, **kwargs: Any) -> _WT: ... + + @overload + def move_into(self, target_dir: SupportsPathLike | str, **kwargs: Any) -> Self: ... + + def move_into( + self, target_dir: _WT | SupportsPathLike | str, **kwargs: Any + ) -> _WT | UPath: + name = self.name + if not name: + raise ValueError(f"{self!r} has an empty name") + elif hasattr(target_dir, "with_segments"): + target = target_dir.with_segments(target_dir, name) # type: ignore + else: + target = self.with_segments(target_dir, name) + return self.move(target) + # --- WritablePath attributes ------------------------------------- def symlink_to( diff --git a/upath/extensions.py b/upath/extensions.py index 715ba610..8c83873c 100644 --- a/upath/extensions.py +++ b/upath/extensions.py @@ -434,6 +434,12 @@ def copy(self, target: WritablePathLike, **kwargs: Any) -> Self: # type: ignore def copy_into(self, target_dir: WritablePathLike, **kwargs: Any) -> Self: # type: ignore[override] # noqa: E501 return self._from_upath(self.__wrapped__.copy_into(target_dir, **kwargs)) + def move(self, target: WritablePathLike, **kwargs: Any) -> Self: # type: ignore[override] # noqa: E501 + return self._from_upath(self.__wrapped__.move(target, **kwargs)) + + def move_into(self, target_dir: WritablePathLike, **kwargs: Any) -> Self: # type: ignore[override] # noqa: E501 + return self._from_upath(self.__wrapped__.move_into(target_dir, **kwargs)) + def write_bytes(self, data: bytes) -> int: return self.__wrapped__.write_bytes(data) diff --git a/upath/implementations/cached.py b/upath/implementations/cached.py index 2f4071f3..dc8800e9 100644 --- a/upath/implementations/cached.py +++ b/upath/implementations/cached.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from upath.core import UPath diff --git a/upath/implementations/data.py b/upath/implementations/data.py index 4197f2de..6ad50667 100644 --- a/upath/implementations/data.py +++ b/upath/implementations/data.py @@ -2,14 +2,16 @@ import sys from collections.abc import Sequence +from typing import TYPE_CHECKING from upath.core import UPath from upath.types import JoinablePathLike -if sys.version_info > (3, 11): - from typing import Self -else: - from typing_extensions import Self +if TYPE_CHECKING: + if sys.version_info > (3, 11): + from typing import Self + else: + from typing_extensions import Self class DataPath(UPath): diff --git a/upath/implementations/github.py b/upath/implementations/github.py index 5fd400d8..692ed5fe 100644 --- a/upath/implementations/github.py +++ b/upath/implementations/github.py @@ -2,16 +2,20 @@ GitHub file system implementation """ +from __future__ import annotations + import sys from collections.abc import Iterator from collections.abc import Sequence +from typing import TYPE_CHECKING import upath.core -if sys.version_info > (3, 11): - from typing import Self -else: - from typing_extensions import Self +if TYPE_CHECKING: + if sys.version_info > (3, 11): + from typing import Self + else: + from typing_extensions import Self class GitHubPath(upath.core.UPath): diff --git a/upath/implementations/hdfs.py b/upath/implementations/hdfs.py index 0cabb42c..dab26167 100644 --- a/upath/implementations/hdfs.py +++ b/upath/implementations/hdfs.py @@ -2,13 +2,15 @@ import sys from collections.abc import Iterator +from typing import TYPE_CHECKING from upath.core import UPath -if sys.version_info > (3, 11): - from typing import Self -else: - from typing_extensions import Self +if TYPE_CHECKING: + if sys.version_info > (3, 11): + from typing import Self + else: + from typing_extensions import Self __all__ = ["HDFSPath"] diff --git a/upath/implementations/local.py b/upath/implementations/local.py index a609202f..e6d4a27d 100644 --- a/upath/implementations/local.py +++ b/upath/implementations/local.py @@ -310,7 +310,7 @@ def open( **fsspec_kwargs, ) - if sys.version_info < (3, 14): + if sys.version_info < (3, 14): # noqa: C901 @overload def copy(self, target: _WT, **kwargs: Any) -> _WT: ... @@ -350,6 +350,39 @@ def copy_into( else: return _copy_into(target_dir, **kwargs) + @overload + def move(self, target: _WT, **kwargs: Any) -> _WT: ... + + @overload + def move(self, target: SupportsPathLike | str, **kwargs: Any) -> Self: ... + + def move( + self, target: _WT | SupportsPathLike | str, **kwargs: Any + ) -> _WT | Self: + target = self.copy(target, **kwargs) + self.fs.rm(self.path, recursive=self.is_dir()) + return target + + @overload + def move_into(self, target_dir: _WT, **kwargs: Any) -> _WT: ... + + @overload + def move_into( + self, target_dir: SupportsPathLike | str, **kwargs: Any + ) -> Self: ... + + def move_into( + self, target_dir: _WT | SupportsPathLike | str, **kwargs: Any + ) -> _WT | Self: + name = self.name + if not name: + raise ValueError(f"{self!r} has an empty name") + elif hasattr(target_dir, "with_segments"): + target = target_dir.with_segments(str(target_dir), name) # type: ignore + else: + target = self.with_segments(str(target_dir), name) + return self.move(target) + @property def info(self) -> PathInfo: return _LocalPathInfo(self) diff --git a/upath/implementations/memory.py b/upath/implementations/memory.py index 350e11c8..33aaa8c9 100644 --- a/upath/implementations/memory.py +++ b/upath/implementations/memory.py @@ -2,13 +2,15 @@ import sys from collections.abc import Iterator +from typing import TYPE_CHECKING from upath.core import UPath -if sys.version_info > (3, 11): - from typing import Self -else: - from typing_extensions import Self +if TYPE_CHECKING: + if sys.version_info > (3, 11): + from typing import Self + else: + from typing_extensions import Self __all__ = ["MemoryPath"] diff --git a/upath/tests/cases.py b/upath/tests/cases.py index fb0e90b2..0d397df3 100644 --- a/upath/tests/cases.py +++ b/upath/tests/cases.py @@ -606,3 +606,46 @@ def test_copy_into_memory(self, clear_fsspec_memory_cache): target = target_dir / "file1.txt" assert target.exists() assert target.read_text() == content + + def test_move_local(self, tmp_path: Path): + target = UPath(tmp_path) / "target-file1.txt" + + source = self.path / "file1.txt" + content = source.read_text() + source.move(target) + assert target.exists() + assert target.read_text() == content + assert not source.exists() + + def test_move_into_local(self, tmp_path: Path): + target_dir = UPath(tmp_path) / "target-dir" + target_dir.mkdir() + + source = self.path / "file1.txt" + content = source.read_text() + source.move_into(target_dir) + target = target_dir / "file1.txt" + assert target.exists() + assert target.read_text() == content + assert not source.exists() + + def test_move_memory(self, clear_fsspec_memory_cache): + target = UPath("memory:///target-file1.txt") + source = self.path / "file1.txt" + content = source.read_text() + source.move(target) + assert target.exists() + assert target.read_text() == content + assert not source.exists() + + def test_move_into_memory(self, clear_fsspec_memory_cache): + target_dir = UPath("memory:///target-dir") + target_dir.mkdir() + + source = self.path / "file1.txt" + content = source.read_text() + source.move_into(target_dir) + target = target_dir / "file1.txt" + assert target.exists() + assert target.read_text() == content + assert not source.exists() diff --git a/upath/tests/implementations/test_data.py b/upath/tests/implementations/test_data.py index 840b93d1..9f2f0077 100644 --- a/upath/tests/implementations/test_data.py +++ b/upath/tests/implementations/test_data.py @@ -264,3 +264,19 @@ def test_copy_into_memory(self, clear_fsspec_memory_cache): target = target_dir / source.name assert target.exists() assert target.read_text() == content + + @pytest.mark.skip(reason="DataPath does not support unlink") + def test_move_local(self, tmp_path): + pass + + @pytest.mark.skip(reason="DataPath does not support unlink") + def test_move_into_local(self, tmp_path): + pass + + @pytest.mark.skip(reason="DataPath does not support unlink") + def test_move_memory(self, clear_fsspec_memory_cache): + pass + + @pytest.mark.skip(reason="DataPath does not support unlink") + def test_move_into_memory(self, clear_fsspec_memory_cache): + pass diff --git a/upath/tests/implementations/test_github.py b/upath/tests/implementations/test_github.py index d6330f2a..92d77581 100644 --- a/upath/tests/implementations/test_github.py +++ b/upath/tests/implementations/test_github.py @@ -71,3 +71,19 @@ def test_write_text(self): @pytest.mark.skip(reason="GitHub filesystem is read-only") def test_fsspec_compat(self): pass + + @pytest.mark.skip(reason="Only testing read on GithubPath") + def test_move_local(self, tmp_path): + pass + + @pytest.mark.skip(reason="Only testing read on GithubPath") + def test_move_into_local(self, tmp_path): + pass + + @pytest.mark.skip(reason="Only testing read on GithubPath") + def test_move_memory(self, clear_fsspec_memory_cache): + pass + + @pytest.mark.skip(reason="Only testing read on GithubPath") + def test_move_into_memory(self, clear_fsspec_memory_cache): + pass diff --git a/upath/tests/implementations/test_http.py b/upath/tests/implementations/test_http.py index 170ad197..473bb95d 100644 --- a/upath/tests/implementations/test_http.py +++ b/upath/tests/implementations/test_http.py @@ -141,6 +141,22 @@ def test_info(self): assert p1.info.is_dir() is True assert p1.info.is_symlink() is False + @pytest.mark.skip(reason="HttpPath does not support unlink") + def test_move_local(self, tmp_path): + pass + + @pytest.mark.skip(reason="HttpPath does not support unlink") + def test_move_into_local(self, tmp_path): + pass + + @pytest.mark.skip(reason="HttpPath does not support unlink") + def test_move_memory(self, clear_fsspec_memory_cache): + pass + + @pytest.mark.skip(reason="HttpPath does not support unlink") + def test_move_into_memory(self, clear_fsspec_memory_cache): + pass + @pytest.mark.parametrize( "args,parts",