Skip to content

Commit ca6571c

Browse files
authored
Normalize databricks paths as part of resolving them (#157)
`Path("/a/b/../c").resolve()` returns `Path("/a/c")` Databricks paths should behave the same, but currently don't. This PR fixes the issue, which participates in databrickslabs/ucx#2882 Progresses databrickslabs/ucx#2882 --------- Co-authored-by: Eric Vergnaud <[email protected]>
1 parent dede64d commit ca6571c

File tree

2 files changed

+33
-1
lines changed

2 files changed

+33
-1
lines changed

src/databricks/labs/blueprint/paths.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,23 @@ def resolve(self: P, strict: bool = False) -> P:
520520
if strict and not absolute.exists():
521521
msg = f"Path does not exist: {self}"
522522
raise FileNotFoundError(msg)
523-
return absolute
523+
# pylint: disable=protected-access
524+
return absolute._normalize()
525+
526+
def _normalize(self: P) -> P:
527+
if ".." not in self._path_parts:
528+
return self
529+
segments: list[str] = []
530+
for part in self._path_parts:
531+
if part == "..":
532+
if segments:
533+
segments.pop()
534+
elif part is None or part == '.':
535+
continue
536+
else:
537+
segments.append(part)
538+
# pylint: disable=protected-access
539+
return self.with_segments(self.anchor, *segments)._normalize()
524540

525541
def absolute(self: P) -> P:
526542
if self.is_absolute():

tests/integration/test_paths.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,22 @@ def test_replace_file(ws, make_random, cls):
191191
tmp_dir.rmdir(recursive=True)
192192

193193

194+
@pytest.mark.parametrize("cls", DATABRICKS_PATHLIKE)
195+
def test_resolve_is_consistent(ws, cls):
196+
path = cls(ws, "/a/b/c") / Path("../../d")
197+
resolved = path.resolve()
198+
assert resolved == cls(ws, "/a/d")
199+
path = cls(ws, "/a/b/c") / "../../d"
200+
resolved = path.resolve()
201+
assert resolved == cls(ws, "/a/d")
202+
resolved = cls(ws, "/a/b/c/../../d").resolve()
203+
assert resolved == cls(ws, "/a/d")
204+
resolved = cls(ws, "/../d").resolve()
205+
assert resolved == cls(ws, "/d")
206+
resolved = cls(ws, "/a/b/c/./../../d").resolve()
207+
assert resolved == cls(ws, "/a/d")
208+
209+
194210
def test_workspace_as_fuse(ws):
195211
wsp = WorkspacePath(ws, "/Users/foo/bar/baz")
196212
assert Path("/Workspace/Users/foo/bar/baz") == wsp.as_fuse()

0 commit comments

Comments
 (0)