From 1788ed3741beeb36406dc6ba1e552836748f34ed Mon Sep 17 00:00:00 2001 From: NaveenKumarG-dev Date: Wed, 17 Jun 2026 10:21:10 +0530 Subject: [PATCH 1/4] gh-151575: Emit DeprecationWarning in mimetypes.guess_type() for file paths --- .../pending-removal-in-future.rst | 4 + Doc/library/mimetypes.rst | 9 +- Doc/whatsnew/3.16.rst | 7 ++ Lib/mimetypes.py | 15 ++- Lib/test/test_mimetypes.py | 91 +++++++++++++++++++ ...6-17-09-45-00.gh-issue-151575.mimetype.rst | 1 + 6 files changed, 122 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-06-17-09-45-00.gh-issue-151575.mimetype.rst 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..c2c9245efaefa7 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: diff --git a/Lib/test/test_mimetypes.py b/Lib/test/test_mimetypes.py index 19983fa3fa7628..c2e5c32203d8f8 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 @@ -489,6 +492,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): 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. From 9276f2c2c2b96ceb3ee22ce9390f6b816b4600b6 Mon Sep 17 00:00:00 2001 From: NaveenKumarG-dev Date: Wed, 17 Jun 2026 20:26:04 +0530 Subject: [PATCH 2/4] feat: introduce MimeTypes class and deprecate file path support in guess_type with new guess_file_type method --- Lib/mimetypes.py | 2 +- Lib/test/test_mimetypes.py | 41 +++++++++++++++++++++++--------------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/Lib/mimetypes.py b/Lib/mimetypes.py index c2c9245efaefa7..34f2f46f49d899 100644 --- a/Lib/mimetypes.py +++ b/Lib/mimetypes.py @@ -764,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 c2e5c32203d8f8..9fe872324bbdb8 100644 --- a/Lib/test/test_mimetypes.py +++ b/Lib/test/test_mimetypes.py @@ -235,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): @@ -392,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): @@ -464,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'), @@ -601,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") From 1aba67d611c76a8bdd70d1bac2255a83463d0a54 Mon Sep 17 00:00:00 2001 From: NaveenKumarG-dev Date: Sun, 28 Jun 2026 14:10:22 +0530 Subject: [PATCH 3/4] feat: add guess_file_type to mimetypes module to support path-based MIME lookups --- Doc/library/mimetypes.rst | 6 +- Lib/mimetypes.py | 5 -- Lib/test/test_mimetypes.py | 85 ------------------- ...6-17-09-45-00.gh-issue-151575.mimetype.rst | 2 +- 4 files changed, 5 insertions(+), 93 deletions(-) diff --git a/Doc/library/mimetypes.rst b/Doc/library/mimetypes.rst index 0659d0b3ec9375..71964c1550e687 100644 --- a/Doc/library/mimetypes.rst +++ b/Doc/library/mimetypes.rst @@ -28,8 +28,8 @@ the information :func:`init` sets up. .. index:: pair: MIME; headers - Guess the type of a file based on its filename, path or URL, given by *url*. - URL can be a string or a :term:`path-like object`. + Guess the type of a file based on its URL, given by *url*. + URL can be a string. The return value is a tuple ``(type, encoding)`` where *type* is ``None`` if the type can't be guessed (missing or unknown suffix) or a string of the form @@ -57,6 +57,7 @@ the information :func:`init` sets up. .. deprecated:: 3.16 Passing a file path (or path-like object) instead of a URL. Use :func:`guess_file_type` instead. + Soft-deprecated since Python 3.13, scheduled for removal in Python 3.21. .. function:: guess_file_type(path, *, strict=True) @@ -265,6 +266,7 @@ than one MIME-type database; it provides an interface similar to the one of the .. deprecated:: 3.16 Passing a file path (or path-like object) instead of a URL. Use :meth:`guess_file_type` instead. + Soft-deprecated since Python 3.13, scheduled for removal in Python 3.21. .. method:: MimeTypes.guess_file_type(path, *, strict=True) diff --git a/Lib/mimetypes.py b/Lib/mimetypes.py index 34f2f46f49d899..66c8ea1127b404 100644 --- a/Lib/mimetypes.py +++ b/Lib/mimetypes.py @@ -108,10 +108,6 @@ def add_type(self, type, ext, strict=True): def guess_type(self, url, strict=True): """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 of the form type/subtype, usable for a MIME Content-type @@ -139,7 +135,6 @@ def guess_type(self, url, strict=True): 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.", diff --git a/Lib/test/test_mimetypes.py b/Lib/test/test_mimetypes.py index 9fe872324bbdb8..3ded5a6be8bca6 100644 --- a/Lib/test/test_mimetypes.py +++ b/Lib/test/test_mimetypes.py @@ -501,92 +501,7 @@ 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") 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 index f34b61e152a6fc..8bb01faf719903 100644 --- 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 @@ -1 +1 @@ -Emit DeprecationWarning when a file path is passed to :func:`mimetypes.guess_type`. Use :func:`mimetypes.guess_file_type` instead. +Deprecate passing a file path to :func:`mimetypes.guess_type`. Use :func:`mimetypes.guess_file_type` instead. From 2f7b8ed43893b209e8d8dfa1d85231f0723316b1 Mon Sep 17 00:00:00 2001 From: NaveenKumarG-dev Date: Sun, 28 Jun 2026 14:16:20 +0530 Subject: [PATCH 4/4] test: add test suite for mimetypes module --- 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 3ded5a6be8bca6..5235ee971e0fbc 100644 --- a/Lib/test/test_mimetypes.py +++ b/Lib/test/test_mimetypes.py @@ -1,12 +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