Skip to content

Commit

Permalink
fea: all relative_to paths as PosixUPath?
Browse files Browse the repository at this point in the history
  • Loading branch information
CompRhys committed Apr 17, 2024
1 parent 4930811 commit 782d4a2
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 65 deletions.
31 changes: 3 additions & 28 deletions upath/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,34 +253,9 @@ def with_suffix(self, suffix):
self.drive, self.root, self._tail[:-1] + [name]
)

def relative_to(self, other, /, *_deprecated, walk_up=False):
if _deprecated:
msg = (
"support for supplying more than one positional argument "
"to pathlib.PurePath.relative_to() is deprecated and "
"scheduled for removal in Python 3.14"
)
warnings.warn(
f"pathlib.PurePath.relative_to(*args) {msg}",
DeprecationWarning,
stacklevel=2,
)
other = self.with_segments(other, *_deprecated)
for step, path in enumerate([other] + list(other.parents)): # noqa: B007
if self.is_relative_to(path):
break
elif not walk_up:
raise ValueError(
f"{str(self)!r} is not in the subpath of {str(other)!r}"
)
elif path.name == "..":
raise ValueError(f"'..' segment in {str(other)!r} cannot be walked")
else:
raise ValueError(
f"{str(self)!r} and {str(other)!r} have different anchors"
)
parts = [".."] * step + self._tail[len(path._tail) :]
return self.with_segments(*parts)
# NOTE relative_to was elevated to UPath as otherwise this would
# cause a circular dependency
# def relative_to(self, other, /, *_deprecated, walk_up=False):

def is_relative_to(self, other, /, *_deprecated):
if _deprecated:
Expand Down
31 changes: 29 additions & 2 deletions upath/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -666,14 +666,41 @@ def __hash__(self):
can add `fsspec.utils.tokenize(storage_options)`
"""
return hash((self.protocol, self.path))

def relative_to(self, other, /, *_deprecated, walk_up=False):
if isinstance(other, UPath) and self.storage_options != other.storage_options:
raise ValueError(
"paths have different storage_options:"
f" {self.storage_options!r} != {other.storage_options!r}"
)
return super().relative_to(other, *_deprecated, walk_up=walk_up)

if _deprecated:
msg = (
"support for supplying more than one positional argument "
"to pathlib.PurePath.relative_to() is deprecated and "
"scheduled for removal in Python 3.14"
)
warnings.warn(
f"pathlib.PurePath.relative_to(*args) {msg}",
DeprecationWarning,
stacklevel=2,
)
other = self.with_segments(other, *_deprecated)
for step, path in enumerate([other] + list(other.parents)): # noqa: B007
if self.is_relative_to(path):
break
elif not walk_up:
raise ValueError(
f"{str(self)!r} is not in the subpath of {str(other)!r}"
)
elif path.name == "..":
raise ValueError(f"'..' segment in {str(other)!r} cannot be walked")
else:
raise ValueError(
f"{str(self)!r} and {str(other)!r} have different anchors"
)
parts = [".."] * step + self._tail[len(path._tail) :]
return UPath(*parts, **self._storage_options)

def is_relative_to(self, other, /, *_deprecated):
if isinstance(other, UPath) and self.storage_options != other.storage_options:
Expand Down
3 changes: 1 addition & 2 deletions upath/implementations/cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,7 @@ def iterdir(self):

def relative_to(self, other, /, *_deprecated, walk_up=False):
# use the parent implementation for the ValueError logic
super().relative_to(other, *_deprecated, walk_up=False)
return self
return super().relative_to(other, *_deprecated, walk_up=False)


class GCSPath(CloudPath):
Expand Down
29 changes: 0 additions & 29 deletions upath/implementations/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,35 +63,6 @@ def iterdir(self):
if _LISTDIR_WORKS_ON_FILES and self.is_file():
raise NotADirectoryError(f"{self}")
return super().iterdir()

def relative_to(self, other, /, *_deprecated, walk_up=False):
if _deprecated:
msg = (
"support for supplying more than one positional argument "
"to pathlib.PurePath.relative_to() is deprecated and "
"scheduled for removal in Python 3.14"
)
warnings.warn(
f"pathlib.PurePath.relative_to(*args) {msg}",
DeprecationWarning,
stacklevel=2,
)
other = self.with_segments(other, *_deprecated)
for step, path in enumerate([other] + list(other.parents)): # noqa: B007
if self.is_relative_to(path):
break
elif not walk_up:
raise ValueError(
f"{str(self)!r} is not in the subpath of {str(other)!r}"
)
elif path.name == "..":
raise ValueError(f"'..' segment in {str(other)!r} cannot be walked")
else:
raise ValueError(
f"{str(self)!r} and {str(other)!r} have different anchors"
)
parts = [".."] * step + self._tail[len(path._tail) :]
return UPath(*parts, **self._storage_options)


_pathlib_py312_ignore = {
Expand Down
15 changes: 15 additions & 0 deletions upath/tests/implementations/test_azure.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from upath import UPath
from upath.implementations.cloud import AzurePath
from upath.implementations.local import PosixUPath

from ..cases import BaseTests
from ..utils import skip_on_windows
Expand Down Expand Up @@ -61,3 +62,17 @@ def test_broken_mkdir(self):

(path / "file").write_text("foo")
assert path.exists()

def test_relative_to(self):
rel_path = UPath("az:///test_bucket/file.txt").relative_to(UPath("az:///test_bucket"))

assert isinstance(rel_path, PosixUPath)
assert not rel_path.is_absolute()
assert 'file.txt' == rel_path.path

with pytest.raises(ValueError):
UPath("az:///test_bucket/file.txt").relative_to(UPath("az:///prod_bucket"))

with pytest.raises(ValueError):
UPath("az:///test_bucket/file.txt").relative_to(UPath("file:///test_bucket"))

17 changes: 13 additions & 4 deletions upath/tests/implementations/test_s3.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
import fsspec
import pytest # noqa: F401

from upath import UPath
from upath.core import UPath
from upath.implementations.local import PosixUPath
from upath.implementations.cloud import S3Path

from ..cases import BaseTests
Expand Down Expand Up @@ -37,9 +38,17 @@ def test_rmdir(self):
self.path.joinpath("file1.txt").rmdir()

def test_relative_to(self):
assert "s3://test_bucket/file.txt" == str(
UPath("s3://test_bucket/file.txt").relative_to(UPath("s3://test_bucket"))
)
rel_path = UPath("s3:///test_bucket/file.txt").relative_to(UPath("s3:///test_bucket"))

assert isinstance(rel_path, PosixUPath)
assert not rel_path.is_absolute()
assert 'file.txt' == rel_path.path

with pytest.raises(ValueError):
UPath("s3:///test_bucket/file.txt").relative_to(UPath("s3:///prod_bucket"))

with pytest.raises(ValueError):
UPath("s3:///test_bucket/file.txt").relative_to(UPath("file:///test_bucket"))

def test_iterdir_root(self):
client_kwargs = self.path._kwargs["client_kwargs"]
Expand Down

0 comments on commit 782d4a2

Please sign in to comment.