Skip to content

Commit a16442b

Browse files
authored
Add open exclusive mode (#1071)
* Add open exclusive mode * Enable test_gzip * Enable test_bz2 cases which require x mode * Fix test_file * Disable tests on Mono * Fix IOException to OSError conversion in Mono
1 parent 6934c96 commit a16442b

7 files changed

Lines changed: 43 additions & 32 deletions

File tree

Src/IronPython/Modules/_bytesio.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,8 @@ public override BigInteger write(CodeContext/*!*/ context, [NotNull] object byte
334334
throw PythonOps.TypeError("a bytes-like object is required, not '{0}'", PythonTypeOps.GetName(bytes));
335335
}
336336

337-
public BigInteger write(CodeContext/*!*/ context, [NotNull] IBufferProtocol bytes) {
337+
// TODO: get rid of virtual? see https://github.com/IronLanguages/ironpython3/issues/1070
338+
public virtual BigInteger write(CodeContext/*!*/ context, [NotNull] IBufferProtocol bytes) {
338339
_checkClosed();
339340
return DoWrite(bytes);
340341
}

Src/IronPython/Modules/_fileio.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ public FileIO(CodeContext/*!*/ context, string name, string mode = "r", bool clo
113113
case "wb":
114114
_readStream = _writeStream = OpenFile(context, pal, name, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
115115
break;
116+
case "xb":
117+
_readStream = _writeStream = OpenFile(context, pal, name, FileMode.CreateNew, FileAccess.Write, FileShare.ReadWrite);
118+
break;
116119
case "ab":
117120
_readStream = _writeStream = OpenFile(context, pal, name, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
118121
_readStream.Seek(0L, SeekOrigin.End);
@@ -123,6 +126,9 @@ public FileIO(CodeContext/*!*/ context, string name, string mode = "r", bool clo
123126
case "wb+":
124127
_readStream = _writeStream = OpenFile(context, pal, name, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
125128
break;
129+
case "xb+":
130+
_readStream = _writeStream = OpenFile(context, pal, name, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.ReadWrite);
131+
break;
126132
case "ab+":
127133
_readStream = OpenFile(context, pal, name, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
128134
_writeStream = OpenFile(context, pal, name, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
@@ -166,6 +172,9 @@ private static string NormalizeMode(string mode, out int flags) {
166172
case "a":
167173
flags = O_APPEND | O_CREAT;
168174
return "ab";
175+
case "x":
176+
flags = O_CREAT | O_EXCL;
177+
return "xb";
169178
case "r+":
170179
case "+r":
171180
flags = O_RDWR;
@@ -178,13 +187,18 @@ private static string NormalizeMode(string mode, out int flags) {
178187
case "+a":
179188
flags = O_APPEND | O_CREAT | O_RDWR;
180189
return "ab+";
190+
case "x+":
191+
case "+x":
192+
flags = O_CREAT | O_RDWR | O_EXCL;
193+
return "xb+";
181194
default:
182195
throw BadMode(mode);
183196
}
184197

185198
// remove all 'b's from mode string to simplify parsing
186199
static string StandardizeMode(string mode) {
187200
int index = mode.IndexOf('b');
201+
if (index == -1) return mode;
188202

189203
if (index == mode.Length - 1) {
190204
mode = mode.Substring(0, index);
@@ -208,15 +222,16 @@ static Exception BadMode(string mode) {
208222
case 'r':
209223
case 'w':
210224
case 'a':
225+
case 'x':
211226
if (foundMode) {
212-
return PythonOps.ValueError("Must have exactly one of read/write/append mode");
227+
return PythonOps.ValueError("Must have exactly one of create/read/write/append mode and at most one plus");
213228
} else {
214229
foundMode = true;
215230
continue;
216231
}
217232
case '+':
218233
if (foundPlus) {
219-
return PythonOps.ValueError("Must have exactly one of read/write/append mode");
234+
return PythonOps.ValueError("Must have exactly one of create/read/write/append mode and at most one plus");
220235
} else {
221236
foundPlus = true;
222237
continue;
@@ -229,7 +244,7 @@ static Exception BadMode(string mode) {
229244
}
230245
}
231246

232-
return PythonOps.ValueError("Must have exactly one of read/write/append mode");
247+
return PythonOps.ValueError("Must have exactly one of create/read/write/append mode and at most one plus");
233248
}
234249
}
235250

Src/IronPython/Modules/_io.cs

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2827,21 +2827,20 @@ public static _IOBase open(
28272827
bool updating = modes.Contains('+');
28282828
bool text = modes.Contains('t');
28292829
bool binary = modes.Contains('b');
2830+
bool creating = modes.Contains('x');
28302831
if (modes.Contains('U')) {
2831-
if (writing || appending) {
2832-
throw PythonOps.ValueError("can't use U and writing mode at once");
2832+
if (creating || writing || appending || updating) {
2833+
// error message from Python 3.6
2834+
throw PythonOps.ValueError("mode U cannot be combined with 'x', 'w', 'a', or '+'");
28332835
}
28342836
PythonOps.Warn(context, PythonExceptions.DeprecationWarning, "'U' mode is deprecated");
28352837
reading = true;
28362838
}
28372839
if (text && binary) {
28382840
throw PythonOps.ValueError("can't have text and binary mode at once");
28392841
}
2840-
if (reading && writing || reading && appending || writing && appending) {
2841-
throw PythonOps.ValueError("can't have read/write/append mode at once");
2842-
}
2843-
if (!(reading || writing || appending)) {
2844-
throw PythonOps.ValueError("must have exactly one of read/write/append mode");
2842+
if (Convert.ToInt32(creating) + Convert.ToInt32(reading) + Convert.ToInt32(writing) + Convert.ToInt32(appending) > 1) {
2843+
throw PythonOps.ValueError("must have exactly one of create/read/write/append mode");
28452844
}
28462845
if (binary && encoding != null) {
28472846
throw PythonOps.ValueError("binary mode doesn't take an encoding argument");
@@ -2851,15 +2850,14 @@ public static _IOBase open(
28512850
}
28522851

28532852
mode = reading ? "r" : "";
2854-
if (writing) {
2855-
mode += 'w';
2856-
}
2857-
if (appending) {
2858-
mode += 'a';
2859-
}
2860-
if (updating) {
2853+
if (creating)
2854+
mode += "x";
2855+
if (writing)
2856+
mode += "w";
2857+
if (appending)
2858+
mode += "a";
2859+
if (updating)
28612860
mode += '+';
2862-
}
28632861

28642862
if (buffering == 0 && !binary) throw PythonOps.ValueError("can't have unbuffered text I/O");
28652863

@@ -2883,7 +2881,7 @@ public static _IOBase open(
28832881
_BufferedIOBase buffer;
28842882
if (updating) {
28852883
buffer = BufferedRandom.Create(context, fio, buffering, null);
2886-
} else if (writing || appending) {
2884+
} else if (writing || appending || creating) {
28872885
buffer = BufferedWriter.Create(context, fio, buffering, null);
28882886
} else if (reading) {
28892887
buffer = BufferedReader.Create(context, fio, buffering);
@@ -3101,7 +3099,7 @@ public static PythonType BlockingIOError {
31013099

31023100
#region Private implementation details
31033101

3104-
private static HashSet<char> _validModes = MakeSet("abrtwU+");
3102+
private static HashSet<char> _validModes = MakeSet("abrtwxU+");
31053103

31063104
private static HashSet<char> MakeSet(string chars) {
31073105
HashSet<char> res = new HashSet<char>();

Src/IronPython/Runtime/Exceptions/PythonExceptions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -786,11 +786,11 @@ internal static object ToPython(System.Exception/*!*/ clrException) {
786786
int errorCode = clrException.HResult;
787787

788788
if ((errorCode & ~0xfff) == unchecked((int)0x80070000)) {
789-
errorCode &= 0xfff;
789+
errorCode = _OSError.WinErrorToErrno(errorCode & 0xfff);
790790
}
791791

792792
// TODO: can we get filename and such?
793-
return CreatePythonThrowable(OSError, errorCode, clrException.Message, null, errorCode);
793+
return CreatePythonThrowable(OSError, errorCode, clrException.Message);
794794
}
795795

796796
BaseException pyExcep;

Src/IronPythonTest/Cases/CPythonCasesManifest.ini

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -493,9 +493,6 @@ IsolationLevel=PROCESS # use app.config - https://docs.microsoft.com/en-us/dotne
493493
RunCondition=$(IS_POSIX)
494494
Reason=Only valid for Unix
495495

496-
[CPython.test_gzip]
497-
Ignore=true
498-
499496
[CPython.test_heapq]
500497
Ignore=true
501498
Reason=TypeError: __next__() takes exactly 1 argument (1 given) # https://github.com/IronLanguages/ironpython3/issues/547

Tests/test_bz2_stdlib.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,14 +97,14 @@ def load_tests(loader, standard_tests, pattern):
9797
suite.addTest(test.test_bz2.CompressDecompressTest('testDecompressToEmptyString'))
9898
suite.addTest(test.test_bz2.CompressDecompressTest('testDecompressTrailingJunk'))
9999
suite.addTest(test.test_bz2.OpenTest('test_bad_params'))
100-
#suite.addTest(test.test_bz2.OpenTest('test_binary_modes')) # ValueError: invalid mode: xb
100+
suite.addTest(test.test_bz2.OpenTest('test_binary_modes'))
101101
suite.addTest(test.test_bz2.OpenTest('test_encoding'))
102102
suite.addTest(test.test_bz2.OpenTest('test_encoding_error_handler'))
103103
suite.addTest(test.test_bz2.OpenTest('test_fileobj'))
104-
#suite.addTest(test.test_bz2.OpenTest('test_implicit_binary_modes')) # ValueError: invalid mode: xb
104+
suite.addTest(test.test_bz2.OpenTest('test_implicit_binary_modes'))
105105
suite.addTest(test.test_bz2.OpenTest('test_newline'))
106-
#suite.addTest(test.test_bz2.OpenTest('test_text_modes')) # ValueError: invalid mode: xb
107-
#suite.addTest(test.test_bz2.OpenTest('test_x_mode')) # ValueError: invalid mode: xb
106+
suite.addTest(test.test_bz2.OpenTest('test_text_modes'))
107+
suite.addTest(test.test_bz2.OpenTest('test_x_mode'))
108108
return suite
109109

110110
else:

Tests/test_file.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -601,13 +601,13 @@ def test_modes(self):
601601
with open(fname, 'w') as x:
602602
self.assertEqual(x.mode, 'w')
603603
# don't allow empty modes
604-
self.assertRaisesMessage(ValueError, 'must have exactly one of read/write/append mode' if is_cli else "Must have exactly one of create/read/write/append mode and at most one plus", open, 'abc', '')
604+
self.assertRaisesMessage(ValueError, "Must have exactly one of create/read/write/append mode and at most one plus", open, 'abc', '')
605605

606606
# mode must start with valid value
607607
self.assertRaisesMessage(ValueError, "invalid mode: 'p'", open, 'abc', 'p')
608608

609609
# allow anything w/ U but r and w
610-
err_msg = "mode U cannot be combined with 'x', 'w', 'a', or '+'" if sys.version_info >= (3,7) else "mode U cannot be combined with x', 'w', 'a', or '+'" if sys.version_info >= (3,6) else "can't use U and writing mode at once"
610+
err_msg = "mode U cannot be combined with 'x', 'w', 'a', or '+'" if is_cli or sys.version_info >= (3,7) else "mode U cannot be combined with x', 'w', 'a', or '+'" if sys.version_info >= (3,6) else "can't use U and writing mode at once"
611611
self.assertRaisesMessage(ValueError, err_msg, open, 'abc', 'Uw')
612612
self.assertRaisesMessage(ValueError, err_msg, open, 'abc', 'Ua')
613613
self.assertRaisesMessage(ValueError, err_msg, open, 'abc', 'Uw+')

0 commit comments

Comments
 (0)