]> granicus.if.org Git - python/commitdiff
Issue #12760: Add a create mode to open(). Patch by David Townshend.
authorCharles-François Natali <neologix@free.fr>
Mon, 9 Jan 2012 21:40:02 +0000 (22:40 +0100)
committerCharles-François Natali <neologix@free.fr>
Mon, 9 Jan 2012 21:40:02 +0000 (22:40 +0100)
Doc/library/io.rst
Doc/library/os.rst
Doc/whatsnew/3.3.rst
Lib/_pyio.py
Lib/test/test_io.py
Misc/ACKS
Misc/NEWS
Modules/_io/_iomodule.c
Modules/_io/fileio.c

index 85e8c5bc0fb105b6da4e0ee0bff9ec249b470aec..82969ebda33c2d852ebbcff2f27936af487dc3e6 100644 (file)
@@ -471,10 +471,13 @@ Raw File I/O
    * an integer representing the number of an existing OS-level file descriptor
      to which the resulting :class:`FileIO` object will give access.
 
-   The *mode* can be ``'r'``, ``'w'`` or ``'a'`` for reading (default), writing,
-   or appending.  The file will be created if it doesn't exist when opened for
-   writing or appending; it will be truncated when opened for writing.  Add a
-   ``'+'`` to the mode to allow simultaneous reading and writing.
+   The *mode* can be ``'r'``, ``'w'``, ``'x'`` or ``'a'`` for reading
+   (default), writing, creating or appending. The file will be created if it
+   doesn't exist when opened for writing or appending; it will be truncated
+   when opened for writing. :exc:`FileExistsError` will be raised if it already
+   exists when opened for creating. Opening a file for creating implies
+   writing, so this mode behaves in a similar way to ``'w'``. Add a ``'+'`` to
+   the mode to allow simultaneous reading and writing.
 
    The :meth:`read` (when called with a positive argument), :meth:`readinto`
    and :meth:`write` methods on this class will only make one system call.
@@ -487,6 +490,7 @@ Raw File I/O
 
    .. versionchanged:: 3.3
       The *opener* parameter was added.
+      The ``'x'`` mode was added.
 
    In addition to the attributes and methods from :class:`IOBase` and
    :class:`RawIOBase`, :class:`FileIO` provides the following data
