From c87667b27eb8f475582506072cf086021de930ff Mon Sep 17 00:00:00 2001 From: htjworld <116538001+htjworld@users.noreply.github.com> Date: Mon, 11 May 2026 23:39:03 +0900 Subject: [PATCH 1/7] gh-149682: Fix mimetypes CLI to write error messages to stderr --- Doc/library/mimetypes.rst | 22 ++++---- Lib/mimetypes.py | 10 +++- Lib/test/test_mimetypes.py | 55 ++++++++++++++++++- ...-05-11-23-38-47.gh-issue-149682.UE08R4.rst | 2 + 4 files changed, 74 insertions(+), 15 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-05-11-23-38-47.gh-issue-149682.UE08R4.rst diff --git a/Doc/library/mimetypes.rst b/Doc/library/mimetypes.rst index 0facacd50fd389..9d022d30cafaa1 100644 --- a/Doc/library/mimetypes.rst +++ b/Doc/library/mimetypes.rst @@ -373,19 +373,15 @@ interface: $ python -m mimetypes filename.tar.gz type: application/x-tar encoding: gzip - $ # get a MIME type for a rare file extension - $ python -m mimetypes filename.pict - error: unknown extension of filename.pict - - $ # now look in the extended database built into Python - $ python -m mimetypes --lenient filename.pict - type: image/pict encoding: None + $ # get a MIME type for an unknown file extension + $ python -m mimetypes filename.zzz + error: media type unknown for filename.zzz $ # get a file extension by a MIME type $ python -m mimetypes --extension text/javascript .js - $ # get a file extension by a rare MIME type + $ # get a file extension by a rare MIME type (error goes to stderr) $ python -m mimetypes --extension text/xul error: unknown type text/xul @@ -393,14 +389,16 @@ interface: $ python -m mimetypes --extension --lenient text/xul .xul - $ # try to feed an unknown file extension - $ python -m mimetypes filename.sh filename.nc filename.xxx filename.txt + $ # try to feed an unknown file extension (error goes to stderr) + $ python -m mimetypes filename.sh filename.nc filename.zzz filename.txt type: application/x-sh encoding: None type: application/x-netcdf encoding: None - error: unknown extension of filename.xxx + error: media type unknown for filename.zzz + type: text/plain encoding: None - $ # try to feed an unknown MIME type + $ # try to feed an unknown MIME type (error goes to stderr) $ python -m mimetypes --extension audio/aac audio/opus audio/future audio/x-wav .aac .opus error: unknown type audio/future + .wav diff --git a/Lib/mimetypes.py b/Lib/mimetypes.py index 6d9278bccf927e..b1c6e60597d4c3 100644 --- a/Lib/mimetypes.py +++ b/Lib/mimetypes.py @@ -756,5 +756,11 @@ def _main(args=None): import sys results = _main() - print("\n".join(results)) - sys.exit(any(result.startswith("error: ") for result in results)) + has_error = False + for result in results: + if result.startswith("error: "): + print(result, file=sys.stderr, flush=True) + has_error = True + else: + print(result, flush=True) + sys.exit(has_error) diff --git a/Lib/test/test_mimetypes.py b/Lib/test/test_mimetypes.py index 2d618081521e10..d3d96f5a74a9ed 100644 --- a/Lib/test/test_mimetypes.py +++ b/Lib/test/test_mimetypes.py @@ -6,8 +6,9 @@ import unittest.mock from platform import win32_edition from test import support -from test.support import cpython_only, force_not_colorized, os_helper +from test.support import cpython_only, force_not_colorized, os_helper, requires_subprocess from test.support.import_helper import ensure_lazy_imports +from test.support.script_helper import assert_python_ok, assert_python_failure try: import _winapi @@ -508,5 +509,57 @@ def test_invocation_error(self): self.assertEqual(result, expected) +@requires_subprocess() +class CommandLineSubprocessTest(unittest.TestCase): + def test_help(self): + rc, stdout, stderr = assert_python_ok('-m', 'mimetypes', '--help') + self.assertIn(b'mimetypes', stdout) + self.assertIn(b'--extension', stdout) + self.assertIn(b'--lenient', stdout) + + def test_type_lookup(self): + rc, stdout, stderr = assert_python_ok('-m', 'mimetypes', 'foo.pdf') + self.assertIn(b'application/pdf', stdout) + self.assertEqual(stderr, b'') + + def test_type_lookup_unknown(self): + rc, stdout, stderr = assert_python_failure('-m', 'mimetypes', 'foo.unknownext12345') + self.assertEqual(stdout, b'') + self.assertIn(b'error:', stderr) + + def test_extension_flag(self): + rc, stdout, stderr = assert_python_ok('-m', 'mimetypes', '-e', 'image/jpeg') + self.assertIn(b'.jpg', stdout) + self.assertEqual(stderr, b'') + + def test_extension_flag_unknown(self): + rc, stdout, stderr = assert_python_failure('-m', 'mimetypes', '-e', 'image/unknowntype12345') + self.assertEqual(stdout, b'') + self.assertIn(b'error:', stderr) + + def test_lenient_flag(self): + rc, stdout, stderr = assert_python_ok('-m', 'mimetypes', '-e', '--lenient', 'text/xul') + self.assertIn(b'.xul', stdout) + self.assertEqual(stderr, b'') + + def test_multiple_inputs(self): + rc, stdout, stderr = assert_python_ok('-m', 'mimetypes', 'foo.pdf', 'foo.png') + self.assertIn(b'application/pdf', stdout) + self.assertIn(b'image/png', stdout) + self.assertEqual(stderr, b'') + + def test_multiple_inputs_with_error(self): + rc, stdout, stderr = assert_python_failure( + '-m', 'mimetypes', 'foo.pdf', 'foo.unknownext12345' + ) + self.assertIn(b'application/pdf', stdout) + self.assertNotIn(b'error:', stdout) + self.assertIn(b'error:', stderr) + + def test_unknown_flag(self): + rc, stdout, stderr = assert_python_failure('-m', 'mimetypes', '--unknown-flag') + self.assertNotEqual(rc, 0) + + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Library/2026-05-11-23-38-47.gh-issue-149682.UE08R4.rst b/Misc/NEWS.d/next/Library/2026-05-11-23-38-47.gh-issue-149682.UE08R4.rst new file mode 100644 index 00000000000000..37fc1a411e1eae --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-05-11-23-38-47.gh-issue-149682.UE08R4.rst @@ -0,0 +1,2 @@ +Fix :mod:`mimetypes` CLI to write error messages to stderr instead of stdout, +and update the documentation examples to reflect the correct behavior. From 77e5b2a473473a9d49f251107cdae66710c1536e Mon Sep 17 00:00:00 2001 From: htjworld <116538001+htjworld@users.noreply.github.com> Date: Mon, 11 May 2026 23:42:01 +0900 Subject: [PATCH 2/7] Remove inline comments from docs examples --- Doc/library/mimetypes.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/mimetypes.rst b/Doc/library/mimetypes.rst index 9d022d30cafaa1..ad5d8ad1afc316 100644 --- a/Doc/library/mimetypes.rst +++ b/Doc/library/mimetypes.rst @@ -381,7 +381,7 @@ interface: $ python -m mimetypes --extension text/javascript .js - $ # get a file extension by a rare MIME type (error goes to stderr) + $ # get a file extension by a rare MIME type $ python -m mimetypes --extension text/xul error: unknown type text/xul @@ -389,14 +389,14 @@ interface: $ python -m mimetypes --extension --lenient text/xul .xul - $ # try to feed an unknown file extension (error goes to stderr) + $ # try to feed an unknown file extension $ python -m mimetypes filename.sh filename.nc filename.zzz filename.txt type: application/x-sh encoding: None type: application/x-netcdf encoding: None error: media type unknown for filename.zzz type: text/plain encoding: None - $ # try to feed an unknown MIME type (error goes to stderr) + $ # try to feed an unknown MIME type $ python -m mimetypes --extension audio/aac audio/opus audio/future audio/x-wav .aac .opus From 5b75aaabfc504b89446f5d74523471d470af4e02 Mon Sep 17 00:00:00 2001 From: htjworld <116538001+htjworld@users.noreply.github.com> Date: Tue, 12 May 2026 00:33:22 +0900 Subject: [PATCH 3/7] Address review feedback: revert stderr routing, update docs and tests Per sobolevn's review: revert the behavior change that routed error messages to stderr, as existing code may rely on stdout behavior. Instead update docs to accurately reflect that errors go to stdout. Also update tests to assert full output contents as suggested. --- Doc/library/mimetypes.rst | 2 +- Lib/mimetypes.py | 10 ++-------- Lib/test/test_mimetypes.py | 20 +++++++++---------- ...-05-11-23-38-47.gh-issue-149682.UE08R4.rst | 2 -- 4 files changed, 13 insertions(+), 21 deletions(-) delete mode 100644 Misc/NEWS.d/next/Library/2026-05-11-23-38-47.gh-issue-149682.UE08R4.rst diff --git a/Doc/library/mimetypes.rst b/Doc/library/mimetypes.rst index ad5d8ad1afc316..9722233c45a3eb 100644 --- a/Doc/library/mimetypes.rst +++ b/Doc/library/mimetypes.rst @@ -348,7 +348,7 @@ it converts file extensions to MIME types. For each ``type`` entry, the script writes a line into the standard output stream. If an unknown type occurs, it writes an error message into the -standard error stream and exits with the return code ``1``. +standard output stream and exits with the return code ``1``. .. mimetypes-cli-example: diff --git a/Lib/mimetypes.py b/Lib/mimetypes.py index b1c6e60597d4c3..6d9278bccf927e 100644 --- a/Lib/mimetypes.py +++ b/Lib/mimetypes.py @@ -756,11 +756,5 @@ def _main(args=None): import sys results = _main() - has_error = False - for result in results: - if result.startswith("error: "): - print(result, file=sys.stderr, flush=True) - has_error = True - else: - print(result, flush=True) - sys.exit(has_error) + print("\n".join(results)) + sys.exit(any(result.startswith("error: ") for result in results)) diff --git a/Lib/test/test_mimetypes.py b/Lib/test/test_mimetypes.py index d3d96f5a74a9ed..576589a6a5b6dd 100644 --- a/Lib/test/test_mimetypes.py +++ b/Lib/test/test_mimetypes.py @@ -519,13 +519,13 @@ def test_help(self): def test_type_lookup(self): rc, stdout, stderr = assert_python_ok('-m', 'mimetypes', 'foo.pdf') - self.assertIn(b'application/pdf', stdout) + self.assertEqual(stdout.strip(), b'type: application/pdf encoding: None') self.assertEqual(stderr, b'') def test_type_lookup_unknown(self): rc, stdout, stderr = assert_python_failure('-m', 'mimetypes', 'foo.unknownext12345') - self.assertEqual(stdout, b'') - self.assertIn(b'error:', stderr) + self.assertEqual(stdout.strip(), b'error: media type unknown for foo.unknownext12345') + self.assertEqual(stderr, b'') def test_extension_flag(self): rc, stdout, stderr = assert_python_ok('-m', 'mimetypes', '-e', 'image/jpeg') @@ -534,8 +534,8 @@ def test_extension_flag(self): def test_extension_flag_unknown(self): rc, stdout, stderr = assert_python_failure('-m', 'mimetypes', '-e', 'image/unknowntype12345') - self.assertEqual(stdout, b'') - self.assertIn(b'error:', stderr) + self.assertEqual(stdout.strip(), b'error: unknown type image/unknowntype12345') + self.assertEqual(stderr, b'') def test_lenient_flag(self): rc, stdout, stderr = assert_python_ok('-m', 'mimetypes', '-e', '--lenient', 'text/xul') @@ -544,17 +544,17 @@ def test_lenient_flag(self): def test_multiple_inputs(self): rc, stdout, stderr = assert_python_ok('-m', 'mimetypes', 'foo.pdf', 'foo.png') - self.assertIn(b'application/pdf', stdout) - self.assertIn(b'image/png', stdout) + self.assertIn(b'type: application/pdf encoding: None', stdout) + self.assertIn(b'type: image/png encoding: None', stdout) self.assertEqual(stderr, b'') def test_multiple_inputs_with_error(self): rc, stdout, stderr = assert_python_failure( '-m', 'mimetypes', 'foo.pdf', 'foo.unknownext12345' ) - self.assertIn(b'application/pdf', stdout) - self.assertNotIn(b'error:', stdout) - self.assertIn(b'error:', stderr) + self.assertIn(b'type: application/pdf encoding: None', stdout) + self.assertIn(b'error: media type unknown for foo.unknownext12345', stdout) + self.assertEqual(stderr, b'') def test_unknown_flag(self): rc, stdout, stderr = assert_python_failure('-m', 'mimetypes', '--unknown-flag') diff --git a/Misc/NEWS.d/next/Library/2026-05-11-23-38-47.gh-issue-149682.UE08R4.rst b/Misc/NEWS.d/next/Library/2026-05-11-23-38-47.gh-issue-149682.UE08R4.rst deleted file mode 100644 index 37fc1a411e1eae..00000000000000 --- a/Misc/NEWS.d/next/Library/2026-05-11-23-38-47.gh-issue-149682.UE08R4.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix :mod:`mimetypes` CLI to write error messages to stderr instead of stdout, -and update the documentation examples to reflect the correct behavior. From ad605cd6dce268ec82e5428ce7fb8b7f2d5134a3 Mon Sep 17 00:00:00 2001 From: htjworld <116538001+htjworld@users.noreply.github.com> Date: Tue, 12 May 2026 00:35:27 +0900 Subject: [PATCH 4/7] Add NEWS entry for mimetypes CLI tests --- .../next/Tests/2026-05-12-00-35-20.gh-issue-131178.mX9pQa.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Tests/2026-05-12-00-35-20.gh-issue-131178.mX9pQa.rst diff --git a/Misc/NEWS.d/next/Tests/2026-05-12-00-35-20.gh-issue-131178.mX9pQa.rst b/Misc/NEWS.d/next/Tests/2026-05-12-00-35-20.gh-issue-131178.mX9pQa.rst new file mode 100644 index 00000000000000..52877520443bc6 --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2026-05-12-00-35-20.gh-issue-131178.mX9pQa.rst @@ -0,0 +1,2 @@ +Add subprocess-based tests for the :mod:`mimetypes` command-line interface, +verifying output format and exit codes for known and unknown types. From f6db7b1d808740edd3c05b7ce458615b72aaca31 Mon Sep 17 00:00:00 2001 From: htjworld <116538001+htjworld@users.noreply.github.com> Date: Tue, 12 May 2026 07:07:22 +0900 Subject: [PATCH 5/7] Address review feedback: strengthen test assertions, remove NEWS entry --- Lib/test/test_mimetypes.py | 5 +++-- .../Tests/2026-05-12-00-35-20.gh-issue-131178.mX9pQa.rst | 2 -- 2 files changed, 3 insertions(+), 4 deletions(-) delete mode 100644 Misc/NEWS.d/next/Tests/2026-05-12-00-35-20.gh-issue-131178.mX9pQa.rst diff --git a/Lib/test/test_mimetypes.py b/Lib/test/test_mimetypes.py index 576589a6a5b6dd..edf23217ea529d 100644 --- a/Lib/test/test_mimetypes.py +++ b/Lib/test/test_mimetypes.py @@ -529,7 +529,7 @@ def test_type_lookup_unknown(self): def test_extension_flag(self): rc, stdout, stderr = assert_python_ok('-m', 'mimetypes', '-e', 'image/jpeg') - self.assertIn(b'.jpg', stdout) + self.assertEqual(stdout.strip(), b'.jpg') self.assertEqual(stderr, b'') def test_extension_flag_unknown(self): @@ -558,7 +558,8 @@ def test_multiple_inputs_with_error(self): def test_unknown_flag(self): rc, stdout, stderr = assert_python_failure('-m', 'mimetypes', '--unknown-flag') - self.assertNotEqual(rc, 0) + self.assertEqual(stdout, b'') + self.assertIn(b'error', stderr) if __name__ == "__main__": diff --git a/Misc/NEWS.d/next/Tests/2026-05-12-00-35-20.gh-issue-131178.mX9pQa.rst b/Misc/NEWS.d/next/Tests/2026-05-12-00-35-20.gh-issue-131178.mX9pQa.rst deleted file mode 100644 index 52877520443bc6..00000000000000 --- a/Misc/NEWS.d/next/Tests/2026-05-12-00-35-20.gh-issue-131178.mX9pQa.rst +++ /dev/null @@ -1,2 +0,0 @@ -Add subprocess-based tests for the :mod:`mimetypes` command-line interface, -verifying output format and exit codes for known and unknown types. From edf95dacf96cb56cba8a70dda113e4a116a8b731 Mon Sep 17 00:00:00 2001 From: htjworld Date: Tue, 12 May 2026 17:02:25 +0900 Subject: [PATCH 6/7] gh-131178: fix test_unknown_flag to supply positional arg --- Lib/test/test_mimetypes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_mimetypes.py b/Lib/test/test_mimetypes.py index edf23217ea529d..72355970d0a704 100644 --- a/Lib/test/test_mimetypes.py +++ b/Lib/test/test_mimetypes.py @@ -557,9 +557,9 @@ def test_multiple_inputs_with_error(self): self.assertEqual(stderr, b'') def test_unknown_flag(self): - rc, stdout, stderr = assert_python_failure('-m', 'mimetypes', '--unknown-flag') + rc, stdout, stderr = assert_python_failure('-m', 'mimetypes', '--unknown-flag', 'foo.pdf') self.assertEqual(stdout, b'') - self.assertIn(b'error', stderr) + self.assertIn(b'error: unrecognized arguments: --unknown-flag', stderr) if __name__ == "__main__": From b00896c2c48d8ad1fe10cb69348b50c59ca98ace Mon Sep 17 00:00:00 2001 From: htjworld <116538001+htjworld@users.noreply.github.com> Date: Tue, 12 May 2026 20:54:10 +0900 Subject: [PATCH 7/7] gh-131178: add @force_not_colorized to test_unknown_flag --- Lib/test/test_mimetypes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_mimetypes.py b/Lib/test/test_mimetypes.py index 72355970d0a704..b49f05c66fcfbe 100644 --- a/Lib/test/test_mimetypes.py +++ b/Lib/test/test_mimetypes.py @@ -556,6 +556,7 @@ def test_multiple_inputs_with_error(self): self.assertIn(b'error: media type unknown for foo.unknownext12345', stdout) self.assertEqual(stderr, b'') + @force_not_colorized def test_unknown_flag(self): rc, stdout, stderr = assert_python_failure('-m', 'mimetypes', '--unknown-flag', 'foo.pdf') self.assertEqual(stdout, b'')