Commit f3b2d88b authored by Antoine Pitrou's avatar Antoine Pitrou

Issue #8828: Add new function os.replace(), for cross-platform renaming with overwriting.

parent 8a894508
...@@ -1889,8 +1889,9 @@ Files and Directories ...@@ -1889,8 +1889,9 @@ Files and Directories
Unix flavors if *src* and *dst* are on different filesystems. If successful, Unix flavors if *src* and *dst* are on different filesystems. If successful,
the renaming will be an atomic operation (this is a POSIX requirement). On the renaming will be an atomic operation (this is a POSIX requirement). On
Windows, if *dst* already exists, :exc:`OSError` will be raised even if it is a Windows, if *dst* already exists, :exc:`OSError` will be raised even if it is a
file; there may be no way to implement an atomic rename when *dst* names an file.
existing file.
If you want cross-platform overwriting of the destination, use :func:`replace`.
Availability: Unix, Windows. Availability: Unix, Windows.
...@@ -1908,6 +1909,19 @@ Files and Directories ...@@ -1908,6 +1909,19 @@ Files and Directories
permissions needed to remove the leaf directory or file. permissions needed to remove the leaf directory or file.
.. function:: replace(src, dst)
Rename the file or directory *src* to *dst*. If *dst* is a directory,
:exc:`OSError` will be raised. If *dst* exists and is a file, it will
be replaced silently if the user has permission. The operation may fail
if *src* and *dst* are on different filesystems. If successful,
the renaming will be an atomic operation (this is a POSIX requirement).
Availability: Unix, Windows
.. versionadded:: 3.3
.. function:: rmdir(path) .. function:: rmdir(path)
Remove (delete) the directory *path*. Only works when the directory is Remove (delete) the directory *path*. Only works when the directory is
......
...@@ -129,6 +129,18 @@ class FileTests(unittest.TestCase): ...@@ -129,6 +129,18 @@ class FileTests(unittest.TestCase):
self.fdopen_helper('r') self.fdopen_helper('r')
self.fdopen_helper('r', 100) self.fdopen_helper('r', 100)
def test_replace(self):
TESTFN2 = support.TESTFN + ".2"
with open(support.TESTFN, 'w') as f:
f.write("1")
with open(TESTFN2, 'w') as f:
f.write("2")
self.addCleanup(os.unlink, TESTFN2)
os.replace(support.TESTFN, TESTFN2)
self.assertRaises(FileNotFoundError, os.stat, support.TESTFN)
with open(TESTFN2, 'r') as f:
self.assertEqual(f.read(), "1")
# Test attributes on return values from os.*stat* family. # Test attributes on return values from os.*stat* family.
class StatAttributeTests(unittest.TestCase): class StatAttributeTests(unittest.TestCase):
......
...@@ -463,6 +463,9 @@ Core and Builtins ...@@ -463,6 +463,9 @@ Core and Builtins
Library Library
------- -------
- Issue #8828: Add new function os.replace(), for cross-platform renaming
with overwriting.
- Issue #13848: open() and the FileIO constructor now check for NUL - Issue #13848: open() and the FileIO constructor now check for NUL
characters in the file name. Patch by Hynek Schlawack. characters in the file name. Patch by Hynek Schlawack.
......
...@@ -3280,17 +3280,16 @@ posix_setpriority(PyObject *self, PyObject *args) ...@@ -3280,17 +3280,16 @@ posix_setpriority(PyObject *self, PyObject *args)
#endif /* HAVE_SETPRIORITY */ #endif /* HAVE_SETPRIORITY */
PyDoc_STRVAR(posix_rename__doc__,
"rename(old, new)\n\n\
Rename a file or directory.");
static PyObject * static PyObject *
posix_rename(PyObject *self, PyObject *args) internal_rename(PyObject *self, PyObject *args, int is_replace)
{ {
#ifdef MS_WINDOWS #ifdef MS_WINDOWS
PyObject *src, *dst; PyObject *src, *dst;
BOOL result; BOOL result;
if (PyArg_ParseTuple(args, "UU:rename", &src, &dst)) int flags = is_replace ? MOVEFILE_REPLACE_EXISTING : 0;
if (PyArg_ParseTuple(args,
is_replace ? "UU:replace" : "UU:rename",
&src, &dst))
{ {
wchar_t *wsrc, *wdst; wchar_t *wsrc, *wdst;
...@@ -3301,16 +3300,17 @@ posix_rename(PyObject *self, PyObject *args) ...@@ -3301,16 +3300,17 @@ posix_rename(PyObject *self, PyObject *args)
if (wdst == NULL) if (wdst == NULL)
return NULL; return NULL;
Py_BEGIN_ALLOW_THREADS Py_BEGIN_ALLOW_THREADS
result = MoveFileW(wsrc, wdst); result = MoveFileExW(wsrc, wdst, flags);
Py_END_ALLOW_THREADS Py_END_ALLOW_THREADS
if (!result) if (!result)
return win32_error("rename", NULL); return win32_error(is_replace ? "replace" : "rename", NULL);
Py_INCREF(Py_None); Py_INCREF(Py_None);
return Py_None; return Py_None;
} }
else { else {
PyErr_Clear(); PyErr_Clear();
if (!PyArg_ParseTuple(args, "O&O&:rename", if (!PyArg_ParseTuple(args,
is_replace ? "O&O&:replace" : "O&O&:rename",
PyUnicode_FSConverter, &src, PyUnicode_FSConverter, &src,
PyUnicode_FSConverter, &dst)) PyUnicode_FSConverter, &dst))
return NULL; return NULL;
...@@ -3319,15 +3319,15 @@ posix_rename(PyObject *self, PyObject *args) ...@@ -3319,15 +3319,15 @@ posix_rename(PyObject *self, PyObject *args)
goto error; goto error;
Py_BEGIN_ALLOW_THREADS Py_BEGIN_ALLOW_THREADS
result = MoveFileA(PyBytes_AS_STRING(src), result = MoveFileExA(PyBytes_AS_STRING(src),
PyBytes_AS_STRING(dst)); PyBytes_AS_STRING(dst), flags);
Py_END_ALLOW_THREADS Py_END_ALLOW_THREADS
Py_XDECREF(src); Py_XDECREF(src);
Py_XDECREF(dst); Py_XDECREF(dst);
if (!result) if (!result)
return win32_error("rename", NULL); return win32_error(is_replace ? "replace" : "rename", NULL);
Py_INCREF(Py_None); Py_INCREF(Py_None);
return Py_None; return Py_None;
...@@ -3337,10 +3337,30 @@ error: ...@@ -3337,10 +3337,30 @@ error:
return NULL; return NULL;
} }
#else #else
return posix_2str(args, "O&O&:rename", rename); return posix_2str(args,
is_replace ? "O&O&:replace" : "O&O&:rename", rename);
#endif #endif
} }
PyDoc_STRVAR(posix_rename__doc__,
"rename(old, new)\n\n\
Rename a file or directory.");
static PyObject *
posix_rename(PyObject *self, PyObject *args)
{
return internal_rename(self, args, 0);
}
PyDoc_STRVAR(posix_replace__doc__,
"replace(old, new)\n\n\
Rename a file or directory, overwriting the destination.");
static PyObject *
posix_replace(PyObject *self, PyObject *args)
{
return internal_rename(self, args, 1);
}
PyDoc_STRVAR(posix_rmdir__doc__, PyDoc_STRVAR(posix_rmdir__doc__,
"rmdir(path)\n\n\ "rmdir(path)\n\n\
...@@ -10555,6 +10575,7 @@ static PyMethodDef posix_methods[] = { ...@@ -10555,6 +10575,7 @@ static PyMethodDef posix_methods[] = {
{"readlink", win_readlink, METH_VARARGS, win_readlink__doc__}, {"readlink", win_readlink, METH_VARARGS, win_readlink__doc__},
#endif /* !defined(HAVE_READLINK) && defined(MS_WINDOWS) */ #endif /* !defined(HAVE_READLINK) && defined(MS_WINDOWS) */
{"rename", posix_rename, METH_VARARGS, posix_rename__doc__}, {"rename", posix_rename, METH_VARARGS, posix_rename__doc__},
{"replace", posix_replace, METH_VARARGS, posix_replace__doc__},
{"rmdir", posix_rmdir, METH_VARARGS, posix_rmdir__doc__}, {"rmdir", posix_rmdir, METH_VARARGS, posix_rmdir__doc__},
{"stat", posix_stat, METH_VARARGS, posix_stat__doc__}, {"stat", posix_stat, METH_VARARGS, posix_stat__doc__},
{"stat_float_times", stat_float_times, METH_VARARGS, stat_float_times__doc__}, {"stat_float_times", stat_float_times, METH_VARARGS, stat_float_times__doc__},
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment