diff --git a/Doc/deprecations/pending-removal-in-future.rst b/Doc/deprecations/pending-removal-in-future.rst index 74f98d33a4b61f..b629abaea60e5b 100644 --- a/Doc/deprecations/pending-removal-in-future.rst +++ b/Doc/deprecations/pending-removal-in-future.rst @@ -75,6 +75,10 @@ although there is currently no date scheduled for their removal. * :mod:`mailbox`: Use of StringIO input and text mode is deprecated, use BytesIO and binary mode instead. +* :mod:`mimetypes`: Passing a file path (including path-like objects and bytes + paths) to :func:`~mimetypes.guess_type`. Use + :func:`~mimetypes.guess_file_type` instead. (:gh:`151575`) + * :mod:`os`: Calling :func:`os.register_at_fork` in a multi-threaded process. * :mod:`os.path`: :func:`os.path.commonprefix` is deprecated, use diff --git a/Doc/library/mimetypes.rst b/Doc/library/mimetypes.rst index 5c29fff146eef0..0659d0b3ec9375 100644 --- a/Doc/library/mimetypes.rst +++ b/Doc/library/mimetypes.rst @@ -54,9 +54,9 @@ the information :func:`init` sets up. .. versionchanged:: 3.8 Added support for *url* being a :term:`path-like object`. - .. soft-deprecated:: 3.13 - Passing a file path instead of URL. - Use :func:`guess_file_type` for this. + .. deprecated:: 3.16 + Passing a file path (or path-like object) instead of a URL. + Use :func:`guess_file_type` instead. .. function:: guess_file_type(path, *, strict=True) @@ -262,6 +262,9 @@ than one MIME-type database; it provides an interface similar to the one of the Similar to the :func:`guess_type` function, using the tables stored as part of the object. + .. deprecated:: 3.16 + Passing a file path (or path-like object) instead of a URL. + Use :meth:`guess_file_type` instead. .. method:: MimeTypes.guess_file_type(path, *, strict=True) diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index 8e4c4a1e9b1de0..a8322f6233e02b 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -271,6 +271,13 @@ Deprecated 3.9, now issues a deprecation warning on use. This property is slated for removal in 3.21. Use ``ast.Tuple.elts`` instead. +* :mod:`mimetypes`: + + * Passing a file path (or :term:`path-like object`) to + :func:`mimetypes.guess_type` is now deprecated. + Use :func:`mimetypes.guess_file_type` instead. + (Contributed by Naveen Kumar G in :gh:`151575`.) + .. Add deprecations above alphabetically, not here at the end. .. include:: ../deprecations/pending-removal-in-3.17.rst diff --git a/Lib/mimetypes.py b/Lib/mimetypes.py index 4339ef5a61397d..34f2f46f49d899 100644 --- a/Lib/mimetypes.py +++ b/Lib/mimetypes.py @@ -106,7 +106,11 @@ def add_type(self, type, ext, strict=True): exts.append(ext) def guess_type(self, url, strict=True): - """Guess the type of a file which is either a URL or a path-like object. + """Guess the type of a file based on its URL. + + .. deprecated:: 3.16 + Passing a file path (or path-like object) is deprecated. + Use :meth:`guess_file_type` instead. Return value is a tuple (type, encoding) where type is None if the type can't be guessed (no or unknown suffix) or a string @@ -127,14 +131,21 @@ def guess_type(self, url, strict=True): # Lazy import to improve module import time import os import urllib.parse + import warnings - # TODO: Deprecate accepting file paths (in particular path-like objects). url = os.fspath(url) p = urllib.parse.urlparse(url) if p.scheme and len(p.scheme) > 1: scheme = p.scheme url = p.path else: + # Input has no URL scheme — it is a file path, not a URL. + warnings.warn( + "Passing a file path to guess_type() is deprecated and will be " + "removed in a future version. Use guess_file_type() instead.", + DeprecationWarning, + stacklevel=2, + ) return self.guess_file_type(url, strict=strict) if scheme == 'data': # syntax of data URLs: @@ -753,7 +764,7 @@ def _main(args=None): return results else: for gtype in args.type: - guess, encoding = guess_type(gtype, not args.lenient) + guess, encoding = guess_file_type(gtype, strict=not args.lenient) if guess: results.append(f"type: {guess} encoding: {encoding}") else: diff --git a/Lib/test/test_mimetypes.py b/Lib/test/test_mimetypes.py index 19983fa3fa7628..9fe872324bbdb8 100644 --- a/Lib/test/test_mimetypes.py +++ b/Lib/test/test_mimetypes.py @@ -1,9 +1,12 @@ import io import mimetypes import os +import pathlib import shlex import sys +import unittest import unittest.mock +import warnings from platform import win32_edition from test import support from test.support import cpython_only, force_not_colorized, os_helper, requires_subprocess @@ -232,14 +235,14 @@ def test_init_knownfiles(self): def test_added_types_are_used(self): mimetypes.add_type('testing/default-type', '') - mime_type, _ = mimetypes.guess_type('') + mime_type, _ = mimetypes.guess_file_type('') self.assertEqual(mime_type, 'testing/default-type') - mime_type, _ = mimetypes.guess_type('test.myext') + mime_type, _ = mimetypes.guess_file_type('test.myext') self.assertEqual(mime_type, None) mimetypes.add_type('testing/type', '.myext') - mime_type, _ = mimetypes.guess_type('test.myext') + mime_type, _ = mimetypes.guess_file_type('test.myext') self.assertEqual(mime_type, 'testing/type') def test_add_type_with_undotted_extension_not_supported(self): @@ -389,21 +392,26 @@ def test_filename_with_url_delimiters(self): path = prefix + name with self.subTest(path=path): eq(self.db.guess_file_type(path), gzip_expected) - eq(self.db.guess_type(path), gzip_expected) + with self.assertWarns(DeprecationWarning): + eq(self.db.guess_type(path), gzip_expected) expected = (None, None) if os.name == 'nt' else gzip_expected for prefix in ('//', '\\\\', '//share/', '\\\\share\\'): path = prefix + name with self.subTest(path=path): eq(self.db.guess_file_type(path), expected) - eq(self.db.guess_type(path), expected) + with self.assertWarns(DeprecationWarning): + eq(self.db.guess_type(path), expected) eq(self.db.guess_file_type(r" \"\`;b&b&c |.tar.gz"), gzip_expected) - eq(self.db.guess_type(r" \"\`;b&b&c |.tar.gz"), gzip_expected) + with self.assertWarns(DeprecationWarning): + eq(self.db.guess_type(r" \"\`;b&b&c |.tar.gz"), gzip_expected) eq(self.db.guess_file_type(r'foo/.tar.gz'), (None, 'gzip')) - eq(self.db.guess_type(r'foo/.tar.gz'), (None, 'gzip')) + with self.assertWarns(DeprecationWarning): + eq(self.db.guess_type(r'foo/.tar.gz'), (None, 'gzip')) expected = (None, 'gzip') if os.name == 'nt' else gzip_expected eq(self.db.guess_file_type(r'foo\.tar.gz'), expected) - eq(self.db.guess_type(r'foo\.tar.gz'), expected) + with self.assertWarns(DeprecationWarning): + eq(self.db.guess_type(r'foo\.tar.gz'), expected) eq(self.db.guess_type(r'scheme:foo\.tar.gz'), gzip_expected) def test_url(self): @@ -461,16 +469,20 @@ def test_path_like_ob(self): expected = self.db.guess_file_type(filename) self.assertEqual(self.db.guess_file_type(filepath), expected) - self.assertEqual(self.db.guess_type(filepath), expected) + with self.assertWarns(DeprecationWarning): + self.assertEqual(self.db.guess_type(filepath), expected) self.assertEqual(self.db.guess_file_type( filepath_with_abs_dir), expected) - self.assertEqual(self.db.guess_type( - filepath_with_abs_dir), expected) + with self.assertWarns(DeprecationWarning): + self.assertEqual(self.db.guess_type( + filepath_with_abs_dir), expected) self.assertEqual(self.db.guess_file_type(filepath_relative), expected) - self.assertEqual(self.db.guess_type(filepath_relative), expected) + with self.assertWarns(DeprecationWarning): + self.assertEqual(self.db.guess_type(filepath_relative), expected) self.assertEqual(self.db.guess_file_type(path_dir), (None, None)) - self.assertEqual(self.db.guess_type(path_dir), (None, None)) + with self.assertWarns(DeprecationWarning): + self.assertEqual(self.db.guess_type(path_dir), (None, None)) def test_bytes_path(self): self.assertEqual(self.db.guess_file_type(b'foo.html'), @@ -489,6 +501,94 @@ def test_keywords_args_api(self): type='image/jpeg', strict=True), ['.jpg', '.jpe', '.jpeg']) +class GuessTypeDeprecationTestCase(unittest.TestCase): + """Tests that guess_type() emits DeprecationWarning for file path inputs.""" + + def setUp(self): + self.db = mimetypes.MimeTypes() + + # --- Module-level function tests --- + + def test_module_plain_string_path_warns(self): + """Module-level guess_type() warns for a plain string with no URL scheme.""" + with self.assertWarns(DeprecationWarning) as cm: + mimetypes.guess_type("file.txt") + self.assertIn("guess_file_type", str(cm.warning)) + self.assertIn("deprecated", str(cm.warning).lower()) + + def test_module_pathlike_warns(self): + """Module-level guess_type() warns for a path-like object.""" + with self.assertWarns(DeprecationWarning): + mimetypes.guess_type(pathlib.Path("file.txt")) + + def test_module_bytes_path_warns(self): + """Module-level guess_type() warns for a bytes path.""" + with self.assertWarns(DeprecationWarning): + mimetypes.guess_type(b"file.txt") + + def test_module_url_with_scheme_no_warning(self): + """Module-level guess_type() does NOT warn for a proper URL.""" + with warnings.catch_warnings(): + warnings.simplefilter("error", DeprecationWarning) + # Should not raise -- http:// is a valid multi-char scheme. + mimetypes.guess_type("http://example.com/file.html") + + def test_module_data_url_no_warning(self): + """Module-level guess_type() does NOT warn for data: URLs.""" + with warnings.catch_warnings(): + warnings.simplefilter("error", DeprecationWarning) + mimetypes.guess_type("data:text/plain,hello") + + # --- MimeTypes class method tests --- + + def test_method_plain_string_path_warns(self): + """MimeTypes.guess_type() warns for a plain string with no URL scheme.""" + with self.assertWarns(DeprecationWarning) as cm: + self.db.guess_type("file.html") + self.assertIn("guess_file_type", str(cm.warning)) + + def test_method_pathlike_warns(self): + """MimeTypes.guess_type() warns for a path-like object.""" + with self.assertWarns(DeprecationWarning): + self.db.guess_type(pathlib.Path("file.html")) + + def test_method_bytes_path_warns(self): + """MimeTypes.guess_type() warns for a bytes path.""" + with self.assertWarns(DeprecationWarning): + self.db.guess_type(b"file.html") + + def test_method_url_with_scheme_no_warning(self): + """MimeTypes.guess_type() does NOT warn for a proper URL.""" + with warnings.catch_warnings(): + warnings.simplefilter("error", DeprecationWarning) + self.db.guess_type("http://example.com/file.html") + + def test_method_ftp_url_no_warning(self): + """MimeTypes.guess_type() does NOT warn for an ftp: URL.""" + with warnings.catch_warnings(): + warnings.simplefilter("error", DeprecationWarning) + self.db.guess_type("ftp://example.com/file.tar.gz") + + def test_result_unchanged(self): + """guess_type() with a file path still returns the correct MIME type.""" + with self.assertWarns(DeprecationWarning): + result = mimetypes.guess_type("file.html") + expected = mimetypes.guess_file_type("file.html") + self.assertEqual(result, expected) + + def test_result_unchanged_pathlike(self): + """guess_type() with a PathLike still returns the correct MIME type.""" + with self.assertWarns(DeprecationWarning): + result = self.db.guess_type(pathlib.Path("file.tar.gz")) + expected = self.db.guess_file_type(pathlib.Path("file.tar.gz")) + self.assertEqual(result, expected) + + def test_os_helper_fakepath_warns(self): + """guess_type() warns for os_helper.FakePath (a path-like object).""" + with self.assertWarns(DeprecationWarning): + self.db.guess_type(os_helper.FakePath("file.tar.gz")) + + @unittest.skipUnless(sys.platform.startswith("win"), "Windows only") class Win32MimeTypesTestCase(unittest.TestCase): def setUp(self): @@ -510,9 +610,9 @@ def test_registry_parsing(self): # Windows registry is undocumented AFAIK. # Use file types that should *always* exist: eq = self.assertEqual - eq(self.db.guess_type("foo.txt"), ("text/plain", None)) - eq(self.db.guess_type("image.jpg"), ("image/jpeg", None)) - eq(self.db.guess_type("image.png"), ("image/png", None)) + eq(self.db.guess_file_type("foo.txt"), ("text/plain", None)) + eq(self.db.guess_file_type("image.jpg"), ("image/jpeg", None)) + eq(self.db.guess_file_type("image.png"), ("image/png", None)) @unittest.skipIf(not hasattr(_winapi, "_mimetypes_read_windows_registry"), "read_windows_registry accelerator unavailable") diff --git a/Misc/NEWS.d/next/Library/2026-06-17-09-45-00.gh-issue-151575.mimetype.rst b/Misc/NEWS.d/next/Library/2026-06-17-09-45-00.gh-issue-151575.mimetype.rst new file mode 100644 index 00000000000000..f34b61e152a6fc --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-17-09-45-00.gh-issue-151575.mimetype.rst @@ -0,0 +1 @@ +Emit DeprecationWarning when a file path is passed to :func:`mimetypes.guess_type`. Use :func:`mimetypes.guess_file_type` instead.