From 0588ae67d4313709d1b1699db2fcdaae6014a23f Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 5 Aug 2026 15:37:18 +0300 Subject: [PATCH 1/3] gh-69370: Fix "python -m inspect --details" for unencodable paths It failed with UnicodeEncodeError if the path of the module contains characters unencodable in the encoding of sys.stdout, e.g. undecodable bytes of a file name. Such characters are now escaped with backslashes, unless stdout uses an error handler which can handle them. Co-Authored-By: Claude Opus 5 (1M context) --- Lib/inspect.py | 5 +++++ Lib/test/test_inspect/test_inspect.py | 18 +++++++++++++++++- ...6-08-05-17-30-00.gh-issue-69370.inspCLI.rst | 4 ++++ 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-08-05-17-30-00.gh-issue-69370.inspCLI.rst diff --git a/Lib/inspect.py b/Lib/inspect.py index 2a14e43b66f2fac..e7cbc439418a6d4 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -3461,6 +3461,11 @@ def _main(): import argparse import importlib + # The printed text can contain characters unencodable in the encoding + # of stdout, e.g. undecodable bytes of a file name. + if getattr(sys.stdout, 'errors', None) == 'strict': + sys.stdout.reconfigure(errors='backslashreplace') + parser = argparse.ArgumentParser() parser.add_argument( 'object', diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 5153e5eb9a4ff8b..05ca9fd52b9c75c 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -40,7 +40,7 @@ from test.support import MISSING_C_DOCSTRINGS, ALWAYS_EQ from test.support import run_no_yield_async_fn, EqualToForwardRef from test.support.import_helper import DirsOnSysPath, ready_to_import -from test.support.os_helper import TESTFN, temp_cwd +from test.support.os_helper import TESTFN, TESTFN_UNDECODABLE, temp_cwd from test.support.script_helper import assert_python_ok, assert_python_failure, kill_python from test.support import has_subprocess_support from test import support @@ -6584,6 +6584,22 @@ def test_error_data(self): lines = err.decode().splitlines() self.assertEqual(lines, [self.NO_SOURCE_TARGET_ERROR]) + @unittest.skipUnless(TESTFN_UNDECODABLE, + 'requires undecodable file names') + def test_details_undecodable_path(self): + # gh-69370: the path of the module is not encodable in the encoding + # of stdout. + with temp_cwd() as test_dir: + subdir = os.path.join(os.fsencode(test_dir), TESTFN_UNDECODABLE) + os.mkdir(subdir) + with open(os.path.join(subdir, b'undecodable_mod.py'), 'w') as f: + f.write('"""Module docstring."""\n') + rc, out, err = assert_python_ok('-X', 'utf8=0', '-m', 'inspect', + '--details', 'undecodable_mod', + PYTHONPATH=os.fsdecode(subdir)) + self.assertIn(b'Target: undecodable_mod', out) + self.assertEqual(err, b'') + def test_details_option_with_package(self): module_name = 'unittest' module = importlib.import_module(module_name) diff --git a/Misc/NEWS.d/next/Library/2026-08-05-17-30-00.gh-issue-69370.inspCLI.rst b/Misc/NEWS.d/next/Library/2026-08-05-17-30-00.gh-issue-69370.inspCLI.rst new file mode 100644 index 000000000000000..411ea9153687288 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-05-17-30-00.gh-issue-69370.inspCLI.rst @@ -0,0 +1,4 @@ +``python -m inspect --details`` no longer fails with +:exc:`UnicodeEncodeError` if the path of the module contains characters +unencodable in the encoding of :data:`sys.stdout`. Such characters are now +escaped with backslashes. From 916efb581905812aee2bcc7531020ba9732386a1 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 5 Aug 2026 18:31:34 +0300 Subject: [PATCH 2/3] Skip the test if undecodable paths are not supported The file system can reject a name with undecodable bytes even if TESTFN_UNDECODABLE is not None (e.g. on macOS). Co-Authored-By: Claude Opus 5 (1M context) --- Lib/test/test_inspect/test_inspect.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 05ca9fd52b9c75c..7ded4b2b2971928 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -6591,7 +6591,10 @@ def test_details_undecodable_path(self): # of stdout. with temp_cwd() as test_dir: subdir = os.path.join(os.fsencode(test_dir), TESTFN_UNDECODABLE) - os.mkdir(subdir) + try: + os.mkdir(subdir) + except OSError: + self.skipTest('undecodable paths are not supported') with open(os.path.join(subdir, b'undecodable_mod.py'), 'w') as f: f.write('"""Module docstring."""\n') rc, out, err = assert_python_ok('-X', 'utf8=0', '-m', 'inspect', From 410ec156efab56bebd55a7f592a0039a3e02f1b2 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 5 Aug 2026 19:14:47 +0300 Subject: [PATCH 3/3] Always reconfigure stdout On Windows sys.stdout uses the surrogateescape error handler, which fails on lone surrogates produced by os.fsdecode(). Co-Authored-By: Claude Opus 5 (1M context) --- Lib/inspect.py | 3 +-- .../Library/2026-08-05-17-30-00.gh-issue-69370.inspCLI.rst | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/inspect.py b/Lib/inspect.py index e7cbc439418a6d4..d18d39ae74be9b7 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -3463,8 +3463,7 @@ def _main(): # The printed text can contain characters unencodable in the encoding # of stdout, e.g. undecodable bytes of a file name. - if getattr(sys.stdout, 'errors', None) == 'strict': - sys.stdout.reconfigure(errors='backslashreplace') + sys.stdout.reconfigure(errors='backslashreplace') parser = argparse.ArgumentParser() parser.add_argument( diff --git a/Misc/NEWS.d/next/Library/2026-08-05-17-30-00.gh-issue-69370.inspCLI.rst b/Misc/NEWS.d/next/Library/2026-08-05-17-30-00.gh-issue-69370.inspCLI.rst index 411ea9153687288..27c97f9bdf07b48 100644 --- a/Misc/NEWS.d/next/Library/2026-08-05-17-30-00.gh-issue-69370.inspCLI.rst +++ b/Misc/NEWS.d/next/Library/2026-08-05-17-30-00.gh-issue-69370.inspCLI.rst @@ -1,4 +1,4 @@ -``python -m inspect --details`` no longer fails with +:program:`python -m inspect --details` no longer fails with :exc:`UnicodeEncodeError` if the path of the module contains characters unencodable in the encoding of :data:`sys.stdout`. Such characters are now escaped with backslashes.