]> granicus.if.org Git - python/commitdiff
Issue #8828: Add new function os.replace(), for cross-platform renaming with overwriting.
authorAntoine Pitrou <solipsis@pitrou.net>
Mon, 30 Jan 2012 21:08:52 +0000 (22:08 +0100)
committerAntoine Pitrou <solipsis@pitrou.net>
Mon, 30 Jan 2012 21:08:52 +0000 (22:08 +0100)
Doc/library/os.rst
Lib/test/test_os.py
Misc/NEWS
Modules/posixmodule.c

index 3c302a4895128616860b0351214886c9cf58d5ae..617f276360387ff39b24415b32a59e8faeddfaf4 100644 (file)
@@ -1889,8 +1889,9 @@ Files and Directories
    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
    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
-   existing file.
+   file.
+
+   If you want cross-platform overwriting of the destination, use :func:`replace`.
 
    Availability: Unix, Windows.
 
@@ -1908,6 +1909,19 @@ Files and Directories
       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)
 
    Remove (delete) the directory *path*.  Only works when the directory is
index e78db48afe049d4305ee1ff9afed383d52042aca..293005b3a1d9275364acfc393a5e196450a4b915 100644 (file)
@@ -129,6 +129,18 @@ class FileTests(unittest.TestCase):
         self.fdopen_helper('r')
         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.
 class StatAttributeTests(unittest.TestCase):
index 2607618a0ebaa9d22bfba9d1f89eb4db74f820d2..8b6725743323f1a4c73c0890415f76d041ea2c79 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -463,6 +463,9 @@ Core and Builtins
 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
   characters in the file name.  Patch by Hynek Schlawack.
 
index 1797a2f64a479fb54b9a54cff7c1d7ef0021b99d..2269fe90218455534cdd711a591a3f0cd52557f8 100644 (file)
@@ -3280,17 +3280,16 @@ posix_setpriority(PyObject *self, PyObject *args)
 #endif /* HAVE_SETPRIORITY */
 
 
-PyDoc_STRVAR(posix_rename__doc__,
-"rename(old, new)\n\n\
-Rename a file or directory.");
-
 static PyObject *
-posix_rename(PyObject *self, PyObject *args)
+internal_rename(PyObject *self, PyObject *args, int is_replace)
 {
 #ifdef MS_WINDOWS
     PyObject *src, *dst;
     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;
 
@@ -3301,16 +3300,17 @@ posix_rename(PyObject *self, PyObject *args)
         if (wdst == NULL)
             return NULL;
         Py_BEGIN_ALLOW_THREADS
-        result = MoveFileW(wsrc, wdst);
+        result = MoveFileExW(wsrc, wdst, flags);
         Py_END_ALLOW_THREADS
         if (!result)
-            return win32_error("rename", NULL);
+            return win32_error(is_replace ? "replace" : "rename", NULL);
         Py_INCREF(Py_None);
         return Py_None;
     }
     else {
         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, &dst))
             return NULL;
@@ -3319,15 +3319,15 @@ posix_rename(PyObject *self, PyObject *args)
             goto error;
 
         Py_BEGIN_ALLOW_THREADS
-        result = MoveFileA(PyBytes_AS_STRING(src),
-                           PyBytes_AS_STRING(dst));
+        result = MoveFileExA(PyBytes_AS_STRING(src),
+                             PyBytes_AS_STRING(dst), flags);
         Py_END_ALLOW_THREADS
 
         Py_XDECREF(src);
         Py_XDECREF(dst);
 
         if (!result)
-            return win32_error("rename", NULL);
+            return win32_error(is_replace ? "replace" : "rename", NULL);
         Py_INCREF(Py_None);
         return Py_None;
 
@@ -3337,10 +3337,30 @@ error:
         return NULL;
     }
 #else
-    return posix_2str(args, "O&O&:rename", rename);
+    return posix_2str(args,
+                      is_replace ? "O&O&:replace" : "O&O&:rename", rename);
 #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__,
 "rmdir(path)\n\n\
@@ -10555,6 +10575,7 @@ static PyMethodDef posix_methods[] = {
     {"readlink",        win_readlink, METH_VARARGS, win_readlink__doc__},
 #endif /* !defined(HAVE_READLINK) && defined(MS_WINDOWS) */
     {"rename",          posix_rename, METH_VARARGS, posix_rename__doc__},
+    {"replace",         posix_replace, METH_VARARGS, posix_replace__doc__},
     {"rmdir",           posix_rmdir, METH_VARARGS, posix_rmdir__doc__},
     {"stat",            posix_stat, METH_VARARGS, posix_stat__doc__},
     {"stat_float_times", stat_float_times, METH_VARARGS, stat_float_times__doc__},