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
2 changes: 2 additions & 0 deletions upath/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,8 @@ def relative_to( # type: ignore[override]
)

def is_relative_to(self, other, /, *_deprecated) -> bool: # type: ignore[override]
if not isinstance(other, str):
other = vfspath(other)
return self.__wrapped__.is_relative_to(other, *_deprecated)

def hardlink_to(self, target: ReadablePathLike) -> None:
Expand Down
96 changes: 96 additions & 0 deletions upath/tests/cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from fsspec import __version__ as fsspec_version
from fsspec import filesystem
from packaging.version import Version
from pathlib_abc import PathParser
from pathlib_abc import vfspath

from upath import UnsupportedOperation
from upath import UPath
Expand All @@ -30,6 +32,31 @@ class JoinablePathTests:

path: UPath

def test_parser(self):
parser = self.path.parser
assert isinstance(parser, PathParser)
assert isinstance(parser.sep, str)
assert parser.altsep is None or isinstance(parser.altsep, str)
assert callable(parser.split)
assert callable(parser.splitext)
assert callable(parser.normcase)

def test_with_segments(self):
p = self.path.with_segments(self.path.__vfspath__(), "folder", "file.txt")
assert p.parts[-2:] == ("folder", "file.txt")
assert type(p) is type(self.path)

def test___vfspath__(self):
assert hasattr(self.path, "__vfspath__")
assert callable(self.path.__vfspath__)
str_path = vfspath(self.path)
assert isinstance(str_path, str)

def test_anchor(self):
anchor = self.path.anchor
assert isinstance(anchor, str)
assert anchor == self.path.drive + self.path.root

def test_is_absolute(self):
assert self.path.is_absolute() is True

Expand Down Expand Up @@ -166,6 +193,21 @@ def test_relative_to(self):
relative = child.relative_to(base)
assert str(relative) == "folder1/file1.txt"

def test_is_relative_to(self):
base = self.path
child = self.path / "folder1" / "file1.txt"
other = UPath("/some/other/path")

assert child.is_relative_to(base) is True
assert base.is_relative_to(child) is False
assert child.is_relative_to(other) is False

def test_full_match(self):
p = self.path / "folder" / "file.txt"
assert p.full_match("**/*") is True
assert p.full_match("**/*.txt") is True
assert p.full_match("*.doesnotexist") is False

def test_trailing_slash_joinpath_is_identical(self):
# setup
cls = type(self.path)
Expand Down Expand Up @@ -409,12 +451,52 @@ def test_read_text(self, local_testdir):
upath.read_text() == Path(local_testdir).joinpath("file1.txt").read_text()
)

def test_read_text_encoding(self):
upath = self.path.joinpath("file1.txt")
content = upath.read_text(encoding="utf-8")
assert content == "hello world"

def test_read_text_errors(self):
upath = self.path.joinpath("file1.txt")
content = upath.read_text(encoding="ascii", errors="strict")
assert content == "hello world"

def test_rglob(self, pathlib_base):
pattern = "*.txt"
result = [*self.path.rglob(pattern)]
expected = [*pathlib_base.rglob(pattern)]
assert len(result) == len(expected)

def test_walk(self, local_testdir):
# collect walk results from UPath
upath_walk = []
for dirpath, dirnames, filenames in self.path.walk():
rel_dirpath = dirpath.relative_to(self.path)
upath_walk.append((str(rel_dirpath), sorted(dirnames), sorted(filenames)))
upath_walk.sort()

# collect walk results using os.walk (compatible with Python 3.9+)
os_walk = []
for dirpath, dirnames, filenames in os.walk(local_testdir):
rel_dirpath = os.path.relpath(dirpath, local_testdir)
os_walk.append((rel_dirpath, sorted(dirnames), sorted(filenames)))
os_walk.sort()

assert upath_walk == os_walk

def test_walk_top_down_false(self):
# test walk with top_down=False returns directories after their contents
paths_seen = []
for dirpath, _, _ in self.path.walk(top_down=False):
paths_seen.append(dirpath)

# in bottom-up walk, parent directories should come after children
for i, path in enumerate(paths_seen):
for _, other in enumerate(paths_seen[i + 1 :], start=i + 1):
# if path is a parent of other, path should come after other
if other.is_relative_to(path) and other != path:
pytest.fail(f"In bottom-up walk, {path} should come after {other}")

def test_samefile(self):
f1 = self.path.joinpath("file1.txt")
f2 = self.path.joinpath("file2.txt")
Expand Down Expand Up @@ -603,6 +685,20 @@ def test_write_text(self, pathlib_base):
path.write_text(s)
assert path.read_text() == s

def test_write_text_encoding(self):
fn = "test_write_text_enc.txt"
s = "hello_world"
path = self.path.joinpath(fn)
path.write_text(s, encoding="utf-8")
assert path.read_text(encoding="utf-8") == s

def test_write_text_errors(self):
fn = "test_write_text_errors.txt"
s = "hello_world"
path = self.path.joinpath(fn)
path.write_text(s, encoding="ascii", errors="strict")
assert path.read_text(encoding="ascii") == s

def test_chmod(self):
with pytest.raises(NotImplementedError):
self.path.joinpath("file1.txt").chmod(777)
Expand Down
37 changes: 37 additions & 0 deletions upath/tests/implementations/test_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import fsspec
import pytest

from upath import UnsupportedOperation
from upath import UPath
from upath.implementations.data import DataPath
from upath.tests.cases import BaseTests
Expand Down Expand Up @@ -33,6 +34,14 @@ def test_is_DataPath(self):
"""
assert isinstance(self.path, DataPath)

def test_with_segments(self):
with pytest.raises(UnsupportedOperation):
super().test_with_segments()

def test_is_relative_to(self):
with pytest.raises(UnsupportedOperation):
super().test_is_relative_to()

@pytest.mark.skip(reason="DataPath does not have directories")
def test_stat_dir_st_mode(self):
super().test_stat_dir_st_mode()
Expand Down Expand Up @@ -304,3 +313,31 @@ def test_trailing_slash_is_stripped(self):
@pytest.mark.skip(reason="DataPath does not support joins")
def test_parents_are_absolute(self):
pass

@pytest.mark.skip(reason="DataPath does not support write_text")
def test_write_text_encoding(self):
pass

@pytest.mark.skip(reason="DataPath does not support write_text")
def test_write_text_errors(self):
pass

@pytest.mark.skip(reason="base test incompatible with DataPath")
def test_read_text_encoding(self):
pass

@pytest.mark.skip(reason="base test incompatible with DataPath")
def test_read_text_errors(self):
pass

@pytest.mark.skip(reason="DataPath does not support walk")
def test_walk(self, local_testdir):
pass

@pytest.mark.skip(reason="DataPath does not support walk")
def test_walk_top_down_false(self):
pass

@pytest.mark.skip(reason="DataPath does not support full_match")
def test_full_match(self):
pass
8 changes: 8 additions & 0 deletions upath/tests/implementations/test_github.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,11 @@ def test_move_into_memory(self, clear_fsspec_memory_cache):
@pytest.mark.skip(reason="Only testing read on GithubPath")
def test_rename_with_target_absolute(self, target_factory):
return super().test_rename_with_target_str_absolute(target_factory)

@pytest.mark.skip(reason="Only testing read on GithubPath")
def test_write_text_encoding(self):
return super().test_write_text_encoding()

@pytest.mark.skip(reason="Only testing read on GithubPath")
def test_write_text_errors(self):
return super().test_write_text_errors()
8 changes: 8 additions & 0 deletions upath/tests/implementations/test_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,14 @@ def test_move_into_memory(self, clear_fsspec_memory_cache):
def test_rename_with_target_absolute(self, target_factory):
return super().test_rename_with_target_absolute(target_factory)

@pytest.mark.skip(reason="Only testing read on HttpPath")
def test_write_text_encoding(self):
return super().test_write_text_encoding()

@pytest.mark.skip(reason="Only testing read on HttpPath")
def test_write_text_errors(self):
return super().test_write_text_errors()


@pytest.mark.parametrize(
"args,parts",
Expand Down
8 changes: 8 additions & 0 deletions upath/tests/implementations/test_tar.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ def test_move_into_memory(self, clear_fsspec_memory_cache):
def test_rename_with_target_absolute(self, target_factory):
return super().test_rename_with_target_str_absolute(target_factory)

@pytest.mark.skip(reason="Only testing read on TarPath")
def test_write_text_encoding(self):
return super().test_write_text_encoding()

@pytest.mark.skip(reason="Only testing read on TarPath")
def test_write_text_errors(self):
return super().test_write_text_errors()


@pytest.fixture(scope="function")
def tarred_testdir_file_in_memory(tarred_testdir_file, clear_fsspec_memory_cache):
Expand Down
8 changes: 8 additions & 0 deletions upath/tests/implementations/test_zip.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,14 @@ def test_fsspec_compat(self):
def test_rename_with_target_absolute(self, target_factory):
return super().test_rename_with_target_absolute(target_factory)

@pytest.mark.skip(reason="fsspec zipfile filesystem is either read xor write mode")
def test_write_text_encoding(self):
return super().test_write_text_encoding()

@pytest.mark.skip(reason="fsspec zipfile filesystem is either read xor write mode")
def test_write_text_errors(self):
return super().test_write_text_errors()


@pytest.fixture(scope="function")
def zipped_testdir_file_in_memory(zipped_testdir_file, clear_fsspec_memory_cache):
Expand Down