Skip to content
4 changes: 4 additions & 0 deletions Doc/deprecations/pending-removal-in-future.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 6 additions & 3 deletions Doc/library/mimetypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)

Expand Down
7 changes: 7 additions & 0 deletions Doc/whatsnew/3.16.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 14 additions & 3 deletions Lib/mimetypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
132 changes: 116 additions & 16 deletions Lib/test/test_mimetypes.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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'),
Expand All @@ -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):
Expand All @@ -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")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Emit DeprecationWarning when a file path is passed to :func:`mimetypes.guess_type`. Use :func:`mimetypes.guess_file_type` instead.
Loading