Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 31 additions & 8 deletions upath/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1221,10 +1221,17 @@ def copy(self, target: _WT | SupportsPathLike | str, **kwargs: Any) -> _WT | UPa
"""
Recursively copy this file or directory tree to the given destination.
"""
if not isinstance(target, UPath):
return super().copy(self.with_segments(target), **kwargs)
else:
return super().copy(target, **kwargs)
if isinstance(target, str):
proto = get_upath_protocol(target)
if proto != self.protocol:
target = UPath(target)
else:
target = self.with_segments(target)
elif not isinstance(target, UPath):
target = UPath(target)
if target.is_dir():
raise IsADirectoryError(str(target))
return super().copy(target, **kwargs)

@overload
def copy_into(self, target_dir: _WT, **kwargs: Any) -> _WT: ...
Expand All @@ -1238,10 +1245,19 @@ def copy_into(
"""
Copy this file or directory tree into the given existing directory.
"""
if not isinstance(target_dir, UPath):
return super().copy_into(self.with_segments(target_dir), **kwargs)
else:
return super().copy_into(target_dir, **kwargs)
if isinstance(target_dir, str):
proto = get_upath_protocol(target_dir)
if proto != self.protocol:
target_dir = UPath(target_dir)
else:
target_dir = self.with_segments(target_dir)
elif not isinstance(target_dir, UPath):
target_dir = UPath(target_dir)
if not target_dir.exists():
raise FileNotFoundError(str(target_dir))
if not target_dir.is_dir():
raise NotADirectoryError(str(target_dir))
return super().copy_into(target_dir, **kwargs)

@overload
def move(self, target: _WT, **kwargs: Any) -> _WT: ...
Expand Down Expand Up @@ -1274,8 +1290,15 @@ def move_into(
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
elif isinstance(target_dir, PurePath):
target = UPath(target_dir, name)
else:
target = self.with_segments(target_dir, name)
td = target.parent
if not td.exists():
raise FileNotFoundError(str(td))
elif not td.is_dir():
raise NotADirectoryError(str(td))
return self.move(target)

def _copy_from(
Expand Down
36 changes: 28 additions & 8 deletions upath/implementations/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from upath._chain import ChainSegment
from upath._chain import FSSpecChainParser
from upath._protocol import compatible_protocol
from upath._protocol import get_upath_protocol
from upath.core import UnsupportedOperation
from upath.core import UPath
from upath.core import _UPathMixin
Expand Down Expand Up @@ -377,10 +378,17 @@ def copy(
# hacky workaround for missing pathlib.Path.copy in python < 3.14
# todo: revisit
_copy: Any = ReadablePath.copy.__get__(self)
if not isinstance(target, UPath):
return _copy(self.with_segments(str(target)), **kwargs)
else:
return _copy(target, **kwargs)
if isinstance(target, str):
proto = get_upath_protocol(target)
if proto != self.protocol:
target = UPath(target)
else:
target = self.with_segments(target)
elif not isinstance(target, UPath):
target = UPath(target)
if target.is_dir():
raise IsADirectoryError(str(target))
return _copy(target, **kwargs)

@overload
def copy_into(self, target_dir: _WT, **kwargs: Any) -> _WT: ...
Expand All @@ -398,10 +406,15 @@ def copy_into(
# hacky workaround for missing pathlib.Path.copy_into in python < 3.14
# todo: revisit
_copy_into: Any = ReadablePath.copy_into.__get__(self)
if not isinstance(target_dir, UPath):
return _copy_into(self.with_segments(str(target_dir)), **kwargs)
else:
return _copy_into(target_dir, **kwargs)
if isinstance(target_dir, str):
proto = get_upath_protocol(target_dir)
if proto != self.protocol:
target_dir = UPath(target_dir)
else:
target_dir = self.with_segments(target_dir)
elif not isinstance(target_dir, UPath):
target_dir = UPath(target_dir)
return _copy_into(target_dir, **kwargs)

@overload
def move(self, target: _WT, **kwargs: Any) -> _WT: ...
Expand Down Expand Up @@ -432,8 +445,15 @@ def move_into(
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
elif isinstance(target_dir, pathlib.PurePath):
target = UPath(target_dir, name)
else:
target = self.with_segments(str(target_dir), name)
td = target.parent
if not td.exists():
raise FileNotFoundError(str(td))
elif not td.is_dir():
raise NotADirectoryError(str(td))
return self.move(target)

@property
Expand Down
60 changes: 60 additions & 0 deletions upath/tests/cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,40 @@ def test_copy_local(self, tmp_path: Path):
assert target.exists()
assert target.read_text() == content

@pytest.mark.parametrize("target_type", [str, Path, UPath])
def test_copy_into__file_to_str_tempdir(self, tmp_path: Path, target_type):
tmp_path = tmp_path.joinpath("somewhere")
tmp_path.mkdir()
target_dir = target_type(tmp_path)
assert isinstance(target_dir, target_type)

source = self.path_file
source.copy_into(target_dir)
target = tmp_path.joinpath(source.name)

assert target.exists()
assert target.read_text() == source.read_text()

@pytest.mark.parametrize("target_type", [str, Path, UPath])
def test_copy_into__dir_to_str_tempdir(self, tmp_path: Path, target_type):
tmp_path = tmp_path.joinpath("somewhere")
tmp_path.mkdir()
target_dir = target_type(tmp_path)
assert isinstance(target_dir, target_type)

source_dir = self.path.joinpath("folder1")
assert source_dir.is_dir()
source_dir.copy_into(target_dir)
target = tmp_path.joinpath(source_dir.name)

assert target.exists()
assert target.is_dir()
for item in source_dir.iterdir():
target_item = target.joinpath(item.name)
assert target_item.exists()
if item.is_file():
assert target_item.read_text() == item.read_text()

def test_copy_into_local(self, tmp_path: Path):
target_dir = UPath(tmp_path) / "target-dir"
target_dir.mkdir()
Expand Down Expand Up @@ -581,6 +615,32 @@ def test_copy_into_memory(self, clear_fsspec_memory_cache):
assert target.exists()
assert target.read_text() == content

def test_copy_exceptions(self, tmp_path: Path):
source = self.path_file
# target is a directory
target = UPath(tmp_path) / "target-folder"
target.mkdir()
# FIXME: pytest.raises(IsADirectoryError) not working on Windows
with pytest.raises(OSError):
source.copy(target)
# target parent does not exist
target = UPath(tmp_path) / "nonexistent-dir" / "target-file1.txt"
with pytest.raises(FileNotFoundError):
source.copy(target)

def test_copy_into_exceptions(self, tmp_path: Path):
source = self.path_file
# target is not a directory
target_file = UPath(tmp_path) / "target-file.txt"
target_file.write_text("content")
# FIXME: pytest.raises(NotADirectoryError) not working on Windows
with pytest.raises(OSError):
source.copy_into(target_file)
# target dir does not exist
target_dir = UPath(tmp_path) / "nonexistent-dir"
with pytest.raises(FileNotFoundError):
source.copy_into(target_dir)

def test_read_with_fsspec(self):
p = self.path_file

Expand Down
5 changes: 5 additions & 0 deletions upath/tests/implementations/test_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,3 +253,8 @@ def test_unlink(self):
# DataPaths can't be deleted
with pytest.raises(UnsupportedOperation):
self.path_file.unlink()

@overrides_base
def test_copy_into__dir_to_str_tempdir(self):
# There are no directories in DataPath
assert not self.path.is_dir()