Repository navigation
Fixed BucketPath using backslash separators on Windows #325
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,11 @@ | ||
| # Unreleased | ||
|
|
||
| ## Summary | ||
|
|
||
| ## Bug Fixing | ||
|
|
||
| * #322: Fixed `BucketPath` and `MountedBucket` using backslash separators on Windows. Paths in a bucket are now always POSIX paths, so `read()`, `write()`, `rm()`, `iterdir()`, `joinpath()`, `/`, `as_udf_path()`, `MountedBucket.files` and `MountedBucket.udf_path` produce `/`-separated paths on every platform. A Windows drive or UNC share in a path is dropped. | ||
|
|
||
| ## Breaking Changes | ||
|
|
||
| * #322: On Windows, `BucketPath.relative_to()` now returns a `PurePosixPath` instead of a `PureWindowsPath`, so it no longer compares equal to a `PurePath` built from the same string on Windows. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| import os | ||
| from pathlib import PureWindowsPath | ||
|
|
||
| import pytest | ||
|
|
||
| import exasol.bucketfs as bfs | ||
| from exasol.bucketfs._buckets import MountedBucket | ||
|
|
||
|
|
||
| class RecordingBucketApi: | ||
| udf_path = "/buckets/bfsdefault/default" | ||
|
|
||
| def __init__(self, files): | ||
| self.files = files | ||
| self.calls = [] | ||
|
|
||
| def upload(self, path, data): | ||
| self.calls.append(("upload", path)) | ||
|
|
||
| def download(self, path, chunk_size): | ||
| self.calls.append(("download", path)) | ||
| return iter([]) | ||
|
|
||
| def delete(self, path): | ||
| self.calls.append(("delete", path)) | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def bucket_api(): | ||
| return RecordingBucketApi(files=["dir/sub/a.txt"]) | ||
|
|
||
|
|
||
| def test_windows_path_object_is_stored_with_posix_separators(bucket_api): | ||
| path = bfs.path.BucketPath(PureWindowsPath("dir", "sub", "a.txt"), bucket_api) | ||
| assert str(path) == "dir/sub/a.txt" | ||
|
|
||
|
|
||
| def test_read_write_and_rm_send_posix_paths(bucket_api): | ||
| path = bfs.path.BucketPath(PureWindowsPath("dir", "sub", "a.txt"), bucket_api) | ||
| path.write(b"x") | ||
| list(path.read()) | ||
| path.rm() | ||
| assert bucket_api.calls == [ | ||
| ("upload", "dir/sub/a.txt"), | ||
| ("download", "dir/sub/a.txt"), | ||
| ("delete", "dir/sub/a.txt"), | ||
| ] | ||
|
|
||
|
|
||
| def test_iterdir_yields_posix_paths(bucket_api): | ||
| root = bfs.path.BucketPath(PureWindowsPath("dir"), bucket_api) | ||
| assert [str(child) for child in root.iterdir()] == ["dir/sub"] | ||
|
|
||
|
|
||
| def test_as_udf_path_uses_posix_separators(bucket_api): | ||
| path = bfs.path.BucketPath(PureWindowsPath("dir", "sub", "a.txt"), bucket_api) | ||
| assert path.as_udf_path() == "/buckets/bfsdefault/default/dir/sub/a.txt" | ||
|
|
||
|
|
||
| def test_windows_path_object_is_found_in_the_bucket_listing(bucket_api): | ||
| path = bfs.path.BucketPath(PureWindowsPath("dir", "sub", "a.txt"), bucket_api) | ||
| assert path.exists() | ||
|
|
||
|
|
||
| def test_string_built_with_the_native_separator_is_found(bucket_api): | ||
| # on Windows this string contains backslashes | ||
| path = bfs.path.BucketPath(os.path.join("dir", "sub", "a.txt"), bucket_api) | ||
| assert str(path) == "dir/sub/a.txt" | ||
| assert path.exists() | ||
| assert path.name == "a.txt" | ||
| assert str(path.parent) == "dir/sub" | ||
|
|
||
|
|
||
| def test_drive_letter_of_a_windows_path_is_dropped(bucket_api): | ||
| path = bfs.path.BucketPath(PureWindowsPath("C:/dir/sub/a.txt"), bucket_api) | ||
| assert str(path) == "/dir/sub/a.txt" | ||
| assert path.exists() | ||
|
|
||
|
|
||
| def test_unc_share_of_a_windows_path_is_dropped(bucket_api): | ||
| path = bfs.path.BucketPath(PureWindowsPath("//server/share/dir/sub"), bucket_api) | ||
| assert str(path) == "/dir/sub" | ||
| assert path.exists() | ||
|
|
||
|
|
||
| def test_joinpath_with_windows_path_objects(bucket_api): | ||
| root = bfs.path.BucketPath("", bucket_api) | ||
| path = root.joinpath(PureWindowsPath("dir", "sub"), PureWindowsPath("a.txt")) | ||
| assert str(path) == "dir/sub/a.txt" | ||
| assert path.exists() | ||
|
|
||
|
|
||
| def test_joinpath_with_a_rooted_windows_path_object_restarts_at_the_root(bucket_api): | ||
| path = bfs.path.BucketPath("a", bucket_api).joinpath(PureWindowsPath("C:/x/y")) | ||
| assert str(path) == "/x/y" | ||
|
|
||
|
|
||
| def test_truediv_with_windows_path_objects(bucket_api): | ||
| root = bfs.path.BucketPath("dir", bucket_api) | ||
| assert str(root / PureWindowsPath("sub", "a.txt")) == "dir/sub/a.txt" | ||
| assert str(root / PureWindowsPath("C:/x/y")) == "/x/y" | ||
|
|
||
|
|
||
| def test_joinpath_with_string_uses_posix_separators(bucket_api): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test uses only forward slashes, so it also passes on the unfixed code and doesn't exercise the bug. Missing coverage: backslash strings, absolute / drive-letter / UNC |
||
| root = bfs.path.BucketPath("", bucket_api) | ||
| path = root.joinpath("dir/sub/a.txt") | ||
| path.write(b"x") | ||
| assert bucket_api.calls == [("upload", "dir/sub/a.txt")] | ||
|
|
||
|
|
||
| def test_mounted_bucket_lists_posix_files_and_udf_path(tmp_path): | ||
| (tmp_path / "dir" / "sub").mkdir(parents=True) | ||
| (tmp_path / "dir" / "sub" / "a.txt").write_bytes(b"x") | ||
| bucket = MountedBucket(base_path=tmp_path) | ||
| assert list(bucket.files) == ["dir/sub/a.txt"] | ||
| assert bucket.udf_path == tmp_path.as_posix() | ||
| path = bfs.path.BucketPath("dir/sub/a.txt", bucket) | ||
| assert path.exists() | ||
| assert path.as_udf_path() == f"{tmp_path.as_posix()}/dir/sub/a.txt" | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MountedBucket.udf_pathreturnsstr(Path(...)), which uses backslashes on Windows, so wrapping it inPurePosixPathproduces mixed separators, e.g.\buckets\bfsdefault\default/dir/a.txt. EitherMountedBucket.udf_pathshould return a POSIX string, or this should convert it.