Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

## Unreleased


### Added

- Added `filter_glob` and `exclude_glob` parameters to `fs.walk.Walker`.
Expand All @@ -16,6 +17,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Fixed
- Elaborated documentation of `filter_dirs` and `exclude_dirs` in `fs.walk.Walker`.
Closes [#371](https://github.com/PyFilesystem/pyfilesystem2/issues/371).
- Fixes a backward incompatibility where `fs.move.move_file` raises `DestinationExists`
([#535](https://github.com/PyFilesystem/pyfilesystem2/issues/535)).


## [2.4.16] - 2022-05-02
Expand Down
13 changes: 11 additions & 2 deletions fs/move.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from .errors import FSError
from .opener import manage_fs
from .osfs import OSFS
from .path import frombase
from .path import frombase, normpath

if typing.TYPE_CHECKING:
from typing import Text, Union
Expand Down Expand Up @@ -64,6 +64,10 @@ def move_file(
with manage_fs(src_fs, writeable=True) as _src_fs:
with manage_fs(dst_fs, writeable=True, create=True) as _dst_fs:
if _src_fs is _dst_fs:
# Exit early if source and destination are the same file
if normpath(src_path) == normpath(dst_path):
return

# Same filesystem, may be optimized
_src_fs.move(
src_path, dst_path, overwrite=True, preserve_time=preserve_time
Expand All @@ -82,7 +86,12 @@ def move_file(
rel_dst = frombase(common, dst_syspath)
with _src_fs.lock(), _dst_fs.lock():
with OSFS(common) as base:
base.move(rel_src, rel_dst, preserve_time=preserve_time)
base.move(
rel_src,
rel_dst,
overwrite=True,
preserve_time=preserve_time,
)
return # optimization worked, exit early
except ValueError:
# This is raised if we cannot find a common base folder.
Expand Down
39 changes: 39 additions & 0 deletions tests/test_move.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,45 @@ def test_move_file_read_only_mem_dest(self):
dst_ro.exists("target.txt"), "file should not have been copied over"
)

@parameterized.expand([("temp", "temp://"), ("mem", "mem://")])
def test_move_file_overwrite(self, _, fs_url):
# we use TempFS and MemoryFS in order to make sure the optimized code path
# behaves like the regular one (TempFS tests the optmized code path).
with open_fs(fs_url) as src, open_fs(fs_url) as dst:
src.writetext("file.txt", "source content")
dst.writetext("target.txt", "target content")
self.assertTrue(src.exists("file.txt"))
self.assertFalse(src.exists("target.txt"))
self.assertFalse(dst.exists("file.txt"))
self.assertTrue(dst.exists("target.txt"))
fs.move.move_file(src, "file.txt", dst, "target.txt")
self.assertFalse(src.exists("file.txt"))
self.assertFalse(src.exists("target.txt"))
self.assertFalse(dst.exists("file.txt"))
self.assertTrue(dst.exists("target.txt"))
self.assertEquals(dst.readtext("target.txt"), "source content")

@parameterized.expand([("temp", "temp://"), ("mem", "mem://")])
def test_move_file_overwrite_itself(self, _, fs_url):
# we use TempFS and MemoryFS in order to make sure the optimized code path
# behaves like the regular one (TempFS tests the optmized code path).
with open_fs(fs_url) as tmp:
tmp.writetext("file.txt", "content")
fs.move.move_file(tmp, "file.txt", tmp, "file.txt")
self.assertTrue(tmp.exists("file.txt"))
self.assertEquals(tmp.readtext("file.txt"), "content")

@parameterized.expand([("temp", "temp://"), ("mem", "mem://")])
def test_move_file_overwrite_itself_relpath(self, _, fs_url):
# we use TempFS and MemoryFS in order to make sure the optimized code path
# behaves like the regular one (TempFS tests the optmized code path).
with open_fs(fs_url) as tmp:
new_dir = tmp.makedir("dir")
new_dir.writetext("file.txt", "content")
fs.move.move_file(tmp, "dir/../dir/file.txt", tmp, "dir/file.txt")
self.assertTrue(tmp.exists("dir/file.txt"))
self.assertEquals(tmp.readtext("dir/file.txt"), "content")

@parameterized.expand([(True,), (False,)])
def test_move_file_cleanup_on_error(self, cleanup):
with open_fs("mem://") as src, open_fs("mem://") as dst:
Expand Down