Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit dc3044c

Browse files
author
Charles-François Natali
committed
Issue #12760: Add a create mode to open(). Patch by David Townshend.
1 parent 8a9b9c7 commit dc3044c

9 files changed

Lines changed: 101 additions & 41 deletions

File tree

Doc/library/io.rst

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -471,10 +471,13 @@ Raw File I/O
471471
* an integer representing the number of an existing OS-level file descriptor
472472
to which the resulting :class:`FileIO` object will give access.
473473

474-
The *mode* can be ``'r'``, ``'w'`` or ``'a'`` for reading (default), writing,
475-
or appending. The file will be created if it doesn't exist when opened for
476-
writing or appending; it will be truncated when opened for writing. Add a
477-
``'+'`` to the mode to allow simultaneous reading and writing.
474+
The *mode* can be ``'r'``, ``'w'``, ``'x'`` or ``'a'`` for reading
475+
(default), writing, creating or appending. The file will be created if it
476+
doesn't exist when opened for writing or appending; it will be truncated
477+
when opened for writing. :exc:`FileExistsError` will be raised if it already
478+
exists when opened for creating. Opening a file for creating implies
479+
writing, so this mode behaves in a similar way to ``'w'``. Add a ``'+'`` to
480+
the mode to allow simultaneous reading and writing.
478481

479482
The :meth:`read` (when called with a positive argument), :meth:`readinto`
480483
and :meth:`write` methods on this class will only make one system call.
@@ -487,6 +490,7 @@ Raw File I/O
487490

488491
.. versionchanged:: 3.3
489492
The *opener* parameter was added.
493+
The ``'x'`` mode was added.
490494

491495
In addition to the attributes and methods from :class:`IOBase` and
492496
:class:`RawIOBase`, :class:`FileIO` provides the following data

