Skip to content

Commit c8a0f0c

Browse files
committed
Add support for buffering in text mode
- support line buffering - closes #549
1 parent 5a2d8f0 commit c8a0f0c

File tree

4 files changed

+117
-19
lines changed

4 files changed

+117
-19
lines changed

CHANGES.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ The released versions correspond to PyPi releases.
44
## Version 4.2.0 (as yet unreleased)
55

66
#### New Features
7-
* add support for the `buffering` parameter in `open` (text line mode not
8-
supported) (see [#549](../../issues/549))
7+
* add support for the `buffering` parameter in `open`
8+
(see [#549](../../issues/549))
99

1010
#### Fixes
1111
* do not truncate file on failed flush

docs/usage.rst

+1
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,7 @@ and you may fail to create new files if the fake file system is full.
540540
with open('/foo/bar.txt', 'w') as f:
541541
with self.assertRaises(OSError):
542542
f.write('a' * 200)
543+
f.flush()
543544
544545
To get the file system size, you may use ``get_disk_usage()``, which is
545546
modeled after ``shutil.disk_usage()``.

pyfakefs/fake_filesystem.py

+17-16
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@
139139
'r+': (True, True, True, False, False, False),
140140
'w+': (False, True, True, True, False, False),
141141
'a+': (False, True, True, False, True, False),
142-
'x': (False, False, True, False, False, True),
142+
'x': (False, False, True, False, False, True),
143143
'x+': (False, True, True, False, False, True)
144144
}
145145

@@ -1406,8 +1406,8 @@ def components_to_path():
14061406
path_components[len(normalized_components):])
14071407
sep = self._path_separator(path)
14081408
normalized_path = sep.join(normalized_components)
1409-
if (self._starts_with_sep(path) and not
1410-
self._starts_with_sep(normalized_path)):
1409+
if (self._starts_with_sep(path)
1410+
and not self._starts_with_sep(normalized_path)):
14111411
normalized_path = sep + normalized_path
14121412
return normalized_path
14131413

@@ -4501,7 +4501,7 @@ class FakeFileWrapper:
45014501
def __init__(self, file_object, file_path, update, read,
45024502
append, delete_on_close, filesystem,
45034503
newline, binary, closefd, encoding,
4504-
errors, buffer_size, raw_io, is_stream=False):
4504+
errors, buffering, raw_io, is_stream=False):
45054505
self.file_object = file_object
45064506
self.file_path = file_path
45074507
self._append = append
@@ -4511,10 +4511,16 @@ def __init__(self, file_object, file_path, update, read,
45114511
self._file_epoch = file_object.epoch
45124512
self.raw_io = raw_io
45134513
self._binary = binary
4514-
self._buffer_size = buffer_size
4515-
self._use_line_buffer = not binary and buffer_size == 1
45164514
self.is_stream = is_stream
45174515
self._changed = False
4516+
self._buffer_size = buffering
4517+
if self._buffer_size == 0 and not binary:
4518+
raise ValueError("can't have unbuffered text I/O")
4519+
# buffer_size is ignored in text mode
4520+
elif self._buffer_size == -1 or not binary:
4521+
self._buffer_size = io.DEFAULT_BUFFER_SIZE
4522+
self._use_line_buffer = not binary and buffering == 1
4523+
45184524
contents = file_object.byte_contents
45194525
self._encoding = encoding or locale.getpreferredencoding(False)
45204526
errors = errors or 'strict'
@@ -4796,8 +4802,10 @@ def write_wrapper(*args, **kwargs):
47964802
new_pos = self._io.tell()
47974803

47984804
# if the buffer size is exceeded, we flush
4799-
if new_pos - self._flush_pos > self._buffer_size:
4800-
flush_all = new_pos - old_pos > self._buffer_size
4805+
use_line_buf = self._use_line_buffer and '\n' in args[0]
4806+
if new_pos - self._flush_pos > self._buffer_size or use_line_buf:
4807+
flush_all = (new_pos - old_pos > self._buffer_size or
4808+
use_line_buf)
48014809
# if the current write does not exceed the buffer size,
48024810
# we revert to the previous position and flush that,
48034811
# otherwise we flush all
@@ -5079,13 +5087,6 @@ def call(self, file_, mode='r', buffering=-1, encoding=None,
50795087
if not filedes:
50805088
closefd = True
50815089

5082-
buffer_size = buffering
5083-
if buffer_size == -1:
5084-
buffer_size = io.DEFAULT_BUFFER_SIZE
5085-
elif buffer_size == 0:
5086-
if not binary:
5087-
raise ValueError("can't have unbuffered text I/O")
5088-
50895090
if (open_modes.must_not_exist and
50905091
(file_object or self.filesystem.islink(file_path) and
50915092
not self.filesystem.is_windows_fs)):
@@ -5122,7 +5123,7 @@ def call(self, file_, mode='r', buffering=-1, encoding=None,
51225123
closefd=closefd,
51235124
encoding=encoding,
51245125
errors=errors,
5125-
buffer_size=buffer_size,
5126+
buffering=buffering,
51265127
raw_io=self.raw_io)
51275128
if filedes is not None:
51285129
fakefile.filedes = filedes

pyfakefs/tests/fake_open_test.py

+97-1
Original file line numberDiff line numberDiff line change
@@ -925,7 +925,6 @@ def use_real_fs(self):
925925

926926

927927
class BufferingModeTest(FakeFileOpenTestBase):
928-
# todo: check text mode, check append mode
929928
def test_no_buffering(self):
930929
file_path = self.make_path("buffertest.bin")
931930
with self.open(file_path, 'wb', buffering=0) as f:
@@ -989,6 +988,103 @@ def test_writing_with_specific_buffer(self):
989988
# new buffer exceeded (600) -> all written
990989
self.assertEqual(1700, len(x))
991990

991+
def test_writing_text_with_line_buffer(self):
992+
file_path = self.make_path("buffertest.bin")
993+
with self.open(file_path, 'w', buffering=1) as f:
994+
f.write('test' * 100)
995+
with self.open(file_path, "r") as r:
996+
x = r.read()
997+
# no new line - not written
998+
self.assertEqual(0, len(x))
999+
f.write('\ntest')
1000+
with self.open(file_path, "r") as r:
1001+
x = r.read()
1002+
# new line - buffer written
1003+
self.assertEqual(405, len(x))
1004+
f.write('test' * 10)
1005+
with self.open(file_path, "r") as r:
1006+
x = r.read()
1007+
# buffer not filled - not written
1008+
self.assertEqual(405, len(x))
1009+
f.write('\ntest')
1010+
with self.open(file_path, "r") as r:
1011+
x = r.read()
1012+
# new line - buffer written
1013+
self.assertEqual(450, len(x))
1014+
1015+
def test_writing_large_text_with_line_buffer(self):
1016+
file_path = self.make_path("buffertest.bin")
1017+
with self.open(file_path, 'w', buffering=1) as f:
1018+
f.write('test' * 4000)
1019+
with self.open(file_path, "r") as r:
1020+
x = r.read()
1021+
# buffer larger than default - written
1022+
self.assertEqual(16000, len(x))
1023+
f.write('test')
1024+
with self.open(file_path, "r") as r:
1025+
x = r.read()
1026+
# buffer not filled - not written
1027+
self.assertEqual(16000, len(x))
1028+
f.write('\ntest')
1029+
with self.open(file_path, "r") as r:
1030+
x = r.read()
1031+
# new line - buffer written
1032+
self.assertEqual(16009, len(x))
1033+
f.write('\ntest')
1034+
with self.open(file_path, "r") as r:
1035+
x = r.read()
1036+
# another new line - buffer written
1037+
self.assertEqual(16014, len(x))
1038+
1039+
def test_writing_text_with_default_buffer(self):
1040+
file_path = self.make_path("buffertest.txt")
1041+
with self.open(file_path, 'w') as f:
1042+
f.write('test' * 5)
1043+
with self.open(file_path, "r") as r:
1044+
x = r.read()
1045+
# buffer not filled - not written
1046+
self.assertEqual(0, len(x))
1047+
f.write('\ntest')
1048+
with self.open(file_path, "r") as r:
1049+
x = r.read()
1050+
# buffer exceeded, but new buffer (400) not - previous written
1051+
self.assertEqual(0, len(x))
1052+
f.write('test' * 10)
1053+
with self.open(file_path, "r") as r:
1054+
x = r.read()
1055+
# buffer not filled - not written
1056+
self.assertEqual(0, len(x))
1057+
f.write('\ntest')
1058+
with self.open(file_path, "r") as r:
1059+
x = r.read()
1060+
self.assertEqual(0, len(x))
1061+
1062+
def test_writing_text_with_specific_buffer(self):
1063+
file_path = self.make_path("buffertest.txt")
1064+
with self.open(file_path, 'w', buffering=2) as f:
1065+
f.write('a' * 8000)
1066+
with self.open(file_path, "r") as r:
1067+
x = r.read()
1068+
# buffer not filled - not written
1069+
self.assertEqual(0, len(x))
1070+
f.write('test')
1071+
with self.open(file_path, "r") as r:
1072+
x = r.read()
1073+
# buffer exceeded, but new buffer (400) not - previous written
1074+
self.assertEqual(0, len(x))
1075+
f.write('test')
1076+
with self.open(file_path, "r") as r:
1077+
x = r.read()
1078+
# buffer not filled - not written
1079+
self.assertEqual(0, len(x))
1080+
f.write('test')
1081+
with self.open(file_path, "r") as r:
1082+
x = r.read()
1083+
self.assertEqual(0, len(x))
1084+
# with self.open(file_path, "r") as r:
1085+
# x = r.read()
1086+
# self.assertEqual(35, len(x))
1087+
9921088
def test_append_with_specific_buffer(self):
9931089
file_path = self.make_path("buffertest.bin")
9941090
with self.open(file_path, 'wb', buffering=512) as f:

0 commit comments

Comments
 (0)