Skip to content

Commit b2eff7e

Browse files
Koncopdap--
andauthored
Fix UPath.rename for absolute paths (#225)
* Fix rename * use protocol to decide if joinpath needed * extend rename tests * upath.core: fix rename protocol comparison if protocols depend on storage_options * upath: fix SMBPath.rename * upath: fix rename kwargs for older fsspec versions * upath: explicitly assert return type in UPath.rename * upath.implementations.smb: from __future__ import annotations --------- Co-authored-by: Andreas Poehlmann <[email protected]>
1 parent 655e8fc commit b2eff7e

File tree

3 files changed

+61
-22
lines changed

3 files changed

+61
-22
lines changed

upath/core.py

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,17 @@
1515
from typing import Mapping
1616
from typing import Sequence
1717
from typing import TextIO
18+
from typing import TypedDict
1819
from typing import TypeVar
1920
from typing import overload
2021
from urllib.parse import urlsplit
2122

2223
if sys.version_info >= (3, 11):
2324
from typing import Self
25+
from typing import Unpack
2426
else:
2527
from typing_extensions import Self
28+
from typing_extensions import Unpack
2629

2730
from fsspec.registry import get_filesystem_class
2831
from fsspec.spec import AbstractFileSystem
@@ -91,6 +94,11 @@ def _make_instance(cls, args, kwargs):
9194
return cls(*args, **kwargs)
9295

9396

97+
class _UPathRenameParams(TypedDict, total=False):
98+
recursive: bool
99+
maxdepth: int | None
100+
101+
94102
# accessors are deprecated
95103
_FSSpecAccessor = FSSpecAccessorShim
96104

@@ -1005,21 +1013,33 @@ def rmdir(self, recursive: bool = True) -> None: # fixme: non-standard
10051013
def rename(
10061014
self,
10071015
target: str | os.PathLike[str] | UPath,
1008-
*,
1009-
recursive: bool = False,
1010-
maxdepth: int | None = None,
1011-
**kwargs: Any,
1012-
) -> UPath: # fixme: non-standard
1013-
target_: UPath
1014-
if not isinstance(target, UPath):
1015-
target_ = self.parent.joinpath(target).resolve()
1016+
**kwargs: Unpack[_UPathRenameParams], # note: non-standard compared to pathlib
1017+
) -> Self:
1018+
if isinstance(target, str) and self.storage_options:
1019+
target = UPath(target, **self.storage_options)
1020+
target_protocol = get_upath_protocol(target)
1021+
if target_protocol:
1022+
if target_protocol != self.protocol:
1023+
raise ValueError(
1024+
f"expected protocol {self.protocol!r}, got: {target_protocol!r}"
1025+
)
1026+
if not isinstance(target, UPath):
1027+
target_ = UPath(target, **self.storage_options)
1028+
else:
1029+
target_ = target
1030+
# avoid calling .resolve for subclasses of UPath
1031+
if ".." in target_.parts or "." in target_.parts:
1032+
target_ = target_.resolve()
10161033
else:
1017-
target_ = target
1034+
parent = self.parent
1035+
# avoid calling .resolve for subclasses of UPath
1036+
if ".." in parent.parts or "." in parent.parts:
1037+
parent = parent.resolve()
1038+
target_ = parent.joinpath(os.path.normpath(target))
1039+
assert isinstance(target_, type(self)), "identical protocols enforced above"
10181040
self.fs.mv(
10191041
self.path,
10201042
target_.path,
1021-
recursive=recursive,
1022-
maxdepth=maxdepth,
10231043
**kwargs,
10241044
)
10251045
return target_

upath/implementations/smb.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
1+
from __future__ import annotations
2+
3+
import os
4+
import sys
15
import warnings
26

7+
if sys.version_info >= (3, 11):
8+
from typing import Self
9+
from typing import Unpack
10+
else:
11+
from typing_extensions import Self
12+
from typing_extensions import Unpack
13+
314
import smbprotocol.exceptions
415

516
from upath import UPath
17+
from upath.core import _UPathRenameParams
618

719

820
class SMBPath(UPath):
@@ -29,24 +41,21 @@ def iterdir(self):
2941
else:
3042
return super().iterdir()
3143

32-
def rename(self, target, **kwargs):
33-
if "recursive" in kwargs:
44+
def rename(
45+
self,
46+
target: str | os.PathLike[str] | UPath,
47+
**kwargs: Unpack[_UPathRenameParams], # note: non-standard compared to pathlib
48+
) -> Self:
49+
if kwargs.pop("recursive", None) is not None:
3450
warnings.warn(
3551
"SMBPath.rename(): recursive is currently ignored.",
3652
UserWarning,
3753
stacklevel=2,
3854
)
39-
if "maxdepth" in kwargs:
55+
if kwargs.pop("maxdepth", None) is not None:
4056
warnings.warn(
4157
"SMBPath.rename(): maxdepth is currently ignored.",
4258
UserWarning,
4359
stacklevel=2,
4460
)
45-
if not isinstance(target, UPath):
46-
target = self.parent.joinpath(target).resolve()
47-
self.fs.mv(
48-
self.path,
49-
target.path,
50-
**kwargs,
51-
)
52-
return target
61+
return super().rename(target, **kwargs)

upath/tests/cases.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,11 @@ def test_rename(self):
282282
assert target == moved
283283
assert not upath.exists()
284284
assert moved.exists()
285+
# reverse with an absolute path as str
286+
back = moved.rename(str(upath))
287+
assert back == upath
288+
assert not moved.exists()
289+
assert back.exists()
285290

286291
def test_rename2(self):
287292
upath = self.path.joinpath("folder1/file2.txt")
@@ -291,6 +296,11 @@ def test_rename2(self):
291296
assert target_path == moved
292297
assert not upath.exists()
293298
assert moved.exists()
299+
# reverse with a relative path as UPath
300+
back = moved.rename(UPath("file2.txt"))
301+
assert back == upath
302+
assert not moved.exists()
303+
assert back.exists()
294304

295305
def test_replace(self):
296306
pass

0 commit comments

Comments
 (0)