Commits

Anonymous committed 1d98fc2

Fix the rest of issue 1400, by introducing a proper implementation of
line buffering. The TextIOWrapper class no longer calls isatty() on
every write() call.

Comments (0)

Files changed (4)

                  closefd)
     if buffering is None:
         buffering = -1
-    if buffering < 0 and raw.isatty():
-        buffering = 1
+    line_buffering = False
+    if buffering == 1 or buffering < 0 and raw.isatty():
+        buffering = -1
+        line_buffering = True
     if buffering < 0:
         buffering = DEFAULT_BUFFER_SIZE
         try:
         buffer.name = file
         buffer.mode = mode
         return buffer
-    text = TextIOWrapper(buffer, encoding, errors, newline)
+    text = TextIOWrapper(buffer, encoding, errors, newline, line_buffering)
     text.name = file
     text.mode = mode
     return text
 
     _CHUNK_SIZE = 128
 
-    def __init__(self, buffer, encoding=None, errors=None, newline=None):
+    def __init__(self, buffer, encoding=None, errors=None, newline=None,
+                 line_buffering=False):
         if newline not in (None, "", "\n", "\r", "\r\n"):
             raise ValueError("illegal newline value: %r" % (newline,))
         if encoding is None:
                 raise ValueError("invalid errors: %r" % errors)
 
         self.buffer = buffer
+        self._line_buffering = line_buffering
         self._encoding = encoding
         self._errors = errors
         self._readuniversal = not newline
     def errors(self):
         return self._errors
 
+    @property
+    def line_buffering(self):
+        return self._line_buffering
+
     # A word about _snapshot.  This attribute is either None, or a
     # tuple (decoder_state, readahead, pending) where decoder_state is
     # the second (integer) item of the decoder state, readahead is the
             raise TypeError("can't write %s to text stream" %
                             s.__class__.__name__)
         length = len(s)
-        haslf = "\n" in s
+        haslf = (self._writetranslate or self._line_buffering) and "\n" in s
         if haslf and self._writetranslate and self._writenl != "\n":
             s = s.replace("\n", self._writenl)
         # XXX What if we were just reading?
         b = s.encode(self._encoding, self._errors)
         self.buffer.write(b)
-        if haslf and self.isatty():
+        if self._line_buffering and (haslf or "\r" in s):
             self.flush()
         self._snapshot = None
         if self._decoder:

Lib/test/output/test_cProfile

 
    ncalls  tottime  percall  cumtime  percall filename:lineno(function)
         1    0.000    0.000    1.000    1.000 <string>:1(<module>)
-        2    0.000    0.000    0.000    0.000 io.py:1193(flush)
-        1    0.000    0.000    0.000    0.000 io.py:257(flush)
-        1    0.000    0.000    0.000    0.000 io.py:644(closed)
-        1    0.000    0.000    0.000    0.000 io.py:862(flush)
+        2    0.000    0.000    0.000    0.000 io.py:1201(flush)
+        1    0.000    0.000    0.000    0.000 io.py:259(flush)
+        1    0.000    0.000    0.000    0.000 io.py:646(closed)
+        1    0.000    0.000    0.000    0.000 io.py:864(flush)
         8    0.064    0.008    0.080    0.010 test_cProfile.py:103(subhelper)
        28    0.028    0.001    0.028    0.001 test_cProfile.py:115(__getattr__)
         1    0.270    0.270    1.000    1.000 test_cProfile.py:30(testfunc)
 Function                                          called...
                                                       ncalls  tottime  cumtime
 <string>:1(<module>)                              ->       1    0.270    1.000  test_cProfile.py:30(testfunc)
-io.py:1193(flush)                                 ->       1    0.000    0.000  io.py:257(flush)
-                                                           1    0.000    0.000  io.py:862(flush)
-io.py:257(flush)                                  -> 
-io.py:644(closed)                                 -> 
-io.py:862(flush)                                  ->       1    0.000    0.000  io.py:644(closed)
+io.py:1201(flush)                                 ->       1    0.000    0.000  io.py:259(flush)
+                                                           1    0.000    0.000  io.py:864(flush)
+io.py:259(flush)                                  -> 
+io.py:646(closed)                                 -> 
+io.py:864(flush)                                  ->       1    0.000    0.000  io.py:646(closed)
 test_cProfile.py:103(subhelper)                   ->      16    0.016    0.016  test_cProfile.py:115(__getattr__)
 test_cProfile.py:115(__getattr__)                 -> 
 test_cProfile.py:30(testfunc)                     ->       1    0.014    0.130  test_cProfile.py:40(factorial)
 test_cProfile.py:93(helper2)                      ->       8    0.064    0.080  test_cProfile.py:103(subhelper)
                                                            8    0.000    0.008  {hasattr}
 {exec}                                            ->       1    0.000    1.000  <string>:1(<module>)
-                                                           2    0.000    0.000  io.py:1193(flush)
+                                                           2    0.000    0.000  io.py:1201(flush)
 {hasattr}                                         ->      12    0.012    0.012  test_cProfile.py:115(__getattr__)
 {method 'append' of 'list' objects}               -> 
 {method 'disable' of '_lsprof.Profiler' objects}  -> 
 Function                                          was called by...
                                                       ncalls  tottime  cumtime
 <string>:1(<module>)                              <-       1    0.000    1.000  {exec}