Doc/library/os.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -591,14 +591,17 @@ These functions create new :term:`file objects <file object>`. (See also :func:`
591591
the built-in :func:`open` function.
592592

593593
When specified, the *mode* argument must start with one of the letters
594-
``'r'``, ``'w'``, or ``'a'``, otherwise a :exc:`ValueError` is raised.
594+
``'r'``, ``'w'``, ``'x'`` or ``'a'``, otherwise a :exc:`ValueError` is
595+
raised.
595596

596597
On Unix, when the *mode* argument starts with ``'a'``, the *O_APPEND* flag is
597598
set on the file descriptor (which the :c:func:`fdopen` implementation already
598599
does on most platforms).
599600

600601
Availability: Unix, Windows.
601602

603+
.. versionchanged:: 3.3
604+
The ``'x'`` mode was added.
602605

603606
.. _os-fd-ops:
604607

Doc/whatsnew/3.3.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,15 @@ parameter to control parameters of the secure channel.
408408
(Contributed by Sijin Joseph in :issue:`8808`)
409409

410410

411+
io
412+
--
413+
414+
The :func:`~io.open` function has a new ``'x'`` mode that can be used to create
415+
a new file, and raise a :exc:`FileExistsError` if the file already exists.
416+
417+
(Contributed by David Townshend in :issue:`12760`)
418+
419+
411420
lzma
412421
----
413422

Lib/_pyio.py

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -38,21 +38,22 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
3838
wrapped. (If a file descriptor is given, it is closed when the
3939
returned I/O object is closed, unless closefd is set to False.)
4040
41-
mode is an optional string that specifies the mode in which the file
42-
is opened. It defaults to 'r' which means open for reading in text
43-
mode. Other common values are 'w' for writing (truncating the file if
44-
it already exists), and 'a' for appending (which on some Unix systems,
45-
means that all writes append to the end of the file regardless of the
46-
current seek position). In text mode, if encoding is not specified the
47-
encoding used is platform dependent. (For reading and writing raw
48-
bytes use binary mode and leave encoding unspecified.) The available
49-
modes are:
41+
mode is an optional string that specifies the mode in which the file is
42+
opened. It defaults to 'r' which means open for reading in text mode. Other
43+
common values are 'w' for writing (truncating the file if it already
44+
exists), 'x' for creating and writing to a new file, and 'a' for appending
45+
(which on some Unix systems, means that all writes append to the end of the
46+
file regardless of the current seek position). In text mode, if encoding is
47+
not specified the encoding used is platform dependent. (For reading and
48+
writing raw bytes use binary mode and leave encoding unspecified.) The
49+
available modes are:
5050
5151
========= ===============================================================
5252
Character Meaning
5353
--------- ---------------------------------------------------------------
5454
'r' open for reading (default)
5555
'w' open for writing, truncating the file first
56+
'x' create a new file and open it for writing
5657
'a' open for writing, appending to the end of the file if it exists
5758
'b' binary mode
5859
't' text mode (default)
@@ -63,7 +64,8 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
6364
6465
The default mode is 'rt' (open for reading text). For binary random
6566
access, the mode 'w+b' opens and truncates the file to 0 bytes, while
66-
'r+b' opens the file without truncation.
67+
'r+b' opens the file without truncation. The 'x' mode implies 'w' and
68+
raises an `FileExistsError` if the file already exists.
6769
6870
Python distinguishes between files opened in binary and text modes,
6971
even when the underlying operating system doesn't. Files opened in
@@ -154,23 +156,24 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
154156
if errors is not None and not isinstance(errors, str):
155157
raise TypeError("invalid errors: %r" % errors)
156158
modes = set(mode)
157-
if modes - set("arwb+tU") or len(mode) > len(modes):
159+
if modes - set("axrwb+tU") or len(mode) > len(modes):
158160
raise ValueError("invalid mode: %r" % mode)
161+
creating = "x" in modes
159162
reading = "r" in modes
160163
writing = "w" in modes
161164
appending = "a" in modes
162165
updating = "+" in modes
163166
text = "t" in modes
164167
binary = "b" in modes
165168
if "U" in modes:
166-
if writing or appending:
169+
if creating or writing or appending:
167170
raise ValueError("can't use U and writing mode at once")
168171
reading = True
169172
if text and binary:
170173
raise ValueError("can't have text and binary mode at once")
171-
if reading + writing + appending > 1:
174+
if creating + reading + writing + appending > 1:
172175
raise ValueError("can't have read/write/append mode at once")
173-
if not (reading or writing or appending):
176+
if not (creating or reading or writing or appending):
174177
raise ValueError("must have exactly one of read/write/append mode")
175178
if binary and encoding is not None:
176179
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,
179182
if binary and newline is not None:
180183
raise ValueError("binary mode doesn't take a newline argument")
181184
raw = FileIO(file,
185+
(creating and "x" or "") +
182186
(reading and "r" or "") +
183187
(writing and "w" or "") +
184188
(appending and "a" or "") +
@@ -205,7 +209,7 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
205209
raise ValueError("can't have unbuffered text I/O")
206210
if updating:
207211
buffer = BufferedRandom(raw, buffering)
208-
elif writing or appending:
212+
elif creating or writing or appending:
209213
buffer = BufferedWriter(raw, buffering)
210214
elif reading:
211215
buffer = BufferedReader(raw, buffering)

Lib/test/test_io.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2825,6 +2825,19 @@ def _test_nonblock_pipe_write(self, bufsize):
28252825
self.assertTrue(wf.closed)
28262826
self.assertTrue(rf.closed)
28272827

2828+
def test_create_fail(self):
2829+
# 'x' mode fails if file is existing
2830+
with self.open(support.TESTFN, 'w'):
2831+
pass
2832+
self.assertRaises(FileExistsError, self.open, support.TESTFN, 'x')
2833+
2834+
def test_create_writes(self):
2835+
# 'x' mode opens for writing
2836+
with self.open(support.TESTFN, 'xb') as f:
2837+
f.write(b"spam")
2838+
with self.open(support.TESTFN, 'rb') as f:
2839+
self.assertEqual(b"spam", f.read())
2840+
28282841
class CMiscIOTest(MiscIOTest):
28292842
io = io
28302843

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -996,6 +996,7 @@ Erik Tollerud
996996
Matias Torchinsky
997997
Sandro Tosi
998998
Richard Townsend
999+
David Townshend
9991000
Laurence Tratt
10001001
Matthias Troffaes
10011002
John Tromp

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ What's New in Python 3.3 Alpha 1?
1010
Core and Builtins
1111
-----------------
1212

13+
- Issue #12760: Add a create mode to open(). Patch by David Townshend.
14+
1315
- Issue #13738: Simplify implementation of bytes.lower() and bytes.upper().
1416

1517
- Issue #13577: Built-in methods and functions now have a __qualname__.

Modules/_io/_iomodule.c

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -108,18 +108,19 @@ PyDoc_STRVAR(open_doc,
108108
"mode is an optional string that specifies the mode in which the file\n"
109109
"is opened. It defaults to 'r' which means open for reading in text\n"
110110
"mode. Other common values are 'w' for writing (truncating the file if\n"
111-
"it already exists), and 'a' for appending (which on some Unix systems,\n"
112-
"means that all writes append to the end of the file regardless of the\n"
113-
"current seek position). In text mode, if encoding is not specified the\n"
114-
"encoding used is platform dependent. (For reading and writing raw\n"
115-
"bytes use binary mode and leave encoding unspecified.) The available\n"
116-
"modes are:\n"
111+
"it already exists), 'x' for creating and writing to a new file, and\n"
112+
"'a' for appending (which on some Unix systems, means that all writes\n"
113+
"append to the end of the file regardless of the current seek position).\n"
114+
"In text mode, if encoding is not specified the encoding used is platform\n"
115+
"dependent. (For reading and writing raw bytes use binary mode and leave\n"
116+
"encoding unspecified.) The available modes are:\n"
117117
"\n"
118118
"========= ===============================================================\n"
119119
"Character Meaning\n"
120120
"--------- ---------------------------------------------------------------\n"
121121
"'r' open for reading (default)\n"
122122
"'w' open for writing, truncating the file first\n"
123+
"'x' create a new file and open it for writing\n"
123124
"'a' open for writing, appending to the end of the file if it exists\n"
124125
"'b' binary mode\n"
125126
"'t' text mode (default)\n"
@@ -130,7 +131,8 @@ PyDoc_STRVAR(open_doc,
130131
"\n"
131132
"The default mode is 'rt' (open for reading text). For binary random\n"
132133
"access, the mode 'w+b' opens and truncates the file to 0 bytes, while\n"
133-
"'r+b' opens the file without truncation.\n"
134+
"'r+b' opens the file without truncation. The 'x' mode implies 'w' and\n"
135+
"raises an `FileExistsError` if the file already exists.\n"
134136
"\n"
135137
"Python distinguishes between files opened in binary and text modes,\n"
136138
"even when the underlying operating system doesn't. Files opened in\n"
@@ -223,7 +225,7 @@ io_open(PyObject *self, PyObject *args, PyObject *kwds)
223225
char *encoding = NULL, *errors = NULL, *newline = NULL;
224226
unsigned i;
225227

226-
int reading = 0, writing = 0, appending = 0, updating = 0;
228+
int creating = 0, reading = 0, writing = 0, appending = 0, updating = 0;
227229
int text = 0, binary = 0, universal = 0;
228230

229231
char rawmode[5], *m;
@@ -254,6 +256,9 @@ io_open(PyObject *self, PyObject *args, PyObject *kwds)
254256
char c = mode[i];
255257

256258
switch (c) {
259+
case 'x':
260+
creating = 1;
261+
break;
257262
case 'r':
258263
reading = 1;
259264
break;
@@ -290,6 +295,7 @@ io_open(PyObject *self, PyObject *args, PyObject *kwds)
290295
}
291296

292297
m = rawmode;
298+
if (creating) *(m++) = 'x';
293299
if (reading) *(m++) = 'r';
294300
if (writing) *(m++) = 'w';
295301
if (appending) *(m++) = 'a';
@@ -312,9 +318,9 @@ io_open(PyObject *self, PyObject *args, PyObject *kwds)
312318
return NULL;
313319
}
314320

315-
if (reading + writing + appending > 1) {
321+
if (creating + reading + writing + appending > 1) {
316322
PyErr_SetString(PyExc_ValueError,
317-
"must have exactly one of read/write/append mode");
323+
"must have exactly one of create/read/write/append mode");
318324
return NULL;
319325
}
320326

@@ -408,7 +414,7 @@ io_open(PyObject *self, PyObject *args, PyObject *kwds)
408414

409415
if (updating)
410416
Buffered_class = (PyObject *)&PyBufferedRandom_Type;
411-
else if (writing || appending)
417+
else if (creating || writing || appending)
412418
Buffered_class = (PyObject *)&PyBufferedWriter_Type;
413419
else if (reading)
414420
Buffered_class = (PyObject *)&PyBufferedReader_Type;

Modules/_io/fileio.c

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
typedef struct {
4747
PyObject_HEAD
4848
int fd;
49+
unsigned int created : 1;
4950
unsigned int readable : 1;
5051
unsigned int writable : 1;
5152
signed int seekable : 2; /* -1 means unknown */
@@ -152,6 +153,7 @@ fileio_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
152153
self = (fileio *) type->tp_alloc(type, 0);
153154
if (self != NULL) {
154155
self->fd = -1;
156+
self->created = 0;
155157
self->readable = 0;
156158
self->writable = 0;
157159
self->seekable = -1;
@@ -290,15 +292,23 @@ fileio_init(PyObject *oself, PyObject *args, PyObject *kwds)
290292
s = mode;
291293
while (*s) {
292294
switch (*s++) {
293-
case 'r':
295+
case 'x':
294296
if (rwa) {
295297
bad_mode:
296298
PyErr_SetString(PyExc_ValueError,
297-
"Must have exactly one of read/write/append "
299+
"Must have exactly one of create/read/write/append "
298300
"mode and at most one plus");
299301
goto error;
300302
}
301303
rwa = 1;
304+
self->created = 1;
305+
self->writable = 1;
306+
flags |= O_EXCL | O_CREAT;
307+
break;
308+
case 'r':
309+
if (rwa)
310+
goto bad_mode;
311+
rwa = 1;
302312
self->readable = 1;
303313
break;
304314
case 'w':
@@ -988,6 +998,12 @@ fileio_truncate(fileio *self, PyObject *args)
988998
static char *
989999
mode_string(fileio *self)
9901000
{
1001+
if (self->created) {
1002+
if (self->readable)
1003+
return "xb+";
1004+
else
1005+
return "xb";
1006+
}
9911007
if (self->readable) {
9921008
if (self->writable)
9931009
return "rb+";
@@ -1049,15 +1065,17 @@ fileio_getstate(fileio *self)
10491065
PyDoc_STRVAR(fileio_doc,
10501066
"file(name: str[, mode: str][, opener: None]) -> file IO object\n"
10511067
"\n"
1052-
"Open a file. The mode can be 'r', 'w' or 'a' for reading (default),\n"
1053-
"writing or appending. The file will be created if it doesn't exist\n"
1054-
"when opened for writing or appending; it will be truncated when\n"
1055-
"opened for writing. Add a '+' to the mode to allow simultaneous\n"
1056-
"reading and writing. A custom opener can be used by passing a\n"
1057-
"callable as *opener*. The underlying file descriptor for the file\n"
1068+
"Open a file. The mode can be 'r', 'w', 'x' or 'a' for reading (default),\n"
1069+
"writing, creating or appending. The file will be created if it doesn't\n"
1070+
"exist when opened for writing or appending; it will be truncated when\n"
1071+
"opened for writing. A `FileExistsError` will be raised if it already\n"
1072+
"exists when opened for creating. Opening a file for creating implies\n"
1073+
"writing so this mode behaves in a similar way to 'w'.Add a '+' to the mode\n"
1074+
"to allow simultaneous reading and writing. A custom opener can be used by\n"
1075+
"passing a callable as *opener*. The underlying file descriptor for the file\n"
10581076
"object is then obtained by calling opener with (*name*, *flags*).\n"
1059-
"*opener* must return an open file descriptor (passing os.open as\n"
1060-
"*opener* results in functionality similar to passing None).");
1077+
"*opener* must return an open file descriptor (passing os.open as *opener*\n"
1078+
"results in functionality similar to passing None).");
10611079

10621080
PyDoc_STRVAR(read_doc,
10631081
"read(size: int) -> bytes. read at most size bytes, returned as bytes.\n"

0 commit comments

Comments
 (0)