index bb1ebd922f5221afced1597ddf3d845d1b8686f4..53f5025216d4a2c9d46d3c971bec4f8260ef1b05 100644 (file)
@@ -591,7 +591,8 @@ These functions create new :term:`file objects <file object>`. (See also :func:`
    the built-in :func:`open` function.
 
    When specified, the *mode* argument must start with one of the letters
-   ``'r'``, ``'w'``, or ``'a'``, otherwise a :exc:`ValueError` is raised.
+   ``'r'``, ``'w'``, ``'x'`` or ``'a'``, otherwise a :exc:`ValueError` is
+   raised.
 
    On Unix, when the *mode* argument starts with ``'a'``, the *O_APPEND* flag is
    set on the file descriptor (which the :c:func:`fdopen` implementation already
@@ -599,6 +600,8 @@ These functions create new :term:`file objects <file object>`. (See also :func:`
 
    Availability: Unix, Windows.
 
+   .. versionchanged:: 3.3
+      The ``'x'`` mode was added.
 
 .. _os-fd-ops:
 
index f458ae0f4f6f397d89f727dfd9e81e359c4515a6..0b0f8f6db68ff4b6036c7afb5a9eb8cf62b65f0e 100644 (file)
@@ -408,6 +408,15 @@ parameter to control parameters of the secure channel.
 (Contributed by Sijin Joseph in :issue:`8808`)
 
 
+io
+--
+
+The :func:`~io.open` function has a new ``'x'`` mode that can be used to create
+a new file, and raise a :exc:`FileExistsError` if the file already exists.
+
+(Contributed by David Townshend in :issue:`12760`)
+
+
 lzma
 ----
 
index fcd548ddc388272e4e544f93ecdb3039cf7bc511..6e8f3691bb0870d23ff3dc7935462df9dc799dc9 100644 (file)
@@ -38,21 +38,22 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
     wrapped. (If a file descriptor is given, it is closed when the
     returned I/O object is closed, unless closefd is set to False.)
 
-    mode is an optional string that specifies the mode in which the file
-    is opened. It defaults to 'r' which means open for reading in text
-    mode.  Other common values are 'w' for writing (truncating the file if
-    it already exists), and 'a' for appending (which on some Unix systems,
-    means that all writes append to the end of the file regardless of the
-    current seek position). In text mode, if encoding is not specified the
-    encoding used is platform dependent. (For reading and writing raw
-    bytes use binary mode and leave encoding unspecified.) The available
-    modes are:
+    mode is an optional string that specifies the mode in which the file is
+    opened. It defaults to 'r' which means open for reading in text mode. Other
+    common values are 'w' for writing (truncating the file if it already
+    exists), 'x' for creating and writing to a new file, and 'a' for appending
+    (which on some Unix systems, means that all writes append to the end of the
+    file regardless of the current seek position). In text mode, if encoding is
+    not specified the encoding used is platform dependent. (For reading and
+    writing raw bytes use binary mode and leave encoding unspecified.) The
+    available modes are:
 
     ========= ===============================================================
     Character Meaning
     --------- ---------------------------------------------------------------
     'r'       open for reading (default)
     'w'       open for writing, truncating the file first
+    'x'       create a new file and open it for writing
     'a'       open for writing, appending to the end of the file if it exists
     'b'       binary mode
     't'       text mode (default)
@@ -63,7 +64,8 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
 
     The default mode is 'rt' (open for reading text). For binary random
     access, the mode 'w+b' opens and truncates the file to 0 bytes, while
-    'r+b' opens the file without truncation.
+    'r+b' opens the file without truncation. The 'x' mode implies 'w' and
+    raises an `FileExistsError` if the file already exists.
 
     Python distinguishes between files opened in binary and text modes,
     even when the underlying operating system doesn't. Files opened in
@@ -154,8 +156,9 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
     if errors is not None and not isinstance(errors, str):
         raise TypeError("invalid errors: %r" % errors)
     modes = set(mode)
-    if modes - set("arwb+tU") or len(mode) > len(modes):
+    if modes - set("axrwb+tU") or len(mode) > len(modes):
         raise ValueError("invalid mode: %r" % mode)
+    creating = "x" in modes
     reading = "r" in modes
     writing = "w" in modes
     appending = "a" in modes
@@ -163,14 +166,14 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
     text = "t" in modes
     binary = "b" in modes
     if "U" in modes:
-        if writing or appending:
+        if creating or writing or appending:
             raise ValueError("can't use U and writing mode at once")
         reading = True
     if text and binary:
         raise ValueError("can't have text and binary mode at once")
-    if reading + writing + appending > 1:
+    if creating + reading + writing + appending > 1:
         raise ValueError("can't have read/write/append mode at once")
-    if not (reading or writing or appending):
+    if not (creating or reading or writing or appending):
         raise ValueError("must have exactly one of read/write/append mode")
     if binary and encoding is not None:
         raise ValueError("binary mode doesn't take an encoding argument")
@@ -179,6 +182,7 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
     if binary and newline is not None:
         raise ValueError("binary mode doesn't take a newline argument")
     raw = FileIO(file,
+                 (creating and "x" or "") +
                  (reading and "r" or "") +
                  (writing and "w" or "") +
                  (appending and "a" or "") +
@@ -205,7 +209,7 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
         raise ValueError("can't have unbuffered text I/O")
     if updating:
         buffer = BufferedRandom(raw, buffering)
-    elif writing or appending:
+    elif creating or writing or appending:
         buffer = BufferedWriter(raw, buffering)
     elif reading:
         buffer = BufferedReader(raw, buffering)
index 5954999e6e9418f04a9d607cf0a77c5f995a1296..bea9395c2525e6f40ac38c0fdbeb1a9b5513756c 100644 (file)
@@ -2825,6 +2825,19 @@ class MiscIOTest(unittest.TestCase):
         self.assertTrue(wf.closed)
         self.assertTrue(rf.closed)
 
+    def test_create_fail(self):
+        # 'x' mode fails if file is existing
+        with self.open(support.TESTFN, 'w'):
+            pass
+        self.assertRaises(FileExistsError, self.open, support.TESTFN, 'x')
+
+    def test_create_writes(self):
+        # 'x' mode opens for writing
+        with self.open(support.TESTFN, 'xb') as f:
+            f.write(b"spam")
+        with self.open(support.TESTFN, 'rb') as f:
+            self.assertEqual(b"spam", f.read())
+
 class CMiscIOTest(MiscIOTest):
     io = io
 
index 4a7dd1116051d67a8666b32e1e2e607f211f6e2e..169262f6be9bd58df5483226dabf1a6ba39655e6 100644 (file)
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -996,6 +996,7 @@ Erik Tollerud
 Matias Torchinsky
 Sandro Tosi
 Richard Townsend
+David Townshend
 Laurence Tratt
 Matthias Troffaes
 John Tromp
index d8ffec45a7ee927f2b720a500961faf87864f918..77b232d286f913b561a74da11a96cc0c959a0264 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,6 +10,8 @@ What's New in Python 3.3 Alpha 1?
 Core and Builtins
 -----------------
 
+- Issue #12760: Add a create mode to open(). Patch by David Townshend.
+
 - Issue #13738: Simplify implementation of bytes.lower() and bytes.upper().
 
 - Issue #13577: Built-in methods and functions now have a __qualname__.
index 6e8d374fcf681c28c8123f32b145704b8b155015..f9bca1ab73d886ec675f8ed35a8fe50cd6b5a583 100644 (file)
@@ -108,18 +108,19 @@ PyDoc_STRVAR(open_doc,
 "mode is an optional string that specifies the mode in which the file\n"
 "is opened. It defaults to 'r' which means open for reading in text\n"
 "mode.  Other common values are 'w' for writing (truncating the file if\n"
-"it already exists), and 'a' for appending (which on some Unix systems,\n"
-"means that all writes append to the end of the file regardless of the\n"
-"current seek position). In text mode, if encoding is not specified the\n"
-"encoding used is platform dependent. (For reading and writing raw\n"
-"bytes use binary mode and leave encoding unspecified.) The available\n"
-"modes are:\n"
+"it already exists), 'x' for creating and writing to a new file, and\n"
+"'a' for appending (which on some Unix systems, means that all writes\n"
+"append to the end of the file regardless of the current seek position).\n"
+"In text mode, if encoding is not specified the encoding used is platform\n"
+"dependent. (For reading and writing raw bytes use binary mode and leave\n"
+"encoding unspecified.) The available modes are:\n"
 "\n"
 "========= ===============================================================\n"
 "Character Meaning\n"
 "--------- ---------------------------------------------------------------\n"
 "'r'       open for reading (default)\n"
 "'w'       open for writing, truncating the file first\n"
+"'x'       create a new file and open it for writing\n"
 "'a'       open for writing, appending to the end of the file if it exists\n"
 "'b'       binary mode\n"
 "'t'       text mode (default)\n"
@@ -130,7 +131,8 @@ PyDoc_STRVAR(open_doc,
 "\n"
 "The default mode is 'rt' (open for reading text). For binary random\n"
 "access, the mode 'w+b' opens and truncates the file to 0 bytes, while\n"
-"'r+b' opens the file without truncation.\n"
+"'r+b' opens the file without truncation. The 'x' mode implies 'w' and\n"
+"raises an `FileExistsError` if the file already exists.\n"
 "\n"
 "Python distinguishes between files opened in binary and text modes,\n"
 "even when the underlying operating system doesn't. Files opened in\n"
@@ -223,7 +225,7 @@ io_open(PyObject *self, PyObject *args, PyObject *kwds)
     char *encoding = NULL, *errors = NULL, *newline = NULL;
     unsigned i;
 
-    int reading = 0, writing = 0, appending = 0, updating = 0;
+    int creating = 0, reading = 0, writing = 0, appending = 0, updating = 0;
     int text = 0, binary = 0, universal = 0;
 
     char rawmode[5], *m;
@@ -254,6 +256,9 @@ io_open(PyObject *self, PyObject *args, PyObject *kwds)
         char c = mode[i];
 
         switch (c) {
+        case 'x':
+            creating = 1;
+            break;
         case 'r':
             reading = 1;
             break;
@@ -290,6 +295,7 @@ io_open(PyObject *self, PyObject *args, PyObject *kwds)
     }
 
     m = rawmode;
+    if (creating)  *(m++) = 'x';
     if (reading)   *(m++) = 'r';
     if (writing)   *(m++) = 'w';
     if (appending) *(m++) = 'a';
@@ -312,9 +318,9 @@ io_open(PyObject *self, PyObject *args, PyObject *kwds)
         return NULL;
     }
 
-    if (reading + writing + appending > 1) {
+    if (creating + reading + writing + appending > 1) {
         PyErr_SetString(PyExc_ValueError,
-                        "must have exactly one of read/write/append mode");
+                        "must have exactly one of create/read/write/append mode");
         return NULL;
     }
 
@@ -408,7 +414,7 @@ io_open(PyObject *self, PyObject *args, PyObject *kwds)
 
         if (updating)
             Buffered_class = (PyObject *)&PyBufferedRandom_Type;
-        else if (writing || appending)
+        else if (creating || writing || appending)
             Buffered_class = (PyObject *)&PyBufferedWriter_Type;
         else if (reading)
             Buffered_class = (PyObject *)&PyBufferedReader_Type;
index 3829e0bb3966cdedf7f46a5df7112501a5c089f3..57cd9aabe2ac562de4fdf5e6767d3e5ebef054e0 100644 (file)
@@ -46,6 +46,7 @@
 typedef struct {
     PyObject_HEAD
     int fd;
+    unsigned int created : 1;
     unsigned int readable : 1;
     unsigned int writable : 1;
     signed int seekable : 2; /* -1 means unknown */
@@ -152,6 +153,7 @@ fileio_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
     self = (fileio *) type->tp_alloc(type, 0);
     if (self != NULL) {
         self->fd = -1;
+        self->created = 0;
         self->readable = 0;
         self->writable = 0;
         self->seekable = -1;
@@ -290,15 +292,23 @@ fileio_init(PyObject *oself, PyObject *args, PyObject *kwds)
     s = mode;
     while (*s) {
         switch (*s++) {
-        case 'r':
+        case 'x':
             if (rwa) {
             bad_mode:
                 PyErr_SetString(PyExc_ValueError,
-                                "Must have exactly one of read/write/append "
+                                "Must have exactly one of create/read/write/append "
                                 "mode and at most one plus");
                 goto error;
             }
             rwa = 1;
+            self->created = 1;
+            self->writable = 1;
+            flags |= O_EXCL | O_CREAT;
+            break;
+        case 'r':
+            if (rwa)
+                goto bad_mode;
+            rwa = 1;
             self->readable = 1;
             break;
         case 'w':
@@ -988,6 +998,12 @@ fileio_truncate(fileio *self, PyObject *args)
 static char *
 mode_string(fileio *self)
 {
+    if (self->created) {
+        if (self->readable)
+            return "xb+";
+        else
+            return "xb";
+    }
     if (self->readable) {
         if (self->writable)
             return "rb+";
@@ -1049,15 +1065,17 @@ fileio_getstate(fileio *self)
 PyDoc_STRVAR(fileio_doc,
 "file(name: str[, mode: str][, opener: None]) -> file IO object\n"
 "\n"
-"Open a file.  The mode can be 'r', 'w' or 'a' for reading (default),\n"
-"writing or appending.  The file will be created if it doesn't exist\n"
-"when opened for writing or appending; it will be truncated when\n"
-"opened for writing.  Add a '+' to the mode to allow simultaneous\n"
-"reading and writing. A custom opener can be used by passing a\n"
-"callable as *opener*. The underlying file descriptor for the file\n"
+"Open a file.  The mode can be 'r', 'w', 'x' or 'a' for reading (default),\n"
+"writing, creating or appending.  The file will be created if it doesn't\n"
+"exist when opened for writing or appending; it will be truncated when\n"
+"opened for writing.  A `FileExistsError` will be raised if it already\n"
+"exists when opened for creating. Opening a file for creating implies\n"
+"writing so this mode behaves in a similar way to 'w'.Add a '+' to the mode\n"
+"to allow simultaneous reading and writing. A custom opener can be used by\n"
+"passing a callable as *opener*. The underlying file descriptor for the file\n"
 "object is then obtained by calling opener with (*name*, *flags*).\n"
-"*opener* must return an open file descriptor (passing os.open as\n"
-"*opener* results in functionality similar to passing None).");
+"*opener* must return an open file descriptor (passing os.open as *opener*\n"
+"results in functionality similar to passing None).");
 
 PyDoc_STRVAR(read_doc,
 "read(size: int) -> bytes.  read at most size bytes, returned as bytes.\n"