# HG changeset patch # User David Townshend # Date 1313495946 -7200 # Node ID 492add6ce12660e6ad7c2bfa29acd0de78f3f4bd # Parent c821e3a54930c949b8612a3d6bad15059e4a7e43 'c' mode added to open and io.FileIO diff -r c821e3a54930 -r 492add6ce126 Doc/library/io.rst --- a/Doc/ACKS.txt Tue Aug 16 13:59:06 2011 +0200 +++ b/Doc/ACKS.txt Tue Aug 16 15:35:55 2011 +0200 @@ -205,6 +205,7 @@ * Jim Tittsler * David Turner * Sandro Tosi + * David Townshend * Ville Vainio * Nadeem Vawda * Martijn Vries --- a/Doc/library/io.rst Mon Aug 15 14:28:46 2011 +0300 +++ b/Doc/library/io.rst Tue Aug 16 13:59:06 2011 +0200 @@ -475,10 +475,13 @@ * 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 ``'c'``, ``'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. An IOError will be raise 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. diff -r c821e3a54930 -r 492add6ce126 Doc/library/os.rst --- a/Doc/library/os.rst Mon Aug 15 14:28:46 2011 +0300 +++ b/Doc/library/os.rst Tue Aug 16 13:59:06 2011 +0200 @@ -596,7 +596,8 @@ 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. + ``'c'``, ``'r'``, ``'w'``, 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 diff -r c821e3a54930 -r 492add6ce126 Lib/_pyio.py --- a/Lib/_pyio.py Mon Aug 15 14:28:46 2011 +0300 +++ b/Lib/_pyio.py Tue Aug 16 13:59:06 2011 +0200 @@ -48,7 +48,8 @@ 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 + mode. Other common values are 'c' for creating and writing to a new + file, '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 @@ -60,6 +61,7 @@ Character Meaning --------- --------------------------------------------------------------- 'r' open for reading (default) + 'c' create a new file and open it for writing 'w' open for writing, truncating the file first 'a' open for writing, appending to the end of the file if it exists 'b' binary mode @@ -71,7 +73,8 @@ 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 'c' mode implies 'w' and + raises an `IOError` 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 @@ -156,8 +159,9 @@ 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("acrwb+tU") or len(mode) > len(modes): raise ValueError("invalid mode: %r" % mode) + creating = "c" in modes reading = "r" in modes writing = "w" in modes appending = "a" in modes @@ -165,14 +169,14 @@ 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 + 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") @@ -181,6 +185,7 @@ if binary and newline is not None: raise ValueError("binary mode doesn't take a newline argument") raw = FileIO(file, + (creating and "c" or "") + (reading and "r" or "") + (writing and "w" or "") + (appending and "a" or "") + @@ -207,7 +212,7 @@ 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) diff -r c821e3a54930 -r 492add6ce126 Lib/test/test_fileio.py --- a/Lib/test/test_fileio.py Mon Aug 15 14:28:46 2011 +0300 +++ b/Lib/test/test_fileio.py Tue Aug 16 13:59:06 2011 +0200 @@ -398,6 +398,21 @@ self.assertRaises(ValueError, _FileIO, "/some/invalid/name", "rt") self.assertEqual(w.warnings, []) + def test_create_fail(self): + # 'c' mode fails if file is existing + with open(TESTFN, 'w'): + pass + try: + self.assertRaises(IOError, _FileIO, TESTFN, 'c') + finally: + os.unlink(TESTFN) + + def test_create_writes(self): + # 'c' mode opens for writing + with open(TESTFN, 'cb') as f: + f.write(b"spam") + with open(TESTFN, 'rb') as f: + self.assertEqual(b"spam", f.read()) def test_main(): # Historically, these tests have been sloppy about removing TESTFN. diff -r c821e3a54930 -r 492add6ce126 Modules/_io/_iomodule.c --- a/Modules/_io/_iomodule.c Mon Aug 15 14:28:46 2011 +0300 +++ b/Modules/_io/_iomodule.c Tue Aug 16 13:59:06 2011 +0200 @@ -190,7 +190,8 @@ "\n" "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" +"mode. Other common values are 'c' for creating and writing to a new \n" +"file, '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" @@ -202,6 +203,7 @@ "Character Meaning\n" "--------- ---------------------------------------------------------------\n" "'r' open for reading (default)\n" +"'c' create a new file and open it for writing\n" "'w' open for writing, truncating the file first\n" "'a' open for writing, appending to the end of the file if it exists\n" "'b' binary mode\n" @@ -213,7 +215,8 @@ "\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 'c' mode implies 'w' and\n" +"raises an `IOError` 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" @@ -300,7 +303,7 @@ 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; @@ -327,6 +330,9 @@ char c = mode[i]; switch (c) { + case 'c': + creating = 1; + break; case 'r': reading = 1; break; @@ -363,6 +369,7 @@ } m = rawmode; + if (creating) *(m++) = 'c'; if (reading) *(m++) = 'r'; if (writing) *(m++) = 'w'; if (appending) *(m++) = 'a'; @@ -385,9 +392,9 @@ 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; } @@ -481,7 +488,7 @@ 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; diff -r c821e3a54930 -r 492add6ce126 Modules/_io/fileio.c --- a/Modules/_io/fileio.c Mon Aug 15 14:28:46 2011 +0300 +++ b/Modules/_io/fileio.c Tue Aug 16 13:59:06 2011 +0200 @@ -52,6 +52,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 */ @@ -157,6 +158,7 @@ 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; @@ -292,15 +294,23 @@ s = mode; while (*s) { switch (*s++) { - case 'r': + case 'c': 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': @@ -474,6 +484,14 @@ } static PyObject * +fileio_created(fileio *self) +{ + if (self->fd < 0) + return err_closed(); + return PyBool_FromLong((long) self->created); +} + +static PyObject * fileio_readable(fileio *self) { if (self->fd < 0) @@ -963,6 +981,12 @@ static char * mode_string(fileio *self) { + if (self->created) { + if (self->readable) + return "cb+"; + else + return "cb"; + } if (self->readable) { if (self->writable) return "rb+"; @@ -1023,11 +1047,13 @@ PyDoc_STRVAR(fileio_doc, "file(name: str[, mode: str]) -> 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."); +"Open a file. The mode can be 'c', 'r', 'w' or 'a' for creating,\n" +"reading (default), writing or appending. The file will be created\n" +"if it doesn't exist when opened for writing or appending; it will be\n" +"truncated when opened for writing. An IOError will be raise if it\n" +"already exists when opened for creating. Opening a file for creating\n" +"implies writing, so this mode behaves in a similar way to 'w'. Add a\n" +"'+' to the mode to allow simultaneous reading and writing."); PyDoc_STRVAR(read_doc, "read(size: int) -> bytes. read at most size bytes, returned as bytes.\n" @@ -1090,6 +1116,9 @@ PyDoc_STRVAR(seekable_doc, "seekable() -> bool. True if file supports random-access."); +PyDoc_STRVAR(created_doc, +"created() -> bool. True if file was newly created when opened with 'c' mode"); + PyDoc_STRVAR(readable_doc, "readable() -> bool. True if file was opened in a read mode."); @@ -1108,6 +1137,7 @@ #endif {"close", (PyCFunction)fileio_close, METH_NOARGS, close_doc}, {"seekable", (PyCFunction)fileio_seekable, METH_NOARGS, seekable_doc}, + {"created", (PyCFunction)fileio_created, METH_NOARGS, created_doc}, {"readable", (PyCFunction)fileio_readable, METH_NOARGS, readable_doc}, {"writable", (PyCFunction)fileio_writable, METH_NOARGS, writable_doc}, {"fileno", (PyCFunction)fileio_fileno, METH_NOARGS, fileno_doc},