Repository: sphinx-gallery/sphinx-gallery

Issue:
Add an "encoding" attribute to _LoggingTee?
Would it be possible to add an "encoding" property or parameter in the  _LoggingTee class? Its value could be "utf-8" for instance, or computed in set_std_and_reset_position.

For information, I am building a gallery with PyTorch Lightning-based scripts. This library (at least its version 1.9.5) uses "sys.stdout.encoding" to print the test results (in pytorch_lightning/loops/dataloader/evaluation_loop.py). If this function is run into the gallery wrapper, sys.stdout is a _LoggingTee which has no "encoding" attribute.

I am not very "proficient" in sys, but it seems that the encoding parameter is a property that is expected and not an eccentricity from the lightning.ai team.

Thank you very much in advance. I can add details if needed.

Discussion:
larsoner: Looks pretty straightforward, complete, and clean -- thanks @o-laurent !

Patch:
diff --git a/sphinx_gallery/gen_rst.py b/sphinx_gallery/gen_rst.py
index 43f94c2..040eea4 100644
--- a/sphinx_gallery/gen_rst.py
+++ b/sphinx_gallery/gen_rst.py
@@ -67,6 +67,10 @@ class _LoggingTee:
         self.logger_buffer = ''
         self.set_std_and_reset_position()
 
+        # For TextIO compatibility
+        self.closed = False
+        self.encoding = "utf-8"
+
     def set_std_and_reset_position(self):
         if not isinstance(sys.stdout, _LoggingTee):
             self.origs = (sys.stdout, sys.stderr)
@@ -107,14 +111,36 @@ class _LoggingTee:
             self.logger.verbose('%s', self.logger_buffer)
             self.logger_buffer = ''
 
-    # Needed to work with Abseil
+    # For TextIO compatibility
     def close(self):
         pass
 
-    # When called from a local terminal seaborn needs it in Python3
+    def fileno(self):
+        return self.output.fileno()
+
     def isatty(self):
         return self.output.isatty()
 
+    def readable(self):
+        return False
+
+    def seekable(self):
+        return False
+
+    def tell(self):
+        return self.output.tell()
+
+    def writable(self):
+        return True
+
+    @property
+    def errors(self):
+        return self.output.errors
+
+    @property
+    def newlines(self):
+        return self.output.newlines
+
     # When called in gen_rst, conveniently use context managing
     def __enter__(self):
         return self


Test patch:
diff --git a/sphinx_gallery/tests/test_gen_rst.py b/sphinx_gallery/tests/test_gen_rst.py
index 03b3bc5..d2459f0 100644
--- a/sphinx_gallery/tests/test_gen_rst.py
+++ b/sphinx_gallery/tests/test_gen_rst.py
@@ -6,6 +6,7 @@ Testing the rst files generator
 import ast
 import codecs
 import importlib
+import io
 import logging
 import tempfile
 import re
@@ -1043,6 +1044,19 @@ def test_multi_line(log_collector_wrap):
     assert tee.logger_buffer == 'third line'
 
 
+def test_textio_compat(log_collector_wrap):
+    _, _, tee, _ = log_collector_wrap
+    assert not tee.readable()
+    assert not tee.seekable()
+    assert tee.writable()
+
+
+def test_fileno(monkeypatch, log_collector_wrap):
+    _, _, tee, _ = log_collector_wrap
+    with pytest.raises(io.UnsupportedOperation):
+        tee.fileno()
+
+
 def test_isatty(monkeypatch, log_collector_wrap):
     _, _, tee, _ = log_collector_wrap
     assert not tee.isatty()
@@ -1050,6 +1064,22 @@ def test_isatty(monkeypatch, log_collector_wrap):
     assert tee.isatty()
 
 
+def test_tell(monkeypatch, log_collector_wrap):
+    _, _, tee, _ = log_collector_wrap
+    assert tee.tell() == tee.output.tell()
+    monkeypatch.setattr(tee.output, 'tell', lambda: 42)
+    assert tee.tell() == tee.output.tell()
+
+
+def test_errors(log_collector_wrap):
+    _, _, tee, _ = log_collector_wrap
+    assert tee.errors == tee.output.errors
+
+
+def test_newlines(log_collector_wrap):
+    _, _, tee, _ = log_collector_wrap
+    assert tee.newlines == tee.output.newlines
+
 # TODO: test that broken thumbnail does appear when needed
 # TODO: test that examples are executed after a no-plot and produce
 #       the correct image in the thumbnail