Skip to content

Commit 8d7fb40

Browse files
Issue #23374: Fixed pydoc failure with non-ASCII files when stdout encoding
differs from file system encoding (e.g. on Mac OS).
1 parent 9c85b07 commit 8d7fb40

3 files changed

Lines changed: 25 additions & 9 deletions

File tree

Lib/pydoc.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1407,9 +1407,6 @@ def bold(self, text):
14071407
def pager(text):
14081408
"""The first time this is called, determine what kind of pager to use."""
14091409
global pager
1410-
# Escape non-encodable characters to avoid encoding errors later
1411-
encoding = sys.getfilesystemencoding()
1412-
text = text.encode(encoding, 'backslashreplace').decode(encoding)
14131410
pager = getpager()
14141411
pager(text)
14151412

@@ -1452,27 +1449,34 @@ def plain(text):
14521449

14531450
def pipepager(text, cmd):
14541451
"""Page through text by feeding it to another program."""
1455-
pipe = os.popen(cmd, 'w')
1452+
import subprocess
1453+
proc = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE)
14561454
try:
1457-
pipe.write(text)
1458-
pipe.close()
1455+
with proc:
1456+
with io.TextIOWrapper(proc.stdin, errors='backslashreplace') as pipe:
1457+
pipe.write(text)
14591458
except OSError:
14601459
pass # Ignore broken pipes caused by quitting the pager program.
14611460

14621461
def tempfilepager(text, cmd):
14631462
"""Page through text by invoking a program on a temporary file."""
14641463
import tempfile
14651464
filename = tempfile.mktemp()
1466-
with open(filename, 'w') as file:
1465+
with open(filename, 'w', errors='backslashreplace') as file:
14671466
file.write(text)
14681467
try:
14691468
os.system(cmd + ' "' + filename + '"')
14701469
finally:
14711470
os.unlink(filename)
14721471

1472+
def _escape_stdout(text):
1473+
# Escape non-encodable characters to avoid encoding errors later
1474+
encoding = getattr(sys.stdout, 'encoding', None) or 'utf-8'
1475+
return text.encode(encoding, 'backslashreplace').decode(encoding)
1476+
14731477
def ttypager(text):
14741478
"""Page through text on a text terminal."""
1475-
lines = plain(text).split('\n')
1479+
lines = plain(_escape_stdout(text)).split('\n')
14761480
try:
14771481
import tty
14781482
fd = sys.stdin.fileno()
@@ -1516,7 +1520,7 @@ def ttypager(text):
15161520

15171521
def plainpager(text):
15181522
"""Simply print unformatted text. This is the ultimate fallback."""
1519-
sys.stdout.write(plain(text))
1523+
sys.stdout.write(plain(_escape_stdout(text)))
15201524

15211525
def describe(thing):
15221526
"""Produce a short description of the given thing."""

Lib/test/test_pydoc.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@
3535
except ImportError:
3636
threading = None
3737

38+
class nonascii:
39+
'Це не латиниця'
40+
pass
41+
3842
if test.support.HAVE_DOCSTRINGS:
3943
expected_data_docstrings = (
4044
'dictionary for instance variables (if defined)',
@@ -474,6 +478,11 @@ def test_not_here(self):
474478
self.assertEqual(expected, result,
475479
"documentation for missing module found")
476480

481+
def test_not_ascii(self):
482+
result = run_pydoc('test.test_pydoc.nonascii', PYTHONIOENCODING='ascii')
483+
encoded = nonascii.__doc__.encode('ascii', 'backslashreplace')
484+
self.assertIn(encoded, result)
485+
477486
def test_input_strip(self):
478487
missing_module = " test.i_am_not_here "
479488
result = str(run_pydoc(missing_module), 'ascii')

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ Core and Builtins
1313
Library
1414
-------
1515

16+
- Issue #23374: Fixed pydoc failure with non-ASCII files when stdout encoding
17+
differs from file system encoding (e.g. on Mac OS).
18+
1619
- Issue #23481: Remove RC4 from the SSL module's default cipher list.
1720

1821
- Issue #21548: Fix pydoc.synopsis() and pydoc.apropos() on modules with empty

0 commit comments

Comments
 (0)