From 2e6a0e5714cc6893ef427f6e9001b4174d607a8c Mon Sep 17 00:00:00 2001 From: Daniel Watkins Date: Wed, 26 Jul 2017 10:41:05 -0400 Subject: [PATCH 01/12] Add a test for mimetypes.add_type --- Lib/test/test_mimetypes.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Lib/test/test_mimetypes.py b/Lib/test/test_mimetypes.py index 7761c3fe867a7e..1aa505e7d4b66a 100644 --- a/Lib/test/test_mimetypes.py +++ b/Lib/test/test_mimetypes.py @@ -161,6 +161,13 @@ def test_keywords_args_api(self): self.assertEqual(self.db.guess_extension( type='image/jpg', strict=False), '.jpg') + def test_added_types_are_used(self): + mime_type, _ = mimetypes.guess_type('test.myext') + self.assertEqual(None, mime_type) + mimetypes.add_type('testing/type', '.myext') + mime_type, _ = mimetypes.guess_type('test.myext') + self.assertEqual('testing/type', mime_type) + @unittest.skipUnless(sys.platform.startswith("win"), "Windows only") class Win32MimeTypesTestCase(unittest.TestCase): From 1485be9efe64f840db93961b487a2ea1440434de Mon Sep 17 00:00:00 2001 From: Daniel Watkins Date: Wed, 26 Jul 2017 11:01:48 -0400 Subject: [PATCH 02/12] bpo-31040: Reject undotted extensions in mimetypes.add_type Extensions that don't start with a dot will never be found by methods/functions that act on the registry, so we should stop users from mistakenly adding them. The one exception is the empty string extension. This could be in use to detect absent extensions, so instead of raising a ValueError we emit a warning. --- Lib/mimetypes.py | 9 +++++++++ Lib/test/test_mimetypes.py | 12 ++++++++++++ .../Library/2019-09-10-09-28-52.bpo-31040.VyAJS9.rst | 1 + 3 files changed, 22 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2019-09-10-09-28-52.bpo-31040.VyAJS9.rst diff --git a/Lib/mimetypes.py b/Lib/mimetypes.py index f38005c9d29598..3afbff2cefaa49 100644 --- a/Lib/mimetypes.py +++ b/Lib/mimetypes.py @@ -27,6 +27,7 @@ import sys import posixpath import urllib.parse +import warnings try: import winreg as _winreg except ImportError: @@ -88,7 +89,15 @@ def add_type(self, type, ext, strict=True): If strict is true, information will be added to list of standard types, else to the list of non-standard types. + + Non-empty extensions that don't start with a '.' are not + valid, so a ValueError will be raised if they are + specified. """ + if not ext: + warnings.warn('Empty extension specified') + elif not ext.startswith('.'): + raise ValueError("Extensions should start with a '.'") self.types_map[strict][ext] = type exts = self.types_map_inv[strict].setdefault(type, []) if ext not in exts: diff --git a/Lib/test/test_mimetypes.py b/Lib/test/test_mimetypes.py index 1aa505e7d4b66a..4f4c9001ba46b1 100644 --- a/Lib/test/test_mimetypes.py +++ b/Lib/test/test_mimetypes.py @@ -4,6 +4,7 @@ import pathlib import sys import unittest +import warnings from test import support from platform import win32_edition @@ -168,6 +169,17 @@ def test_added_types_are_used(self): mime_type, _ = mimetypes.guess_type('test.myext') self.assertEqual('testing/type', mime_type) + def test_add_type_with_undotted_extension_raises_exception(self): + with self.assertRaises(ValueError): + mimetypes.add_type('testing/type', 'undotted') + + def test_add_type_with_empty_extension_emits_warning(self): + with warnings.catch_warnings(record=True) as wlog: + mimetypes.add_type('testing/type', '') + self.assertEqual(1, len(wlog)) + warning = wlog[0] + self.assertEqual('Empty extension specified', str(warning.message)) + @unittest.skipUnless(sys.platform.startswith("win"), "Windows only") class Win32MimeTypesTestCase(unittest.TestCase): diff --git a/Misc/NEWS.d/next/Library/2019-09-10-09-28-52.bpo-31040.VyAJS9.rst b/Misc/NEWS.d/next/Library/2019-09-10-09-28-52.bpo-31040.VyAJS9.rst new file mode 100644 index 00000000000000..7843f6f5a2f0b5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-09-10-09-28-52.bpo-31040.VyAJS9.rst @@ -0,0 +1 @@ +Reject undotted extensions in mimetypes.add_type. From f0c1bf701e559aeee1bcd34a17087b609bc7facd Mon Sep 17 00:00:00 2001 From: Oleg Iarygin Date: Sat, 15 Apr 2023 20:25:44 +0400 Subject: [PATCH 03/12] Address the review --- Lib/mimetypes.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Lib/mimetypes.py b/Lib/mimetypes.py index 3afbff2cefaa49..f15ef21c4846bf 100644 --- a/Lib/mimetypes.py +++ b/Lib/mimetypes.py @@ -94,10 +94,8 @@ def add_type(self, type, ext, strict=True): valid, so a ValueError will be raised if they are specified. """ - if not ext: - warnings.warn('Empty extension specified') - elif not ext.startswith('.'): - raise ValueError("Extensions should start with a '.'") + if ext and not ext.startswith('.'): + raise ValueError("Extensions should start with a '.' or be empty") self.types_map[strict][ext] = type exts = self.types_map_inv[strict].setdefault(type, []) if ext not in exts: From ef5cf14e235c00e67e6f7bf43e62a4f3dbe7d713 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 16 Nov 2024 14:20:44 +0200 Subject: [PATCH 04/12] Assert output first, expected result second; link to method in news --- Lib/test/test_mimetypes.py | 8 ++++---- .../next/Library/2019-09-10-09-28-52.bpo-31040.VyAJS9.rst | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_mimetypes.py b/Lib/test/test_mimetypes.py index fbf704f60212ef..3a59924dda399b 100644 --- a/Lib/test/test_mimetypes.py +++ b/Lib/test/test_mimetypes.py @@ -308,10 +308,10 @@ def test_keywords_args_api(self): def test_added_types_are_used(self): mime_type, _ = mimetypes.guess_type('test.myext') - self.assertEqual(None, mime_type) + self.assertEqual(mime_type, None) mimetypes.add_type('testing/type', '.myext') mime_type, _ = mimetypes.guess_type('test.myext') - self.assertEqual('testing/type', mime_type) + self.assertEqual(mime_type, 'testing/type') def test_add_type_with_undotted_extension_raises_exception(self): with self.assertRaises(ValueError): @@ -320,9 +320,9 @@ def test_add_type_with_undotted_extension_raises_exception(self): def test_add_type_with_empty_extension_emits_warning(self): with warnings.catch_warnings(record=True) as wlog: mimetypes.add_type('testing/type', '') - self.assertEqual(1, len(wlog)) + self.assertEqual(len(wlog), 1) warning = wlog[0] - self.assertEqual('Empty extension specified', str(warning.message)) + self.assertEqual(str(warning.message), 'Empty extension specified') @unittest.skipUnless(sys.platform.startswith("win"), "Windows only") diff --git a/Misc/NEWS.d/next/Library/2019-09-10-09-28-52.bpo-31040.VyAJS9.rst b/Misc/NEWS.d/next/Library/2019-09-10-09-28-52.bpo-31040.VyAJS9.rst index 7843f6f5a2f0b5..aa42820ec13630 100644 --- a/Misc/NEWS.d/next/Library/2019-09-10-09-28-52.bpo-31040.VyAJS9.rst +++ b/Misc/NEWS.d/next/Library/2019-09-10-09-28-52.bpo-31040.VyAJS9.rst @@ -1 +1 @@ -Reject undotted extensions in mimetypes.add_type. +Reject undotted extensions in :meth:`MimeTypes.add_type`. From bac8d5d69bb963baa25fd6dc3276cd7a525168ac Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 16 Nov 2024 14:29:47 +0200 Subject: [PATCH 05/12] Assert adding MIME type with empty extension emits no warning --- Lib/test/test_mimetypes.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_mimetypes.py b/Lib/test/test_mimetypes.py index 3a59924dda399b..51dece266a588e 100644 --- a/Lib/test/test_mimetypes.py +++ b/Lib/test/test_mimetypes.py @@ -317,12 +317,10 @@ def test_add_type_with_undotted_extension_raises_exception(self): with self.assertRaises(ValueError): mimetypes.add_type('testing/type', 'undotted') - def test_add_type_with_empty_extension_emits_warning(self): + def test_add_type_with_empty_extension_emits_no_warning(self): with warnings.catch_warnings(record=True) as wlog: mimetypes.add_type('testing/type', '') - self.assertEqual(len(wlog), 1) - warning = wlog[0] - self.assertEqual(str(warning.message), 'Empty extension specified') + self.assertEqual(len(wlog), 0) @unittest.skipUnless(sys.platform.startswith("win"), "Windows only") From e1773987eee9a8a356f60ad6fe62e52940777392 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 16 Nov 2024 14:30:43 +0200 Subject: [PATCH 06/12] Assert exception message when adding MIME type with undotted extension --- Lib/test/test_mimetypes.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_mimetypes.py b/Lib/test/test_mimetypes.py index 51dece266a588e..d504df2cbabd59 100644 --- a/Lib/test/test_mimetypes.py +++ b/Lib/test/test_mimetypes.py @@ -314,7 +314,9 @@ def test_added_types_are_used(self): self.assertEqual(mime_type, 'testing/type') def test_add_type_with_undotted_extension_raises_exception(self): - with self.assertRaises(ValueError): + with self.assertRaisesRegex( + ValueError, "Extensions should start with a '.' or be empty" + ): mimetypes.add_type('testing/type', 'undotted') def test_add_type_with_empty_extension_emits_no_warning(self): From 094f69bb5644ebeb706dd2933e59f5fee990568d Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 16 Nov 2024 14:35:31 +0200 Subject: [PATCH 07/12] Remove unused import --- Lib/mimetypes.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/mimetypes.py b/Lib/mimetypes.py index bf442042d8e16d..c236d4dc4fb4f8 100644 --- a/Lib/mimetypes.py +++ b/Lib/mimetypes.py @@ -27,7 +27,6 @@ import sys import posixpath import urllib.parse -import warnings try: from _winapi import _mimetypes_read_windows_registry From cbc542107359dd3228fbd162030875f30ea8cf83 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 16 Nov 2024 14:43:16 +0200 Subject: [PATCH 08/12] Fix Sphinx reference; replace bpo with gh-issue --- .../NEWS.d/next/Library/2019-09-10-09-28-52.bpo-31040.VyAJS9.rst | 1 - Misc/NEWS.d/next/Library/2019-09-10-09-28-52.gh-75223.VyAJS9.rst | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 Misc/NEWS.d/next/Library/2019-09-10-09-28-52.bpo-31040.VyAJS9.rst create mode 100644 Misc/NEWS.d/next/Library/2019-09-10-09-28-52.gh-75223.VyAJS9.rst diff --git a/Misc/NEWS.d/next/Library/2019-09-10-09-28-52.bpo-31040.VyAJS9.rst b/Misc/NEWS.d/next/Library/2019-09-10-09-28-52.bpo-31040.VyAJS9.rst deleted file mode 100644 index aa42820ec13630..00000000000000 --- a/Misc/NEWS.d/next/Library/2019-09-10-09-28-52.bpo-31040.VyAJS9.rst +++ /dev/null @@ -1 +0,0 @@ -Reject undotted extensions in :meth:`MimeTypes.add_type`. diff --git a/Misc/NEWS.d/next/Library/2019-09-10-09-28-52.gh-75223.VyAJS9.rst b/Misc/NEWS.d/next/Library/2019-09-10-09-28-52.gh-75223.VyAJS9.rst new file mode 100644 index 00000000000000..bafcbe1b714b72 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-09-10-09-28-52.gh-75223.VyAJS9.rst @@ -0,0 +1 @@ +Reject undotted extensions in :meth:`mimetypes.MimeTypes.add_type`. From 41d4e8902bed91a26a02103cbd39f8b13b98503f Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 16 Nov 2024 15:09:19 +0200 Subject: [PATCH 09/12] Replace with more useful test case --- Lib/test/test_mimetypes.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_mimetypes.py b/Lib/test/test_mimetypes.py index d504df2cbabd59..9bb00c106f4b5c 100644 --- a/Lib/test/test_mimetypes.py +++ b/Lib/test/test_mimetypes.py @@ -307,8 +307,13 @@ def test_keywords_args_api(self): type='image/jpg', strict=False), '.jpg') def test_added_types_are_used(self): + mimetypes.add_type('testing/default-type', '') + mime_type, _ = mimetypes.guess_type('') + self.assertEqual(mime_type, 'testing/default-type') + mime_type, _ = mimetypes.guess_type('test.myext') self.assertEqual(mime_type, None) + mimetypes.add_type('testing/type', '.myext') mime_type, _ = mimetypes.guess_type('test.myext') self.assertEqual(mime_type, 'testing/type') @@ -319,11 +324,6 @@ def test_add_type_with_undotted_extension_raises_exception(self): ): mimetypes.add_type('testing/type', 'undotted') - def test_add_type_with_empty_extension_emits_no_warning(self): - with warnings.catch_warnings(record=True) as wlog: - mimetypes.add_type('testing/type', '') - self.assertEqual(len(wlog), 0) - @unittest.skipUnless(sys.platform.startswith("win"), "Windows only") class Win32MimeTypesTestCase(unittest.TestCase): From 50179cf3c0a3c523cb8fe958494e22f98fbb37da Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 16 Nov 2024 15:12:14 +0200 Subject: [PATCH 10/12] Improve wording: avoid triple negative --- Lib/mimetypes.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/mimetypes.py b/Lib/mimetypes.py index c236d4dc4fb4f8..6c7d343135088e 100644 --- a/Lib/mimetypes.py +++ b/Lib/mimetypes.py @@ -95,9 +95,8 @@ def add_type(self, type, ext, strict=True): list of standard types, else to the list of non-standard types. - Non-empty extensions that don't start with a '.' are not - valid, so a ValueError will be raised if they are - specified. + Valid extensions are empty or start with a '.'. + Raise a ValueError for invalid extensions. """ if ext and not ext.startswith('.'): raise ValueError("Extensions should start with a '.' or be empty") From 352811bbeb9c0223d592f1f57814a80d160f1055 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 16 Nov 2024 15:14:37 +0200 Subject: [PATCH 11/12] Fix news filename --- ...3.VyAJS9.rst => 2019-09-10-09-28-52.gh-issue-75223.VyAJS9.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Misc/NEWS.d/next/Library/{2019-09-10-09-28-52.gh-75223.VyAJS9.rst => 2019-09-10-09-28-52.gh-issue-75223.VyAJS9.rst} (100%) diff --git a/Misc/NEWS.d/next/Library/2019-09-10-09-28-52.gh-75223.VyAJS9.rst b/Misc/NEWS.d/next/Library/2019-09-10-09-28-52.gh-issue-75223.VyAJS9.rst similarity index 100% rename from Misc/NEWS.d/next/Library/2019-09-10-09-28-52.gh-75223.VyAJS9.rst rename to Misc/NEWS.d/next/Library/2019-09-10-09-28-52.gh-issue-75223.VyAJS9.rst From e3b2ee9381f2ed1a612d89a32e67bf57cc62f4ec Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Wed, 8 Jan 2025 16:32:35 +0200 Subject: [PATCH 12/12] Deprecate undotted extensions in mimetypes.add_type --- Doc/deprecations/pending-removal-in-3.16.rst | 8 ++++++++ Doc/library/mimetypes.rst | 7 ++++++- Doc/whatsnew/3.14.rst | 7 +++++++ Lib/mimetypes.py | 13 +++++++++++-- Lib/test/test_mimetypes.py | 9 +++------ .../2019-09-10-09-28-52.gh-issue-75223.VyAJS9.rst | 3 ++- 6 files changed, 37 insertions(+), 10 deletions(-) diff --git a/Doc/deprecations/pending-removal-in-3.16.rst b/Doc/deprecations/pending-removal-in-3.16.rst index d093deb648baf7..536cb793f5db26 100644 --- a/Doc/deprecations/pending-removal-in-3.16.rst +++ b/Doc/deprecations/pending-removal-in-3.16.rst @@ -57,6 +57,14 @@ Pending removal in Python 3.16 In the rare case that you need the bitwise inversion of the underlying integer, convert to ``int`` explicitly (``~int(x)``). +* :mod:`mimetypes`: + + * Valid extensions start with a '.' or are empty for + :meth:`mimetypes.MimeTypes.add_type`. + Undotted extensions are deprecated and will + raise a :exc:`ValueError` in Python 3.16. + (Contributed by Hugo van Kemenade in :gh:`75223`.) + * :mod:`shutil`: * The :class:`!ExecError` exception diff --git a/Doc/library/mimetypes.rst b/Doc/library/mimetypes.rst index 8ad4850584a7e1..c8b006e0a9f56a 100644 --- a/Doc/library/mimetypes.rst +++ b/Doc/library/mimetypes.rst @@ -299,9 +299,14 @@ than one MIME-type database; it provides an interface similar to the one of the .. method:: MimeTypes.add_type(type, ext, strict=True) - Add a mapping from the MIME type *type* to the extension *ext*. When the + Add a mapping from the MIME type *type* to the extension *ext*. + Valid extensions start with a '.' or are empty. When the extension is already known, the new type will replace the old one. When the type is already known the extension will be added to the list of known extensions. When *strict* is ``True`` (the default), the mapping will be added to the official MIME types, otherwise to the non-standard ones. + + .. deprecated-removed:: 3.14 3.16 + Invalid, undotted extensions will raise a + :exc:`ValueError` in Python 3.16. diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 16851b4e63ea2c..2a0158d3eb44ec 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -774,6 +774,13 @@ Deprecated or *sequence* as keyword arguments is now deprecated. (Contributed by Kirill Podoprigora in :gh:`121676`.) +* :mod:`mimetypes`: + Valid extensions start with a '.' or are empty for + :meth:`mimetypes.MimeTypes.add_type`. + Undotted extensions are deprecated and will + raise a :exc:`ValueError` in Python 3.16. + (Contributed by Hugo van Kemenade in :gh:`75223`.) + * :mod:`os`: :term:`Soft deprecate ` :func:`os.popen` and :func:`os.spawn* ` functions. They should no longer be used to diff --git a/Lib/mimetypes.py b/Lib/mimetypes.py index 574f86ef276f5d..f12c7684733c1f 100644 --- a/Lib/mimetypes.py +++ b/Lib/mimetypes.py @@ -91,10 +91,19 @@ def add_type(self, type, ext, strict=True): types. Valid extensions are empty or start with a '.'. - Raise a ValueError for invalid extensions. + The use of invalid extensions is deprecated and + will raise a ValueError in Python 3.16. """ if ext and not ext.startswith('.'): - raise ValueError("Extensions should start with a '.' or be empty") + from warnings import _deprecated + + _deprecated( + "Undotted extensions", + "Using undotted extensions is deprecated and " + "will raise a ValueError in Python {remove}", + remove=(3, 16), + ) + self.types_map[strict][ext] = type exts = self.types_map_inv[strict].setdefault(type, []) if ext not in exts: diff --git a/Lib/test/test_mimetypes.py b/Lib/test/test_mimetypes.py index 5d480c9eb9217c..aced84fd1e3771 100644 --- a/Lib/test/test_mimetypes.py +++ b/Lib/test/test_mimetypes.py @@ -3,7 +3,6 @@ import os import sys import unittest.mock -import warnings from test import support from test.support import os_helper @@ -338,11 +337,9 @@ def test_added_types_are_used(self): mime_type, _ = mimetypes.guess_type('test.myext') self.assertEqual(mime_type, 'testing/type') - def test_add_type_with_undotted_extension_raises_exception(self): - with self.assertRaisesRegex( - ValueError, "Extensions should start with a '.' or be empty" - ): - mimetypes.add_type('testing/type', 'undotted') + def test_add_type_with_undotted_extension_deprecated(self): + with self.assertWarns(DeprecationWarning): + mimetypes.add_type("testing/type", "undotted") @unittest.skipUnless(sys.platform.startswith("win"), "Windows only") diff --git a/Misc/NEWS.d/next/Library/2019-09-10-09-28-52.gh-issue-75223.VyAJS9.rst b/Misc/NEWS.d/next/Library/2019-09-10-09-28-52.gh-issue-75223.VyAJS9.rst index bafcbe1b714b72..d3c8d1b747e5c9 100644 --- a/Misc/NEWS.d/next/Library/2019-09-10-09-28-52.gh-issue-75223.VyAJS9.rst +++ b/Misc/NEWS.d/next/Library/2019-09-10-09-28-52.gh-issue-75223.VyAJS9.rst @@ -1 +1,2 @@ -Reject undotted extensions in :meth:`mimetypes.MimeTypes.add_type`. +Deprecate undotted extensions in :meth:`mimetypes.MimeTypes.add_type`. +Patch by Hugo van Kemenade.