diff --git a/upath/core.py b/upath/core.py index 3aaa93af..0e7b48ff 100644 --- a/upath/core.py +++ b/upath/core.py @@ -1355,7 +1355,7 @@ def relative_to( # type: ignore[override] if self.__class__ is not other.__class__: raise ValueError( "incompatible protocols:" - f" {self._protocol!r} != {other._protocol!r}" + f" {self.protocol!r} != {other.protocol!r}" ) if self.storage_options != other.storage_options: raise ValueError( @@ -1371,7 +1371,7 @@ def relative_to( # type: ignore[override] raise ValueError(f"{self!s} is not in the subpath of {other!s}") else: rel = copy(self) - rel._relative_base = str(other) + rel._relative_base = other.path return rel def is_relative_to(self, other, /, *_deprecated) -> bool: # type: ignore[override] diff --git a/upath/extensions.py b/upath/extensions.py index fabc86a6..48966cbd 100644 --- a/upath/extensions.py +++ b/upath/extensions.py @@ -370,6 +370,8 @@ def relative_to( # type: ignore[override] *_deprecated, walk_up=False, ) -> Self: + if isinstance(other, ProxyUPath): + other = other.__wrapped__ return self._from_upath( self.__wrapped__.relative_to(other, *_deprecated, walk_up=walk_up) ) diff --git a/upath/implementations/sftp.py b/upath/implementations/sftp.py index 018b8e8b..ff924a52 100644 --- a/upath/implementations/sftp.py +++ b/upath/implementations/sftp.py @@ -36,6 +36,19 @@ def __init__( **storage_options: Unpack[SFTPStorageOptions], ) -> None: ... + @property + def path(self) -> str: + path = super().path + if len(path) > 1: + return path.removesuffix("/") + return path + + def __str__(self) -> str: + path_str = super().__str__() + if path_str.startswith(("ssh:///", "sftp:///")): + return path_str.removesuffix("/") + return path_str + def iterdir(self) -> Iterator[Self]: if not self.is_dir(): raise NotADirectoryError(str(self)) diff --git a/upath/implementations/smb.py b/upath/implementations/smb.py index 685f4539..7adaade1 100644 --- a/upath/implementations/smb.py +++ b/upath/implementations/smb.py @@ -40,6 +40,19 @@ def __init__( **storage_options: Unpack[SMBStorageOptions], ) -> None: ... + @property + def path(self) -> str: + path = super().path + if len(path) > 1: + return path.removesuffix("/") + return path + + def __str__(self) -> str: + path_str = super().__str__() + if path_str.startswith("smb:///"): + return path_str.removesuffix("/") + return path_str + def mkdir( self, mode: int = 0o777, diff --git a/upath/tests/cases.py b/upath/tests/cases.py index 56bb0141..82bd136d 100644 --- a/upath/tests/cases.py +++ b/upath/tests/cases.py @@ -655,3 +655,9 @@ def test_move_into_memory(self, clear_fsspec_memory_cache): assert target.exists() assert target.read_text() == content assert not source.exists() + + def test_relative_to(self): + base = self.path + child = self.path / "folder1" / "file1.txt" + relative = child.relative_to(base) + assert str(relative) == "folder1/file1.txt" diff --git a/upath/tests/implementations/test_data.py b/upath/tests/implementations/test_data.py index 5b1fe92e..db59cb10 100644 --- a/upath/tests/implementations/test_data.py +++ b/upath/tests/implementations/test_data.py @@ -283,3 +283,7 @@ def test_move_memory(self, clear_fsspec_memory_cache): @pytest.mark.skip(reason="DataPath does not support unlink") def test_move_into_memory(self, clear_fsspec_memory_cache): pass + + @pytest.mark.skip(reason="DataPath does not support relative_to") + def test_relative_to(self): + pass