-io.py:1193(flush)                                 <-       2    0.000    0.000  {exec}
-io.py:257(flush)                                  <-       1    0.000    0.000  io.py:1193(flush)
-io.py:644(closed)                                 <-       1    0.000    0.000  io.py:862(flush)
-io.py:862(flush)                                  <-       1    0.000    0.000  io.py:1193(flush)
+io.py:1201(flush)                                 <-       2    0.000    0.000  {exec}
+io.py:259(flush)                                  <-       1    0.000    0.000  io.py:1201(flush)
+io.py:646(closed)                                 <-       1    0.000    0.000  io.py:864(flush)
+io.py:864(flush)                                  <-       1    0.000    0.000  io.py:1201(flush)
 test_cProfile.py:103(subhelper)                   <-       8    0.064    0.080  test_cProfile.py:93(helper2)
 test_cProfile.py:115(__getattr__)                 <-      16    0.016    0.016  test_cProfile.py:103(subhelper)
                                                           12    0.012    0.012  {hasattr}

Lib/test/output/test_profile

        12    0.000    0.000    0.012    0.001 :0(hasattr)
         1    0.000    0.000    0.000    0.000 :0(setprofile)
         1    0.000    0.000    1.000    1.000 <string>:1(<module>)
-        2    0.000    0.000    0.000    0.000 io.py:1193(flush)
-        1    0.000    0.000    0.000    0.000 io.py:257(flush)
-        1    0.000    0.000    0.000    0.000 io.py:644(closed)
-        1    0.000    0.000    0.000    0.000 io.py:862(flush)
+        2    0.000    0.000    0.000    0.000 io.py:1201(flush)
+        1    0.000    0.000    0.000    0.000 io.py:259(flush)
+        1    0.000    0.000    0.000    0.000 io.py:646(closed)
+        1    0.000    0.000    0.000    0.000 io.py:864(flush)
         0    0.000             0.000          profile:0(profiler)
         1    0.000    0.000    1.000    1.000 profile:0(testfunc())
         8    0.064    0.008    0.080    0.010 test_profile.py:103(subhelper)
 :0(append)                            -> 
 :0(exc_info)                          -> 
 :0(exec)                              -> <string>:1(<module>)(1)    1.000
-                                         io.py:1193(flush)(2)    0.000
+                                         io.py:1201(flush)(2)    0.000
 :0(hasattr)                           -> test_profile.py:115(__getattr__)(12)    0.028
 :0(setprofile)                        -> 
 <string>:1(<module>)                  -> test_profile.py:30(testfunc)(1)    1.000
-io.py:1193(flush)                     -> io.py:257(flush)(1)    0.000
-                                         io.py:862(flush)(1)    0.000
-io.py:257(flush)                      -> 
-io.py:644(closed)                     -> 
-io.py:862(flush)                      -> io.py:644(closed)(1)    0.000
+io.py:1201(flush)                     -> io.py:259(flush)(1)    0.000
+                                         io.py:864(flush)(1)    0.000
+io.py:259(flush)                      -> 
+io.py:646(closed)                     -> 
+io.py:864(flush)                      -> io.py:646(closed)(1)    0.000
 profile:0(profiler)                   -> profile:0(testfunc())(1)    1.000
 profile:0(testfunc())                 -> :0(exec)(1)    1.000
                                          :0(setprofile)(1)    0.000
                                          test_profile.py:93(helper2)(8)    0.400
 :0(setprofile)                        <- profile:0(testfunc())(1)    1.000
 <string>:1(<module>)                  <- :0(exec)(1)    1.000
-io.py:1193(flush)                     <- :0(exec)(2)    1.000
-io.py:257(flush)                      <- io.py:1193(flush)(1)    0.000
-io.py:644(closed)                     <- io.py:862(flush)(1)    0.000
-io.py:862(flush)                      <- io.py:1193(flush)(1)    0.000
+io.py:1201(flush)                     <- :0(exec)(2)    1.000
+io.py:259(flush)                      <- io.py:1201(flush)(1)    0.000
+io.py:646(closed)                     <- io.py:864(flush)(1)    0.000
+io.py:864(flush)                      <- io.py:1201(flush)(1)    0.000
 profile:0(profiler)                   <- 
 profile:0(testfunc())                 <- profile:0(profiler)(1)    0.000
 test_profile.py:103(subhelper)        <- test_profile.py:93(helper2)(8)    0.400

Lib/test/test_io.py

     def tearDown(self):
         test_support.unlink(test_support.TESTFN)
 
+    def testLineBuffering(self):
+        r = io.BytesIO()
+        b = io.BufferedWriter(r, 1000)
+        t = io.TextIOWrapper(b, newline="\n", line_buffering=True)
+        t.write("X")
+        self.assertEquals(r.getvalue(), b"")  # No flush happened
+        t.write("Y\nZ")
+        self.assertEquals(r.getvalue(), b"XY\nZ")  # All got flushed
+        t.write("A\rB")
+        self.assertEquals(r.getvalue(), b"XY\nZA\rB")
+
     def testEncodingErrorsReading(self):
         # (1) default
         b = io.BytesIO(b"abc\n\xff\n")
         self.assertRaises(UnicodeError, t.write, "\xff")
         # (3) ignore
         b = io.BytesIO()
-        t = io.TextIOWrapper(b, encoding="ascii", errors="ignore", newline="\n")
+        t = io.TextIOWrapper(b, encoding="ascii", errors="ignore",
+                             newline="\n")
         t.write("abc\xffdef\n")
         t.flush()
         self.assertEquals(b.getvalue(), b"abcdef\n")
         # (4) replace
         b = io.BytesIO()
-        t = io.TextIOWrapper(b, encoding="ascii", errors="replace", newline="\n")
+        t = io.TextIOWrapper(b, encoding="ascii", errors="replace",
+                             newline="\n")
         t.write("abc\xffdef\n")
         t.flush()
         self.assertEquals(b.getvalue(), b"abc?def\n")