Skip to content
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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add audeer.move() #128

Merged
merged 18 commits into from
Dec 6, 2023
1 change: 1 addition & 0 deletions audeer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from audeer.core.io import list_file_names
from audeer.core.io import md5
from audeer.core.io import mkdir
from audeer.core.io import move
from audeer.core.io import move_file
from audeer.core.io import replace_file_extension
from audeer.core.io import rmdir
Expand Down
48 changes: 47 additions & 1 deletion audeer/core/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,48 @@ def md5_read_chunk(
yield data


def move(
src_path,
dst_path,
):
"""Move a file or folder independent of operating system.

As :func:`os.rename` works differently
under Unix and Windows
and :func:`shutil.move` can be slow,
we use :func:`os.replace`
to move the file/folder.

Args:
src_path: source file/folder path
dst_path: destination file/folder path

Raises:
OSError: if ``src_path`` is a file
and ``dst_path`` is an existing folder
OSError: if ``src_path`` is a folder
and ``dst_path`` is an existing file
(not raised under Windows)
OSError: if ``dst_path`` is a non-empty folder,
different from ``src_path``,
and ``src_path`` is also a folder
OSError: if ``dst_path`` is an empty folder,
different from ``src_path``
and ``src_path`` is also a folder
(raised only under Windows)

Examples:
>>> path = mkdir('folder')
>>> src_path = touch(os.path.join(path, 'file1'))
hagenw marked this conversation as resolved.
Show resolved Hide resolved
>>> dst_path = os.path.join(path, 'file2')
>>> move(src_path, dst_path)
>>> list_file_names(path, basenames=True)
['file2']

"""
os.replace(src_path, dst_path)


def move_file(
src_path,
dst_path,
Expand All @@ -854,6 +896,10 @@ def move_file(
we use :func:`os.replace`
to move the file.

Warning:
:func:`audeer.move_file` is deprecated,
please use :func:`audeer.move` instead.

Args:
src_path: source file path
dst_path: destination file path
Expand All @@ -867,7 +913,7 @@ def move_file(
['file2']

"""
os.replace(src_path, dst_path)
move(src_path, dst_path)


def replace_file_extension(
Expand Down
1 change: 1 addition & 0 deletions docs/api-src/audeer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ audeer
LooseVersion
md5
mkdir
move
move_file
path
progress_bar
Expand Down
253 changes: 251 additions & 2 deletions tests/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -1135,6 +1135,256 @@ def test_mkdir(tmpdir):
assert mode != int('775', 8)


@pytest.mark.parametrize(
'src_path, dst_path',
[
(
'path1',
'path1',
),
(
'path1',
'path2',
),
]
)
def test_move(tmpdir, src_path, dst_path):

system = platform.system()
tmp_dir = audeer.mkdir(tmpdir, 'folder')

# src: file
# dst: new file
audeer.touch(tmp_dir, src_path)
audeer.move_file(
audeerington marked this conversation as resolved.
Show resolved Hide resolved
os.path.join(tmp_dir, src_path),
os.path.join(tmp_dir, dst_path),
)
if src_path != dst_path:
assert not os.path.exists(os.path.join(tmp_dir, src_path))
assert os.path.exists(os.path.join(tmp_dir, dst_path))

# src: file
# dst: existing file
audeer.rmdir(tmp_dir)
audeer.mkdir(tmp_dir)
audeer.touch(tmp_dir, src_path)
audeer.touch(tmp_dir, dst_path)
audeer.move_file(
os.path.join(tmp_dir, src_path),
os.path.join(tmp_dir, dst_path),
)
if src_path != dst_path:
assert not os.path.exists(os.path.join(tmp_dir, src_path))
assert os.path.exists(os.path.join(tmp_dir, dst_path))

# src: folder
# dst: new folder
audeer.rmdir(tmp_dir)
audeer.mkdir(tmp_dir, src_path)
audeer.touch(tmp_dir, src_path, 'file.txt')
audeer.move_file(
os.path.join(tmp_dir, src_path),
os.path.join(tmp_dir, dst_path),
)
if src_path != dst_path:
assert not os.path.exists(os.path.join(tmp_dir, src_path))
assert os.path.exists(os.path.join(tmp_dir, dst_path))
assert os.path.exists(os.path.join(tmp_dir, dst_path, 'file.txt'))
assert os.path.isdir(os.path.join(tmp_dir, dst_path))

# src: non-empty folder
# dst: existing non-empty folder
audeer.rmdir(tmp_dir)
audeer.mkdir(tmp_dir, src_path)
audeer.touch(tmp_dir, src_path, 'file.txt')
if src_path != dst_path:
audeer.mkdir(tmp_dir, dst_path)
audeer.touch(tmp_dir, dst_path, 'file.txt')
if src_path != dst_path:
if system == 'Windows':
error_msg = 'Access is denied'
else:
error_msg = 'Directory not empty'
with pytest.raises(OSError, match=error_msg):
audeer.move_file(
os.path.join(tmp_dir, src_path),
os.path.join(tmp_dir, dst_path),
)

# src: non-empty folder
# dst: existing empty folder
os.remove(os.path.join(tmp_dir, dst_path, 'file.txt'))
if system == 'Windows':
# Only under Windows
# we get an error
# if destination is an empty folder
with pytest.raises(OSError, match='Access is denied'):
audeer.move_file(
os.path.join(tmp_dir, src_path),
os.path.join(tmp_dir, dst_path),
)
else:
audeer.move_file(
os.path.join(tmp_dir, src_path),
os.path.join(tmp_dir, dst_path),
)

if src_path != dst_path:
assert not os.path.exists(os.path.join(tmp_dir, src_path))
assert os.path.exists(os.path.join(tmp_dir, dst_path))
assert os.path.exists(os.path.join(tmp_dir, dst_path, 'file.txt'))
assert os.path.isdir(os.path.join(tmp_dir, dst_path))

# src: non-empty folder
# dst: identical to src
if src_path == dst_path:
audeer.move_file(
os.path.join(tmp_dir, src_path),
os.path.join(tmp_dir, dst_path),
)

if src_path != dst_path:
assert not os.path.exists(os.path.join(tmp_dir, src_path))
assert os.path.exists(os.path.join(tmp_dir, dst_path))
assert os.path.exists(os.path.join(tmp_dir, dst_path, 'file.txt'))
assert os.path.isdir(os.path.join(tmp_dir, dst_path))

# src: empty folder
# dst: existing non-empty folder
audeer.rmdir(tmp_dir)
audeer.mkdir(tmp_dir, src_path)
if src_path != dst_path:
audeer.mkdir(tmp_dir, dst_path)
audeer.touch(tmp_dir, dst_path, 'file.txt')
if src_path != dst_path:
if system == 'Windows':
error_msg = 'Access is denied'
else:
error_msg = 'Directory not empty'
with pytest.raises(OSError, match=error_msg):
audeer.move_file(
os.path.join(tmp_dir, src_path),
os.path.join(tmp_dir, dst_path),
)

# src: empty folder
# dst: existing empty folder
os.remove(os.path.join(tmp_dir, dst_path, 'file.txt'))
if system == 'Windows':
# Only under Windows
# we get an error
# if destination is an empty folder
with pytest.raises(OSError, match='Access is denied'):
audeer.move_file(
os.path.join(tmp_dir, src_path),
os.path.join(tmp_dir, dst_path),
)
else:
audeer.move_file(
os.path.join(tmp_dir, src_path),
os.path.join(tmp_dir, dst_path),
)

if src_path != dst_path:
assert not os.path.exists(os.path.join(tmp_dir, src_path))
assert os.path.exists(os.path.join(tmp_dir, dst_path))
assert not os.path.exists(
os.path.join(tmp_dir, dst_path, 'file.txt')
)
assert os.path.isdir(os.path.join(tmp_dir, dst_path))

# src: empty folder
# dst: identical to src
if src_path == dst_path:
audeer.move_file(
os.path.join(tmp_dir, src_path),
os.path.join(tmp_dir, dst_path),
)

if src_path != dst_path:
assert not os.path.exists(os.path.join(tmp_dir, src_path))
assert os.path.exists(os.path.join(tmp_dir, dst_path))
assert not os.path.exists(os.path.join(tmp_dir, dst_path, 'file.txt'))
assert os.path.isdir(os.path.join(tmp_dir, dst_path))

if src_path != dst_path:

# src: file
# dst: non-empty folder
audeer.rmdir(tmp_dir)
audeer.mkdir(tmp_dir)
audeer.touch(tmp_dir, src_path)
audeer.mkdir(tmp_dir, dst_path)
audeer.touch(tmp_dir, dst_path, 'file.txt')
if system == 'Windows':
error_msg = 'Access is denied'
else:
error_msg = 'Is a directory'
with pytest.raises(OSError, match=error_msg):
audeer.move_file(
os.path.join(tmp_dir, src_path),
os.path.join(tmp_dir, dst_path),
)

# src: file
# dst: empty folder
os.remove(os.path.join(tmp_dir, dst_path, 'file.txt'))
with pytest.raises(OSError, match=error_msg):
audeer.move_file(
os.path.join(tmp_dir, src_path),
os.path.join(tmp_dir, dst_path),
)

# src: non-empty folder
# dst: file
audeer.rmdir(tmp_dir)
audeer.mkdir(tmp_dir)
audeer.mkdir(tmp_dir, src_path)
audeer.touch(tmp_dir, src_path, 'file.txt')
audeer.touch(tmp_dir, dst_path)
if system != 'Windows':
error_msg = 'Not a directory'
with pytest.raises(OSError, match=error_msg):
audeer.move_file(
os.path.join(tmp_dir, src_path),
os.path.join(tmp_dir, dst_path),
)
else:
audeer.move_file(
os.path.join(tmp_dir, src_path),
os.path.join(tmp_dir, dst_path),
)
assert not os.path.exists(os.path.join(tmp_dir, src_path))
assert os.path.exists(os.path.join(tmp_dir, dst_path))
assert os.path.exists(os.path.join(tmp_dir, dst_path, 'file.txt'))
assert os.path.isdir(os.path.join(tmp_dir, dst_path))

# src: empty folder
# dst: file
audeer.rmdir(tmp_dir)
audeer.mkdir(tmp_dir, src_path)
audeer.touch(tmp_dir, dst_path)
if system != 'Windows':
error_msg = 'Not a directory'
with pytest.raises(OSError, match=error_msg):
audeer.move_file(
os.path.join(tmp_dir, src_path),
os.path.join(tmp_dir, dst_path),
)
else:
audeer.move_file(
os.path.join(tmp_dir, src_path),
os.path.join(tmp_dir, dst_path),
)
assert not os.path.exists(os.path.join(tmp_dir, src_path))
assert os.path.exists(os.path.join(tmp_dir, dst_path))
assert not os.path.exists(
os.path.join(tmp_dir, dst_path, 'file.txt')
)
assert os.path.isdir(os.path.join(tmp_dir, dst_path))


@pytest.mark.parametrize(
'src_file, dst_file',
[
Expand All @@ -1150,8 +1400,7 @@ def test_mkdir(tmpdir):
)
def test_move_file(tmpdir, src_file, dst_file):

tmp_path = str(tmpdir.mkdir('folder'))
tmp_path = audeer.mkdir(tmp_path)
tmp_path = audeer.mkdir(tmpdir, 'folder')

src_path = audeer.touch(tmp_path, src_file)
dst_path = os.path.join(tmp_path, dst_file)
Expand Down